Merge branch 'master' into addComplexScript
This commit is contained in:
commit
9f96fd097b
179 changed files with 11314 additions and 4583 deletions
4
.prettierrc.json
Normal file
4
.prettierrc.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
19
.travis.yml
19
.travis.yml
|
@ -1,16 +1,27 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
before_install:
|
||||||
|
- if [ $TEST_SUITE = "integration" ]; then
|
||||||
|
docker pull junderw/bitcoinjs-regtest-server &&
|
||||||
|
docker run -d -p 127.0.0.1:8080:8080 junderw/bitcoinjs-regtest-server &&
|
||||||
|
docker ps -a;
|
||||||
|
fi
|
||||||
node_js:
|
node_js:
|
||||||
|
- "8"
|
||||||
- "lts/*"
|
- "lts/*"
|
||||||
- "9"
|
|
||||||
- "10"
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- node_js: "lts/*"
|
- node_js: "lts/*"
|
||||||
env: TEST_SUITE=standard
|
env: TEST_SUITE=format:ci
|
||||||
|
- node_js: "lts/*"
|
||||||
|
env: TEST_SUITE=gitdiff:ci
|
||||||
|
- node_js: "lts/*"
|
||||||
|
env: TEST_SUITE=lint
|
||||||
- node_js: "lts/*"
|
- node_js: "lts/*"
|
||||||
env: TEST_SUITE=coverage
|
env: TEST_SUITE=coverage
|
||||||
env:
|
env:
|
||||||
- TEST_SUITE=unit
|
- TEST_SUITE=unit
|
||||||
- TEST_SUITE=integration
|
- TEST_SUITE=integration APIURL=http://127.0.0.1:8080/1
|
||||||
script: npm run-script $TEST_SUITE
|
script: npm run-script $TEST_SUITE
|
||||||
|
|
41
CHANGELOG.md
41
CHANGELOG.md
|
@ -1,3 +1,40 @@
|
||||||
|
# 5.0.0
|
||||||
|
__added__
|
||||||
|
- TypeScript support (#1319)
|
||||||
|
- `Block.prototype.checkTxRoots` will check the merkleRoot and witnessCommit if it exists against the transactions array. (e52abec) (0426c66)
|
||||||
|
|
||||||
|
__changed__
|
||||||
|
- `Transaction.prototype.getHash` now has `forWitness?: boolean` which when true returns the hash for wtxid (a652d04)
|
||||||
|
- `Block.calculateMerkleRoot` now has `forWitness?: boolean` which when true returns the witness commit (a652d04)
|
||||||
|
|
||||||
|
__removed__
|
||||||
|
- `Block.prototype.checkMerkleRoot` was removed, please use `checkTxRoots` (0426c66)
|
||||||
|
|
||||||
|
# 4.0.5
|
||||||
|
__fixed__
|
||||||
|
- Fixed bug where Angular apps break due to lack of crypto at build time. Reverted #1373 and added (6bead5d).
|
||||||
|
|
||||||
|
# 4.0.4
|
||||||
|
__fixed__
|
||||||
|
- Fixed bug where Electron v4 breaks due to lack of `'rmd160'` alias for ripemd160 hash. (#1373)
|
||||||
|
|
||||||
|
# 4.0.3
|
||||||
|
__fixed__
|
||||||
|
- Fixed `TransactionBuilder` to require that the Transaction has outputs before signing (#1151)
|
||||||
|
- Fixed `payments.p2sh`, which now takes the network from the redeem attribute if one is not given in the object argument (#1232)
|
||||||
|
- Fixed `Block.calculateTarget` to allow for exponents up to 29 (#1285)
|
||||||
|
- Fixed some low priority rarely occurring bugs with multisig payments and `TransactionBuilder` multisig processing (#1307)
|
||||||
|
|
||||||
|
__added__
|
||||||
|
- Regtest network object to `networks` (#1261)
|
||||||
|
|
||||||
|
# 4.0.2
|
||||||
|
__fixed__
|
||||||
|
- Fixed `TransactionBuilder` not throwing when payment type validation should fail (#1195)
|
||||||
|
|
||||||
|
__removed__
|
||||||
|
- Removed rogue `package.json` from `src/payments` (#1216)
|
||||||
|
|
||||||
# 4.0.1
|
# 4.0.1
|
||||||
__fixed__
|
__fixed__
|
||||||
- Fixed `tiny-secp256k1` dependency version (used `ecurve`) (#1139)
|
- Fixed `tiny-secp256k1` dependency version (used `ecurve`) (#1139)
|
||||||
|
@ -12,10 +49,10 @@ __added__
|
||||||
|
|
||||||
__changed__
|
__changed__
|
||||||
- `ECPair.prototype.sign` now returns a 64-byte signature `Buffer`, not an `ECSignature` object (#1084)
|
- `ECPair.prototype.sign` now returns a 64-byte signature `Buffer`, not an `ECSignature` object (#1084)
|
||||||
- `ECPair` (and all ECDSA code) now uses [`tiny-secp256k1`](http://github.com/bitcoinjs/tiny-secp256k1), which uses the [`libsecp256k1` library](https://github.com/bitcoin-core/secp256k1) (#1070)
|
- `ECPair` (and all ECDSA code) now uses [`tiny-secp256k1`](https://github.com/bitcoinjs/tiny-secp256k1), which uses the [`libsecp256k1` library](https://github.com/bitcoin-core/secp256k1) (#1070)
|
||||||
- `TransactionBuilder` internal variables are now `__` prefixed to discourage public usage (#1038)
|
- `TransactionBuilder` internal variables are now `__` prefixed to discourage public usage (#1038)
|
||||||
- `TransactionBuilder` now defaults to version 2 transaction versions (#1036)
|
- `TransactionBuilder` now defaults to version 2 transaction versions (#1036)
|
||||||
- `script.decompile` now returns `Buffer` or `null`, if decompilation failed (#1039)
|
- `script.decompile` now returns `[Buffer]` or `null`, if decompilation failed (#1039)
|
||||||
|
|
||||||
__fixed__
|
__fixed__
|
||||||
- Fixed `TransactionBuilder` rejecting uncompressed public keys to comply with BIP143 (#987)
|
- Fixed `TransactionBuilder` rejecting uncompressed public keys to comply with BIP143 (#987)
|
||||||
|
|
|
@ -49,6 +49,22 @@ The length of time required for peer review is unpredictable and will vary from
|
||||||
Refer to the [Git manual](https://git-scm.com/doc) for any information about `git`.
|
Refer to the [Git manual](https://git-scm.com/doc) for any information about `git`.
|
||||||
|
|
||||||
|
|
||||||
|
## Regarding TypeScript
|
||||||
|
This library is written in TypeScript with tslint, prettier, and the tsc transpiler. These tools will help during testing to notice improper logic before committing and sending a pull request.
|
||||||
|
|
||||||
|
Some rules regarding TypeScript:
|
||||||
|
|
||||||
|
* Modify the typescript source code in an IDE that will give you warnings for transpile/lint errors.
|
||||||
|
* Once you are done with the modifications, run `npm run format` then `npm test`
|
||||||
|
* Running the tests will transpile the ts files into js and d.ts files.
|
||||||
|
* Use `git diff` or other tools to verify that the ts and js are changing the same parts.
|
||||||
|
* Commit all changes to ts, js, and d.ts files.
|
||||||
|
* Add tests where necessary.
|
||||||
|
* Submit your pull request.
|
||||||
|
|
||||||
|
Using TypeScript is for preventing bugs while writing code, as well as automatically generating type definitions. However, the JS file diffs must be verified, and any unverified JS will not be published to npm.
|
||||||
|
|
||||||
|
|
||||||
## We adhere to Bitcoin-Core policy
|
## We adhere to Bitcoin-Core policy
|
||||||
Bitcoin script payment/script templates are based on community consensus, but typically adhere to bitcoin-core node policy by default.
|
Bitcoin script payment/script templates are based on community consensus, but typically adhere to bitcoin-core node policy by default.
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2011-2018 bitcoinjs-lib contributors
|
Copyright (c) 2011-2019 bitcoinjs-lib contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
145
README.md
145
README.md
|
@ -2,9 +2,9 @@
|
||||||
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib)
|
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib)
|
||||||
[![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib)
|
[![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib)
|
||||||
|
|
||||||
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
|
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
|
||||||
|
|
||||||
A javascript Bitcoin library for node.js and browsers.
|
A javascript Bitcoin library for node.js and browsers. Written in TypeScript, but committing the JS files to verify.
|
||||||
|
|
||||||
Released under the terms of the [MIT LICENSE](LICENSE).
|
Released under the terms of the [MIT LICENSE](LICENSE).
|
||||||
|
|
||||||
|
@ -16,16 +16,17 @@ Master is not stable; it is our development branch, and [only tagged releases ma
|
||||||
## Can I trust this code?
|
## Can I trust this code?
|
||||||
> Don't trust. Verify.
|
> Don't trust. Verify.
|
||||||
|
|
||||||
We recommend every user of this library and the [bitcoinjs](https://github.com/bitcoinjs) ecosystem audit and verify any underlying code for its validity and suitability.
|
We recommend every user of this library and the [bitcoinjs](https://github.com/bitcoinjs) ecosystem audit and verify any underlying code for its validity and suitability, including reviewing any and all of your project's dependencies.
|
||||||
|
|
||||||
Mistakes and bugs happen, but with your help in resolving and reporting [issues](https://github.com/bitcoinjs/bitcoinjs-lib/issues), together we can produce open source software that is:
|
Mistakes and bugs happen, but with your help in resolving and reporting [issues](https://github.com/bitcoinjs/bitcoinjs-lib/issues), together we can produce open source software that is:
|
||||||
|
|
||||||
- Easy to audit and verify,
|
- Easy to audit and verify,
|
||||||
- Tested, with test coverage >95%,
|
- Tested, with test coverage >95%,
|
||||||
- Advanced and feature rich,
|
- Advanced and feature rich,
|
||||||
- Standardized, using [standard](http://github.com/standard/standard) and Node `Buffer`'s throughout, and
|
- Standardized, using [prettier](https://github.com/prettier/prettier) and Node `Buffer`'s throughout, and
|
||||||
- Friendly, with a strong and helpful community, ready to answer questions.
|
- Friendly, with a strong and helpful community, ready to answer questions.
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
Presently, we do not have any formal documentation other than our [examples](#examples), please [ask for help](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new) if our examples aren't enough to guide you.
|
Presently, we do not have any formal documentation other than our [examples](#examples), please [ask for help](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new) if our examples aren't enough to guide you.
|
||||||
|
|
||||||
|
@ -42,90 +43,84 @@ If in doubt, see the [.travis.yml](.travis.yml) for what versions are used by ou
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
Crypto is hard.
|
||||||
|
|
||||||
|
When working with private keys, the random number generator is fundamentally one of the most important parts of any software you write.
|
||||||
|
For random number generation, we *default* to the [`randombytes`](https://github.com/crypto-browserify/randombytes) module, which uses [`window.crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues) in the browser, or Node js' [`crypto.randomBytes`](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback), depending on your build system.
|
||||||
|
Although this default is ~OK, there is no simple way to detect if the underlying RNG provided is good enough, or if it is **catastrophically bad**.
|
||||||
|
You should always verify this yourself to your own standards.
|
||||||
|
|
||||||
|
This library uses [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1), which uses [RFC6979](https://tools.ietf.org/html/rfc6979) to help prevent `k` re-use and exploitation.
|
||||||
|
Unfortunately, this isn't a silver bullet.
|
||||||
|
Often, Javascript itself is working against us by bypassing these counter-measures.
|
||||||
|
|
||||||
|
Problems in [`Buffer (UInt8Array)`](https://github.com/feross/buffer), for example, can trivially result in **catastrophic fund loss** without any warning.
|
||||||
|
It can do this through undermining your random number generation, accidentally producing a [duplicate `k` value](https://www.nilsschneider.net/2013/01/28/recovering-bitcoin-private-keys.html), sending Bitcoin to a malformed output script, or any of a million different ways.
|
||||||
|
Running tests in your target environment is important and a recommended step to verify continuously.
|
||||||
|
|
||||||
|
Finally, **adhere to best practice**.
|
||||||
|
We are not an authorative source of best practice, but, at the very least:
|
||||||
|
|
||||||
|
* [Don't re-use addresses](https://en.bitcoin.it/wiki/Address_reuse).
|
||||||
|
* Don't share BIP32 extended public keys ('xpubs'). [They are a liability](https://bitcoin.stackexchange.com/questions/56916/derivation-of-parent-private-key-from-non-hardened-child), and it only takes 1 misplaced private key (or a buggy implementation!) and you are vulnerable to **catastrophic fund loss**.
|
||||||
|
* [Don't use `Math.random`](https://security.stackexchange.com/questions/181580/why-is-math-random-not-designed-to-be-cryptographically-secure) - in any way - don't.
|
||||||
|
* Enforce that users always verify (manually) a freshly-decoded human-readable version of their intended transaction before broadcast.
|
||||||
|
* Don't *ask* users to generate mnemonics, or 'brain wallets', humans are terrible random number generators.
|
||||||
|
* Lastly, if you can, use [Typescript](https://www.typescriptlang.org/) or similar.
|
||||||
|
|
||||||
|
|
||||||
### Browser
|
### Browser
|
||||||
The recommended method of using `bitcoinjs-lib` in your browser is through [Browserify](https://github.com/substack/node-browserify).
|
The recommended method of using `bitcoinjs-lib` in your browser is through [Browserify](https://github.com/substack/node-browserify).
|
||||||
If you're familiar with how to use browserify, ignore this and carry on, otherwise, it is recommended to read the tutorial at http://browserify.org/.
|
If you're familiar with how to use browserify, ignore this and carry on, otherwise, it is recommended to read the tutorial at https://browserify.org/.
|
||||||
|
|
||||||
**NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](http://babeljs.io/docs/plugins/preset-es2015/) preset).
|
**NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](https://babeljs.io/docs/plugins/preset-es2015/) preset).
|
||||||
|
|
||||||
**NOTE**: If you expect this library to run on an iOS 10 device, ensure that you are using [buffer@5.0.5](https://github.com/feross/buffer/pull/155) or greater.
|
**WARNING**: iOS devices have [problems](https://github.com/feross/buffer/issues/136), use atleast [buffer@5.0.5](https://github.com/feross/buffer/pull/155) or greater, and enforce the test suites (for `Buffer`, and any other dependency) pass before use.
|
||||||
|
|
||||||
### Typescript or VSCode users
|
### Typescript or VSCode users
|
||||||
Type declarations for Typescript [are available](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/0897921174860ec3d5318992d2323b3ae8100a68/types/bitcoinjs-lib) for version `^3.0.0` of the library.
|
Type declarations for Typescript are included in this library. Normal installation should include all the needed type information.
|
||||||
|
|
||||||
``` bash
|
|
||||||
npm install @types/bitcoinjs-lib
|
|
||||||
```
|
|
||||||
|
|
||||||
For VSCode (and other editors), it is advised to install the type declarations, as Intellisense uses that information to help you code (autocompletion, static analysis).
|
|
||||||
|
|
||||||
**WARNING**: These Typescript definitions are not maintained by the maintainers of this repository, and are instead maintained at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped).
|
|
||||||
Please report any issues or problems there.
|
|
||||||
|
|
||||||
|
|
||||||
### Flow
|
|
||||||
[Flow-type](https://flowtype.org/) definitions for are available in the [flow-*typed* repository](https://github.com/flowtype/flow-typed/tree/master/definitions/npm/bitcoinjs-lib_v2.x.x) for version `^2.0.0` of the library.
|
|
||||||
|
|
||||||
You can [download them directly](https://github.com/flowtype/flow-typed/blob/master/definitions/npm/bitcoinjs-lib_v2.x.x/flow_v0.17.x-/bitcoinjs-lib_v2.x.x.js), or using the flow-typed CLI:
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
npm install -g flow-typed
|
|
||||||
flow-typed install -f 0.27 bitcoinjs-lib@2.2.0
|
|
||||||
```
|
|
||||||
|
|
||||||
These definitions are maintained by [@runn1ng](https://github.com/runn1ng).
|
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
The below examples are implemented as integration tests, they should be very easy to understand.
|
The below examples are implemented as integration tests, they should be very easy to understand.
|
||||||
Otherwise, pull requests are appreciated.
|
Otherwise, pull requests are appreciated.
|
||||||
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).
|
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).
|
||||||
|
|
||||||
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L22)
|
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate an address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L29)
|
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L40)
|
- [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L47)
|
- [Generate a SegWit address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate a SegWit address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L60)
|
- [Generate a SegWit P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate a SegWit P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L67)
|
- [Generate a SegWit 3-of-4 multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate a SegWit 3-of-4 multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L76)
|
- [Generate a SegWit 2-of-2 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate a SegWit 2-of-2 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L90)
|
- [Support the retrieval of transactions for an address (3rd party blockchain)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Support the retrieval of transactions for an address (3rd party blockchain)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L104)
|
- [Generate a Testnet address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate a Testnet address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L123)
|
- [Generate a Litecoin address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js)
|
||||||
- [Generate a Litecoin address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L133)
|
- [Create a 1-to-1 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create a 1-to-1 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L13)
|
- [Create a 2-to-2 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create a 2-to-2 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L28)
|
- [Create (and broadcast via 3PBP) a typical Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create (and broadcast via 3PBP) a typical Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L47)
|
- [Create (and broadcast via 3PBP) a Transaction with an OP\_RETURN output](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction with an OP\_RETURN output](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L83)
|
- [Create (and broadcast via 3PBP) a Transaction with a 2-of-4 P2SH(multisig) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction with a 2-of-4 P2SH(multisig) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L105)
|
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2SH(P2WPKH) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2SH(P2WPKH) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L143)
|
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2WPKH input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2WPKH input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L174)
|
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2PK input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2PK input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L218)
|
- [Create (and broadcast via 3PBP) a Transaction with a SegWit 3-of-4 P2SH(P2WSH(multisig)) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction with a SegWit 3-of-4 P2SH(P2WSH(multisig)) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L263)
|
- [Verify a Transaction signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js)
|
||||||
- [Verify a Transaction signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js#L304)
|
- [Import a BIP32 testnet xpriv and export to WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js)
|
||||||
- [Import a BIP32 testnet xpriv and export to WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L12)
|
- [Export a BIP32 xpriv, then import it](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js)
|
||||||
- [Export a BIP32 xpriv, then import it](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L20)
|
- [Export a BIP32 xpub](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js)
|
||||||
- [Export a BIP32 xpub](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L31)
|
- [Create a BIP32, bitcoin, account 0, external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js)
|
||||||
- [Create a BIP32, bitcoin, account 0, external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L40)
|
- [Create a BIP44, bitcoin, account 0, external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js)
|
||||||
- [Create a BIP44, bitcoin, account 0, external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L55)
|
- [Create a BIP49, bitcoin testnet, account 0, external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js)
|
||||||
- [Create a BIP49, bitcoin testnet, account 0, external address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L71)
|
- [Use BIP39 to generate BIP32 addresses](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js)
|
||||||
- [Use BIP39 to generate BIP32 addresses](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L86)
|
- [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L43)
|
- [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L88)
|
- [Create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L144)
|
- [Create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js)
|
||||||
- [Create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L190)
|
- [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js#L72)
|
- [Create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js)
|
||||||
- [Create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js#L131)
|
- [Create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js#L177)
|
- [Create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js#L234)
|
- [Create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js)
|
||||||
- [Create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js#L296)
|
|
||||||
- [Recover a private key from duplicate R values](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L14)
|
|
||||||
- [Recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L68)
|
|
||||||
- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L72)
|
|
||||||
- [Generate a single-key stealth address (randomly)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L91)
|
|
||||||
- [Recover parent recipient.d, if a derived private key is leaked (and nonce was revealed)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L107)
|
|
||||||
- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L124)
|
|
||||||
- [Generate a dual-key stealth address (randomly)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L147)
|
|
||||||
|
|
||||||
If you have a use case that you feel could be listed here, please [ask for it](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new)!
|
If you have a use case that you feel could be listed here, please [ask for it](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new)!
|
||||||
|
|
||||||
|
|
1973
package-lock.json
generated
Normal file
1973
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
50
package.json
50
package.json
|
@ -1,8 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "bitcoinjs-lib",
|
"name": "bitcoinjs-lib",
|
||||||
"version": "4.0.1",
|
"version": "5.0.3",
|
||||||
"description": "Client-side Bitcoin JavaScript library",
|
"description": "Client-side Bitcoin JavaScript library",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
},
|
},
|
||||||
|
@ -14,24 +15,39 @@
|
||||||
"bitcoinjs"
|
"bitcoinjs"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"coverage-report": "nyc report --reporter=lcov",
|
"build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs",
|
||||||
"coverage-html": "nyc report --reporter=html",
|
"clean": "rimraf src",
|
||||||
"coverage": "nyc --check-coverage --branches 90 --functions 90 mocha",
|
"coverage-report": "npm run build && npm run nobuild:coverage-report",
|
||||||
"integration": "mocha --timeout 50000 test/integration/",
|
"coverage-html": "npm run build && npm run nobuild:coverage-html",
|
||||||
"standard": "standard",
|
"coverage": "npm run build && npm run nobuild:coverage",
|
||||||
"test": "npm run standard && npm run coverage",
|
"format": "npm run prettier -- --write",
|
||||||
"unit": "mocha"
|
"formatjs": "npm run prettierjs -- --write > /dev/null 2>&1",
|
||||||
|
"format:ci": "npm run prettier -- --check && npm run prettierjs -- --check",
|
||||||
|
"gitdiff:ci": "npm run build && git diff --exit-code",
|
||||||
|
"integration": "npm run build && npm run nobuild:integration",
|
||||||
|
"lint": "tslint -p tsconfig.json -c tslint.json",
|
||||||
|
"nobuild:coverage-report": "nyc report --reporter=lcov",
|
||||||
|
"nobuild:coverage-html": "nyc report --reporter=html",
|
||||||
|
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha",
|
||||||
|
"nobuild:integration": "mocha --timeout 50000 test/integration/",
|
||||||
|
"nobuild:unit": "mocha",
|
||||||
|
"prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore",
|
||||||
|
"prettierjs": "prettier 'src/**/*.js' --ignore-path ./.prettierignore",
|
||||||
|
"test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage",
|
||||||
|
"unit": "npm run build && npm run nobuild:unit"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/bitcoinjs/bitcoinjs-lib.git"
|
"url": "https://github.com/bitcoinjs/bitcoinjs-lib.git"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src"
|
"src",
|
||||||
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/node": "10.12.18",
|
||||||
"bech32": "^1.1.2",
|
"bech32": "^1.1.2",
|
||||||
"bip32": "^1.0.0",
|
"bip32": "^2.0.3",
|
||||||
"bip66": "^1.1.0",
|
"bip66": "^1.1.0",
|
||||||
"bitcoin-ops": "^1.4.0",
|
"bitcoin-ops": "^1.4.0",
|
||||||
"bs58check": "^2.0.0",
|
"bs58check": "^2.0.0",
|
||||||
|
@ -40,8 +56,7 @@
|
||||||
"merkle-lib": "^2.0.10",
|
"merkle-lib": "^2.0.10",
|
||||||
"pushdata-bitcoin": "^1.0.1",
|
"pushdata-bitcoin": "^1.0.1",
|
||||||
"randombytes": "^2.0.1",
|
"randombytes": "^2.0.1",
|
||||||
"safe-buffer": "^5.1.1",
|
"tiny-secp256k1": "^1.1.1",
|
||||||
"tiny-secp256k1": "^1.0.0",
|
|
||||||
"typeforce": "^1.11.3",
|
"typeforce": "^1.11.3",
|
||||||
"varuint-bitcoin": "^1.0.4",
|
"varuint-bitcoin": "^1.0.4",
|
||||||
"wif": "^2.0.1"
|
"wif": "^2.0.1"
|
||||||
|
@ -52,13 +67,16 @@
|
||||||
"bip68": "^1.0.3",
|
"bip68": "^1.0.3",
|
||||||
"bn.js": "^4.11.8",
|
"bn.js": "^4.11.8",
|
||||||
"bs58": "^4.0.0",
|
"bs58": "^4.0.0",
|
||||||
"dhttp": "^2.5.0",
|
"dhttp": "^3.0.0",
|
||||||
"hoodwink": "^1.0.0",
|
"hoodwink": "^2.0.0",
|
||||||
"minimaldata": "^1.0.2",
|
"minimaldata": "^1.0.2",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"nyc": "^11.8.0",
|
"nyc": "^14.1.1",
|
||||||
|
"prettier": "1.16.4",
|
||||||
"proxyquire": "^2.0.1",
|
"proxyquire": "^2.0.1",
|
||||||
"standard": "^11.0.1"
|
"rimraf": "^2.6.3",
|
||||||
|
"tslint": "^5.16.0",
|
||||||
|
"typescript": "3.2.2"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
154
src/address.js
154
src/address.js
|
@ -1,97 +1,91 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
'use strict';
|
||||||
const bech32 = require('bech32')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bs58check = require('bs58check')
|
const networks = require('./networks');
|
||||||
const bscript = require('./script')
|
const payments = require('./payments');
|
||||||
const networks = require('./networks')
|
const bscript = require('./script');
|
||||||
const typeforce = require('typeforce')
|
const types = require('./types');
|
||||||
const types = require('./types')
|
const bech32 = require('bech32');
|
||||||
const payments = require('./payments')
|
const bs58check = require('bs58check');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
function fromBase58Check (address) {
|
function fromBase58Check(address) {
|
||||||
const payload = bs58check.decode(address)
|
const payload = bs58check.decode(address);
|
||||||
|
|
||||||
// TODO: 4.0.0, move to "toOutputScript"
|
// TODO: 4.0.0, move to "toOutputScript"
|
||||||
if (payload.length < 21) throw new TypeError(address + ' is too short')
|
if (payload.length < 21) throw new TypeError(address + ' is too short');
|
||||||
if (payload.length > 21) throw new TypeError(address + ' is too long')
|
if (payload.length > 21) throw new TypeError(address + ' is too long');
|
||||||
|
const version = payload.readUInt8(0);
|
||||||
const version = payload.readUInt8(0)
|
const hash = payload.slice(1);
|
||||||
const hash = payload.slice(1)
|
return { version, hash };
|
||||||
|
|
||||||
return { version: version, hash: hash }
|
|
||||||
}
|
}
|
||||||
|
exports.fromBase58Check = fromBase58Check;
|
||||||
function fromBech32 (address) {
|
function fromBech32(address) {
|
||||||
const result = bech32.decode(address)
|
const result = bech32.decode(address);
|
||||||
const data = bech32.fromWords(result.words.slice(1))
|
const data = bech32.fromWords(result.words.slice(1));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: result.words[0],
|
version: result.words[0],
|
||||||
prefix: result.prefix,
|
prefix: result.prefix,
|
||||||
data: Buffer.from(data)
|
data: Buffer.from(data),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
exports.fromBech32 = fromBech32;
|
||||||
function toBase58Check (hash, version) {
|
function toBase58Check(hash, version) {
|
||||||
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments)
|
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
|
||||||
|
const payload = Buffer.allocUnsafe(21);
|
||||||
const payload = Buffer.allocUnsafe(21)
|
payload.writeUInt8(version, 0);
|
||||||
payload.writeUInt8(version, 0)
|
hash.copy(payload, 1);
|
||||||
hash.copy(payload, 1)
|
return bs58check.encode(payload);
|
||||||
|
|
||||||
return bs58check.encode(payload)
|
|
||||||
}
|
}
|
||||||
|
exports.toBase58Check = toBase58Check;
|
||||||
function toBech32 (data, version, prefix) {
|
function toBech32(data, version, prefix) {
|
||||||
const words = bech32.toWords(data)
|
const words = bech32.toWords(data);
|
||||||
words.unshift(version)
|
words.unshift(version);
|
||||||
|
return bech32.encode(prefix, words);
|
||||||
return bech32.encode(prefix, words)
|
|
||||||
}
|
}
|
||||||
|
exports.toBech32 = toBech32;
|
||||||
function fromOutputScript (output, network) {
|
function fromOutputScript(output, network) {
|
||||||
network = network || networks.bitcoin
|
// TODO: Network
|
||||||
|
network = network || networks.bitcoin;
|
||||||
try { return payments.p2pkh({ output, network }).address } catch (e) {}
|
|
||||||
try { return payments.p2sh({ output, network }).address } catch (e) {}
|
|
||||||
try { return payments.p2wpkh({ output, network }).address } catch (e) {}
|
|
||||||
try { return payments.p2wsh({ output, network }).address } catch (e) {}
|
|
||||||
|
|
||||||
throw new Error(bscript.toASM(output) + ' has no matching Address')
|
|
||||||
}
|
|
||||||
|
|
||||||
function toOutputScript (address, network) {
|
|
||||||
network = network || networks.bitcoin
|
|
||||||
|
|
||||||
let decode
|
|
||||||
try {
|
try {
|
||||||
decode = fromBase58Check(address)
|
return payments.p2pkh({ output, network }).address;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
try {
|
||||||
if (decode) {
|
return payments.p2sh({ output, network }).address;
|
||||||
if (decode.version === network.pubKeyHash) return payments.p2pkh({ hash: decode.hash }).output
|
} catch (e) {}
|
||||||
if (decode.version === network.scriptHash) return payments.p2sh({ hash: decode.hash }).output
|
try {
|
||||||
|
return payments.p2wpkh({ output, network }).address;
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
return payments.p2wsh({ output, network }).address;
|
||||||
|
} catch (e) {}
|
||||||
|
throw new Error(bscript.toASM(output) + ' has no matching Address');
|
||||||
|
}
|
||||||
|
exports.fromOutputScript = fromOutputScript;
|
||||||
|
function toOutputScript(address, network) {
|
||||||
|
network = network || networks.bitcoin;
|
||||||
|
let decodeBase58;
|
||||||
|
let decodeBech32;
|
||||||
|
try {
|
||||||
|
decodeBase58 = fromBase58Check(address);
|
||||||
|
} catch (e) {}
|
||||||
|
if (decodeBase58) {
|
||||||
|
if (decodeBase58.version === network.pubKeyHash)
|
||||||
|
return payments.p2pkh({ hash: decodeBase58.hash }).output;
|
||||||
|
if (decodeBase58.version === network.scriptHash)
|
||||||
|
return payments.p2sh({ hash: decodeBase58.hash }).output;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
decode = fromBech32(address)
|
decodeBech32 = fromBech32(address);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
if (decodeBech32) {
|
||||||
if (decode) {
|
if (decodeBech32.prefix !== network.bech32)
|
||||||
if (decode.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix')
|
throw new Error(address + ' has an invalid prefix');
|
||||||
if (decode.version === 0) {
|
if (decodeBech32.version === 0) {
|
||||||
if (decode.data.length === 20) return payments.p2wpkh({ hash: decode.data }).output
|
if (decodeBech32.data.length === 20)
|
||||||
if (decode.data.length === 32) return payments.p2wsh({ hash: decode.data }).output
|
return payments.p2wpkh({ hash: decodeBech32.data }).output;
|
||||||
|
if (decodeBech32.data.length === 32)
|
||||||
|
return payments.p2wsh({ hash: decodeBech32.data }).output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw new Error(address + ' has no matching Script');
|
||||||
throw new Error(address + ' has no matching Script')
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fromBase58Check: fromBase58Check,
|
|
||||||
fromBech32: fromBech32,
|
|
||||||
fromOutputScript: fromOutputScript,
|
|
||||||
toBase58Check: toBase58Check,
|
|
||||||
toBech32: toBech32,
|
|
||||||
toOutputScript: toOutputScript
|
|
||||||
}
|
}
|
||||||
|
exports.toOutputScript = toOutputScript;
|
||||||
|
|
395
src/block.js
395
src/block.js
|
@ -1,177 +1,242 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
'use strict';
|
||||||
const bcrypto = require('./crypto')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const fastMerkleRoot = require('merkle-lib/fastRoot')
|
const bufferutils_1 = require('./bufferutils');
|
||||||
const typeforce = require('typeforce')
|
const bcrypto = require('./crypto');
|
||||||
const types = require('./types')
|
const transaction_1 = require('./transaction');
|
||||||
const varuint = require('varuint-bitcoin')
|
const types = require('./types');
|
||||||
|
const fastMerkleRoot = require('merkle-lib/fastRoot');
|
||||||
const Transaction = require('./transaction')
|
const typeforce = require('typeforce');
|
||||||
|
const varuint = require('varuint-bitcoin');
|
||||||
function Block () {
|
const errorMerkleNoTxes = new TypeError(
|
||||||
this.version = 1
|
'Cannot compute merkle root for zero transactions',
|
||||||
this.prevHash = null
|
);
|
||||||
this.merkleRoot = null
|
const errorWitnessNotSegwit = new TypeError(
|
||||||
this.timestamp = 0
|
'Cannot compute witness commit for non-segwit block',
|
||||||
this.bits = 0
|
);
|
||||||
this.nonce = 0
|
class Block {
|
||||||
}
|
constructor() {
|
||||||
|
this.version = 1;
|
||||||
Block.fromBuffer = function (buffer) {
|
this.prevHash = undefined;
|
||||||
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)')
|
this.merkleRoot = undefined;
|
||||||
|
this.timestamp = 0;
|
||||||
let offset = 0
|
this.witnessCommit = undefined;
|
||||||
function readSlice (n) {
|
this.bits = 0;
|
||||||
offset += n
|
this.nonce = 0;
|
||||||
return buffer.slice(offset - n, offset)
|
this.transactions = undefined;
|
||||||
}
|
}
|
||||||
|
static fromBuffer(buffer) {
|
||||||
function readUInt32 () {
|
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)');
|
||||||
const i = buffer.readUInt32LE(offset)
|
let offset = 0;
|
||||||
offset += 4
|
const readSlice = n => {
|
||||||
return i
|
offset += n;
|
||||||
|
return buffer.slice(offset - n, offset);
|
||||||
|
};
|
||||||
|
const readUInt32 = () => {
|
||||||
|
const i = buffer.readUInt32LE(offset);
|
||||||
|
offset += 4;
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
const readInt32 = () => {
|
||||||
|
const i = buffer.readInt32LE(offset);
|
||||||
|
offset += 4;
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
const block = new Block();
|
||||||
|
block.version = readInt32();
|
||||||
|
block.prevHash = readSlice(32);
|
||||||
|
block.merkleRoot = readSlice(32);
|
||||||
|
block.timestamp = readUInt32();
|
||||||
|
block.bits = readUInt32();
|
||||||
|
block.nonce = readUInt32();
|
||||||
|
if (buffer.length === 80) return block;
|
||||||
|
const readVarInt = () => {
|
||||||
|
const vi = varuint.decode(buffer, offset);
|
||||||
|
offset += varuint.decode.bytes;
|
||||||
|
return vi;
|
||||||
|
};
|
||||||
|
const readTransaction = () => {
|
||||||
|
const tx = transaction_1.Transaction.fromBuffer(
|
||||||
|
buffer.slice(offset),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
offset += tx.byteLength();
|
||||||
|
return tx;
|
||||||
|
};
|
||||||
|
const nTransactions = readVarInt();
|
||||||
|
block.transactions = [];
|
||||||
|
for (let i = 0; i < nTransactions; ++i) {
|
||||||
|
const tx = readTransaction();
|
||||||
|
block.transactions.push(tx);
|
||||||
|
}
|
||||||
|
const witnessCommit = block.getWitnessCommit();
|
||||||
|
// This Block contains a witness commit
|
||||||
|
if (witnessCommit) block.witnessCommit = witnessCommit;
|
||||||
|
return block;
|
||||||
}
|
}
|
||||||
|
static fromHex(hex) {
|
||||||
function readInt32 () {
|
return Block.fromBuffer(Buffer.from(hex, 'hex'));
|
||||||
const i = buffer.readInt32LE(offset)
|
|
||||||
offset += 4
|
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
|
static calculateTarget(bits) {
|
||||||
const block = new Block()
|
const exponent = ((bits & 0xff000000) >> 24) - 3;
|
||||||
block.version = readInt32()
|
const mantissa = bits & 0x007fffff;
|
||||||
block.prevHash = readSlice(32)
|
const target = Buffer.alloc(32, 0);
|
||||||
block.merkleRoot = readSlice(32)
|
target.writeUIntBE(mantissa, 29 - exponent, 3);
|
||||||
block.timestamp = readUInt32()
|
return target;
|
||||||
block.bits = readUInt32()
|
|
||||||
block.nonce = readUInt32()
|
|
||||||
|
|
||||||
if (buffer.length === 80) return block
|
|
||||||
|
|
||||||
function readVarInt () {
|
|
||||||
const vi = varuint.decode(buffer, offset)
|
|
||||||
offset += varuint.decode.bytes
|
|
||||||
return vi
|
|
||||||
}
|
}
|
||||||
|
static calculateMerkleRoot(transactions, forWitness) {
|
||||||
function readTransaction () {
|
typeforce([{ getHash: types.Function }], transactions);
|
||||||
const tx = Transaction.fromBuffer(buffer.slice(offset), true)
|
if (transactions.length === 0) throw errorMerkleNoTxes;
|
||||||
offset += tx.byteLength()
|
if (forWitness && !txesHaveWitnessCommit(transactions))
|
||||||
return tx
|
throw errorWitnessNotSegwit;
|
||||||
|
const hashes = transactions.map(transaction =>
|
||||||
|
transaction.getHash(forWitness),
|
||||||
|
);
|
||||||
|
const rootHash = fastMerkleRoot(hashes, bcrypto.hash256);
|
||||||
|
return forWitness
|
||||||
|
? bcrypto.hash256(
|
||||||
|
Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]),
|
||||||
|
)
|
||||||
|
: rootHash;
|
||||||
}
|
}
|
||||||
|
getWitnessCommit() {
|
||||||
const nTransactions = readVarInt()
|
if (!txesHaveWitnessCommit(this.transactions)) return null;
|
||||||
block.transactions = []
|
// The merkle root for the witness data is in an OP_RETURN output.
|
||||||
|
// There is no rule for the index of the output, so use filter to find it.
|
||||||
for (var i = 0; i < nTransactions; ++i) {
|
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
|
||||||
const tx = readTransaction()
|
// If multiple commits are found, the output with highest index is assumed.
|
||||||
block.transactions.push(tx)
|
const witnessCommits = this.transactions[0].outs
|
||||||
|
.filter(out =>
|
||||||
|
out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')),
|
||||||
|
)
|
||||||
|
.map(out => out.script.slice(6, 38));
|
||||||
|
if (witnessCommits.length === 0) return null;
|
||||||
|
// Use the commit with the highest output (should only be one though)
|
||||||
|
const result = witnessCommits[witnessCommits.length - 1];
|
||||||
|
if (!(result instanceof Buffer && result.length === 32)) return null;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
hasWitnessCommit() {
|
||||||
return block
|
if (
|
||||||
}
|
this.witnessCommit instanceof Buffer &&
|
||||||
|
this.witnessCommit.length === 32
|
||||||
Block.prototype.byteLength = function (headersOnly) {
|
)
|
||||||
if (headersOnly || !this.transactions) return 80
|
return true;
|
||||||
|
if (this.getWitnessCommit() !== null) return true;
|
||||||
return 80 + varuint.encodingLength(this.transactions.length) + this.transactions.reduce(function (a, x) {
|
return false;
|
||||||
return a + x.byteLength()
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Block.fromHex = function (hex) {
|
|
||||||
return Block.fromBuffer(Buffer.from(hex, 'hex'))
|
|
||||||
}
|
|
||||||
|
|
||||||
Block.prototype.getHash = function () {
|
|
||||||
return bcrypto.hash256(this.toBuffer(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
Block.prototype.getId = function () {
|
|
||||||
return this.getHash().reverse().toString('hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
Block.prototype.getUTCDate = function () {
|
|
||||||
const date = new Date(0) // epoch
|
|
||||||
date.setUTCSeconds(this.timestamp)
|
|
||||||
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: buffer, offset compatibility
|
|
||||||
Block.prototype.toBuffer = function (headersOnly) {
|
|
||||||
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly))
|
|
||||||
|
|
||||||
let offset = 0
|
|
||||||
function writeSlice (slice) {
|
|
||||||
slice.copy(buffer, offset)
|
|
||||||
offset += slice.length
|
|
||||||
}
|
}
|
||||||
|
hasWitness() {
|
||||||
function writeInt32 (i) {
|
return anyTxHasWitness(this.transactions);
|
||||||
buffer.writeInt32LE(i, offset)
|
|
||||||
offset += 4
|
|
||||||
}
|
}
|
||||||
function writeUInt32 (i) {
|
byteLength(headersOnly) {
|
||||||
buffer.writeUInt32LE(i, offset)
|
if (headersOnly || !this.transactions) return 80;
|
||||||
offset += 4
|
return (
|
||||||
|
80 +
|
||||||
|
varuint.encodingLength(this.transactions.length) +
|
||||||
|
this.transactions.reduce((a, x) => a + x.byteLength(), 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
getHash() {
|
||||||
|
return bcrypto.hash256(this.toBuffer(true));
|
||||||
|
}
|
||||||
|
getId() {
|
||||||
|
return bufferutils_1.reverseBuffer(this.getHash()).toString('hex');
|
||||||
|
}
|
||||||
|
getUTCDate() {
|
||||||
|
const date = new Date(0); // epoch
|
||||||
|
date.setUTCSeconds(this.timestamp);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
// TODO: buffer, offset compatibility
|
||||||
|
toBuffer(headersOnly) {
|
||||||
|
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
|
||||||
|
let offset = 0;
|
||||||
|
const writeSlice = slice => {
|
||||||
|
slice.copy(buffer, offset);
|
||||||
|
offset += slice.length;
|
||||||
|
};
|
||||||
|
const writeInt32 = i => {
|
||||||
|
buffer.writeInt32LE(i, offset);
|
||||||
|
offset += 4;
|
||||||
|
};
|
||||||
|
const writeUInt32 = i => {
|
||||||
|
buffer.writeUInt32LE(i, offset);
|
||||||
|
offset += 4;
|
||||||
|
};
|
||||||
|
writeInt32(this.version);
|
||||||
|
writeSlice(this.prevHash);
|
||||||
|
writeSlice(this.merkleRoot);
|
||||||
|
writeUInt32(this.timestamp);
|
||||||
|
writeUInt32(this.bits);
|
||||||
|
writeUInt32(this.nonce);
|
||||||
|
if (headersOnly || !this.transactions) return buffer;
|
||||||
|
varuint.encode(this.transactions.length, buffer, offset);
|
||||||
|
offset += varuint.encode.bytes;
|
||||||
|
this.transactions.forEach(tx => {
|
||||||
|
const txSize = tx.byteLength(); // TODO: extract from toBuffer?
|
||||||
|
tx.toBuffer(buffer, offset);
|
||||||
|
offset += txSize;
|
||||||
|
});
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
toHex(headersOnly) {
|
||||||
|
return this.toBuffer(headersOnly).toString('hex');
|
||||||
|
}
|
||||||
|
checkTxRoots() {
|
||||||
|
// If the Block has segwit transactions but no witness commit,
|
||||||
|
// there's no way it can be valid, so fail the check.
|
||||||
|
const hasWitnessCommit = this.hasWitnessCommit();
|
||||||
|
if (!hasWitnessCommit && this.hasWitness()) return false;
|
||||||
|
return (
|
||||||
|
this.__checkMerkleRoot() &&
|
||||||
|
(hasWitnessCommit ? this.__checkWitnessCommit() : true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
checkProofOfWork() {
|
||||||
|
const hash = bufferutils_1.reverseBuffer(this.getHash());
|
||||||
|
const target = Block.calculateTarget(this.bits);
|
||||||
|
return hash.compare(target) <= 0;
|
||||||
|
}
|
||||||
|
__checkMerkleRoot() {
|
||||||
|
if (!this.transactions) throw errorMerkleNoTxes;
|
||||||
|
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
|
||||||
|
return this.merkleRoot.compare(actualMerkleRoot) === 0;
|
||||||
|
}
|
||||||
|
__checkWitnessCommit() {
|
||||||
|
if (!this.transactions) throw errorMerkleNoTxes;
|
||||||
|
if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit;
|
||||||
|
const actualWitnessCommit = Block.calculateMerkleRoot(
|
||||||
|
this.transactions,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return this.witnessCommit.compare(actualWitnessCommit) === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeInt32(this.version)
|
|
||||||
writeSlice(this.prevHash)
|
|
||||||
writeSlice(this.merkleRoot)
|
|
||||||
writeUInt32(this.timestamp)
|
|
||||||
writeUInt32(this.bits)
|
|
||||||
writeUInt32(this.nonce)
|
|
||||||
|
|
||||||
if (headersOnly || !this.transactions) return buffer
|
|
||||||
|
|
||||||
varuint.encode(this.transactions.length, buffer, offset)
|
|
||||||
offset += varuint.encode.bytes
|
|
||||||
|
|
||||||
this.transactions.forEach(function (tx) {
|
|
||||||
const txSize = tx.byteLength() // TODO: extract from toBuffer?
|
|
||||||
tx.toBuffer(buffer, offset)
|
|
||||||
offset += txSize
|
|
||||||
})
|
|
||||||
|
|
||||||
return buffer
|
|
||||||
}
|
}
|
||||||
|
exports.Block = Block;
|
||||||
Block.prototype.toHex = function (headersOnly) {
|
function txesHaveWitnessCommit(transactions) {
|
||||||
return this.toBuffer(headersOnly).toString('hex')
|
return (
|
||||||
|
transactions instanceof Array &&
|
||||||
|
transactions[0] &&
|
||||||
|
transactions[0].ins &&
|
||||||
|
transactions[0].ins instanceof Array &&
|
||||||
|
transactions[0].ins[0] &&
|
||||||
|
transactions[0].ins[0].witness &&
|
||||||
|
transactions[0].ins[0].witness instanceof Array &&
|
||||||
|
transactions[0].ins[0].witness.length > 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
function anyTxHasWitness(transactions) {
|
||||||
Block.calculateTarget = function (bits) {
|
return (
|
||||||
const exponent = ((bits & 0xff000000) >> 24) - 3
|
transactions instanceof Array &&
|
||||||
const mantissa = bits & 0x007fffff
|
transactions.some(
|
||||||
const target = Buffer.alloc(32, 0)
|
tx =>
|
||||||
target.writeUInt32BE(mantissa, 28 - exponent)
|
typeof tx === 'object' &&
|
||||||
return target
|
tx.ins instanceof Array &&
|
||||||
|
tx.ins.some(
|
||||||
|
input =>
|
||||||
|
typeof input === 'object' &&
|
||||||
|
input.witness instanceof Array &&
|
||||||
|
input.witness.length > 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Block.calculateMerkleRoot = function (transactions) {
|
|
||||||
typeforce([{ getHash: types.Function }], transactions)
|
|
||||||
if (transactions.length === 0) throw TypeError('Cannot compute merkle root for zero transactions')
|
|
||||||
|
|
||||||
const hashes = transactions.map(function (transaction) {
|
|
||||||
return transaction.getHash()
|
|
||||||
})
|
|
||||||
|
|
||||||
return fastMerkleRoot(hashes, bcrypto.hash256)
|
|
||||||
}
|
|
||||||
|
|
||||||
Block.prototype.checkMerkleRoot = function () {
|
|
||||||
if (!this.transactions) return false
|
|
||||||
|
|
||||||
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions)
|
|
||||||
return this.merkleRoot.compare(actualMerkleRoot) === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Block.prototype.checkProofOfWork = function () {
|
|
||||||
const hash = this.getHash().reverse()
|
|
||||||
const target = Block.calculateTarget(this.bits)
|
|
||||||
|
|
||||||
return hash.compare(target) <= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Block
|
|
||||||
|
|
|
@ -1,29 +1,40 @@
|
||||||
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
||||||
function verifuint (value, max) {
|
function verifuint(value, max) {
|
||||||
if (typeof value !== 'number') throw new Error('cannot write a non-number as a number')
|
if (typeof value !== 'number')
|
||||||
if (value < 0) throw new Error('specified a negative value for writing an unsigned value')
|
throw new Error('cannot write a non-number as a number');
|
||||||
if (value > max) throw new Error('RangeError: value out of range')
|
if (value < 0)
|
||||||
if (Math.floor(value) !== value) throw new Error('value has a fractional component')
|
throw new Error('specified a negative value for writing an unsigned value');
|
||||||
|
if (value > max) throw new Error('RangeError: value out of range');
|
||||||
|
if (Math.floor(value) !== value)
|
||||||
|
throw new Error('value has a fractional component');
|
||||||
}
|
}
|
||||||
|
function readUInt64LE(buffer, offset) {
|
||||||
function readUInt64LE (buffer, offset) {
|
const a = buffer.readUInt32LE(offset);
|
||||||
const a = buffer.readUInt32LE(offset)
|
let b = buffer.readUInt32LE(offset + 4);
|
||||||
let b = buffer.readUInt32LE(offset + 4)
|
b *= 0x100000000;
|
||||||
b *= 0x100000000
|
verifuint(b + a, 0x001fffffffffffff);
|
||||||
|
return b + a;
|
||||||
verifuint(b + a, 0x001fffffffffffff)
|
|
||||||
return b + a
|
|
||||||
}
|
}
|
||||||
|
exports.readUInt64LE = readUInt64LE;
|
||||||
function writeUInt64LE (buffer, value, offset) {
|
function writeUInt64LE(buffer, value, offset) {
|
||||||
verifuint(value, 0x001fffffffffffff)
|
verifuint(value, 0x001fffffffffffff);
|
||||||
|
buffer.writeInt32LE(value & -1, offset);
|
||||||
buffer.writeInt32LE(value & -1, offset)
|
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
|
||||||
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4)
|
return offset + 8;
|
||||||
return offset + 8
|
|
||||||
}
|
}
|
||||||
|
exports.writeUInt64LE = writeUInt64LE;
|
||||||
module.exports = {
|
function reverseBuffer(buffer) {
|
||||||
readUInt64LE: readUInt64LE,
|
if (buffer.length < 1) return buffer;
|
||||||
writeUInt64LE: writeUInt64LE
|
let j = buffer.length - 1;
|
||||||
|
let tmp = 0;
|
||||||
|
for (let i = 0; i < buffer.length / 2; i++) {
|
||||||
|
tmp = buffer[i];
|
||||||
|
buffer[i] = buffer[j];
|
||||||
|
buffer[j] = tmp;
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
exports.reverseBuffer = reverseBuffer;
|
||||||
|
|
101
src/classify.js
101
src/classify.js
|
@ -1,15 +1,16 @@
|
||||||
const decompile = require('./script').decompile
|
'use strict';
|
||||||
const multisig = require('./templates/multisig')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const nullData = require('./templates/nulldata')
|
const script_1 = require('./script');
|
||||||
const pubKey = require('./templates/pubkey')
|
const multisig = require('./templates/multisig');
|
||||||
const pubKeyHash = require('./templates/pubkeyhash')
|
const nullData = require('./templates/nulldata');
|
||||||
const scriptHash = require('./templates/scripthash')
|
const pubKey = require('./templates/pubkey');
|
||||||
const witnessPubKeyHash = require('./templates/witnesspubkeyhash')
|
const pubKeyHash = require('./templates/pubkeyhash');
|
||||||
const witnessScriptHash = require('./templates/witnessscripthash')
|
const scriptHash = require('./templates/scripthash');
|
||||||
const witnessCommitment = require('./templates/witnesscommitment')
|
const witnessCommitment = require('./templates/witnesscommitment');
|
||||||
|
const witnessPubKeyHash = require('./templates/witnesspubkeyhash');
|
||||||
|
const witnessScriptHash = require('./templates/witnessscripthash');
|
||||||
const types = {
|
const types = {
|
||||||
MULTISIG: 'multisig',
|
P2MS: 'multisig',
|
||||||
NONSTANDARD: 'nonstandard',
|
NONSTANDARD: 'nonstandard',
|
||||||
NULLDATA: 'nulldata',
|
NULLDATA: 'nulldata',
|
||||||
P2PK: 'pubkey',
|
P2PK: 'pubkey',
|
||||||
|
@ -17,54 +18,42 @@ const types = {
|
||||||
P2SH: 'scripthash',
|
P2SH: 'scripthash',
|
||||||
P2WPKH: 'witnesspubkeyhash',
|
P2WPKH: 'witnesspubkeyhash',
|
||||||
P2WSH: 'witnessscripthash',
|
P2WSH: 'witnessscripthash',
|
||||||
WITNESS_COMMITMENT: 'witnesscommitment'
|
WITNESS_COMMITMENT: 'witnesscommitment',
|
||||||
}
|
};
|
||||||
|
exports.types = types;
|
||||||
function classifyOutput (script) {
|
function classifyOutput(script) {
|
||||||
if (witnessPubKeyHash.output.check(script)) return types.P2WPKH
|
if (witnessPubKeyHash.output.check(script)) return types.P2WPKH;
|
||||||
if (witnessScriptHash.output.check(script)) return types.P2WSH
|
if (witnessScriptHash.output.check(script)) return types.P2WSH;
|
||||||
if (pubKeyHash.output.check(script)) return types.P2PKH
|
if (pubKeyHash.output.check(script)) return types.P2PKH;
|
||||||
if (scriptHash.output.check(script)) return types.P2SH
|
if (scriptHash.output.check(script)) return types.P2SH;
|
||||||
|
|
||||||
// XXX: optimization, below functions .decompile before use
|
// XXX: optimization, below functions .decompile before use
|
||||||
const chunks = decompile(script)
|
const chunks = script_1.decompile(script);
|
||||||
if (!chunks) throw new TypeError('Invalid script')
|
if (!chunks) throw new TypeError('Invalid script');
|
||||||
|
if (multisig.output.check(chunks)) return types.P2MS;
|
||||||
if (multisig.output.check(chunks)) return types.MULTISIG
|
if (pubKey.output.check(chunks)) return types.P2PK;
|
||||||
if (pubKey.output.check(chunks)) return types.P2PK
|
if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT;
|
||||||
if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT
|
if (nullData.output.check(chunks)) return types.NULLDATA;
|
||||||
if (nullData.output.check(chunks)) return types.NULLDATA
|
return types.NONSTANDARD;
|
||||||
|
|
||||||
return types.NONSTANDARD
|
|
||||||
}
|
}
|
||||||
|
exports.output = classifyOutput;
|
||||||
function classifyInput (script, allowIncomplete) {
|
function classifyInput(script, allowIncomplete) {
|
||||||
// XXX: optimization, below functions .decompile before use
|
// XXX: optimization, below functions .decompile before use
|
||||||
const chunks = decompile(script)
|
const chunks = script_1.decompile(script);
|
||||||
if (!chunks) throw new TypeError('Invalid script')
|
if (!chunks) throw new TypeError('Invalid script');
|
||||||
|
if (pubKeyHash.input.check(chunks)) return types.P2PKH;
|
||||||
if (pubKeyHash.input.check(chunks)) return types.P2PKH
|
if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH;
|
||||||
if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH
|
if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS;
|
||||||
if (multisig.input.check(chunks, allowIncomplete)) return types.MULTISIG
|
if (pubKey.input.check(chunks)) return types.P2PK;
|
||||||
if (pubKey.input.check(chunks)) return types.P2PK
|
return types.NONSTANDARD;
|
||||||
|
|
||||||
return types.NONSTANDARD
|
|
||||||
}
|
}
|
||||||
|
exports.input = classifyInput;
|
||||||
function classifyWitness (script, allowIncomplete) {
|
function classifyWitness(script, allowIncomplete) {
|
||||||
// XXX: optimization, below functions .decompile before use
|
// XXX: optimization, below functions .decompile before use
|
||||||
const chunks = decompile(script)
|
const chunks = script_1.decompile(script);
|
||||||
if (!chunks) throw new TypeError('Invalid script')
|
if (!chunks) throw new TypeError('Invalid script');
|
||||||
|
if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH;
|
||||||
if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH
|
if (witnessScriptHash.input.check(chunks, allowIncomplete))
|
||||||
if (witnessScriptHash.input.check(chunks, allowIncomplete)) return types.P2WSH
|
return types.P2WSH;
|
||||||
|
return types.NONSTANDARD;
|
||||||
return types.NONSTANDARD
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
input: classifyInput,
|
|
||||||
output: classifyOutput,
|
|
||||||
witness: classifyWitness,
|
|
||||||
types: types
|
|
||||||
}
|
}
|
||||||
|
exports.witness = classifyWitness;
|
||||||
|
|
|
@ -1,29 +1,35 @@
|
||||||
const createHash = require('create-hash')
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
function ripemd160 (buffer) {
|
const createHash = require('create-hash');
|
||||||
return createHash('rmd160').update(buffer).digest()
|
function ripemd160(buffer) {
|
||||||
|
try {
|
||||||
|
return createHash('rmd160')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
} catch (err) {
|
||||||
|
return createHash('ripemd160')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
exports.ripemd160 = ripemd160;
|
||||||
function sha1 (buffer) {
|
function sha1(buffer) {
|
||||||
return createHash('sha1').update(buffer).digest()
|
return createHash('sha1')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
}
|
}
|
||||||
|
exports.sha1 = sha1;
|
||||||
function sha256 (buffer) {
|
function sha256(buffer) {
|
||||||
return createHash('sha256').update(buffer).digest()
|
return createHash('sha256')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
}
|
}
|
||||||
|
exports.sha256 = sha256;
|
||||||
function hash160 (buffer) {
|
function hash160(buffer) {
|
||||||
return ripemd160(sha256(buffer))
|
return ripemd160(sha256(buffer));
|
||||||
}
|
}
|
||||||
|
exports.hash160 = hash160;
|
||||||
function hash256 (buffer) {
|
function hash256(buffer) {
|
||||||
return sha256(sha256(buffer))
|
return sha256(sha256(buffer));
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
hash160: hash160,
|
|
||||||
hash256: hash256,
|
|
||||||
ripemd160: ripemd160,
|
|
||||||
sha1: sha1,
|
|
||||||
sha256: sha256
|
|
||||||
}
|
}
|
||||||
|
exports.hash256 = hash256;
|
||||||
|
|
187
src/ecpair.js
187
src/ecpair.js
|
@ -1,106 +1,105 @@
|
||||||
const ecc = require('tiny-secp256k1')
|
'use strict';
|
||||||
const randomBytes = require('randombytes')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const typeforce = require('typeforce')
|
const NETWORKS = require('./networks');
|
||||||
const types = require('./types')
|
const types = require('./types');
|
||||||
const wif = require('wif')
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const randomBytes = require('randombytes');
|
||||||
const NETWORKS = require('./networks')
|
const typeforce = require('typeforce');
|
||||||
const isOptions = typeforce.maybe(typeforce.compile({
|
const wif = require('wif');
|
||||||
compressed: types.maybe(types.Boolean),
|
const isOptions = typeforce.maybe(
|
||||||
network: types.maybe(types.Network)
|
typeforce.compile({
|
||||||
}))
|
compressed: types.maybe(types.Boolean),
|
||||||
|
network: types.maybe(types.Network),
|
||||||
function ECPair (d, Q, options) {
|
}),
|
||||||
options = options || {}
|
);
|
||||||
|
class ECPair {
|
||||||
this.compressed = options.compressed === undefined ? true : options.compressed
|
constructor(__D, __Q, options) {
|
||||||
this.network = options.network || NETWORKS.bitcoin
|
this.__D = __D;
|
||||||
|
this.__Q = __Q;
|
||||||
this.__d = d || null
|
if (options === undefined) options = {};
|
||||||
this.__Q = null
|
this.compressed =
|
||||||
if (Q) this.__Q = ecc.pointCompress(Q, this.compressed)
|
options.compressed === undefined ? true : options.compressed;
|
||||||
|
this.network = options.network || NETWORKS.bitcoin;
|
||||||
|
if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed);
|
||||||
|
}
|
||||||
|
get privateKey() {
|
||||||
|
return this.__D;
|
||||||
|
}
|
||||||
|
get publicKey() {
|
||||||
|
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__D, this.compressed);
|
||||||
|
return this.__Q;
|
||||||
|
}
|
||||||
|
toWIF() {
|
||||||
|
if (!this.__D) throw new Error('Missing private key');
|
||||||
|
return wif.encode(this.network.wif, this.__D, this.compressed);
|
||||||
|
}
|
||||||
|
sign(hash, lowR = false) {
|
||||||
|
if (!this.__D) throw new Error('Missing private key');
|
||||||
|
if (lowR === false) {
|
||||||
|
return ecc.sign(hash, this.__D);
|
||||||
|
} else {
|
||||||
|
let sig = ecc.sign(hash, this.__D);
|
||||||
|
const extraData = Buffer.alloc(32, 0);
|
||||||
|
let counter = 0;
|
||||||
|
// if first try is lowR, skip the loop
|
||||||
|
// for second try and on, add extra entropy counting up
|
||||||
|
while (sig[0] > 0x7f) {
|
||||||
|
counter++;
|
||||||
|
extraData.writeUIntLE(counter, 0, 6);
|
||||||
|
sig = ecc.signWithEntropy(hash, this.__D, extraData);
|
||||||
|
}
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verify(hash, signature) {
|
||||||
|
return ecc.verify(hash, this.publicKey, signature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
function fromPrivateKey(buffer, options) {
|
||||||
Object.defineProperty(ECPair.prototype, 'privateKey', {
|
typeforce(types.Buffer256bit, buffer);
|
||||||
enumerable: false,
|
if (!ecc.isPrivate(buffer))
|
||||||
get: function () { return this.__d }
|
throw new TypeError('Private key not in range [1, n)');
|
||||||
})
|
typeforce(isOptions, options);
|
||||||
|
return new ECPair(buffer, undefined, options);
|
||||||
Object.defineProperty(ECPair.prototype, 'publicKey', { get: function () {
|
|
||||||
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed)
|
|
||||||
return this.__Q
|
|
||||||
}})
|
|
||||||
|
|
||||||
ECPair.prototype.toWIF = function () {
|
|
||||||
if (!this.__d) throw new Error('Missing private key')
|
|
||||||
return wif.encode(this.network.wif, this.__d, this.compressed)
|
|
||||||
}
|
}
|
||||||
|
exports.fromPrivateKey = fromPrivateKey;
|
||||||
ECPair.prototype.sign = function (hash) {
|
function fromPublicKey(buffer, options) {
|
||||||
if (!this.__d) throw new Error('Missing private key')
|
typeforce(ecc.isPoint, buffer);
|
||||||
return ecc.sign(hash, this.__d)
|
typeforce(isOptions, options);
|
||||||
|
return new ECPair(undefined, buffer, options);
|
||||||
}
|
}
|
||||||
|
exports.fromPublicKey = fromPublicKey;
|
||||||
ECPair.prototype.verify = function (hash, signature) {
|
function fromWIF(wifString, network) {
|
||||||
return ecc.verify(hash, this.publicKey, signature)
|
const decoded = wif.decode(wifString);
|
||||||
}
|
const version = decoded.version;
|
||||||
|
|
||||||
function fromPrivateKey (buffer, options) {
|
|
||||||
typeforce(types.Buffer256bit, buffer)
|
|
||||||
if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)')
|
|
||||||
typeforce(isOptions, options)
|
|
||||||
|
|
||||||
return new ECPair(buffer, null, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromPublicKey (buffer, options) {
|
|
||||||
typeforce(ecc.isPoint, buffer)
|
|
||||||
typeforce(isOptions, options)
|
|
||||||
return new ECPair(null, buffer, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromWIF (string, network) {
|
|
||||||
const decoded = wif.decode(string)
|
|
||||||
const version = decoded.version
|
|
||||||
|
|
||||||
// list of networks?
|
// list of networks?
|
||||||
if (types.Array(network)) {
|
if (types.Array(network)) {
|
||||||
network = network.filter(function (x) {
|
network = network
|
||||||
return version === x.wif
|
.filter(x => {
|
||||||
}).pop()
|
return version === x.wif;
|
||||||
|
})
|
||||||
if (!network) throw new Error('Unknown network version')
|
.pop();
|
||||||
|
if (!network) throw new Error('Unknown network version');
|
||||||
// otherwise, assume a network object (or default to bitcoin)
|
// otherwise, assume a network object (or default to bitcoin)
|
||||||
} else {
|
} else {
|
||||||
network = network || NETWORKS.bitcoin
|
network = network || NETWORKS.bitcoin;
|
||||||
|
if (version !== network.wif) throw new Error('Invalid network version');
|
||||||
if (version !== network.wif) throw new Error('Invalid network version')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fromPrivateKey(decoded.privateKey, {
|
return fromPrivateKey(decoded.privateKey, {
|
||||||
compressed: decoded.compressed,
|
compressed: decoded.compressed,
|
||||||
network: network
|
network: network,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
exports.fromWIF = fromWIF;
|
||||||
function makeRandom (options) {
|
function makeRandom(options) {
|
||||||
typeforce(isOptions, options)
|
typeforce(isOptions, options);
|
||||||
options = options || {}
|
if (options === undefined) options = {};
|
||||||
const rng = options.rng || randomBytes
|
const rng = options.rng || randomBytes;
|
||||||
|
let d;
|
||||||
let d
|
|
||||||
do {
|
do {
|
||||||
d = rng(32)
|
d = rng(32);
|
||||||
typeforce(types.Buffer256bit, d)
|
typeforce(types.Buffer256bit, d);
|
||||||
} while (!ecc.isPrivate(d))
|
} while (!ecc.isPrivate(d));
|
||||||
|
return fromPrivateKey(d, options);
|
||||||
return fromPrivateKey(d, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
makeRandom,
|
|
||||||
fromPrivateKey,
|
|
||||||
fromPublicKey,
|
|
||||||
fromWIF
|
|
||||||
}
|
}
|
||||||
|
exports.makeRandom = makeRandom;
|
||||||
|
|
40
src/index.js
40
src/index.js
|
@ -1,16 +1,24 @@
|
||||||
const script = require('./script')
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
module.exports = {
|
const bip32 = require('bip32');
|
||||||
Block: require('./block'),
|
exports.bip32 = bip32;
|
||||||
ECPair: require('./ecpair'),
|
const address = require('./address');
|
||||||
Transaction: require('./transaction'),
|
exports.address = address;
|
||||||
TransactionBuilder: require('./transaction_builder'),
|
const crypto = require('./crypto');
|
||||||
|
exports.crypto = crypto;
|
||||||
address: require('./address'),
|
const ECPair = require('./ecpair');
|
||||||
bip32: require('bip32'),
|
exports.ECPair = ECPair;
|
||||||
crypto: require('./crypto'),
|
const networks = require('./networks');
|
||||||
networks: require('./networks'),
|
exports.networks = networks;
|
||||||
opcodes: require('bitcoin-ops'),
|
const payments = require('./payments');
|
||||||
payments: require('./payments'),
|
exports.payments = payments;
|
||||||
script: script
|
const script = require('./script');
|
||||||
}
|
exports.script = script;
|
||||||
|
var block_1 = require('./block');
|
||||||
|
exports.Block = block_1.Block;
|
||||||
|
var script_1 = require('./script');
|
||||||
|
exports.opcodes = script_1.OPS;
|
||||||
|
var transaction_1 = require('./transaction');
|
||||||
|
exports.Transaction = transaction_1.Transaction;
|
||||||
|
var transaction_builder_1 = require('./transaction_builder');
|
||||||
|
exports.TransactionBuilder = transaction_builder_1.TransactionBuilder;
|
||||||
|
|
|
@ -1,27 +1,35 @@
|
||||||
// https://en.bitcoin.it/wiki/List_of_address_prefixes
|
'use strict';
|
||||||
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
exports.bitcoin = {
|
||||||
module.exports = {
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bitcoin: {
|
bech32: 'bc',
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
bip32: {
|
||||||
bech32: 'bc',
|
public: 0x0488b21e,
|
||||||
bip32: {
|
private: 0x0488ade4,
|
||||||
public: 0x0488b21e,
|
|
||||||
private: 0x0488ade4
|
|
||||||
},
|
|
||||||
pubKeyHash: 0x00,
|
|
||||||
scriptHash: 0x05,
|
|
||||||
wif: 0x80
|
|
||||||
},
|
},
|
||||||
testnet: {
|
pubKeyHash: 0x00,
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
scriptHash: 0x05,
|
||||||
bech32: 'tb',
|
wif: 0x80,
|
||||||
bip32: {
|
};
|
||||||
public: 0x043587cf,
|
exports.regtest = {
|
||||||
private: 0x04358394
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
},
|
bech32: 'bcrt',
|
||||||
pubKeyHash: 0x6f,
|
bip32: {
|
||||||
scriptHash: 0xc4,
|
public: 0x043587cf,
|
||||||
wif: 0xef
|
private: 0x04358394,
|
||||||
}
|
},
|
||||||
}
|
pubKeyHash: 0x6f,
|
||||||
|
scriptHash: 0xc4,
|
||||||
|
wif: 0xef,
|
||||||
|
};
|
||||||
|
exports.testnet = {
|
||||||
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
|
bech32: 'tb',
|
||||||
|
bip32: {
|
||||||
|
public: 0x043587cf,
|
||||||
|
private: 0x04358394,
|
||||||
|
},
|
||||||
|
pubKeyHash: 0x6f,
|
||||||
|
scriptHash: 0xc4,
|
||||||
|
wif: 0xef,
|
||||||
|
};
|
||||||
|
|
|
@ -1,56 +1,49 @@
|
||||||
const lazy = require('./lazy')
|
'use strict';
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const networks_1 = require('../networks');
|
||||||
|
const bscript = require('../script');
|
||||||
const bscript = require('../script')
|
const lazy = require('./lazy');
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const typef = require('typeforce');
|
||||||
|
const OPS = bscript.OPS;
|
||||||
function stacksEqual (a, b) {
|
function stacksEqual(a, b) {
|
||||||
if (a.length !== b.length) return false
|
if (a.length !== b.length) return false;
|
||||||
|
return a.every((x, i) => {
|
||||||
return a.every(function (x, i) {
|
return x.equals(b[i]);
|
||||||
return x.equals(b[i])
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// output: OP_RETURN ...
|
// output: OP_RETURN ...
|
||||||
function p2data (a, opts) {
|
function p2data(a, opts) {
|
||||||
if (
|
if (!a.data && !a.output) throw new TypeError('Not enough data');
|
||||||
!a.data &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output
|
typef(
|
||||||
) throw new TypeError('Not enough data')
|
{
|
||||||
opts = opts || { validate: true }
|
network: typef.maybe(typef.Object),
|
||||||
|
output: typef.maybe(typef.Buffer),
|
||||||
typef({
|
data: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
network: typef.maybe(typef.Object),
|
},
|
||||||
output: typef.maybe(typef.Buffer),
|
a,
|
||||||
data: typef.maybe(typef.arrayOf(typef.Buffer))
|
);
|
||||||
}, a)
|
const network = a.network || networks_1.bitcoin;
|
||||||
|
const o = { network };
|
||||||
const network = a.network || BITCOIN_NETWORK
|
lazy.prop(o, 'output', () => {
|
||||||
const o = { network }
|
if (!a.data) return;
|
||||||
|
return bscript.compile([OPS.OP_RETURN].concat(a.data));
|
||||||
lazy.prop(o, 'output', function () {
|
});
|
||||||
if (!a.data) return
|
lazy.prop(o, 'data', () => {
|
||||||
return bscript.compile([OPS.OP_RETURN].concat(a.data))
|
if (!a.output) return;
|
||||||
})
|
return bscript.decompile(a.output).slice(1);
|
||||||
lazy.prop(o, 'data', function () {
|
});
|
||||||
if (!a.output) return
|
|
||||||
return bscript.decompile(a.output).slice(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
const chunks = bscript.decompile(a.output)
|
const chunks = bscript.decompile(a.output);
|
||||||
if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid')
|
if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid');
|
||||||
if (!chunks.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid')
|
if (!chunks.slice(1).every(typef.Buffer))
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
if (a.data && !stacksEqual(a.data, o.data)) throw new TypeError('Data mismatch')
|
if (a.data && !stacksEqual(a.data, o.data))
|
||||||
|
throw new TypeError('Data mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2data = p2data;
|
||||||
module.exports = p2data
|
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
const embed = require('./embed')
|
'use strict';
|
||||||
const p2ms = require('./p2ms')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const p2pk = require('./p2pk')
|
const embed_1 = require('./embed');
|
||||||
const p2pkh = require('./p2pkh')
|
exports.embed = embed_1.p2data;
|
||||||
const p2sh = require('./p2sh')
|
const p2ms_1 = require('./p2ms');
|
||||||
const p2wpkh = require('./p2wpkh')
|
exports.p2ms = p2ms_1.p2ms;
|
||||||
const p2wsh = require('./p2wsh')
|
const p2pk_1 = require('./p2pk');
|
||||||
|
exports.p2pk = p2pk_1.p2pk;
|
||||||
module.exports = { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }
|
const p2pkh_1 = require('./p2pkh');
|
||||||
|
exports.p2pkh = p2pkh_1.p2pkh;
|
||||||
|
const p2sh_1 = require('./p2sh');
|
||||||
|
exports.p2sh = p2sh_1.p2sh;
|
||||||
|
const p2wpkh_1 = require('./p2wpkh');
|
||||||
|
exports.p2wpkh = p2wpkh_1.p2wpkh;
|
||||||
|
const p2wsh_1 = require('./p2wsh');
|
||||||
|
exports.p2wsh = p2wsh_1.p2wsh;
|
||||||
// TODO
|
// TODO
|
||||||
// witness commitment
|
// witness commitment
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
function prop (object, name, f) {
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
function prop(object, name, f) {
|
||||||
Object.defineProperty(object, name, {
|
Object.defineProperty(object, name, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get: function () {
|
get() {
|
||||||
let value = f.call(this)
|
const _value = f.call(this);
|
||||||
this[name] = value
|
this[name] = _value;
|
||||||
return value
|
return _value;
|
||||||
},
|
},
|
||||||
set: function (value) {
|
set(_value) {
|
||||||
Object.defineProperty(this, name, {
|
Object.defineProperty(this, name, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
value: value,
|
value: _value,
|
||||||
writable: true
|
writable: true,
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
exports.prop = prop;
|
||||||
function value (f) {
|
function value(f) {
|
||||||
let value
|
let _value;
|
||||||
return function () {
|
return () => {
|
||||||
if (value !== undefined) return value
|
if (_value !== undefined) return _value;
|
||||||
value = f()
|
_value = f();
|
||||||
return value
|
return _value;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
exports.value = value;
|
||||||
module.exports = { prop, value }
|
|
||||||
|
|
|
@ -1,140 +1,141 @@
|
||||||
const lazy = require('./lazy')
|
'use strict';
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const networks_1 = require('../networks');
|
||||||
const ecc = require('tiny-secp256k1')
|
const bscript = require('../script');
|
||||||
|
const lazy = require('./lazy');
|
||||||
const bscript = require('../script')
|
const OPS = bscript.OPS;
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const typef = require('typeforce');
|
||||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
|
||||||
function stacksEqual (a, b) {
|
function stacksEqual(a, b) {
|
||||||
if (a.length !== b.length) return false
|
if (a.length !== b.length) return false;
|
||||||
|
return a.every((x, i) => {
|
||||||
return a.every(function (x, i) {
|
return x.equals(b[i]);
|
||||||
return x.equals(b[i])
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// input: OP_0 [signatures ...]
|
// input: OP_0 [signatures ...]
|
||||||
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
||||||
function p2ms (a, opts) {
|
function p2ms(a, opts) {
|
||||||
if (
|
if (
|
||||||
!a.input &&
|
!a.input &&
|
||||||
!a.output &&
|
!a.output &&
|
||||||
!(a.pubkeys && a.m !== undefined) &&
|
!(a.pubkeys && a.m !== undefined) &&
|
||||||
!a.signatures
|
!a.signatures
|
||||||
) throw new TypeError('Not enough data')
|
)
|
||||||
opts = opts || { validate: true }
|
throw new TypeError('Not enough data');
|
||||||
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
function isAcceptableSignature (x) {
|
function isAcceptableSignature(x) {
|
||||||
return bscript.isCanonicalScriptSignature(x) || (opts.allowIncomplete && (x === OPS.OP_0))
|
return (
|
||||||
|
bscript.isCanonicalScriptSignature(x) ||
|
||||||
|
(opts.allowIncomplete && x === OPS.OP_0) !== undefined
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
typef(
|
||||||
typef({
|
{
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
m: typef.maybe(typef.Number),
|
m: typef.maybe(typef.Number),
|
||||||
n: typef.maybe(typef.Number),
|
n: typef.maybe(typef.Number),
|
||||||
output: typef.maybe(typef.Buffer),
|
output: typef.maybe(typef.Buffer),
|
||||||
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
|
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
|
||||||
|
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
|
||||||
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
|
input: typef.maybe(typef.Buffer),
|
||||||
input: typef.maybe(typef.Buffer)
|
},
|
||||||
}, a)
|
a,
|
||||||
|
);
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const network = a.network || networks_1.bitcoin;
|
||||||
const o = { network }
|
const o = { network };
|
||||||
|
let chunks = [];
|
||||||
let chunks
|
let decoded = false;
|
||||||
let decoded = false
|
function decode(output) {
|
||||||
function decode (output) {
|
if (decoded) return;
|
||||||
if (decoded) return
|
decoded = true;
|
||||||
decoded = true
|
chunks = bscript.decompile(output);
|
||||||
chunks = bscript.decompile(output)
|
o.m = chunks[0] - OP_INT_BASE;
|
||||||
o.m = chunks[0] - OP_INT_BASE
|
o.n = chunks[chunks.length - 2] - OP_INT_BASE;
|
||||||
o.n = chunks[chunks.length - 2] - OP_INT_BASE
|
o.pubkeys = chunks.slice(1, -2);
|
||||||
o.pubkeys = chunks.slice(1, -2)
|
|
||||||
}
|
}
|
||||||
|
lazy.prop(o, 'output', () => {
|
||||||
lazy.prop(o, 'output', function () {
|
if (!a.m) return;
|
||||||
if (!a.m) return
|
if (!o.n) return;
|
||||||
if (!o.n) return
|
if (!a.pubkeys) return;
|
||||||
if (!a.pubkeys) return
|
return bscript.compile(
|
||||||
return bscript.compile([].concat(
|
[].concat(
|
||||||
OP_INT_BASE + a.m,
|
OP_INT_BASE + a.m,
|
||||||
a.pubkeys,
|
a.pubkeys,
|
||||||
OP_INT_BASE + o.n,
|
OP_INT_BASE + o.n,
|
||||||
OPS.OP_CHECKMULTISIG
|
OPS.OP_CHECKMULTISIG,
|
||||||
))
|
),
|
||||||
})
|
);
|
||||||
lazy.prop(o, 'm', function () {
|
});
|
||||||
if (!o.output) return
|
lazy.prop(o, 'm', () => {
|
||||||
decode(o.output)
|
if (!o.output) return;
|
||||||
return o.m
|
decode(o.output);
|
||||||
})
|
return o.m;
|
||||||
lazy.prop(o, 'n', function () {
|
});
|
||||||
if (!o.pubkeys) return
|
lazy.prop(o, 'n', () => {
|
||||||
return o.pubkeys.length
|
if (!o.pubkeys) return;
|
||||||
})
|
return o.pubkeys.length;
|
||||||
lazy.prop(o, 'pubkeys', function () {
|
});
|
||||||
if (!a.output) return
|
lazy.prop(o, 'pubkeys', () => {
|
||||||
decode(a.output)
|
if (!a.output) return;
|
||||||
return o.pubkeys
|
decode(a.output);
|
||||||
})
|
return o.pubkeys;
|
||||||
lazy.prop(o, 'signatures', function () {
|
});
|
||||||
if (!a.input) return
|
lazy.prop(o, 'signatures', () => {
|
||||||
return bscript.decompile(a.input).slice(1)
|
if (!a.input) return;
|
||||||
})
|
return bscript.decompile(a.input).slice(1);
|
||||||
lazy.prop(o, 'input', function () {
|
});
|
||||||
if (!a.signatures) return
|
lazy.prop(o, 'input', () => {
|
||||||
return bscript.compile([OPS.OP_0].concat(a.signatures))
|
if (!a.signatures) return;
|
||||||
})
|
return bscript.compile([OPS.OP_0].concat(a.signatures));
|
||||||
lazy.prop(o, 'witness', function () {
|
});
|
||||||
if (!o.input) return
|
lazy.prop(o, 'witness', () => {
|
||||||
return []
|
if (!o.input) return;
|
||||||
})
|
return [];
|
||||||
|
});
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
decode(a.output)
|
decode(a.output);
|
||||||
if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid')
|
if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid');
|
||||||
if (!typef.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid')
|
if (!typef.Number(chunks[chunks.length - 2]))
|
||||||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid')
|
throw new TypeError('Output is invalid');
|
||||||
|
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG)
|
||||||
if (
|
throw new TypeError('Output is invalid');
|
||||||
o.m <= 0 ||
|
if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3)
|
||||||
o.n > 16 ||
|
throw new TypeError('Output is invalid');
|
||||||
o.m > o.n ||
|
if (!o.pubkeys.every(x => ecc.isPoint(x)))
|
||||||
o.n !== chunks.length - 3) throw new TypeError('Output is invalid')
|
throw new TypeError('Output is invalid');
|
||||||
if (!o.pubkeys.every(x => ecc.isPoint(x))) throw new TypeError('Output is invalid')
|
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch');
|
||||||
|
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch');
|
||||||
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch')
|
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys))
|
||||||
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch')
|
throw new TypeError('Pubkeys mismatch');
|
||||||
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) throw new TypeError('Pubkeys mismatch')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.pubkeys) {
|
if (a.pubkeys) {
|
||||||
if (a.n !== undefined && a.n !== a.pubkeys.length) throw new TypeError('Pubkey count mismatch')
|
if (a.n !== undefined && a.n !== a.pubkeys.length)
|
||||||
o.n = a.pubkeys.length
|
throw new TypeError('Pubkey count mismatch');
|
||||||
|
o.n = a.pubkeys.length;
|
||||||
if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m')
|
if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.signatures) {
|
if (a.signatures) {
|
||||||
if (a.signatures.length < o.m) throw new TypeError('Not enough signatures provided')
|
if (a.signatures.length < o.m)
|
||||||
if (a.signatures.length > o.m) throw new TypeError('Too many signatures provided')
|
throw new TypeError('Not enough signatures provided');
|
||||||
|
if (a.signatures.length > o.m)
|
||||||
|
throw new TypeError('Too many signatures provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid')
|
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid');
|
||||||
if (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature)) throw new TypeError('Input has invalid signature(s)')
|
if (
|
||||||
|
o.signatures.length === 0 ||
|
||||||
if (a.signatures && !stacksEqual(a.signatures.equals(o.signatures))) throw new TypeError('Signature mismatch')
|
!o.signatures.every(isAcceptableSignature)
|
||||||
if (a.m !== undefined && a.m !== a.signatures.length) throw new TypeError('Signature count mismatch')
|
)
|
||||||
|
throw new TypeError('Input has invalid signature(s)');
|
||||||
|
if (a.signatures && !stacksEqual(a.signatures, o.signatures))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
|
if (a.m !== undefined && a.m !== a.signatures.length)
|
||||||
|
throw new TypeError('Signature count mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2ms = p2ms;
|
||||||
module.exports = p2ms
|
|
||||||
|
|
|
@ -1,83 +1,72 @@
|
||||||
let lazy = require('./lazy')
|
'use strict';
|
||||||
let typef = require('typeforce')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
let OPS = require('bitcoin-ops')
|
const networks_1 = require('../networks');
|
||||||
let ecc = require('tiny-secp256k1')
|
const bscript = require('../script');
|
||||||
|
const lazy = require('./lazy');
|
||||||
let bscript = require('../script')
|
const typef = require('typeforce');
|
||||||
let BITCOIN_NETWORK = require('../networks').bitcoin
|
const OPS = bscript.OPS;
|
||||||
|
const ecc = require('tiny-secp256k1');
|
||||||
// input: {signature}
|
// input: {signature}
|
||||||
// output: {pubKey} OP_CHECKSIG
|
// output: {pubKey} OP_CHECKSIG
|
||||||
function p2pk (a, opts) {
|
function p2pk(a, opts) {
|
||||||
if (
|
if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature)
|
||||||
!a.input &&
|
throw new TypeError('Not enough data');
|
||||||
!a.output &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.pubkey &&
|
typef(
|
||||||
!a.input &&
|
{
|
||||||
!a.signature
|
network: typef.maybe(typef.Object),
|
||||||
) throw new TypeError('Not enough data')
|
output: typef.maybe(typef.Buffer),
|
||||||
opts = opts || { validate: true }
|
pubkey: typef.maybe(ecc.isPoint),
|
||||||
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||||
typef({
|
input: typef.maybe(typef.Buffer),
|
||||||
network: typef.maybe(typef.Object),
|
},
|
||||||
output: typef.maybe(typef.Buffer),
|
a,
|
||||||
pubkey: typef.maybe(ecc.isPoint),
|
);
|
||||||
|
const _chunks = lazy.value(() => {
|
||||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
return bscript.decompile(a.input);
|
||||||
input: typef.maybe(typef.Buffer)
|
});
|
||||||
}, a)
|
const network = a.network || networks_1.bitcoin;
|
||||||
|
const o = { network };
|
||||||
let _chunks = lazy.value(function () { return bscript.decompile(a.input) })
|
lazy.prop(o, 'output', () => {
|
||||||
|
if (!a.pubkey) return;
|
||||||
let network = a.network || BITCOIN_NETWORK
|
return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]);
|
||||||
let o = { network }
|
});
|
||||||
|
lazy.prop(o, 'pubkey', () => {
|
||||||
lazy.prop(o, 'output', function () {
|
if (!a.output) return;
|
||||||
if (!a.pubkey) return
|
return a.output.slice(1, -1);
|
||||||
return bscript.compile([
|
});
|
||||||
a.pubkey,
|
lazy.prop(o, 'signature', () => {
|
||||||
OPS.OP_CHECKSIG
|
if (!a.input) return;
|
||||||
])
|
return _chunks()[0];
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'pubkey', function () {
|
lazy.prop(o, 'input', () => {
|
||||||
if (!a.output) return
|
if (!a.signature) return;
|
||||||
return a.output.slice(1, -1)
|
return bscript.compile([a.signature]);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'signature', function () {
|
lazy.prop(o, 'witness', () => {
|
||||||
if (!a.input) return
|
if (!o.input) return;
|
||||||
return _chunks()[0]
|
return [];
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'input', function () {
|
|
||||||
if (!a.signature) return
|
|
||||||
return bscript.compile([a.signature])
|
|
||||||
})
|
|
||||||
lazy.prop(o, 'witness', function () {
|
|
||||||
if (!o.input) return
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
|
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
if (a.pubkey && a.output) {
|
|
||||||
if (!a.pubkey.equals(o.pubkey)) throw new TypeError('Pubkey mismatch')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid')
|
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG)
|
||||||
if (!ecc.isPoint(o.pubkey)) throw new TypeError('Output pubkey is invalid')
|
throw new TypeError('Output is invalid');
|
||||||
|
if (!ecc.isPoint(o.pubkey))
|
||||||
|
throw new TypeError('Output pubkey is invalid');
|
||||||
|
if (a.pubkey && !a.pubkey.equals(o.pubkey))
|
||||||
|
throw new TypeError('Pubkey mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.signature) {
|
if (a.signature) {
|
||||||
if (a.input && !a.input.equals(o.input)) throw new TypeError('Input mismatch')
|
if (a.input && !a.input.equals(o.input))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
if (_chunks().length !== 1) throw new TypeError('Input is invalid')
|
if (_chunks().length !== 1) throw new TypeError('Input is invalid');
|
||||||
if (!bscript.isCanonicalScriptSignature(_chunks()[0])) throw new TypeError('Input has invalid signature')
|
if (!bscript.isCanonicalScriptSignature(o.signature))
|
||||||
|
throw new TypeError('Input has invalid signature');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2pk = p2pk;
|
||||||
module.exports = p2pk
|
|
||||||
|
|
|
@ -1,102 +1,95 @@
|
||||||
const lazy = require('./lazy')
|
'use strict';
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require('../crypto');
|
||||||
const ecc = require('tiny-secp256k1')
|
const networks_1 = require('../networks');
|
||||||
|
const bscript = require('../script');
|
||||||
const bcrypto = require('../crypto')
|
const lazy = require('./lazy');
|
||||||
const bscript = require('../script')
|
const typef = require('typeforce');
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const OPS = bscript.OPS;
|
||||||
const bs58check = require('bs58check')
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const bs58check = require('bs58check');
|
||||||
// input: {signature} {pubkey}
|
// input: {signature} {pubkey}
|
||||||
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
|
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
|
||||||
function p2pkh (a, opts) {
|
function p2pkh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
typef(
|
||||||
!a.pubkey &&
|
{
|
||||||
!a.input
|
network: typef.maybe(typef.Object),
|
||||||
) throw new TypeError('Not enough data')
|
address: typef.maybe(typef.String),
|
||||||
opts = opts || { validate: true }
|
hash: typef.maybe(typef.BufferN(20)),
|
||||||
|
output: typef.maybe(typef.BufferN(25)),
|
||||||
typef({
|
pubkey: typef.maybe(ecc.isPoint),
|
||||||
network: typef.maybe(typef.Object),
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||||
address: typef.maybe(typef.String),
|
input: typef.maybe(typef.Buffer),
|
||||||
hash: typef.maybe(typef.BufferN(20)),
|
},
|
||||||
output: typef.maybe(typef.BufferN(25)),
|
a,
|
||||||
|
);
|
||||||
pubkey: typef.maybe(ecc.isPoint),
|
const _address = lazy.value(() => {
|
||||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
const payload = bs58check.decode(a.address);
|
||||||
input: typef.maybe(typef.Buffer)
|
const version = payload.readUInt8(0);
|
||||||
}, a)
|
const hash = payload.slice(1);
|
||||||
|
return { version, hash };
|
||||||
const _address = lazy.value(function () {
|
});
|
||||||
const payload = bs58check.decode(a.address)
|
const _chunks = lazy.value(() => {
|
||||||
const version = payload.readUInt8(0)
|
return bscript.decompile(a.input);
|
||||||
const hash = payload.slice(1)
|
});
|
||||||
return { version, hash }
|
const network = a.network || networks_1.bitcoin;
|
||||||
})
|
const o = { network };
|
||||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) })
|
lazy.prop(o, 'address', () => {
|
||||||
|
if (!o.hash) return;
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const payload = Buffer.allocUnsafe(21);
|
||||||
const o = { network }
|
payload.writeUInt8(network.pubKeyHash, 0);
|
||||||
|
o.hash.copy(payload, 1);
|
||||||
lazy.prop(o, 'address', function () {
|
return bs58check.encode(payload);
|
||||||
if (!o.hash) return
|
});
|
||||||
|
lazy.prop(o, 'hash', () => {
|
||||||
const payload = Buffer.allocUnsafe(21)
|
if (a.output) return a.output.slice(3, 23);
|
||||||
payload.writeUInt8(network.pubKeyHash, 0)
|
if (a.address) return _address().hash;
|
||||||
o.hash.copy(payload, 1)
|
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey);
|
||||||
return bs58check.encode(payload)
|
});
|
||||||
})
|
lazy.prop(o, 'output', () => {
|
||||||
lazy.prop(o, 'hash', function () {
|
if (!o.hash) return;
|
||||||
if (a.output) return a.output.slice(3, 23)
|
|
||||||
if (a.address) return _address().hash
|
|
||||||
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey)
|
|
||||||
})
|
|
||||||
lazy.prop(o, 'output', function () {
|
|
||||||
if (!o.hash) return
|
|
||||||
return bscript.compile([
|
return bscript.compile([
|
||||||
OPS.OP_DUP,
|
OPS.OP_DUP,
|
||||||
OPS.OP_HASH160,
|
OPS.OP_HASH160,
|
||||||
o.hash,
|
o.hash,
|
||||||
OPS.OP_EQUALVERIFY,
|
OPS.OP_EQUALVERIFY,
|
||||||
OPS.OP_CHECKSIG
|
OPS.OP_CHECKSIG,
|
||||||
])
|
]);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'pubkey', function () {
|
lazy.prop(o, 'pubkey', () => {
|
||||||
if (!a.input) return
|
if (!a.input) return;
|
||||||
return _chunks()[1]
|
return _chunks()[1];
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'signature', function () {
|
lazy.prop(o, 'signature', () => {
|
||||||
if (!a.input) return
|
if (!a.input) return;
|
||||||
return _chunks()[0]
|
return _chunks()[0];
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'input', function () {
|
lazy.prop(o, 'input', () => {
|
||||||
if (!a.pubkey) return
|
if (!a.pubkey) return;
|
||||||
if (!a.signature) return
|
if (!a.signature) return;
|
||||||
return bscript.compile([a.signature, a.pubkey])
|
return bscript.compile([a.signature, a.pubkey]);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'witness', function () {
|
lazy.prop(o, 'witness', () => {
|
||||||
if (!o.input) return
|
if (!o.input) return;
|
||||||
return []
|
return [];
|
||||||
})
|
});
|
||||||
|
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (_address().version !== network.pubKeyHash) throw new TypeError('Invalid version or Network mismatch')
|
if (_address().version !== network.pubKeyHash)
|
||||||
if (_address().hash.length !== 20) throw new TypeError('Invalid address')
|
throw new TypeError('Invalid version or Network mismatch');
|
||||||
hash = _address().hash
|
if (_address().hash.length !== 20) throw new TypeError('Invalid address');
|
||||||
|
hash = _address().hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (
|
||||||
a.output.length !== 25 ||
|
a.output.length !== 25 ||
|
||||||
|
@ -104,33 +97,36 @@ function p2pkh (a, opts) {
|
||||||
a.output[1] !== OPS.OP_HASH160 ||
|
a.output[1] !== OPS.OP_HASH160 ||
|
||||||
a.output[2] !== 0x14 ||
|
a.output[2] !== 0x14 ||
|
||||||
a.output[23] !== OPS.OP_EQUALVERIFY ||
|
a.output[23] !== OPS.OP_EQUALVERIFY ||
|
||||||
a.output[24] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid')
|
a.output[24] !== OPS.OP_CHECKSIG
|
||||||
|
)
|
||||||
if (hash && !hash.equals(a.output.slice(3, 23))) throw new TypeError('Hash mismatch')
|
throw new TypeError('Output is invalid');
|
||||||
else hash = a.output.slice(3, 23)
|
const hash2 = a.output.slice(3, 23);
|
||||||
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.pubkey) {
|
if (a.pubkey) {
|
||||||
let pkh = bcrypto.hash160(a.pubkey)
|
const pkh = bcrypto.hash160(a.pubkey);
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
else hash = pkh
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = pkh;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
let chunks = _chunks()
|
const chunks = _chunks();
|
||||||
if (chunks.length !== 2) throw new TypeError('Input is invalid')
|
if (chunks.length !== 2) throw new TypeError('Input is invalid');
|
||||||
if (!bscript.isCanonicalScriptSignature(chunks[0])) throw new TypeError('Input has invalid signature')
|
if (!bscript.isCanonicalScriptSignature(chunks[0]))
|
||||||
if (!ecc.isPoint(chunks[1])) throw new TypeError('Input has invalid pubkey')
|
throw new TypeError('Input has invalid signature');
|
||||||
|
if (!ecc.isPoint(chunks[1]))
|
||||||
if (a.signature && !a.signature.equals(chunks[0])) throw new TypeError('Signature mismatch')
|
throw new TypeError('Input has invalid pubkey');
|
||||||
if (a.pubkey && !a.pubkey.equals(chunks[1])) throw new TypeError('Pubkey mismatch')
|
if (a.signature && !a.signature.equals(chunks[0]))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
let pkh = bcrypto.hash160(chunks[1])
|
if (a.pubkey && !a.pubkey.equals(chunks[1]))
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
|
throw new TypeError('Pubkey mismatch');
|
||||||
|
const pkh = bcrypto.hash160(chunks[1]);
|
||||||
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2pkh = p2pkh;
|
||||||
module.exports = p2pkh
|
|
||||||
|
|
|
@ -1,187 +1,178 @@
|
||||||
const lazy = require('./lazy')
|
'use strict';
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require('../crypto');
|
||||||
|
const networks_1 = require('../networks');
|
||||||
const bcrypto = require('../crypto')
|
const bscript = require('../script');
|
||||||
const bscript = require('../script')
|
const lazy = require('./lazy');
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const typef = require('typeforce');
|
||||||
const bs58check = require('bs58check')
|
const OPS = bscript.OPS;
|
||||||
|
const bs58check = require('bs58check');
|
||||||
function stacksEqual (a, b) {
|
function stacksEqual(a, b) {
|
||||||
if (a.length !== b.length) return false
|
if (a.length !== b.length) return false;
|
||||||
|
return a.every((x, i) => {
|
||||||
return a.every(function (x, i) {
|
return x.equals(b[i]);
|
||||||
return x.equals(b[i])
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// input: [redeemScriptSig ...] {redeemScript}
|
// input: [redeemScriptSig ...] {redeemScript}
|
||||||
// witness: <?>
|
// witness: <?>
|
||||||
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
|
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
|
||||||
function p2sh (a, opts) {
|
function p2sh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.redeem && !a.input)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
typef(
|
||||||
!a.redeem &&
|
{
|
||||||
!a.input
|
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = opts || { validate: true }
|
|
||||||
|
|
||||||
typef({
|
|
||||||
network: typef.maybe(typef.Object),
|
|
||||||
|
|
||||||
address: typef.maybe(typef.String),
|
|
||||||
hash: typef.maybe(typef.BufferN(20)),
|
|
||||||
output: typef.maybe(typef.BufferN(23)),
|
|
||||||
|
|
||||||
redeem: typef.maybe({
|
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
output: typef.maybe(typef.Buffer),
|
address: typef.maybe(typef.String),
|
||||||
|
hash: typef.maybe(typef.BufferN(20)),
|
||||||
|
output: typef.maybe(typef.BufferN(23)),
|
||||||
|
redeem: typef.maybe({
|
||||||
|
network: typef.maybe(typef.Object),
|
||||||
|
output: typef.maybe(typef.Buffer),
|
||||||
|
input: typef.maybe(typef.Buffer),
|
||||||
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
|
}),
|
||||||
input: typef.maybe(typef.Buffer),
|
input: typef.maybe(typef.Buffer),
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
}),
|
},
|
||||||
input: typef.maybe(typef.Buffer),
|
a,
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
);
|
||||||
}, a)
|
let network = a.network;
|
||||||
|
if (!network) {
|
||||||
const network = a.network || BITCOIN_NETWORK
|
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
||||||
const o = { network }
|
}
|
||||||
|
const o = { network };
|
||||||
const _address = lazy.value(function () {
|
const _address = lazy.value(() => {
|
||||||
const payload = bs58check.decode(a.address)
|
const payload = bs58check.decode(a.address);
|
||||||
const version = payload.readUInt8(0)
|
const version = payload.readUInt8(0);
|
||||||
const hash = payload.slice(1)
|
const hash = payload.slice(1);
|
||||||
return { version, hash }
|
return { version, hash };
|
||||||
})
|
});
|
||||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input) })
|
const _chunks = lazy.value(() => {
|
||||||
const _redeem = lazy.value(function () {
|
return bscript.decompile(a.input);
|
||||||
const chunks = _chunks()
|
});
|
||||||
|
const _redeem = lazy.value(() => {
|
||||||
|
const chunks = _chunks();
|
||||||
return {
|
return {
|
||||||
network: network,
|
network,
|
||||||
output: chunks[chunks.length - 1],
|
output: chunks[chunks.length - 1],
|
||||||
input: bscript.compile(chunks.slice(0, -1)),
|
input: bscript.compile(chunks.slice(0, -1)),
|
||||||
witness: a.witness || []
|
witness: a.witness || [],
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
// output dependents
|
// output dependents
|
||||||
lazy.prop(o, 'address', function () {
|
lazy.prop(o, 'address', () => {
|
||||||
if (!o.hash) return
|
if (!o.hash) return;
|
||||||
|
const payload = Buffer.allocUnsafe(21);
|
||||||
const payload = Buffer.allocUnsafe(21)
|
payload.writeUInt8(o.network.scriptHash, 0);
|
||||||
payload.writeUInt8(network.scriptHash, 0)
|
o.hash.copy(payload, 1);
|
||||||
o.hash.copy(payload, 1)
|
return bs58check.encode(payload);
|
||||||
return bs58check.encode(payload)
|
});
|
||||||
})
|
lazy.prop(o, 'hash', () => {
|
||||||
lazy.prop(o, 'hash', function () {
|
|
||||||
// in order of least effort
|
// in order of least effort
|
||||||
if (a.output) return a.output.slice(2, 22)
|
if (a.output) return a.output.slice(2, 22);
|
||||||
if (a.address) return _address().hash
|
if (a.address) return _address().hash;
|
||||||
if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output)
|
if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'output', function () {
|
lazy.prop(o, 'output', () => {
|
||||||
if (!o.hash) return
|
if (!o.hash) return;
|
||||||
return bscript.compile([
|
return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]);
|
||||||
OPS.OP_HASH160,
|
});
|
||||||
o.hash,
|
|
||||||
OPS.OP_EQUAL
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
// input dependents
|
// input dependents
|
||||||
lazy.prop(o, 'redeem', function () {
|
lazy.prop(o, 'redeem', () => {
|
||||||
if (!a.input) return
|
if (!a.input) return;
|
||||||
return _redeem()
|
return _redeem();
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'input', function () {
|
lazy.prop(o, 'input', () => {
|
||||||
if (!a.redeem || !a.redeem.input || !a.redeem.output) return
|
if (!a.redeem || !a.redeem.input || !a.redeem.output) return;
|
||||||
return bscript.compile([].concat(
|
return bscript.compile(
|
||||||
bscript.decompile(a.redeem.input),
|
[].concat(bscript.decompile(a.redeem.input), a.redeem.output),
|
||||||
a.redeem.output
|
);
|
||||||
))
|
});
|
||||||
})
|
lazy.prop(o, 'witness', () => {
|
||||||
lazy.prop(o, 'witness', function () {
|
if (o.redeem && o.redeem.witness) return o.redeem.witness;
|
||||||
if (o.redeem && o.redeem.witness) return o.redeem.witness
|
if (o.input) return [];
|
||||||
if (o.input) return []
|
});
|
||||||
})
|
|
||||||
|
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (_address().version !== network.scriptHash) throw new TypeError('Invalid version or Network mismatch')
|
if (_address().version !== network.scriptHash)
|
||||||
if (_address().hash.length !== 20) throw new TypeError('Invalid address')
|
throw new TypeError('Invalid version or Network mismatch');
|
||||||
else hash = _address().hash
|
if (_address().hash.length !== 20) throw new TypeError('Invalid address');
|
||||||
|
hash = _address().hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (
|
||||||
a.output.length !== 23 ||
|
a.output.length !== 23 ||
|
||||||
a.output[0] !== OPS.OP_HASH160 ||
|
a.output[0] !== OPS.OP_HASH160 ||
|
||||||
a.output[1] !== 0x14 ||
|
a.output[1] !== 0x14 ||
|
||||||
a.output[22] !== OPS.OP_EQUAL) throw new TypeError('Output is invalid')
|
a.output[22] !== OPS.OP_EQUAL
|
||||||
const hash2 = a.output.slice(2, 22)
|
)
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
throw new TypeError('Output is invalid');
|
||||||
else hash = hash2
|
const hash2 = a.output.slice(2, 22);
|
||||||
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// inlined to prevent 'no-inner-declarations' failing
|
// inlined to prevent 'no-inner-declarations' failing
|
||||||
const checkRedeem = function (redeem) {
|
const checkRedeem = redeem => {
|
||||||
// is the redeem output empty/invalid?
|
// is the redeem output empty/invalid?
|
||||||
if (redeem.output) {
|
if (redeem.output) {
|
||||||
const decompile = bscript.decompile(redeem.output)
|
const decompile = bscript.decompile(redeem.output);
|
||||||
if (!decompile || decompile.length < 1) throw new TypeError('Redeem.output too short')
|
if (!decompile || decompile.length < 1)
|
||||||
|
throw new TypeError('Redeem.output too short');
|
||||||
// match hash against other sources
|
// match hash against other sources
|
||||||
const hash2 = bcrypto.hash160(redeem.output)
|
const hash2 = bcrypto.hash160(redeem.output);
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
else hash = hash2
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redeem.input) {
|
if (redeem.input) {
|
||||||
const hasInput = redeem.input.length > 0
|
const hasInput = redeem.input.length > 0;
|
||||||
const hasWitness = redeem.witness && redeem.witness.length > 0
|
const hasWitness = redeem.witness && redeem.witness.length > 0;
|
||||||
if (!hasInput && !hasWitness) throw new TypeError('Empty input')
|
if (!hasInput && !hasWitness) throw new TypeError('Empty input');
|
||||||
if (hasInput && hasWitness) throw new TypeError('Input and witness provided')
|
if (hasInput && hasWitness)
|
||||||
|
throw new TypeError('Input and witness provided');
|
||||||
if (hasInput) {
|
if (hasInput) {
|
||||||
const richunks = bscript.decompile(redeem.input)
|
const richunks = bscript.decompile(redeem.input);
|
||||||
if (!bscript.isPushOnly(richunks)) throw new TypeError('Non push-only scriptSig')
|
if (!bscript.isPushOnly(richunks))
|
||||||
|
throw new TypeError('Non push-only scriptSig');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (a.input) {
|
if (a.input) {
|
||||||
const chunks = _chunks()
|
const chunks = _chunks();
|
||||||
if (!chunks || chunks.length < 1) throw new TypeError('Input too short')
|
if (!chunks || chunks.length < 1) throw new TypeError('Input too short');
|
||||||
if (!Buffer.isBuffer(_redeem().output)) throw new TypeError('Input is invalid')
|
if (!Buffer.isBuffer(_redeem().output))
|
||||||
|
throw new TypeError('Input is invalid');
|
||||||
checkRedeem(_redeem())
|
checkRedeem(_redeem());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.redeem) {
|
if (a.redeem) {
|
||||||
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch')
|
if (a.redeem.network && a.redeem.network !== network)
|
||||||
if (o.redeem) {
|
throw new TypeError('Network mismatch');
|
||||||
if (a.redeem.output && !a.redeem.output.equals(o.redeem.output)) throw new TypeError('Redeem.output mismatch')
|
if (a.input) {
|
||||||
if (a.redeem.input && !a.redeem.input.equals(o.redeem.input)) throw new TypeError('Redeem.input mismatch')
|
const redeem = _redeem();
|
||||||
|
if (a.redeem.output && !a.redeem.output.equals(redeem.output))
|
||||||
|
throw new TypeError('Redeem.output mismatch');
|
||||||
|
if (a.redeem.input && !a.redeem.input.equals(redeem.input))
|
||||||
|
throw new TypeError('Redeem.input mismatch');
|
||||||
}
|
}
|
||||||
|
checkRedeem(a.redeem);
|
||||||
checkRedeem(a.redeem)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.witness) {
|
if (a.witness) {
|
||||||
if (
|
if (
|
||||||
a.redeem &&
|
a.redeem &&
|
||||||
a.redeem.witness &&
|
a.redeem.witness &&
|
||||||
!stacksEqual(a.redeem.witness, a.witness)) throw new TypeError('Witness and redeem.witness mismatch')
|
!stacksEqual(a.redeem.witness, a.witness)
|
||||||
|
)
|
||||||
|
throw new TypeError('Witness and redeem.witness mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2sh = p2sh;
|
||||||
module.exports = p2sh
|
|
||||||
|
|
|
@ -1,136 +1,128 @@
|
||||||
const lazy = require('./lazy')
|
'use strict';
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require('../crypto');
|
||||||
const ecc = require('tiny-secp256k1')
|
const networks_1 = require('../networks');
|
||||||
|
const bscript = require('../script');
|
||||||
const bcrypto = require('../crypto')
|
const lazy = require('./lazy');
|
||||||
const bech32 = require('bech32')
|
const typef = require('typeforce');
|
||||||
const bscript = require('../script')
|
const OPS = bscript.OPS;
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const bech32 = require('bech32');
|
||||||
const EMPTY_BUFFER = Buffer.alloc(0)
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
|
||||||
// witness: {signature} {pubKey}
|
// witness: {signature} {pubKey}
|
||||||
// input: <>
|
// input: <>
|
||||||
// output: OP_0 {pubKeyHash}
|
// output: OP_0 {pubKeyHash}
|
||||||
function p2wpkh (a, opts) {
|
function p2wpkh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
typef(
|
||||||
!a.pubkey &&
|
{
|
||||||
!a.witness
|
address: typef.maybe(typef.String),
|
||||||
) throw new TypeError('Not enough data')
|
hash: typef.maybe(typef.BufferN(20)),
|
||||||
opts = opts || { validate: true }
|
input: typef.maybe(typef.BufferN(0)),
|
||||||
|
network: typef.maybe(typef.Object),
|
||||||
typef({
|
output: typef.maybe(typef.BufferN(22)),
|
||||||
address: typef.maybe(typef.String),
|
pubkey: typef.maybe(ecc.isPoint),
|
||||||
hash: typef.maybe(typef.BufferN(20)),
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||||
input: typef.maybe(typef.BufferN(0)),
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
network: typef.maybe(typef.Object),
|
},
|
||||||
output: typef.maybe(typef.BufferN(22)),
|
a,
|
||||||
pubkey: typef.maybe(ecc.isPoint),
|
);
|
||||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
const _address = lazy.value(() => {
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
const result = bech32.decode(a.address);
|
||||||
}, a)
|
const version = result.words.shift();
|
||||||
|
const data = bech32.fromWords(result.words);
|
||||||
const _address = lazy.value(function () {
|
|
||||||
const result = bech32.decode(a.address)
|
|
||||||
const version = result.words.shift()
|
|
||||||
const data = bech32.fromWords(result.words)
|
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
prefix: result.prefix,
|
prefix: result.prefix,
|
||||||
data: Buffer.from(data)
|
data: Buffer.from(data),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
const network = a.network || networks_1.bitcoin;
|
||||||
const network = a.network || BITCOIN_NETWORK
|
const o = { network };
|
||||||
const o = { network }
|
lazy.prop(o, 'address', () => {
|
||||||
|
if (!o.hash) return;
|
||||||
lazy.prop(o, 'address', function () {
|
const words = bech32.toWords(o.hash);
|
||||||
if (!o.hash) return
|
words.unshift(0x00);
|
||||||
|
return bech32.encode(network.bech32, words);
|
||||||
const words = bech32.toWords(o.hash)
|
});
|
||||||
words.unshift(0x00)
|
lazy.prop(o, 'hash', () => {
|
||||||
return bech32.encode(network.bech32, words)
|
if (a.output) return a.output.slice(2, 22);
|
||||||
})
|
if (a.address) return _address().data;
|
||||||
lazy.prop(o, 'hash', function () {
|
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey);
|
||||||
if (a.output) return a.output.slice(2, 22)
|
});
|
||||||
if (a.address) return _address().data
|
lazy.prop(o, 'output', () => {
|
||||||
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey)
|
if (!o.hash) return;
|
||||||
})
|
return bscript.compile([OPS.OP_0, o.hash]);
|
||||||
lazy.prop(o, 'output', function () {
|
});
|
||||||
if (!o.hash) return
|
lazy.prop(o, 'pubkey', () => {
|
||||||
return bscript.compile([
|
if (a.pubkey) return a.pubkey;
|
||||||
OPS.OP_0,
|
if (!a.witness) return;
|
||||||
o.hash
|
return a.witness[1];
|
||||||
])
|
});
|
||||||
})
|
lazy.prop(o, 'signature', () => {
|
||||||
lazy.prop(o, 'pubkey', function () {
|
if (!a.witness) return;
|
||||||
if (a.pubkey) return a.pubkey
|
return a.witness[0];
|
||||||
if (!a.witness) return
|
});
|
||||||
return a.witness[1]
|
lazy.prop(o, 'input', () => {
|
||||||
})
|
if (!o.witness) return;
|
||||||
lazy.prop(o, 'signature', function () {
|
return EMPTY_BUFFER;
|
||||||
if (!a.witness) return
|
});
|
||||||
return a.witness[0]
|
lazy.prop(o, 'witness', () => {
|
||||||
})
|
if (!a.pubkey) return;
|
||||||
lazy.prop(o, 'input', function () {
|
if (!a.signature) return;
|
||||||
if (!o.witness) return
|
return [a.signature, a.pubkey];
|
||||||
return EMPTY_BUFFER
|
});
|
||||||
})
|
|
||||||
lazy.prop(o, 'witness', function () {
|
|
||||||
if (!a.pubkey) return
|
|
||||||
if (!a.signature) return
|
|
||||||
return [a.signature, a.pubkey]
|
|
||||||
})
|
|
||||||
|
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch')
|
if (network && network.bech32 !== _address().prefix)
|
||||||
if (_address().version !== 0x00) throw new TypeError('Invalid address version')
|
throw new TypeError('Invalid prefix or Network mismatch');
|
||||||
if (_address().data.length !== 20) throw new TypeError('Invalid address data')
|
if (_address().version !== 0x00)
|
||||||
// if (hash && !hash.equals(_address().data)) throw new TypeError('Hash mismatch')
|
throw new TypeError('Invalid address version');
|
||||||
hash = _address().data
|
if (_address().data.length !== 20)
|
||||||
|
throw new TypeError('Invalid address data');
|
||||||
|
hash = _address().data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (
|
||||||
a.output.length !== 22 ||
|
a.output.length !== 22 ||
|
||||||
a.output[0] !== OPS.OP_0 ||
|
a.output[0] !== OPS.OP_0 ||
|
||||||
a.output[1] !== 0x14) throw new TypeError('Output is invalid')
|
a.output[1] !== 0x14
|
||||||
if (hash && !hash.equals(a.output.slice(2))) throw new TypeError('Hash mismatch')
|
)
|
||||||
else hash = a.output.slice(2)
|
throw new TypeError('Output is invalid');
|
||||||
|
if (hash.length > 0 && !hash.equals(a.output.slice(2)))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = a.output.slice(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.pubkey) {
|
if (a.pubkey) {
|
||||||
const pkh = bcrypto.hash160(a.pubkey)
|
const pkh = bcrypto.hash160(a.pubkey);
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
else hash = pkh
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = pkh;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.witness) {
|
if (a.witness) {
|
||||||
if (a.witness.length !== 2) throw new TypeError('Witness is invalid')
|
if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
|
||||||
if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Witness has invalid signature')
|
if (!bscript.isCanonicalScriptSignature(a.witness[0]))
|
||||||
if (!ecc.isPoint(a.witness[1])) throw new TypeError('Witness has invalid pubkey')
|
throw new TypeError('Witness has invalid signature');
|
||||||
|
if (!ecc.isPoint(a.witness[1]))
|
||||||
if (a.signature && !a.signature.equals(a.witness[0])) throw new TypeError('Signature mismatch')
|
throw new TypeError('Witness has invalid pubkey');
|
||||||
if (a.pubkey && !a.pubkey.equals(a.witness[1])) throw new TypeError('Pubkey mismatch')
|
if (a.signature && !a.signature.equals(a.witness[0]))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
const pkh = bcrypto.hash160(a.witness[1])
|
if (a.pubkey && !a.pubkey.equals(a.witness[1]))
|
||||||
if (hash && !hash.equals(pkh)) throw new TypeError('Hash mismatch')
|
throw new TypeError('Pubkey mismatch');
|
||||||
|
const pkh = bcrypto.hash160(a.witness[1]);
|
||||||
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2wpkh = p2wpkh;
|
||||||
module.exports = p2wpkh
|
|
||||||
|
|
|
@ -1,98 +1,89 @@
|
||||||
const lazy = require('./lazy')
|
'use strict';
|
||||||
const typef = require('typeforce')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const OPS = require('bitcoin-ops')
|
const bcrypto = require('../crypto');
|
||||||
|
const networks_1 = require('../networks');
|
||||||
const bech32 = require('bech32')
|
const bscript = require('../script');
|
||||||
const bcrypto = require('../crypto')
|
const lazy = require('./lazy');
|
||||||
const bscript = require('../script')
|
const typef = require('typeforce');
|
||||||
const BITCOIN_NETWORK = require('../networks').bitcoin
|
const OPS = bscript.OPS;
|
||||||
|
const bech32 = require('bech32');
|
||||||
const EMPTY_BUFFER = Buffer.alloc(0)
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
function stacksEqual(a, b) {
|
||||||
function stacksEqual (a, b) {
|
if (a.length !== b.length) return false;
|
||||||
if (a.length !== b.length) return false
|
return a.every((x, i) => {
|
||||||
|
return x.equals(b[i]);
|
||||||
return a.every(function (x, i) {
|
});
|
||||||
return x.equals(b[i])
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// input: <>
|
// input: <>
|
||||||
// witness: [redeemScriptSig ...] {redeemScript}
|
// witness: [redeemScriptSig ...] {redeemScript}
|
||||||
// output: OP_0 {sha256(redeemScript)}
|
// output: OP_0 {sha256(redeemScript)}
|
||||||
function p2wsh (a, opts) {
|
function p2wsh(a, opts) {
|
||||||
if (
|
if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness)
|
||||||
!a.address &&
|
throw new TypeError('Not enough data');
|
||||||
!a.hash &&
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
!a.output &&
|
typef(
|
||||||
!a.redeem &&
|
{
|
||||||
!a.witness
|
|
||||||
) throw new TypeError('Not enough data')
|
|
||||||
opts = opts || { validate: true }
|
|
||||||
|
|
||||||
typef({
|
|
||||||
network: typef.maybe(typef.Object),
|
|
||||||
|
|
||||||
address: typef.maybe(typef.String),
|
|
||||||
hash: typef.maybe(typef.BufferN(32)),
|
|
||||||
output: typef.maybe(typef.BufferN(34)),
|
|
||||||
|
|
||||||
redeem: typef.maybe({
|
|
||||||
input: typef.maybe(typef.Buffer),
|
|
||||||
network: typef.maybe(typef.Object),
|
network: typef.maybe(typef.Object),
|
||||||
output: typef.maybe(typef.Buffer),
|
address: typef.maybe(typef.String),
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
hash: typef.maybe(typef.BufferN(32)),
|
||||||
}),
|
output: typef.maybe(typef.BufferN(34)),
|
||||||
input: typef.maybe(typef.BufferN(0)),
|
redeem: typef.maybe({
|
||||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
input: typef.maybe(typef.Buffer),
|
||||||
}, a)
|
network: typef.maybe(typef.Object),
|
||||||
|
output: typef.maybe(typef.Buffer),
|
||||||
const _address = lazy.value(function () {
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
const result = bech32.decode(a.address)
|
}),
|
||||||
const version = result.words.shift()
|
input: typef.maybe(typef.BufferN(0)),
|
||||||
const data = bech32.fromWords(result.words)
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
|
},
|
||||||
|
a,
|
||||||
|
);
|
||||||
|
const _address = lazy.value(() => {
|
||||||
|
const result = bech32.decode(a.address);
|
||||||
|
const version = result.words.shift();
|
||||||
|
const data = bech32.fromWords(result.words);
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
prefix: result.prefix,
|
prefix: result.prefix,
|
||||||
data: Buffer.from(data)
|
data: Buffer.from(data),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input) })
|
const _rchunks = lazy.value(() => {
|
||||||
|
return bscript.decompile(a.redeem.input);
|
||||||
const network = a.network || BITCOIN_NETWORK
|
});
|
||||||
const o = { network }
|
let network = a.network;
|
||||||
|
if (!network) {
|
||||||
lazy.prop(o, 'address', function () {
|
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
||||||
if (!o.hash) return
|
}
|
||||||
const words = bech32.toWords(o.hash)
|
const o = { network };
|
||||||
words.unshift(0x00)
|
lazy.prop(o, 'address', () => {
|
||||||
return bech32.encode(network.bech32, words)
|
if (!o.hash) return;
|
||||||
})
|
const words = bech32.toWords(o.hash);
|
||||||
lazy.prop(o, 'hash', function () {
|
words.unshift(0x00);
|
||||||
if (a.output) return a.output.slice(2)
|
return bech32.encode(network.bech32, words);
|
||||||
if (a.address) return _address().data
|
});
|
||||||
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output)
|
lazy.prop(o, 'hash', () => {
|
||||||
})
|
if (a.output) return a.output.slice(2);
|
||||||
lazy.prop(o, 'output', function () {
|
if (a.address) return _address().data;
|
||||||
if (!o.hash) return
|
if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output);
|
||||||
return bscript.compile([
|
});
|
||||||
OPS.OP_0,
|
lazy.prop(o, 'output', () => {
|
||||||
o.hash
|
if (!o.hash) return;
|
||||||
])
|
return bscript.compile([OPS.OP_0, o.hash]);
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'redeem', function () {
|
lazy.prop(o, 'redeem', () => {
|
||||||
if (!a.witness) return
|
if (!a.witness) return;
|
||||||
return {
|
return {
|
||||||
output: a.witness[a.witness.length - 1],
|
output: a.witness[a.witness.length - 1],
|
||||||
input: EMPTY_BUFFER,
|
input: EMPTY_BUFFER,
|
||||||
witness: a.witness.slice(0, -1)
|
witness: a.witness.slice(0, -1),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'input', function () {
|
lazy.prop(o, 'input', () => {
|
||||||
if (!o.witness) return
|
if (!o.witness) return;
|
||||||
return EMPTY_BUFFER
|
return EMPTY_BUFFER;
|
||||||
})
|
});
|
||||||
lazy.prop(o, 'witness', function () {
|
lazy.prop(o, 'witness', () => {
|
||||||
// transform redeem input to witness stack?
|
// transform redeem input to witness stack?
|
||||||
if (
|
if (
|
||||||
a.redeem &&
|
a.redeem &&
|
||||||
|
@ -101,76 +92,85 @@ function p2wsh (a, opts) {
|
||||||
a.redeem.output &&
|
a.redeem.output &&
|
||||||
a.redeem.output.length > 0
|
a.redeem.output.length > 0
|
||||||
) {
|
) {
|
||||||
const stack = bscript.toStack(_rchunks())
|
const stack = bscript.toStack(_rchunks());
|
||||||
|
|
||||||
// assign, and blank the existing input
|
// assign, and blank the existing input
|
||||||
o.redeem = Object.assign({ witness: stack }, a.redeem)
|
o.redeem = Object.assign({ witness: stack }, a.redeem);
|
||||||
o.redeem.input = EMPTY_BUFFER
|
o.redeem.input = EMPTY_BUFFER;
|
||||||
return [].concat(stack, a.redeem.output)
|
return [].concat(stack, a.redeem.output);
|
||||||
}
|
}
|
||||||
|
if (!a.redeem) return;
|
||||||
if (!a.redeem) return
|
if (!a.redeem.output) return;
|
||||||
if (!a.redeem.output) return
|
if (!a.redeem.witness) return;
|
||||||
if (!a.redeem.witness) return
|
return [].concat(a.redeem.witness, a.redeem.output);
|
||||||
return [].concat(a.redeem.witness, a.redeem.output)
|
});
|
||||||
})
|
|
||||||
|
|
||||||
// extended validation
|
// extended validation
|
||||||
if (opts.validate) {
|
if (opts.validate) {
|
||||||
let hash
|
let hash = Buffer.from([]);
|
||||||
if (a.address) {
|
if (a.address) {
|
||||||
if (_address().prefix !== network.bech32) throw new TypeError('Invalid prefix or Network mismatch')
|
if (_address().prefix !== network.bech32)
|
||||||
if (_address().version !== 0x00) throw new TypeError('Invalid address version')
|
throw new TypeError('Invalid prefix or Network mismatch');
|
||||||
if (_address().data.length !== 32) throw new TypeError('Invalid address data')
|
if (_address().version !== 0x00)
|
||||||
else hash = _address().data
|
throw new TypeError('Invalid address version');
|
||||||
|
if (_address().data.length !== 32)
|
||||||
|
throw new TypeError('Invalid address data');
|
||||||
|
hash = _address().data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.hash) {
|
if (a.hash) {
|
||||||
if (hash && !hash.equals(a.hash)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
else hash = a.hash
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = a.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.output) {
|
if (a.output) {
|
||||||
if (
|
if (
|
||||||
a.output.length !== 34 ||
|
a.output.length !== 34 ||
|
||||||
a.output[0] !== OPS.OP_0 ||
|
a.output[0] !== OPS.OP_0 ||
|
||||||
a.output[1] !== 0x20) throw new TypeError('Output is invalid')
|
a.output[1] !== 0x20
|
||||||
const hash2 = a.output.slice(2)
|
)
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
throw new TypeError('Output is invalid');
|
||||||
else hash = hash2
|
const hash2 = a.output.slice(2);
|
||||||
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.redeem) {
|
if (a.redeem) {
|
||||||
if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch')
|
if (a.redeem.network && a.redeem.network !== network)
|
||||||
|
throw new TypeError('Network mismatch');
|
||||||
// is there two redeem sources?
|
// is there two redeem sources?
|
||||||
if (
|
if (
|
||||||
a.redeem.input &&
|
a.redeem.input &&
|
||||||
a.redeem.input.length > 0 &&
|
a.redeem.input.length > 0 &&
|
||||||
a.redeem.witness &&
|
a.redeem.witness &&
|
||||||
a.redeem.witness.length > 0
|
a.redeem.witness.length > 0
|
||||||
) throw new TypeError('Ambiguous witness source')
|
)
|
||||||
|
throw new TypeError('Ambiguous witness source');
|
||||||
// is the redeem output non-empty?
|
// is the redeem output non-empty?
|
||||||
if (a.redeem.output) {
|
if (a.redeem.output) {
|
||||||
if (bscript.decompile(a.redeem.output).length === 0) throw new TypeError('Redeem.output is invalid')
|
if (bscript.decompile(a.redeem.output).length === 0)
|
||||||
|
throw new TypeError('Redeem.output is invalid');
|
||||||
// match hash against other sources
|
// match hash against other sources
|
||||||
const hash2 = bcrypto.sha256(a.redeem.output)
|
const hash2 = bcrypto.sha256(a.redeem.output);
|
||||||
if (hash && !hash.equals(hash2)) throw new TypeError('Hash mismatch')
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
else hash = hash2
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = hash2;
|
||||||
}
|
}
|
||||||
|
if (a.redeem.input && !bscript.isPushOnly(_rchunks()))
|
||||||
if (a.redeem.input && !bscript.isPushOnly(_rchunks())) throw new TypeError('Non push-only scriptSig')
|
throw new TypeError('Non push-only scriptSig');
|
||||||
if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) throw new TypeError('Witness and redeem.witness mismatch')
|
if (
|
||||||
|
a.witness &&
|
||||||
|
a.redeem.witness &&
|
||||||
|
!stacksEqual(a.witness, a.redeem.witness)
|
||||||
|
)
|
||||||
|
throw new TypeError('Witness and redeem.witness mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.witness) {
|
if (a.witness) {
|
||||||
if (a.redeem && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1])) throw new TypeError('Witness and redeem.output mismatch')
|
if (
|
||||||
|
a.redeem &&
|
||||||
|
a.redeem.output &&
|
||||||
|
!a.redeem.output.equals(a.witness[a.witness.length - 1])
|
||||||
|
)
|
||||||
|
throw new TypeError('Witness and redeem.output mismatch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
return Object.assign(o, a)
|
|
||||||
}
|
}
|
||||||
|
exports.p2wsh = p2wsh;
|
||||||
module.exports = p2wsh
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"name": "bitcoinjs-playground",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Go nuts!",
|
|
||||||
"main": "_testnet.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/bitcoinjs/bitcoinjs-playground.git"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/bitcoinjs/bitcoinjs-playground/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/bitcoinjs/bitcoinjs-playground#readme",
|
|
||||||
"dependencies": {
|
|
||||||
"async": "^2.5.0",
|
|
||||||
"bech32": "^1.1.3",
|
|
||||||
"bip21": "^2.0.1",
|
|
||||||
"bip32-utils": "^0.11.1",
|
|
||||||
"bip38": "^2.0.2",
|
|
||||||
"bip39": "^2.5.0",
|
|
||||||
"bip69": "^2.1.1",
|
|
||||||
"bitcoin-ops": "^1.4.1",
|
|
||||||
"bitcoinjs-lib": "^3.3.2",
|
|
||||||
"bs58": "^4.0.1",
|
|
||||||
"bs58check": "^2.1.1",
|
|
||||||
"cb-http-client": "^0.2.3",
|
|
||||||
"coinselect": "^3.1.11",
|
|
||||||
"dhttp": "^2.4.2",
|
|
||||||
"merkle-lib": "^2.0.10",
|
|
||||||
"mocha": "^5.0.5",
|
|
||||||
"tape": "^4.9.0",
|
|
||||||
"typeforce": "^1.11.4",
|
|
||||||
"utxo": "^2.0.4"
|
|
||||||
}
|
|
||||||
}
|
|
306
src/script.js
306
src/script.js
|
@ -1,205 +1,177 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
'use strict';
|
||||||
const bip66 = require('bip66')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const ecc = require('tiny-secp256k1')
|
const scriptNumber = require('./script_number');
|
||||||
const pushdata = require('pushdata-bitcoin')
|
const scriptSignature = require('./script_signature');
|
||||||
const typeforce = require('typeforce')
|
const types = require('./types');
|
||||||
const types = require('./types')
|
const bip66 = require('bip66');
|
||||||
const scriptNumber = require('./script_number')
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const pushdata = require('pushdata-bitcoin');
|
||||||
const OPS = require('bitcoin-ops')
|
const typeforce = require('typeforce');
|
||||||
const REVERSE_OPS = require('bitcoin-ops/map')
|
exports.OPS = require('bitcoin-ops');
|
||||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const REVERSE_OPS = require('bitcoin-ops/map');
|
||||||
|
const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1
|
||||||
function isOPInt (value) {
|
function isOPInt(value) {
|
||||||
return types.Number(value) &&
|
return (
|
||||||
((value === OPS.OP_0) ||
|
types.Number(value) &&
|
||||||
(value >= OPS.OP_1 && value <= OPS.OP_16) ||
|
(value === exports.OPS.OP_0 ||
|
||||||
(value === OPS.OP_1NEGATE))
|
(value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) ||
|
||||||
|
value === exports.OPS.OP_1NEGATE)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
function isPushOnlyChunk(value) {
|
||||||
function isPushOnlyChunk (value) {
|
return types.Buffer(value) || isOPInt(value);
|
||||||
return types.Buffer(value) || isOPInt(value)
|
|
||||||
}
|
}
|
||||||
|
function isPushOnly(value) {
|
||||||
function isPushOnly (value) {
|
return types.Array(value) && value.every(isPushOnlyChunk);
|
||||||
return types.Array(value) && value.every(isPushOnlyChunk)
|
|
||||||
}
|
}
|
||||||
|
exports.isPushOnly = isPushOnly;
|
||||||
function asMinimalOP (buffer) {
|
function asMinimalOP(buffer) {
|
||||||
if (buffer.length === 0) return OPS.OP_0
|
if (buffer.length === 0) return exports.OPS.OP_0;
|
||||||
if (buffer.length !== 1) return
|
if (buffer.length !== 1) return;
|
||||||
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]
|
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0];
|
||||||
if (buffer[0] === 0x81) return OPS.OP_1NEGATE
|
if (buffer[0] === 0x81) return exports.OPS.OP_1NEGATE;
|
||||||
}
|
}
|
||||||
|
function chunksIsBuffer(buf) {
|
||||||
function compile (chunks) {
|
return Buffer.isBuffer(buf);
|
||||||
|
}
|
||||||
|
function chunksIsArray(buf) {
|
||||||
|
return types.Array(buf);
|
||||||
|
}
|
||||||
|
function singleChunkIsBuffer(buf) {
|
||||||
|
return Buffer.isBuffer(buf);
|
||||||
|
}
|
||||||
|
function compile(chunks) {
|
||||||
// TODO: remove me
|
// TODO: remove me
|
||||||
if (Buffer.isBuffer(chunks)) return chunks
|
if (chunksIsBuffer(chunks)) return chunks;
|
||||||
|
typeforce(types.Array, chunks);
|
||||||
typeforce(types.Array, chunks)
|
const bufferSize = chunks.reduce((accum, chunk) => {
|
||||||
|
|
||||||
const bufferSize = chunks.reduce(function (accum, chunk) {
|
|
||||||
// data chunk
|
// data chunk
|
||||||
if (Buffer.isBuffer(chunk)) {
|
if (singleChunkIsBuffer(chunk)) {
|
||||||
// adhere to BIP62.3, minimal push policy
|
// adhere to BIP62.3, minimal push policy
|
||||||
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
|
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
|
||||||
return accum + 1
|
return accum + 1;
|
||||||
}
|
}
|
||||||
|
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
|
||||||
return accum + pushdata.encodingLength(chunk.length) + chunk.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// opcode
|
// opcode
|
||||||
return accum + 1
|
return accum + 1;
|
||||||
}, 0.0)
|
}, 0.0);
|
||||||
|
const buffer = Buffer.allocUnsafe(bufferSize);
|
||||||
const buffer = Buffer.allocUnsafe(bufferSize)
|
let offset = 0;
|
||||||
let offset = 0
|
chunks.forEach(chunk => {
|
||||||
|
|
||||||
chunks.forEach(function (chunk) {
|
|
||||||
// data chunk
|
// data chunk
|
||||||
if (Buffer.isBuffer(chunk)) {
|
if (singleChunkIsBuffer(chunk)) {
|
||||||
// adhere to BIP62.3, minimal push policy
|
// adhere to BIP62.3, minimal push policy
|
||||||
const opcode = asMinimalOP(chunk)
|
const opcode = asMinimalOP(chunk);
|
||||||
if (opcode !== undefined) {
|
if (opcode !== undefined) {
|
||||||
buffer.writeUInt8(opcode, offset)
|
buffer.writeUInt8(opcode, offset);
|
||||||
offset += 1
|
offset += 1;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
offset += pushdata.encode(buffer, chunk.length, offset);
|
||||||
offset += pushdata.encode(buffer, chunk.length, offset)
|
chunk.copy(buffer, offset);
|
||||||
chunk.copy(buffer, offset)
|
offset += chunk.length;
|
||||||
offset += chunk.length
|
// opcode
|
||||||
|
|
||||||
// opcode
|
|
||||||
} else {
|
} else {
|
||||||
buffer.writeUInt8(chunk, offset)
|
buffer.writeUInt8(chunk, offset);
|
||||||
offset += 1
|
offset += 1;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
if (offset !== buffer.length) throw new Error('Could not decode chunks');
|
||||||
if (offset !== buffer.length) throw new Error('Could not decode chunks')
|
return buffer;
|
||||||
return buffer
|
|
||||||
}
|
}
|
||||||
|
exports.compile = compile;
|
||||||
function decompile (buffer) {
|
function decompile(buffer) {
|
||||||
// TODO: remove me
|
// TODO: remove me
|
||||||
if (types.Array(buffer)) return buffer
|
if (chunksIsArray(buffer)) return buffer;
|
||||||
|
typeforce(types.Buffer, buffer);
|
||||||
typeforce(types.Buffer, buffer)
|
const chunks = [];
|
||||||
|
let i = 0;
|
||||||
const chunks = []
|
|
||||||
let i = 0
|
|
||||||
|
|
||||||
while (i < buffer.length) {
|
while (i < buffer.length) {
|
||||||
const opcode = buffer[i]
|
const opcode = buffer[i];
|
||||||
|
|
||||||
// data chunk
|
// data chunk
|
||||||
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) {
|
if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) {
|
||||||
const d = pushdata.decode(buffer, i)
|
const d = pushdata.decode(buffer, i);
|
||||||
|
|
||||||
// did reading a pushDataInt fail?
|
// did reading a pushDataInt fail?
|
||||||
if (d === null) return null
|
if (d === null) return null;
|
||||||
i += d.size
|
i += d.size;
|
||||||
|
|
||||||
// attempt to read too much data?
|
// attempt to read too much data?
|
||||||
if (i + d.number > buffer.length) return null
|
if (i + d.number > buffer.length) return null;
|
||||||
|
const data = buffer.slice(i, i + d.number);
|
||||||
const data = buffer.slice(i, i + d.number)
|
i += d.number;
|
||||||
i += d.number
|
|
||||||
|
|
||||||
// decompile minimally
|
// decompile minimally
|
||||||
const op = asMinimalOP(data)
|
const op = asMinimalOP(data);
|
||||||
if (op !== undefined) {
|
if (op !== undefined) {
|
||||||
chunks.push(op)
|
chunks.push(op);
|
||||||
} else {
|
} else {
|
||||||
chunks.push(data)
|
chunks.push(data);
|
||||||
}
|
}
|
||||||
|
// opcode
|
||||||
// opcode
|
|
||||||
} else {
|
} else {
|
||||||
chunks.push(opcode)
|
chunks.push(opcode);
|
||||||
|
i += 1;
|
||||||
i += 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
exports.decompile = decompile;
|
||||||
|
function toASM(chunks) {
|
||||||
|
if (chunksIsBuffer(chunks)) {
|
||||||
|
chunks = decompile(chunks);
|
||||||
|
}
|
||||||
return chunks
|
return chunks
|
||||||
|
.map(chunk => {
|
||||||
|
// data?
|
||||||
|
if (singleChunkIsBuffer(chunk)) {
|
||||||
|
const op = asMinimalOP(chunk);
|
||||||
|
if (op === undefined) return chunk.toString('hex');
|
||||||
|
chunk = op;
|
||||||
|
}
|
||||||
|
// opcode!
|
||||||
|
return REVERSE_OPS[chunk];
|
||||||
|
})
|
||||||
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
exports.toASM = toASM;
|
||||||
function toASM (chunks) {
|
function fromASM(asm) {
|
||||||
if (Buffer.isBuffer(chunks)) {
|
typeforce(types.String, asm);
|
||||||
chunks = decompile(chunks)
|
return compile(
|
||||||
}
|
asm.split(' ').map(chunkStr => {
|
||||||
|
// opcode?
|
||||||
return chunks.map(function (chunk) {
|
if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr];
|
||||||
// data?
|
typeforce(types.Hex, chunkStr);
|
||||||
if (Buffer.isBuffer(chunk)) {
|
// data!
|
||||||
const op = asMinimalOP(chunk)
|
return Buffer.from(chunkStr, 'hex');
|
||||||
if (op === undefined) return chunk.toString('hex')
|
}),
|
||||||
chunk = op
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// opcode!
|
|
||||||
return REVERSE_OPS[chunk]
|
|
||||||
}).join(' ')
|
|
||||||
}
|
}
|
||||||
|
exports.fromASM = fromASM;
|
||||||
function fromASM (asm) {
|
function toStack(chunks) {
|
||||||
typeforce(types.String, asm)
|
chunks = decompile(chunks);
|
||||||
|
typeforce(isPushOnly, chunks);
|
||||||
return compile(asm.split(' ').map(function (chunkStr) {
|
return chunks.map(op => {
|
||||||
// opcode?
|
if (singleChunkIsBuffer(op)) return op;
|
||||||
if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
|
if (op === exports.OPS.OP_0) return Buffer.allocUnsafe(0);
|
||||||
typeforce(types.Hex, chunkStr)
|
return scriptNumber.encode(op - OP_INT_BASE);
|
||||||
|
});
|
||||||
// data!
|
|
||||||
return Buffer.from(chunkStr, 'hex')
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
exports.toStack = toStack;
|
||||||
function toStack (chunks) {
|
function isCanonicalPubKey(buffer) {
|
||||||
chunks = decompile(chunks)
|
return ecc.isPoint(buffer);
|
||||||
typeforce(isPushOnly, chunks)
|
|
||||||
|
|
||||||
return chunks.map(function (op) {
|
|
||||||
if (Buffer.isBuffer(op)) return op
|
|
||||||
if (op === OPS.OP_0) return Buffer.allocUnsafe(0)
|
|
||||||
|
|
||||||
return scriptNumber.encode(op - OP_INT_BASE)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
exports.isCanonicalPubKey = isCanonicalPubKey;
|
||||||
function isCanonicalPubKey (buffer) {
|
function isDefinedHashType(hashType) {
|
||||||
return ecc.isPoint(buffer)
|
const hashTypeMod = hashType & ~0x80;
|
||||||
}
|
|
||||||
|
|
||||||
function isDefinedHashType (hashType) {
|
|
||||||
const hashTypeMod = hashType & ~0x80
|
|
||||||
|
|
||||||
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
||||||
return hashTypeMod > 0x00 && hashTypeMod < 0x04
|
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
|
||||||
}
|
}
|
||||||
|
exports.isDefinedHashType = isDefinedHashType;
|
||||||
function isCanonicalScriptSignature (buffer) {
|
function isCanonicalScriptSignature(buffer) {
|
||||||
if (!Buffer.isBuffer(buffer)) return false
|
if (!Buffer.isBuffer(buffer)) return false;
|
||||||
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
|
if (!isDefinedHashType(buffer[buffer.length - 1])) return false;
|
||||||
|
return bip66.check(buffer.slice(0, -1));
|
||||||
return bip66.check(buffer.slice(0, -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
compile: compile,
|
|
||||||
decompile: decompile,
|
|
||||||
fromASM: fromASM,
|
|
||||||
toASM: toASM,
|
|
||||||
toStack: toStack,
|
|
||||||
|
|
||||||
number: require('./script_number'),
|
|
||||||
signature: require('./script_signature'),
|
|
||||||
|
|
||||||
isCanonicalPubKey: isCanonicalPubKey,
|
|
||||||
isCanonicalScriptSignature: isCanonicalScriptSignature,
|
|
||||||
isPushOnly: isPushOnly,
|
|
||||||
isDefinedHashType: isDefinedHashType
|
|
||||||
}
|
}
|
||||||
|
exports.isCanonicalScriptSignature = isCanonicalScriptSignature;
|
||||||
|
// tslint:disable-next-line variable-name
|
||||||
|
exports.number = scriptNumber;
|
||||||
|
exports.signature = scriptSignature;
|
||||||
|
|
|
@ -1,67 +1,61 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
function decode (buffer, maxLength, minimal) {
|
function decode(buffer, maxLength, minimal) {
|
||||||
maxLength = maxLength || 4
|
maxLength = maxLength || 4;
|
||||||
minimal = minimal === undefined ? true : minimal
|
minimal = minimal === undefined ? true : minimal;
|
||||||
|
const length = buffer.length;
|
||||||
const length = buffer.length
|
if (length === 0) return 0;
|
||||||
if (length === 0) return 0
|
if (length > maxLength) throw new TypeError('Script number overflow');
|
||||||
if (length > maxLength) throw new TypeError('Script number overflow')
|
|
||||||
if (minimal) {
|
if (minimal) {
|
||||||
if ((buffer[length - 1] & 0x7f) === 0) {
|
if ((buffer[length - 1] & 0x7f) === 0) {
|
||||||
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) throw new Error('Non-minimally encoded script number')
|
if (length <= 1 || (buffer[length - 2] & 0x80) === 0)
|
||||||
|
throw new Error('Non-minimally encoded script number');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 40-bit
|
// 40-bit
|
||||||
if (length === 5) {
|
if (length === 5) {
|
||||||
const a = buffer.readUInt32LE(0)
|
const a = buffer.readUInt32LE(0);
|
||||||
const b = buffer.readUInt8(4)
|
const b = buffer.readUInt8(4);
|
||||||
|
if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a);
|
||||||
if (b & 0x80) return -(((b & ~0x80) * 0x100000000) + a)
|
return b * 0x100000000 + a;
|
||||||
return (b * 0x100000000) + a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 32-bit / 24-bit / 16-bit / 8-bit
|
// 32-bit / 24-bit / 16-bit / 8-bit
|
||||||
let result = 0
|
let result = 0;
|
||||||
for (var i = 0; i < length; ++i) {
|
for (let i = 0; i < length; ++i) {
|
||||||
result |= buffer[i] << (8 * i)
|
result |= buffer[i] << (8 * i);
|
||||||
}
|
}
|
||||||
|
if (buffer[length - 1] & 0x80)
|
||||||
if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1))))
|
return -(result & ~(0x80 << (8 * (length - 1))));
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
exports.decode = decode;
|
||||||
function scriptNumSize (i) {
|
function scriptNumSize(i) {
|
||||||
return i > 0x7fffffff ? 5
|
return i > 0x7fffffff
|
||||||
: i > 0x7fffff ? 4
|
? 5
|
||||||
: i > 0x7fff ? 3
|
: i > 0x7fffff
|
||||||
: i > 0x7f ? 2
|
? 4
|
||||||
: i > 0x00 ? 1
|
: i > 0x7fff
|
||||||
: 0
|
? 3
|
||||||
|
: i > 0x7f
|
||||||
|
? 2
|
||||||
|
: i > 0x00
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
}
|
}
|
||||||
|
function encode(_number) {
|
||||||
function encode (number) {
|
let value = Math.abs(_number);
|
||||||
let value = Math.abs(number)
|
const size = scriptNumSize(value);
|
||||||
const size = scriptNumSize(value)
|
const buffer = Buffer.allocUnsafe(size);
|
||||||
const buffer = Buffer.allocUnsafe(size)
|
const negative = _number < 0;
|
||||||
const negative = number < 0
|
for (let i = 0; i < size; ++i) {
|
||||||
|
buffer.writeUInt8(value & 0xff, i);
|
||||||
for (var i = 0; i < size; ++i) {
|
value >>= 8;
|
||||||
buffer.writeUInt8(value & 0xff, i)
|
|
||||||
value >>= 8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer[size - 1] & 0x80) {
|
if (buffer[size - 1] & 0x80) {
|
||||||
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1)
|
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1);
|
||||||
} else if (negative) {
|
} else if (negative) {
|
||||||
buffer[size - 1] |= 0x80
|
buffer[size - 1] |= 0x80;
|
||||||
}
|
}
|
||||||
|
return buffer;
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
decode: decode,
|
|
||||||
encode: encode
|
|
||||||
}
|
}
|
||||||
|
exports.encode = encode;
|
||||||
|
|
|
@ -1,64 +1,52 @@
|
||||||
const bip66 = require('bip66')
|
'use strict';
|
||||||
const Buffer = require('safe-buffer').Buffer
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const typeforce = require('typeforce')
|
const types = require('./types');
|
||||||
const types = require('./types')
|
const bip66 = require('bip66');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
const ZERO = Buffer.alloc(1, 0)
|
const ZERO = Buffer.alloc(1, 0);
|
||||||
function toDER (x) {
|
function toDER(x) {
|
||||||
let i = 0
|
let i = 0;
|
||||||
while (x[i] === 0) ++i
|
while (x[i] === 0) ++i;
|
||||||
if (i === x.length) return ZERO
|
if (i === x.length) return ZERO;
|
||||||
x = x.slice(i)
|
x = x.slice(i);
|
||||||
if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length)
|
if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length);
|
||||||
return x
|
return x;
|
||||||
}
|
}
|
||||||
|
function fromDER(x) {
|
||||||
function fromDER (x) {
|
if (x[0] === 0x00) x = x.slice(1);
|
||||||
if (x[0] === 0x00) x = x.slice(1)
|
const buffer = Buffer.alloc(32, 0);
|
||||||
const buffer = Buffer.alloc(32, 0)
|
const bstart = Math.max(0, 32 - x.length);
|
||||||
const bstart = Math.max(0, 32 - x.length)
|
x.copy(buffer, bstart);
|
||||||
x.copy(buffer, bstart)
|
return buffer;
|
||||||
return buffer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
|
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
|
||||||
function decode (buffer) {
|
function decode(buffer) {
|
||||||
const hashType = buffer.readUInt8(buffer.length - 1)
|
const hashType = buffer.readUInt8(buffer.length - 1);
|
||||||
const hashTypeMod = hashType & ~0x80
|
const hashTypeMod = hashType & ~0x80;
|
||||||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType)
|
if (hashTypeMod <= 0 || hashTypeMod >= 4)
|
||||||
|
throw new Error('Invalid hashType ' + hashType);
|
||||||
const decode = bip66.decode(buffer.slice(0, -1))
|
const decoded = bip66.decode(buffer.slice(0, -1));
|
||||||
const r = fromDER(decode.r)
|
const r = fromDER(decoded.r);
|
||||||
const s = fromDER(decode.s)
|
const s = fromDER(decoded.s);
|
||||||
|
const signature = Buffer.concat([r, s], 64);
|
||||||
return {
|
return { signature, hashType };
|
||||||
signature: Buffer.concat([r, s], 64),
|
|
||||||
hashType: hashType
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
exports.decode = decode;
|
||||||
function encode (signature, hashType) {
|
function encode(signature, hashType) {
|
||||||
typeforce({
|
typeforce(
|
||||||
signature: types.BufferN(64),
|
{
|
||||||
hashType: types.UInt8
|
signature: types.BufferN(64),
|
||||||
}, { signature, hashType })
|
hashType: types.UInt8,
|
||||||
|
},
|
||||||
const hashTypeMod = hashType & ~0x80
|
{ signature, hashType },
|
||||||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType)
|
);
|
||||||
|
const hashTypeMod = hashType & ~0x80;
|
||||||
const hashTypeBuffer = Buffer.allocUnsafe(1)
|
if (hashTypeMod <= 0 || hashTypeMod >= 4)
|
||||||
hashTypeBuffer.writeUInt8(hashType, 0)
|
throw new Error('Invalid hashType ' + hashType);
|
||||||
|
const hashTypeBuffer = Buffer.allocUnsafe(1);
|
||||||
const r = toDER(signature.slice(0, 32))
|
hashTypeBuffer.writeUInt8(hashType, 0);
|
||||||
const s = toDER(signature.slice(32, 64))
|
const r = toDER(signature.slice(0, 32));
|
||||||
|
const s = toDER(signature.slice(32, 64));
|
||||||
return Buffer.concat([
|
return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]);
|
||||||
bip66.encode(r, s),
|
|
||||||
hashTypeBuffer
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
decode: decode,
|
|
||||||
encode: encode
|
|
||||||
}
|
}
|
||||||
|
exports.encode = encode;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
'use strict';
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
output: require('./output')
|
const input = require('./input');
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require('./output');
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
|
'use strict';
|
||||||
// OP_0 [signatures ...]
|
// OP_0 [signatures ...]
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require('../../script');
|
||||||
|
function partialSignature(value) {
|
||||||
function partialSignature (value) {
|
return (
|
||||||
return value === OPS.OP_0 || bscript.isCanonicalScriptSignature(value)
|
value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
function check(script, allowIncomplete) {
|
||||||
function check (script, allowIncomplete) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
if (chunks.length < 2) return false;
|
||||||
if (chunks.length < 2) return false
|
if (chunks[0] !== script_1.OPS.OP_0) return false;
|
||||||
if (chunks[0] !== OPS.OP_0) return false
|
|
||||||
|
|
||||||
if (allowIncomplete) {
|
if (allowIncomplete) {
|
||||||
return chunks.slice(1).every(partialSignature)
|
return chunks.slice(1).every(partialSignature);
|
||||||
}
|
}
|
||||||
|
return chunks.slice(1).every(bscript.isCanonicalScriptSignature);
|
||||||
return chunks.slice(1).every(bscript.isCanonicalScriptSignature)
|
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'multisig input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'multisig input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
|
'use strict';
|
||||||
// m [pubKeys ...] n OP_CHECKMULTISIG
|
// m [pubKeys ...] n OP_CHECKMULTISIG
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const types = require('../../types')
|
const script_1 = require('../../script');
|
||||||
const OPS = require('bitcoin-ops')
|
const types = require('../../types');
|
||||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1
|
||||||
|
function check(script, allowIncomplete) {
|
||||||
function check (script, allowIncomplete) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
if (chunks.length < 4) return false;
|
||||||
|
if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) return false;
|
||||||
if (chunks.length < 4) return false
|
if (!types.Number(chunks[0])) return false;
|
||||||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false
|
if (!types.Number(chunks[chunks.length - 2])) return false;
|
||||||
if (!types.Number(chunks[0])) return false
|
const m = chunks[0] - OP_INT_BASE;
|
||||||
if (!types.Number(chunks[chunks.length - 2])) return false
|
const n = chunks[chunks.length - 2] - OP_INT_BASE;
|
||||||
const m = chunks[0] - OP_INT_BASE
|
if (m <= 0) return false;
|
||||||
const n = chunks[chunks.length - 2] - OP_INT_BASE
|
if (n > 16) return false;
|
||||||
|
if (m > n) return false;
|
||||||
if (m <= 0) return false
|
if (n !== chunks.length - 3) return false;
|
||||||
if (n > 16) return false
|
if (allowIncomplete) return true;
|
||||||
if (m > n) return false
|
const keys = chunks.slice(1, -2);
|
||||||
if (n !== chunks.length - 3) return false
|
return keys.every(bscript.isCanonicalPubKey);
|
||||||
if (allowIncomplete) return true
|
|
||||||
|
|
||||||
const keys = chunks.slice(1, -2)
|
|
||||||
return keys.every(bscript.isCanonicalPubKey)
|
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'multi-sig output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'multi-sig output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
// OP_RETURN {data}
|
// OP_RETURN {data}
|
||||||
|
const bscript = require('../script');
|
||||||
const bscript = require('../script')
|
const OPS = bscript.OPS;
|
||||||
const OPS = require('bitcoin-ops')
|
function check(script) {
|
||||||
|
const buffer = bscript.compile(script);
|
||||||
function check (script) {
|
return buffer.length > 1 && buffer[0] === OPS.OP_RETURN;
|
||||||
const buffer = bscript.compile(script)
|
|
||||||
|
|
||||||
return buffer.length > 1 &&
|
|
||||||
buffer[0] === OPS.OP_RETURN
|
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'null data output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { output: { check: check } }
|
return 'null data output';
|
||||||
|
};
|
||||||
|
const output = { check };
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
'use strict';
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
output: require('./output')
|
const input = require('./input');
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require('./output');
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
|
'use strict';
|
||||||
// {signature}
|
// {signature}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return chunks.length === 1 && bscript.isCanonicalScriptSignature(chunks[0]);
|
||||||
|
|
||||||
return chunks.length === 1 &&
|
|
||||||
bscript.isCanonicalScriptSignature(chunks[0])
|
|
||||||
}
|
|
||||||
check.toJSON = function () { return 'pubKey input' }
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
check: check
|
|
||||||
}
|
}
|
||||||
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
|
return 'pubKey input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
'use strict';
|
||||||
// {pubKey} OP_CHECKSIG
|
// {pubKey} OP_CHECKSIG
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require('../../script');
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return (
|
||||||
|
chunks.length === 2 &&
|
||||||
return chunks.length === 2 &&
|
|
||||||
bscript.isCanonicalPubKey(chunks[0]) &&
|
bscript.isCanonicalPubKey(chunks[0]) &&
|
||||||
chunks[1] === OPS.OP_CHECKSIG
|
chunks[1] === script_1.OPS.OP_CHECKSIG
|
||||||
|
);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'pubKey output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'pubKey output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
'use strict';
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
output: require('./output')
|
const input = require('./input');
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require('./output');
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
|
'use strict';
|
||||||
// {signature} {pubKey}
|
// {signature} {pubKey}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return (
|
||||||
|
chunks.length === 2 &&
|
||||||
return chunks.length === 2 &&
|
|
||||||
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
||||||
bscript.isCanonicalPubKey(chunks[1])
|
bscript.isCanonicalPubKey(chunks[1])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'pubKeyHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'pubKeyHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
|
'use strict';
|
||||||
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require('../../script');
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return (
|
||||||
|
buffer.length === 25 &&
|
||||||
return buffer.length === 25 &&
|
buffer[0] === script_1.OPS.OP_DUP &&
|
||||||
buffer[0] === OPS.OP_DUP &&
|
buffer[1] === script_1.OPS.OP_HASH160 &&
|
||||||
buffer[1] === OPS.OP_HASH160 &&
|
|
||||||
buffer[2] === 0x14 &&
|
buffer[2] === 0x14 &&
|
||||||
buffer[23] === OPS.OP_EQUALVERIFY &&
|
buffer[23] === script_1.OPS.OP_EQUALVERIFY &&
|
||||||
buffer[24] === OPS.OP_CHECKSIG
|
buffer[24] === script_1.OPS.OP_CHECKSIG
|
||||||
|
);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'pubKeyHash output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'pubKeyHash output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
'use strict';
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
output: require('./output')
|
const input = require('./input');
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require('./output');
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,48 +1,50 @@
|
||||||
|
'use strict';
|
||||||
// <scriptSig> {serialized scriptPubKey script}
|
// <scriptSig> {serialized scriptPubKey script}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const Buffer = require('safe-buffer').Buffer
|
const bscript = require('../../script');
|
||||||
const bscript = require('../../script')
|
const p2ms = require('../multisig');
|
||||||
|
const p2pk = require('../pubkey');
|
||||||
const p2ms = require('../multisig/')
|
const p2pkh = require('../pubkeyhash');
|
||||||
const p2pk = require('../pubkey/')
|
const p2wpkho = require('../witnesspubkeyhash/output');
|
||||||
const p2pkh = require('../pubkeyhash/')
|
const p2wsho = require('../witnessscripthash/output');
|
||||||
const p2wpkho = require('../witnesspubkeyhash/output')
|
function check(script, allowIncomplete) {
|
||||||
const p2wsho = require('../witnessscripthash/output')
|
const chunks = bscript.decompile(script);
|
||||||
|
if (chunks.length < 1) return false;
|
||||||
function check (script, allowIncomplete) {
|
const lastChunk = chunks[chunks.length - 1];
|
||||||
const chunks = bscript.decompile(script)
|
if (!Buffer.isBuffer(lastChunk)) return false;
|
||||||
if (chunks.length < 1) return false
|
const scriptSigChunks = bscript.decompile(
|
||||||
|
bscript.compile(chunks.slice(0, -1)),
|
||||||
const lastChunk = chunks[chunks.length - 1]
|
);
|
||||||
if (!Buffer.isBuffer(lastChunk)) return false
|
const redeemScriptChunks = bscript.decompile(lastChunk);
|
||||||
|
|
||||||
const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1)))
|
|
||||||
const redeemScriptChunks = bscript.decompile(lastChunk)
|
|
||||||
|
|
||||||
// is redeemScript a valid script?
|
// is redeemScript a valid script?
|
||||||
if (!redeemScriptChunks) return false
|
if (!redeemScriptChunks) return false;
|
||||||
|
|
||||||
// is redeemScriptSig push only?
|
// is redeemScriptSig push only?
|
||||||
if (!bscript.isPushOnly(scriptSigChunks)) return false
|
if (!bscript.isPushOnly(scriptSigChunks)) return false;
|
||||||
|
|
||||||
// is witness?
|
// is witness?
|
||||||
if (chunks.length === 1) {
|
if (chunks.length === 1) {
|
||||||
return p2wsho.check(redeemScriptChunks) ||
|
return (
|
||||||
p2wpkho.check(redeemScriptChunks)
|
p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// match types
|
// match types
|
||||||
if (p2pkh.input.check(scriptSigChunks) &&
|
if (
|
||||||
p2pkh.output.check(redeemScriptChunks)) return true
|
p2pkh.input.check(scriptSigChunks) &&
|
||||||
|
p2pkh.output.check(redeemScriptChunks)
|
||||||
if (p2ms.input.check(scriptSigChunks, allowIncomplete) &&
|
)
|
||||||
p2ms.output.check(redeemScriptChunks)) return true
|
return true;
|
||||||
|
if (
|
||||||
if (p2pk.input.check(scriptSigChunks) &&
|
p2ms.input.check(scriptSigChunks, allowIncomplete) &&
|
||||||
p2pk.output.check(redeemScriptChunks)) return true
|
p2ms.output.check(redeemScriptChunks)
|
||||||
|
)
|
||||||
return false
|
return true;
|
||||||
|
if (
|
||||||
|
p2pk.input.check(scriptSigChunks) &&
|
||||||
|
p2pk.output.check(redeemScriptChunks)
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'scriptHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'scriptHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
|
'use strict';
|
||||||
// OP_HASH160 {scriptHash} OP_EQUAL
|
// OP_HASH160 {scriptHash} OP_EQUAL
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require('../../script');
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return (
|
||||||
|
buffer.length === 23 &&
|
||||||
return buffer.length === 23 &&
|
buffer[0] === script_1.OPS.OP_HASH160 &&
|
||||||
buffer[0] === OPS.OP_HASH160 &&
|
|
||||||
buffer[1] === 0x14 &&
|
buffer[1] === 0x14 &&
|
||||||
buffer[22] === OPS.OP_EQUAL
|
buffer[22] === script_1.OPS.OP_EQUAL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'scriptHash output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'scriptHash output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
module.exports = {
|
'use strict';
|
||||||
output: require('./output')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
}
|
const output = require('./output');
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,42 +1,34 @@
|
||||||
|
'use strict';
|
||||||
// OP_RETURN {aa21a9ed} {commitment}
|
// OP_RETURN {aa21a9ed} {commitment}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const Buffer = require('safe-buffer').Buffer
|
const bscript = require('../../script');
|
||||||
const bscript = require('../../script')
|
const script_1 = require('../../script');
|
||||||
const types = require('../../types')
|
const types = require('../../types');
|
||||||
const typeforce = require('typeforce')
|
const typeforce = require('typeforce');
|
||||||
const OPS = require('bitcoin-ops')
|
const HEADER = Buffer.from('aa21a9ed', 'hex');
|
||||||
|
function check(script) {
|
||||||
const HEADER = Buffer.from('aa21a9ed', 'hex')
|
const buffer = bscript.compile(script);
|
||||||
|
return (
|
||||||
function check (script) {
|
buffer.length > 37 &&
|
||||||
const buffer = bscript.compile(script)
|
buffer[0] === script_1.OPS.OP_RETURN &&
|
||||||
|
|
||||||
return buffer.length > 37 &&
|
|
||||||
buffer[0] === OPS.OP_RETURN &&
|
|
||||||
buffer[1] === 0x24 &&
|
buffer[1] === 0x24 &&
|
||||||
buffer.slice(2, 6).equals(HEADER)
|
buffer.slice(2, 6).equals(HEADER)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
exports.check = check;
|
||||||
check.toJSON = function () { return 'Witness commitment output' }
|
check.toJSON = () => {
|
||||||
|
return 'Witness commitment output';
|
||||||
function encode (commitment) {
|
};
|
||||||
typeforce(types.Hash256bit, commitment)
|
function encode(commitment) {
|
||||||
|
typeforce(types.Hash256bit, commitment);
|
||||||
const buffer = Buffer.allocUnsafe(36)
|
const buffer = Buffer.allocUnsafe(36);
|
||||||
HEADER.copy(buffer, 0)
|
HEADER.copy(buffer, 0);
|
||||||
commitment.copy(buffer, 4)
|
commitment.copy(buffer, 4);
|
||||||
|
return bscript.compile([script_1.OPS.OP_RETURN, buffer]);
|
||||||
return bscript.compile([OPS.OP_RETURN, buffer])
|
|
||||||
}
|
}
|
||||||
|
exports.encode = encode;
|
||||||
function decode (buffer) {
|
function decode(buffer) {
|
||||||
typeforce(check, buffer)
|
typeforce(check, buffer);
|
||||||
|
return bscript.decompile(buffer)[1].slice(4, 36);
|
||||||
return bscript.decompile(buffer)[1].slice(4, 36)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
check: check,
|
|
||||||
decode: decode,
|
|
||||||
encode: encode
|
|
||||||
}
|
}
|
||||||
|
exports.decode = decode;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
'use strict';
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
output: require('./output')
|
const input = require('./input');
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require('./output');
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
|
'use strict';
|
||||||
// {signature} {pubKey}
|
// {signature} {pubKey}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
|
function isCompressedCanonicalPubKey(pubKey) {
|
||||||
function isCompressedCanonicalPubKey (pubKey) {
|
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33;
|
||||||
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33
|
|
||||||
}
|
}
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const chunks = bscript.decompile(script);
|
||||||
const chunks = bscript.decompile(script)
|
return (
|
||||||
|
chunks.length === 2 &&
|
||||||
return chunks.length === 2 &&
|
|
||||||
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
||||||
isCompressedCanonicalPubKey(chunks[1])
|
isCompressedCanonicalPubKey(chunks[1])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'witnessPubKeyHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'witnessPubKeyHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
|
'use strict';
|
||||||
// OP_0 {pubKeyHash}
|
// OP_0 {pubKeyHash}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require('../../script');
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return (
|
||||||
|
buffer.length === 22 &&
|
||||||
return buffer.length === 22 &&
|
buffer[0] === script_1.OPS.OP_0 &&
|
||||||
buffer[0] === OPS.OP_0 &&
|
|
||||||
buffer[1] === 0x14
|
buffer[1] === 0x14
|
||||||
|
);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'Witness pubKeyHash output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = {
|
return 'Witness pubKeyHash output';
|
||||||
check
|
};
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
'use strict';
|
||||||
input: require('./input'),
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
output: require('./output')
|
const input = require('./input');
|
||||||
}
|
exports.input = input;
|
||||||
|
const output = require('./output');
|
||||||
|
exports.output = output;
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
|
'use strict';
|
||||||
// <scriptSig> {serialized scriptPubKey script}
|
// <scriptSig> {serialized scriptPubKey script}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const types = require('../../types')
|
const typeforce = require('typeforce');
|
||||||
const typeforce = require('typeforce')
|
const p2ms = require('../multisig');
|
||||||
|
const p2pk = require('../pubkey');
|
||||||
const p2ms = require('../multisig/')
|
const p2pkh = require('../pubkeyhash');
|
||||||
const p2pk = require('../pubkey/')
|
function check(chunks, allowIncomplete) {
|
||||||
const p2pkh = require('../pubkeyhash/')
|
typeforce(typeforce.Array, chunks);
|
||||||
|
if (chunks.length < 1) return false;
|
||||||
function check (chunks, allowIncomplete) {
|
const witnessScript = chunks[chunks.length - 1];
|
||||||
typeforce(types.Array, chunks)
|
if (!Buffer.isBuffer(witnessScript)) return false;
|
||||||
if (chunks.length < 1) return false
|
const witnessScriptChunks = bscript.decompile(witnessScript);
|
||||||
|
|
||||||
const witnessScript = chunks[chunks.length - 1]
|
|
||||||
if (!Buffer.isBuffer(witnessScript)) return false
|
|
||||||
|
|
||||||
const witnessScriptChunks = bscript.decompile(witnessScript)
|
|
||||||
|
|
||||||
// is witnessScript a valid script?
|
// is witnessScript a valid script?
|
||||||
if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false
|
if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false;
|
||||||
|
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1));
|
||||||
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1))
|
|
||||||
|
|
||||||
// match types
|
// match types
|
||||||
if (p2pkh.input.check(witnessRawScriptSig) &&
|
if (
|
||||||
p2pkh.output.check(witnessScriptChunks)) return true
|
p2pkh.input.check(witnessRawScriptSig) &&
|
||||||
|
p2pkh.output.check(witnessScriptChunks)
|
||||||
if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) &&
|
)
|
||||||
p2ms.output.check(witnessScriptChunks)) return true
|
return true;
|
||||||
|
if (
|
||||||
if (p2pk.input.check(witnessRawScriptSig) &&
|
p2ms.input.check(witnessRawScriptSig, allowIncomplete) &&
|
||||||
p2pk.output.check(witnessScriptChunks)) return true
|
p2ms.output.check(witnessScriptChunks)
|
||||||
|
)
|
||||||
return false
|
return true;
|
||||||
|
if (
|
||||||
|
p2pk.input.check(witnessRawScriptSig) &&
|
||||||
|
p2pk.output.check(witnessScriptChunks)
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'witnessScriptHash input' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'witnessScriptHash input';
|
||||||
|
};
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
'use strict';
|
||||||
// OP_0 {scriptHash}
|
// OP_0 {scriptHash}
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('../../script')
|
const bscript = require('../../script');
|
||||||
const OPS = require('bitcoin-ops')
|
const script_1 = require('../../script');
|
||||||
|
function check(script) {
|
||||||
function check (script) {
|
const buffer = bscript.compile(script);
|
||||||
const buffer = bscript.compile(script)
|
return (
|
||||||
|
buffer.length === 34 &&
|
||||||
return buffer.length === 34 &&
|
buffer[0] === script_1.OPS.OP_0 &&
|
||||||
buffer[0] === OPS.OP_0 &&
|
|
||||||
buffer[1] === 0x20
|
buffer[1] === 0x20
|
||||||
|
);
|
||||||
}
|
}
|
||||||
check.toJSON = function () { return 'Witness scriptHash output' }
|
exports.check = check;
|
||||||
|
check.toJSON = () => {
|
||||||
module.exports = { check }
|
return 'Witness scriptHash output';
|
||||||
|
};
|
||||||
|
|
|
@ -1,492 +1,478 @@
|
||||||
const Buffer = require('safe-buffer').Buffer
|
'use strict';
|
||||||
const bcrypto = require('./crypto')
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bscript = require('./script')
|
const bufferutils = require('./bufferutils');
|
||||||
const bufferutils = require('./bufferutils')
|
const bufferutils_1 = require('./bufferutils');
|
||||||
const opcodes = require('bitcoin-ops')
|
const bcrypto = require('./crypto');
|
||||||
const typeforce = require('typeforce')
|
const bscript = require('./script');
|
||||||
const types = require('./types')
|
const script_1 = require('./script');
|
||||||
const varuint = require('varuint-bitcoin')
|
const types = require('./types');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
function varSliceSize (someScript) {
|
const varuint = require('varuint-bitcoin');
|
||||||
const length = someScript.length
|
function varSliceSize(someScript) {
|
||||||
|
const length = someScript.length;
|
||||||
return varuint.encodingLength(length) + length
|
return varuint.encodingLength(length) + length;
|
||||||
}
|
}
|
||||||
|
function vectorSize(someVector) {
|
||||||
function vectorSize (someVector) {
|
const length = someVector.length;
|
||||||
const length = someVector.length
|
return (
|
||||||
|
varuint.encodingLength(length) +
|
||||||
return varuint.encodingLength(length) + someVector.reduce(function (sum, witness) {
|
someVector.reduce((sum, witness) => {
|
||||||
return sum + varSliceSize(witness)
|
return sum + varSliceSize(witness);
|
||||||
}, 0)
|
}, 0)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
|
||||||
function Transaction () {
|
const EMPTY_WITNESS = [];
|
||||||
this.version = 1
|
const ZERO = Buffer.from(
|
||||||
this.locktime = 0
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
this.ins = []
|
'hex',
|
||||||
this.outs = []
|
);
|
||||||
}
|
const ONE = Buffer.from(
|
||||||
|
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||||
Transaction.DEFAULT_SEQUENCE = 0xffffffff
|
'hex',
|
||||||
Transaction.SIGHASH_ALL = 0x01
|
);
|
||||||
Transaction.SIGHASH_NONE = 0x02
|
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
|
||||||
Transaction.SIGHASH_SINGLE = 0x03
|
|
||||||
Transaction.SIGHASH_ANYONECANPAY = 0x80
|
|
||||||
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00
|
|
||||||
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01
|
|
||||||
|
|
||||||
const EMPTY_SCRIPT = Buffer.allocUnsafe(0)
|
|
||||||
const EMPTY_WITNESS = []
|
|
||||||
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
|
|
||||||
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
|
|
||||||
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex')
|
|
||||||
const BLANK_OUTPUT = {
|
const BLANK_OUTPUT = {
|
||||||
script: EMPTY_SCRIPT,
|
script: EMPTY_SCRIPT,
|
||||||
valueBuffer: VALUE_UINT64_MAX
|
valueBuffer: VALUE_UINT64_MAX,
|
||||||
|
};
|
||||||
|
function isOutput(out) {
|
||||||
|
return out.value !== undefined;
|
||||||
}
|
}
|
||||||
|
class Transaction {
|
||||||
Transaction.fromBuffer = function (buffer, __noStrict) {
|
constructor() {
|
||||||
let offset = 0
|
this.version = 1;
|
||||||
function readSlice (n) {
|
this.locktime = 0;
|
||||||
offset += n
|
this.ins = [];
|
||||||
return buffer.slice(offset - n, offset)
|
this.outs = [];
|
||||||
}
|
}
|
||||||
|
static fromBuffer(buffer, _NO_STRICT) {
|
||||||
function readUInt32 () {
|
let offset = 0;
|
||||||
const i = buffer.readUInt32LE(offset)
|
function readSlice(n) {
|
||||||
offset += 4
|
offset += n;
|
||||||
return i
|
return buffer.slice(offset - n, offset);
|
||||||
}
|
|
||||||
|
|
||||||
function readInt32 () {
|
|
||||||
const i = buffer.readInt32LE(offset)
|
|
||||||
offset += 4
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
function readUInt64 () {
|
|
||||||
const i = bufferutils.readUInt64LE(buffer, offset)
|
|
||||||
offset += 8
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
function readVarInt () {
|
|
||||||
const vi = varuint.decode(buffer, offset)
|
|
||||||
offset += varuint.decode.bytes
|
|
||||||
return vi
|
|
||||||
}
|
|
||||||
|
|
||||||
function readVarSlice () {
|
|
||||||
return readSlice(readVarInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
function readVector () {
|
|
||||||
const count = readVarInt()
|
|
||||||
const vector = []
|
|
||||||
for (var i = 0; i < count; i++) vector.push(readVarSlice())
|
|
||||||
return vector
|
|
||||||
}
|
|
||||||
|
|
||||||
const tx = new Transaction()
|
|
||||||
tx.version = readInt32()
|
|
||||||
|
|
||||||
const marker = buffer.readUInt8(offset)
|
|
||||||
const flag = buffer.readUInt8(offset + 1)
|
|
||||||
|
|
||||||
let hasWitnesses = false
|
|
||||||
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
|
|
||||||
flag === Transaction.ADVANCED_TRANSACTION_FLAG) {
|
|
||||||
offset += 2
|
|
||||||
hasWitnesses = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const vinLen = readVarInt()
|
|
||||||
for (var i = 0; i < vinLen; ++i) {
|
|
||||||
tx.ins.push({
|
|
||||||
hash: readSlice(32),
|
|
||||||
index: readUInt32(),
|
|
||||||
script: readVarSlice(),
|
|
||||||
sequence: readUInt32(),
|
|
||||||
witness: EMPTY_WITNESS
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const voutLen = readVarInt()
|
|
||||||
for (i = 0; i < voutLen; ++i) {
|
|
||||||
tx.outs.push({
|
|
||||||
value: readUInt64(),
|
|
||||||
script: readVarSlice()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasWitnesses) {
|
|
||||||
for (i = 0; i < vinLen; ++i) {
|
|
||||||
tx.ins[i].witness = readVector()
|
|
||||||
}
|
}
|
||||||
|
function readUInt32() {
|
||||||
// was this pointless?
|
const i = buffer.readUInt32LE(offset);
|
||||||
if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data')
|
offset += 4;
|
||||||
}
|
return i;
|
||||||
|
|
||||||
tx.locktime = readUInt32()
|
|
||||||
|
|
||||||
if (__noStrict) return tx
|
|
||||||
if (offset !== buffer.length) throw new Error('Transaction has unexpected data')
|
|
||||||
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.fromHex = function (hex) {
|
|
||||||
return Transaction.fromBuffer(Buffer.from(hex, 'hex'))
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.isCoinbaseHash = function (buffer) {
|
|
||||||
typeforce(types.Hash256bit, buffer)
|
|
||||||
for (var i = 0; i < 32; ++i) {
|
|
||||||
if (buffer[i] !== 0) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.isCoinbase = function () {
|
|
||||||
return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.addInput = function (hash, index, sequence, scriptSig) {
|
|
||||||
typeforce(types.tuple(
|
|
||||||
types.Hash256bit,
|
|
||||||
types.UInt32,
|
|
||||||
types.maybe(types.UInt32),
|
|
||||||
types.maybe(types.Buffer)
|
|
||||||
), arguments)
|
|
||||||
|
|
||||||
if (types.Null(sequence)) {
|
|
||||||
sequence = Transaction.DEFAULT_SEQUENCE
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the input and return the input's index
|
|
||||||
return (this.ins.push({
|
|
||||||
hash: hash,
|
|
||||||
index: index,
|
|
||||||
script: scriptSig || EMPTY_SCRIPT,
|
|
||||||
sequence: sequence,
|
|
||||||
witness: EMPTY_WITNESS
|
|
||||||
}) - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.addOutput = function (scriptPubKey, value) {
|
|
||||||
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments)
|
|
||||||
|
|
||||||
// Add the output and return the output's index
|
|
||||||
return (this.outs.push({
|
|
||||||
script: scriptPubKey,
|
|
||||||
value: value
|
|
||||||
}) - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.hasWitnesses = function () {
|
|
||||||
return this.ins.some(function (x) {
|
|
||||||
return x.witness.length !== 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.weight = function () {
|
|
||||||
const base = this.__byteLength(false)
|
|
||||||
const total = this.__byteLength(true)
|
|
||||||
return base * 3 + total
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.virtualSize = function () {
|
|
||||||
return Math.ceil(this.weight() / 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.byteLength = function () {
|
|
||||||
return this.__byteLength(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.__byteLength = function (__allowWitness) {
|
|
||||||
const hasWitnesses = __allowWitness && this.hasWitnesses()
|
|
||||||
|
|
||||||
return (
|
|
||||||
(hasWitnesses ? 10 : 8) +
|
|
||||||
varuint.encodingLength(this.ins.length) +
|
|
||||||
varuint.encodingLength(this.outs.length) +
|
|
||||||
this.ins.reduce(function (sum, input) { return sum + 40 + varSliceSize(input.script) }, 0) +
|
|
||||||
this.outs.reduce(function (sum, output) { return sum + 8 + varSliceSize(output.script) }, 0) +
|
|
||||||
(hasWitnesses ? this.ins.reduce(function (sum, input) { return sum + vectorSize(input.witness) }, 0) : 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.clone = function () {
|
|
||||||
const newTx = new Transaction()
|
|
||||||
newTx.version = this.version
|
|
||||||
newTx.locktime = this.locktime
|
|
||||||
|
|
||||||
newTx.ins = this.ins.map(function (txIn) {
|
|
||||||
return {
|
|
||||||
hash: txIn.hash,
|
|
||||||
index: txIn.index,
|
|
||||||
script: txIn.script,
|
|
||||||
sequence: txIn.sequence,
|
|
||||||
witness: txIn.witness
|
|
||||||
}
|
}
|
||||||
})
|
function readInt32() {
|
||||||
|
const i = buffer.readInt32LE(offset);
|
||||||
newTx.outs = this.outs.map(function (txOut) {
|
offset += 4;
|
||||||
return {
|
return i;
|
||||||
script: txOut.script,
|
|
||||||
value: txOut.value
|
|
||||||
}
|
}
|
||||||
})
|
function readUInt64() {
|
||||||
|
const i = bufferutils.readUInt64LE(buffer, offset);
|
||||||
return newTx
|
offset += 8;
|
||||||
}
|
return i;
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash transaction for signing a specific input.
|
|
||||||
*
|
|
||||||
* Bitcoin uses a different hash for each signed transaction input.
|
|
||||||
* This method copies the transaction, makes the necessary changes based on the
|
|
||||||
* hashType, and then hashes the result.
|
|
||||||
* This hash can then be used to sign the provided transaction input.
|
|
||||||
*/
|
|
||||||
Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) {
|
|
||||||
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments)
|
|
||||||
|
|
||||||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
|
|
||||||
if (inIndex >= this.ins.length) return ONE
|
|
||||||
|
|
||||||
// ignore OP_CODESEPARATOR
|
|
||||||
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) {
|
|
||||||
return x !== opcodes.OP_CODESEPARATOR
|
|
||||||
}))
|
|
||||||
|
|
||||||
const txTmp = this.clone()
|
|
||||||
|
|
||||||
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
|
|
||||||
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
|
||||||
txTmp.outs = []
|
|
||||||
|
|
||||||
// ignore sequence numbers (except at inIndex)
|
|
||||||
txTmp.ins.forEach(function (input, i) {
|
|
||||||
if (i === inIndex) return
|
|
||||||
|
|
||||||
input.sequence = 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
|
|
||||||
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
|
|
||||||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
|
|
||||||
if (inIndex >= this.outs.length) return ONE
|
|
||||||
|
|
||||||
// truncate outputs after
|
|
||||||
txTmp.outs.length = inIndex + 1
|
|
||||||
|
|
||||||
// "blank" outputs before
|
|
||||||
for (var i = 0; i < inIndex; i++) {
|
|
||||||
txTmp.outs[i] = BLANK_OUTPUT
|
|
||||||
}
|
}
|
||||||
|
function readVarInt() {
|
||||||
// ignore sequence numbers (except at inIndex)
|
const vi = varuint.decode(buffer, offset);
|
||||||
txTmp.ins.forEach(function (input, y) {
|
offset += varuint.decode.bytes;
|
||||||
if (y === inIndex) return
|
return vi;
|
||||||
|
}
|
||||||
input.sequence = 0
|
function readVarSlice() {
|
||||||
})
|
return readSlice(readVarInt());
|
||||||
|
}
|
||||||
|
function readVector() {
|
||||||
|
const count = readVarInt();
|
||||||
|
const vector = [];
|
||||||
|
for (let i = 0; i < count; i++) vector.push(readVarSlice());
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
const tx = new Transaction();
|
||||||
|
tx.version = readInt32();
|
||||||
|
const marker = buffer.readUInt8(offset);
|
||||||
|
const flag = buffer.readUInt8(offset + 1);
|
||||||
|
let hasWitnesses = false;
|
||||||
|
if (
|
||||||
|
marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
|
||||||
|
flag === Transaction.ADVANCED_TRANSACTION_FLAG
|
||||||
|
) {
|
||||||
|
offset += 2;
|
||||||
|
hasWitnesses = true;
|
||||||
|
}
|
||||||
|
const vinLen = readVarInt();
|
||||||
|
for (let i = 0; i < vinLen; ++i) {
|
||||||
|
tx.ins.push({
|
||||||
|
hash: readSlice(32),
|
||||||
|
index: readUInt32(),
|
||||||
|
script: readVarSlice(),
|
||||||
|
sequence: readUInt32(),
|
||||||
|
witness: EMPTY_WITNESS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const voutLen = readVarInt();
|
||||||
|
for (let i = 0; i < voutLen; ++i) {
|
||||||
|
tx.outs.push({
|
||||||
|
value: readUInt64(),
|
||||||
|
script: readVarSlice(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasWitnesses) {
|
||||||
|
for (let i = 0; i < vinLen; ++i) {
|
||||||
|
tx.ins[i].witness = readVector();
|
||||||
|
}
|
||||||
|
// was this pointless?
|
||||||
|
if (!tx.hasWitnesses())
|
||||||
|
throw new Error('Transaction has superfluous witness data');
|
||||||
|
}
|
||||||
|
tx.locktime = readUInt32();
|
||||||
|
if (_NO_STRICT) return tx;
|
||||||
|
if (offset !== buffer.length)
|
||||||
|
throw new Error('Transaction has unexpected data');
|
||||||
|
return tx;
|
||||||
}
|
}
|
||||||
|
static fromHex(hex) {
|
||||||
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
|
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false);
|
||||||
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
|
||||||
txTmp.ins = [txTmp.ins[inIndex]]
|
|
||||||
txTmp.ins[0].script = ourScript
|
|
||||||
|
|
||||||
// SIGHASH_ALL: only ignore input scripts
|
|
||||||
} else {
|
|
||||||
// "blank" others input scripts
|
|
||||||
txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT })
|
|
||||||
txTmp.ins[inIndex].script = ourScript
|
|
||||||
}
|
}
|
||||||
|
static isCoinbaseHash(buffer) {
|
||||||
// serialize and hash
|
typeforce(types.Hash256bit, buffer);
|
||||||
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4)
|
for (let i = 0; i < 32; ++i) {
|
||||||
buffer.writeInt32LE(hashType, buffer.length - 4)
|
if (buffer[i] !== 0) return false;
|
||||||
txTmp.__toBuffer(buffer, 0, false)
|
}
|
||||||
|
return true;
|
||||||
return bcrypto.hash256(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.hashForWitnessV0 = function (inIndex, prevOutScript, value, hashType) {
|
|
||||||
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments)
|
|
||||||
|
|
||||||
let tbuffer, toffset
|
|
||||||
function writeSlice (slice) { toffset += slice.copy(tbuffer, toffset) }
|
|
||||||
function writeUInt32 (i) { toffset = tbuffer.writeUInt32LE(i, toffset) }
|
|
||||||
function writeUInt64 (i) { toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) }
|
|
||||||
function writeVarInt (i) {
|
|
||||||
varuint.encode(i, tbuffer, toffset)
|
|
||||||
toffset += varuint.encode.bytes
|
|
||||||
}
|
}
|
||||||
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) }
|
isCoinbase() {
|
||||||
|
return (
|
||||||
let hashOutputs = ZERO
|
this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
|
||||||
let hashPrevouts = ZERO
|
);
|
||||||
let hashSequence = ZERO
|
|
||||||
|
|
||||||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
|
|
||||||
tbuffer = Buffer.allocUnsafe(36 * this.ins.length)
|
|
||||||
toffset = 0
|
|
||||||
|
|
||||||
this.ins.forEach(function (txIn) {
|
|
||||||
writeSlice(txIn.hash)
|
|
||||||
writeUInt32(txIn.index)
|
|
||||||
})
|
|
||||||
|
|
||||||
hashPrevouts = bcrypto.hash256(tbuffer)
|
|
||||||
}
|
}
|
||||||
|
addInput(hash, index, sequence, scriptSig) {
|
||||||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
|
typeforce(
|
||||||
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
types.tuple(
|
||||||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
types.Hash256bit,
|
||||||
tbuffer = Buffer.allocUnsafe(4 * this.ins.length)
|
types.UInt32,
|
||||||
toffset = 0
|
types.maybe(types.UInt32),
|
||||||
|
types.maybe(types.Buffer),
|
||||||
this.ins.forEach(function (txIn) {
|
),
|
||||||
writeUInt32(txIn.sequence)
|
arguments,
|
||||||
})
|
);
|
||||||
|
if (types.Null(sequence)) {
|
||||||
hashSequence = bcrypto.hash256(tbuffer)
|
sequence = Transaction.DEFAULT_SEQUENCE;
|
||||||
|
}
|
||||||
|
// Add the input and return the input's index
|
||||||
|
return (
|
||||||
|
this.ins.push({
|
||||||
|
hash,
|
||||||
|
index,
|
||||||
|
script: scriptSig || EMPTY_SCRIPT,
|
||||||
|
sequence: sequence,
|
||||||
|
witness: EMPTY_WITNESS,
|
||||||
|
}) - 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
addOutput(scriptPubKey, value) {
|
||||||
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments);
|
||||||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
// Add the output and return the output's index
|
||||||
const txOutsSize = this.outs.reduce(function (sum, output) {
|
return (
|
||||||
return sum + 8 + varSliceSize(output.script)
|
this.outs.push({
|
||||||
}, 0)
|
script: scriptPubKey,
|
||||||
|
value,
|
||||||
tbuffer = Buffer.allocUnsafe(txOutsSize)
|
}) - 1
|
||||||
toffset = 0
|
);
|
||||||
|
|
||||||
this.outs.forEach(function (out) {
|
|
||||||
writeUInt64(out.value)
|
|
||||||
writeVarSlice(out.script)
|
|
||||||
})
|
|
||||||
|
|
||||||
hashOutputs = bcrypto.hash256(tbuffer)
|
|
||||||
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) {
|
|
||||||
const output = this.outs[inIndex]
|
|
||||||
|
|
||||||
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script))
|
|
||||||
toffset = 0
|
|
||||||
writeUInt64(output.value)
|
|
||||||
writeVarSlice(output.script)
|
|
||||||
|
|
||||||
hashOutputs = bcrypto.hash256(tbuffer)
|
|
||||||
}
|
}
|
||||||
|
hasWitnesses() {
|
||||||
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript))
|
return this.ins.some(x => {
|
||||||
toffset = 0
|
return x.witness.length !== 0;
|
||||||
|
});
|
||||||
const input = this.ins[inIndex]
|
|
||||||
writeUInt32(this.version)
|
|
||||||
writeSlice(hashPrevouts)
|
|
||||||
writeSlice(hashSequence)
|
|
||||||
writeSlice(input.hash)
|
|
||||||
writeUInt32(input.index)
|
|
||||||
writeVarSlice(prevOutScript)
|
|
||||||
writeUInt64(value)
|
|
||||||
writeUInt32(input.sequence)
|
|
||||||
writeSlice(hashOutputs)
|
|
||||||
writeUInt32(this.locktime)
|
|
||||||
writeUInt32(hashType)
|
|
||||||
return bcrypto.hash256(tbuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.getHash = function () {
|
|
||||||
return bcrypto.hash256(this.__toBuffer(undefined, undefined, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.getId = function () {
|
|
||||||
// transaction hash's are displayed in reverse order
|
|
||||||
return this.getHash().reverse().toString('hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.toBuffer = function (buffer, initialOffset) {
|
|
||||||
return this.__toBuffer(buffer, initialOffset, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitness) {
|
|
||||||
if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness))
|
|
||||||
|
|
||||||
let offset = initialOffset || 0
|
|
||||||
function writeSlice (slice) { offset += slice.copy(buffer, offset) }
|
|
||||||
function writeUInt8 (i) { offset = buffer.writeUInt8(i, offset) }
|
|
||||||
function writeUInt32 (i) { offset = buffer.writeUInt32LE(i, offset) }
|
|
||||||
function writeInt32 (i) { offset = buffer.writeInt32LE(i, offset) }
|
|
||||||
function writeUInt64 (i) { offset = bufferutils.writeUInt64LE(buffer, i, offset) }
|
|
||||||
function writeVarInt (i) {
|
|
||||||
varuint.encode(i, buffer, offset)
|
|
||||||
offset += varuint.encode.bytes
|
|
||||||
}
|
}
|
||||||
function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) }
|
weight() {
|
||||||
function writeVector (vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice) }
|
const base = this.__byteLength(false);
|
||||||
|
const total = this.__byteLength(true);
|
||||||
writeInt32(this.version)
|
return base * 3 + total;
|
||||||
|
|
||||||
const hasWitnesses = __allowWitness && this.hasWitnesses()
|
|
||||||
|
|
||||||
if (hasWitnesses) {
|
|
||||||
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER)
|
|
||||||
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG)
|
|
||||||
}
|
}
|
||||||
|
virtualSize() {
|
||||||
writeVarInt(this.ins.length)
|
return Math.ceil(this.weight() / 4);
|
||||||
|
}
|
||||||
this.ins.forEach(function (txIn) {
|
byteLength() {
|
||||||
writeSlice(txIn.hash)
|
return this.__byteLength(true);
|
||||||
writeUInt32(txIn.index)
|
}
|
||||||
writeVarSlice(txIn.script)
|
clone() {
|
||||||
writeUInt32(txIn.sequence)
|
const newTx = new Transaction();
|
||||||
})
|
newTx.version = this.version;
|
||||||
|
newTx.locktime = this.locktime;
|
||||||
writeVarInt(this.outs.length)
|
newTx.ins = this.ins.map(txIn => {
|
||||||
this.outs.forEach(function (txOut) {
|
return {
|
||||||
if (!txOut.valueBuffer) {
|
hash: txIn.hash,
|
||||||
writeUInt64(txOut.value)
|
index: txIn.index,
|
||||||
|
script: txIn.script,
|
||||||
|
sequence: txIn.sequence,
|
||||||
|
witness: txIn.witness,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
newTx.outs = this.outs.map(txOut => {
|
||||||
|
return {
|
||||||
|
script: txOut.script,
|
||||||
|
value: txOut.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return newTx;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Hash transaction for signing a specific input.
|
||||||
|
*
|
||||||
|
* Bitcoin uses a different hash for each signed transaction input.
|
||||||
|
* This method copies the transaction, makes the necessary changes based on the
|
||||||
|
* hashType, and then hashes the result.
|
||||||
|
* This hash can then be used to sign the provided transaction input.
|
||||||
|
*/
|
||||||
|
hashForSignature(inIndex, prevOutScript, hashType) {
|
||||||
|
typeforce(
|
||||||
|
types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number),
|
||||||
|
arguments,
|
||||||
|
);
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
|
||||||
|
if (inIndex >= this.ins.length) return ONE;
|
||||||
|
// ignore OP_CODESEPARATOR
|
||||||
|
const ourScript = bscript.compile(
|
||||||
|
bscript.decompile(prevOutScript).filter(x => {
|
||||||
|
return x !== script_1.OPS.OP_CODESEPARATOR;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const txTmp = this.clone();
|
||||||
|
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
|
||||||
|
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
||||||
|
txTmp.outs = [];
|
||||||
|
// ignore sequence numbers (except at inIndex)
|
||||||
|
txTmp.ins.forEach((input, i) => {
|
||||||
|
if (i === inIndex) return;
|
||||||
|
input.sequence = 0;
|
||||||
|
});
|
||||||
|
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
|
||||||
|
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
|
||||||
|
if (inIndex >= this.outs.length) return ONE;
|
||||||
|
// truncate outputs after
|
||||||
|
txTmp.outs.length = inIndex + 1;
|
||||||
|
// "blank" outputs before
|
||||||
|
for (let i = 0; i < inIndex; i++) {
|
||||||
|
txTmp.outs[i] = BLANK_OUTPUT;
|
||||||
|
}
|
||||||
|
// ignore sequence numbers (except at inIndex)
|
||||||
|
txTmp.ins.forEach((input, y) => {
|
||||||
|
if (y === inIndex) return;
|
||||||
|
input.sequence = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
|
||||||
|
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
||||||
|
txTmp.ins = [txTmp.ins[inIndex]];
|
||||||
|
txTmp.ins[0].script = ourScript;
|
||||||
|
// SIGHASH_ALL: only ignore input scripts
|
||||||
} else {
|
} else {
|
||||||
writeSlice(txOut.valueBuffer)
|
// "blank" others input scripts
|
||||||
|
txTmp.ins.forEach(input => {
|
||||||
|
input.script = EMPTY_SCRIPT;
|
||||||
|
});
|
||||||
|
txTmp.ins[inIndex].script = ourScript;
|
||||||
}
|
}
|
||||||
|
// serialize and hash
|
||||||
writeVarSlice(txOut.script)
|
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4);
|
||||||
})
|
buffer.writeInt32LE(hashType, buffer.length - 4);
|
||||||
|
txTmp.__toBuffer(buffer, 0, false);
|
||||||
if (hasWitnesses) {
|
return bcrypto.hash256(buffer);
|
||||||
this.ins.forEach(function (input) {
|
}
|
||||||
writeVector(input.witness)
|
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
|
||||||
})
|
typeforce(
|
||||||
|
types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32),
|
||||||
|
arguments,
|
||||||
|
);
|
||||||
|
let tbuffer = Buffer.from([]);
|
||||||
|
let toffset = 0;
|
||||||
|
function writeSlice(slice) {
|
||||||
|
toffset += slice.copy(tbuffer, toffset);
|
||||||
|
}
|
||||||
|
function writeUInt32(i) {
|
||||||
|
toffset = tbuffer.writeUInt32LE(i, toffset);
|
||||||
|
}
|
||||||
|
function writeUInt64(i) {
|
||||||
|
toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset);
|
||||||
|
}
|
||||||
|
function writeVarInt(i) {
|
||||||
|
varuint.encode(i, tbuffer, toffset);
|
||||||
|
toffset += varuint.encode.bytes;
|
||||||
|
}
|
||||||
|
function writeVarSlice(slice) {
|
||||||
|
writeVarInt(slice.length);
|
||||||
|
writeSlice(slice);
|
||||||
|
}
|
||||||
|
let hashOutputs = ZERO;
|
||||||
|
let hashPrevouts = ZERO;
|
||||||
|
let hashSequence = ZERO;
|
||||||
|
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
|
||||||
|
tbuffer = Buffer.allocUnsafe(36 * this.ins.length);
|
||||||
|
toffset = 0;
|
||||||
|
this.ins.forEach(txIn => {
|
||||||
|
writeSlice(txIn.hash);
|
||||||
|
writeUInt32(txIn.index);
|
||||||
|
});
|
||||||
|
hashPrevouts = bcrypto.hash256(tbuffer);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
|
||||||
|
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
||||||
|
(hashType & 0x1f) !== Transaction.SIGHASH_NONE
|
||||||
|
) {
|
||||||
|
tbuffer = Buffer.allocUnsafe(4 * this.ins.length);
|
||||||
|
toffset = 0;
|
||||||
|
this.ins.forEach(txIn => {
|
||||||
|
writeUInt32(txIn.sequence);
|
||||||
|
});
|
||||||
|
hashSequence = bcrypto.hash256(tbuffer);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
||||||
|
(hashType & 0x1f) !== Transaction.SIGHASH_NONE
|
||||||
|
) {
|
||||||
|
const txOutsSize = this.outs.reduce((sum, output) => {
|
||||||
|
return sum + 8 + varSliceSize(output.script);
|
||||||
|
}, 0);
|
||||||
|
tbuffer = Buffer.allocUnsafe(txOutsSize);
|
||||||
|
toffset = 0;
|
||||||
|
this.outs.forEach(out => {
|
||||||
|
writeUInt64(out.value);
|
||||||
|
writeVarSlice(out.script);
|
||||||
|
});
|
||||||
|
hashOutputs = bcrypto.hash256(tbuffer);
|
||||||
|
} else if (
|
||||||
|
(hashType & 0x1f) === Transaction.SIGHASH_SINGLE &&
|
||||||
|
inIndex < this.outs.length
|
||||||
|
) {
|
||||||
|
const output = this.outs[inIndex];
|
||||||
|
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script));
|
||||||
|
toffset = 0;
|
||||||
|
writeUInt64(output.value);
|
||||||
|
writeVarSlice(output.script);
|
||||||
|
hashOutputs = bcrypto.hash256(tbuffer);
|
||||||
|
}
|
||||||
|
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript));
|
||||||
|
toffset = 0;
|
||||||
|
const input = this.ins[inIndex];
|
||||||
|
writeUInt32(this.version);
|
||||||
|
writeSlice(hashPrevouts);
|
||||||
|
writeSlice(hashSequence);
|
||||||
|
writeSlice(input.hash);
|
||||||
|
writeUInt32(input.index);
|
||||||
|
writeVarSlice(prevOutScript);
|
||||||
|
writeUInt64(value);
|
||||||
|
writeUInt32(input.sequence);
|
||||||
|
writeSlice(hashOutputs);
|
||||||
|
writeUInt32(this.locktime);
|
||||||
|
writeUInt32(hashType);
|
||||||
|
return bcrypto.hash256(tbuffer);
|
||||||
|
}
|
||||||
|
getHash(forWitness) {
|
||||||
|
// wtxid for coinbase is always 32 bytes of 0x00
|
||||||
|
if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0);
|
||||||
|
return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness));
|
||||||
|
}
|
||||||
|
getId() {
|
||||||
|
// transaction hash's are displayed in reverse order
|
||||||
|
return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex');
|
||||||
|
}
|
||||||
|
toBuffer(buffer, initialOffset) {
|
||||||
|
return this.__toBuffer(buffer, initialOffset, true);
|
||||||
|
}
|
||||||
|
toHex() {
|
||||||
|
return this.toBuffer(undefined, undefined).toString('hex');
|
||||||
|
}
|
||||||
|
setInputScript(index, scriptSig) {
|
||||||
|
typeforce(types.tuple(types.Number, types.Buffer), arguments);
|
||||||
|
this.ins[index].script = scriptSig;
|
||||||
|
}
|
||||||
|
setWitness(index, witness) {
|
||||||
|
typeforce(types.tuple(types.Number, [types.Buffer]), arguments);
|
||||||
|
this.ins[index].witness = witness;
|
||||||
|
}
|
||||||
|
__byteLength(_ALLOW_WITNESS) {
|
||||||
|
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
|
||||||
|
return (
|
||||||
|
(hasWitnesses ? 10 : 8) +
|
||||||
|
varuint.encodingLength(this.ins.length) +
|
||||||
|
varuint.encodingLength(this.outs.length) +
|
||||||
|
this.ins.reduce((sum, input) => {
|
||||||
|
return sum + 40 + varSliceSize(input.script);
|
||||||
|
}, 0) +
|
||||||
|
this.outs.reduce((sum, output) => {
|
||||||
|
return sum + 8 + varSliceSize(output.script);
|
||||||
|
}, 0) +
|
||||||
|
(hasWitnesses
|
||||||
|
? this.ins.reduce((sum, input) => {
|
||||||
|
return sum + vectorSize(input.witness);
|
||||||
|
}, 0)
|
||||||
|
: 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
__toBuffer(buffer, initialOffset, _ALLOW_WITNESS) {
|
||||||
|
if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS));
|
||||||
|
let offset = initialOffset || 0;
|
||||||
|
function writeSlice(slice) {
|
||||||
|
offset += slice.copy(buffer, offset);
|
||||||
|
}
|
||||||
|
function writeUInt8(i) {
|
||||||
|
offset = buffer.writeUInt8(i, offset);
|
||||||
|
}
|
||||||
|
function writeUInt32(i) {
|
||||||
|
offset = buffer.writeUInt32LE(i, offset);
|
||||||
|
}
|
||||||
|
function writeInt32(i) {
|
||||||
|
offset = buffer.writeInt32LE(i, offset);
|
||||||
|
}
|
||||||
|
function writeUInt64(i) {
|
||||||
|
offset = bufferutils.writeUInt64LE(buffer, i, offset);
|
||||||
|
}
|
||||||
|
function writeVarInt(i) {
|
||||||
|
varuint.encode(i, buffer, offset);
|
||||||
|
offset += varuint.encode.bytes;
|
||||||
|
}
|
||||||
|
function writeVarSlice(slice) {
|
||||||
|
writeVarInt(slice.length);
|
||||||
|
writeSlice(slice);
|
||||||
|
}
|
||||||
|
function writeVector(vector) {
|
||||||
|
writeVarInt(vector.length);
|
||||||
|
vector.forEach(writeVarSlice);
|
||||||
|
}
|
||||||
|
writeInt32(this.version);
|
||||||
|
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
|
||||||
|
if (hasWitnesses) {
|
||||||
|
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER);
|
||||||
|
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG);
|
||||||
|
}
|
||||||
|
writeVarInt(this.ins.length);
|
||||||
|
this.ins.forEach(txIn => {
|
||||||
|
writeSlice(txIn.hash);
|
||||||
|
writeUInt32(txIn.index);
|
||||||
|
writeVarSlice(txIn.script);
|
||||||
|
writeUInt32(txIn.sequence);
|
||||||
|
});
|
||||||
|
writeVarInt(this.outs.length);
|
||||||
|
this.outs.forEach(txOut => {
|
||||||
|
if (isOutput(txOut)) {
|
||||||
|
writeUInt64(txOut.value);
|
||||||
|
} else {
|
||||||
|
writeSlice(txOut.valueBuffer);
|
||||||
|
}
|
||||||
|
writeVarSlice(txOut.script);
|
||||||
|
});
|
||||||
|
if (hasWitnesses) {
|
||||||
|
this.ins.forEach(input => {
|
||||||
|
writeVector(input.witness);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
writeUInt32(this.locktime);
|
||||||
|
// avoid slicing unless necessary
|
||||||
|
if (initialOffset !== undefined) return buffer.slice(initialOffset, offset);
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeUInt32(this.locktime)
|
|
||||||
|
|
||||||
// avoid slicing unless necessary
|
|
||||||
if (initialOffset !== undefined) return buffer.slice(initialOffset, offset)
|
|
||||||
return buffer
|
|
||||||
}
|
}
|
||||||
|
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
|
||||||
Transaction.prototype.toHex = function () {
|
Transaction.SIGHASH_ALL = 0x01;
|
||||||
return this.toBuffer().toString('hex')
|
Transaction.SIGHASH_NONE = 0x02;
|
||||||
}
|
Transaction.SIGHASH_SINGLE = 0x03;
|
||||||
|
Transaction.SIGHASH_ANYONECANPAY = 0x80;
|
||||||
Transaction.prototype.setInputScript = function (index, scriptSig) {
|
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
|
||||||
typeforce(types.tuple(types.Number, types.Buffer), arguments)
|
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
|
||||||
|
exports.Transaction = Transaction;
|
||||||
this.ins[index].script = scriptSig
|
|
||||||
}
|
|
||||||
|
|
||||||
Transaction.prototype.setWitness = function (index, witness) {
|
|
||||||
typeforce(types.tuple(types.Number, [types.Buffer]), arguments)
|
|
||||||
|
|
||||||
this.ins[index].witness = witness
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Transaction
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
77
src/types.js
77
src/types.js
|
@ -1,49 +1,50 @@
|
||||||
const typeforce = require('typeforce')
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const UINT31_MAX = Math.pow(2, 31) - 1
|
const typeforce = require('typeforce');
|
||||||
function UInt31 (value) {
|
const UINT31_MAX = Math.pow(2, 31) - 1;
|
||||||
return typeforce.UInt32(value) && value <= UINT31_MAX
|
function UInt31(value) {
|
||||||
|
return typeforce.UInt32(value) && value <= UINT31_MAX;
|
||||||
}
|
}
|
||||||
|
exports.UInt31 = UInt31;
|
||||||
function BIP32Path (value) {
|
function BIP32Path(value) {
|
||||||
return typeforce.String(value) && value.match(/^(m\/)?(\d+'?\/)*\d+'?$/)
|
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/);
|
||||||
}
|
}
|
||||||
BIP32Path.toJSON = function () { return 'BIP32 derivation path' }
|
exports.BIP32Path = BIP32Path;
|
||||||
|
BIP32Path.toJSON = () => {
|
||||||
const SATOSHI_MAX = 21 * 1e14
|
return 'BIP32 derivation path';
|
||||||
function Satoshi (value) {
|
};
|
||||||
return typeforce.UInt53(value) && value <= SATOSHI_MAX
|
const SATOSHI_MAX = 21 * 1e14;
|
||||||
|
function Satoshi(value) {
|
||||||
|
return typeforce.UInt53(value) && value <= SATOSHI_MAX;
|
||||||
}
|
}
|
||||||
|
exports.Satoshi = Satoshi;
|
||||||
// external dependent types
|
// external dependent types
|
||||||
const ECPoint = typeforce.quacksLike('Point')
|
exports.ECPoint = typeforce.quacksLike('Point');
|
||||||
|
|
||||||
// exposed, external API
|
// exposed, external API
|
||||||
const Network = typeforce.compile({
|
exports.Network = typeforce.compile({
|
||||||
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
|
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
|
||||||
bip32: {
|
bip32: {
|
||||||
public: typeforce.UInt32,
|
public: typeforce.UInt32,
|
||||||
private: typeforce.UInt32
|
private: typeforce.UInt32,
|
||||||
},
|
},
|
||||||
pubKeyHash: typeforce.UInt8,
|
pubKeyHash: typeforce.UInt8,
|
||||||
scriptHash: typeforce.UInt8,
|
scriptHash: typeforce.UInt8,
|
||||||
wif: typeforce.UInt8
|
wif: typeforce.UInt8,
|
||||||
})
|
});
|
||||||
|
exports.Buffer256bit = typeforce.BufferN(32);
|
||||||
// extend typeforce types with ours
|
exports.Hash160bit = typeforce.BufferN(20);
|
||||||
const types = {
|
exports.Hash256bit = typeforce.BufferN(32);
|
||||||
BIP32Path: BIP32Path,
|
exports.Number = typeforce.Number; // tslint:disable-line variable-name
|
||||||
Buffer256bit: typeforce.BufferN(32),
|
exports.Array = typeforce.Array;
|
||||||
ECPoint: ECPoint,
|
exports.Boolean = typeforce.Boolean; // tslint:disable-line variable-name
|
||||||
Hash160bit: typeforce.BufferN(20),
|
exports.String = typeforce.String; // tslint:disable-line variable-name
|
||||||
Hash256bit: typeforce.BufferN(32),
|
exports.Buffer = typeforce.Buffer;
|
||||||
Network: Network,
|
exports.Hex = typeforce.Hex;
|
||||||
Satoshi: Satoshi,
|
exports.maybe = typeforce.maybe;
|
||||||
UInt31: UInt31
|
exports.tuple = typeforce.tuple;
|
||||||
}
|
exports.UInt8 = typeforce.UInt8;
|
||||||
|
exports.UInt32 = typeforce.UInt32;
|
||||||
for (var typeName in typeforce) {
|
exports.Function = typeforce.Function;
|
||||||
types[typeName] = typeforce[typeName]
|
exports.BufferN = typeforce.BufferN;
|
||||||
}
|
exports.Null = typeforce.Null;
|
||||||
|
exports.oneOf = typeforce.oneOf;
|
||||||
module.exports = types
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const baddress = require('../src/address')
|
const baddress = require('../src/address')
|
||||||
const bscript = require('../src/script')
|
const bscript = require('../src/script')
|
||||||
|
@ -17,12 +16,12 @@ const NETWORKS = Object.assign({
|
||||||
}
|
}
|
||||||
}, require('../src/networks'))
|
}, require('../src/networks'))
|
||||||
|
|
||||||
describe('address', function () {
|
describe('address', () => {
|
||||||
describe('fromBase58Check', function () {
|
describe('fromBase58Check', () => {
|
||||||
fixtures.standard.forEach(function (f) {
|
fixtures.standard.forEach(f => {
|
||||||
if (!f.base58check) return
|
if (!f.base58check) return
|
||||||
|
|
||||||
it('decodes ' + f.base58check, function () {
|
it('decodes ' + f.base58check, () => {
|
||||||
const decode = baddress.fromBase58Check(f.base58check)
|
const decode = baddress.fromBase58Check(f.base58check)
|
||||||
|
|
||||||
assert.strictEqual(decode.version, f.version)
|
assert.strictEqual(decode.version, f.version)
|
||||||
|
@ -30,20 +29,20 @@ describe('address', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.fromBase58Check.forEach(function (f) {
|
fixtures.invalid.fromBase58Check.forEach(f => {
|
||||||
it('throws on ' + f.exception, function () {
|
it('throws on ' + f.exception, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
baddress.fromBase58Check(f.address)
|
baddress.fromBase58Check(f.address)
|
||||||
}, new RegExp(f.address + ' ' + f.exception))
|
}, new RegExp(f.address + ' ' + f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromBech32', function () {
|
describe('fromBech32', () => {
|
||||||
fixtures.standard.forEach((f) => {
|
fixtures.standard.forEach(f => {
|
||||||
if (!f.bech32) return
|
if (!f.bech32) return
|
||||||
|
|
||||||
it('decodes ' + f.bech32, function () {
|
it('decodes ' + f.bech32, () => {
|
||||||
const actual = baddress.fromBech32(f.bech32)
|
const actual = baddress.fromBech32(f.bech32)
|
||||||
|
|
||||||
assert.strictEqual(actual.version, f.version)
|
assert.strictEqual(actual.version, f.version)
|
||||||
|
@ -53,17 +52,17 @@ describe('address', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.bech32.forEach((f, i) => {
|
fixtures.invalid.bech32.forEach((f, i) => {
|
||||||
it('decode fails for ' + f.bech32 + '(' + f.exception + ')', function () {
|
it('decode fails for ' + f.bech32 + '(' + f.exception + ')', () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
baddress.fromBech32(f.address)
|
baddress.fromBech32(f.address)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromOutputScript', function () {
|
describe('fromOutputScript', () => {
|
||||||
fixtures.standard.forEach(function (f) {
|
fixtures.standard.forEach(f => {
|
||||||
it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
|
it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => {
|
||||||
const script = bscript.fromASM(f.script)
|
const script = bscript.fromASM(f.script)
|
||||||
const address = baddress.fromOutputScript(script, NETWORKS[f.network])
|
const address = baddress.fromOutputScript(script, NETWORKS[f.network])
|
||||||
|
|
||||||
|
@ -71,22 +70,22 @@ describe('address', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.fromOutputScript.forEach(function (f) {
|
fixtures.invalid.fromOutputScript.forEach(f => {
|
||||||
it('throws when ' + f.script.slice(0, 30) + '... ' + f.exception, function () {
|
it('throws when ' + f.script.slice(0, 30) + '... ' + f.exception, () => {
|
||||||
const script = bscript.fromASM(f.script)
|
const script = bscript.fromASM(f.script)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
baddress.fromOutputScript(script)
|
baddress.fromOutputScript(script)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('toBase58Check', function () {
|
describe('toBase58Check', () => {
|
||||||
fixtures.standard.forEach(function (f) {
|
fixtures.standard.forEach(f => {
|
||||||
if (!f.base58check) return
|
if (!f.base58check) return
|
||||||
|
|
||||||
it('encodes ' + f.hash + ' (' + f.network + ')', function () {
|
it('encodes ' + f.hash + ' (' + f.network + ')', () => {
|
||||||
const address = baddress.toBase58Check(Buffer.from(f.hash, 'hex'), f.version)
|
const address = baddress.toBase58Check(Buffer.from(f.hash, 'hex'), f.version)
|
||||||
|
|
||||||
assert.strictEqual(address, f.base58check)
|
assert.strictEqual(address, f.base58check)
|
||||||
|
@ -94,39 +93,39 @@ describe('address', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('toBech32', function () {
|
describe('toBech32', () => {
|
||||||
fixtures.bech32.forEach((f, i) => {
|
fixtures.bech32.forEach((f, i) => {
|
||||||
if (!f.bech32) return
|
if (!f.bech32) return
|
||||||
const data = Buffer.from(f.data, 'hex')
|
const data = Buffer.from(f.data, 'hex')
|
||||||
|
|
||||||
it('encode ' + f.address, function () {
|
it('encode ' + f.address, () => {
|
||||||
assert.deepEqual(baddress.toBech32(data, f.version, f.prefix), f.address)
|
assert.deepStrictEqual(baddress.toBech32(data, f.version, f.prefix), f.address)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.bech32.forEach((f, i) => {
|
fixtures.invalid.bech32.forEach((f, i) => {
|
||||||
if (!f.prefix || f.version === undefined || f.data === undefined) return
|
if (!f.prefix || f.version === undefined || f.data === undefined) return
|
||||||
|
|
||||||
it('encode fails (' + f.exception, function () {
|
it('encode fails (' + f.exception, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
baddress.toBech32(Buffer.from(f.data, 'hex'), f.version, f.prefix)
|
baddress.toBech32(Buffer.from(f.data, 'hex'), f.version, f.prefix)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('toOutputScript', function () {
|
describe('toOutputScript', () => {
|
||||||
fixtures.standard.forEach(function (f) {
|
fixtures.standard.forEach(f => {
|
||||||
it('decodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', function () {
|
it('decodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => {
|
||||||
const script = baddress.toOutputScript(f.base58check || f.bech32, NETWORKS[f.network])
|
const script = baddress.toOutputScript(f.base58check || f.bech32, NETWORKS[f.network])
|
||||||
|
|
||||||
assert.strictEqual(bscript.toASM(script), f.script)
|
assert.strictEqual(bscript.toASM(script), f.script)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.toOutputScript.forEach(function (f) {
|
fixtures.invalid.toOutputScript.forEach(f => {
|
||||||
it('throws when ' + f.exception, function () {
|
it('throws when ' + f.exception, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
baddress.toOutputScript(f.address, f.network)
|
baddress.toOutputScript(f.address, f.network)
|
||||||
}, new RegExp(f.address + ' ' + f.exception))
|
}, new RegExp(f.address + ' ' + f.exception))
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const bitcoin = require('../')
|
const bitcoin = require('../')
|
||||||
|
@ -13,21 +12,21 @@ const sigHash = require('./fixtures/core/sighash.json')
|
||||||
const sigNoncanonical = require('./fixtures/core/sig_noncanonical.json')
|
const sigNoncanonical = require('./fixtures/core/sig_noncanonical.json')
|
||||||
const txValid = require('./fixtures/core/tx_valid.json')
|
const txValid = require('./fixtures/core/tx_valid.json')
|
||||||
|
|
||||||
describe('Bitcoin-core', function () {
|
describe('Bitcoin-core', () => {
|
||||||
// base58EncodeDecode
|
// base58EncodeDecode
|
||||||
describe('base58', function () {
|
describe('base58', () => {
|
||||||
base58EncodeDecode.forEach(function (f) {
|
base58EncodeDecode.forEach(f => {
|
||||||
const fhex = f[0]
|
const fhex = f[0]
|
||||||
const fb58 = f[1]
|
const fb58 = f[1]
|
||||||
|
|
||||||
it('can decode ' + fb58, function () {
|
it('can decode ' + fb58, () => {
|
||||||
const buffer = base58.decode(fb58)
|
const buffer = base58.decode(fb58)
|
||||||
const actual = buffer.toString('hex')
|
const actual = buffer.toString('hex')
|
||||||
|
|
||||||
assert.strictEqual(actual, fhex)
|
assert.strictEqual(actual, fhex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can encode ' + fhex, function () {
|
it('can encode ' + fhex, () => {
|
||||||
const buffer = Buffer.from(fhex, 'hex')
|
const buffer = Buffer.from(fhex, 'hex')
|
||||||
const actual = base58.encode(buffer)
|
const actual = base58.encode(buffer)
|
||||||
|
|
||||||
|
@ -37,13 +36,13 @@ describe('Bitcoin-core', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
// base58KeysValid
|
// base58KeysValid
|
||||||
describe('address.toBase58Check', function () {
|
describe('address.toBase58Check', () => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'pubkey': 'pubKeyHash',
|
'pubkey': 'pubKeyHash',
|
||||||
'script': 'scriptHash'
|
'script': 'scriptHash'
|
||||||
}
|
}
|
||||||
|
|
||||||
base58KeysValid.forEach(function (f) {
|
base58KeysValid.forEach(f => {
|
||||||
const expected = f[0]
|
const expected = f[0]
|
||||||
const hash = Buffer.from(f[1], 'hex')
|
const hash = Buffer.from(f[1], 'hex')
|
||||||
const params = f[2]
|
const params = f[2]
|
||||||
|
@ -53,14 +52,14 @@ describe('Bitcoin-core', function () {
|
||||||
const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
|
const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
|
||||||
const version = network[typeMap[params.addrType]]
|
const version = network[typeMap[params.addrType]]
|
||||||
|
|
||||||
it('can export ' + expected, function () {
|
it('can export ' + expected, () => {
|
||||||
assert.strictEqual(bitcoin.address.toBase58Check(hash, version), expected)
|
assert.strictEqual(bitcoin.address.toBase58Check(hash, version), expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// base58KeysInvalid
|
// base58KeysInvalid
|
||||||
describe('address.fromBase58Check', function () {
|
describe('address.fromBase58Check', () => {
|
||||||
const allowedNetworks = [
|
const allowedNetworks = [
|
||||||
bitcoin.networks.bitcoin.pubkeyhash,
|
bitcoin.networks.bitcoin.pubkeyhash,
|
||||||
bitcoin.networks.bitcoin.scripthash,
|
bitcoin.networks.bitcoin.scripthash,
|
||||||
|
@ -68,22 +67,22 @@ describe('Bitcoin-core', function () {
|
||||||
bitcoin.networks.testnet.scripthash
|
bitcoin.networks.testnet.scripthash
|
||||||
]
|
]
|
||||||
|
|
||||||
base58KeysInvalid.forEach(function (f) {
|
base58KeysInvalid.forEach(f => {
|
||||||
const string = f[0]
|
const string = f[0]
|
||||||
|
|
||||||
it('throws on ' + string, function () {
|
it('throws on ' + string, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
const address = bitcoin.address.fromBase58Check(string)
|
const address = bitcoin.address.fromBase58Check(string)
|
||||||
|
|
||||||
assert.notEqual(allowedNetworks.indexOf(address.version), -1, 'Invalid network')
|
assert.notStrictEqual(allowedNetworks.indexOf(address.version), -1, 'Invalid network')
|
||||||
}, /(Invalid (checksum|network))|(too (short|long))/)
|
}, /(Invalid (checksum|network))|(too (short|long))/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// base58KeysValid
|
// base58KeysValid
|
||||||
describe('ECPair', function () {
|
describe('ECPair', () => {
|
||||||
base58KeysValid.forEach(function (f) {
|
base58KeysValid.forEach(f => {
|
||||||
const string = f[0]
|
const string = f[0]
|
||||||
const hex = f[1]
|
const hex = f[1]
|
||||||
const params = f[2]
|
const params = f[2]
|
||||||
|
@ -93,38 +92,38 @@ describe('Bitcoin-core', function () {
|
||||||
const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
|
const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin
|
||||||
const keyPair = bitcoin.ECPair.fromWIF(string, network)
|
const keyPair = bitcoin.ECPair.fromWIF(string, network)
|
||||||
|
|
||||||
it('fromWIF imports ' + string, function () {
|
it('fromWIF imports ' + string, () => {
|
||||||
assert.strictEqual(keyPair.privateKey.toString('hex'), hex)
|
assert.strictEqual(keyPair.privateKey.toString('hex'), hex)
|
||||||
assert.strictEqual(keyPair.compressed, params.isCompressed)
|
assert.strictEqual(keyPair.compressed, params.isCompressed)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toWIF exports ' + hex + ' to ' + string, function () {
|
it('toWIF exports ' + hex + ' to ' + string, () => {
|
||||||
assert.strictEqual(keyPair.toWIF(), string)
|
assert.strictEqual(keyPair.toWIF(), string)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// base58KeysInvalid
|
// base58KeysInvalid
|
||||||
describe('ECPair.fromWIF', function () {
|
describe('ECPair.fromWIF', () => {
|
||||||
const allowedNetworks = [
|
const allowedNetworks = [
|
||||||
bitcoin.networks.bitcoin,
|
bitcoin.networks.bitcoin,
|
||||||
bitcoin.networks.testnet
|
bitcoin.networks.testnet
|
||||||
]
|
]
|
||||||
|
|
||||||
base58KeysInvalid.forEach(function (f) {
|
base58KeysInvalid.forEach(f => {
|
||||||
const string = f[0]
|
const string = f[0]
|
||||||
|
|
||||||
it('throws on ' + string, function () {
|
it('throws on ' + string, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
bitcoin.ECPair.fromWIF(string, allowedNetworks)
|
bitcoin.ECPair.fromWIF(string, allowedNetworks)
|
||||||
}, /(Invalid|Unknown) (checksum|compression flag|network version|WIF length)/)
|
}, /(Invalid|Unknown) (checksum|compression flag|network version|WIF length)/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Block.fromHex', function () {
|
describe('Block.fromHex', () => {
|
||||||
blocksValid.forEach(function (f) {
|
blocksValid.forEach(f => {
|
||||||
it('can parse ' + f.id, function () {
|
it('can parse ' + f.id, () => {
|
||||||
const block = bitcoin.Block.fromHex(f.hex)
|
const block = bitcoin.Block.fromHex(f.hex)
|
||||||
|
|
||||||
assert.strictEqual(block.getId(), f.id)
|
assert.strictEqual(block.getId(), f.id)
|
||||||
|
@ -134,8 +133,8 @@ describe('Bitcoin-core', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
// txValid
|
// txValid
|
||||||
describe('Transaction.fromHex', function () {
|
describe('Transaction.fromHex', () => {
|
||||||
txValid.forEach(function (f) {
|
txValid.forEach(f => {
|
||||||
// Objects that are only a single string are ignored
|
// Objects that are only a single string are ignored
|
||||||
if (f.length === 1) return
|
if (f.length === 1) return
|
||||||
|
|
||||||
|
@ -143,17 +142,17 @@ describe('Bitcoin-core', function () {
|
||||||
const fhex = f[1]
|
const fhex = f[1]
|
||||||
// const verifyFlags = f[2] // TODO: do we need to test this?
|
// const verifyFlags = f[2] // TODO: do we need to test this?
|
||||||
|
|
||||||
it('can decode ' + fhex, function () {
|
it('can decode ' + fhex, () => {
|
||||||
const transaction = bitcoin.Transaction.fromHex(fhex)
|
const transaction = bitcoin.Transaction.fromHex(fhex)
|
||||||
|
|
||||||
transaction.ins.forEach(function (txIn, i) {
|
transaction.ins.forEach((txIn, i) => {
|
||||||
const input = inputs[i]
|
const input = inputs[i]
|
||||||
|
|
||||||
// reverse because test data is reversed
|
// reverse because test data is reversed
|
||||||
const prevOutHash = Buffer.from(input[0], 'hex').reverse()
|
const prevOutHash = Buffer.from(input[0], 'hex').reverse()
|
||||||
const prevOutIndex = input[1]
|
const prevOutIndex = input[1]
|
||||||
|
|
||||||
assert.deepEqual(txIn.hash, prevOutHash)
|
assert.deepStrictEqual(txIn.hash, prevOutHash)
|
||||||
|
|
||||||
// we read UInt32, not Int32
|
// we read UInt32, not Int32
|
||||||
assert.strictEqual(txIn.index & 0xffffffff, prevOutIndex)
|
assert.strictEqual(txIn.index & 0xffffffff, prevOutIndex)
|
||||||
|
@ -163,8 +162,8 @@ describe('Bitcoin-core', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
// sighash
|
// sighash
|
||||||
describe('Transaction', function () {
|
describe('Transaction', () => {
|
||||||
sigHash.forEach(function (f) {
|
sigHash.forEach(f => {
|
||||||
// Objects that are only a single string are ignored
|
// Objects that are only a single string are ignored
|
||||||
if (f.length === 1) return
|
if (f.length === 1) return
|
||||||
|
|
||||||
|
@ -182,7 +181,7 @@ describe('Bitcoin-core', function () {
|
||||||
|
|
||||||
const hashTypeName = hashTypes.join(' | ')
|
const hashTypeName = hashTypes.join(' | ')
|
||||||
|
|
||||||
it('should hash ' + txHex.slice(0, 40) + '... (' + hashTypeName + ')', function () {
|
it('should hash ' + txHex.slice(0, 40) + '... (' + hashTypeName + ')', () => {
|
||||||
const transaction = bitcoin.Transaction.fromHex(txHex)
|
const transaction = bitcoin.Transaction.fromHex(txHex)
|
||||||
assert.strictEqual(transaction.toHex(), txHex)
|
assert.strictEqual(transaction.toHex(), txHex)
|
||||||
|
|
||||||
|
@ -193,16 +192,16 @@ describe('Bitcoin-core', function () {
|
||||||
const hash = transaction.hashForSignature(inIndex, script, hashType)
|
const hash = transaction.hashForSignature(inIndex, script, hashType)
|
||||||
|
|
||||||
// reverse because test data is reversed
|
// reverse because test data is reversed
|
||||||
assert.equal(hash.reverse().toString('hex'), expectedHash)
|
assert.strictEqual(hash.reverse().toString('hex'), expectedHash)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('script.signature.decode', function () {
|
describe('script.signature.decode', () => {
|
||||||
sigCanonical.forEach(function (hex) {
|
sigCanonical.forEach(hex => {
|
||||||
const buffer = Buffer.from(hex, 'hex')
|
const buffer = Buffer.from(hex, 'hex')
|
||||||
|
|
||||||
it('can parse ' + hex, function () {
|
it('can parse ' + hex, () => {
|
||||||
const parsed = bitcoin.script.signature.decode(buffer)
|
const parsed = bitcoin.script.signature.decode(buffer)
|
||||||
const actual = bitcoin.script.signature.encode(parsed.signature, parsed.hashType)
|
const actual = bitcoin.script.signature.encode(parsed.signature, parsed.hashType)
|
||||||
|
|
||||||
|
@ -210,15 +209,15 @@ describe('Bitcoin-core', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
sigNoncanonical.forEach(function (hex, i) {
|
sigNoncanonical.forEach((hex, i) => {
|
||||||
if (i === 0) return
|
if (i === 0) return
|
||||||
if (i % 2 !== 0) return
|
if (i % 2 !== 0) return
|
||||||
|
|
||||||
const description = sigNoncanonical[i - 1].slice(0, -1)
|
const description = sigNoncanonical[i - 1].slice(0, -1)
|
||||||
const buffer = Buffer.from(hex, 'hex')
|
const buffer = Buffer.from(hex, 'hex')
|
||||||
|
|
||||||
it('throws on ' + description, function () {
|
it('throws on ' + description, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
bitcoin.script.signature.decode(buffer)
|
bitcoin.script.signature.decode(buffer)
|
||||||
}, /Expected DER (integer|sequence)|(R|S) value (excessively padded|is negative)|(R|S|DER sequence) length is (zero|too short|too long|invalid)|Invalid hashType/)
|
}, /Expected DER (integer|sequence)|(R|S) value (excessively padded|is negative)|(R|S|DER sequence) length is (zero|too short|too long|invalid)|Invalid hashType/)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,38 +1,40 @@
|
||||||
/* global describe, it, beforeEach */
|
const { describe, it, beforeEach } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const Block = require('../src/block')
|
const Block = require('..').Block
|
||||||
|
|
||||||
const fixtures = require('./fixtures/block')
|
const fixtures = require('./fixtures/block')
|
||||||
|
|
||||||
describe('Block', function () {
|
describe('Block', () => {
|
||||||
describe('version', function () {
|
describe('version', () => {
|
||||||
it('should be interpreted as an int32le', function () {
|
it('should be interpreted as an int32le', () => {
|
||||||
const blockHex = 'ffffffff0000000000000000000000000000000000000000000000000000000000000000414141414141414141414141414141414141414141414141414141414141414101000000020000000300000000'
|
const blockHex = 'ffffffff0000000000000000000000000000000000000000000000000000000000000000414141414141414141414141414141414141414141414141414141414141414101000000020000000300000000'
|
||||||
const block = Block.fromHex(blockHex)
|
const block = Block.fromHex(blockHex)
|
||||||
assert.equal(-1, block.version)
|
assert.strictEqual(-1, block.version)
|
||||||
assert.equal(1, block.timestamp)
|
assert.strictEqual(1, block.timestamp)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('calculateTarget', function () {
|
describe('calculateTarget', () => {
|
||||||
fixtures.targets.forEach(function (f) {
|
fixtures.targets.forEach(f => {
|
||||||
it('returns ' + f.expected + ' for 0x' + f.bits, function () {
|
it('returns ' + f.expected + ' for 0x' + f.bits, () => {
|
||||||
const bits = parseInt(f.bits, 16)
|
const bits = parseInt(f.bits, 16)
|
||||||
|
|
||||||
assert.equal(Block.calculateTarget(bits).toString('hex'), f.expected)
|
assert.strictEqual(Block.calculateTarget(bits).toString('hex'), f.expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromBuffer/fromHex', function () {
|
describe('fromBuffer/fromHex', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('imports ' + f.description, function () {
|
it('imports ' + f.description, () => {
|
||||||
const block = Block.fromHex(f.hex)
|
const block = Block.fromHex(f.hex)
|
||||||
|
|
||||||
assert.strictEqual(block.version, f.version)
|
assert.strictEqual(block.version, f.version)
|
||||||
assert.strictEqual(block.prevHash.toString('hex'), f.prevHash)
|
assert.strictEqual(block.prevHash.toString('hex'), f.prevHash)
|
||||||
assert.strictEqual(block.merkleRoot.toString('hex'), f.merkleRoot)
|
assert.strictEqual(block.merkleRoot.toString('hex'), f.merkleRoot)
|
||||||
|
if (block.witnessCommit) {
|
||||||
|
assert.strictEqual(block.witnessCommit.toString('hex'), f.witnessCommit)
|
||||||
|
}
|
||||||
assert.strictEqual(block.timestamp, f.timestamp)
|
assert.strictEqual(block.timestamp, f.timestamp)
|
||||||
assert.strictEqual(block.bits, f.bits)
|
assert.strictEqual(block.bits, f.bits)
|
||||||
assert.strictEqual(block.nonce, f.nonce)
|
assert.strictEqual(block.nonce, f.nonce)
|
||||||
|
@ -40,54 +42,54 @@ describe('Block', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.forEach(function (f) {
|
fixtures.invalid.forEach(f => {
|
||||||
it('throws on ' + f.exception, function () {
|
it('throws on ' + f.exception, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
Block.fromHex(f.hex)
|
Block.fromHex(f.hex)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('toBuffer/toHex', function () {
|
describe('toBuffer/toHex', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
let block
|
let block
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
block = Block.fromHex(f.hex)
|
block = Block.fromHex(f.hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('exports ' + f.description, function () {
|
it('exports ' + f.description, () => {
|
||||||
assert.strictEqual(block.toHex(true), f.hex.slice(0, 160))
|
assert.strictEqual(block.toHex(true), f.hex.slice(0, 160))
|
||||||
assert.strictEqual(block.toHex(), f.hex)
|
assert.strictEqual(block.toHex(), f.hex)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getHash/getId', function () {
|
describe('getHash/getId', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
let block
|
let block
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
block = Block.fromHex(f.hex)
|
block = Block.fromHex(f.hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns ' + f.id + ' for ' + f.description, function () {
|
it('returns ' + f.id + ' for ' + f.description, () => {
|
||||||
assert.strictEqual(block.getHash().toString('hex'), f.hash)
|
assert.strictEqual(block.getHash().toString('hex'), f.hash)
|
||||||
assert.strictEqual(block.getId(), f.id)
|
assert.strictEqual(block.getId(), f.id)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getUTCDate', function () {
|
describe('getUTCDate', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
let block
|
let block
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
block = Block.fromHex(f.hex)
|
block = Block.fromHex(f.hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns UTC date of ' + f.id, function () {
|
it('returns UTC date of ' + f.id, () => {
|
||||||
const utcDate = block.getUTCDate().getTime()
|
const utcDate = block.getUTCDate().getTime()
|
||||||
|
|
||||||
assert.strictEqual(utcDate, f.timestamp * 1e3)
|
assert.strictEqual(utcDate, f.timestamp * 1e3)
|
||||||
|
@ -95,53 +97,59 @@ describe('Block', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('calculateMerkleRoot', function () {
|
describe('calculateMerkleRoot', () => {
|
||||||
it('should throw on zero-length transaction array', function () {
|
it('should throw on zero-length transaction array', () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
Block.calculateMerkleRoot([])
|
Block.calculateMerkleRoot([])
|
||||||
}, /Cannot compute merkle root for zero transactions/)
|
}, /Cannot compute merkle root for zero transactions/)
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
if (f.hex.length === 160) return
|
if (f.hex.length === 160) return
|
||||||
|
|
||||||
let block
|
let block
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
block = Block.fromHex(f.hex)
|
block = Block.fromHex(f.hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns ' + f.merkleRoot + ' for ' + f.id, function () {
|
it('returns ' + f.merkleRoot + ' for ' + f.id, () => {
|
||||||
assert.strictEqual(Block.calculateMerkleRoot(block.transactions).toString('hex'), f.merkleRoot)
|
assert.strictEqual(Block.calculateMerkleRoot(block.transactions).toString('hex'), f.merkleRoot)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (f.witnessCommit) {
|
||||||
|
it('returns witness commit ' + f.witnessCommit + ' for ' + f.id, () => {
|
||||||
|
assert.strictEqual(Block.calculateMerkleRoot(block.transactions, true).toString('hex'), f.witnessCommit)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('checkMerkleRoot', function () {
|
describe('checkTxRoots', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
if (f.hex.length === 160) return
|
if (f.hex.length === 160) return
|
||||||
|
|
||||||
let block
|
let block
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
block = Block.fromHex(f.hex)
|
block = Block.fromHex(f.hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns ' + f.valid + ' for ' + f.id, function () {
|
it('returns ' + f.valid + ' for ' + f.id, () => {
|
||||||
assert.strictEqual(block.checkMerkleRoot(), true)
|
assert.strictEqual(block.checkTxRoots(), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('checkProofOfWork', function () {
|
describe('checkProofOfWork', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
let block
|
let block
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
block = Block.fromHex(f.hex)
|
block = Block.fromHex(f.hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns ' + f.valid + ' for ' + f.id, function () {
|
it('returns ' + f.valid + ' for ' + f.id, () => {
|
||||||
assert.strictEqual(block.checkProofOfWork(), f.valid)
|
assert.strictEqual(block.checkProofOfWork(), f.valid)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bufferutils = require('../src/bufferutils')
|
const bufferutils = require('../src/bufferutils')
|
||||||
|
|
||||||
const fixtures = require('./fixtures/bufferutils.json')
|
const fixtures = require('./fixtures/bufferutils.json')
|
||||||
|
|
||||||
describe('bufferutils', function () {
|
describe('bufferutils', () => {
|
||||||
describe('readUInt64LE', function () {
|
describe('readUInt64LE', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('decodes ' + f.hex, function () {
|
it('decodes ' + f.hex, () => {
|
||||||
const buffer = Buffer.from(f.hex, 'hex')
|
const buffer = Buffer.from(f.hex, 'hex')
|
||||||
const number = bufferutils.readUInt64LE(buffer, 0)
|
const number = bufferutils.readUInt64LE(buffer, 0)
|
||||||
|
|
||||||
|
@ -16,20 +15,20 @@ describe('bufferutils', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.readUInt64LE.forEach(function (f) {
|
fixtures.invalid.readUInt64LE.forEach(f => {
|
||||||
it('throws on ' + f.description, function () {
|
it('throws on ' + f.description, () => {
|
||||||
const buffer = Buffer.from(f.hex, 'hex')
|
const buffer = Buffer.from(f.hex, 'hex')
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
bufferutils.readUInt64LE(buffer, 0)
|
bufferutils.readUInt64LE(buffer, 0)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('writeUInt64LE', function () {
|
describe('writeUInt64LE', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('encodes ' + f.dec, function () {
|
it('encodes ' + f.dec, () => {
|
||||||
const buffer = Buffer.alloc(8, 0)
|
const buffer = Buffer.alloc(8, 0)
|
||||||
|
|
||||||
bufferutils.writeUInt64LE(buffer, f.dec, 0)
|
bufferutils.writeUInt64LE(buffer, f.dec, 0)
|
||||||
|
@ -37,11 +36,11 @@ describe('bufferutils', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.readUInt64LE.forEach(function (f) {
|
fixtures.invalid.readUInt64LE.forEach(f => {
|
||||||
it('throws on ' + f.description, function () {
|
it('throws on ' + f.description, () => {
|
||||||
const buffer = Buffer.alloc(8, 0)
|
const buffer = Buffer.alloc(8, 0)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
bufferutils.writeUInt64LE(buffer, f.dec, 0)
|
bufferutils.writeUInt64LE(buffer, f.dec, 0)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bscript = require('../src/script')
|
const bscript = require('../src/script')
|
||||||
const classify = require('../src/classify')
|
const classify = require('../src/classify')
|
||||||
|
@ -26,12 +25,12 @@ const tmap = {
|
||||||
witnessCommitment
|
witnessCommitment
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('classify', function () {
|
describe('classify', () => {
|
||||||
describe('input', function () {
|
describe('input', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
if (!f.input) return
|
if (!f.input) return
|
||||||
|
|
||||||
it('classifies ' + f.input + ' as ' + f.type, function () {
|
it('classifies ' + f.input + ' as ' + f.type, () => {
|
||||||
const input = bscript.fromASM(f.input)
|
const input = bscript.fromASM(f.input)
|
||||||
const type = classify.input(input)
|
const type = classify.input(input)
|
||||||
|
|
||||||
|
@ -39,11 +38,11 @@ describe('classify', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
if (!f.input) return
|
if (!f.input) return
|
||||||
if (!f.typeIncomplete) return
|
if (!f.typeIncomplete) return
|
||||||
|
|
||||||
it('classifies incomplete ' + f.input + ' as ' + f.typeIncomplete, function () {
|
it('classifies incomplete ' + f.input + ' as ' + f.typeIncomplete, () => {
|
||||||
const input = bscript.fromASM(f.input)
|
const input = bscript.fromASM(f.input)
|
||||||
const type = classify.input(input, true)
|
const type = classify.input(input, true)
|
||||||
|
|
||||||
|
@ -52,11 +51,11 @@ describe('classify', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('classifyOutput', function () {
|
describe('classifyOutput', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
if (!f.output) return
|
if (!f.output) return
|
||||||
|
|
||||||
it('classifies ' + f.output + ' as ' + f.type, function () {
|
it('classifies ' + f.output + ' as ' + f.type, () => {
|
||||||
const output = bscript.fromASM(f.output)
|
const output = bscript.fromASM(f.output)
|
||||||
const type = classify.output(output)
|
const type = classify.output(output)
|
||||||
|
|
||||||
|
@ -74,12 +73,12 @@ describe('classify', function () {
|
||||||
'multisig',
|
'multisig',
|
||||||
'nullData',
|
'nullData',
|
||||||
'witnessCommitment'
|
'witnessCommitment'
|
||||||
].forEach(function (name) {
|
].forEach(name => {
|
||||||
const inputType = tmap[name].input
|
const inputType = tmap[name].input
|
||||||
const outputType = tmap[name].output
|
const outputType = tmap[name].output
|
||||||
|
|
||||||
describe(name + '.input.check', function () {
|
describe(name + '.input.check', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
if (name.toLowerCase() === classify.types.P2WPKH) return
|
if (name.toLowerCase() === classify.types.P2WPKH) return
|
||||||
if (name.toLowerCase() === classify.types.P2WSH) return
|
if (name.toLowerCase() === classify.types.P2WSH) return
|
||||||
const expected = name.toLowerCase() === f.type.toLowerCase()
|
const expected = name.toLowerCase() === f.type.toLowerCase()
|
||||||
|
@ -87,14 +86,14 @@ describe('classify', function () {
|
||||||
if (inputType && f.input) {
|
if (inputType && f.input) {
|
||||||
const input = bscript.fromASM(f.input)
|
const input = bscript.fromASM(f.input)
|
||||||
|
|
||||||
it('returns ' + expected + ' for ' + f.input, function () {
|
it('returns ' + expected + ' for ' + f.input, () => {
|
||||||
assert.strictEqual(inputType.check(input), expected)
|
assert.strictEqual(inputType.check(input), expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (f.typeIncomplete) {
|
if (f.typeIncomplete) {
|
||||||
const expectedIncomplete = name.toLowerCase() === f.typeIncomplete
|
const expectedIncomplete = name.toLowerCase() === f.typeIncomplete
|
||||||
|
|
||||||
it('returns ' + expected + ' for ' + f.input, function () {
|
it('returns ' + expected + ' for ' + f.input, () => {
|
||||||
assert.strictEqual(inputType.check(input, true), expectedIncomplete)
|
assert.strictEqual(inputType.check(input, true), expectedIncomplete)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -103,10 +102,10 @@ describe('classify', function () {
|
||||||
|
|
||||||
if (!(fixtures.invalid[name])) return
|
if (!(fixtures.invalid[name])) return
|
||||||
|
|
||||||
fixtures.invalid[name].inputs.forEach(function (f) {
|
fixtures.invalid[name].inputs.forEach(f => {
|
||||||
if (!f.input && !f.inputHex) return
|
if (!f.input && !f.inputHex) return
|
||||||
|
|
||||||
it('returns false for ' + f.description + ' (' + (f.input || f.inputHex) + ')', function () {
|
it('returns false for ' + f.description + ' (' + (f.input || f.inputHex) + ')', () => {
|
||||||
let input
|
let input
|
||||||
|
|
||||||
if (f.input) {
|
if (f.input) {
|
||||||
|
@ -120,12 +119,12 @@ describe('classify', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe(name + '.output.check', function () {
|
describe(name + '.output.check', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
const expected = name.toLowerCase() === f.type
|
const expected = name.toLowerCase() === f.type
|
||||||
|
|
||||||
if (outputType && f.output) {
|
if (outputType && f.output) {
|
||||||
it('returns ' + expected + ' for ' + f.output, function () {
|
it('returns ' + expected + ' for ' + f.output, () => {
|
||||||
const output = bscript.fromASM(f.output)
|
const output = bscript.fromASM(f.output)
|
||||||
|
|
||||||
if (name.toLowerCase() === 'nulldata' && f.type === classify.types.WITNESS_COMMITMENT) return
|
if (name.toLowerCase() === 'nulldata' && f.type === classify.types.WITNESS_COMMITMENT) return
|
||||||
|
@ -137,10 +136,10 @@ describe('classify', function () {
|
||||||
|
|
||||||
if (!(fixtures.invalid[name])) return
|
if (!(fixtures.invalid[name])) return
|
||||||
|
|
||||||
fixtures.invalid[name].outputs.forEach(function (f) {
|
fixtures.invalid[name].outputs.forEach(f => {
|
||||||
if (!f.output && !f.outputHex) return
|
if (!f.output && !f.outputHex) return
|
||||||
|
|
||||||
it('returns false for ' + f.description + ' (' + (f.output || f.outputHex) + ')', function () {
|
it('returns false for ' + f.description + ' (' + (f.output || f.outputHex) + ')', () => {
|
||||||
let output
|
let output
|
||||||
|
|
||||||
if (f.output) {
|
if (f.output) {
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bcrypto = require('../src/crypto')
|
const bcrypto = require('../src/crypto')
|
||||||
|
|
||||||
const fixtures = require('./fixtures/crypto')
|
const fixtures = require('./fixtures/crypto')
|
||||||
|
|
||||||
describe('crypto', function () {
|
describe('crypto', () => {
|
||||||
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(function (algorithm) {
|
['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => {
|
||||||
describe(algorithm, function () {
|
describe(algorithm, () => {
|
||||||
fixtures.forEach(function (f) {
|
fixtures.forEach(f => {
|
||||||
const fn = bcrypto[algorithm]
|
const fn = bcrypto[algorithm]
|
||||||
const expected = f[algorithm]
|
const expected = f[algorithm]
|
||||||
|
|
||||||
it('returns ' + expected + ' for ' + f.hex, function () {
|
it('returns ' + expected + ' for ' + f.hex, () => {
|
||||||
const data = Buffer.from(f.hex, 'hex')
|
const data = Buffer.from(f.hex, 'hex')
|
||||||
const actual = fn(data).toString('hex')
|
const actual = fn(data).toString('hex')
|
||||||
|
|
||||||
|
|
138
test/ecpair.js
138
test/ecpair.js
|
@ -1,6 +1,4 @@
|
||||||
/* global describe, it, beforeEach */
|
const { describe, it, beforeEach } = require('mocha')
|
||||||
/* eslint-disable no-new */
|
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const proxyquire = require('proxyquire')
|
const proxyquire = require('proxyquire')
|
||||||
const hoodwink = require('hoodwink')
|
const hoodwink = require('hoodwink')
|
||||||
|
@ -21,16 +19,16 @@ const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000
|
||||||
const GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex')
|
const GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex')
|
||||||
const GROUP_ORDER_LESS_1 = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', 'hex')
|
const GROUP_ORDER_LESS_1 = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', 'hex')
|
||||||
|
|
||||||
describe('ECPair', function () {
|
describe('ECPair', () => {
|
||||||
describe('getPublicKey', function () {
|
describe('getPublicKey', () => {
|
||||||
let keyPair
|
let keyPair
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
keyPair = ECPair.fromPrivateKey(ONE)
|
keyPair = ECPair.fromPrivateKey(ONE)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls pointFromScalar lazily', hoodwink(function () {
|
it('calls pointFromScalar lazily', hoodwink(() => {
|
||||||
assert.strictEqual(keyPair.__Q, null)
|
assert.strictEqual(keyPair.__Q, undefined)
|
||||||
|
|
||||||
// .publicKey forces the memoization
|
// .publicKey forces the memoization
|
||||||
assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
||||||
|
@ -38,14 +36,14 @@ describe('ECPair', function () {
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromPrivateKey', function () {
|
describe('fromPrivateKey', () => {
|
||||||
it('defaults to compressed', function () {
|
it('defaults to compressed', () => {
|
||||||
const keyPair = ECPair.fromPrivateKey(ONE)
|
const keyPair = ECPair.fromPrivateKey(ONE)
|
||||||
|
|
||||||
assert.strictEqual(keyPair.compressed, true)
|
assert.strictEqual(keyPair.compressed, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports the uncompressed option', function () {
|
it('supports the uncompressed option', () => {
|
||||||
const keyPair = ECPair.fromPrivateKey(ONE, {
|
const keyPair = ECPair.fromPrivateKey(ONE, {
|
||||||
compressed: false
|
compressed: false
|
||||||
})
|
})
|
||||||
|
@ -53,7 +51,7 @@ describe('ECPair', function () {
|
||||||
assert.strictEqual(keyPair.compressed, false)
|
assert.strictEqual(keyPair.compressed, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports the network option', function () {
|
it('supports the network option', () => {
|
||||||
const keyPair = ECPair.fromPrivateKey(ONE, {
|
const keyPair = ECPair.fromPrivateKey(ONE, {
|
||||||
compressed: false,
|
compressed: false,
|
||||||
network: NETWORKS.testnet
|
network: NETWORKS.testnet
|
||||||
|
@ -62,8 +60,8 @@ describe('ECPair', function () {
|
||||||
assert.strictEqual(keyPair.network, NETWORKS.testnet)
|
assert.strictEqual(keyPair.network, NETWORKS.testnet)
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('derives public key for ' + f.WIF, function () {
|
it('derives public key for ' + f.WIF, () => {
|
||||||
const d = Buffer.from(f.d, 'hex')
|
const d = Buffer.from(f.d, 'hex')
|
||||||
const keyPair = ECPair.fromPrivateKey(d, {
|
const keyPair = ECPair.fromPrivateKey(d, {
|
||||||
compressed: f.compressed
|
compressed: f.compressed
|
||||||
|
@ -73,30 +71,30 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.fromPrivateKey.forEach(function (f) {
|
fixtures.invalid.fromPrivateKey.forEach(f => {
|
||||||
it('throws ' + f.exception, function () {
|
it('throws ' + f.exception, () => {
|
||||||
const d = Buffer.from(f.d, 'hex')
|
const d = Buffer.from(f.d, 'hex')
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
ECPair.fromPrivateKey(d, f.options)
|
ECPair.fromPrivateKey(d, f.options)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromPublicKey', function () {
|
describe('fromPublicKey', () => {
|
||||||
fixtures.invalid.fromPublicKey.forEach(function (f) {
|
fixtures.invalid.fromPublicKey.forEach(f => {
|
||||||
it('throws ' + f.exception, function () {
|
it('throws ' + f.exception, () => {
|
||||||
const Q = Buffer.from(f.Q, 'hex')
|
const Q = Buffer.from(f.Q, 'hex')
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
ECPair.fromPublicKey(Q, f.options)
|
ECPair.fromPublicKey(Q, f.options)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromWIF', function () {
|
describe('fromWIF', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('imports ' + f.WIF + ' (' + f.network + ')', function () {
|
it('imports ' + f.WIF + ' (' + f.network + ')', () => {
|
||||||
const network = NETWORKS[f.network]
|
const network = NETWORKS[f.network]
|
||||||
const keyPair = ECPair.fromWIF(f.WIF, network)
|
const keyPair = ECPair.fromWIF(f.WIF, network)
|
||||||
|
|
||||||
|
@ -106,8 +104,8 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('imports ' + f.WIF + ' (via list of networks)', function () {
|
it('imports ' + f.WIF + ' (via list of networks)', () => {
|
||||||
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
||||||
|
|
||||||
assert.strictEqual(keyPair.privateKey.toString('hex'), f.d)
|
assert.strictEqual(keyPair.privateKey.toString('hex'), f.d)
|
||||||
|
@ -116,9 +114,9 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.fromWIF.forEach(function (f) {
|
fixtures.invalid.fromWIF.forEach(f => {
|
||||||
it('throws on ' + f.WIF, function () {
|
it('throws on ' + f.WIF, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
const networks = f.network ? NETWORKS[f.network] : NETWORKS_LIST
|
const networks = f.network ? NETWORKS[f.network] : NETWORKS_LIST
|
||||||
|
|
||||||
ECPair.fromWIF(f.WIF, networks)
|
ECPair.fromWIF(f.WIF, networks)
|
||||||
|
@ -127,9 +125,9 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('toWIF', function () {
|
describe('toWIF', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('exports ' + f.WIF, function () {
|
it('exports ' + f.WIF, () => {
|
||||||
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
||||||
const result = keyPair.toWIF()
|
const result = keyPair.toWIF()
|
||||||
assert.strictEqual(result, f.WIF)
|
assert.strictEqual(result, f.WIF)
|
||||||
|
@ -137,13 +135,13 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('makeRandom', function () {
|
describe('makeRandom', () => {
|
||||||
const d = Buffer.alloc(32, 4)
|
const d = Buffer.alloc(32, 4)
|
||||||
const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv'
|
const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv'
|
||||||
|
|
||||||
describe('uses randombytes RNG', function () {
|
describe('uses randombytes RNG', () => {
|
||||||
it('generates a ECPair', function () {
|
it('generates a ECPair', () => {
|
||||||
const stub = { randombytes: function () { return d } }
|
const stub = { randombytes: () => { return d } }
|
||||||
const ProxiedECPair = proxyquire('../src/ecpair', stub)
|
const ProxiedECPair = proxyquire('../src/ecpair', stub)
|
||||||
|
|
||||||
const keyPair = ProxiedECPair.makeRandom()
|
const keyPair = ProxiedECPair.makeRandom()
|
||||||
|
@ -151,22 +149,22 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('allows a custom RNG to be used', function () {
|
it('allows a custom RNG to be used', () => {
|
||||||
const keyPair = ECPair.makeRandom({
|
const keyPair = ECPair.makeRandom({
|
||||||
rng: function (size) { return d.slice(0, size) }
|
rng: size => { return d.slice(0, size) }
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.strictEqual(keyPair.toWIF(), exWIF)
|
assert.strictEqual(keyPair.toWIF(), exWIF)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('retains the same defaults as ECPair constructor', function () {
|
it('retains the same defaults as ECPair constructor', () => {
|
||||||
const keyPair = ECPair.makeRandom()
|
const keyPair = ECPair.makeRandom()
|
||||||
|
|
||||||
assert.strictEqual(keyPair.compressed, true)
|
assert.strictEqual(keyPair.compressed, true)
|
||||||
assert.strictEqual(keyPair.network, NETWORKS.bitcoin)
|
assert.strictEqual(keyPair.network, NETWORKS.bitcoin)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports the options parameter', function () {
|
it('supports the options parameter', () => {
|
||||||
const keyPair = ECPair.makeRandom({
|
const keyPair = ECPair.makeRandom({
|
||||||
compressed: false,
|
compressed: false,
|
||||||
network: NETWORKS.testnet
|
network: NETWORKS.testnet
|
||||||
|
@ -176,19 +174,19 @@ describe('ECPair', function () {
|
||||||
assert.strictEqual(keyPair.network, NETWORKS.testnet)
|
assert.strictEqual(keyPair.network, NETWORKS.testnet)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws if d is bad length', function () {
|
it('throws if d is bad length', () => {
|
||||||
function rng () {
|
function rng () {
|
||||||
return Buffer.alloc(28)
|
return Buffer.alloc(28)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
ECPair.makeRandom({ rng: rng })
|
ECPair.makeRandom({ rng: rng })
|
||||||
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 28\)/)
|
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 28\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('loops until d is within interval [1, n) : 1', hoodwink(function () {
|
it('loops until d is within interval [1, n) : 1', hoodwink(function () {
|
||||||
const rng = this.stub(function f () {
|
const rng = this.stub(() => {
|
||||||
if (f.calls === 0) return ZERO // 0
|
if (rng.calls === 0) return ZERO // 0
|
||||||
return ONE // >0
|
return ONE // >0
|
||||||
}, 2)
|
}, 2)
|
||||||
|
|
||||||
|
@ -196,9 +194,9 @@ describe('ECPair', function () {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('loops until d is within interval [1, n) : n - 1', hoodwink(function () {
|
it('loops until d is within interval [1, n) : n - 1', hoodwink(function () {
|
||||||
const rng = this.stub(function f () {
|
const rng = this.stub(() => {
|
||||||
if (f.calls === 0) return ZERO // <1
|
if (rng.calls === 0) return ZERO // <1
|
||||||
if (f.calls === 1) return GROUP_ORDER // >n-1
|
if (rng.calls === 1) return GROUP_ORDER // >n-1
|
||||||
return GROUP_ORDER_LESS_1 // n-1
|
return GROUP_ORDER_LESS_1 // n-1
|
||||||
}, 3)
|
}, 3)
|
||||||
|
|
||||||
|
@ -206,9 +204,9 @@ describe('ECPair', function () {
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('.network', function () {
|
describe('.network', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('returns ' + f.network + ' for ' + f.WIF, function () {
|
it('returns ' + f.network + ' for ' + f.WIF, () => {
|
||||||
const network = NETWORKS[f.network]
|
const network = NETWORKS[f.network]
|
||||||
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
||||||
|
|
||||||
|
@ -217,20 +215,20 @@ describe('ECPair', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('tinysecp wrappers', function () {
|
describe('tinysecp wrappers', () => {
|
||||||
let keyPair
|
let keyPair
|
||||||
let hash
|
let hash
|
||||||
let signature
|
let signature
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
keyPair = ECPair.makeRandom()
|
keyPair = ECPair.makeRandom()
|
||||||
hash = ZERO
|
hash = ZERO
|
||||||
signature = Buffer.alloc(64, 1)
|
signature = Buffer.alloc(64, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('signing', function () {
|
describe('signing', () => {
|
||||||
it('wraps tinysecp.sign', hoodwink(function () {
|
it('wraps tinysecp.sign', hoodwink(function () {
|
||||||
this.mock(tinysecp, 'sign', function (h, d) {
|
this.mock(tinysecp, 'sign', (h, d) => {
|
||||||
assert.strictEqual(h, hash)
|
assert.strictEqual(h, hash)
|
||||||
assert.strictEqual(d, keyPair.privateKey)
|
assert.strictEqual(d, keyPair.privateKey)
|
||||||
return signature
|
return signature
|
||||||
|
@ -239,18 +237,18 @@ describe('ECPair', function () {
|
||||||
assert.strictEqual(keyPair.sign(hash), signature)
|
assert.strictEqual(keyPair.sign(hash), signature)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('throws if no private key is found', function () {
|
it('throws if no private key is found', () => {
|
||||||
delete keyPair.__d
|
delete keyPair.__D
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
keyPair.sign(hash)
|
keyPair.sign(hash)
|
||||||
}, /Missing private key/)
|
}, /Missing private key/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('verify', function () {
|
describe('verify', () => {
|
||||||
it('wraps tinysecp.verify', hoodwink(function () {
|
it('wraps tinysecp.verify', hoodwink(function () {
|
||||||
this.mock(tinysecp, 'verify', function (h, q, s) {
|
this.mock(tinysecp, 'verify', (h, q, s) => {
|
||||||
assert.strictEqual(h, hash)
|
assert.strictEqual(h, hash)
|
||||||
assert.strictEqual(q, keyPair.publicKey)
|
assert.strictEqual(q, keyPair.publicKey)
|
||||||
assert.strictEqual(s, signature)
|
assert.strictEqual(s, signature)
|
||||||
|
@ -261,4 +259,26 @@ describe('ECPair', function () {
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('optional low R signing', () => {
|
||||||
|
const sig = Buffer.from('95a6619140fca3366f1d3b013b0367c4f86e39508a50fdce' +
|
||||||
|
'e5245fbb8bd60aa6086449e28cf15387cf9f85100bfd0838624ca96759e59f65c10a00' +
|
||||||
|
'16b86f5229', 'hex')
|
||||||
|
const sigLowR = Buffer.from('6a2660c226e8055afad317eeba918a304be79208d505' +
|
||||||
|
'3bc5ea4a5e4c5892b4a061c717c5284ae5202d721c0e49b4717b79966280906b1d3b52' +
|
||||||
|
'95d1fdde963c35', 'hex')
|
||||||
|
const lowRKeyPair = ECPair.fromWIF('L3nThUzbAwpUiBAjR5zCu66ybXSPMr2zZ3ikp' +
|
||||||
|
'ScpTPiYTxBynfZu')
|
||||||
|
const dataToSign = Buffer.from('b6c5c548a7f6164c8aa7af5350901626ebd69f9ae' +
|
||||||
|
'2c1ecf8871f5088ec204cfe', 'hex')
|
||||||
|
|
||||||
|
it('signs with normal R by default', () => {
|
||||||
|
const signed = lowRKeyPair.sign(dataToSign)
|
||||||
|
assert.deepStrictEqual(sig, signed)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('signs with low R when true is passed', () => {
|
||||||
|
const signed = lowRKeyPair.sign(dataToSign, true)
|
||||||
|
assert.deepStrictEqual(sigLowR, signed)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
15
test/fixtures/address.json
vendored
15
test/fixtures/address.json
vendored
|
@ -63,6 +63,20 @@
|
||||||
"bech32": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
|
"bech32": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
|
||||||
"data": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
|
"data": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
|
||||||
"script": "OP_0 000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
|
"script": "OP_0 000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"network": "regtest",
|
||||||
|
"version": 0,
|
||||||
|
"bech32": "bcrt1qjh3dnrafy8f2zszh5sdqn6c3ycfljh930yza9nt72v30dkw8mlvscn82zx",
|
||||||
|
"data": "95e2d98fa921d2a14057a41a09eb112613f95cb17905d2cd7e5322f6d9c7dfd9",
|
||||||
|
"script": "OP_0 95e2d98fa921d2a14057a41a09eb112613f95cb17905d2cd7e5322f6d9c7dfd9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"network": "regtest",
|
||||||
|
"version": 0,
|
||||||
|
"bech32": "bcrt1qqqqqqqqqqqqqqahrwf6d62emdxmpq8gu3xe9au9fjwc9sxxn4k2qujfh7u",
|
||||||
|
"data": "000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94",
|
||||||
|
"script": "OP_0 000000000000000076e37274dd2b3b69b6101d1c89b25ef0a993b05818d3ad94"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bech32": [
|
"bech32": [
|
||||||
|
@ -179,4 +193,3 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
test/fixtures/block.json
vendored
19
test/fixtures/block.json
vendored
|
@ -19,6 +19,10 @@
|
||||||
{
|
{
|
||||||
"bits": "cffca00",
|
"bits": "cffca00",
|
||||||
"expected": "00000000000000000000000000000000000000007fca00000000000000000000"
|
"expected": "00000000000000000000000000000000000000007fca00000000000000000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bits": "207fffff",
|
||||||
|
"expected": "7fffff0000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"valid": [
|
"valid": [
|
||||||
|
@ -115,6 +119,21 @@
|
||||||
"timestamp": 1231006505,
|
"timestamp": 1231006505,
|
||||||
"valid": true,
|
"valid": true,
|
||||||
"version": 1
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Block with witness commit",
|
||||||
|
"bits": 388503969,
|
||||||
|
"hash": "ec61d8d62a4945034a1df40dd3dfda364221c0562c3a14000000000000000000",
|
||||||
|
"height": 542213,
|
||||||
|
"hex": "000000208980ebb11236bacc66c447d5ad961bc546c0f9cc385a08000000000000000000135e9b653aee9a70028aff2db53d4f43f4484ad52558c3ae300410b79cb6a864df50a35ba11928171396e30104010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5003054608174d696e656420627920416e74506f6f6c393b205ba350dffabe6d6d3a3e92d9efff857664de632e89fa3182f1e793d00be2e71a117b273145945a810400000000000000db250000acba0500ffffffff028a8f814a000000001976a914edf10a7fac6b32e24daa5305c723f3de58db1bc888ac0000000000000000266a24aa21a9ed4a657fcaa2149342376247e2e283a55a6b92dcc35d4d89e4ac7b74488cb63be201200000000000000000000000000000000000000000000000000000000000000000000000000100000001b898273f98d49399ecb5194ffdb1ed15c2fb37cf6d7696b4389bc7d1b76b63db010000006b483045022100e2e9bc1f6bae2deed086e935bb49fd6ac1e13dc3a44c36cd8b9a6f4257efb70d022076537c7021f12d761e1202796029f13798503bc22ab8c2ee8cb98207cbfeb414012102071c2c88e4560b47a03c033c736149a2ddd6071aea54ab85c5169cee156712f8ffffffff02b0710b00000000001976a914849a95fc65eeaa2ac47b6b6fc1f1883edb2c6c9788ace6b62501000000001976a9142964198f7ae9f7b920a2ab7c0b96b90e4ec9b14d88ac000000000100000000010152405e2660055b3540f63424a1b0b3b7bb9bbef10ceec970cd18f6f86f84a7880000000017160014dde2f1a9a4bfda011ba9ec4062990c7e1a531585ffffffff0266d418000000000017a914adc5ec550548f087371e645047170864d5fbdc03877e0400000000000016001441f6746110cc0fec102e83053d4c0ae56fab1bdd02483045022100f93bd5d3529418f60dddd477f169c32ea5ab340c99573438ee7c40d705db9ba20220323f1b4d8840098b1271c95365cdc7e8f81d0bf1fcc568c68fc7de8b871b4bee012103211d047d92547bca4aa116bc51ec4b09188e5991f69f0432fe1b5dfd8947859700000000010000000ac1a19854e5b92792ae96d08eb9f7fc016fb57c51a9047161c342e6b8de8ce721000000006b483045022100fa00a6651015ac807b03d8d54559831db40d80329f46a886b93ca6b3996daf9b02201d0fafccc88c4654a9c3d205ef0828e32376ecb42f79587615203f23fc8f2d42012102a2cda42f6954605e40cbc5601f65673621c057f8c12f16149fbbf632e8be8ed8ffffffffda16ff4a7324f78f16d5e5d4a740168de9df426cf53df569c9a33d1b94e2c25f000000006a4730440220167b4777a23db304f50ac75febfae5db0b1578c90d85769bc76e0f5458484319022023f763e95ea7771c15ea0df91c17519014b2223cd726a3b0a8b1a6f67f99b2bf012103b9492a823f03b70a1750e0fac44f58f5c81e09f4c18d0b28755b44c911ffab5ffffffffff1533f2ea34b859d2be05bbb6859d6105ad4389c911545487187437acae21b70000000006b483045022100be0b8dc174f4136e3fb4f3ccf1a2be67a44d2086179c5726c61a1af010d5232f02205c0fb4cdd7c9cb8698ceed05e688e10a0cbdd0ee5db1a0a2ef92f322e1a60e9f0121020b9f404317cc6ab5a699f607a9bb0acd0bf5588777672f8f7f4c1a13304e9f76ffffffff1395191837aa5b1cfa4cc2a79d6d6bda7d198e8a795a0f7a85f556c446119572000000006b483045022100ef3c61b5ba155b94fd02a96bf788e9a9be2de0ee53e3fe1fa6c24dd9d9aaee12022044c07be54a9c59fed96dfb778da0079f6753ab634d1920bf0fac25ebf7ac49d0012103f93e29be8b393773228f704151964662a91df4c1a3e15364e4cfb38a8cacbda9ffffffff55120f684d5fbb845fd0198cd734e46fc29f40dc8f906bf36743d7f740f5fd7c000000006a4730440220421aef290bbd39a18d1281ecfbb420b43daf2cc1c315b6bb44c895f88cb3cfcc022007a512c65b7768b1505f789b6d78479063d04ffb243072f2061eeb66fb1ade81012102712eea19c72fd644b2698c5480ebe77ce6face0bed64238a34a32085567f2f8efffffffff3553d19ce3156464e9cfa06a5260f9d9d01b16ccad6e2ebaab233ba10472ba6000000006b483045022100a29aea775d2c46028f40dceb1707b23e720bb314cef455854313569200c83162022009d0cdcef2e1f0a9217794991fa838fe6ce2bdc6e3c04ad490343c7e4a0ee7d3012102ed59ec6d98f9c2a4dd1324d46d74c56ff7e15935925f69799e547969a523ea98ffffffff530ab6d95f0d83669bfb12cbe983febe6ca638255139ce2ba0e35887f2fe3db6000000006b483045022100fad9f10989ab4ab019da4f6e430f9fe9ebe76941a9200d7346447358e93b1dce0220756c864b029a64ed9d6eedc13cf8b7d602572fcd7a3d3cb528350bb032d7e3ec012102e3e0c78d034627b1616cf196aa69bfaf20009ba9ebf09cf069453cc0423239e2ffffffff4b8e70824941d965df99703cacadfabcf79bc040029e528ff931e9ba4a7ee4d8000000006b48304502210095f37fb2700c9f96d5e5c02d4b043a7cd804f79dd071d8b221b7ae781f0f5b4e02200ae3135b8bebf813449956ae19e7c02db5eff2472da023afa8b8d4e9baba0cdd01210226cc53dfc0a41cc0cf7117dfd406db2b87161e89e2bab9908a2382173fc6dbe1ffffffff5262129e9881722b217f7e4882ed2b83ee53d9e23b9bc647494c9487ae9fabdf000000006a473044022032361f724fe006079cf37b3df61cb6c51cb0c5fd77f29b61a318134ca4948d0e02202fbe6d484a78730899230244f3662fd5578b87e9eaaccdf9bf8f05d23917de8301210254e84223b3d7f7cfd14315be8fa0c7d7eb1a1a34a672f08e2b8ec134472a66d4ffffffff067040e03288282ebe5db7b705130849c9984458b13ea7ca3c755f07b9d1b9f4000000006b483045022100b78b9c24f5f3e950aba637b827d5b11615c17ae2f43105941d172cc4a8b73c80022064998c17becd06abbcfde47c91b9d87da5ac67873ed9a412010b08b954e0b5cd01210329a0acc2b0d60dd243eef46073a672ed0caf467c92f63ef7293f2036a3851a1effffffff02027f0000000000001976a914c6a396ae979670eeaa6929df3dd1c2d8fba31c3d88ac85a19b020000000017a914409dbd0e9a1ab27853186367130e6aab2509e47f8700000000",
|
||||||
|
"id": "000000000000000000143a2c56c0214236dadfd30df41d4a0345492ad6d861ec",
|
||||||
|
"merkleRoot": "135e9b653aee9a70028aff2db53d4f43f4484ad52558c3ae300410b79cb6a864",
|
||||||
|
"witnessCommit": "4a657fcaa2149342376247e2e283a55a6b92dcc35d4d89e4ac7b74488cb63be2",
|
||||||
|
"nonce": 31692307,
|
||||||
|
"prevHash": "8980ebb11236bacc66c447d5ad961bc546c0f9cc385a08000000000000000000",
|
||||||
|
"timestamp": 1537429727,
|
||||||
|
"valid": true,
|
||||||
|
"version": 536870912
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"invalid": [
|
"invalid": [
|
||||||
|
|
9
test/fixtures/embed.json
vendored
9
test/fixtures/embed.json
vendored
|
@ -5,6 +5,7 @@
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"output": "OP_RETURN a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4"
|
"output": "OP_RETURN a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4"
|
||||||
},
|
},
|
||||||
|
"options": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"data": [
|
"data": [
|
||||||
"a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4"
|
"a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4"
|
||||||
|
@ -35,6 +36,14 @@
|
||||||
{
|
{
|
||||||
"exception": "Not enough data",
|
"exception": "Not enough data",
|
||||||
"arguments": {}
|
"arguments": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "First OP is not OP_RETURN",
|
||||||
|
"exception": "Output is invalid",
|
||||||
|
"options": {},
|
||||||
|
"arguments": {
|
||||||
|
"output": "OP_1 OP_2 OP_ADD"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dynamic": {
|
"dynamic": {
|
||||||
|
|
18
test/fixtures/p2ms.json
vendored
18
test/fixtures/p2ms.json
vendored
|
@ -5,6 +5,7 @@
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG"
|
"output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG"
|
||||||
},
|
},
|
||||||
|
"options": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"m": 2,
|
"m": 2,
|
||||||
"n": 2,
|
"n": 2,
|
||||||
|
@ -239,6 +240,7 @@
|
||||||
{
|
{
|
||||||
"description": "n !== output pubkeys",
|
"description": "n !== output pubkeys",
|
||||||
"exception": "Output is invalid",
|
"exception": "Output is invalid",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_2 OP_CHECKMULTISIG"
|
"output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_2 OP_CHECKMULTISIG"
|
||||||
}
|
}
|
||||||
|
@ -266,6 +268,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exception": "Pubkeys mismatch",
|
"exception": "Pubkeys mismatch",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"pubkeys": [
|
"pubkeys": [
|
||||||
"030000000000000000000000000000000000000000000000000000000000000001"
|
"030000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
@ -307,6 +310,20 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"exception": "Signature mismatch",
|
||||||
|
"arguments": {
|
||||||
|
"m": 1,
|
||||||
|
"pubkeys": [
|
||||||
|
"030000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"030000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
],
|
||||||
|
"signatures": [
|
||||||
|
"300602010002010001"
|
||||||
|
],
|
||||||
|
"input": "OP_0 300602010002010101"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"exception": "Too many signatures provided",
|
"exception": "Too many signatures provided",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
|
@ -325,6 +342,7 @@
|
||||||
{
|
{
|
||||||
"description": "Missing OP_0",
|
"description": "Missing OP_0",
|
||||||
"exception": "Input is invalid",
|
"exception": "Input is invalid",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"m": 2,
|
"m": 2,
|
||||||
"pubkeys": [
|
"pubkeys": [
|
||||||
|
|
19
test/fixtures/p2pk.json
vendored
19
test/fixtures/p2pk.json
vendored
|
@ -5,9 +5,10 @@
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG"
|
"output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG"
|
||||||
},
|
},
|
||||||
|
"options": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"signatures": null,
|
"signature": null,
|
||||||
"input": null,
|
"input": null,
|
||||||
"witness": null
|
"witness": null
|
||||||
}
|
}
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
},
|
},
|
||||||
"expected": {
|
"expected": {
|
||||||
"output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG",
|
"output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG",
|
||||||
"signatures": null,
|
"signature": null,
|
||||||
"input": null,
|
"input": null,
|
||||||
"witness": null
|
"witness": null
|
||||||
}
|
}
|
||||||
|
@ -97,6 +98,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exception": "Pubkey mismatch",
|
"exception": "Pubkey mismatch",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"output": "030000000000000000000000000000000000000000000000000000000000000002 OP_CHECKSIG"
|
"output": "030000000000000000000000000000000000000000000000000000000000000002 OP_CHECKSIG"
|
||||||
|
@ -116,6 +118,19 @@
|
||||||
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"input": "ffffffffffffffff"
|
"input": "ffffffffffffffff"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exception": "Input has invalid signature",
|
||||||
|
"arguments": {
|
||||||
|
"input": "30060201ff0201ff01"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exception": "Signature mismatch",
|
||||||
|
"arguments": {
|
||||||
|
"signature": "300602010002010001",
|
||||||
|
"input": "300602010302010301"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dynamic": {
|
"dynamic": {
|
||||||
|
|
9
test/fixtures/p2pkh.json
vendored
9
test/fixtures/p2pkh.json
vendored
|
@ -5,6 +5,7 @@
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh"
|
"address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh"
|
||||||
},
|
},
|
||||||
|
"options": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"hash": "168b992bcfc44050310b3a94bd0771136d0b28d1",
|
"hash": "168b992bcfc44050310b3a94bd0771136d0b28d1",
|
||||||
"output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG",
|
"output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
@ -103,6 +104,7 @@
|
||||||
{
|
{
|
||||||
"description": "Unexpected OP_DUP",
|
"description": "Unexpected OP_DUP",
|
||||||
"exception": "Output is invalid",
|
"exception": "Output is invalid",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"output": "OP_DUP OP_DUP 168b992bcfc44050310b3a94bd0771136d0b28d137 OP_EQUALVERIFY"
|
"output": "OP_DUP OP_DUP 168b992bcfc44050310b3a94bd0771136d0b28d137 OP_EQUALVERIFY"
|
||||||
}
|
}
|
||||||
|
@ -204,6 +206,13 @@
|
||||||
"hash": "ffffffffffffffffffffffffffffffffffffffff",
|
"hash": "ffffffffffffffffffffffffffffffffffffffff",
|
||||||
"input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001"
|
"input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exception": "Signature mismatch",
|
||||||
|
"arguments": {
|
||||||
|
"signature": "300602010002010001",
|
||||||
|
"input": "300602010302010301 030000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dynamic": {
|
"dynamic": {
|
||||||
|
|
38
test/fixtures/p2sh.json
vendored
38
test/fixtures/p2sh.json
vendored
|
@ -5,6 +5,7 @@
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT"
|
"address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT"
|
||||||
},
|
},
|
||||||
|
"options": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086",
|
"hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086",
|
||||||
"output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL",
|
"output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL",
|
||||||
|
@ -165,6 +166,24 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "p2sh-p2pkh, out (network derived from redeem)",
|
||||||
|
"arguments": {
|
||||||
|
"redeem": {
|
||||||
|
"address": "this is P2PKH context, unknown and ignored by P2SH",
|
||||||
|
"output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
"network": "testnet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expected": {
|
||||||
|
"address": "2N7nfc7zeWuADtpdR4MrR7Wq3dzr7LxTCgS",
|
||||||
|
"hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086",
|
||||||
|
"output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL",
|
||||||
|
"input": null,
|
||||||
|
"witness": null,
|
||||||
|
"network": "testnet"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"invalid": [
|
"invalid": [
|
||||||
|
@ -182,6 +201,7 @@
|
||||||
{
|
{
|
||||||
"description": "Expected OP_HASH160",
|
"description": "Expected OP_HASH160",
|
||||||
"exception": "Output is invalid",
|
"exception": "Output is invalid",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL"
|
"output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL"
|
||||||
}
|
}
|
||||||
|
@ -323,6 +343,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"exception": "Network mismatch",
|
||||||
|
"arguments": {
|
||||||
|
"network": "bitcoin",
|
||||||
|
"redeem": {
|
||||||
|
"network": "testnet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exception": "Network mismatch",
|
||||||
|
"arguments": {
|
||||||
|
"network": "testnet",
|
||||||
|
"redeem": {
|
||||||
|
"network": "bitcoin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"exception": "Empty input",
|
"exception": "Empty input",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
|
|
2
test/fixtures/p2wpkh.json
vendored
2
test/fixtures/p2wpkh.json
vendored
|
@ -5,6 +5,7 @@
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d"
|
"address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d"
|
||||||
},
|
},
|
||||||
|
"options": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727",
|
"hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727",
|
||||||
"output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727",
|
"output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727",
|
||||||
|
@ -108,6 +109,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exception": "Pubkey mismatch",
|
"exception": "Pubkey mismatch",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
"pubkey": "030000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"witness": [
|
"witness": [
|
||||||
|
|
43
test/fixtures/p2wsh.json
vendored
43
test/fixtures/p2wsh.json
vendored
|
@ -5,6 +5,7 @@
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q"
|
"address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q"
|
||||||
},
|
},
|
||||||
|
"options": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0",
|
"hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0",
|
||||||
"output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0",
|
"output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0",
|
||||||
|
@ -178,6 +179,24 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "p2wsh-p2pkh, out (network derived from redeem)",
|
||||||
|
"arguments": {
|
||||||
|
"redeem": {
|
||||||
|
"address": "this is P2PKH context, unknown and ignored by p2wsh",
|
||||||
|
"output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
"network": "testnet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expected": {
|
||||||
|
"address": "tb1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ssaskm8v",
|
||||||
|
"hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1",
|
||||||
|
"output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1",
|
||||||
|
"input": null,
|
||||||
|
"witness": null,
|
||||||
|
"network": "testnet"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"invalid": [
|
"invalid": [
|
||||||
|
@ -221,6 +240,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exception": "Output is invalid",
|
"exception": "Output is invalid",
|
||||||
|
"options": {},
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff OP_EQUAL"
|
"output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff OP_EQUAL"
|
||||||
}
|
}
|
||||||
|
@ -269,6 +289,20 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"exception": "Witness and redeem.witness mismatch",
|
||||||
|
"arguments": {
|
||||||
|
"redeem": {
|
||||||
|
"output": "OP_TRUE",
|
||||||
|
"witness": [
|
||||||
|
"04000000ff"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"witness": [
|
||||||
|
"04000000ee"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"exception": "Ambiguous witness source",
|
"exception": "Ambiguous witness source",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
|
@ -290,6 +324,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"exception": "Network mismatch",
|
||||||
|
"arguments": {
|
||||||
|
"network": "testnet",
|
||||||
|
"redeem": {
|
||||||
|
"network": "bitcoin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"exception": "Invalid prefix or Network mismatch",
|
"exception": "Invalid prefix or Network mismatch",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
|
|
112
test/fixtures/transaction_builder.json
vendored
112
test/fixtures/transaction_builder.json
vendored
|
@ -443,7 +443,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash: SINGLE (random)",
|
"description": "SIGHASH SINGLE (random)",
|
||||||
"txHex": "01000000012ffb29d53528ad30c37c267fbbeda3c6fce08f5f6f5d3b1eab22193599a3612a010000006b483045022100f963f1d9564075a934d7c3cfa333bd1378859b84cba947e149926fc9ec89b5ae02202b5b912e507bae65002aff972f9752e2aeb2e22c5fdbaaad672090378184df37032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff0260a62f01000000001976a9140de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b888ac80969800000000001976a91454d0e925d5ee0ee26768a237067dee793d01a70688ac00000000",
|
"txHex": "01000000012ffb29d53528ad30c37c267fbbeda3c6fce08f5f6f5d3b1eab22193599a3612a010000006b483045022100f963f1d9564075a934d7c3cfa333bd1378859b84cba947e149926fc9ec89b5ae02202b5b912e507bae65002aff972f9752e2aeb2e22c5fdbaaad672090378184df37032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff0260a62f01000000001976a9140de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b888ac80969800000000001976a91454d0e925d5ee0ee26768a237067dee793d01a70688ac00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -473,7 +473,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash: ALL",
|
"description": "SIGHASH ALL",
|
||||||
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006a47304402206abb0622b8b6ca83f1f4de84830cf38bf4615dc9e47a7dcdcc489905f26aa9cb02201d2d8a7815242b88e4cd66390ca46da802238f9b1395e0d118213d30dad38184012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006b483045022100de13b42804f87a09bb46def12ab4608108d8c2db41db4bc09064f9c46fcf493102205e5c759ab7b2895c9b0447e56029f6895ff7bb20e0847c564a88a3cfcf080c4f012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b4830450221009100a3f5b30182d1cb0172792af6947b6d8d42badb0539f2c209aece5a0628f002200ae91702ca63347e344c85fcb536f30ee97b75cdf4900de534ed5e040e71a548012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006a47304402206abb0622b8b6ca83f1f4de84830cf38bf4615dc9e47a7dcdcc489905f26aa9cb02201d2d8a7815242b88e4cd66390ca46da802238f9b1395e0d118213d30dad38184012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006b483045022100de13b42804f87a09bb46def12ab4608108d8c2db41db4bc09064f9c46fcf493102205e5c759ab7b2895c9b0447e56029f6895ff7bb20e0847c564a88a3cfcf080c4f012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b4830450221009100a3f5b30182d1cb0172792af6947b6d8d42badb0539f2c209aece5a0628f002200ae91702ca63347e344c85fcb536f30ee97b75cdf4900de534ed5e040e71a548012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -533,7 +533,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash: ALL | ANYONECANPAY",
|
"description": "SIGHASH ALL | ANYONECANPAY",
|
||||||
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100bd2829550e9b3a081747281029b5f5a96bbd83bb6a92fa2f8310f1bd0d53abc90220071b469417c55cdb3b04171fd7900d2768981b7ab011553d84d24ea85d277079812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402206295e17c45c6356ffb20365b696bcbb869db7e8697f4b8a684098ee2bff85feb02202905c441abe39ec9c480749236b84fdd3ebd91ecd25b559136370aacfcf2815c812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b483045022100f58e7c98ac8412944d575bcdece0e5966d4018f05988b5b60b6f46b8cb7a543102201c5854d3361e29b58123f34218cec2c722f5ec7a08235ebd007ec637b07c193a812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100bd2829550e9b3a081747281029b5f5a96bbd83bb6a92fa2f8310f1bd0d53abc90220071b469417c55cdb3b04171fd7900d2768981b7ab011553d84d24ea85d277079812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402206295e17c45c6356ffb20365b696bcbb869db7e8697f4b8a684098ee2bff85feb02202905c441abe39ec9c480749236b84fdd3ebd91ecd25b559136370aacfcf2815c812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b483045022100f58e7c98ac8412944d575bcdece0e5966d4018f05988b5b60b6f46b8cb7a543102201c5854d3361e29b58123f34218cec2c722f5ec7a08235ebd007ec637b07c193a812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -593,7 +593,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash: SINGLE",
|
"description": "SIGHASH SINGLE",
|
||||||
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100e822f152bb15a1d623b91913cd0fb915e9f85a8dc6c26d51948208bbc0218e800220255f78549d9614c88eac9551429bc00224f22cdcb41a3af70d52138f7e98d333032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402206f37f79adeb86e0e2da679f79ff5c3ba206c6d35cd9a21433f0de34ee83ddbc00220118cabbac5d83b3aa4c2dc01b061e4b2fe83750d85a72ae6a1752300ee5d9aff032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006a473044022042ac843d220a56b3de05f24c85a63e71efa7e5fc7c2ec766a2ffae82a88572b0022051a816b317313ea8d90010a77c3e02d41da4a500e67e6a5347674f836f528d82032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100e822f152bb15a1d623b91913cd0fb915e9f85a8dc6c26d51948208bbc0218e800220255f78549d9614c88eac9551429bc00224f22cdcb41a3af70d52138f7e98d333032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402206f37f79adeb86e0e2da679f79ff5c3ba206c6d35cd9a21433f0de34ee83ddbc00220118cabbac5d83b3aa4c2dc01b061e4b2fe83750d85a72ae6a1752300ee5d9aff032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006a473044022042ac843d220a56b3de05f24c85a63e71efa7e5fc7c2ec766a2ffae82a88572b0022051a816b317313ea8d90010a77c3e02d41da4a500e67e6a5347674f836f528d82032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -653,7 +653,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash: SINGLE|ANYONECANPAY",
|
"description": "SIGHASH SINGLE|ANYONECANPAY",
|
||||||
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100d05a3b6cf2f0301000b0e45c09054f2c61570ce8798ebf571eef72da3b1c94a1022016d7ef3c133fa703bae2c75158ea08d335ac698506f99b3c369c37a9e8fc4beb832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006b483045022100ee6bf07b051001dcbfa062692a40adddd070303286b714825b3fb4693dd8fcdb022056610885e5053e5d47f2be3433051305abe7978ead8f7cf2d0368947aff6b307832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b483045022100cfc930d5b5272d0220d9da98fabec97b9e66306f735efa837f43f6adc675cad902202f9dff76b8b9ec8f613d46094f17f64d875804292d8804aa59fd295b6fc1416b832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100d05a3b6cf2f0301000b0e45c09054f2c61570ce8798ebf571eef72da3b1c94a1022016d7ef3c133fa703bae2c75158ea08d335ac698506f99b3c369c37a9e8fc4beb832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006b483045022100ee6bf07b051001dcbfa062692a40adddd070303286b714825b3fb4693dd8fcdb022056610885e5053e5d47f2be3433051305abe7978ead8f7cf2d0368947aff6b307832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b483045022100cfc930d5b5272d0220d9da98fabec97b9e66306f735efa837f43f6adc675cad902202f9dff76b8b9ec8f613d46094f17f64d875804292d8804aa59fd295b6fc1416b832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -713,7 +713,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash: NONE",
|
"description": "SIGHASH NONE",
|
||||||
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100e7f0a1ddd2c0b81e093e029b8a503afa27fe43549b0668d2141abf35eb3a63be022037f12d12cd50fc94a135f933406a8937557de9b9566a8841ff1548c1b6984531022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a473044022008451123ec2535dab545ade9d697519e63b28df5e311ea05e0ce28d39877a7c8022061ce5dbfb7ab478dd9e05b0acfd959ac3eb2641f61958f5d352f37621073d7c0022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006a47304402205c001bcdfb35c70d8aa3bdbc75399afb72eb7cf1926ca7c1dfcddcb4d4d3e0f8022028992fffdcd4e9f34ab726f97c24157917641c2ef99361f588e3d4147d46eea5022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100e7f0a1ddd2c0b81e093e029b8a503afa27fe43549b0668d2141abf35eb3a63be022037f12d12cd50fc94a135f933406a8937557de9b9566a8841ff1548c1b6984531022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a473044022008451123ec2535dab545ade9d697519e63b28df5e311ea05e0ce28d39877a7c8022061ce5dbfb7ab478dd9e05b0acfd959ac3eb2641f61958f5d352f37621073d7c0022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006a47304402205c001bcdfb35c70d8aa3bdbc75399afb72eb7cf1926ca7c1dfcddcb4d4d3e0f8022028992fffdcd4e9f34ab726f97c24157917641c2ef99361f588e3d4147d46eea5022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -773,7 +773,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash: NONE | ANYONECANPAY",
|
"description": "SIGHASH NONE | ANYONECANPAY",
|
||||||
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006a47304402204ed272952177aaa5a1b171c2ca5a7a3d300ffcd7e04b040c0baaa4e3561862a502207e65a5b8f99c8a632b186c8a60496a12bf3116f51909b7497413aefdc3be7bf6822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402203ec365300cc67602f4cc5be027959d3667b48db34c6c87d267c94a7e210d5c1f02204843350311c0a9711cad1960b17ce9e323a1ce6f37deefc3ffe63082d480be92822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b48304502210084f86f905c36372eff9c54ccd509a519a3325bcace8abfeed7ed3f0d579979e902201ff330dd2402e5ca9989a8a294fa36d6cf3a093edb18d29c9d9644186a3efeb4822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
"txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006a47304402204ed272952177aaa5a1b171c2ca5a7a3d300ffcd7e04b040c0baaa4e3561862a502207e65a5b8f99c8a632b186c8a60496a12bf3116f51909b7497413aefdc3be7bf6822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402203ec365300cc67602f4cc5be027959d3667b48db34c6c87d267c94a7e210d5c1f02204843350311c0a9711cad1960b17ce9e323a1ce6f37deefc3ffe63082d480be92822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b48304502210084f86f905c36372eff9c54ccd509a519a3325bcace8abfeed7ed3f0d579979e902201ff330dd2402e5ca9989a8a294fa36d6cf3a093edb18d29c9d9644186a3efeb4822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -1491,7 +1491,7 @@
|
||||||
],
|
],
|
||||||
"fromTransaction": [
|
"fromTransaction": [
|
||||||
{
|
{
|
||||||
"description": "Transaction w/ P2SH(P2MS 2/2) -> OP_RETURN | 1 OP_0, no signatures",
|
"description": "Transaction w/ P2SH(P2MS 2/2) -> OP_RETURN | 1 OP_0 fixes to 2 OP_0, no signatures",
|
||||||
"network": "testnet",
|
"network": "testnet",
|
||||||
"incomplete": true,
|
"incomplete": true,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -1547,6 +1547,28 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"fromTransactionSequential": [
|
||||||
|
{
|
||||||
|
"description": "Transaction w/ P2SH(P2MS 2/3) -> ?",
|
||||||
|
"network": "testnet",
|
||||||
|
"txHex": "0100000001b033b2214568b49fda417371aba0634b0303a2b6a19884c25d03d0b91bdbe231000000006f000000004c6952210258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a9121038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f2103b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d953aeffffffff0200e1f5050000000017a914a9974100aeee974a20cda9a2f545704a0ab54fdc87c72831010000000017a9149f57a6712ef023f85ffac631ed4263b977b2d0678700000000",
|
||||||
|
"txHexAfter": "0100000001b033b2214568b49fda417371aba0634b0303a2b6a19884c25d03d0b91bdbe23100000000b60000004730440220793d87f2a8afeb856816efa38984418c692c15170e99ca371f547454079c0dd3022074ae95e438fee1f37619fabe0ce1083c3be0d65c3defb5337833d50fdc694b13014c6952210258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a9121038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f2103b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d953aeffffffff0200e1f5050000000017a914a9974100aeee974a20cda9a2f545704a0ab54fdc87c72831010000000017a9149f57a6712ef023f85ffac631ed4263b977b2d0678700000000",
|
||||||
|
"incomplete": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"vout": 0,
|
||||||
|
"scriptSig": "OP_0 OP_0 OP_0 OP_0 52210258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a9121038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f2103b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d953ae",
|
||||||
|
"scriptSigAfter": "OP_0 OP_0 OP_0 30440220793d87f2a8afeb856816efa38984418c692c15170e99ca371f547454079c0dd3022074ae95e438fee1f37619fabe0ce1083c3be0d65c3defb5337833d50fdc694b1301 52210258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a9121038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f2103b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d953ae",
|
||||||
|
"signs": [
|
||||||
|
{
|
||||||
|
"keyPair": "cTkcnMZoFYH1UgumzCFHv2veLMNN1PaJyHHUxFT127zhNGBqqEZ2",
|
||||||
|
"redeemScript": "OP_2 0258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a91 038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f 03b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d9 OP_3 OP_CHECKMULTISIG"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"classification": {
|
"classification": {
|
||||||
"hex": "01000000059c06fb641a8cd69be81ca91e68d8a115cb698396876ecd77120ec1e4ab9002279f000000b500483045022100d58f828ab39cfac592f89fe372fb520992975218698c683a893f29e39cf0080302207cc0485dab5ce621089bdd15e1f15db0ecbde8dd4bb661bcf0e3af6ecab075e6014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff0821dc00213d2b7993f8f2a1553800c6f2f31106da176505d0ade467b68401d795000000b400473044022028e937a7bba888fe3428f442f6e22d92ce2ddba01548c38780d40890fa6cc305022043204d0bcfb1150b045d54cf9b13462e44e2ef47fee03d3cea08e84a8060fc30014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffffaa997ac385dc666af1f5947ef615431024eb314cac2308d5e1b903e28ca466f499000000b50048304502210093efc26facedc5f51e304aa270a7b4f1a911b2d912c3674e5c6e2ad4ac7a410402201cf0b62c240461902f9f16d8a0bc3a210b7bfcd2c06523dd4b4b63be22e85252014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffffd9f61bf98a021ee144f33ba5a6b04274de8fcb5c05f1ff7c12367fb7a608b2dd9e000000b4004730440220456e1201c1fa727288cba7fa0054dc02d8dd6c7418cae1e97006ef0652891c9202201192d0fbf3a9c00afb99a415f2bf515509e1150805acd8de95c496c27cb6570f014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff1f8119e3bc7c2f451feaa79f42ec5a63502afb425c253c935e43d217d5c29bdea1000000b500483045022100f669004f770490093eba4ac4903cb7581f7d18ea9245c538585ef5367e520e4702205485fafe0be178563a599d41e0cc172bb01314ed65d0e48df19a5258f17bdbc4014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff0380f0fa02000000001976a91439692085cf9d27e8c1cf63e76bd32d9bd15cab8b88ac50c300000000000017a9147204bb26950ce1595255897f63d205779f033f3e875b5409000000000017a9142538893d984a4b5695e4bfde1a90a9f02fabf8e38700000000"
|
"hex": "01000000059c06fb641a8cd69be81ca91e68d8a115cb698396876ecd77120ec1e4ab9002279f000000b500483045022100d58f828ab39cfac592f89fe372fb520992975218698c683a893f29e39cf0080302207cc0485dab5ce621089bdd15e1f15db0ecbde8dd4bb661bcf0e3af6ecab075e6014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff0821dc00213d2b7993f8f2a1553800c6f2f31106da176505d0ade467b68401d795000000b400473044022028e937a7bba888fe3428f442f6e22d92ce2ddba01548c38780d40890fa6cc305022043204d0bcfb1150b045d54cf9b13462e44e2ef47fee03d3cea08e84a8060fc30014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffffaa997ac385dc666af1f5947ef615431024eb314cac2308d5e1b903e28ca466f499000000b50048304502210093efc26facedc5f51e304aa270a7b4f1a911b2d912c3674e5c6e2ad4ac7a410402201cf0b62c240461902f9f16d8a0bc3a210b7bfcd2c06523dd4b4b63be22e85252014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffffd9f61bf98a021ee144f33ba5a6b04274de8fcb5c05f1ff7c12367fb7a608b2dd9e000000b4004730440220456e1201c1fa727288cba7fa0054dc02d8dd6c7418cae1e97006ef0652891c9202201192d0fbf3a9c00afb99a415f2bf515509e1150805acd8de95c496c27cb6570f014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff1f8119e3bc7c2f451feaa79f42ec5a63502afb425c253c935e43d217d5c29bdea1000000b500483045022100f669004f770490093eba4ac4903cb7581f7d18ea9245c538585ef5367e520e4702205485fafe0be178563a599d41e0cc172bb01314ed65d0e48df19a5258f17bdbc4014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff0380f0fa02000000001976a91439692085cf9d27e8c1cf63e76bd32d9bd15cab8b88ac50c300000000000017a9147204bb26950ce1595255897f63d205779f033f3e875b5409000000000017a9142538893d984a4b5695e4bfde1a90a9f02fabf8e38700000000"
|
||||||
},
|
},
|
||||||
|
@ -1785,11 +1807,10 @@
|
||||||
"scriptSig": "OP_0 OP_0 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae"
|
"scriptSig": "OP_0 OP_0 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filterOP_0": true,
|
|
||||||
"pubKeyIndex": 0,
|
"pubKeyIndex": 0,
|
||||||
"keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx",
|
"keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx",
|
||||||
"scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae",
|
"scriptSigBefore": "OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae",
|
||||||
"scriptSigFiltered": "OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae"
|
"scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1896,6 +1917,29 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Incomplete Transaction P2SH(P2MS 2/3), missing signature",
|
||||||
|
"exception": "Not enough signatures provided",
|
||||||
|
"network": "testnet",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"vout": 0,
|
||||||
|
"signs": [
|
||||||
|
{
|
||||||
|
"keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx",
|
||||||
|
"redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
"value": 1000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Duplicate transaction outs",
|
"description": "Duplicate transaction outs",
|
||||||
"exception": "Duplicate TxOut: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff:0",
|
"exception": "Duplicate TxOut: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff:0",
|
||||||
|
@ -1932,7 +1976,7 @@
|
||||||
"sign": [
|
"sign": [
|
||||||
{
|
{
|
||||||
"description": "Transaction w/ witness value mismatch",
|
"description": "Transaction w/ witness value mismatch",
|
||||||
"exception": "Input didn\\'t match witnessValue",
|
"exception": "Input did not match witnessValue",
|
||||||
"network": "testnet",
|
"network": "testnet",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
|
@ -2336,6 +2380,50 @@
|
||||||
"value": 1000
|
"value": 1000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Transaction w/ no outputs (but 1 SIGHASH_NONE)",
|
||||||
|
"exception": "Transaction needs outputs",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"vout": 0,
|
||||||
|
"signs": [
|
||||||
|
{
|
||||||
|
"keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn",
|
||||||
|
"hashType": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"vout": 1,
|
||||||
|
"signs": [
|
||||||
|
{
|
||||||
|
"keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn",
|
||||||
|
"throws": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Transaction w/ no outputs",
|
||||||
|
"exception": "Transaction needs outputs",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"vout": 0,
|
||||||
|
"signs": [
|
||||||
|
{
|
||||||
|
"keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn",
|
||||||
|
"throws": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"fromTransaction": [
|
"fromTransaction": [
|
||||||
|
|
|
@ -1,99 +1,129 @@
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bitcoin = require('../../')
|
const bitcoin = require('../../')
|
||||||
const dhttp = require('dhttp/200')
|
const dhttpCallback = require('dhttp/200')
|
||||||
|
// use Promises
|
||||||
|
const dhttp = options => new Promise((resolve, reject) => {
|
||||||
|
return dhttpCallback(options, (err, data) => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
else return resolve(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const APIPASS = process.env.APIPASS || 'satoshi'
|
const APIPASS = process.env.APIPASS || 'satoshi'
|
||||||
const APIURL = 'https://api.dcousens.cloud/1'
|
const APIURL = process.env.APIURL || 'https://regtest.bitbank.cc/1'
|
||||||
const NETWORK = bitcoin.networks.testnet
|
const NETWORK = bitcoin.networks.testnet
|
||||||
|
|
||||||
function broadcast (txHex, callback) {
|
function broadcast (txHex) {
|
||||||
dhttp({
|
return dhttp({
|
||||||
method: 'PUT',
|
method: 'POST',
|
||||||
url: APIURL + '/t/push',
|
url: APIURL + '/t/push',
|
||||||
body: txHex
|
body: txHex
|
||||||
}, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mine (count, callback) {
|
|
||||||
dhttp({
|
|
||||||
method: 'POST',
|
|
||||||
url: APIURL + '/r/generate?count=' + count + '&key=' + APIPASS
|
|
||||||
}, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
function height (callback) {
|
|
||||||
dhttp({
|
|
||||||
method: 'GET',
|
|
||||||
url: APIURL + '/b/best/height'
|
|
||||||
}, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
function faucet (address, value, callback) {
|
|
||||||
dhttp({
|
|
||||||
method: 'POST',
|
|
||||||
url: APIURL + '/r/faucet?address=' + address + '&value=' + value + '&key=' + APIPASS
|
|
||||||
}, function (err, txId) {
|
|
||||||
if (err) return callback(err)
|
|
||||||
|
|
||||||
unspents(address, function (err, results) {
|
|
||||||
if (err) return callback(err)
|
|
||||||
|
|
||||||
const unspents = results.filter(x => x.txId === txId)
|
|
||||||
if (unspents.length === 0) return callback(new Error('Missing unspent'))
|
|
||||||
|
|
||||||
callback(null, unspents.pop())
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function faucetComplex (output, value, callback) {
|
function mine (count) {
|
||||||
|
return dhttp({
|
||||||
|
method: 'POST',
|
||||||
|
url: APIURL + '/r/generate?count=' + count + '&key=' + APIPASS
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function height () {
|
||||||
|
return dhttp({
|
||||||
|
method: 'GET',
|
||||||
|
url: APIURL + '/b/best/height'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function _faucetRequest (address, value) {
|
||||||
|
return dhttp({
|
||||||
|
method: 'POST',
|
||||||
|
url: APIURL + '/r/faucet?address=' + address + '&value=' + value + '&key=' + APIPASS
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function faucet (address, value) {
|
||||||
|
let count = 0
|
||||||
|
let _unspents = []
|
||||||
|
const sleep = ms => new Promise((resolve, reject) => setTimeout(resolve, ms))
|
||||||
|
const randInt = (min, max) => min + Math.floor((max - min + 1) * Math.random())
|
||||||
|
while (_unspents.length === 0) {
|
||||||
|
if (count > 0) {
|
||||||
|
if (count >= 5) throw new Error('Missing Inputs')
|
||||||
|
console.log('Missing Inputs, retry #' + count)
|
||||||
|
await sleep(randInt(150, 250))
|
||||||
|
}
|
||||||
|
|
||||||
|
const txId = await _faucetRequest(address, value)
|
||||||
|
.then(
|
||||||
|
v => v, // Pass success value as is
|
||||||
|
async err => {
|
||||||
|
// Bad Request error is fixed by making sure height is >= 432
|
||||||
|
const currentHeight = await height()
|
||||||
|
if (err.message === 'Bad Request' && currentHeight < 432) {
|
||||||
|
await mine(432 - currentHeight)
|
||||||
|
return _faucetRequest(address, value)
|
||||||
|
} else if (err.message === 'Bad Request' && currentHeight >= 432) {
|
||||||
|
return _faucetRequest(address, value)
|
||||||
|
} else {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await sleep(randInt(10, 40))
|
||||||
|
|
||||||
|
const results = await unspents(address)
|
||||||
|
|
||||||
|
_unspents = results.filter(x => x.txId === txId)
|
||||||
|
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return _unspents.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function faucetComplex (output, value) {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: NETWORK })
|
const keyPair = bitcoin.ECPair.makeRandom({ network: NETWORK })
|
||||||
const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: NETWORK })
|
const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: NETWORK })
|
||||||
|
|
||||||
faucet(p2pkh.address, value * 2, (err, unspent) => {
|
const unspent = await faucet(p2pkh.address, value * 2)
|
||||||
if (err) return callback(err)
|
|
||||||
|
|
||||||
const txvb = new bitcoin.TransactionBuilder(NETWORK)
|
const txvb = new bitcoin.TransactionBuilder(NETWORK)
|
||||||
txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output)
|
txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output)
|
||||||
txvb.addOutput(output, value)
|
txvb.addOutput(output, value)
|
||||||
txvb.sign(0, keyPair)
|
txvb.sign(0, keyPair)
|
||||||
const txv = txvb.build()
|
const txv = txvb.build()
|
||||||
|
|
||||||
broadcast(txv.toHex(), function (err) {
|
await broadcast(txv.toHex())
|
||||||
if (err) return callback(err)
|
|
||||||
|
|
||||||
return callback(null, {
|
return {
|
||||||
txId: txv.getId(),
|
txId: txv.getId(),
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value
|
value
|
||||||
})
|
}
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetch (txId, callback) {
|
function fetch (txId) {
|
||||||
dhttp({
|
return dhttp({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: APIURL + '/t/' + txId + '/json'
|
url: APIURL + '/t/' + txId + '/json'
|
||||||
}, callback)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function unspents (address, callback) {
|
function unspents (address) {
|
||||||
dhttp({
|
return dhttp({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: APIURL + '/a/' + address + '/unspents'
|
url: APIURL + '/a/' + address + '/unspents'
|
||||||
}, callback)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function verify (txo, callback) {
|
async function verify (txo) {
|
||||||
fetch(txo.txId, function (err, tx) {
|
const tx = await fetch(txo.txId)
|
||||||
if (err) return callback(err)
|
|
||||||
|
|
||||||
const txoActual = tx.outs[txo.vout]
|
const txoActual = tx.outs[txo.vout]
|
||||||
if (txo.address) assert.strictEqual(txoActual.address, txo.address)
|
if (txo.address) assert.strictEqual(txoActual.address, txo.address)
|
||||||
if (txo.value) assert.strictEqual(txoActual.value, txo.value)
|
if (txo.value) assert.strictEqual(txoActual.value, txo.value)
|
||||||
callback()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAddress (node, network) {
|
function getAddress (node, network) {
|
||||||
|
@ -108,6 +138,7 @@ function randomAddress () {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
broadcast,
|
broadcast,
|
||||||
|
dhttp,
|
||||||
faucet,
|
faucet,
|
||||||
faucetComplex,
|
faucetComplex,
|
||||||
fetch,
|
fetch,
|
||||||
|
|
|
@ -1,50 +1,36 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bitcoin = require('../../')
|
const bitcoin = require('../../')
|
||||||
const dhttp = require('dhttp/200')
|
const dhttp = require('./_regtest').dhttp
|
||||||
|
const TESTNET = bitcoin.networks.testnet
|
||||||
|
|
||||||
const LITECOIN = {
|
describe('bitcoinjs-lib (addresses)', () => {
|
||||||
messagePrefix: '\x19Litecoin Signed Message:\n',
|
it('can generate a random address [and support the retrieval of transactions for that address (via 3PBP)', async () => {
|
||||||
bip32: {
|
const keyPair = bitcoin.ECPair.makeRandom()
|
||||||
public: 0x019da462,
|
|
||||||
private: 0x019d9cfe
|
|
||||||
},
|
|
||||||
pubKeyHash: 0x30,
|
|
||||||
scriptHash: 0x32,
|
|
||||||
wif: 0xb0
|
|
||||||
}
|
|
||||||
|
|
||||||
// deterministic RNG for testing only
|
|
||||||
function rng () { return Buffer.from('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz') }
|
|
||||||
|
|
||||||
describe('bitcoinjs-lib (addresses)', function () {
|
|
||||||
it('can generate a random address', function () {
|
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ rng: rng })
|
|
||||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
||||||
|
|
||||||
assert.strictEqual(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64')
|
// bitcoin P2PKH addresses start with a '1'
|
||||||
|
assert.strictEqual(address.startsWith('1'), true)
|
||||||
|
|
||||||
|
const result = await dhttp({
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://blockchain.info/rawaddr/' + address
|
||||||
|
})
|
||||||
|
|
||||||
|
// random private keys [probably!] have no transactions
|
||||||
|
assert.strictEqual(result.n_tx, 0)
|
||||||
|
assert.strictEqual(result.total_received, 0)
|
||||||
|
assert.strictEqual(result.total_sent, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate an address from a SHA256 hash', function () {
|
it('can import an address via WIF', () => {
|
||||||
const hash = bitcoin.crypto.sha256(Buffer.from('correct horse battery staple'))
|
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
|
||||||
|
|
||||||
const keyPair = bitcoin.ECPair.fromPrivateKey(hash)
|
|
||||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
||||||
|
|
||||||
// Generating addresses from SHA256 hashes is not secure if the input to the hash function is predictable
|
assert.strictEqual(address, '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH')
|
||||||
// Do not use with predictable inputs
|
|
||||||
assert.strictEqual(address, '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can import an address via WIF', function () {
|
it('can generate a P2SH, pay-to-multisig (2-of-3) address', () => {
|
||||||
const keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
|
|
||||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
|
||||||
|
|
||||||
assert.strictEqual(address, '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can generate a P2SH, pay-to-multisig (2-of-3) address', function () {
|
|
||||||
const pubkeys = [
|
const pubkeys = [
|
||||||
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
||||||
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
|
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
|
||||||
|
@ -57,23 +43,23 @@ describe('bitcoinjs-lib (addresses)', function () {
|
||||||
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
|
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate a SegWit address', function () {
|
it('can generate a SegWit address', () => {
|
||||||
const keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
|
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
|
||||||
const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
|
const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
|
||||||
|
|
||||||
assert.strictEqual(address, 'bc1qt97wqg464zrhnx23upykca5annqvwkwujjglky')
|
assert.strictEqual(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate a SegWit address (via P2SH)', function () {
|
it('can generate a SegWit address (via P2SH)', () => {
|
||||||
const keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
|
const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn')
|
||||||
const { address } = bitcoin.payments.p2sh({
|
const { address } = bitcoin.payments.p2sh({
|
||||||
redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
|
redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey })
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.strictEqual(address, '34AgLJhwXrvmkZS1o5TrcdeevMt22Nar53')
|
assert.strictEqual(address, '3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate a P2WSH (SegWit), pay-to-multisig (3-of-4) address', function () {
|
it('can generate a P2WSH (SegWit), pay-to-multisig (3-of-4) address', () => {
|
||||||
const pubkeys = [
|
const pubkeys = [
|
||||||
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
||||||
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
|
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
|
||||||
|
@ -87,7 +73,7 @@ describe('bitcoinjs-lib (addresses)', function () {
|
||||||
assert.strictEqual(address, 'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul')
|
assert.strictEqual(address, 'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate a P2SH(P2WSH(...)), pay-to-multisig (2-of-2) address', function () {
|
it('can generate a P2SH(P2WSH(...)), pay-to-multisig (2-of-2) address', () => {
|
||||||
const pubkeys = [
|
const pubkeys = [
|
||||||
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
|
||||||
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9'
|
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9'
|
||||||
|
@ -101,41 +87,31 @@ describe('bitcoinjs-lib (addresses)', function () {
|
||||||
assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN')
|
assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can support the retrieval of transactions for an address (via 3PBP)', function (done) {
|
// examples using other network information
|
||||||
const keyPair = bitcoin.ECPair.makeRandom()
|
it('can generate a Testnet address', () => {
|
||||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey })
|
const keyPair = bitcoin.ECPair.makeRandom({ network: TESTNET })
|
||||||
|
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: TESTNET })
|
||||||
|
|
||||||
dhttp({
|
// bitcoin testnet P2PKH addresses start with a 'm' or 'n'
|
||||||
method: 'GET',
|
assert.strictEqual(address.startsWith('m') || address.startsWith('n'), true)
|
||||||
url: 'https://blockchain.info/rawaddr/' + address
|
|
||||||
}, function (err, result) {
|
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
// random private keys [probably!] have no transactions
|
|
||||||
assert.strictEqual(result.n_tx, 0)
|
|
||||||
assert.strictEqual(result.total_received, 0)
|
|
||||||
assert.strictEqual(result.total_sent, 0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// other networks
|
it('can generate a Litecoin address', () => {
|
||||||
it('can generate a Testnet address', function () {
|
// WARNING: although possible, bitcoinjs is NOT necessarily compatible with Litecoin
|
||||||
const testnet = bitcoin.networks.testnet
|
const LITECOIN = {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: testnet, rng: rng })
|
messagePrefix: '\x19Litecoin Signed Message:\n',
|
||||||
const wif = keyPair.toWIF()
|
bip32: {
|
||||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: testnet })
|
public: 0x019da462,
|
||||||
|
private: 0x019d9cfe
|
||||||
|
},
|
||||||
|
pubKeyHash: 0x30,
|
||||||
|
scriptHash: 0x32,
|
||||||
|
wif: 0xb0
|
||||||
|
}
|
||||||
|
|
||||||
assert.strictEqual(address, 'mubSzQNtZfDj1YdNP6pNDuZy6zs6GDn61L')
|
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN })
|
||||||
assert.strictEqual(wif, 'cRgnQe9MUu1JznntrLaoQpB476M8PURvXVQB5R2eqms5tXnzNsrr')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can generate a Litecoin address', function () {
|
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN, rng: rng })
|
|
||||||
const wif = keyPair.toWIF()
|
|
||||||
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: LITECOIN })
|
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: LITECOIN })
|
||||||
|
|
||||||
assert.strictEqual(address, 'LZJSxZbjqJ2XVEquqfqHg1RQTDdfST5PTn')
|
assert.strictEqual(address.startsWith('L'), true)
|
||||||
assert.strictEqual(wif, 'T7A4PUSgTDHecBxW1ZiYFrDNRih2o7M8Gf9xpoCgudPF9gDiNvuS')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bip32 = require('bip32')
|
const bip32 = require('bip32')
|
||||||
const bip39 = require('bip39')
|
const bip39 = require('bip39')
|
||||||
|
@ -9,35 +8,35 @@ function getAddress (node, network) {
|
||||||
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address
|
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('bitcoinjs-lib (BIP32)', function () {
|
describe('bitcoinjs-lib (BIP32)', () => {
|
||||||
it('can import a BIP32 testnet xpriv and export to WIF', function () {
|
it('can import a BIP32 testnet xpriv and export to WIF', () => {
|
||||||
const xpriv = 'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK'
|
const xpriv = 'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK'
|
||||||
const node = bip32.fromBase58(xpriv, bitcoin.networks.testnet)
|
const node = bip32.fromBase58(xpriv, bitcoin.networks.testnet)
|
||||||
|
|
||||||
assert.equal(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7')
|
assert.strictEqual(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can export a BIP32 xpriv, then import it', function () {
|
it('can export a BIP32 xpriv, then import it', () => {
|
||||||
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||||
const node = bip32.fromSeed(seed)
|
const node = bip32.fromSeed(seed)
|
||||||
const string = node.toBase58()
|
const string = node.toBase58()
|
||||||
const restored = bip32.fromBase58(string)
|
const restored = bip32.fromBase58(string)
|
||||||
|
|
||||||
assert.equal(getAddress(node), getAddress(restored)) // same public key
|
assert.strictEqual(getAddress(node), getAddress(restored)) // same public key
|
||||||
assert.equal(node.toWIF(), restored.toWIF()) // same private key
|
assert.strictEqual(node.toWIF(), restored.toWIF()) // same private key
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can export a BIP32 xpub', function () {
|
it('can export a BIP32 xpub', () => {
|
||||||
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||||
const node = bip32.fromSeed(seed)
|
const node = bip32.fromSeed(seed)
|
||||||
const string = node.neutered().toBase58()
|
const string = node.neutered().toBase58()
|
||||||
|
|
||||||
assert.equal(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n')
|
assert.strictEqual(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create a BIP32, bitcoin, account 0, external address', function () {
|
it('can create a BIP32, bitcoin, account 0, external address', () => {
|
||||||
const path = "m/0'/0/0"
|
const path = "m/0'/0/0"
|
||||||
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
|
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
|
||||||
|
|
||||||
|
@ -48,11 +47,11 @@ describe('bitcoinjs-lib (BIP32)', function () {
|
||||||
.derive(0)
|
.derive(0)
|
||||||
.derive(0)
|
.derive(0)
|
||||||
|
|
||||||
assert.equal(getAddress(child1), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
|
assert.strictEqual(getAddress(child1), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
|
||||||
assert.equal(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
|
assert.strictEqual(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create a BIP44, bitcoin, account 0, external address', function () {
|
it('can create a BIP44, bitcoin, account 0, external address', () => {
|
||||||
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
|
const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex'))
|
||||||
|
|
||||||
const child1 = root.derivePath("m/44'/0'/0'/0/0")
|
const child1 = root.derivePath("m/44'/0'/0'/0/0")
|
||||||
|
@ -64,11 +63,11 @@ describe('bitcoinjs-lib (BIP32)', function () {
|
||||||
.derive(0)
|
.derive(0)
|
||||||
.derive(0)
|
.derive(0)
|
||||||
|
|
||||||
assert.equal(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
|
assert.strictEqual(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
|
||||||
assert.equal(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
|
assert.strictEqual(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create a BIP49, bitcoin testnet, account 0, external address', function () {
|
it('can create a BIP49, bitcoin testnet, account 0, external address', () => {
|
||||||
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
|
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
|
||||||
const seed = bip39.mnemonicToSeed(mnemonic)
|
const seed = bip39.mnemonicToSeed(mnemonic)
|
||||||
const root = bip32.fromSeed(seed)
|
const root = bip32.fromSeed(seed)
|
||||||
|
@ -80,10 +79,10 @@ describe('bitcoinjs-lib (BIP32)', function () {
|
||||||
redeem: bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }),
|
redeem: bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }),
|
||||||
network: bitcoin.networks.testnet
|
network: bitcoin.networks.testnet
|
||||||
})
|
})
|
||||||
assert.equal(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2')
|
assert.strictEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can use BIP39 to generate BIP32 addresses', function () {
|
it('can use BIP39 to generate BIP32 addresses', () => {
|
||||||
// var mnemonic = bip39.generateMnemonic()
|
// var mnemonic = bip39.generateMnemonic()
|
||||||
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'
|
||||||
assert(bip39.validateMnemonic(mnemonic))
|
assert(bip39.validateMnemonic(mnemonic))
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/* global describe, it */
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const { describe, it } = require('mocha')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bitcoin = require('../../')
|
const bitcoin = require('../../')
|
||||||
|
|
||||||
describe('bitcoinjs-lib (blocks)', function () {
|
describe('bitcoinjs-lib (blocks)', () => {
|
||||||
it('can extract a height from a CoinBase transaction', function () {
|
it('can extract a height from a CoinBase transaction', () => {
|
||||||
// from 00000000000000000097669cdca131f24d40c4cc7d80eaa65967a2d09acf6ce6
|
// from 00000000000000000097669cdca131f24d40c4cc7d80eaa65967a2d09acf6ce6
|
||||||
const txHex = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000'
|
const txHex = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000'
|
||||||
const tx = bitcoin.Transaction.fromHex(txHex)
|
const tx = bitcoin.Transaction.fromHex(txHex)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it, before */
|
const { describe, it, before } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bitcoin = require('../../')
|
const bitcoin = require('../../')
|
||||||
const regtestUtils = require('./_regtest')
|
const regtestUtils = require('./_regtest')
|
||||||
|
@ -9,10 +8,10 @@ const bip65 = require('bip65')
|
||||||
const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
|
const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
|
||||||
const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
|
const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
|
||||||
|
|
||||||
describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
|
describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
|
||||||
// force update MTP
|
// force update MTP
|
||||||
before(function (done) {
|
before(async () => {
|
||||||
regtestUtils.mine(11, done)
|
await regtestUtils.mine(11)
|
||||||
})
|
})
|
||||||
|
|
||||||
const hashType = bitcoin.Transaction.SIGHASH_ALL
|
const hashType = bitcoin.Transaction.SIGHASH_ALL
|
||||||
|
@ -39,184 +38,160 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// expiry past, {Alice's signature} OP_TRUE
|
// expiry past, {Alice's signature} OP_TRUE
|
||||||
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)', async () => {
|
||||||
// 3 hours ago
|
// 3 hours ago
|
||||||
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) })
|
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) })
|
||||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// fund the P2SH(CLTV) address
|
||||||
regtestUtils.faucet(address, 1e5, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(address, 1e5)
|
||||||
if (err) return done(err)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
|
txb.setLockTime(lockTime)
|
||||||
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
|
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||||
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
// {Alice's signature} OP_TRUE
|
||||||
txb.setLockTime(lockTime)
|
const tx = txb.buildIncomplete()
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
|
redeem: {
|
||||||
|
input: bitcoin.script.compile([
|
||||||
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
|
bitcoin.opcodes.OP_TRUE
|
||||||
|
]),
|
||||||
|
output: redeemScript
|
||||||
|
}
|
||||||
|
}).input
|
||||||
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
const tx = txb.buildIncomplete()
|
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
|
||||||
redeem: {
|
|
||||||
input: bitcoin.script.compile([
|
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
|
||||||
bitcoin.opcodes.OP_TRUE
|
|
||||||
]),
|
|
||||||
output: redeemScript
|
|
||||||
}
|
|
||||||
}).input
|
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.verify({
|
||||||
if (err) return done(err)
|
txId: tx.getId(),
|
||||||
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
regtestUtils.verify({
|
vout: 0,
|
||||||
txId: tx.getId(),
|
value: 7e4
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
|
||||||
vout: 0,
|
|
||||||
value: 7e4
|
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// expiry will pass, {Alice's signature} OP_TRUE
|
// expiry will pass, {Alice's signature} OP_TRUE
|
||||||
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', async () => {
|
||||||
regtestUtils.height(function (err, height) {
|
const height = await regtestUtils.height()
|
||||||
if (err) return done(err)
|
// 5 blocks from now
|
||||||
|
const lockTime = bip65.encode({ blocks: height + 5 })
|
||||||
|
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||||
|
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||||
|
|
||||||
// 5 blocks from now
|
// fund the P2SH(CLTV) address
|
||||||
const lockTime = bip65.encode({ blocks: height + 5 })
|
const unspent = await regtestUtils.faucet(address, 1e5)
|
||||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
txb.setLockTime(lockTime)
|
||||||
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
|
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||||
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// {Alice's signature} OP_TRUE
|
||||||
regtestUtils.faucet(address, 1e5, function (err, unspent) {
|
const tx = txb.buildIncomplete()
|
||||||
if (err) return done(err)
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||||
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
|
redeem: {
|
||||||
|
input: bitcoin.script.compile([
|
||||||
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
|
bitcoin.opcodes.OP_TRUE
|
||||||
|
]),
|
||||||
|
output: redeemScript
|
||||||
|
}
|
||||||
|
}).input
|
||||||
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
|
||||||
txb.setLockTime(lockTime)
|
// ...
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
// into the future!
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
await regtestUtils.mine(5)
|
||||||
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
// {Alice's signature} OP_TRUE
|
await regtestUtils.verify({
|
||||||
const tx = txb.buildIncomplete()
|
txId: tx.getId(),
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
vout: 0,
|
||||||
redeem: {
|
value: 7e4
|
||||||
input: bitcoin.script.compile([
|
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
|
||||||
bitcoin.opcodes.OP_TRUE
|
|
||||||
]),
|
|
||||||
output: redeemScript
|
|
||||||
}
|
|
||||||
}).input
|
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
|
||||||
|
|
||||||
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
|
|
||||||
// ...
|
|
||||||
// into the future!
|
|
||||||
regtestUtils.mine(5, function (err) {
|
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
|
||||||
txId: tx.getId(),
|
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
|
||||||
vout: 0,
|
|
||||||
value: 7e4
|
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE
|
// expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE
|
||||||
it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', async () => {
|
||||||
// two hours ago
|
// two hours ago
|
||||||
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 2) })
|
const lockTime = bip65.encode({ utc: utcNow() - (3600 * 2) })
|
||||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// fund the P2SH(CLTV) address
|
||||||
regtestUtils.faucet(address, 2e5, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(address, 2e5)
|
||||||
if (err) return done(err)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
|
txb.setLockTime(lockTime)
|
||||||
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
|
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||||
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4)
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
// {Alice's signature} {Bob's signature} OP_FALSE
|
||||||
txb.setLockTime(lockTime)
|
const tx = txb.buildIncomplete()
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4)
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
|
redeem: {
|
||||||
|
input: bitcoin.script.compile([
|
||||||
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
|
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||||
|
bitcoin.opcodes.OP_FALSE
|
||||||
|
]),
|
||||||
|
output: redeemScript
|
||||||
|
}
|
||||||
|
}).input
|
||||||
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
// {Alice's signature} {Bob's signature} OP_FALSE
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
const tx = txb.buildIncomplete()
|
await regtestUtils.verify({
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
txId: tx.getId(),
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
redeem: {
|
vout: 0,
|
||||||
input: bitcoin.script.compile([
|
value: 8e4
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
|
||||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
|
||||||
bitcoin.opcodes.OP_FALSE
|
|
||||||
]),
|
|
||||||
output: redeemScript
|
|
||||||
}
|
|
||||||
}).input
|
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
|
||||||
txId: tx.getId(),
|
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
|
||||||
vout: 0,
|
|
||||||
value: 8e4
|
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// expiry in the future, {Alice's signature} OP_TRUE
|
// expiry in the future, {Alice's signature} OP_TRUE
|
||||||
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', function (done) {
|
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', async () => {
|
||||||
// two hours from now
|
// two hours from now
|
||||||
const lockTime = bip65.encode({ utc: utcNow() + (3600 * 2) })
|
const lockTime = bip65.encode({ utc: utcNow() + (3600 * 2) })
|
||||||
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
const redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
|
||||||
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest })
|
||||||
|
|
||||||
// fund the P2SH(CLTV) address
|
// fund the P2SH(CLTV) address
|
||||||
regtestUtils.faucet(address, 2e4, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(address, 2e4)
|
||||||
if (err) return done(err)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
|
txb.setLockTime(lockTime)
|
||||||
|
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
|
||||||
|
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
||||||
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
// {Alice's signature} OP_TRUE
|
||||||
txb.setLockTime(lockTime)
|
const tx = txb.buildIncomplete()
|
||||||
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe)
|
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
|
redeem: {
|
||||||
|
input: bitcoin.script.compile([
|
||||||
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
|
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||||
|
bitcoin.opcodes.OP_TRUE
|
||||||
|
]),
|
||||||
|
output: redeemScript
|
||||||
|
}
|
||||||
|
}).input
|
||||||
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
await regtestUtils.broadcast(tx.toHex()).catch(err => {
|
||||||
const tx = txb.buildIncomplete()
|
assert.throws(() => {
|
||||||
const signatureHash = tx.hashForSignature(0, redeemScript, hashType)
|
if (err) throw err
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
}, /Error: non-final \(code 64\)/)
|
||||||
redeem: {
|
|
||||||
input: bitcoin.script.compile([
|
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
|
||||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
|
||||||
bitcoin.opcodes.OP_TRUE
|
|
||||||
]),
|
|
||||||
output: redeemScript
|
|
||||||
}
|
|
||||||
}).input
|
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
|
||||||
assert.throws(function () {
|
|
||||||
if (err) throw err
|
|
||||||
}, /Error: 64: non-final/)
|
|
||||||
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
/* global describe, it */
|
|
||||||
|
|
||||||
const assert = require('assert')
|
|
||||||
const BN = require('bn.js')
|
|
||||||
const bitcoin = require('../../')
|
|
||||||
const bip32 = require('bip32')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
const tinysecp = require('tiny-secp256k1')
|
|
||||||
|
|
||||||
describe('bitcoinjs-lib (crypto)', function () {
|
|
||||||
it('can recover a private key from duplicate R values', function () {
|
|
||||||
// https://blockchain.info/tx/f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50
|
|
||||||
const tx = bitcoin.Transaction.fromHex('01000000020b668015b32a6178d8524cfef6dc6fc0a4751915c2e9b2ed2d2eab02424341c8000000006a47304402205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022024bf5f506968f5f23f1835574d5afe0e9021b4a5b65cf9742332d5e4acb68f41012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffffa95fa69f11dc1cbb77ef64f25a95d4b12ebda57d19d843333819d95c9172ff89000000006b48304502205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022100832176b59e8f50c56631acbc824bcba936c9476c559c42a4468be98975d07562012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffff02b000eb04000000001976a91472956eed9a8ecb19ae7e3ebd7b06cae4668696a788ac303db000000000001976a9146c0bd55dd2592287cd9992ce3ba3fc1208fb76da88ac00000000')
|
|
||||||
|
|
||||||
tx.ins.forEach(function (input, vin) {
|
|
||||||
const { output: prevOutput, pubkey, signature } = bitcoin.payments.p2pkh({ input: input.script })
|
|
||||||
|
|
||||||
const scriptSignature = bitcoin.script.signature.decode(signature)
|
|
||||||
const m = tx.hashForSignature(vin, prevOutput, scriptSignature.hashType)
|
|
||||||
assert(bitcoin.ECPair.fromPublicKey(pubkey).verify(m, scriptSignature.signature), 'Invalid m')
|
|
||||||
|
|
||||||
// store the required information
|
|
||||||
input.signature = scriptSignature.signature
|
|
||||||
input.z = new BN(m)
|
|
||||||
})
|
|
||||||
|
|
||||||
const n = new BN('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16)
|
|
||||||
|
|
||||||
for (var i = 0; i < tx.ins.length; ++i) {
|
|
||||||
for (var j = i + 1; j < tx.ins.length; ++j) {
|
|
||||||
const inputA = tx.ins[i]
|
|
||||||
const inputB = tx.ins[j]
|
|
||||||
|
|
||||||
// enforce matching r values
|
|
||||||
const r = inputA.signature.slice(0, 32)
|
|
||||||
const rB = inputB.signature.slice(0, 32)
|
|
||||||
assert.strictEqual(r.toString('hex'), rB.toString('hex'))
|
|
||||||
|
|
||||||
const rInv = new BN(r).invm(n)
|
|
||||||
|
|
||||||
const s1 = new BN(inputA.signature.slice(32, 64))
|
|
||||||
const s2 = new BN(inputB.signature.slice(32, 64))
|
|
||||||
const z1 = inputA.z
|
|
||||||
const z2 = inputB.z
|
|
||||||
|
|
||||||
const zz = z1.sub(z2).mod(n)
|
|
||||||
const ss = s1.sub(s2).mod(n)
|
|
||||||
|
|
||||||
// k = (z1 - z2) / (s1 - s2)
|
|
||||||
// d1 = (s1 * k - z1) / r
|
|
||||||
// d2 = (s2 * k - z2) / r
|
|
||||||
const k = zz.mul(ss.invm(n)).mod(n)
|
|
||||||
const d1 = ((s1.mul(k).mod(n)).sub(z1).mod(n)).mul(rInv).mod(n)
|
|
||||||
const d2 = ((s2.mul(k).mod(n)).sub(z2).mod(n)).mul(rInv).mod(n)
|
|
||||||
|
|
||||||
// enforce matching private keys
|
|
||||||
assert.strictEqual(d1.toString(), d2.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key', function () {
|
|
||||||
function recoverParent (master, child) {
|
|
||||||
assert(master.isNeutered(), 'You already have the parent private key')
|
|
||||||
assert(!child.isNeutered(), 'Missing child private key')
|
|
||||||
|
|
||||||
const serQP = master.publicKey
|
|
||||||
const d1 = child.privateKey
|
|
||||||
const data = Buffer.alloc(37)
|
|
||||||
serQP.copy(data, 0)
|
|
||||||
|
|
||||||
// search index space until we find it
|
|
||||||
let d2
|
|
||||||
for (var i = 0; i < 0x80000000; ++i) {
|
|
||||||
data.writeUInt32BE(i, 33)
|
|
||||||
|
|
||||||
// calculate I
|
|
||||||
const I = crypto.createHmac('sha512', master.chainCode).update(data).digest()
|
|
||||||
const IL = I.slice(0, 32)
|
|
||||||
|
|
||||||
// See bip32.js:273 to understand
|
|
||||||
d2 = tinysecp.privateSub(d1, IL)
|
|
||||||
|
|
||||||
const Qp = bip32.fromPrivateKey(d2, Buffer.alloc(32, 0)).publicKey
|
|
||||||
if (Qp.equals(serQP)) break
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = bip32.fromPrivateKey(d2, master.chainCode, master.network)
|
|
||||||
node.depth = master.depth
|
|
||||||
node.index = master.index
|
|
||||||
node.masterFingerprint = master.masterFingerprint
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
const seed = crypto.randomBytes(32)
|
|
||||||
const master = bip32.fromSeed(seed)
|
|
||||||
const child = master.derive(6) // m/6
|
|
||||||
|
|
||||||
// now for the recovery
|
|
||||||
const neuteredMaster = master.neutered()
|
|
||||||
const recovered = recoverParent(neuteredMaster, child)
|
|
||||||
assert.strictEqual(recovered.toBase58(), master.toBase58())
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it, before */
|
const { describe, it, before } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bitcoin = require('../../')
|
const bitcoin = require('../../')
|
||||||
const regtestUtils = require('./_regtest')
|
const regtestUtils = require('./_regtest')
|
||||||
|
@ -11,10 +10,10 @@ const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZs
|
||||||
const charles = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf', regtest)
|
const charles = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf', regtest)
|
||||||
const dave = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest)
|
const dave = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest)
|
||||||
|
|
||||||
describe('bitcoinjs-lib (transactions w/ CSV)', function () {
|
describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
||||||
// force update MTP
|
// force update MTP
|
||||||
before(function (done) {
|
before(async () => {
|
||||||
regtestUtils.mine(11, done)
|
await regtestUtils.mine(11)
|
||||||
})
|
})
|
||||||
|
|
||||||
const hashType = bitcoin.Transaction.SIGHASH_ALL
|
const hashType = bitcoin.Transaction.SIGHASH_ALL
|
||||||
|
@ -70,66 +69,56 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// expiry will pass, {Alice's signature} OP_TRUE
|
// expiry will pass, {Alice's signature} OP_TRUE
|
||||||
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future) (simple CHECKSEQUENCEVERIFY)', async () => {
|
||||||
regtestUtils.height(function (err, height) {
|
// 5 blocks from now
|
||||||
if (err) return done(err)
|
const sequence = bip68.encode({ blocks: 5 })
|
||||||
|
const p2sh = bitcoin.payments.p2sh({
|
||||||
|
redeem: {
|
||||||
|
output: csvCheckSigOutput(alice, bob, sequence)
|
||||||
|
},
|
||||||
|
network: regtest
|
||||||
|
})
|
||||||
|
|
||||||
// 5 blocks from now
|
// fund the P2SH(CSV) address
|
||||||
const sequence = bip68.encode({ blocks: 5 })
|
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
|
||||||
const p2sh = bitcoin.payments.p2sh({
|
|
||||||
redeem: {
|
|
||||||
output: csvCheckSigOutput(alice, bob, sequence)
|
|
||||||
},
|
|
||||||
network: regtest
|
|
||||||
})
|
|
||||||
|
|
||||||
// fund the P2SH(CSV) address
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
|
txb.addInput(unspent.txId, unspent.vout, sequence)
|
||||||
if (err) return done(err)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
// {Alice's signature} OP_TRUE
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence)
|
const tx = txb.buildIncomplete()
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||||
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
|
network: regtest,
|
||||||
|
redeem: {
|
||||||
|
network: regtest,
|
||||||
|
output: p2sh.redeem.output,
|
||||||
|
input: bitcoin.script.compile([
|
||||||
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
|
bitcoin.opcodes.OP_TRUE
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}).input
|
||||||
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
|
||||||
const tx = txb.buildIncomplete()
|
// ...
|
||||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
// into the future!
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
await regtestUtils.mine(10)
|
||||||
network: regtest,
|
|
||||||
redeem: {
|
|
||||||
network: regtest,
|
|
||||||
output: p2sh.redeem.output,
|
|
||||||
input: bitcoin.script.compile([
|
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
|
||||||
bitcoin.opcodes.OP_TRUE
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}).input
|
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
|
||||||
|
|
||||||
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
// ...
|
|
||||||
// into the future!
|
|
||||||
regtestUtils.mine(10, function (err) {
|
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.verify({
|
||||||
if (err) return done(err)
|
txId: tx.getId(),
|
||||||
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
regtestUtils.verify({
|
vout: 0,
|
||||||
txId: tx.getId(),
|
value: 7e4
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
|
||||||
vout: 0,
|
|
||||||
value: 7e4
|
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// expiry in the future, {Alice's signature} OP_TRUE
|
// expiry in the future, {Alice's signature} OP_TRUE
|
||||||
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', function (done) {
|
it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry (simple CHECKSEQUENCEVERIFY)', async () => {
|
||||||
// two hours after confirmation
|
// two hours after confirmation
|
||||||
const sequence = bip68.encode({ seconds: 7168 })
|
const sequence = bip68.encode({ seconds: 7168 })
|
||||||
const p2sh = bitcoin.payments.p2sh({
|
const p2sh = bitcoin.payments.p2sh({
|
||||||
|
@ -140,215 +129,189 @@ describe('bitcoinjs-lib (transactions w/ CSV)', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
// fund the P2SH(CSV) address
|
// fund the P2SH(CSV) address
|
||||||
regtestUtils.faucet(p2sh.address, 2e4, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2sh.address, 2e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence)
|
txb.addInput(unspent.txId, unspent.vout, sequence)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
||||||
|
|
||||||
// {Alice's signature} OP_TRUE
|
// {Alice's signature} OP_TRUE
|
||||||
const tx = txb.buildIncomplete()
|
const tx = txb.buildIncomplete()
|
||||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
|
network: regtest,
|
||||||
|
redeem: {
|
||||||
network: regtest,
|
network: regtest,
|
||||||
redeem: {
|
output: p2sh.redeem.output,
|
||||||
network: regtest,
|
input: bitcoin.script.compile([
|
||||||
output: p2sh.redeem.output,
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
input: bitcoin.script.compile([
|
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
bitcoin.opcodes.OP_TRUE
|
||||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
])
|
||||||
bitcoin.opcodes.OP_TRUE
|
}
|
||||||
])
|
}).input
|
||||||
}
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
}).input
|
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex()).catch(err => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
}, /Error: 64: non-BIP68-final/)
|
}, /Error: non-BIP68-final \(code 64\)/)
|
||||||
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check first combination of complex CSV, 2 of 3
|
// Check first combination of complex CSV, 2 of 3
|
||||||
it('can create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction where Bob and Charles can send (complex CHECKSEQUENCEVERIFY)', async () => {
|
||||||
regtestUtils.height(function (err, height) {
|
const height = await regtestUtils.height()
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
// 2 blocks from now
|
// 2 blocks from now
|
||||||
const sequence1 = bip68.encode({ blocks: 2 })
|
const sequence1 = bip68.encode({ blocks: 2 })
|
||||||
// 5 blocks from now
|
// 5 blocks from now
|
||||||
const sequence2 = bip68.encode({ blocks: 5 })
|
const sequence2 = bip68.encode({ blocks: 5 })
|
||||||
const p2sh = bitcoin.payments.p2sh({
|
const p2sh = bitcoin.payments.p2sh({
|
||||||
redeem: {
|
redeem: {
|
||||||
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
||||||
},
|
},
|
||||||
network: regtest
|
network: regtest
|
||||||
})
|
})
|
||||||
|
|
||||||
// fund the P2SH(CCSV) address
|
// fund the P2SH(CCSV) address
|
||||||
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout)
|
txb.addInput(unspent.txId, unspent.vout)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||||
|
|
||||||
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
|
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
|
||||||
const tx = txb.buildIncomplete()
|
const tx = txb.buildIncomplete()
|
||||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
network: regtest,
|
network: regtest,
|
||||||
redeem: {
|
redeem: {
|
||||||
network: regtest,
|
network: regtest,
|
||||||
output: p2sh.redeem.output,
|
output: p2sh.redeem.output,
|
||||||
input: bitcoin.script.compile([
|
input: bitcoin.script.compile([
|
||||||
bitcoin.opcodes.OP_0,
|
bitcoin.opcodes.OP_0,
|
||||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||||
bitcoin.script.signature.encode(charles.sign(signatureHash), hashType),
|
bitcoin.script.signature.encode(charles.sign(signatureHash), hashType),
|
||||||
bitcoin.opcodes.OP_TRUE,
|
bitcoin.opcodes.OP_TRUE,
|
||||||
bitcoin.opcodes.OP_TRUE
|
bitcoin.opcodes.OP_TRUE
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}).input
|
}).input
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 7e4
|
value: 7e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check first combination of complex CSV, mediator + 1 of 3 after 2 blocks
|
// Check first combination of complex CSV, mediator + 1 of 3 after 2 blocks
|
||||||
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) and Bob can send after 2 blocks (complex CHECKSEQUENCEVERIFY)', async () => {
|
||||||
regtestUtils.height(function (err, height) {
|
const height = await regtestUtils.height()
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
// 2 blocks from now
|
// 2 blocks from now
|
||||||
const sequence1 = bip68.encode({ blocks: 2 })
|
const sequence1 = bip68.encode({ blocks: 2 })
|
||||||
// 5 blocks from now
|
// 5 blocks from now
|
||||||
const sequence2 = bip68.encode({ blocks: 5 })
|
const sequence2 = bip68.encode({ blocks: 5 })
|
||||||
const p2sh = bitcoin.payments.p2sh({
|
const p2sh = bitcoin.payments.p2sh({
|
||||||
redeem: {
|
redeem: {
|
||||||
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
||||||
},
|
},
|
||||||
network: regtest
|
network: regtest
|
||||||
})
|
})
|
||||||
|
|
||||||
// fund the P2SH(CCSV) address
|
// fund the P2SH(CCSV) address
|
||||||
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence1) // Set sequence1 for input
|
txb.addInput(unspent.txId, unspent.vout, sequence1) // Set sequence1 for input
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||||
|
|
||||||
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
|
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
|
||||||
const tx = txb.buildIncomplete()
|
const tx = txb.buildIncomplete()
|
||||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
network: regtest,
|
network: regtest,
|
||||||
redeem: {
|
redeem: {
|
||||||
network: regtest,
|
network: regtest,
|
||||||
output: p2sh.redeem.output,
|
output: p2sh.redeem.output,
|
||||||
input: bitcoin.script.compile([
|
input: bitcoin.script.compile([
|
||||||
bitcoin.opcodes.OP_0,
|
bitcoin.opcodes.OP_0,
|
||||||
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
bitcoin.opcodes.OP_0,
|
bitcoin.opcodes.OP_0,
|
||||||
bitcoin.opcodes.OP_TRUE
|
bitcoin.opcodes.OP_TRUE
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}).input
|
}).input
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
// Wait 2 blocks
|
// Wait 2 blocks
|
||||||
regtestUtils.mine(2, function (err) {
|
await regtestUtils.mine(2)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 7e4
|
value: 7e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check first combination of complex CSV, mediator after 5 blocks
|
// Check first combination of complex CSV, mediator after 5 blocks
|
||||||
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction where Alice (mediator) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)', async () => {
|
||||||
regtestUtils.height(function (err, height) {
|
const height = await regtestUtils.height()
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
// 2 blocks from now
|
// 2 blocks from now
|
||||||
const sequence1 = bip68.encode({ blocks: 2 })
|
const sequence1 = bip68.encode({ blocks: 2 })
|
||||||
// 5 blocks from now
|
// 5 blocks from now
|
||||||
const sequence2 = bip68.encode({ blocks: 5 })
|
const sequence2 = bip68.encode({ blocks: 5 })
|
||||||
const p2sh = bitcoin.payments.p2sh({
|
const p2sh = bitcoin.payments.p2sh({
|
||||||
redeem: {
|
redeem: {
|
||||||
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2)
|
||||||
},
|
},
|
||||||
network: regtest
|
network: regtest
|
||||||
})
|
})
|
||||||
|
|
||||||
// fund the P2SH(CCSV) address
|
// fund the P2SH(CCSV) address
|
||||||
regtestUtils.faucet(p2sh.address, 1e5, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2sh.address, 1e5)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout, sequence2) // Set sequence2 for input
|
txb.addInput(unspent.txId, unspent.vout, sequence2) // Set sequence2 for input
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
|
||||||
|
|
||||||
// {Alice mediator sig} OP_FALSE
|
// {Alice mediator sig} OP_FALSE
|
||||||
const tx = txb.buildIncomplete()
|
const tx = txb.buildIncomplete()
|
||||||
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
const signatureHash = tx.hashForSignature(0, p2sh.redeem.output, hashType)
|
||||||
const redeemScriptSig = bitcoin.payments.p2sh({
|
const redeemScriptSig = bitcoin.payments.p2sh({
|
||||||
network: regtest,
|
network: regtest,
|
||||||
redeem: {
|
redeem: {
|
||||||
network: regtest,
|
network: regtest,
|
||||||
output: p2sh.redeem.output,
|
output: p2sh.redeem.output,
|
||||||
input: bitcoin.script.compile([
|
input: bitcoin.script.compile([
|
||||||
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
|
||||||
bitcoin.opcodes.OP_0
|
bitcoin.opcodes.OP_0
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}).input
|
}).input
|
||||||
tx.setInputScript(0, redeemScriptSig)
|
tx.setInputScript(0, redeemScriptSig)
|
||||||
|
|
||||||
// Wait 5 blocks
|
// Wait 5 blocks
|
||||||
regtestUtils.mine(5, function (err) {
|
await regtestUtils.mine(5)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 7e4
|
value: 7e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* global describe, it */
|
|
||||||
|
|
||||||
const bitcoin = require('../../')
|
const bitcoin = require('../../')
|
||||||
|
|
||||||
|
const { describe, it } = require('mocha')
|
||||||
const regtestUtils = require('./_regtest')
|
const regtestUtils = require('./_regtest')
|
||||||
const NETWORK = regtestUtils.network
|
const NETWORK = regtestUtils.network
|
||||||
const keyPairs = [
|
const keyPairs = [
|
||||||
|
@ -9,27 +8,25 @@ const keyPairs = [
|
||||||
bitcoin.ECPair.makeRandom({ network: NETWORK })
|
bitcoin.ECPair.makeRandom({ network: NETWORK })
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) {
|
async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) {
|
||||||
regtestUtils.faucetComplex(prevOutput, 5e4, (err, unspent) => {
|
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(NETWORK)
|
const txb = new bitcoin.TransactionBuilder(NETWORK)
|
||||||
txb.addInput(unspent.txId, unspent.vout, null, prevOutput)
|
txb.addInput(unspent.txId, unspent.vout, null, prevOutput)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
||||||
|
|
||||||
if (depends.signatures) {
|
if (depends.signatures) {
|
||||||
keyPairs.forEach((keyPair) => {
|
keyPairs.forEach(keyPair => {
|
||||||
txb.sign(0, keyPair, redeemScript, null, unspent.value, witnessScript)
|
txb.sign(0, keyPair, redeemScript, null, unspent.value, witnessScript)
|
||||||
})
|
})
|
||||||
} else if (depends.signature) {
|
} else if (depends.signature) {
|
||||||
txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript)
|
txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
regtestUtils.broadcast(txb.build().toHex(), done)
|
return regtestUtils.broadcast(txb.build().toHex())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach((k) => {
|
;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {
|
||||||
const fixtures = require('../fixtures/' + k)
|
const fixtures = require('../fixtures/' + k)
|
||||||
const { depends } = fixtures.dynamic
|
const { depends } = fixtures.dynamic
|
||||||
const fn = bitcoin.payments[k]
|
const fn = bitcoin.payments[k]
|
||||||
|
@ -42,29 +39,29 @@ function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) {
|
||||||
const { output } = fn(base)
|
const { output } = fn(base)
|
||||||
if (!output) throw new TypeError('Missing output')
|
if (!output) throw new TypeError('Missing output')
|
||||||
|
|
||||||
describe('bitcoinjs-lib (payments - ' + k + ')', function () {
|
describe('bitcoinjs-lib (payments - ' + k + ')', () => {
|
||||||
it('can broadcast as an output, and be spent as an input', (done) => {
|
it('can broadcast as an output, and be spent as an input', async () => {
|
||||||
buildAndSign(depends, output, null, null, done)
|
await buildAndSign(depends, output, null, null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can (as P2SH(' + k + ')) broadcast as an output, and be spent as an input', (done) => {
|
it('can (as P2SH(' + k + ')) broadcast as an output, and be spent as an input', async () => {
|
||||||
const p2sh = bitcoin.payments.p2sh({ redeem: { output }, network: NETWORK })
|
const p2sh = bitcoin.payments.p2sh({ redeem: { output }, network: NETWORK })
|
||||||
buildAndSign(depends, p2sh.output, p2sh.redeem.output, null, done)
|
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, null)
|
||||||
})
|
})
|
||||||
|
|
||||||
// NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail
|
// NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail
|
||||||
if (k === 'p2wpkh') return
|
if (k === 'p2wpkh') return
|
||||||
|
|
||||||
it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', (done) => {
|
it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', async () => {
|
||||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
|
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
|
||||||
buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output, done)
|
await buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', (done) => {
|
it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', async () => {
|
||||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
|
const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK })
|
||||||
const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK })
|
const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK })
|
||||||
|
|
||||||
buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output, done)
|
await buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
/* global describe, it */
|
|
||||||
|
|
||||||
const assert = require('assert')
|
|
||||||
const bitcoin = require('../../')
|
|
||||||
const ecc = require('tiny-secp256k1')
|
|
||||||
|
|
||||||
function getAddress (node, network) {
|
|
||||||
return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address
|
|
||||||
}
|
|
||||||
|
|
||||||
// vG = (dG \+ sha256(e * dG)G)
|
|
||||||
function stealthSend (e, Q) {
|
|
||||||
const eQ = ecc.pointMultiply(Q, e, true) // shared secret
|
|
||||||
const c = bitcoin.crypto.sha256(eQ)
|
|
||||||
const Qc = ecc.pointAddScalar(Q, c)
|
|
||||||
const vG = bitcoin.ECPair.fromPublicKey(Qc)
|
|
||||||
|
|
||||||
return vG
|
|
||||||
}
|
|
||||||
|
|
||||||
// v = (d + sha256(eG * d))
|
|
||||||
function stealthReceive (d, eG) {
|
|
||||||
const eQ = ecc.pointMultiply(eG, d) // shared secret
|
|
||||||
const c = bitcoin.crypto.sha256(eQ)
|
|
||||||
const dc = ecc.privateAdd(d, c)
|
|
||||||
const v = bitcoin.ECPair.fromPrivateKey(dc)
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// d = (v - sha256(e * dG))
|
|
||||||
function stealthRecoverLeaked (v, e, Q) {
|
|
||||||
const eQ = ecc.pointMultiply(Q, e) // shared secret
|
|
||||||
const c = bitcoin.crypto.sha256(eQ)
|
|
||||||
const vc = ecc.privateSub(v, c)
|
|
||||||
const d = bitcoin.ECPair.fromPrivateKey(vc)
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// vG = (rG \+ sha256(e * dG)G)
|
|
||||||
function stealthDualSend (e, R, Q) {
|
|
||||||
const eQ = ecc.pointMultiply(Q, e) // shared secret
|
|
||||||
const c = bitcoin.crypto.sha256(eQ)
|
|
||||||
const Rc = ecc.pointAddScalar(R, c)
|
|
||||||
const vG = bitcoin.ECPair.fromPublicKey(Rc)
|
|
||||||
|
|
||||||
return vG
|
|
||||||
}
|
|
||||||
|
|
||||||
// vG = (rG \+ sha256(eG * d)G)
|
|
||||||
function stealthDualScan (d, R, eG) {
|
|
||||||
const eQ = ecc.pointMultiply(eG, d) // shared secret
|
|
||||||
const c = bitcoin.crypto.sha256(eQ)
|
|
||||||
const Rc = ecc.pointAddScalar(R, c)
|
|
||||||
const vG = bitcoin.ECPair.fromPublicKey(Rc)
|
|
||||||
|
|
||||||
return vG
|
|
||||||
}
|
|
||||||
|
|
||||||
// v = (r + sha256(eG * d))
|
|
||||||
function stealthDualReceive (d, r, eG) {
|
|
||||||
const eQ = ecc.pointMultiply(eG, d) // shared secret
|
|
||||||
const c = bitcoin.crypto.sha256(eQ)
|
|
||||||
const rc = ecc.privateAdd(r, c)
|
|
||||||
const v = bitcoin.ECPair.fromPrivateKey(rc)
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('bitcoinjs-lib (crypto)', function () {
|
|
||||||
it('can generate a single-key stealth address', function () {
|
|
||||||
// XXX: should be randomly generated, see next test for example
|
|
||||||
const recipient = bitcoin.ECPair.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') // private to recipient
|
|
||||||
const nonce = bitcoin.ECPair.fromWIF('KxVqB96pxbw1pokzQrZkQbLfVBjjHFfp2mFfEp8wuEyGenLFJhM9') // private to sender
|
|
||||||
|
|
||||||
// ... recipient reveals public key (recipient.Q) to sender
|
|
||||||
const forSender = stealthSend(nonce.privateKey, recipient.publicKey)
|
|
||||||
assert.equal(getAddress(forSender), '1CcZWwCpACJL3AxqoDbwEt4JgDFuTHUspE')
|
|
||||||
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
|
|
||||||
|
|
||||||
// ... sender reveals nonce public key (nonce.Q) to recipient
|
|
||||||
const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey)
|
|
||||||
assert.equal(getAddress(forRecipient), '1CcZWwCpACJL3AxqoDbwEt4JgDFuTHUspE')
|
|
||||||
assert.equal(forRecipient.toWIF(), 'L1yjUN3oYyCXV3LcsBrmxCNTa62bZKWCybxVJMvqjMmmfDE8yk7n')
|
|
||||||
|
|
||||||
// sender and recipient, both derived same address
|
|
||||||
assert.equal(getAddress(forSender), getAddress(forRecipient))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can generate a single-key stealth address (randomly)', function () {
|
|
||||||
const recipient = bitcoin.ECPair.makeRandom() // private to recipient
|
|
||||||
const nonce = bitcoin.ECPair.makeRandom() // private to sender
|
|
||||||
|
|
||||||
// ... recipient reveals public key (recipient.Q) to sender
|
|
||||||
const forSender = stealthSend(nonce.privateKey, recipient.publicKey)
|
|
||||||
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
|
|
||||||
|
|
||||||
// ... sender reveals nonce public key (nonce.Q) to recipient
|
|
||||||
const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey)
|
|
||||||
assert.doesNotThrow(function () { forRecipient.toWIF() })
|
|
||||||
|
|
||||||
// sender and recipient, both derived same address
|
|
||||||
assert.equal(getAddress(forSender), getAddress(forRecipient))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can recover parent recipient.d, if a derived private key is leaked [and nonce was revealed]', function () {
|
|
||||||
const recipient = bitcoin.ECPair.makeRandom() // private to recipient
|
|
||||||
const nonce = bitcoin.ECPair.makeRandom() // private to sender
|
|
||||||
|
|
||||||
// ... recipient reveals public key (recipient.Q) to sender
|
|
||||||
const forSender = stealthSend(nonce.privateKey, recipient.publicKey)
|
|
||||||
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
|
|
||||||
|
|
||||||
// ... sender reveals nonce public key (nonce.Q) to recipient
|
|
||||||
const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey)
|
|
||||||
assert.doesNotThrow(function () { forRecipient.toWIF() })
|
|
||||||
|
|
||||||
// ... recipient accidentally leaks forRecipient.d on the blockchain
|
|
||||||
const leaked = stealthRecoverLeaked(forRecipient.privateKey, nonce.privateKey, recipient.publicKey)
|
|
||||||
assert.equal(leaked.toWIF(), recipient.toWIF())
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can generate a dual-key stealth address', function () {
|
|
||||||
// XXX: should be randomly generated, see next test for example
|
|
||||||
const recipient = bitcoin.ECPair.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') // private to recipient
|
|
||||||
const scan = bitcoin.ECPair.fromWIF('L5DkCk3xLLoGKncqKsWQTdaPSR4V8gzc14WVghysQGkdryRudjBM') // private to scanner/recipient
|
|
||||||
const nonce = bitcoin.ECPair.fromWIF('KxVqB96pxbw1pokzQrZkQbLfVBjjHFfp2mFfEp8wuEyGenLFJhM9') // private to sender
|
|
||||||
|
|
||||||
// ... recipient reveals public key(s) (recipient.Q, scan.Q) to sender
|
|
||||||
const forSender = stealthDualSend(nonce.privateKey, recipient.publicKey, scan.publicKey)
|
|
||||||
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
|
|
||||||
|
|
||||||
// ... sender reveals nonce public key (nonce.Q) to scanner
|
|
||||||
const forScanner = stealthDualScan(scan.privateKey, recipient.publicKey, nonce.publicKey)
|
|
||||||
assert.throws(function () { forScanner.toWIF() }, /Error: Missing private key/)
|
|
||||||
|
|
||||||
// ... scanner reveals relevant transaction + nonce public key (nonce.Q) to recipient
|
|
||||||
const forRecipient = stealthDualReceive(scan.privateKey, recipient.privateKey, nonce.publicKey)
|
|
||||||
assert.doesNotThrow(function () { forRecipient.toWIF() })
|
|
||||||
|
|
||||||
// scanner, sender and recipient, all derived same address
|
|
||||||
assert.equal(getAddress(forSender), getAddress(forScanner))
|
|
||||||
assert.equal(getAddress(forSender), getAddress(forRecipient))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can generate a dual-key stealth address (randomly)', function () {
|
|
||||||
const recipient = bitcoin.ECPair.makeRandom() // private to recipient
|
|
||||||
const scan = bitcoin.ECPair.makeRandom() // private to scanner/recipient
|
|
||||||
const nonce = bitcoin.ECPair.makeRandom() // private to sender
|
|
||||||
|
|
||||||
// ... recipient reveals public key(s) (recipient.Q, scan.Q) to sender
|
|
||||||
const forSender = stealthDualSend(nonce.privateKey, recipient.publicKey, scan.publicKey)
|
|
||||||
assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/)
|
|
||||||
|
|
||||||
// ... sender reveals nonce public key (nonce.Q) to scanner
|
|
||||||
const forScanner = stealthDualScan(scan.privateKey, recipient.publicKey, nonce.publicKey)
|
|
||||||
assert.throws(function () { forScanner.toWIF() }, /Error: Missing private key/)
|
|
||||||
|
|
||||||
// ... scanner reveals relevant transaction + nonce public key (nonce.Q) to recipient
|
|
||||||
const forRecipient = stealthDualReceive(scan.privateKey, recipient.privateKey, nonce.publicKey)
|
|
||||||
assert.doesNotThrow(function () { forRecipient.toWIF() })
|
|
||||||
|
|
||||||
// scanner, sender and recipient, all derived same address
|
|
||||||
assert.equal(getAddress(forSender), getAddress(forScanner))
|
|
||||||
assert.equal(getAddress(forSender), getAddress(forRecipient))
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bitcoin = require('../../')
|
const bitcoin = require('../../')
|
||||||
const regtestUtils = require('./_regtest')
|
const regtestUtils = require('./_regtest')
|
||||||
|
@ -9,8 +8,8 @@ function rng () {
|
||||||
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64')
|
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('bitcoinjs-lib (transactions)', function () {
|
describe('bitcoinjs-lib (transactions)', () => {
|
||||||
it('can create a 1-to-1 Transaction', function () {
|
it('can create a 1-to-1 Transaction', () => {
|
||||||
const alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
|
const alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
|
||||||
const txb = new bitcoin.TransactionBuilder()
|
const txb = new bitcoin.TransactionBuilder()
|
||||||
|
|
||||||
|
@ -25,7 +24,7 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
assert.strictEqual(txb.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000')
|
assert.strictEqual(txb.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create a 2-to-2 Transaction', function () {
|
it('can create a 2-to-2 Transaction', () => {
|
||||||
const alice = bitcoin.ECPair.fromWIF('L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1')
|
const alice = bitcoin.ECPair.fromWIF('L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1')
|
||||||
const bob = bitcoin.ECPair.fromWIF('KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z')
|
const bob = bitcoin.ECPair.fromWIF('KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z')
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
assert.strictEqual(txb.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000')
|
assert.strictEqual(txb.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a typical Transaction', function (done) {
|
it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
|
||||||
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest })
|
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest })
|
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
const aliceChange = bitcoin.ECPair.makeRandom({ network: regtest, rng: rng })
|
const aliceChange = bitcoin.ECPair.makeRandom({ network: regtest, rng: rng })
|
||||||
|
@ -54,51 +53,45 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
const aliceCpkh = bitcoin.payments.p2pkh({ pubkey: aliceChange.publicKey, network: regtest })
|
const aliceCpkh = bitcoin.payments.p2pkh({ pubkey: aliceChange.publicKey, network: regtest })
|
||||||
|
|
||||||
// give Alice 2 unspent outputs
|
// give Alice 2 unspent outputs
|
||||||
regtestUtils.faucet(alice1pkh.address, 5e4, function (err, unspent0) {
|
const unspent0 = await regtestUtils.faucet(alice1pkh.address, 5e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.faucet(alice2pkh.address, 7e4, function (err, unspent1) {
|
const unspent1 = await regtestUtils.faucet(alice2pkh.address, 7e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent0.txId, unspent0.vout) // alice1 unspent
|
txb.addInput(unspent0.txId, unspent0.vout) // alice1 unspent
|
||||||
txb.addInput(unspent1.txId, unspent1.vout) // alice2 unspent
|
txb.addInput(unspent1.txId, unspent1.vout) // alice2 unspent
|
||||||
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4) // the actual "spend"
|
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4) // the actual "spend"
|
||||||
txb.addOutput(aliceCpkh.address, 1e4) // Alice's change
|
txb.addOutput(aliceCpkh.address, 1e4) // Alice's change
|
||||||
// (in)(4e4 + 2e4) - (out)(1e4 + 3e4) = (fee)2e4 = 20000, this is the miner fee
|
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
|
||||||
|
|
||||||
// Alice signs each input with the respective private keys
|
// Alice signs each input with the respective private keys
|
||||||
txb.sign(0, alice1)
|
txb.sign(0, alice1)
|
||||||
txb.sign(1, alice2)
|
txb.sign(1, alice2)
|
||||||
|
|
||||||
// build and broadcast our RegTest network
|
// build and broadcast our RegTest network
|
||||||
regtestUtils.broadcast(txb.build().toHex(), done)
|
await regtestUtils.broadcast(txb.build().toHex())
|
||||||
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
|
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: regtest })
|
const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: regtest })
|
||||||
|
|
||||||
regtestUtils.faucet(p2pkh.address, 2e5, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2pkh.address, 2e5)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
const data = Buffer.from('bitcoinjs-lib', 'utf8')
|
const data = Buffer.from('bitcoinjs-lib', 'utf8')
|
||||||
const embed = bitcoin.payments.embed({ data: [data] })
|
const embed = bitcoin.payments.embed({ data: [data] })
|
||||||
txb.addInput(unspent.txId, unspent.vout)
|
txb.addInput(unspent.txId, unspent.vout)
|
||||||
txb.addOutput(embed.output, 1000)
|
txb.addOutput(embed.output, 1000)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
|
|
||||||
// build and broadcast to the RegTest network
|
// build and broadcast to the RegTest network
|
||||||
regtestUtils.broadcast(txb.build().toHex(), done)
|
await regtestUtils.broadcast(txb.build().toHex())
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => {
|
||||||
const keyPairs = [
|
const keyPairs = [
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||||
|
@ -109,118 +102,102 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network: regtest })
|
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network: regtest })
|
||||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest })
|
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest })
|
||||||
|
|
||||||
regtestUtils.faucet(p2sh.address, 2e4, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2sh.address, 2e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout)
|
txb.addInput(unspent.txId, unspent.vout)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
||||||
|
|
||||||
txb.sign(0, keyPairs[0], p2sh.redeem.output)
|
txb.sign(0, keyPairs[0], p2sh.redeem.output)
|
||||||
txb.sign(0, keyPairs[2], p2sh.redeem.output)
|
txb.sign(0, keyPairs[2], p2sh.redeem.output)
|
||||||
const tx = txb.build()
|
const tx = txb.build()
|
||||||
|
|
||||||
// build and broadcast to the Bitcoin RegTest network
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 1e4
|
value: 1e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
|
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
|
||||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest })
|
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest })
|
||||||
|
|
||||||
regtestUtils.faucet(p2sh.address, 5e4, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2sh.address, 5e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout)
|
txb.addInput(unspent.txId, unspent.vout)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
||||||
txb.sign(0, keyPair, p2sh.redeem.output, null, unspent.value)
|
txb.sign(0, keyPair, p2sh.redeem.output, null, unspent.value)
|
||||||
|
|
||||||
const tx = txb.build()
|
const tx = txb.build()
|
||||||
|
|
||||||
// build and broadcast to the Bitcoin RegTest network
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 2e4
|
value: 2e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
|
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest })
|
||||||
|
|
||||||
regtestUtils.faucetComplex(p2wpkh.address, 5e4, function (err, unspent) {
|
const unspent = await regtestUtils.faucetComplex(p2wpkh.address, 5e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
// XXX: build the Transaction w/ a P2WPKH input
|
// XXX: build the Transaction w/ a P2WPKH input
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript!
|
txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript!
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
||||||
txb.sign(0, keyPair, null, null, unspent.value) // NOTE: no redeem script
|
txb.sign(0, keyPair, null, null, unspent.value) // NOTE: no redeem script
|
||||||
const tx = txb.build()
|
const tx = txb.build()
|
||||||
|
|
||||||
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
|
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 2e4
|
value: 2e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
const p2pk = bitcoin.payments.p2pk({ pubkey: keyPair.publicKey, network: regtest })
|
const p2pk = bitcoin.payments.p2pk({ pubkey: keyPair.publicKey, network: regtest })
|
||||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest })
|
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest })
|
||||||
|
|
||||||
regtestUtils.faucetComplex(p2wsh.address, 5e4, function (err, unspent) {
|
const unspent = await regtestUtils.faucetComplex(p2wsh.address, 5e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
// XXX: build the Transaction w/ a P2WSH input
|
// XXX: build the Transaction w/ a P2WSH input
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript!
|
txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript!
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4)
|
||||||
txb.sign(0, keyPair, null, null, 5e4, p2wsh.redeem.output) // NOTE: provide a witnessScript!
|
txb.sign(0, keyPair, null, null, 5e4, p2wsh.redeem.output) // NOTE: provide a witnessScript!
|
||||||
const tx = txb.build()
|
const tx = txb.build()
|
||||||
|
|
||||||
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network
|
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 2e4
|
value: 2e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
|
||||||
const keyPairs = [
|
const keyPairs = [
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||||
|
@ -233,43 +210,39 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest })
|
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest })
|
||||||
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest })
|
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest })
|
||||||
|
|
||||||
regtestUtils.faucet(p2sh.address, 6e4, function (err, unspent) {
|
const unspent = await regtestUtils.faucet(p2sh.address, 6e4)
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout, null, p2sh.output)
|
txb.addInput(unspent.txId, unspent.vout, null, p2sh.output)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4)
|
||||||
txb.sign(0, keyPairs[0], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
|
txb.sign(0, keyPairs[0], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
|
||||||
txb.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
|
txb.sign(0, keyPairs[2], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
|
||||||
txb.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
|
txb.sign(0, keyPairs[3], p2sh.redeem.output, null, unspent.value, p2wsh.redeem.output)
|
||||||
|
|
||||||
const tx = txb.build()
|
const tx = txb.build()
|
||||||
|
|
||||||
// build and broadcast to the Bitcoin RegTest network
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
regtestUtils.broadcast(tx.toHex(), function (err) {
|
await regtestUtils.broadcast(tx.toHex())
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
regtestUtils.verify({
|
await regtestUtils.verify({
|
||||||
txId: tx.getId(),
|
txId: tx.getId(),
|
||||||
address: regtestUtils.RANDOM_ADDRESS,
|
address: regtestUtils.RANDOM_ADDRESS,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
value: 3e4
|
value: 3e4
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can verify Transaction signatures', function () {
|
it('can verify Transaction (P2PKH) signatures', () => {
|
||||||
const txHex = '010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700'
|
const txHex = '010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700'
|
||||||
const keyPairs = [
|
const keyPairs = [
|
||||||
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d',
|
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d',
|
||||||
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a',
|
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a',
|
||||||
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f'
|
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f'
|
||||||
].map(function (q) { return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')) })
|
].map(q => { return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')) })
|
||||||
|
|
||||||
const tx = bitcoin.Transaction.fromHex(txHex)
|
const tx = bitcoin.Transaction.fromHex(txHex)
|
||||||
|
|
||||||
tx.ins.forEach(function (input, i) {
|
tx.ins.forEach((input, i) => {
|
||||||
const keyPair = keyPairs[i]
|
const keyPair = keyPairs[i]
|
||||||
const p2pkh = bitcoin.payments.p2pkh({
|
const p2pkh = bitcoin.payments.p2pkh({
|
||||||
pubkey: keyPair.publicKey,
|
pubkey: keyPair.publicKey,
|
||||||
|
@ -282,4 +255,34 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
|
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can verify Transaction (P2SH(P2WPKH)) signatures', () => {
|
||||||
|
const utxos = {
|
||||||
|
'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': {
|
||||||
|
value: 50000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const txHex = '02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000'
|
||||||
|
const tx = bitcoin.Transaction.fromHex(txHex)
|
||||||
|
|
||||||
|
tx.ins.forEach((input, i) => {
|
||||||
|
const txId = Buffer.from(input.hash).reverse().toString('hex')
|
||||||
|
const utxo = utxos[`${txId}:${i}`]
|
||||||
|
if (!utxo) throw new Error('Missing utxo')
|
||||||
|
|
||||||
|
const p2sh = bitcoin.payments.p2sh({
|
||||||
|
input: input.script,
|
||||||
|
witness: input.witness
|
||||||
|
})
|
||||||
|
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem)
|
||||||
|
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }) // because P2WPKH is annoying
|
||||||
|
|
||||||
|
const ss = bitcoin.script.signature.decode(p2wpkh.signature)
|
||||||
|
const hash = tx.hashForWitnessV0(i, p2pkh.output, utxo.value, ss.hashType)
|
||||||
|
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey) // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
|
||||||
|
|
||||||
|
assert.strictEqual(keyPair.verify(hash, ss.signature), true)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const u = require('./payments.utils')
|
const u = require('./payments.utils')
|
||||||
|
|
||||||
;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(function (p) {
|
;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(p => {
|
||||||
describe(p, function () {
|
describe(p, () => {
|
||||||
const fn = require('../src/payments/' + p)
|
let fn
|
||||||
|
let payment = require('../src/payments/' + p)
|
||||||
|
if (p === 'embed') {
|
||||||
|
fn = payment.p2data
|
||||||
|
} else {
|
||||||
|
fn = payment[p]
|
||||||
|
}
|
||||||
const fixtures = require('./fixtures/' + p)
|
const fixtures = require('./fixtures/' + p)
|
||||||
|
|
||||||
fixtures.valid.forEach(function (f, i) {
|
fixtures.valid.forEach((f, i) => {
|
||||||
it(f.description + ' as expected', function () {
|
it(f.description + ' as expected', () => {
|
||||||
const args = u.preform(f.arguments)
|
const args = u.preform(f.arguments)
|
||||||
const actual = fn(args, f.options)
|
const actual = fn(args, f.options)
|
||||||
|
|
||||||
u.equate(actual, f.expected, f.arguments)
|
u.equate(actual, f.expected, f.arguments)
|
||||||
})
|
})
|
||||||
|
|
||||||
it(f.description + ' as expected (no validation)', function () {
|
it(f.description + ' as expected (no validation)', () => {
|
||||||
const args = u.preform(f.arguments)
|
const args = u.preform(f.arguments)
|
||||||
const actual = fn(args, Object.assign({}, f.options, {
|
const actual = fn(args, Object.assign({}, f.options, {
|
||||||
validate: false
|
validate: false
|
||||||
|
@ -26,11 +31,11 @@ const u = require('./payments.utils')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.forEach(function (f) {
|
fixtures.invalid.forEach(f => {
|
||||||
it('throws ' + f.exception + (f.description ? ('for ' + f.description) : ''), function () {
|
it('throws ' + f.exception + (f.description ? ('for ' + f.description) : ''), () => {
|
||||||
const args = u.preform(f.arguments)
|
const args = u.preform(f.arguments)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
fn(args, f.options)
|
fn(args, f.options)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
|
@ -40,23 +45,23 @@ const u = require('./payments.utils')
|
||||||
if (!fixtures.dynamic) return
|
if (!fixtures.dynamic) return
|
||||||
const { depends, details } = fixtures.dynamic
|
const { depends, details } = fixtures.dynamic
|
||||||
|
|
||||||
details.forEach(function (f) {
|
details.forEach(f => {
|
||||||
const detail = u.preform(f)
|
const detail = u.preform(f)
|
||||||
const disabled = {}
|
const disabled = {}
|
||||||
if (f.disabled) f.disabled.forEach(function (k) { disabled[k] = true })
|
if (f.disabled) f.disabled.forEach(k => { disabled[k] = true })
|
||||||
|
|
||||||
for (let key in depends) {
|
for (let key in depends) {
|
||||||
if (key in disabled) continue
|
if (key in disabled) continue
|
||||||
const dependencies = depends[key]
|
const dependencies = depends[key]
|
||||||
|
|
||||||
dependencies.forEach(function (dependency) {
|
dependencies.forEach(dependency => {
|
||||||
if (!Array.isArray(dependency)) dependency = [dependency]
|
if (!Array.isArray(dependency)) dependency = [dependency]
|
||||||
|
|
||||||
const args = {}
|
const args = {}
|
||||||
dependency.forEach(function (d) { u.from(d, detail, args) })
|
dependency.forEach(d => { u.from(d, detail, args) })
|
||||||
const expected = u.from(key, detail)
|
const expected = u.from(key, detail)
|
||||||
|
|
||||||
it(f.description + ', ' + key + ' derives from ' + JSON.stringify(dependency), function () {
|
it(f.description + ', ' + key + ' derives from ' + JSON.stringify(dependency), () => {
|
||||||
u.equate(fn(args), expected)
|
u.equate(fn(args), expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
let t = require('assert')
|
const t = require('assert')
|
||||||
let bscript = require('../src/script')
|
const bscript = require('../src/script')
|
||||||
let bnetworks = require('../src/networks')
|
const BNETWORKS = require('../src/networks')
|
||||||
|
|
||||||
function tryHex (x) {
|
function tryHex (x) {
|
||||||
if (Buffer.isBuffer(x)) return x.toString('hex')
|
if (Buffer.isBuffer(x)) return x.toString('hex')
|
||||||
|
@ -39,7 +39,7 @@ function carryOver (a, b) {
|
||||||
function equateBase (a, b, context) {
|
function equateBase (a, b, context) {
|
||||||
if ('output' in b) t.strictEqual(tryASM(a.output), tryASM(b.output), `Inequal ${context}output`)
|
if ('output' in b) t.strictEqual(tryASM(a.output), tryASM(b.output), `Inequal ${context}output`)
|
||||||
if ('input' in b) t.strictEqual(tryASM(a.input), tryASM(b.input), `Inequal ${context}input`)
|
if ('input' in b) t.strictEqual(tryASM(a.input), tryASM(b.input), `Inequal ${context}input`)
|
||||||
if ('witness' in b) t.deepEqual(tryHex(a.witness), tryHex(b.witness), `Inequal ${context}witness`)
|
if ('witness' in b) t.deepStrictEqual(tryHex(a.witness), tryHex(b.witness), `Inequal ${context}witness`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function equate (a, b, args) {
|
function equate (a, b, args) {
|
||||||
|
@ -58,25 +58,26 @@ function equate (a, b, args) {
|
||||||
|
|
||||||
equateBase(a, b, '')
|
equateBase(a, b, '')
|
||||||
if (b.redeem) equateBase(a.redeem, b.redeem, 'redeem.')
|
if (b.redeem) equateBase(a.redeem, b.redeem, 'redeem.')
|
||||||
if (b.network) t.deepEqual(a.network, b.network, 'Inequal *.network')
|
if (b.network) t.deepStrictEqual(a.network, BNETWORKS[b.network], 'Inequal *.network')
|
||||||
|
|
||||||
// contextual
|
// contextual
|
||||||
if (b.signature === null) b.signature = undefined
|
if (b.signature === null) b.signature = undefined
|
||||||
|
if (b.signatures === null) b.signatures = undefined
|
||||||
if ('address' in b) t.strictEqual(a.address, b.address, 'Inequal *.address')
|
if ('address' in b) t.strictEqual(a.address, b.address, 'Inequal *.address')
|
||||||
if ('hash' in b) t.strictEqual(tryHex(a.hash), tryHex(b.hash), 'Inequal *.hash')
|
if ('hash' in b) t.strictEqual(tryHex(a.hash), tryHex(b.hash), 'Inequal *.hash')
|
||||||
if ('pubkey' in b) t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey')
|
if ('pubkey' in b) t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey')
|
||||||
if ('signature' in b) t.strictEqual(tryHex(a.signature), tryHex(b.signature), 'Inequal signature')
|
if ('signature' in b) t.strictEqual(tryHex(a.signature), tryHex(b.signature), 'Inequal signature')
|
||||||
if ('m' in b) t.strictEqual(a.m, b.m, 'Inequal *.m')
|
if ('m' in b) t.strictEqual(a.m, b.m, 'Inequal *.m')
|
||||||
if ('n' in b) t.strictEqual(a.n, b.n, 'Inequal *.n')
|
if ('n' in b) t.strictEqual(a.n, b.n, 'Inequal *.n')
|
||||||
if ('pubkeys' in b) t.deepEqual(tryHex(a.pubkeys), tryHex(b.pubkeys), 'Inequal *.pubkeys')
|
if ('pubkeys' in b) t.deepStrictEqual(tryHex(a.pubkeys), tryHex(b.pubkeys), 'Inequal *.pubkeys')
|
||||||
if ('signatures' in b) t.deepEqual(tryHex(a.signatures), tryHex(b.signatures), 'Inequal *.signatures')
|
if ('signatures' in b) t.deepStrictEqual(tryHex(a.signatures), tryHex(b.signatures), 'Inequal *.signatures')
|
||||||
if ('data' in b) t.deepEqual(tryHex(a.data), tryHex(b.data), 'Inequal *.data')
|
if ('data' in b) t.deepStrictEqual(tryHex(a.data), tryHex(b.data), 'Inequal *.data')
|
||||||
}
|
}
|
||||||
|
|
||||||
function preform (x) {
|
function preform (x) {
|
||||||
x = Object.assign({}, x)
|
x = Object.assign({}, x)
|
||||||
|
|
||||||
if (x.network) x.network = bnetworks[x.network]
|
if (x.network) x.network = BNETWORKS[x.network]
|
||||||
if (typeof x.inputHex === 'string') {
|
if (typeof x.inputHex === 'string') {
|
||||||
x.input = Buffer.from(x.inputHex, 'hex')
|
x.input = Buffer.from(x.inputHex, 'hex')
|
||||||
delete x.inputHex
|
delete x.inputHex
|
||||||
|
@ -94,12 +95,13 @@ function preform (x) {
|
||||||
if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex')
|
if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex')
|
||||||
if (x.signature) x.signature = Buffer.from(x.signature, 'hex')
|
if (x.signature) x.signature = Buffer.from(x.signature, 'hex')
|
||||||
if (x.pubkeys) x.pubkeys = x.pubkeys.map(fromHex)
|
if (x.pubkeys) x.pubkeys = x.pubkeys.map(fromHex)
|
||||||
if (x.signatures) x.signatures = x.signatures.map(function (y) { return Number.isFinite(y) ? y : Buffer.from(y, 'hex') })
|
if (x.signatures) x.signatures = x.signatures.map(y => { return Number.isFinite(y) ? y : Buffer.from(y, 'hex') })
|
||||||
if (x.redeem) {
|
if (x.redeem) {
|
||||||
|
x.redeem = Object.assign({}, x.redeem)
|
||||||
if (typeof x.redeem.input === 'string') x.redeem.input = asmToBuffer(x.redeem.input)
|
if (typeof x.redeem.input === 'string') x.redeem.input = asmToBuffer(x.redeem.input)
|
||||||
if (typeof x.redeem.output === 'string') x.redeem.output = asmToBuffer(x.redeem.output)
|
if (typeof x.redeem.output === 'string') x.redeem.output = asmToBuffer(x.redeem.output)
|
||||||
if (Array.isArray(x.redeem.witness)) x.redeem.witness = x.redeem.witness.map(fromHex)
|
if (Array.isArray(x.redeem.witness)) x.redeem.witness = x.redeem.witness.map(fromHex)
|
||||||
if (x.redeem.network) x.redeem.network = bnetworks[x.redeem.network]
|
if (x.redeem.network) x.redeem.network = BNETWORKS[x.redeem.network]
|
||||||
}
|
}
|
||||||
|
|
||||||
return x
|
return x
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bscript = require('../src/script')
|
const bscript = require('../src/script')
|
||||||
const minimalData = require('minimaldata')
|
const minimalData = require('minimaldata')
|
||||||
|
@ -7,44 +6,44 @@ const minimalData = require('minimaldata')
|
||||||
const fixtures = require('./fixtures/script.json')
|
const fixtures = require('./fixtures/script.json')
|
||||||
const fixtures2 = require('./fixtures/templates.json')
|
const fixtures2 = require('./fixtures/templates.json')
|
||||||
|
|
||||||
describe('script', function () {
|
describe('script', () => {
|
||||||
// TODO
|
// TODO
|
||||||
describe('isCanonicalPubKey', function () {
|
describe('isCanonicalPubKey', () => {
|
||||||
it('rejects if not provided a Buffer', function () {
|
it('rejects if not provided a Buffer', () => {
|
||||||
assert.strictEqual(false, bscript.isCanonicalPubKey(0))
|
assert.strictEqual(false, bscript.isCanonicalPubKey(0))
|
||||||
})
|
})
|
||||||
it('rejects smaller than 33', function () {
|
it('rejects smaller than 33', () => {
|
||||||
for (var i = 0; i < 33; i++) {
|
for (var i = 0; i < 33; i++) {
|
||||||
assert.strictEqual(false, bscript.isCanonicalPubKey(Buffer.from('', i)))
|
assert.strictEqual(false, bscript.isCanonicalPubKey(Buffer.from('', i)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe.skip('isCanonicalScriptSignature', function () {
|
describe.skip('isCanonicalScriptSignature', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromASM/toASM', function () {
|
describe('fromASM/toASM', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('encodes/decodes ' + f.asm, function () {
|
it('encodes/decodes ' + f.asm, () => {
|
||||||
const script = bscript.fromASM(f.asm)
|
const script = bscript.fromASM(f.asm)
|
||||||
assert.strictEqual(bscript.toASM(script), f.asm)
|
assert.strictEqual(bscript.toASM(script), f.asm)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.fromASM.forEach(function (f) {
|
fixtures.invalid.fromASM.forEach(f => {
|
||||||
it('throws ' + f.description, function () {
|
it('throws ' + f.description, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
bscript.fromASM(f.script)
|
bscript.fromASM(f.script)
|
||||||
}, new RegExp(f.description))
|
}, new RegExp(f.description))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fromASM/toASM (templates)', function () {
|
describe('fromASM/toASM (templates)', () => {
|
||||||
fixtures2.valid.forEach(function (f) {
|
fixtures2.valid.forEach(f => {
|
||||||
if (f.inputHex) {
|
if (f.inputHex) {
|
||||||
const ih = bscript.toASM(Buffer.from(f.inputHex, 'hex'))
|
const ih = bscript.toASM(Buffer.from(f.inputHex, 'hex'))
|
||||||
|
|
||||||
it('encodes/decodes ' + ih, function () {
|
it('encodes/decodes ' + ih, () => {
|
||||||
const script = bscript.fromASM(f.input)
|
const script = bscript.fromASM(f.input)
|
||||||
assert.strictEqual(script.toString('hex'), f.inputHex)
|
assert.strictEqual(script.toString('hex'), f.inputHex)
|
||||||
assert.strictEqual(bscript.toASM(script), f.input)
|
assert.strictEqual(bscript.toASM(script), f.input)
|
||||||
|
@ -52,7 +51,7 @@ describe('script', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f.outputHex) {
|
if (f.outputHex) {
|
||||||
it('encodes/decodes ' + f.output, function () {
|
it('encodes/decodes ' + f.output, () => {
|
||||||
const script = bscript.fromASM(f.output)
|
const script = bscript.fromASM(f.output)
|
||||||
assert.strictEqual(script.toString('hex'), f.outputHex)
|
assert.strictEqual(script.toString('hex'), f.outputHex)
|
||||||
assert.strictEqual(bscript.toASM(script), f.output)
|
assert.strictEqual(bscript.toASM(script), f.output)
|
||||||
|
@ -61,9 +60,9 @@ describe('script', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('isPushOnly', function () {
|
describe('isPushOnly', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('returns ' + !!f.stack + ' for ' + f.asm, function () {
|
it('returns ' + !!f.stack + ' for ' + f.asm, () => {
|
||||||
const script = bscript.fromASM(f.asm)
|
const script = bscript.fromASM(f.asm)
|
||||||
const chunks = bscript.decompile(script)
|
const chunks = bscript.decompile(script)
|
||||||
|
|
||||||
|
@ -72,26 +71,26 @@ describe('script', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('toStack', function () {
|
describe('toStack', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('returns ' + !!f.stack + ' for ' + f.asm, function () {
|
it('returns ' + !!f.stack + ' for ' + f.asm, () => {
|
||||||
if (!f.stack || !f.asm) return
|
if (!f.stack || !f.asm) return
|
||||||
|
|
||||||
const script = bscript.fromASM(f.asm)
|
const script = bscript.fromASM(f.asm)
|
||||||
|
|
||||||
const stack = bscript.toStack(script)
|
const stack = bscript.toStack(script)
|
||||||
assert.deepEqual(stack.map(function (x) {
|
assert.deepStrictEqual(stack.map(x => {
|
||||||
return x.toString('hex')
|
return x.toString('hex')
|
||||||
}), f.stack)
|
}), f.stack)
|
||||||
|
|
||||||
assert.equal(bscript.toASM(bscript.compile(stack)), f.asm, 'should rebuild same script from stack')
|
assert.strictEqual(bscript.toASM(bscript.compile(stack)), f.asm, 'should rebuild same script from stack')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('compile (via fromASM)', function () {
|
describe('compile (via fromASM)', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('(' + f.type + ') compiles ' + f.asm, function () {
|
it('(' + f.type + ') compiles ' + f.asm, () => {
|
||||||
const scriptSig = bscript.fromASM(f.asm)
|
const scriptSig = bscript.fromASM(f.asm)
|
||||||
|
|
||||||
assert.strictEqual(scriptSig.toString('hex'), f.script)
|
assert.strictEqual(scriptSig.toString('hex'), f.script)
|
||||||
|
@ -105,9 +104,9 @@ describe('script', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('decompile', function () {
|
describe('decompile', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('decompiles ' + f.asm, function () {
|
it('decompiles ' + f.asm, () => {
|
||||||
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'))
|
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'))
|
||||||
|
|
||||||
assert.strictEqual(bscript.compile(chunks).toString('hex'), f.script)
|
assert.strictEqual(bscript.compile(chunks).toString('hex'), f.script)
|
||||||
|
@ -124,8 +123,8 @@ describe('script', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.decompile.forEach(function (f) {
|
fixtures.invalid.decompile.forEach(f => {
|
||||||
it('fails to decompile ' + f.script + ', because "' + f.description + '"', function () {
|
it('fails to decompile ' + f.script + ', because "' + f.description + '"', () => {
|
||||||
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'))
|
const chunks = bscript.decompile(Buffer.from(f.script, 'hex'))
|
||||||
|
|
||||||
assert.strictEqual(chunks, null)
|
assert.strictEqual(chunks, null)
|
||||||
|
@ -133,9 +132,9 @@ describe('script', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('SCRIPT_VERIFY_MINIMALDATA policy', function () {
|
describe('SCRIPT_VERIFY_MINIMALDATA policy', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('compliant for ' + f.type + ' scriptSig ' + f.asm, function () {
|
it('compliant for ' + f.type + ' scriptSig ' + f.asm, () => {
|
||||||
const script = Buffer.from(f.script, 'hex')
|
const script = Buffer.from(f.script, 'hex')
|
||||||
|
|
||||||
assert(minimalData(script))
|
assert(minimalData(script))
|
||||||
|
@ -143,7 +142,7 @@ describe('script', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
function testEncodingForSize (i) {
|
function testEncodingForSize (i) {
|
||||||
it('compliant for data PUSH of length ' + i, function () {
|
it('compliant for data PUSH of length ' + i, () => {
|
||||||
const buffer = Buffer.alloc(i)
|
const buffer = Buffer.alloc(i)
|
||||||
const script = bscript.compile([buffer])
|
const script = bscript.compile([buffer])
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const scriptNumber = require('../src/script_number')
|
const scriptNumber = require('../src/script_number')
|
||||||
const fixtures = require('./fixtures/script_number.json')
|
const fixtures = require('./fixtures/script_number.json')
|
||||||
|
|
||||||
describe('script-number', function () {
|
describe('script-number', () => {
|
||||||
describe('decode', function () {
|
describe('decode', () => {
|
||||||
fixtures.forEach(function (f) {
|
fixtures.forEach(f => {
|
||||||
it(f.hex + ' returns ' + f.number, function () {
|
it(f.hex + ' returns ' + f.number, () => {
|
||||||
const actual = scriptNumber.decode(Buffer.from(f.hex, 'hex'), f.bytes)
|
const actual = scriptNumber.decode(Buffer.from(f.hex, 'hex'), f.bytes)
|
||||||
|
|
||||||
assert.strictEqual(actual, f.number)
|
assert.strictEqual(actual, f.number)
|
||||||
|
@ -15,9 +14,9 @@ describe('script-number', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('encode', function () {
|
describe('encode', () => {
|
||||||
fixtures.forEach(function (f) {
|
fixtures.forEach(f => {
|
||||||
it(f.number + ' returns ' + f.hex, function () {
|
it(f.number + ' returns ' + f.hex, () => {
|
||||||
const actual = scriptNumber.encode(f.number)
|
const actual = scriptNumber.encode(f.number)
|
||||||
|
|
||||||
assert.strictEqual(actual.toString('hex'), f.hex)
|
assert.strictEqual(actual.toString('hex'), f.hex)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bscriptSig = require('../src/script').signature
|
const bscriptSig = require('../src/script').signature
|
||||||
const Buffer = require('safe-buffer').Buffer
|
const Buffer = require('safe-buffer').Buffer
|
||||||
const fixtures = require('./fixtures/signature.json')
|
const fixtures = require('./fixtures/signature.json')
|
||||||
|
|
||||||
describe('Script Signatures', function () {
|
describe('Script Signatures', () => {
|
||||||
function fromRaw (signature) {
|
function fromRaw (signature) {
|
||||||
return Buffer.concat([
|
return Buffer.concat([
|
||||||
Buffer.from(signature.r, 'hex'),
|
Buffer.from(signature.r, 'hex'),
|
||||||
|
@ -20,43 +19,43 @@ describe('Script Signatures', function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('encode', function () {
|
describe('encode', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('encodes ' + f.hex, function () {
|
it('encodes ' + f.hex, () => {
|
||||||
const buffer = bscriptSig.encode(fromRaw(f.raw), f.hashType)
|
const buffer = bscriptSig.encode(fromRaw(f.raw), f.hashType)
|
||||||
|
|
||||||
assert.strictEqual(buffer.toString('hex'), f.hex)
|
assert.strictEqual(buffer.toString('hex'), f.hex)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.forEach(function (f) {
|
fixtures.invalid.forEach(f => {
|
||||||
if (!f.raw) return
|
if (!f.raw) return
|
||||||
|
|
||||||
it('throws ' + f.exception, function () {
|
it('throws ' + f.exception, () => {
|
||||||
const signature = fromRaw(f.raw)
|
const signature = fromRaw(f.raw)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
bscriptSig.encode(signature, f.hashType)
|
bscriptSig.encode(signature, f.hashType)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('decode', function () {
|
describe('decode', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('decodes ' + f.hex, function () {
|
it('decodes ' + f.hex, () => {
|
||||||
const decode = bscriptSig.decode(Buffer.from(f.hex, 'hex'))
|
const decode = bscriptSig.decode(Buffer.from(f.hex, 'hex'))
|
||||||
|
|
||||||
assert.deepEqual(toRaw(decode.signature), f.raw)
|
assert.deepStrictEqual(toRaw(decode.signature), f.raw)
|
||||||
assert.strictEqual(decode.hashType, f.hashType)
|
assert.strictEqual(decode.hashType, f.hashType)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.forEach(function (f) {
|
fixtures.invalid.forEach(f => {
|
||||||
it('throws on ' + f.hex, function () {
|
it('throws on ' + f.hex, () => {
|
||||||
const buffer = Buffer.from(f.hex, 'hex')
|
const buffer = Buffer.from(f.hex, 'hex')
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
bscriptSig.decode(buffer)
|
bscriptSig.decode(buffer)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
/* global describe, it, beforeEach */
|
const { describe, it, beforeEach } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const bscript = require('../src/script')
|
const bscript = require('../src/script')
|
||||||
const fixtures = require('./fixtures/transaction')
|
const fixtures = require('./fixtures/transaction')
|
||||||
const Transaction = require('../src/transaction')
|
const Transaction = require('..').Transaction
|
||||||
|
|
||||||
describe('Transaction', function () {
|
describe('Transaction', () => {
|
||||||
function fromRaw (raw, noWitness) {
|
function fromRaw (raw, noWitness) {
|
||||||
const tx = new Transaction()
|
const tx = new Transaction()
|
||||||
tx.version = raw.version
|
tx.version = raw.version
|
||||||
tx.locktime = raw.locktime
|
tx.locktime = raw.locktime
|
||||||
|
|
||||||
raw.ins.forEach(function (txIn, i) {
|
raw.ins.forEach((txIn, i) => {
|
||||||
const txHash = Buffer.from(txIn.hash, 'hex')
|
const txHash = Buffer.from(txIn.hash, 'hex')
|
||||||
let scriptSig
|
let scriptSig
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ describe('Transaction', function () {
|
||||||
tx.addInput(txHash, txIn.index, txIn.sequence, scriptSig)
|
tx.addInput(txHash, txIn.index, txIn.sequence, scriptSig)
|
||||||
|
|
||||||
if (!noWitness && txIn.witness) {
|
if (!noWitness && txIn.witness) {
|
||||||
const witness = txIn.witness.map(function (x) {
|
const witness = txIn.witness.map(x => {
|
||||||
return Buffer.from(x, 'hex')
|
return Buffer.from(x, 'hex')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ describe('Transaction', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
raw.outs.forEach(function (txOut) {
|
raw.outs.forEach(txOut => {
|
||||||
let script
|
let script
|
||||||
|
|
||||||
if (txOut.data) {
|
if (txOut.data) {
|
||||||
|
@ -47,19 +46,19 @@ describe('Transaction', function () {
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('fromBuffer/fromHex', function () {
|
describe('fromBuffer/fromHex', () => {
|
||||||
function importExport (f) {
|
function importExport (f) {
|
||||||
const id = f.id || f.hash
|
const id = f.id || f.hash
|
||||||
const txHex = f.hex || f.txHex
|
const txHex = f.hex || f.txHex
|
||||||
|
|
||||||
it('imports ' + f.description + ' (' + id + ')', function () {
|
it('imports ' + f.description + ' (' + id + ')', () => {
|
||||||
const actual = Transaction.fromHex(txHex)
|
const actual = Transaction.fromHex(txHex)
|
||||||
|
|
||||||
assert.strictEqual(actual.toHex(), txHex)
|
assert.strictEqual(actual.toHex(), txHex)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (f.whex) {
|
if (f.whex) {
|
||||||
it('imports ' + f.description + ' (' + id + ') as witness', function () {
|
it('imports ' + f.description + ' (' + id + ') as witness', () => {
|
||||||
const actual = Transaction.fromHex(f.whex)
|
const actual = Transaction.fromHex(f.whex)
|
||||||
|
|
||||||
assert.strictEqual(actual.toHex(), f.whex)
|
assert.strictEqual(actual.toHex(), f.whex)
|
||||||
|
@ -71,38 +70,38 @@ describe('Transaction', function () {
|
||||||
fixtures.hashForSignature.forEach(importExport)
|
fixtures.hashForSignature.forEach(importExport)
|
||||||
fixtures.hashForWitnessV0.forEach(importExport)
|
fixtures.hashForWitnessV0.forEach(importExport)
|
||||||
|
|
||||||
fixtures.invalid.fromBuffer.forEach(function (f) {
|
fixtures.invalid.fromBuffer.forEach(f => {
|
||||||
it('throws on ' + f.exception, function () {
|
it('throws on ' + f.exception, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
Transaction.fromHex(f.hex)
|
Transaction.fromHex(f.hex)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('.version should be interpreted as an int32le', function () {
|
it('.version should be interpreted as an int32le', () => {
|
||||||
const txHex = 'ffffffff0000ffffffff'
|
const txHex = 'ffffffff0000ffffffff'
|
||||||
const tx = Transaction.fromHex(txHex)
|
const tx = Transaction.fromHex(txHex)
|
||||||
assert.equal(-1, tx.version)
|
assert.strictEqual(-1, tx.version)
|
||||||
assert.equal(0xffffffff, tx.locktime)
|
assert.strictEqual(0xffffffff, tx.locktime)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('toBuffer/toHex', function () {
|
describe('toBuffer/toHex', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('exports ' + f.description + ' (' + f.id + ')', function () {
|
it('exports ' + f.description + ' (' + f.id + ')', () => {
|
||||||
const actual = fromRaw(f.raw, true)
|
const actual = fromRaw(f.raw, true)
|
||||||
assert.strictEqual(actual.toHex(), f.hex)
|
assert.strictEqual(actual.toHex(), f.hex)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (f.whex) {
|
if (f.whex) {
|
||||||
it('exports ' + f.description + ' (' + f.id + ') as witness', function () {
|
it('exports ' + f.description + ' (' + f.id + ') as witness', () => {
|
||||||
const wactual = fromRaw(f.raw)
|
const wactual = fromRaw(f.raw)
|
||||||
assert.strictEqual(wactual.toHex(), f.whex)
|
assert.strictEqual(wactual.toHex(), f.whex)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts target Buffer and offset parameters', function () {
|
it('accepts target Buffer and offset parameters', () => {
|
||||||
const f = fixtures.valid[0]
|
const f = fixtures.valid[0]
|
||||||
const actual = fromRaw(f.raw)
|
const actual = fromRaw(f.raw)
|
||||||
const byteLength = actual.byteLength()
|
const byteLength = actual.byteLength()
|
||||||
|
@ -115,31 +114,31 @@ describe('Transaction', function () {
|
||||||
assert.strictEqual(b.length, byteLength)
|
assert.strictEqual(b.length, byteLength)
|
||||||
assert.strictEqual(a.toString('hex'), f.hex)
|
assert.strictEqual(a.toString('hex'), f.hex)
|
||||||
assert.strictEqual(b.toString('hex'), f.hex)
|
assert.strictEqual(b.toString('hex'), f.hex)
|
||||||
assert.deepEqual(a, b)
|
assert.deepStrictEqual(a, b)
|
||||||
assert.deepEqual(a, target.slice(0, byteLength))
|
assert.deepStrictEqual(a, target.slice(0, byteLength))
|
||||||
assert.deepEqual(b, target.slice(byteLength))
|
assert.deepStrictEqual(b, target.slice(byteLength))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('hasWitnesses', function () {
|
describe('hasWitnesses', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
it('detects if the transaction has witnesses: ' + (f.whex ? 'true' : 'false'), function () {
|
it('detects if the transaction has witnesses: ' + (f.whex ? 'true' : 'false'), () => {
|
||||||
assert.strictEqual(Transaction.fromHex(f.whex ? f.whex : f.hex).hasWitnesses(), !!f.whex)
|
assert.strictEqual(Transaction.fromHex(f.whex ? f.whex : f.hex).hasWitnesses(), !!f.whex)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('weight/virtualSize', function () {
|
describe('weight/virtualSize', () => {
|
||||||
it('computes virtual size', function () {
|
it('computes virtual size', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex)
|
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex)
|
||||||
|
|
||||||
assert.strictEqual(transaction.virtualSize(), f.virtualSize)
|
assert.strictEqual(transaction.virtualSize(), f.virtualSize)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('computes weight', function () {
|
it('computes weight', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex)
|
const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex)
|
||||||
|
|
||||||
assert.strictEqual(transaction.weight(), f.weight)
|
assert.strictEqual(transaction.weight(), f.weight)
|
||||||
|
@ -147,19 +146,19 @@ describe('Transaction', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addInput', function () {
|
describe('addInput', () => {
|
||||||
let prevTxHash
|
let prevTxHash
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
prevTxHash = Buffer.from('ffffffff00ffff000000000000000000000000000000000000000000101010ff', 'hex')
|
prevTxHash = Buffer.from('ffffffff00ffff000000000000000000000000000000000000000000101010ff', 'hex')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an index', function () {
|
it('returns an index', () => {
|
||||||
const tx = new Transaction()
|
const tx = new Transaction()
|
||||||
assert.strictEqual(tx.addInput(prevTxHash, 0), 0)
|
assert.strictEqual(tx.addInput(prevTxHash, 0), 0)
|
||||||
assert.strictEqual(tx.addInput(prevTxHash, 0), 1)
|
assert.strictEqual(tx.addInput(prevTxHash, 0), 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('defaults to empty script, witness and 0xffffffff SEQUENCE number', function () {
|
it('defaults to empty script, witness and 0xffffffff SEQUENCE number', () => {
|
||||||
const tx = new Transaction()
|
const tx = new Transaction()
|
||||||
tx.addInput(prevTxHash, 0)
|
tx.addInput(prevTxHash, 0)
|
||||||
|
|
||||||
|
@ -168,49 +167,49 @@ describe('Transaction', function () {
|
||||||
assert.strictEqual(tx.ins[0].sequence, 0xffffffff)
|
assert.strictEqual(tx.ins[0].sequence, 0xffffffff)
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.addInput.forEach(function (f) {
|
fixtures.invalid.addInput.forEach(f => {
|
||||||
it('throws on ' + f.exception, function () {
|
it('throws on ' + f.exception, () => {
|
||||||
const tx = new Transaction()
|
const tx = new Transaction()
|
||||||
const hash = Buffer.from(f.hash, 'hex')
|
const hash = Buffer.from(f.hash, 'hex')
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
tx.addInput(hash, f.index)
|
tx.addInput(hash, f.index)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addOutput', function () {
|
describe('addOutput', () => {
|
||||||
it('returns an index', function () {
|
it('returns an index', () => {
|
||||||
const tx = new Transaction()
|
const tx = new Transaction()
|
||||||
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 0)
|
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 0)
|
||||||
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 1)
|
assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('clone', function () {
|
describe('clone', () => {
|
||||||
fixtures.valid.forEach(function (f) {
|
fixtures.valid.forEach(f => {
|
||||||
let actual
|
let actual
|
||||||
let expected
|
let expected
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
expected = Transaction.fromHex(f.hex)
|
expected = Transaction.fromHex(f.hex)
|
||||||
actual = expected.clone()
|
actual = expected.clone()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have value equality', function () {
|
it('should have value equality', () => {
|
||||||
assert.deepEqual(actual, expected)
|
assert.deepStrictEqual(actual, expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not have reference equality', function () {
|
it('should not have reference equality', () => {
|
||||||
assert.notEqual(actual, expected)
|
assert.notStrictEqual(actual, expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getHash/getId', function () {
|
describe('getHash/getId', () => {
|
||||||
function verify (f) {
|
function verify (f) {
|
||||||
it('should return the id for ' + f.id + '(' + f.description + ')', function () {
|
it('should return the id for ' + f.id + '(' + f.description + ')', () => {
|
||||||
const tx = Transaction.fromHex(f.whex || f.hex)
|
const tx = Transaction.fromHex(f.whex || f.hex)
|
||||||
|
|
||||||
assert.strictEqual(tx.getHash().toString('hex'), f.hash)
|
assert.strictEqual(tx.getHash().toString('hex'), f.hash)
|
||||||
|
@ -221,9 +220,9 @@ describe('Transaction', function () {
|
||||||
fixtures.valid.forEach(verify)
|
fixtures.valid.forEach(verify)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('isCoinbase', function () {
|
describe('isCoinbase', () => {
|
||||||
function verify (f) {
|
function verify (f) {
|
||||||
it('should return ' + f.coinbase + ' for ' + f.id + '(' + f.description + ')', function () {
|
it('should return ' + f.coinbase + ' for ' + f.id + '(' + f.description + ')', () => {
|
||||||
const tx = Transaction.fromHex(f.hex)
|
const tx = Transaction.fromHex(f.hex)
|
||||||
|
|
||||||
assert.strictEqual(tx.isCoinbase(), f.coinbase)
|
assert.strictEqual(tx.isCoinbase(), f.coinbase)
|
||||||
|
@ -233,8 +232,8 @@ describe('Transaction', function () {
|
||||||
fixtures.valid.forEach(verify)
|
fixtures.valid.forEach(verify)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('hashForSignature', function () {
|
describe('hashForSignature', () => {
|
||||||
it('does not use Witness serialization', function () {
|
it('does not use Witness serialization', () => {
|
||||||
const randScript = Buffer.from('6a', 'hex')
|
const randScript = Buffer.from('6a', 'hex')
|
||||||
|
|
||||||
const tx = new Transaction()
|
const tx = new Transaction()
|
||||||
|
@ -242,24 +241,24 @@ describe('Transaction', function () {
|
||||||
tx.addOutput(randScript, 5000000000)
|
tx.addOutput(randScript, 5000000000)
|
||||||
|
|
||||||
const original = tx.__toBuffer
|
const original = tx.__toBuffer
|
||||||
tx.__toBuffer = function (a, b, c) {
|
tx.__toBuffer = (a, b, c) => {
|
||||||
if (c !== false) throw new Error('hashForSignature MUST pass false')
|
if (c !== false) throw new Error('hashForSignature MUST pass false')
|
||||||
|
|
||||||
return original.call(this, a, b, c)
|
return original.call(this, a, b, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
tx.__toBuffer(undefined, undefined, true)
|
tx.__toBuffer(undefined, undefined, true)
|
||||||
}, /hashForSignature MUST pass false/)
|
}, /hashForSignature MUST pass false/)
|
||||||
|
|
||||||
// assert hashForSignature does not pass false
|
// assert hashForSignature does not pass false
|
||||||
assert.doesNotThrow(function () {
|
assert.doesNotThrow(() => {
|
||||||
tx.hashForSignature(0, randScript, 1)
|
tx.hashForSignature(0, randScript, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.hashForSignature.forEach(function (f) {
|
fixtures.hashForSignature.forEach(f => {
|
||||||
it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : f.script), function () {
|
it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : f.script), () => {
|
||||||
const tx = Transaction.fromHex(f.txHex)
|
const tx = Transaction.fromHex(f.txHex)
|
||||||
const script = bscript.fromASM(f.script)
|
const script = bscript.fromASM(f.script)
|
||||||
|
|
||||||
|
@ -268,9 +267,9 @@ describe('Transaction', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('hashForWitnessV0', function () {
|
describe('hashForWitnessV0', () => {
|
||||||
fixtures.hashForWitnessV0.forEach(function (f) {
|
fixtures.hashForWitnessV0.forEach(f => {
|
||||||
it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : ''), function () {
|
it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : ''), () => {
|
||||||
const tx = Transaction.fromHex(f.txHex)
|
const tx = Transaction.fromHex(f.txHex)
|
||||||
const script = bscript.fromASM(f.script)
|
const script = bscript.fromASM(f.script)
|
||||||
|
|
||||||
|
@ -279,9 +278,9 @@ describe('Transaction', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('setWitness', function () {
|
describe('setWitness', () => {
|
||||||
it('only accepts a a witness stack (Array of Buffers)', function () {
|
it('only accepts a a witness stack (Array of Buffers)', () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
(new Transaction()).setWitness(0, 'foobar')
|
(new Transaction()).setWitness(0, 'foobar')
|
||||||
}, /Expected property "1" of type \[Buffer], got String "foobar"/)
|
}, /Expected property "1" of type \[Buffer], got String "foobar"/)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,65 +1,23 @@
|
||||||
/* global describe, it, beforeEach */
|
const { describe, it, beforeEach } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const baddress = require('../src/address')
|
const baddress = require('../src/address')
|
||||||
const bcrypto = require('../src/crypto')
|
|
||||||
const bscript = require('../src/script')
|
const bscript = require('../src/script')
|
||||||
const ops = require('bitcoin-ops')
|
|
||||||
const payments = require('../src/payments')
|
const payments = require('../src/payments')
|
||||||
|
|
||||||
const ECPair = require('../src/ecpair')
|
const ECPair = require('../src/ecpair')
|
||||||
const Transaction = require('../src/transaction')
|
const Transaction = require('..').Transaction
|
||||||
const TransactionBuilder = require('../src/transaction_builder')
|
const TransactionBuilder = require('..').TransactionBuilder
|
||||||
const NETWORKS = require('../src/networks')
|
const NETWORKS = require('../src/networks')
|
||||||
|
|
||||||
const fixtures = require('./fixtures/transaction_builder')
|
const fixtures = require('./fixtures/transaction_builder')
|
||||||
|
|
||||||
// TODO: remove
|
function constructSign (f, txb) {
|
||||||
function getAddress (node) {
|
|
||||||
return baddress.toBase58Check(bcrypto.hash160(node.publicKey), NETWORKS.bitcoin.pubKeyHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
function construct (f, dontSign) {
|
|
||||||
const network = NETWORKS[f.network]
|
const network = NETWORKS[f.network]
|
||||||
let txb = new TransactionBuilder(network)
|
|
||||||
|
|
||||||
if (Number.isFinite(f.version)) txb.setVersion(f.version)
|
|
||||||
if (f.locktime !== undefined) txb.setLockTime(f.locktime)
|
|
||||||
|
|
||||||
f.inputs.forEach(function (input) {
|
|
||||||
let prevTx
|
|
||||||
if (input.txRaw) {
|
|
||||||
const constructed = construct(input.txRaw)
|
|
||||||
if (input.txRaw.incomplete) prevTx = constructed.buildIncomplete()
|
|
||||||
else prevTx = constructed.build()
|
|
||||||
} else if (input.txHex) {
|
|
||||||
prevTx = Transaction.fromHex(input.txHex)
|
|
||||||
} else {
|
|
||||||
prevTx = input.txId
|
|
||||||
}
|
|
||||||
|
|
||||||
let prevTxScript
|
|
||||||
if (input.prevTxScript) {
|
|
||||||
prevTxScript = bscript.fromASM(input.prevTxScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
txb.addInput(prevTx, input.vout, input.sequence, prevTxScript)
|
|
||||||
})
|
|
||||||
|
|
||||||
f.outputs.forEach(function (output) {
|
|
||||||
if (output.address) {
|
|
||||||
txb.addOutput(output.address, output.value)
|
|
||||||
} else {
|
|
||||||
txb.addOutput(bscript.fromASM(output.script), output.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (dontSign) return txb
|
|
||||||
|
|
||||||
const stages = f.stages && f.stages.concat()
|
const stages = f.stages && f.stages.concat()
|
||||||
f.inputs.forEach(function (input, index) {
|
|
||||||
|
f.inputs.forEach((input, index) => {
|
||||||
if (!input.signs) return
|
if (!input.signs) return
|
||||||
input.signs.forEach(function (sign) {
|
input.signs.forEach(sign => {
|
||||||
const keyPair = ECPair.fromWIF(sign.keyPair, network)
|
const keyPair = ECPair.fromWIF(sign.keyPair, network)
|
||||||
let redeemScript
|
let redeemScript
|
||||||
let witnessScript
|
let witnessScript
|
||||||
|
@ -90,20 +48,59 @@ function construct (f, dontSign) {
|
||||||
return txb
|
return txb
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('TransactionBuilder', function () {
|
function construct (f, dontSign) {
|
||||||
|
const network = NETWORKS[f.network]
|
||||||
|
const txb = new TransactionBuilder(network)
|
||||||
|
|
||||||
|
if (Number.isFinite(f.version)) txb.setVersion(f.version)
|
||||||
|
if (f.locktime !== undefined) txb.setLockTime(f.locktime)
|
||||||
|
|
||||||
|
f.inputs.forEach(input => {
|
||||||
|
let prevTx
|
||||||
|
if (input.txRaw) {
|
||||||
|
const constructed = construct(input.txRaw)
|
||||||
|
if (input.txRaw.incomplete) prevTx = constructed.buildIncomplete()
|
||||||
|
else prevTx = constructed.build()
|
||||||
|
} else if (input.txHex) {
|
||||||
|
prevTx = Transaction.fromHex(input.txHex)
|
||||||
|
} else {
|
||||||
|
prevTx = input.txId
|
||||||
|
}
|
||||||
|
|
||||||
|
let prevTxScript
|
||||||
|
if (input.prevTxScript) {
|
||||||
|
prevTxScript = bscript.fromASM(input.prevTxScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
txb.addInput(prevTx, input.vout, input.sequence, prevTxScript)
|
||||||
|
})
|
||||||
|
|
||||||
|
f.outputs.forEach(output => {
|
||||||
|
if (output.address) {
|
||||||
|
txb.addOutput(output.address, output.value)
|
||||||
|
} else {
|
||||||
|
txb.addOutput(bscript.fromASM(output.script), output.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dontSign) return txb
|
||||||
|
return constructSign(f, txb)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TransactionBuilder', () => {
|
||||||
// constants
|
// constants
|
||||||
const keyPair = ECPair.fromPrivateKey(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'))
|
const keyPair = ECPair.fromPrivateKey(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'))
|
||||||
const scripts = [
|
const scripts = [
|
||||||
'1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH',
|
'1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH',
|
||||||
'1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP'
|
'1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP'
|
||||||
].map(function (x) {
|
].map(x => {
|
||||||
return baddress.toOutputScript(x)
|
return baddress.toOutputScript(x)
|
||||||
})
|
})
|
||||||
const txHash = Buffer.from('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex')
|
const txHash = Buffer.from('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex')
|
||||||
|
|
||||||
describe('fromTransaction', function () {
|
describe('fromTransaction', () => {
|
||||||
fixtures.valid.build.forEach(function (f) {
|
fixtures.valid.build.forEach(f => {
|
||||||
it('returns TransactionBuilder, with ' + f.description, function () {
|
it('returns TransactionBuilder, with ' + f.description, () => {
|
||||||
const network = NETWORKS[f.network || 'bitcoin']
|
const network = NETWORKS[f.network || 'bitcoin']
|
||||||
|
|
||||||
const tx = Transaction.fromHex(f.txHex)
|
const tx = Transaction.fromHex(f.txHex)
|
||||||
|
@ -115,83 +112,104 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.valid.fromTransaction.forEach(function (f) {
|
fixtures.valid.fromTransaction.forEach(f => {
|
||||||
it('returns TransactionBuilder, with ' + f.description, function () {
|
it('returns TransactionBuilder, with ' + f.description, () => {
|
||||||
const tx = new Transaction()
|
const tx = new Transaction()
|
||||||
|
|
||||||
f.inputs.forEach(function (input) {
|
f.inputs.forEach(input => {
|
||||||
const txHash2 = Buffer.from(input.txId, 'hex').reverse()
|
const txHash2 = Buffer.from(input.txId, 'hex').reverse()
|
||||||
|
|
||||||
tx.addInput(txHash2, input.vout, undefined, bscript.fromASM(input.scriptSig))
|
tx.addInput(txHash2, input.vout, undefined, bscript.fromASM(input.scriptSig))
|
||||||
})
|
})
|
||||||
|
|
||||||
f.outputs.forEach(function (output) {
|
f.outputs.forEach(output => {
|
||||||
tx.addOutput(bscript.fromASM(output.script), output.value)
|
tx.addOutput(bscript.fromASM(output.script), output.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
const txb = TransactionBuilder.fromTransaction(tx)
|
const txb = TransactionBuilder.fromTransaction(tx)
|
||||||
const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build()
|
const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build()
|
||||||
|
|
||||||
txAfter.ins.forEach(function (input, i) {
|
txAfter.ins.forEach((input, i) => {
|
||||||
assert.equal(bscript.toASM(input.script), f.inputs[i].scriptSigAfter)
|
assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter)
|
||||||
})
|
})
|
||||||
|
|
||||||
txAfter.outs.forEach(function (output, i) {
|
txAfter.outs.forEach((output, i) => {
|
||||||
assert.equal(bscript.toASM(output.script), f.outputs[i].script)
|
assert.strictEqual(bscript.toASM(output.script), f.outputs[i].script)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('classifies transaction inputs', function () {
|
fixtures.valid.fromTransactionSequential.forEach(f => {
|
||||||
|
it('with ' + f.description, () => {
|
||||||
|
const network = NETWORKS[f.network]
|
||||||
|
const tx = Transaction.fromHex(f.txHex)
|
||||||
|
const txb = TransactionBuilder.fromTransaction(tx, network)
|
||||||
|
|
||||||
|
tx.ins.forEach((input, i) => {
|
||||||
|
assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSig)
|
||||||
|
})
|
||||||
|
|
||||||
|
constructSign(f, txb)
|
||||||
|
const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build()
|
||||||
|
|
||||||
|
txAfter.ins.forEach((input, i) => {
|
||||||
|
assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.strictEqual(txAfter.toHex(), f.txHexAfter)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('classifies transaction inputs', () => {
|
||||||
const tx = Transaction.fromHex(fixtures.valid.classification.hex)
|
const tx = Transaction.fromHex(fixtures.valid.classification.hex)
|
||||||
const txb = TransactionBuilder.fromTransaction(tx)
|
const txb = TransactionBuilder.fromTransaction(tx)
|
||||||
|
|
||||||
txb.__inputs.forEach(function (i) {
|
txb.__INPUTS.forEach(i => {
|
||||||
assert.strictEqual(i.prevOutType, 'scripthash')
|
assert.strictEqual(i.prevOutType, 'scripthash')
|
||||||
assert.strictEqual(i.redeemScriptType, 'multisig')
|
assert.strictEqual(i.redeemScriptType, 'multisig')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.fromTransaction.forEach(function (f) {
|
fixtures.invalid.fromTransaction.forEach(f => {
|
||||||
it('throws ' + f.exception, function () {
|
it('throws ' + f.exception, () => {
|
||||||
const tx = Transaction.fromHex(f.txHex)
|
const tx = Transaction.fromHex(f.txHex)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
TransactionBuilder.fromTransaction(tx)
|
TransactionBuilder.fromTransaction(tx)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addInput', function () {
|
describe('addInput', () => {
|
||||||
let txb
|
let txb
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
txb = new TransactionBuilder()
|
txb = new TransactionBuilder()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts a txHash, index [and sequence number]', function () {
|
it('accepts a txHash, index [and sequence number]', () => {
|
||||||
const vin = txb.addInput(txHash, 1, 54)
|
const vin = txb.addInput(txHash, 1, 54)
|
||||||
assert.strictEqual(vin, 0)
|
assert.strictEqual(vin, 0)
|
||||||
|
|
||||||
const txIn = txb.__tx.ins[0]
|
const txIn = txb.__TX.ins[0]
|
||||||
assert.strictEqual(txIn.hash, txHash)
|
assert.strictEqual(txIn.hash, txHash)
|
||||||
assert.strictEqual(txIn.index, 1)
|
assert.strictEqual(txIn.index, 1)
|
||||||
assert.strictEqual(txIn.sequence, 54)
|
assert.strictEqual(txIn.sequence, 54)
|
||||||
assert.strictEqual(txb.__inputs[0].prevOutScript, undefined)
|
assert.strictEqual(txb.__INPUTS[0].prevOutScript, undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts a txHash, index [, sequence number and scriptPubKey]', function () {
|
it('accepts a txHash, index [, sequence number and scriptPubKey]', () => {
|
||||||
const vin = txb.addInput(txHash, 1, 54, scripts[1])
|
const vin = txb.addInput(txHash, 1, 54, scripts[1])
|
||||||
assert.strictEqual(vin, 0)
|
assert.strictEqual(vin, 0)
|
||||||
|
|
||||||
const txIn = txb.__tx.ins[0]
|
const txIn = txb.__TX.ins[0]
|
||||||
assert.strictEqual(txIn.hash, txHash)
|
assert.strictEqual(txIn.hash, txHash)
|
||||||
assert.strictEqual(txIn.index, 1)
|
assert.strictEqual(txIn.index, 1)
|
||||||
assert.strictEqual(txIn.sequence, 54)
|
assert.strictEqual(txIn.sequence, 54)
|
||||||
assert.strictEqual(txb.__inputs[0].prevOutScript, scripts[1])
|
assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts a prevTx, index [and sequence number]', function () {
|
it('accepts a prevTx, index [and sequence number]', () => {
|
||||||
const prevTx = new Transaction()
|
const prevTx = new Transaction()
|
||||||
prevTx.addOutput(scripts[0], 0)
|
prevTx.addOutput(scripts[0], 0)
|
||||||
prevTx.addOutput(scripts[1], 1)
|
prevTx.addOutput(scripts[1], 1)
|
||||||
|
@ -199,115 +217,117 @@ describe('TransactionBuilder', function () {
|
||||||
const vin = txb.addInput(prevTx, 1, 54)
|
const vin = txb.addInput(prevTx, 1, 54)
|
||||||
assert.strictEqual(vin, 0)
|
assert.strictEqual(vin, 0)
|
||||||
|
|
||||||
const txIn = txb.__tx.ins[0]
|
const txIn = txb.__TX.ins[0]
|
||||||
assert.deepEqual(txIn.hash, prevTx.getHash())
|
assert.deepStrictEqual(txIn.hash, prevTx.getHash())
|
||||||
assert.strictEqual(txIn.index, 1)
|
assert.strictEqual(txIn.index, 1)
|
||||||
assert.strictEqual(txIn.sequence, 54)
|
assert.strictEqual(txIn.sequence, 54)
|
||||||
assert.strictEqual(txb.__inputs[0].prevOutScript, scripts[1])
|
assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns the input index', function () {
|
it('returns the input index', () => {
|
||||||
assert.strictEqual(txb.addInput(txHash, 0), 0)
|
assert.strictEqual(txb.addInput(txHash, 0), 0)
|
||||||
assert.strictEqual(txb.addInput(txHash, 1), 1)
|
assert.strictEqual(txb.addInput(txHash, 1), 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function () {
|
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => {
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
|
txb.addOutput(scripts[0], 1000)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
}, /No, this would invalidate signatures/)
|
}, /No, this would invalidate signatures/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addOutput', function () {
|
describe('addOutput', () => {
|
||||||
let txb
|
let txb
|
||||||
beforeEach(function () {
|
beforeEach(() => {
|
||||||
txb = new TransactionBuilder()
|
txb = new TransactionBuilder()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts an address string and value', function () {
|
it('accepts an address string and value', () => {
|
||||||
const address = getAddress(keyPair)
|
const { address } = payments.p2pkh({ pubkey: keyPair.publicKey })
|
||||||
const vout = txb.addOutput(address, 1000)
|
const vout = txb.addOutput(address, 1000)
|
||||||
assert.strictEqual(vout, 0)
|
assert.strictEqual(vout, 0)
|
||||||
|
|
||||||
const txout = txb.__tx.outs[0]
|
const txout = txb.__TX.outs[0]
|
||||||
assert.deepEqual(txout.script, scripts[0])
|
assert.deepStrictEqual(txout.script, scripts[0])
|
||||||
assert.strictEqual(txout.value, 1000)
|
assert.strictEqual(txout.value, 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('accepts a ScriptPubKey and value', function () {
|
it('accepts a ScriptPubKey and value', () => {
|
||||||
const vout = txb.addOutput(scripts[0], 1000)
|
const vout = txb.addOutput(scripts[0], 1000)
|
||||||
assert.strictEqual(vout, 0)
|
assert.strictEqual(vout, 0)
|
||||||
|
|
||||||
const txout = txb.__tx.outs[0]
|
const txout = txb.__TX.outs[0]
|
||||||
assert.deepEqual(txout.script, scripts[0])
|
assert.deepStrictEqual(txout.script, scripts[0])
|
||||||
assert.strictEqual(txout.value, 1000)
|
assert.strictEqual(txout.value, 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws if address is of the wrong network', function () {
|
it('throws if address is of the wrong network', () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
txb.addOutput('2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9', 1000)
|
txb.addOutput('2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9', 1000)
|
||||||
}, /2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9 has no matching Script/)
|
}, /2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9 has no matching Script/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('add second output after signed first input with SIGHASH_NONE', function () {
|
it('add second output after signed first input with SIGHASH_NONE', () => {
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
txb.addOutput(scripts[0], 2000)
|
txb.addOutput(scripts[0], 2000)
|
||||||
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE)
|
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE)
|
||||||
assert.equal(txb.addOutput(scripts[1], 9000), 1)
|
assert.strictEqual(txb.addOutput(scripts[1], 9000), 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('add first output after signed first input with SIGHASH_NONE', function () {
|
it('add first output after signed first input with SIGHASH_NONE', () => {
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE)
|
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_NONE)
|
||||||
assert.equal(txb.addOutput(scripts[0], 2000), 0)
|
assert.strictEqual(txb.addOutput(scripts[0], 2000), 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('add second output after signed first input with SIGHASH_SINGLE', function () {
|
it('add second output after signed first input with SIGHASH_SINGLE', () => {
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
txb.addOutput(scripts[0], 2000)
|
txb.addOutput(scripts[0], 2000)
|
||||||
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE)
|
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE)
|
||||||
assert.equal(txb.addOutput(scripts[1], 9000), 1)
|
assert.strictEqual(txb.addOutput(scripts[1], 9000), 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('add first output after signed first input with SIGHASH_SINGLE', function () {
|
it('add first output after signed first input with SIGHASH_SINGLE', () => {
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE)
|
txb.sign(0, keyPair, undefined, Transaction.SIGHASH_SINGLE)
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
txb.addOutput(scripts[0], 2000)
|
txb.addOutput(scripts[0], 2000)
|
||||||
}, /No, this would invalidate signatures/)
|
}, /No, this would invalidate signatures/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function () {
|
it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => {
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
txb.addOutput(scripts[0], 2000)
|
txb.addOutput(scripts[0], 2000)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
txb.addOutput(scripts[1], 9000)
|
txb.addOutput(scripts[1], 9000)
|
||||||
}, /No, this would invalidate signatures/)
|
}, /No, this would invalidate signatures/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('setLockTime', function () {
|
describe('setLockTime', () => {
|
||||||
it('throws if if there exist any scriptSigs', function () {
|
it('throws if if there exist any scriptSigs', () => {
|
||||||
const txb = new TransactionBuilder()
|
const txb = new TransactionBuilder()
|
||||||
txb.addInput(txHash, 0)
|
txb.addInput(txHash, 0)
|
||||||
|
txb.addOutput(scripts[0], 100)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
txb.setLockTime(65535)
|
txb.setLockTime(65535)
|
||||||
}, /No, this would invalidate signatures/)
|
}, /No, this would invalidate signatures/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sign', function () {
|
describe('sign', () => {
|
||||||
it('supports the alternative abstract interface { publicKey, sign }', function () {
|
it('supports the alternative abstract interface { publicKey, sign }', () => {
|
||||||
const keyPair = {
|
const keyPair = {
|
||||||
publicKey: ECPair.makeRandom({ rng: function () { return Buffer.alloc(32, 1) } }).publicKey,
|
publicKey: ECPair.makeRandom({ rng: () => { return Buffer.alloc(32, 1) } }).publicKey,
|
||||||
sign: function (hash) { return Buffer.alloc(64, 0x5f) }
|
sign: hash => { return Buffer.alloc(64, 0x5f) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const txb = new TransactionBuilder()
|
const txb = new TransactionBuilder()
|
||||||
|
@ -315,15 +335,35 @@ describe('TransactionBuilder', function () {
|
||||||
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
|
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
|
||||||
txb.addOutput('1111111111111111111114oLvT2', 100000)
|
txb.addOutput('1111111111111111111114oLvT2', 100000)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
assert.equal(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
|
assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
|
||||||
})
|
})
|
||||||
|
|
||||||
fixtures.invalid.sign.forEach(function (f) {
|
it('supports low R signature signing', () => {
|
||||||
it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), function () {
|
let txb = new TransactionBuilder()
|
||||||
|
txb.setVersion(1)
|
||||||
|
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
|
||||||
|
txb.addOutput('1111111111111111111114oLvT2', 100000)
|
||||||
|
txb.sign(0, keyPair)
|
||||||
|
// high R
|
||||||
|
assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006b483045022100b872677f35c9c14ad9c41d83649fb049250f32574e0b2547d67e209ed14ff05d022059b36ad058be54e887a1a311d5c393cb4941f6b93a0b090845ec67094de8972b01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
|
||||||
|
|
||||||
|
txb = new TransactionBuilder()
|
||||||
|
txb.setVersion(1)
|
||||||
|
txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1)
|
||||||
|
txb.addOutput('1111111111111111111114oLvT2', 100000)
|
||||||
|
txb.setLowR()
|
||||||
|
txb.sign(0, keyPair)
|
||||||
|
// low R
|
||||||
|
assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000')
|
||||||
|
})
|
||||||
|
|
||||||
|
fixtures.invalid.sign.forEach(f => {
|
||||||
|
it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => {
|
||||||
const txb = construct(f, true)
|
const txb = construct(f, true)
|
||||||
|
|
||||||
f.inputs.forEach(function (input, index) {
|
let threw = false
|
||||||
input.signs.forEach(function (sign) {
|
f.inputs.forEach((input, index) => {
|
||||||
|
input.signs.forEach(sign => {
|
||||||
const keyPairNetwork = NETWORKS[sign.network || f.network]
|
const keyPairNetwork = NETWORKS[sign.network || f.network]
|
||||||
const keyPair2 = ECPair.fromWIF(sign.keyPair, keyPairNetwork)
|
const keyPair2 = ECPair.fromWIF(sign.keyPair, keyPairNetwork)
|
||||||
let redeemScript
|
let redeemScript
|
||||||
|
@ -337,22 +377,25 @@ describe('TransactionBuilder', function () {
|
||||||
witnessScript = bscript.fromASM(sign.witnessScript)
|
witnessScript = bscript.fromASM(sign.witnessScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sign.throws) {
|
if (sign.throws) {
|
||||||
txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript)
|
assert.throws(() => {
|
||||||
} else {
|
|
||||||
assert.throws(function () {
|
|
||||||
txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript)
|
txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript)
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
|
threw = true
|
||||||
|
} else {
|
||||||
|
txb.sign(index, keyPair2, redeemScript, sign.hashType, sign.value, witnessScript)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
assert.strictEqual(threw, true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('build', function () {
|
describe('build', () => {
|
||||||
fixtures.valid.build.forEach(function (f) {
|
fixtures.valid.build.forEach(f => {
|
||||||
it('builds "' + f.description + '"', function () {
|
it('builds "' + f.description + '"', () => {
|
||||||
const txb = construct(f)
|
const txb = construct(f)
|
||||||
const tx = f.incomplete ? txb.buildIncomplete() : txb.build()
|
const tx = f.incomplete ? txb.buildIncomplete() : txb.build()
|
||||||
|
|
||||||
|
@ -361,10 +404,10 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: remove duplicate test code
|
// TODO: remove duplicate test code
|
||||||
fixtures.invalid.build.forEach(function (f) {
|
fixtures.invalid.build.forEach(f => {
|
||||||
describe('for ' + (f.description || f.exception), function () {
|
describe('for ' + (f.description || f.exception), () => {
|
||||||
it('throws ' + f.exception, function () {
|
it('throws ' + f.exception, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
let txb
|
let txb
|
||||||
if (f.txHex) {
|
if (f.txHex) {
|
||||||
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
||||||
|
@ -378,8 +421,8 @@ describe('TransactionBuilder', function () {
|
||||||
|
|
||||||
// if throws on incomplete too, enforce that
|
// if throws on incomplete too, enforce that
|
||||||
if (f.incomplete) {
|
if (f.incomplete) {
|
||||||
it('throws ' + f.exception, function () {
|
it('throws ' + f.exception, () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
let txb
|
let txb
|
||||||
if (f.txHex) {
|
if (f.txHex) {
|
||||||
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
||||||
|
@ -391,7 +434,7 @@ describe('TransactionBuilder', function () {
|
||||||
}, new RegExp(f.exception))
|
}, new RegExp(f.exception))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
it('does not throw if buildIncomplete', function () {
|
it('does not throw if buildIncomplete', () => {
|
||||||
let txb
|
let txb
|
||||||
if (f.txHex) {
|
if (f.txHex) {
|
||||||
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
|
||||||
|
@ -405,7 +448,7 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('for incomplete with 0 signatures', function () {
|
it('for incomplete with 0 signatures', () => {
|
||||||
const randomTxData = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'
|
const randomTxData = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000'
|
||||||
const randomAddress = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH'
|
const randomAddress = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH'
|
||||||
|
|
||||||
|
@ -417,7 +460,7 @@ describe('TransactionBuilder', function () {
|
||||||
assert(tx)
|
assert(tx)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('for incomplete P2SH with 0 signatures', function () {
|
it('for incomplete P2SH with 0 signatures', () => {
|
||||||
const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000017a91471a8ec07ff69c6c4fee489184c462a9b1b9237488700000000', 'hex') // arbitrary P2SH input
|
const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000017a91471a8ec07ff69c6c4fee489184c462a9b1b9237488700000000', 'hex') // arbitrary P2SH input
|
||||||
const inpTx = Transaction.fromBuffer(inp)
|
const inpTx = Transaction.fromBuffer(inp)
|
||||||
|
|
||||||
|
@ -428,7 +471,7 @@ describe('TransactionBuilder', function () {
|
||||||
txb.buildIncomplete()
|
txb.buildIncomplete()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('for incomplete P2WPKH with 0 signatures', function () {
|
it('for incomplete P2WPKH with 0 signatures', () => {
|
||||||
const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a8040000001600141a15805e1f4040c9f68ccc887fca2e63547d794b00000000', 'hex')
|
const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a8040000001600141a15805e1f4040c9f68ccc887fca2e63547d794b00000000', 'hex')
|
||||||
const inpTx = Transaction.fromBuffer(inp)
|
const inpTx = Transaction.fromBuffer(inp)
|
||||||
|
|
||||||
|
@ -439,7 +482,7 @@ describe('TransactionBuilder', function () {
|
||||||
txb.buildIncomplete()
|
txb.buildIncomplete()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('for incomplete P2WSH with 0 signatures', function () {
|
it('for incomplete P2WSH with 0 signatures', () => {
|
||||||
const inpTx = Transaction.fromBuffer(Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000022002072df76fcc0b231b94bdf7d8c25d7eef4716597818d211e19ade7813bff7a250200000000', 'hex'))
|
const inpTx = Transaction.fromBuffer(Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000022002072df76fcc0b231b94bdf7d8c25d7eef4716597818d211e19ade7813bff7a250200000000', 'hex'))
|
||||||
|
|
||||||
const txb = new TransactionBuilder(NETWORKS.testnet)
|
const txb = new TransactionBuilder(NETWORKS.testnet)
|
||||||
|
@ -450,40 +493,25 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('multisig', function () {
|
describe('multisig', () => {
|
||||||
fixtures.valid.multisig.forEach(function (f) {
|
fixtures.valid.multisig.forEach(f => {
|
||||||
it(f.description, function () {
|
it(f.description, () => {
|
||||||
const network = NETWORKS[f.network]
|
const network = NETWORKS[f.network]
|
||||||
let txb = construct(f, true)
|
let txb = construct(f, true)
|
||||||
let tx
|
let tx
|
||||||
|
|
||||||
f.inputs.forEach(function (input, i) {
|
f.inputs.forEach((input, i) => {
|
||||||
const redeemScript = bscript.fromASM(input.redeemScript)
|
const redeemScript = bscript.fromASM(input.redeemScript)
|
||||||
|
|
||||||
input.signs.forEach(function (sign) {
|
input.signs.forEach(sign => {
|
||||||
// rebuild the transaction each-time after the first
|
// rebuild the transaction each-time after the first
|
||||||
if (tx) {
|
if (tx) {
|
||||||
// do we filter OP_0's beforehand?
|
// manually override the scriptSig?
|
||||||
if (sign.filterOP_0) {
|
if (sign.scriptSigBefore) {
|
||||||
const scriptSig = tx.ins[i].script
|
tx.ins[i].script = bscript.fromASM(sign.scriptSigBefore)
|
||||||
|
|
||||||
// ignore OP_0 on the front, ignore redeemScript
|
|
||||||
const signatures = bscript.decompile(scriptSig)
|
|
||||||
.slice(1, -1)
|
|
||||||
.filter(x => x !== ops.OP_0)
|
|
||||||
|
|
||||||
// rebuild/replace the scriptSig without them
|
|
||||||
const replacement = payments.p2sh({
|
|
||||||
redeem: payments.p2ms({
|
|
||||||
output: redeemScript,
|
|
||||||
signatures
|
|
||||||
}, { allowIncomplete: true })
|
|
||||||
}).input
|
|
||||||
assert.strictEqual(bscript.toASM(replacement), sign.scriptSigFiltered)
|
|
||||||
|
|
||||||
tx.ins[i].script = replacement
|
|
||||||
}
|
}
|
||||||
// now import it
|
|
||||||
|
// rebuild
|
||||||
txb = TransactionBuilder.fromTransaction(tx, network)
|
txb = TransactionBuilder.fromTransaction(tx, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,6 +520,7 @@ describe('TransactionBuilder', function () {
|
||||||
|
|
||||||
// update the tx
|
// update the tx
|
||||||
tx = txb.buildIncomplete()
|
tx = txb.buildIncomplete()
|
||||||
|
|
||||||
// now verify the serialized scriptSig is as expected
|
// now verify the serialized scriptSig is as expected
|
||||||
assert.strictEqual(bscript.toASM(tx.ins[i].script), sign.scriptSig)
|
assert.strictEqual(bscript.toASM(tx.ins[i].script), sign.scriptSig)
|
||||||
})
|
})
|
||||||
|
@ -503,10 +532,10 @@ describe('TransactionBuilder', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('various edge case', function () {
|
describe('various edge case', () => {
|
||||||
const network = NETWORKS.testnet
|
const network = NETWORKS.testnet
|
||||||
|
|
||||||
it('should warn of high fee for segwit transaction based on VSize, not Size', function () {
|
it('should warn of high fee for segwit transaction based on VSize, not Size', () => {
|
||||||
const rawtx = '01000000000104fdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a' +
|
const rawtx = '01000000000104fdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a' +
|
||||||
'1df90000000000fffffffffdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a1df9' +
|
'1df90000000000fffffffffdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a1df9' +
|
||||||
'0100000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca40000' +
|
'0100000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca40000' +
|
||||||
|
@ -523,17 +552,17 @@ describe('TransactionBuilder', function () {
|
||||||
'194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' +
|
'194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' +
|
||||||
'0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000'
|
'0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000'
|
||||||
const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx))
|
const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx))
|
||||||
txb.__inputs[0].value = 241530
|
txb.__INPUTS[0].value = 241530
|
||||||
txb.__inputs[1].value = 241530
|
txb.__INPUTS[1].value = 241530
|
||||||
txb.__inputs[2].value = 248920
|
txb.__INPUTS[2].value = 248920
|
||||||
txb.__inputs[3].value = 248920
|
txb.__INPUTS[3].value = 248920
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
txb.build()
|
txb.build()
|
||||||
}, new RegExp('Transaction has absurd fees'))
|
}, new RegExp('Transaction has absurd fees'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should classify witness inputs with witness = true during multisigning', function () {
|
it('should classify witness inputs with witness = true during multisigning', () => {
|
||||||
const keyPair = ECPair.fromWIF('cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS', network)
|
const keyPair = ECPair.fromWIF('cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS', network)
|
||||||
const witnessScript = Buffer.from('522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae', 'hex')
|
const witnessScript = Buffer.from('522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae', 'hex')
|
||||||
const redeemScript = Buffer.from('002024376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af', 'hex')
|
const redeemScript = Buffer.from('002024376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af', 'hex')
|
||||||
|
@ -548,13 +577,13 @@ describe('TransactionBuilder', function () {
|
||||||
const tx = txb.buildIncomplete()
|
const tx = txb.buildIncomplete()
|
||||||
|
|
||||||
// Only input is segwit, so txid should be accurate with the final tx
|
// Only input is segwit, so txid should be accurate with the final tx
|
||||||
assert.equal(tx.getId(), 'f15d0a65b21b4471405b21a099f8b18e1ae4d46d55efbd0f4766cf11ad6cb821')
|
assert.strictEqual(tx.getId(), 'f15d0a65b21b4471405b21a099f8b18e1ae4d46d55efbd0f4766cf11ad6cb821')
|
||||||
|
|
||||||
const txHex = tx.toHex()
|
const txHex = tx.toHex()
|
||||||
TransactionBuilder.fromTransaction(Transaction.fromHex(txHex))
|
TransactionBuilder.fromTransaction(Transaction.fromHex(txHex))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle badly pre-filled OP_0s', function () {
|
it('should handle badly pre-filled OP_0s', () => {
|
||||||
// OP_0 is used where a signature is missing
|
// OP_0 is used where a signature is missing
|
||||||
const redeemScripSig = bscript.fromASM('OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
|
const redeemScripSig = bscript.fromASM('OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
|
||||||
const redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG')
|
const redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG')
|
||||||
|
@ -570,11 +599,11 @@ describe('TransactionBuilder', function () {
|
||||||
txb.sign(0, keyPair2, redeemScript)
|
txb.sign(0, keyPair2, redeemScript)
|
||||||
|
|
||||||
const tx2 = txb.build()
|
const tx2 = txb.build()
|
||||||
assert.equal(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9')
|
assert.strictEqual(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9')
|
||||||
assert.equal(bscript.toASM(tx2.ins[0].script), 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
|
assert.strictEqual(bscript.toASM(tx2.ins[0].script), 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not classify blank scripts as nonstandard', function () {
|
it('should not classify blank scripts as nonstandard', () => {
|
||||||
let txb = new TransactionBuilder()
|
let txb = new TransactionBuilder()
|
||||||
txb.setVersion(1)
|
txb.setVersion(1)
|
||||||
txb.addInput('aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31', 0)
|
txb.addInput('aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31', 0)
|
||||||
|
@ -586,14 +615,14 @@ describe('TransactionBuilder', function () {
|
||||||
txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
|
txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
const txId = txb.build().getId()
|
const txId = txb.build().getId()
|
||||||
assert.equal(txId, '54f097315acbaedb92a95455da3368eb45981cdae5ffbc387a9afc872c0f29b3')
|
assert.strictEqual(txId, '54f097315acbaedb92a95455da3368eb45981cdae5ffbc387a9afc872c0f29b3')
|
||||||
|
|
||||||
// and, repeat
|
// and, repeat
|
||||||
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(incomplete))
|
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(incomplete))
|
||||||
txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
|
txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
|
||||||
txb.sign(0, keyPair)
|
txb.sign(0, keyPair)
|
||||||
const txId2 = txb.build().getId()
|
const txId2 = txb.build().getId()
|
||||||
assert.equal(txId, txId2)
|
assert.strictEqual(txId, txId2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
/* global describe, it */
|
const { describe, it } = require('mocha')
|
||||||
|
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const types = require('../src/types')
|
const types = require('../src/types')
|
||||||
const typeforce = require('typeforce')
|
const typeforce = require('typeforce')
|
||||||
|
|
||||||
describe('types', function () {
|
describe('types', () => {
|
||||||
describe('Buffer Hash160/Hash256', function () {
|
describe('Buffer Hash160/Hash256', () => {
|
||||||
const buffer20byte = Buffer.alloc(20)
|
const buffer20byte = Buffer.alloc(20)
|
||||||
const buffer32byte = Buffer.alloc(32)
|
const buffer32byte = Buffer.alloc(32)
|
||||||
|
|
||||||
it('return true for valid size', function () {
|
it('return true for valid size', () => {
|
||||||
assert(types.Hash160bit(buffer20byte))
|
assert(types.Hash160bit(buffer20byte))
|
||||||
assert(types.Hash256bit(buffer32byte))
|
assert(types.Hash256bit(buffer32byte))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('return true for oneOf', function () {
|
it('return true for oneOf', () => {
|
||||||
assert.doesNotThrow(function () {
|
assert.doesNotThrow(() => {
|
||||||
typeforce(types.oneOf(types.Hash160bit, types.Hash256bit), buffer32byte)
|
typeforce(types.oneOf(types.Hash160bit, types.Hash256bit), buffer32byte)
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.doesNotThrow(function () {
|
assert.doesNotThrow(() => {
|
||||||
typeforce(types.oneOf(types.Hash256bit, types.Hash160bit), buffer32byte)
|
typeforce(types.oneOf(types.Hash256bit, types.Hash160bit), buffer32byte)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws for invalid size', function () {
|
it('throws for invalid size', () => {
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
types.Hash160bit(buffer32byte)
|
types.Hash160bit(buffer32byte)
|
||||||
}, /Expected Buffer\(Length: 20\), got Buffer\(Length: 32\)/)
|
}, /Expected Buffer\(Length: 20\), got Buffer\(Length: 32\)/)
|
||||||
|
|
||||||
assert.throws(function () {
|
assert.throws(() => {
|
||||||
types.Hash256bit(buffer20byte)
|
types.Hash256bit(buffer20byte)
|
||||||
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 20\)/)
|
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 20\)/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Satoshi', function () {
|
describe('Satoshi', () => {
|
||||||
[
|
[
|
||||||
{ value: -1, result: false },
|
{ value: -1, result: false },
|
||||||
{ value: 0, result: true },
|
{ value: 0, result: true },
|
||||||
|
@ -43,8 +42,8 @@ describe('types', function () {
|
||||||
{ value: 20999999 * 1e8, result: true },
|
{ value: 20999999 * 1e8, result: true },
|
||||||
{ value: 21000000 * 1e8, result: true },
|
{ value: 21000000 * 1e8, result: true },
|
||||||
{ value: 21000001 * 1e8, result: false }
|
{ value: 21000001 * 1e8, result: false }
|
||||||
].forEach(function (f) {
|
].forEach(f => {
|
||||||
it('returns ' + f.result + ' for valid for ' + f.value, function () {
|
it('returns ' + f.result + ' for valid for ' + f.value, () => {
|
||||||
assert.strictEqual(types.Satoshi(f.value), f.result)
|
assert.strictEqual(types.Satoshi(f.value), f.result)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
119
ts_src/address.ts
Normal file
119
ts_src/address.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import { Network } from './networks';
|
||||||
|
import * as networks from './networks';
|
||||||
|
import * as payments from './payments';
|
||||||
|
import * as bscript from './script';
|
||||||
|
import * as types from './types';
|
||||||
|
|
||||||
|
const bech32 = require('bech32');
|
||||||
|
const bs58check = require('bs58check');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
|
|
||||||
|
export interface Base58CheckResult {
|
||||||
|
hash: Buffer;
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Bech32Result {
|
||||||
|
version: number;
|
||||||
|
prefix: string;
|
||||||
|
data: Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromBase58Check(address: string): Base58CheckResult {
|
||||||
|
const payload = bs58check.decode(address);
|
||||||
|
|
||||||
|
// TODO: 4.0.0, move to "toOutputScript"
|
||||||
|
if (payload.length < 21) throw new TypeError(address + ' is too short');
|
||||||
|
if (payload.length > 21) throw new TypeError(address + ' is too long');
|
||||||
|
|
||||||
|
const version = payload.readUInt8(0);
|
||||||
|
const hash = payload.slice(1);
|
||||||
|
|
||||||
|
return { version, hash };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromBech32(address: string): Bech32Result {
|
||||||
|
const result = bech32.decode(address);
|
||||||
|
const data = bech32.fromWords(result.words.slice(1));
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: result.words[0],
|
||||||
|
prefix: result.prefix,
|
||||||
|
data: Buffer.from(data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toBase58Check(hash: Buffer, version: number): string {
|
||||||
|
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
|
||||||
|
|
||||||
|
const payload = Buffer.allocUnsafe(21);
|
||||||
|
payload.writeUInt8(version, 0);
|
||||||
|
hash.copy(payload, 1);
|
||||||
|
|
||||||
|
return bs58check.encode(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toBech32(
|
||||||
|
data: Buffer,
|
||||||
|
version: number,
|
||||||
|
prefix: string,
|
||||||
|
): string {
|
||||||
|
const words = bech32.toWords(data);
|
||||||
|
words.unshift(version);
|
||||||
|
|
||||||
|
return bech32.encode(prefix, words);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromOutputScript(output: Buffer, network?: Network): string {
|
||||||
|
// TODO: Network
|
||||||
|
network = network || networks.bitcoin;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return payments.p2pkh({ output, network }).address as string;
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
return payments.p2sh({ output, network }).address as string;
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
return payments.p2wpkh({ output, network }).address as string;
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
return payments.p2wsh({ output, network }).address as string;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
throw new Error(bscript.toASM(output) + ' has no matching Address');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toOutputScript(address: string, network?: Network): Buffer {
|
||||||
|
network = network || networks.bitcoin;
|
||||||
|
|
||||||
|
let decodeBase58: Base58CheckResult | undefined;
|
||||||
|
let decodeBech32: Bech32Result | undefined;
|
||||||
|
try {
|
||||||
|
decodeBase58 = fromBase58Check(address);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (decodeBase58) {
|
||||||
|
if (decodeBase58.version === network.pubKeyHash)
|
||||||
|
return payments.p2pkh({ hash: decodeBase58.hash }).output as Buffer;
|
||||||
|
if (decodeBase58.version === network.scriptHash)
|
||||||
|
return payments.p2sh({ hash: decodeBase58.hash }).output as Buffer;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
decodeBech32 = fromBech32(address);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (decodeBech32) {
|
||||||
|
if (decodeBech32.prefix !== network.bech32)
|
||||||
|
throw new Error(address + ' has an invalid prefix');
|
||||||
|
if (decodeBech32.version === 0) {
|
||||||
|
if (decodeBech32.data.length === 20)
|
||||||
|
return payments.p2wpkh({ hash: decodeBech32.data }).output as Buffer;
|
||||||
|
if (decodeBech32.data.length === 32)
|
||||||
|
return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(address + ' has no matching Script');
|
||||||
|
}
|
285
ts_src/block.ts
Normal file
285
ts_src/block.ts
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
import { reverseBuffer } from './bufferutils';
|
||||||
|
import * as bcrypto from './crypto';
|
||||||
|
import { Transaction } from './transaction';
|
||||||
|
import * as types from './types';
|
||||||
|
|
||||||
|
const fastMerkleRoot = require('merkle-lib/fastRoot');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
|
const varuint = require('varuint-bitcoin');
|
||||||
|
|
||||||
|
const errorMerkleNoTxes = new TypeError(
|
||||||
|
'Cannot compute merkle root for zero transactions',
|
||||||
|
);
|
||||||
|
const errorWitnessNotSegwit = new TypeError(
|
||||||
|
'Cannot compute witness commit for non-segwit block',
|
||||||
|
);
|
||||||
|
|
||||||
|
export class Block {
|
||||||
|
static fromBuffer(buffer: Buffer): Block {
|
||||||
|
if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)');
|
||||||
|
|
||||||
|
let offset: number = 0;
|
||||||
|
const readSlice = (n: number): Buffer => {
|
||||||
|
offset += n;
|
||||||
|
return buffer.slice(offset - n, offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
const readUInt32 = (): number => {
|
||||||
|
const i = buffer.readUInt32LE(offset);
|
||||||
|
offset += 4;
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readInt32 = (): number => {
|
||||||
|
const i = buffer.readInt32LE(offset);
|
||||||
|
offset += 4;
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
const block = new Block();
|
||||||
|
block.version = readInt32();
|
||||||
|
block.prevHash = readSlice(32);
|
||||||
|
block.merkleRoot = readSlice(32);
|
||||||
|
block.timestamp = readUInt32();
|
||||||
|
block.bits = readUInt32();
|
||||||
|
block.nonce = readUInt32();
|
||||||
|
|
||||||
|
if (buffer.length === 80) return block;
|
||||||
|
|
||||||
|
const readVarInt = (): number => {
|
||||||
|
const vi = varuint.decode(buffer, offset);
|
||||||
|
offset += varuint.decode.bytes;
|
||||||
|
return vi;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readTransaction = (): any => {
|
||||||
|
const tx = Transaction.fromBuffer(buffer.slice(offset), true);
|
||||||
|
offset += tx.byteLength();
|
||||||
|
return tx;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nTransactions = readVarInt();
|
||||||
|
block.transactions = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < nTransactions; ++i) {
|
||||||
|
const tx = readTransaction();
|
||||||
|
block.transactions.push(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
const witnessCommit = block.getWitnessCommit();
|
||||||
|
// This Block contains a witness commit
|
||||||
|
if (witnessCommit) block.witnessCommit = witnessCommit;
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromHex(hex: string): Block {
|
||||||
|
return Block.fromBuffer(Buffer.from(hex, 'hex'));
|
||||||
|
}
|
||||||
|
|
||||||
|
static calculateTarget(bits: number): Buffer {
|
||||||
|
const exponent = ((bits & 0xff000000) >> 24) - 3;
|
||||||
|
const mantissa = bits & 0x007fffff;
|
||||||
|
const target = Buffer.alloc(32, 0);
|
||||||
|
target.writeUIntBE(mantissa, 29 - exponent, 3);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
static calculateMerkleRoot(
|
||||||
|
transactions: Transaction[],
|
||||||
|
forWitness?: boolean,
|
||||||
|
): Buffer {
|
||||||
|
typeforce([{ getHash: types.Function }], transactions);
|
||||||
|
if (transactions.length === 0) throw errorMerkleNoTxes;
|
||||||
|
if (forWitness && !txesHaveWitnessCommit(transactions))
|
||||||
|
throw errorWitnessNotSegwit;
|
||||||
|
|
||||||
|
const hashes = transactions.map(transaction =>
|
||||||
|
transaction.getHash(forWitness!),
|
||||||
|
);
|
||||||
|
|
||||||
|
const rootHash = fastMerkleRoot(hashes, bcrypto.hash256);
|
||||||
|
|
||||||
|
return forWitness
|
||||||
|
? bcrypto.hash256(
|
||||||
|
Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]),
|
||||||
|
)
|
||||||
|
: rootHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
version: number = 1;
|
||||||
|
prevHash?: Buffer = undefined;
|
||||||
|
merkleRoot?: Buffer = undefined;
|
||||||
|
timestamp: number = 0;
|
||||||
|
witnessCommit?: Buffer = undefined;
|
||||||
|
bits: number = 0;
|
||||||
|
nonce: number = 0;
|
||||||
|
transactions?: Transaction[] = undefined;
|
||||||
|
|
||||||
|
getWitnessCommit(): Buffer | null {
|
||||||
|
if (!txesHaveWitnessCommit(this.transactions!)) return null;
|
||||||
|
|
||||||
|
// The merkle root for the witness data is in an OP_RETURN output.
|
||||||
|
// There is no rule for the index of the output, so use filter to find it.
|
||||||
|
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
|
||||||
|
// If multiple commits are found, the output with highest index is assumed.
|
||||||
|
const witnessCommits = this.transactions![0].outs.filter(out =>
|
||||||
|
out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')),
|
||||||
|
).map(out => out.script.slice(6, 38));
|
||||||
|
if (witnessCommits.length === 0) return null;
|
||||||
|
// Use the commit with the highest output (should only be one though)
|
||||||
|
const result = witnessCommits[witnessCommits.length - 1];
|
||||||
|
|
||||||
|
if (!(result instanceof Buffer && result.length === 32)) return null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasWitnessCommit(): boolean {
|
||||||
|
if (
|
||||||
|
this.witnessCommit instanceof Buffer &&
|
||||||
|
this.witnessCommit.length === 32
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
if (this.getWitnessCommit() !== null) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasWitness(): boolean {
|
||||||
|
return anyTxHasWitness(this.transactions!);
|
||||||
|
}
|
||||||
|
|
||||||
|
byteLength(headersOnly: boolean): number {
|
||||||
|
if (headersOnly || !this.transactions) return 80;
|
||||||
|
|
||||||
|
return (
|
||||||
|
80 +
|
||||||
|
varuint.encodingLength(this.transactions.length) +
|
||||||
|
this.transactions.reduce((a, x) => a + x.byteLength(), 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHash(): Buffer {
|
||||||
|
return bcrypto.hash256(this.toBuffer(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return reverseBuffer(this.getHash()).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
getUTCDate(): Date {
|
||||||
|
const date = new Date(0); // epoch
|
||||||
|
date.setUTCSeconds(this.timestamp);
|
||||||
|
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: buffer, offset compatibility
|
||||||
|
toBuffer(headersOnly: boolean): Buffer {
|
||||||
|
const buffer: Buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
|
||||||
|
|
||||||
|
let offset: number = 0;
|
||||||
|
const writeSlice = (slice: Buffer): void => {
|
||||||
|
slice.copy(buffer, offset);
|
||||||
|
offset += slice.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeInt32 = (i: number): void => {
|
||||||
|
buffer.writeInt32LE(i, offset);
|
||||||
|
offset += 4;
|
||||||
|
};
|
||||||
|
const writeUInt32 = (i: number): void => {
|
||||||
|
buffer.writeUInt32LE(i, offset);
|
||||||
|
offset += 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
writeInt32(this.version);
|
||||||
|
writeSlice(this.prevHash!);
|
||||||
|
writeSlice(this.merkleRoot!);
|
||||||
|
writeUInt32(this.timestamp);
|
||||||
|
writeUInt32(this.bits);
|
||||||
|
writeUInt32(this.nonce);
|
||||||
|
|
||||||
|
if (headersOnly || !this.transactions) return buffer;
|
||||||
|
|
||||||
|
varuint.encode(this.transactions.length, buffer, offset);
|
||||||
|
offset += varuint.encode.bytes;
|
||||||
|
|
||||||
|
this.transactions.forEach(tx => {
|
||||||
|
const txSize = tx.byteLength(); // TODO: extract from toBuffer?
|
||||||
|
tx.toBuffer(buffer, offset);
|
||||||
|
offset += txSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
toHex(headersOnly: boolean): string {
|
||||||
|
return this.toBuffer(headersOnly).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTxRoots(): boolean {
|
||||||
|
// If the Block has segwit transactions but no witness commit,
|
||||||
|
// there's no way it can be valid, so fail the check.
|
||||||
|
const hasWitnessCommit = this.hasWitnessCommit();
|
||||||
|
if (!hasWitnessCommit && this.hasWitness()) return false;
|
||||||
|
return (
|
||||||
|
this.__checkMerkleRoot() &&
|
||||||
|
(hasWitnessCommit ? this.__checkWitnessCommit() : true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkProofOfWork(): boolean {
|
||||||
|
const hash: Buffer = reverseBuffer(this.getHash());
|
||||||
|
const target = Block.calculateTarget(this.bits);
|
||||||
|
|
||||||
|
return hash.compare(target) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private __checkMerkleRoot(): boolean {
|
||||||
|
if (!this.transactions) throw errorMerkleNoTxes;
|
||||||
|
|
||||||
|
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
|
||||||
|
return this.merkleRoot!.compare(actualMerkleRoot) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private __checkWitnessCommit(): boolean {
|
||||||
|
if (!this.transactions) throw errorMerkleNoTxes;
|
||||||
|
if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit;
|
||||||
|
|
||||||
|
const actualWitnessCommit = Block.calculateMerkleRoot(
|
||||||
|
this.transactions,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return this.witnessCommit!.compare(actualWitnessCommit) === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function txesHaveWitnessCommit(transactions: Transaction[]): boolean {
|
||||||
|
return (
|
||||||
|
transactions instanceof Array &&
|
||||||
|
transactions[0] &&
|
||||||
|
transactions[0].ins &&
|
||||||
|
transactions[0].ins instanceof Array &&
|
||||||
|
transactions[0].ins[0] &&
|
||||||
|
transactions[0].ins[0].witness &&
|
||||||
|
transactions[0].ins[0].witness instanceof Array &&
|
||||||
|
transactions[0].ins[0].witness.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function anyTxHasWitness(transactions: Transaction[]): boolean {
|
||||||
|
return (
|
||||||
|
transactions instanceof Array &&
|
||||||
|
transactions.some(
|
||||||
|
tx =>
|
||||||
|
typeof tx === 'object' &&
|
||||||
|
tx.ins instanceof Array &&
|
||||||
|
tx.ins.some(
|
||||||
|
input =>
|
||||||
|
typeof input === 'object' &&
|
||||||
|
input.witness instanceof Array &&
|
||||||
|
input.witness.length > 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
44
ts_src/bufferutils.ts
Normal file
44
ts_src/bufferutils.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
||||||
|
function verifuint(value: number, max: number): void {
|
||||||
|
if (typeof value !== 'number')
|
||||||
|
throw new Error('cannot write a non-number as a number');
|
||||||
|
if (value < 0)
|
||||||
|
throw new Error('specified a negative value for writing an unsigned value');
|
||||||
|
if (value > max) throw new Error('RangeError: value out of range');
|
||||||
|
if (Math.floor(value) !== value)
|
||||||
|
throw new Error('value has a fractional component');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readUInt64LE(buffer: Buffer, offset: number): number {
|
||||||
|
const a = buffer.readUInt32LE(offset);
|
||||||
|
let b = buffer.readUInt32LE(offset + 4);
|
||||||
|
b *= 0x100000000;
|
||||||
|
|
||||||
|
verifuint(b + a, 0x001fffffffffffff);
|
||||||
|
return b + a;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeUInt64LE(
|
||||||
|
buffer: Buffer,
|
||||||
|
value: number,
|
||||||
|
offset: number,
|
||||||
|
): number {
|
||||||
|
verifuint(value, 0x001fffffffffffff);
|
||||||
|
|
||||||
|
buffer.writeInt32LE(value & -1, offset);
|
||||||
|
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
|
||||||
|
return offset + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reverseBuffer(buffer: Buffer): Buffer {
|
||||||
|
if (buffer.length < 1) return buffer;
|
||||||
|
let j = buffer.length - 1;
|
||||||
|
let tmp = 0;
|
||||||
|
for (let i = 0; i < buffer.length / 2; i++) {
|
||||||
|
tmp = buffer[i];
|
||||||
|
buffer[i] = buffer[j];
|
||||||
|
buffer[j] = tmp;
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
71
ts_src/classify.ts
Normal file
71
ts_src/classify.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { decompile } from './script';
|
||||||
|
import * as multisig from './templates/multisig';
|
||||||
|
import * as nullData from './templates/nulldata';
|
||||||
|
import * as pubKey from './templates/pubkey';
|
||||||
|
import * as pubKeyHash from './templates/pubkeyhash';
|
||||||
|
import * as scriptHash from './templates/scripthash';
|
||||||
|
import * as witnessCommitment from './templates/witnesscommitment';
|
||||||
|
import * as witnessPubKeyHash from './templates/witnesspubkeyhash';
|
||||||
|
import * as witnessScriptHash from './templates/witnessscripthash';
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
P2MS: 'multisig' as string,
|
||||||
|
NONSTANDARD: 'nonstandard' as string,
|
||||||
|
NULLDATA: 'nulldata' as string,
|
||||||
|
P2PK: 'pubkey' as string,
|
||||||
|
P2PKH: 'pubkeyhash' as string,
|
||||||
|
P2SH: 'scripthash' as string,
|
||||||
|
P2WPKH: 'witnesspubkeyhash' as string,
|
||||||
|
P2WSH: 'witnessscripthash' as string,
|
||||||
|
WITNESS_COMMITMENT: 'witnesscommitment' as string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function classifyOutput(script: Buffer): string {
|
||||||
|
if (witnessPubKeyHash.output.check(script)) return types.P2WPKH;
|
||||||
|
if (witnessScriptHash.output.check(script)) return types.P2WSH;
|
||||||
|
if (pubKeyHash.output.check(script)) return types.P2PKH;
|
||||||
|
if (scriptHash.output.check(script)) return types.P2SH;
|
||||||
|
|
||||||
|
// XXX: optimization, below functions .decompile before use
|
||||||
|
const chunks = decompile(script);
|
||||||
|
if (!chunks) throw new TypeError('Invalid script');
|
||||||
|
|
||||||
|
if (multisig.output.check(chunks)) return types.P2MS;
|
||||||
|
if (pubKey.output.check(chunks)) return types.P2PK;
|
||||||
|
if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT;
|
||||||
|
if (nullData.output.check(chunks)) return types.NULLDATA;
|
||||||
|
|
||||||
|
return types.NONSTANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifyInput(script: Buffer, allowIncomplete: boolean): string {
|
||||||
|
// XXX: optimization, below functions .decompile before use
|
||||||
|
const chunks = decompile(script);
|
||||||
|
if (!chunks) throw new TypeError('Invalid script');
|
||||||
|
|
||||||
|
if (pubKeyHash.input.check(chunks)) return types.P2PKH;
|
||||||
|
if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH;
|
||||||
|
if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS;
|
||||||
|
if (pubKey.input.check(chunks)) return types.P2PK;
|
||||||
|
|
||||||
|
return types.NONSTANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifyWitness(script: Buffer[], allowIncomplete: boolean): string {
|
||||||
|
// XXX: optimization, below functions .decompile before use
|
||||||
|
const chunks = decompile(script);
|
||||||
|
if (!chunks) throw new TypeError('Invalid script');
|
||||||
|
|
||||||
|
if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH;
|
||||||
|
if (witnessScriptHash.input.check(chunks as Buffer[], allowIncomplete))
|
||||||
|
return types.P2WSH;
|
||||||
|
|
||||||
|
return types.NONSTANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
classifyInput as input,
|
||||||
|
classifyOutput as output,
|
||||||
|
classifyWitness as witness,
|
||||||
|
types,
|
||||||
|
};
|
33
ts_src/crypto.ts
Normal file
33
ts_src/crypto.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
const createHash = require('create-hash');
|
||||||
|
|
||||||
|
export function ripemd160(buffer: Buffer): Buffer {
|
||||||
|
try {
|
||||||
|
return createHash('rmd160')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
} catch (err) {
|
||||||
|
return createHash('ripemd160')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha1(buffer: Buffer): Buffer {
|
||||||
|
return createHash('sha1')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha256(buffer: Buffer): Buffer {
|
||||||
|
return createHash('sha256')
|
||||||
|
.update(buffer)
|
||||||
|
.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hash160(buffer: Buffer): Buffer {
|
||||||
|
return ripemd160(sha256(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hash256(buffer: Buffer): Buffer {
|
||||||
|
return sha256(sha256(buffer));
|
||||||
|
}
|
146
ts_src/ecpair.ts
Normal file
146
ts_src/ecpair.ts
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import { Network } from './networks';
|
||||||
|
import * as NETWORKS from './networks';
|
||||||
|
import * as types from './types';
|
||||||
|
const ecc = require('tiny-secp256k1');
|
||||||
|
const randomBytes = require('randombytes');
|
||||||
|
const typeforce = require('typeforce');
|
||||||
|
const wif = require('wif');
|
||||||
|
|
||||||
|
const isOptions = typeforce.maybe(
|
||||||
|
typeforce.compile({
|
||||||
|
compressed: types.maybe(types.Boolean),
|
||||||
|
network: types.maybe(types.Network),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ECPairOptions {
|
||||||
|
compressed?: boolean;
|
||||||
|
network?: Network;
|
||||||
|
rng?(arg0: number): Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ECPairInterface {
|
||||||
|
compressed: boolean;
|
||||||
|
network: Network;
|
||||||
|
publicKey: Buffer;
|
||||||
|
privateKey?: Buffer;
|
||||||
|
toWIF(): string;
|
||||||
|
sign(hash: Buffer, lowR?: boolean): Buffer;
|
||||||
|
verify(hash: Buffer, signature: Buffer): boolean;
|
||||||
|
getPublicKey?(): Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ECPair implements ECPairInterface {
|
||||||
|
compressed: boolean;
|
||||||
|
network: Network;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private __D?: Buffer,
|
||||||
|
private __Q?: Buffer,
|
||||||
|
options?: ECPairOptions,
|
||||||
|
) {
|
||||||
|
if (options === undefined) options = {};
|
||||||
|
this.compressed =
|
||||||
|
options.compressed === undefined ? true : options.compressed;
|
||||||
|
this.network = options.network || NETWORKS.bitcoin;
|
||||||
|
|
||||||
|
if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
get privateKey(): Buffer | undefined {
|
||||||
|
return this.__D;
|
||||||
|
}
|
||||||
|
|
||||||
|
get publicKey(): Buffer {
|
||||||
|
if (!this.__Q)
|
||||||
|
this.__Q = ecc.pointFromScalar(this.__D, this.compressed) as Buffer;
|
||||||
|
return this.__Q;
|
||||||
|
}
|
||||||
|
|
||||||
|
toWIF(): string {
|
||||||
|
if (!this.__D) throw new Error('Missing private key');
|
||||||
|
return wif.encode(this.network.wif, this.__D, this.compressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
sign(hash: Buffer, lowR: boolean = false): Buffer {
|
||||||
|
if (!this.__D) throw new Error('Missing private key');
|
||||||
|
if (lowR === false) {
|
||||||
|
return ecc.sign(hash, this.__D);
|
||||||
|
} else {
|
||||||
|
let sig = ecc.sign(hash, this.__D);
|
||||||
|
const extraData = Buffer.alloc(32, 0);
|
||||||
|
let counter = 0;
|
||||||
|
// if first try is lowR, skip the loop
|
||||||
|
// for second try and on, add extra entropy counting up
|
||||||
|
while (sig[0] > 0x7f) {
|
||||||
|
counter++;
|
||||||
|
extraData.writeUIntLE(counter, 0, 6);
|
||||||
|
sig = ecc.signWithEntropy(hash, this.__D, extraData);
|
||||||
|
}
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(hash: Buffer, signature: Buffer): boolean {
|
||||||
|
return ecc.verify(hash, this.publicKey, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair {
|
||||||
|
typeforce(types.Buffer256bit, buffer);
|
||||||
|
if (!ecc.isPrivate(buffer))
|
||||||
|
throw new TypeError('Private key not in range [1, n)');
|
||||||
|
typeforce(isOptions, options);
|
||||||
|
|
||||||
|
return new ECPair(buffer, undefined, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair {
|
||||||
|
typeforce(ecc.isPoint, buffer);
|
||||||
|
typeforce(isOptions, options);
|
||||||
|
return new ECPair(undefined, buffer, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromWIF(wifString: string, network?: Network | Network[]): ECPair {
|
||||||
|
const decoded = wif.decode(wifString);
|
||||||
|
const version = decoded.version;
|
||||||
|
|
||||||
|
// list of networks?
|
||||||
|
if (types.Array(network)) {
|
||||||
|
network = (network as Network[])
|
||||||
|
.filter((x: Network) => {
|
||||||
|
return version === x.wif;
|
||||||
|
})
|
||||||
|
.pop() as Network;
|
||||||
|
|
||||||
|
if (!network) throw new Error('Unknown network version');
|
||||||
|
|
||||||
|
// otherwise, assume a network object (or default to bitcoin)
|
||||||
|
} else {
|
||||||
|
network = network || NETWORKS.bitcoin;
|
||||||
|
|
||||||
|
if (version !== (network as Network).wif)
|
||||||
|
throw new Error('Invalid network version');
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromPrivateKey(decoded.privateKey, {
|
||||||
|
compressed: decoded.compressed,
|
||||||
|
network: network as Network,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRandom(options?: ECPairOptions): ECPair {
|
||||||
|
typeforce(isOptions, options);
|
||||||
|
if (options === undefined) options = {};
|
||||||
|
const rng = options.rng || randomBytes;
|
||||||
|
|
||||||
|
let d;
|
||||||
|
do {
|
||||||
|
d = rng(32);
|
||||||
|
typeforce(types.Buffer256bit, d);
|
||||||
|
} while (!ecc.isPrivate(d));
|
||||||
|
|
||||||
|
return fromPrivateKey(d, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF };
|
20
ts_src/index.ts
Normal file
20
ts_src/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import * as bip32 from 'bip32';
|
||||||
|
import * as address from './address';
|
||||||
|
import * as crypto from './crypto';
|
||||||
|
import * as ECPair from './ecpair';
|
||||||
|
import * as networks from './networks';
|
||||||
|
import * as payments from './payments';
|
||||||
|
import * as script from './script';
|
||||||
|
|
||||||
|
export { ECPair, address, bip32, crypto, networks, payments, script };
|
||||||
|
|
||||||
|
export { Block } from './block';
|
||||||
|
export { OPS as opcodes } from './script';
|
||||||
|
export { Transaction } from './transaction';
|
||||||
|
export { TransactionBuilder } from './transaction_builder';
|
||||||
|
|
||||||
|
export { BIP32Interface } from 'bip32';
|
||||||
|
export { Network } from './networks';
|
||||||
|
export { Payment, PaymentOpts } from './payments';
|
||||||
|
export { OpCode } from './script';
|
||||||
|
export { Input as TxInput, Output as TxOutput } from './transaction';
|
49
ts_src/networks.ts
Normal file
49
ts_src/networks.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// https://en.bitcoin.it/wiki/List_of_address_prefixes
|
||||||
|
// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731
|
||||||
|
export interface Network {
|
||||||
|
messagePrefix: string;
|
||||||
|
bech32: string;
|
||||||
|
bip32: Bip32;
|
||||||
|
pubKeyHash: number;
|
||||||
|
scriptHash: number;
|
||||||
|
wif: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Bip32 {
|
||||||
|
public: number;
|
||||||
|
private: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bitcoin: Network = {
|
||||||
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
|
bech32: 'bc',
|
||||||
|
bip32: {
|
||||||
|
public: 0x0488b21e,
|
||||||
|
private: 0x0488ade4,
|
||||||
|
},
|
||||||
|
pubKeyHash: 0x00,
|
||||||
|
scriptHash: 0x05,
|
||||||
|
wif: 0x80,
|
||||||
|
};
|
||||||
|
export const regtest: Network = {
|
||||||
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
|
bech32: 'bcrt',
|
||||||
|
bip32: {
|
||||||
|
public: 0x043587cf,
|
||||||
|
private: 0x04358394,
|
||||||
|
},
|
||||||
|
pubKeyHash: 0x6f,
|
||||||
|
scriptHash: 0xc4,
|
||||||
|
wif: 0xef,
|
||||||
|
};
|
||||||
|
export const testnet: Network = {
|
||||||
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
|
bech32: 'tb',
|
||||||
|
bip32: {
|
||||||
|
public: 0x043587cf,
|
||||||
|
private: 0x04358394,
|
||||||
|
},
|
||||||
|
pubKeyHash: 0x6f,
|
||||||
|
scriptHash: 0xc4,
|
||||||
|
wif: 0xef,
|
||||||
|
};
|
58
ts_src/payments/embed.ts
Normal file
58
ts_src/payments/embed.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
||||||
|
import * as bscript from '../script';
|
||||||
|
import { Payment, PaymentOpts, Stack } from './index';
|
||||||
|
import * as lazy from './lazy';
|
||||||
|
|
||||||
|
const typef = require('typeforce');
|
||||||
|
const OPS = bscript.OPS;
|
||||||
|
|
||||||
|
function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
|
||||||
|
return a.every((x, i) => {
|
||||||
|
return x.equals(b[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// output: OP_RETURN ...
|
||||||
|
export function p2data(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
|
if (!a.data && !a.output) throw new TypeError('Not enough data');
|
||||||
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
|
|
||||||
|
typef(
|
||||||
|
{
|
||||||
|
network: typef.maybe(typef.Object),
|
||||||
|
output: typef.maybe(typef.Buffer),
|
||||||
|
data: typef.maybe(typef.arrayOf(typef.Buffer)),
|
||||||
|
},
|
||||||
|
a,
|
||||||
|
);
|
||||||
|
|
||||||
|
const network = a.network || BITCOIN_NETWORK;
|
||||||
|
const o = { network } as Payment;
|
||||||
|
|
||||||
|
lazy.prop(o, 'output', () => {
|
||||||
|
if (!a.data) return;
|
||||||
|
return bscript.compile(([OPS.OP_RETURN] as Stack).concat(a.data));
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'data', () => {
|
||||||
|
if (!a.output) return;
|
||||||
|
return bscript.decompile(a.output)!.slice(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// extended validation
|
||||||
|
if (opts.validate) {
|
||||||
|
if (a.output) {
|
||||||
|
const chunks = bscript.decompile(a.output);
|
||||||
|
if (chunks![0] !== OPS.OP_RETURN)
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
|
if (!chunks!.slice(1).every(typef.Buffer))
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
|
|
||||||
|
if (a.data && !stacksEqual(a.data, o.data as Buffer[]))
|
||||||
|
throw new TypeError('Data mismatch');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(o, a);
|
||||||
|
}
|
41
ts_src/payments/index.ts
Normal file
41
ts_src/payments/index.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { Network } from '../networks';
|
||||||
|
import { p2data as embed } from './embed';
|
||||||
|
import { p2ms } from './p2ms';
|
||||||
|
import { p2pk } from './p2pk';
|
||||||
|
import { p2pkh } from './p2pkh';
|
||||||
|
import { p2sh } from './p2sh';
|
||||||
|
import { p2wpkh } from './p2wpkh';
|
||||||
|
import { p2wsh } from './p2wsh';
|
||||||
|
|
||||||
|
export interface Payment {
|
||||||
|
network?: Network;
|
||||||
|
output?: Buffer;
|
||||||
|
data?: Buffer[];
|
||||||
|
m?: number;
|
||||||
|
n?: number;
|
||||||
|
pubkeys?: Buffer[];
|
||||||
|
input?: Buffer;
|
||||||
|
signatures?: Buffer[];
|
||||||
|
pubkey?: Buffer;
|
||||||
|
signature?: Buffer;
|
||||||
|
address?: string;
|
||||||
|
hash?: Buffer;
|
||||||
|
redeem?: Payment;
|
||||||
|
witness?: Buffer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaymentFunction = () => Payment;
|
||||||
|
|
||||||
|
export interface PaymentOpts {
|
||||||
|
validate?: boolean;
|
||||||
|
allowIncomplete?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StackElement = Buffer | number;
|
||||||
|
export type Stack = StackElement[];
|
||||||
|
export type StackFunction = () => Stack;
|
||||||
|
|
||||||
|
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// witness commitment
|
28
ts_src/payments/lazy.ts
Normal file
28
ts_src/payments/lazy.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
export function prop(object: {}, name: string, f: () => any): void {
|
||||||
|
Object.defineProperty(object, name, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get(): any {
|
||||||
|
const _value = f.call(this);
|
||||||
|
this[name] = _value;
|
||||||
|
return _value;
|
||||||
|
},
|
||||||
|
set(_value: any): void {
|
||||||
|
Object.defineProperty(this, name, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
value: _value,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function value<T>(f: () => T): () => T {
|
||||||
|
let _value: T;
|
||||||
|
return (): T => {
|
||||||
|
if (_value !== undefined) return _value;
|
||||||
|
_value = f();
|
||||||
|
return _value;
|
||||||
|
};
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue