diff --git a/.gitignore b/.gitignore index 0abc934bd..a6c0ab829 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -bitcoin.js coverage node_modules +.nyc_output +npm-debug.log diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 74e448642..000000000 --- a/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -.gitignore -.travis.yml -jshint.json -test/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..e69de29bb diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..a20502b7f --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/.travis.yml b/.travis.yml index 26f05da87..bb87738bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,27 @@ +sudo: false language: node_js +services: + - docker before_install: - - "npm install npm -g" + - 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: - - "0.11" - - "0.10" + - "8" + - "lts/*" +matrix: + include: + - node_js: "lts/*" + env: TEST_SUITE=format:ci + - node_js: "lts/*" + env: TEST_SUITE=gitdiff:ci + - node_js: "lts/*" + env: TEST_SUITE=lint + - node_js: "lts/*" + env: TEST_SUITE=coverage env: - - TEST_SUITE=coveralls - - TEST_SUITE=integration - - TEST_SUITE=standard - TEST_SUITE=unit -script: "npm run-script $TEST_SUITE" + - TEST_SUITE=integration APIURL=http://127.0.0.1:8080/1 +script: npm run-script $TEST_SUITE diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..cbb2deafd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,289 @@ +# 5.1.2 +__added__ +- `ECPair` and `bip32` objects now have a lowR boolean attribute defaulted to false. You may set it to true to ensure that the sign method uses low R values (#1442) (This is to enable low R usage in Psbt, since we decided not to give the low R flag to the Psbt class, since it makes more sense to be an attribute of the Signer interface) + +# 5.1.1 +__changed__ +- Name inconsistencies for Psbt class. (Quick fix) + +# 5.1.0 +__added__ +- A new `Psbt` class for creating, distributing, combining, signing, and compiling Transactions (#1425) +- A `name` attribute to the Payment interface. P2SH and P2WSH are nested with `'-'` as separator, and p2ms is in the format of `'p2ms(m of n)''` all others are just hard coded. (#1433) + +__changed__ +- `TransactionBuilder`: Migrate to stricter type checks during sign by switching to a single object parameter (#1416) +- `tests`: Use regtest-client as separate library (#1421) + +# 5.0.5 +__added__ +- Added `ECPairInterface` `Stack` and `StackElement` interfaces to the main index.ts export (TypeScript only affected) + +# 5.0.4 +__added__ +- low R value support for ECPair, bip32, and TransactionBuilder (default off) via `txb.setLowR()` (#1385) + +__fixed__ +- Fixed Various TypeScript types that have been pushed out since v5.0.0 (#1388) + +# 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 +__fixed__ +- Fixed `tiny-secp256k1` dependency version (used `ecurve`) (#1139) +- Fixed `TransactionBuilder` throwing when trying to sign `P2WSH(P2WPKH)` (#1135) + +# 4.0.0 +__added__ +- Added [`bip32`](https://github.com/bitcoinjs/bip32) dependency as a primary export (#1073) +- Added `ECPair.fromPrivateKey` (#1070) +- Added `payments` export, with support for `p2pkh`, `p2pk`, `p2ms`, `p2sh`, `p2wpkh`, `p2wsh` and `embed` payment types (#1096, #1119) +- Added `script.signature.encode/decode` for script signatures (#459) + +__changed__ +- `ECPair.prototype.sign` now returns a 64-byte signature `Buffer`, not an `ECSignature` object (#1084) +- `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` now defaults to version 2 transaction versions (#1036) +- `script.decompile` now returns `[Buffer]` or `null`, if decompilation failed (#1039) + +__fixed__ +- Fixed `TransactionBuilder` rejecting uncompressed public keys to comply with BIP143 (#987) + +__removed__ +- Removed Node 4/5 LTS support (#1080) +- Removed `ECPair.fromPublicKeyBuffer`, use `ECPair.fromPublicKey` (#1070) +- Removed `ECPair.prototype.getAddress`, use `payments.p2pkh` instead (#1085) +- Removed `ECPair.prototype.getPrivateKey`, use `ECPair.prototype.privateKey` property (#1070) +- Removed `ECPair.prototype.getPublicKey`, use `ECPair.prototype.publicKey` property (#1070) +- Removed `ECPair.prototype.getNetwork`, use `ECPair.prototype.network` property (#1070) +- Removed `ECSignature`, use `script.signature.encode/decode` instead (#459) +- Removed `HDNode`, use `bip32` export instead (#1073) +- Removed `bufferutils` (#1035) +- Removed `networks.litecoin`, BYO non-Bitcoin networks instead (#1095) +- Removed `script.isCanonicalSignature`, use `script.isCanonicalScriptSignature` instead (#1094) +- Removed `script.*.input/output/check` functions (`templates`), use `payments.*` instead (`templates` previously added in #681, #682) (#1119) +- Removed dependency `bigi`, uses `bn.js` internally now (via `tiny-secp256k1`) (#1070, #1112) +- Removed public access to `ECPair` constructor, use exported functions `ECPair.fromPrivateKey`, `ECPair.fromWIF`, `ECPair.makeRandom`, or `ECPair.fromPublicKey` (#1070) + +# 3.3.2 +__fixed__ +- Fixed `decodeStack` arbitrarily supporting non-Array arguments (#942) + +# 3.3.1 +__changed__ +- Increased the `TransactionBuilder` `maximumFeeRate` from 1000 to 2500 satoshis/byte. (#931) + +# 3.3.0 +__added__ +- Added `ECSignature.prototype.toRSBuffer`/`ECSignature.fromRSBuffer` (#915) +- Added support to `TransactionBuilder` for 64-byte signatures via `.sign` (#915) +- Added support to `TransactionBuilder` for the `.publicKey` standard as an alternative to `.getPublicKey()` (#915) + +# 3.2.1 +__fixed__ +- Fixed `script.scripthash.input.check` recursion (#898) +- Fixed `TransactionBuilder` sometimes ignoring witness value (#901) +- Fixed `script.witnessScriptHash.input` implementation (previously used the P2SH impl.) (#911) + +# 3.2.0 +__added__ +- Added `address.fromBech32/toBech32` (#846) + +# 3.1.0 +__added__ +- Added `Transaction.prototype.virtualSize` (#811) +- Added `Transaction.prototype.weight` (#811) + +# 3.0.0 +From this release users can expect out-of-the-box Segregated Witness support. +The majority of breaking changes have been in how `script` encoding/decoding occurs, with the introduction of witness stacks. + +__added__ +- Added `script.types` enums (#679) +- Added `script.*.*.{check,encode,decode[,encodeStack,decodeStack]}` functions (#681, #682) +- Added minimal `TransactionBuilder.prototype.build` absurd fee-safety (#696) +- Added `script.(decompile/compile)PushOnly` and `script.toStack` functions (#700) +- Added `Transaction.prototype.toBuffer` Segregated Witness serialization support (#684, #701) +- Added `Transaction.prototype.hasWitnesses` (#718) +- Added `script.witnessCommitment.*` template +- Added `TransactionBuilder.prototype.sign` now has two additional parameters, `witnessValue`, and `witnessScript` +- Added `Transaction.hashForWitnessV0` and `Transaction.setWitness` (5c2fdb60436714f18440dc709f0be065928c1e49) + +__fixed__ +- Fixed `script` must compile minimally (#638) +- Fixed `Transaction` and `Block` versions should be Int32, signed integers (#662) + +__removed__ +- Removed `ecdsa.calcPubKeyRecoveryParam`, `ecdsa.recoverPubKey` (#456) +- Removed `buffer-equals`/`buffer-compare` dependencies (#650) +- Removed `HDNode.prototype.toString` (#665) +- Removed `dogecoin` network (#675) +- Removed `message` export, moved to [`bitcoinjs-message`](https://github.com/bitcoinjs/bitcoinjs-message) (#456) + +__renamed__ +- Removed `script.*` functions in favour of `bitcoin.script.*.(input/output).(encode/decode/check)` style (#682) + +# 2.3.0 +__added__ +- Added `HDNode.prototype.isNeutered` (#536) +- Added `HDNode.prototype.derivePath` (#538) +- Added typeforce checking for `HDNode.prototype.derive*` (#539) +- Added `Transaction.prototype.isCoinbase` (#578) +- Added `Block.prototype.checkMerkleRoot` (#580) +- Added `Block.calculateMerkleRoot` (#580) +- Added `TransactionBuilder.prototype.setVersion` (#599) +- Added `script.isWitnessPubKeyHashOutput` (#602) +- Added `script.isWitnessScriptHashOutput` (#602) +- Added `script.witnessPubKeyHashOutput` (#602) +- Added `script.witnessScriptHashOutput` (#602) +- Added `script.witnessScriptHashInput` (#602) + +__fixed__ +- Fixed "BIP32 is undefined" when network list given to `HDNode` but no compatible version found (#550) +- Fixed `writePushDataInt` output to adhere to minimal data push policy (#617) + + +# 2.2.0 +__added__ +- Added `Block.calculateTarget` for difficulty calculations (#509) +- Added `Block.prototype.checkProofOfWork` (#509) +- Added `opcodes.OP_CHECKLOCKTIMEVERIFY` alias for `OP_NOP2` (#511) +- Added `script.number.[encode/decode]` for CScriptNum-encoded `Buffer`s (#516) +- Added `TransactionBuilder.prototype.setLockTime` (#507) + +__fixed__ +- Bumped `typeforce` version to fix erroneous error message from `types.Hash*bit` types (#534) + + +# 2.1.4 +__fixed__ +- script.isPubKeyHashOutput and script.isScriptHashOutput no longer allow for non-minimal data pushes (per bitcoin/bitcoin `IsStandard` policy) (#499) +- TransactionBuilder.addOutput now allows for SIGHASH_SINGLE, throwing if the contract is violated (#504) +- remove use of `const`, use ES5 only (#502) + + +# 2.1.3 +__fixed__ +- Bumped typeforce to 1.5.5 (see #493) + + +# 2.1.2 +__fixed__ +- Add missing CHANGELOG entry for 2.1.1 + + +# 2.1.1 +__changed__ +- removed use of `buffer-reverse`, dependency only kept for `bufferutils.reverse`, to be deprecated (#478) + +__fixed__ +- `isMultisigOutput` no longer allows data chunks for `m`/`n` (#482) +- `isMultisigOutput`'s `n` value must now match the number of public keys (as per bitcoin/bitcoin) (#484) + + +# 2.1.0 +From this release users should use the HDNode directly (compared to accessing `.keyPair`) when performing ECDSA operations such as `sign` or `verify`. +Ideally you shoud not have to directly access `HDNode` internals for general usage, as it can often be confusing and error prone. + +__added__ +- `ECPair.prototype.getNetwork` +- `HDNode.prototype.getNetwork`, wraps the underyling keyPair's `getNetwork` method +- `HDNode.prototype.getPublicKeyBuffer`, wraps the underyling keyPair's `getPublicKeyBuffer` method +- `HDNode.prototype.sign`, wraps the underlying keyPair's `sign` method +- `HDNode.prototype.verify`, wraps the underlying keyPair's `verify` method + + +# 2.0.0 +In this release we have strived to simplify the API, [using native types](https://github.com/bitcoinjs/bitcoinjs-lib/issues/407) wherevever possible to encourage cross-compatibility with other open source community modules. + +The `ecdsa` module has been removed in lieu of using a new ECDSA module (for performance and safety reasons) during the `2.x.y` major release. +Several other cumbersome modules have been removed, with their new independent modules recommended for usage instead for greater modularity in your projects. + +----------------------------- + +Backward incompatible changes: + +__added__ +- export `address`, for `address` based [utility functions](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/address.js), most compatible, just without `Address` instantiation, see #401, #444 +- export `script`, for `script` based [utility functions](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/script.js), mostly compatible, just without `Script` instantiation, see #438, #444 +- export `ECPair`, a merged replacement for `ECKey`/`ECPubKey`, invalid types will throw via `typeforce` + +__changed__ +- `address.toOutputScript`, `ECPair.prototype.fromWIF` and `HDNode.prototype.fromBase58` no longer automatically detect the network, `networks.bitcoin` is always assumed unless given. +- `assert` was used for type checking, now replaced by `typeforce` +- `BIP66` compliant strict DER signature validation was added to `ECSignature.fromDER`, changing the exact exception messages slightly, see #448. + +- `new HDNode(d/Q, chainCode, network)` -> `new HDNode(keyPair, chainCode)`, now uses `ECPair` +- `HDNode.prototype.toBase58(false)` -> `HDNode.prototype.neutered().toBase58()` for exporting an extended public key +- `HDNode.prototype.toBase58(true)` -> `HDNode.prototype.toBase58()` for exporting an extended private key + +- `Transaction.prototype.hashForSignature(prevOutScript, inIndex, hashType)` -> `Transaction.prototype.hashForSignature(inIndex, prevOutScript, hashType)` +- `Transaction.prototype.addInput(hash, ...)`: `hash` could be a string, Transaction or Buffer -> `hash` can now **only** be a `Buffer`. +- `Transaction.prototype.addOutput(scriptPubKey, ...)`: `scriptPubKey ` could be a string, `Address` or a `Buffer` -> `scriptPubKey` can now **only** be a `Buffer`. +- `TransactionBuilder` API unchanged. + +__removed__ +- export `Address`, `strings` are now used, benchwith no performance loss for most use cases +- export `base58check`, use [`bs58check`](https://github.com/bitcoinjs/bs58check) instead +- export `ecdsa`, use [`ecurve`](https://github.com/cryptocoinjs/ecurve) instead +- export `ECKey`, use new export `ECPair` instead +- export `ECPubKey`, use new export `ECPair` instead +- export `Wallet`, see README.md#complementing-libraries instead +- export `Script`, use new utility export `script` instead (#438 for more information) + +- `crypto.HmacSHA256 `, use [node crypto](https://nodejs.org/api/crypto.html) instead +- `crypto.HmacSHA512 `, use [node crypto](https://nodejs.org/api/crypto.html) instead + +- `Transaction.prototype.sign`, use `TransactionBuilder.prototype.sign` +- `Transaction.prototype.signInput`, use `TransactionBuilder.prototype.sign` +- `Transaction.prototype.validateInput`, use `Transaction.prototype.hashForSignature` and `ECPair.verify` + +- `HDNode.fromBuffer`, use `HDNode.fromBase58` instead +- `HDNode.fromHex`, use `HDNode.fromBase58` instead +- `HDNode.toBuffer`, use `HDNode.prototype.toBase58` instead +- `HDNode.toHex`, use `HDNode.prototype.toBase58` instead + +- `networks.*.magic`, see the comment [here](https://github.com/bitcoinjs/bitcoinjs-lib/pull/432/files#r36715792) +- `networks.[viacoin|viacointestnet|gamerscoin|jumbucks|zetacoin]`, import these yourself (see #383/a0e6ee7) +- `networks.*.estimateFee`, out-dated + +__renamed__ +- `Message` -> `message` +- `scripts` -> `script` +- `scripts.dataOutput ` -> `script.nullDataOutput` (per [convention](https://org/en/glossary/null-data-transaction)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..2c72633de --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,83 @@ + +[//]: # (This is partially derived from https://github.com/bitcoin/bitcoin/blob/6579d80572d2d33aceabbd3db45a6a9f809aa5e3/CONTRIBUTING.md) + +# Contributing to bitcoinjs-lib +Firstly in terms of structure, there is no particular concept of "bitcoinjs developers" in a sense of privileged people. +Open source revolves around a meritocracy where contributors who help gain trust from the community. + +For practical purpose, there are repository "maintainers" who are responsible for merging pull requests. + +We are always accepting of pull requests, but we do adhere to specific standards in regards to coding style, test driven development and commit messages. + + +## Communication Channels +GitHub is the preferred method of communication between members. + +Otherwise, in order of preference: +* bitcoinjs.slack.com +* #bitcoinjs-dev on Freenode IRC + + +## Workflow +The codebase is maintained using the "contributor workflow" where everyone without exception contributes patch proposals using "pull requests". +This facilitates social contribution, easy testing and peer review. + +To contribute a patch, the workflow is as follows: + + 1. Fork repository + 1. Create topic branch + 1. Commit patches + 1. Push changes to your fork + 1. Submit a pull request to https://github.com/bitcoinjs/bitcoinjs-lib + +[Commits should be atomic](https://en.wikipedia.org/wiki/Atomic_commit#Atomic_commit_convention) and diffs easy to read. + +If your pull request is accepted for merging, you may be asked by a maintainer to squash and or [rebase](https://git-scm.com/docs/git-rebase) your commits before it is merged. + +Please refrain from creating several pull requests for the same change. + +Patchsets should be focused: + + * Adding a feature, or + * Fixing a bug, or + * Refactoring code. + +If you combine these, the PR may be rejected or asked to be split up. + +The length of time required for peer review is unpredictable and will vary from pull request to pull request. + +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 +Bitcoin script payment/script templates are based on community consensus, but typically adhere to bitcoin-core node policy by default. + +- `bitcoinjs.script.decompile` is consensus bound only, it does not reject based on policy. +- `bitcoinjs.script.compile` will try to adhere to bitcoin-core `IsStandard` policies rules. (eg. minimalpush in https://github.com/bitcoinjs/bitcoinjs-lib/pull/638) + +Any elliptic curve `sign` operations should adhere to `IsStandard` policies, like `LOW_S`, but `verify` should not reject them [by default]. + +If you need non-standard rejecting `decoding`, you should use an external module, not this library. + +#### TLDR +Where "standards compliant" refers to the default policies of bitcoin-core, we adhere to the following: +- Any "creation" event must create standards-compliant data (standards bound) +- Any "validation" event must allow for non-standards compliant data (consensus bound) + +For stricter validation, use an external module which we [may have] provided. diff --git a/LICENSE b/LICENSE index 7f5a4ce67..064b88538 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2011-2015 Bitcoinjs-lib contributors +Copyright (c) 2011-2019 bitcoinjs-lib contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 045332e77..6f4600476 100644 --- a/README.md +++ b/README.md @@ -1,162 +1,169 @@ # BitcoinJS (bitcoinjs-lib) - [![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) -[![Coverage Status](https://coveralls.io/repos/bitcoinjs/bitcoinjs-lib/badge.png)](https://coveralls.io/r/bitcoinjs/bitcoinjs-lib) -[![tip for next commit](http://tip4commit.com/projects/735.svg)](http://tip4commit.com/projects/735) - -[![NPM](https://nodei.co/npm/bitcoinjs-lib.png)](https://nodei.co/npm/bitcoinjs-lib/) - -The pure JavaScript Bitcoin library for node.js and browsers. -A continued implementation of the original `0.1.3` version used by over a million wallet users; the backbone for almost all Bitcoin web wallets in production today. - +[![NPM](https://img.shields.io/npm/v/bitcoinjs-lib.svg)](https://www.npmjs.org/package/bitcoinjs-lib) -## Features +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) -- Clean: Pure JavaScript, concise code, easy to read. -- Tested: Coverage > 90%, third-party integration tests. -- Careful: Two person approval process for small, focused pull requests. -- Compatible: Works on Node.js and all modern browsers. -- Powerful: Support for advanced features, such as multi-sig, HD Wallets. -- Secure: Strong random number generation, PGP signed releases, trusted developers. -- Principled: No support for browsers with crap RNG (IE < 11) -- Standardized: Node community coding style, Browserify, Node's stdlib and Buffers. -- Fast: Optimized code, uses typed arrays instead of byte arrays for performance. -- Experiment-friendly: Bitcoin Mainnet and Testnet support. -- Altcoin-ready: Capable of working with bitcoin-derived cryptocurrencies (such as Dogecoin). +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). ## Should I use this in production? +If you are thinking of using the *master* branch of this library in production, **stop**. +Master is not stable; it is our development branch, and [only tagged releases may be classified as stable](https://github.com/bitcoinjs/bitcoinjs-lib/tags). -If you are thinking of using the master branch of this library in production, *stop*. -Master is not stable; it is our development branch, and only tagged releases may be classified as stable. -If you are looking for the original, it is tagged as `0.1.3`. Unless you need it for dependency reasons, it is strongly recommended that you use (or upgrade to) the newest version, which adds major functionality, cleans up the interface, fixes many bugs, and adds over 1,300 more tests. +## Can I trust this code? +> 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, including reviewing any and all of your project's dependencies. -## Installation - -`npm install bitcoinjs-lib` +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, +- Tested, with test coverage >95%, +- Advanced and feature rich, +- 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. -## Setup -### Node.js +## 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. - var bitcoin = require('bitcoinjs-lib') - -### Browser - -If you're familiar with how to use browserify, ignore this and proceed normally. -These steps are advisory only and allow you to use the API to its full extent. - -[Browserify](https://github.com/substack/node-browserify) is assumed to be installed for these steps. - -From your repository, create a `foobar.js` file - -``` javascript -var foobar = { - base58: require('bs58'), - bitcoin: require('bitcoinjs-lib'), - ecurve: require('ecurve'), - BigInteger: require('bigi'), - Buffer: require('buffer') -} - -module.exports = foobar +## Installation +``` bash +npm install bitcoinjs-lib ``` -Each of these included packages are seperate to `bitcoinjs-lib`, and must be installed separately. -They are however used in the bitcoinjs-lib public API. +Typically we support the [Node Maintenance LTS version](https://github.com/nodejs/Release). +If in doubt, see the [.travis.yml](.travis.yml) for what versions are used by our continuous integration tests. -Using browserify, compile `foobar.js` for use in the browser: +**WARNING**: We presently don't provide any tooling to verify that the release on `npm` matches GitHub. As such, you should verify anything downloaded by `npm` against your own verified copy. - $ browserify foobar.js -s foobar > foobar.js -You will then be able to load `foobar.js` into your browser, with each of the dependencies above accessible from the global `foobar` object. +## Usage +Crypto is hard. -**NOTE**: See our package.json for the currently supported version of browserify used by this repository. +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. -## Examples +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. -The below examples are implemented as integration tests, they should be very easy to understand. Otherwise, pull requests are appreciated. +Finally, **adhere to best practice**. +We are not an authorative source of best practice, but, at the very least: -- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L8) -- [Generate a address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L20) -- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L29) -- [Create a Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L36) -- [Sign a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L9) -- [Verify a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L17) -- [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L24) -- [Create a 2-of-3 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L8) -- [Spend from a 2-of-2 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L22) -- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L7) -- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L42) -- [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#L44) -- [Recover a Private key from duplicate R values in a signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L90) +* [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. -## Projects utilizing BitcoinJS - -- [BitAddress](https://www.bitaddress.org) -- [Blockchain.info](https://blockchain.info/wallet) -- [Brainwallet](https://brainwallet.github.io) -- [Coinkite](https://coinkite.com) -- [Coinpunk](https://coinpunk.com) -- [Dark Wallet](https://darkwallet.unsystem.net) -- [DecentralBank](http://decentralbank.com/) -- [Dogechain Wallet](https://dogechain.info) -- [GreenAddress](https://greenaddress.it) -- [Hive Wallet](https://www.hivewallet.com) -- [QuickCoin](https://wallet.quickcoin.co) -- [Robocoin](https://wallet.robocoin.com) -- [Skyhook ATM](http://projectskyhook.com) - +### Browser +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 https://browserify.org/. -## Contributors +**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). -Stefan Thomas is the inventor and creator of this project. His pioneering work made Bitcoin web wallets possible. +**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. -Since then, many people have contributed. [Click here](https://github.com/bitcoinjs/bitcoinjs-lib/graphs/contributors) to see the comprehensive list. +### Typescript or VSCode users +Type declarations for Typescript are included in this library. Normal installation should include all the needed type information. -Daniel Cousens, Wei Lu, JP Richardson and Kyle Drake lead the major refactor of the library from 0.1.3 to 1.0.0. +## Examples +The below examples are implemented as integration tests, they should be very easy to understand. +Otherwise, pull requests are appreciated. +Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). + +### Warning: Currently the tests use TransactionBuilder, which will be removed in the future (v6.x.x or higher) +We will move towards replacing all instances of TransactionBuilder in the tests with the new Psbt. + +Currently we have a few examples on how to use the newer Psbt class at the following link: +- [Psbt examples](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions-psbt.js) + +The rest of the examples are below (using TransactionBuilder for Transaction creation) + +- [Generate a random address](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) +- [Generate a 2-of-3 P2SH multisig 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) +- [Generate a SegWit P2SH 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) +- [Generate a SegWit 2-of-2 P2SH multisig address](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) +- [Generate a Testnet 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) +- [Create a 1-to-1 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) +- [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 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 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 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 P2WPKH 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) +- [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) +- [Verify a Transaction signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js) +- [Import a BIP32 testnet xpriv and export to WIF](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) +- [Export a BIP32 xpub](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) +- [Create a BIP44, bitcoin, 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) +- [Use BIP39 to generate BIP32 addresses](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.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) +- [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 and Bob can redeem the output at any time](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) +- [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 (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 (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 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) can send after 5 blocks (complex CHECKSEQUENCEVERIFY)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.js) + +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)! ## Contributing - -Join the ongoing IRC development channel at `#bitcoinjs-dev` on Freenode. -We are always accepting of Pull requests, but we do adhere to specific standards in regards to coding style, test driven development and commit messages. - -Please make your best effort to adhere to these when contributing to save on trivial corrections. +See [CONTRIBUTING.md](CONTRIBUTING.md). ### Running the test suite - $ npm test - $ npm run-script coverage - +``` bash +npm test +npm run-script coverage +``` ## Complementing Libraries - -- [BIP39](https://github.com/weilu/bip39) - Mnemonic code for generating deterministic keys -- [BIP38](https://github.com/cryptocoinjs/bip38) - Passphrase-protected private keys -- [BCoin](https://github.com/indutny/bcoin) - BIP37 / Bloom Filters / SPV client -- [insight](https://github.com/bitpay/insight) - A bitcoin blockchain API for web wallets. +- [BIP21](https://github.com/bitcoinjs/bip21) - A BIP21 compatible URL encoding library +- [BIP38](https://github.com/bitcoinjs/bip38) - Passphrase-protected private keys +- [BIP39](https://github.com/bitcoinjs/bip39) - Mnemonic generation for deterministic keys +- [BIP32-Utils](https://github.com/bitcoinjs/bip32-utils) - A set of utilities for working with BIP32 +- [BIP66](https://github.com/bitcoinjs/bip66) - Strict DER signature decoding +- [BIP68](https://github.com/bitcoinjs/bip68) - Relative lock-time encoding library +- [BIP69](https://github.com/bitcoinjs/bip69) - Lexicographical Indexing of Transaction Inputs and Outputs +- [Base58](https://github.com/cryptocoinjs/bs58) - Base58 encoding/decoding +- [Base58 Check](https://github.com/bitcoinjs/bs58check) - Base58 check encoding/decoding +- [Bech32](https://github.com/bitcoinjs/bech32) - A BIP173 compliant Bech32 encoding library +- [coinselect](https://github.com/bitcoinjs/coinselect) - A fee-optimizing, transaction input selection module for bitcoinjs-lib. +- [merkle-lib](https://github.com/bitcoinjs/merkle-lib) - A performance conscious library for merkle root and tree calculations. +- [minimaldata](https://github.com/bitcoinjs/minimaldata) - A module to check bitcoin policy: SCRIPT_VERIFY_MINIMALDATA ## Alternatives - +- [BCoin](https://github.com/indutny/bcoin) - [Bitcore](https://github.com/bitpay/bitcore) - [Cryptocoin](https://github.com/cryptocoinjs/cryptocoin) -## License - -This library is free and open-source software released under the MIT license. - - -## Copyright - -BitcoinJS (c) 2011-2014 Bitcoinjs-lib contributors -Released under MIT license +## LICENSE [MIT](LICENSE) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..9aac26187 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1989 @@ +{ + "name": "bitcoinjs-lib", + "version": "5.1.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz", + "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==", + "dev": true + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz", + "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base-x": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz", + "integrity": "sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "bech32": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.3.tgz", + "integrity": "sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bip174": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-1.0.0.tgz", + "integrity": "sha512-AaoWrkYtv6A2y8H+qzs6NvRWypzNbADT8PQGpM9rnP+jLzeol+uzhe3Myeuq/dwrHYtmsW8V71HmX2oXhQGagw==" + }, + "bip32": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.4.tgz", + "integrity": "sha512-ioPytarPDIrWckWMuK4RNUtvwhvWEc2fvuhnO0WEwu732k5OLjUXv4rXi2c/KJHw9ZMNQMkYRJrBw81RujShGQ==", + "requires": { + "@types/node": "10.12.18", + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.1.0", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + } + }, + "bip39": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-2.5.0.tgz", + "integrity": "sha512-xwIx/8JKoT2+IPJpFEfXoWdYwP7UVAoUxxLNfGCfVowaJE7yg1Y5B1BVPqlUNsBq5/nGwmFkwRJ8xDW4sX8OdA==", + "dev": true, + "requires": { + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1", + "safe-buffer": "^5.0.1", + "unorm": "^1.3.3" + } + }, + "bip65": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bip65/-/bip65-1.0.3.tgz", + "integrity": "sha512-RQ1nc7xtnLa5XltnCqkoR2zmhuz498RjMJwrLKQzOE049D1HUqnYfon7cVSbwS5UGm0/EQlC2CH+NY3MyITA4Q==", + "dev": true + }, + "bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "bip68": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bip68/-/bip68-1.0.4.tgz", + "integrity": "sha512-O1htyufFTYy3EO0JkHg2CLykdXEtV2ssqw47Gq9A0WByp662xpJnMEB9m43LZjsSDjIAOozWRExlFQk2hlV1XQ==", + "dev": true + }, + "bitcoin-ops": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", + "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "dhttp": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dhttp/-/dhttp-3.0.3.tgz", + "integrity": "sha512-map1b8iyvxSv0uEw3DUDDK5XvH3aYA7QU9DcXy8e3FBIXSwHPHTZWVrOot7Iu9mieWq5XcrZemEJlob6IdCBmg==", + "dev": true, + "requires": { + "statuses": "^1.5.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "dev": true, + "requires": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoodwink": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz", + "integrity": "sha512-j1jog3tDfhpWlqbVbh29qc7FG7w+NT4ed+QQFGqvww83+50AzzretB7wykZGOe28mBdvCYH3GdHaVWJQ2lJ/4w==", + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "requires": { + "handlebars": "^4.1.2" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "merkle-lib": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz", + "integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY=" + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimaldata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minimaldata/-/minimaldata-1.0.2.tgz", + "integrity": "sha1-AfOywB2LJzmEP9hT1AY2xaLrdms=", + "dev": true, + "requires": { + "bitcoin-ops": "^1.3.0", + "pushdata-bitcoin": "^1.0.1" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "prettier": { + "version": "1.16.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", + "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "dev": true + }, + "proxyquire": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.0.tgz", + "integrity": "sha512-kptdFArCfGRtQFv3Qwjr10lwbEV0TBJYvfqzhwucyfEXqVgmnAkyEw/S3FYzR5HI9i5QOq4rcqQjZ6AlknlCDQ==", + "dev": true, + "requires": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.0", + "resolve": "~1.8.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pushdata-bitcoin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", + "integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=", + "requires": { + "bitcoin-ops": "^1.3.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "regtest-client": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regtest-client/-/regtest-client-0.2.0.tgz", + "integrity": "sha512-eIcC8Kle/wjS47pRlw7nJpstrJDWp0bkvVPl2KJpJcK3JDNW0fMxJgE/CGpMEUSjhhFXW1rtJMN6kyKw5NIzqg==", + "dev": true, + "requires": { + "bs58check": "^2.1.2", + "dhttp": "^3.0.3", + "randombytes": "^2.1.0" + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", + "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "tiny-secp256k1": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.1.tgz", + "integrity": "sha512-jA9WalQuhKun1svJrAVi9Vu8aUWKMfR7nMV903kHjrHTTY/IFa0petSq+Jk/Mv447dGD9LC8fGsmGRubBbcNng==", + "requires": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", + "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "typescript": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "dev": true + }, + "uglify-js": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.14.tgz", + "integrity": "sha512-dgyjIw8KFK6AyVl5vm2tEqPewv5TKGEiiVFLI1LbF+oHua/Njd8tZk3lIbF1AWU1rNdEg7scaceADb4zqCcWXg==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true, + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "unorm": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.5.0.tgz", + "integrity": "sha512-sMfSWoiRaXXeDZSXC+YRZ23H4xchQpwxjpw1tmfR+kgbBCaOgln4NI0LXejJIhnBuKINrB3WRn+ZI8IWssirVw==", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "varuint-bitcoin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.0.tgz", + "integrity": "sha512-jCEPG+COU/1Rp84neKTyDJQr478/hAfVp5xxYn09QEH0yBjbmPeMfuuQIrp+BUD83hybtYZKhr5elV3bvdV1bA==", + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "requires": { + "bs58check": "<3.0.0" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", + "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + }, + "yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/package.json b/package.json index fd4259be2..daa28132c 100644 --- a/package.json +++ b/package.json @@ -1,65 +1,84 @@ { "name": "bitcoinjs-lib", - "version": "1.5.0", + "version": "5.1.2", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", + "types": "./types/index.d.ts", + "engines": { + "node": ">=8.0.0" + }, "keywords": [ + "bitcoinjs", "bitcoin", - "browser", - "client", - "library" - ], - "contributors": [ - { - "name": "Daniel Cousens", - "email": "bitcoin@dcousens.com", - "url": "http://dcousens.com" - }, - { - "name": "Kyle Drake", - "email": "kyle@kyledrake.net", - "url": "http://kyledrake.net/" - }, - { - "name": "Wei Lu", - "email": "luwei.here@gmail.com", - "url": "http://weilu.github.io/" - }, - { - "name": "Stefan Thomas", - "email": "justmoon@members.fsf.org", - "url": "http://www.justmoon.net" - } + "browserify", + "javascript", + "bitcoinjs" ], "scripts": { - "compile": "browserify ./src/index.js -s bitcoin > bitcoin.js", - "coverage": "istanbul cover _mocha -- test/*.js", - "coveralls": "npm run-script coverage && coveralls < coverage/lcov.info", - "integration": "mocha --reporter list test/integration/*.js", - "standard": "standard", - "test": "npm run-script unit", - "unit": "istanbul test mocha -- --reporter list test/*.js" + "build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs", + "clean": "rimraf src types", + "coverage-report": "npm run build && npm run nobuild:coverage-report", + "coverage-html": "npm run build && npm run nobuild:coverage-html", + "coverage": "npm run build && npm run nobuild:coverage", + "format": "npm run prettier -- --write", + "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": { "type": "git", "url": "https://github.com/bitcoinjs/bitcoinjs-lib.git" }, + "files": [ + "src", + "types" + ], "dependencies": { - "bigi": "^1.4.0", - "bs58check": "^1.0.4", - "ecurve": "^1.0.0", - "typeforce": "^0.1.0" + "@types/node": "10.12.18", + "bech32": "^1.1.2", + "bip174": "^1.0.0", + "bip32": "^2.0.4", + "bip66": "^1.1.0", + "bitcoin-ops": "^1.4.0", + "bs58check": "^2.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.3", + "merkle-lib": "^2.0.10", + "pushdata-bitcoin": "^1.0.1", + "randombytes": "^2.0.1", + "tiny-secp256k1": "^1.1.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.0.4", + "wif": "^2.0.1" }, "devDependencies": { - "async": "^0.9.0", - "browserify": "^9.0.3", - "bs58": "^2.0.1", - "cb-helloblock": "^0.4.10", - "coveralls": "^2.11.2", - "istanbul": "^0.3.5", - "mocha": "^2.1.0", - "mocha-lcov-reporter": "0.0.1", - "sinon": "^1.12.2", - "standard": "^2.7.3" - } + "bip39": "^2.3.0", + "bip65": "^1.0.1", + "bip68": "^1.0.3", + "bn.js": "^4.11.8", + "bs58": "^4.0.0", + "dhttp": "^3.0.0", + "hoodwink": "^2.0.0", + "minimaldata": "^1.0.2", + "mocha": "^5.2.0", + "nyc": "^14.1.1", + "prettier": "1.16.4", + "proxyquire": "^2.0.1", + "regtest-client": "0.2.0", + "rimraf": "^2.6.3", + "tslint": "^5.16.0", + "typescript": "3.2.2" + }, + "license": "MIT" } diff --git a/src/address.js b/src/address.js index 4d8c67a26..e15c55e69 100644 --- a/src/address.js +++ b/src/address.js @@ -1,62 +1,91 @@ -var assert = require('assert') -var base58check = require('bs58check') -var typeForce = require('typeforce') -var networks = require('./networks') -var scripts = require('./scripts') - -function findScriptTypeByVersion (version) { - for (var networkName in networks) { - var network = networks[networkName] - - if (version === network.pubKeyHash) return 'pubkeyhash' - if (version === network.scriptHash) return 'scripthash' - } +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks = require('./networks'); +const payments = require('./payments'); +const bscript = require('./script'); +const types = require('./types'); +const bech32 = require('bech32'); +const bs58check = require('bs58check'); +const typeforce = require('typeforce'); +function fromBase58Check(address) { + 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 }; } - -function Address (hash, version) { - typeForce('Buffer', hash) - - assert.strictEqual(hash.length, 20, 'Invalid hash length') - assert.strictEqual(version & 0xff, version, 'Invalid version byte') - - this.hash = hash - this.version = version +exports.fromBase58Check = fromBase58Check; +function fromBech32(address) { + 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), + }; } - -Address.fromBase58Check = function (string) { - var payload = base58check.decode(string) - var version = payload.readUInt8(0) - var hash = payload.slice(1) - - return new Address(hash, version) +exports.fromBech32 = fromBech32; +function toBase58Check(hash, version) { + 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); } - -Address.fromOutputScript = function (script, network) { - network = network || networks.bitcoin - - if (scripts.isPubKeyHashOutput(script)) return new Address(script.chunks[2], network.pubKeyHash) - if (scripts.isScriptHashOutput(script)) return new Address(script.chunks[1], network.scriptHash) - - assert(false, script.toASM() + ' has no matching Address') +exports.toBase58Check = toBase58Check; +function toBech32(data, version, prefix) { + const words = bech32.toWords(data); + words.unshift(version); + return bech32.encode(prefix, words); } - -Address.prototype.toBase58Check = function () { - var payload = new Buffer(21) - payload.writeUInt8(this.version, 0) - this.hash.copy(payload, 1) - - return base58check.encode(payload) +exports.toBech32 = toBech32; +function fromOutputScript(output, network) { + // 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'); } - -Address.prototype.toOutputScript = function () { - var scriptType = findScriptTypeByVersion(this.version) - - if (scriptType === 'pubkeyhash') return scripts.pubKeyHashOutput(this.hash) - if (scriptType === 'scripthash') return scripts.scriptHashOutput(this.hash) - - assert(false, this.toString() + ' has no matching Script') +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 { + 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; + if (decodeBech32.data.length === 32) + return payments.p2wsh({ hash: decodeBech32.data }).output; + } + } + } + throw new Error(address + ' has no matching Script'); } - -Address.prototype.toString = Address.prototype.toBase58Check - -module.exports = Address +exports.toOutputScript = toOutputScript; diff --git a/src/block.js b/src/block.js index dca1f4cf3..22449fd5f 100644 --- a/src/block.js +++ b/src/block.js @@ -1,120 +1,242 @@ -var assert = require('assert') -var bufferutils = require('./bufferutils') -var crypto = require('./crypto') - -var Transaction = require('./transaction') - -function Block () { - this.version = 1 - this.prevHash = null - this.merkleRoot = null - this.timestamp = 0 - this.bits = 0 - this.nonce = 0 -} - -Block.fromBuffer = function (buffer) { - assert(buffer.length >= 80, 'Buffer too small (< 80 bytes)') - - var offset = 0 - function readSlice (n) { - offset += n - return buffer.slice(offset - n, offset) - } - - function readUInt32 () { - var i = buffer.readUInt32LE(offset) - offset += 4 - return i - } - - var block = new Block() - block.version = readUInt32() - block.prevHash = readSlice(32) - block.merkleRoot = readSlice(32) - block.timestamp = readUInt32() - block.bits = readUInt32() - block.nonce = readUInt32() - - if (buffer.length === 80) return block - - function readVarInt () { - var vi = bufferutils.readVarInt(buffer, offset) - offset += vi.size - return vi.number - } - - // FIXME: poor performance - function readTransaction () { - var tx = Transaction.fromBuffer(buffer.slice(offset), true) - - offset += tx.toBuffer().length - return tx - } - - var nTransactions = readVarInt() - block.transactions = [] - - for (var i = 0; i < nTransactions; ++i) { - var tx = readTransaction() - block.transactions.push(tx) - } - - return block -} - -Block.fromHex = function (hex) { - return Block.fromBuffer(new Buffer(hex, 'hex')) -} - -Block.prototype.getHash = function () { - return crypto.hash256(this.toBuffer(true)) -} - -Block.prototype.getId = function () { - return bufferutils.reverse(this.getHash()).toString('hex') -} - -Block.prototype.getUTCDate = function () { - var date = new Date(0) // epoch - date.setUTCSeconds(this.timestamp) - - return date +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils_1 = require('./bufferutils'); +const bcrypto = require('./crypto'); +const transaction_1 = require('./transaction'); +const types = require('./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', +); +class Block { + constructor() { + this.version = 1; + this.prevHash = undefined; + this.merkleRoot = undefined; + this.timestamp = 0; + this.witnessCommit = undefined; + this.bits = 0; + this.nonce = 0; + this.transactions = undefined; + } + static fromBuffer(buffer) { + if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); + let offset = 0; + const readSlice = n => { + 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) { + return Block.fromBuffer(Buffer.from(hex, 'hex')); + } + static calculateTarget(bits) { + 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, forWitness) { + 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; + } + getWitnessCommit() { + 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() { + if ( + this.witnessCommit instanceof Buffer && + this.witnessCommit.length === 32 + ) + return true; + if (this.getWitnessCommit() !== null) return true; + return false; + } + hasWitness() { + return anyTxHasWitness(this.transactions); + } + byteLength(headersOnly) { + if (headersOnly || !this.transactions) return 80; + 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; + } } - -Block.prototype.toBuffer = function (headersOnly) { - var buffer = new Buffer(80) - - var offset = 0 - function writeSlice (slice) { - slice.copy(buffer, offset) - offset += slice.length - } - - function writeUInt32 (i) { - buffer.writeUInt32LE(i, offset) - offset += 4 - } - - writeUInt32(this.version) - writeSlice(this.prevHash) - writeSlice(this.merkleRoot) - writeUInt32(this.timestamp) - writeUInt32(this.bits) - writeUInt32(this.nonce) - - if (headersOnly || !this.transactions) return buffer - - var txLenBuffer = bufferutils.varIntBuffer(this.transactions.length) - var txBuffers = this.transactions.map(function (tx) { - return tx.toBuffer() - }) - - return Buffer.concat([buffer, txLenBuffer].concat(txBuffers)) +exports.Block = Block; +function txesHaveWitnessCommit(transactions) { + 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 + ); } - -Block.prototype.toHex = function (headersOnly) { - return this.toBuffer(headersOnly).toString('hex') +function anyTxHasWitness(transactions) { + 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, + ), + ) + ); } - -module.exports = Block diff --git a/src/bufferutils.js b/src/bufferutils.js index afe03e622..54ce1c9c6 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,196 +1,40 @@ -var assert = require('assert') -var opcodes = require('./opcodes') - +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); // https://github.com/feross/buffer/blob/master/index.js#L1127 -function verifuint (value, max) { - assert(typeof value === 'number', 'cannot write a non-number as a number') - assert(value >= 0, 'specified a negative value for writing an unsigned value') - assert(value <= max, 'value is larger than maximum value for type') - assert(Math.floor(value) === value, 'value has a fractional component') -} - -function pushDataSize (i) { - return i < opcodes.OP_PUSHDATA1 ? 1 - : i < 0xff ? 2 - : i < 0xffff ? 3 - : 5 -} - -function readPushDataInt (buffer, offset) { - var opcode = buffer.readUInt8(offset) - var number, size - - // ~6 bit - if (opcode < opcodes.OP_PUSHDATA1) { - number = opcode - size = 1 - - // 8 bit - } else if (opcode === opcodes.OP_PUSHDATA1) { - number = buffer.readUInt8(offset + 1) - size = 2 - - // 16 bit - } else if (opcode === opcodes.OP_PUSHDATA2) { - number = buffer.readUInt16LE(offset + 1) - size = 3 - - // 32 bit - } else { - assert.equal(opcode, opcodes.OP_PUSHDATA4, 'Unexpected opcode') - - number = buffer.readUInt32LE(offset + 1) - size = 5 - } - - return { - opcode: opcode, - number: number, - size: size - } -} - -function readUInt64LE (buffer, offset) { - var a = buffer.readUInt32LE(offset) - var b = buffer.readUInt32LE(offset + 4) - b *= 0x100000000 - - verifuint(b + a, 0x001fffffffffffff) - - return b + a -} - -function readVarInt (buffer, offset) { - var t = buffer.readUInt8(offset) - var number, size - - // 8 bit - if (t < 253) { - number = t - size = 1 - - // 16 bit - } else if (t < 254) { - number = buffer.readUInt16LE(offset + 1) - size = 3 - - // 32 bit - } else if (t < 255) { - number = buffer.readUInt32LE(offset + 1) - size = 5 - - // 64 bit - } else { - number = readUInt64LE(buffer, offset + 1) - size = 9 - } - - return { - number: number, - size: size - } -} - -function writePushDataInt (buffer, number, offset) { - var size = pushDataSize(number) - - // ~6 bit - if (size === 1) { - buffer.writeUInt8(number, offset) - - // 8 bit - } else if (size === 2) { - buffer.writeUInt8(opcodes.OP_PUSHDATA1, offset) - buffer.writeUInt8(number, offset + 1) - - // 16 bit - } else if (size === 3) { - buffer.writeUInt8(opcodes.OP_PUSHDATA2, offset) - buffer.writeUInt16LE(number, offset + 1) - - // 32 bit - } else { - buffer.writeUInt8(opcodes.OP_PUSHDATA4, offset) - buffer.writeUInt32LE(number, offset + 1) +function verifuint(value, max) { + 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'); +} +function readUInt64LE(buffer, offset) { + const a = buffer.readUInt32LE(offset); + let b = buffer.readUInt32LE(offset + 4); + b *= 0x100000000; + verifuint(b + a, 0x001fffffffffffff); + return b + a; +} +exports.readUInt64LE = readUInt64LE; +function writeUInt64LE(buffer, value, offset) { + verifuint(value, 0x001fffffffffffff); + buffer.writeInt32LE(value & -1, offset); + buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); + return offset + 8; +} +exports.writeUInt64LE = writeUInt64LE; +function reverseBuffer(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 size -} - -function writeUInt64LE (buffer, value, offset) { - verifuint(value, 0x001fffffffffffff) - - buffer.writeInt32LE(value & -1, offset) - buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4) -} - -function varIntSize (i) { - return i < 253 ? 1 - : i < 0x10000 ? 3 - : i < 0x100000000 ? 5 - : 9 -} - -function writeVarInt (buffer, number, offset) { - var size = varIntSize(number) - - // 8 bit - if (size === 1) { - buffer.writeUInt8(number, offset) - - // 16 bit - } else if (size === 3) { - buffer.writeUInt8(253, offset) - buffer.writeUInt16LE(number, offset + 1) - - // 32 bit - } else if (size === 5) { - buffer.writeUInt8(254, offset) - buffer.writeUInt32LE(number, offset + 1) - - // 64 bit - } else { - buffer.writeUInt8(255, offset) - writeUInt64LE(buffer, number, offset + 1) - } - - return size -} - -function varIntBuffer (i) { - var size = varIntSize(i) - var buffer = new Buffer(size) - writeVarInt(buffer, i, 0) - - return buffer -} - -function equal (a, b) { - if (a.length !== b.length) return false - - for (var i = 0; i < a.length; ++i) { - if (a[i] !== b[i]) return false - } - - return true -} - -function reverse (buffer) { - var buffer2 = new Buffer(buffer) - Array.prototype.reverse.call(buffer2) - return buffer2 -} - -module.exports = { - equal: equal, - pushDataSize: pushDataSize, - readPushDataInt: readPushDataInt, - readUInt64LE: readUInt64LE, - readVarInt: readVarInt, - reverse: reverse, - varIntBuffer: varIntBuffer, - varIntSize: varIntSize, - writePushDataInt: writePushDataInt, - writeUInt64LE: writeUInt64LE, - writeVarInt: writeVarInt + return buffer; } +exports.reverseBuffer = reverseBuffer; diff --git a/src/classify.js b/src/classify.js new file mode 100644 index 000000000..70c600ce8 --- /dev/null +++ b/src/classify.js @@ -0,0 +1,59 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const script_1 = require('./script'); +const multisig = require('./templates/multisig'); +const nullData = require('./templates/nulldata'); +const pubKey = require('./templates/pubkey'); +const pubKeyHash = require('./templates/pubkeyhash'); +const scriptHash = require('./templates/scripthash'); +const witnessCommitment = require('./templates/witnesscommitment'); +const witnessPubKeyHash = require('./templates/witnesspubkeyhash'); +const witnessScriptHash = require('./templates/witnessscripthash'); +const types = { + P2MS: 'multisig', + NONSTANDARD: 'nonstandard', + NULLDATA: 'nulldata', + P2PK: 'pubkey', + P2PKH: 'pubkeyhash', + P2SH: 'scripthash', + P2WPKH: 'witnesspubkeyhash', + P2WSH: 'witnessscripthash', + WITNESS_COMMITMENT: 'witnesscommitment', +}; +exports.types = types; +function classifyOutput(script) { + 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 = script_1.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; +} +exports.output = classifyOutput; +function classifyInput(script, allowIncomplete) { + // XXX: optimization, below functions .decompile before use + const chunks = script_1.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; +} +exports.input = classifyInput; +function classifyWitness(script, allowIncomplete) { + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) throw new TypeError('Invalid script'); + if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH; + if (witnessScriptHash.input.check(chunks, allowIncomplete)) + return types.P2WSH; + return types.NONSTANDARD; +} +exports.witness = classifyWitness; diff --git a/src/crypto.js b/src/crypto.js index dec8a79bf..e7dd596bd 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,29 +1,35 @@ -var crypto = require('crypto') - -function hash160 (buffer) { - return ripemd160(sha256(buffer)) +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const createHash = require('create-hash'); +function ripemd160(buffer) { + try { + return createHash('rmd160') + .update(buffer) + .digest(); + } catch (err) { + return createHash('ripemd160') + .update(buffer) + .digest(); + } } - -function hash256 (buffer) { - return sha256(sha256(buffer)) +exports.ripemd160 = ripemd160; +function sha1(buffer) { + return createHash('sha1') + .update(buffer) + .digest(); } - -function ripemd160 (buffer) { - return crypto.createHash('rmd160').update(buffer).digest() +exports.sha1 = sha1; +function sha256(buffer) { + return createHash('sha256') + .update(buffer) + .digest(); } - -function sha1 (buffer) { - return crypto.createHash('sha1').update(buffer).digest() +exports.sha256 = sha256; +function hash160(buffer) { + return ripemd160(sha256(buffer)); } - -function sha256 (buffer) { - return crypto.createHash('sha256').update(buffer).digest() -} - -module.exports = { - hash160: hash160, - hash256: hash256, - ripemd160: ripemd160, - sha1: sha1, - sha256: sha256 +exports.hash160 = hash160; +function hash256(buffer) { + return sha256(sha256(buffer)); } +exports.hash256 = hash256; diff --git a/src/ecdsa.js b/src/ecdsa.js deleted file mode 100644 index 0455d198b..000000000 --- a/src/ecdsa.js +++ /dev/null @@ -1,228 +0,0 @@ -var assert = require('assert') -var crypto = require('crypto') -var typeForce = require('typeforce') - -var BigInteger = require('bigi') -var ECSignature = require('./ecsignature') - -var ZERO = new Buffer([0]) -var ONE = new Buffer([1]) - -// https://tools.ietf.org/html/rfc6979#section-3.2 -function deterministicGenerateK (curve, hash, d, checkSig) { - typeForce('Buffer', hash) - typeForce('BigInteger', d) - typeForce('Function', checkSig) - - // sanity check - assert.equal(hash.length, 32, 'Hash must be 256 bit') - - var x = d.toBuffer(32) - var k = new Buffer(32) - var v = new Buffer(32) - - // Step A, ignored as hash already provided - // Step B - v.fill(1) - - // Step C - k.fill(0) - - // Step D - k = crypto.createHmac('sha256', k) - .update(v) - .update(ZERO) - .update(x) - .update(hash) - .digest() - - // Step E - v = crypto.createHmac('sha256', k).update(v).digest() - - // Step F - k = crypto.createHmac('sha256', k) - .update(v) - .update(ONE) - .update(x) - .update(hash) - .digest() - - // Step G - v = crypto.createHmac('sha256', k).update(v).digest() - - // Step H1/H2a, ignored as tlen === qlen (256 bit) - // Step H2b - v = crypto.createHmac('sha256', k).update(v).digest() - - var T = BigInteger.fromBuffer(v) - - // Step H3, repeat until T is within the interval [1, n - 1] and is suitable for ECDSA - while ((T.signum() <= 0) || (T.compareTo(curve.n) >= 0) || !checkSig(T)) { - k = crypto.createHmac('sha256', k) - .update(v) - .update(ZERO) - .digest() - - v = crypto.createHmac('sha256', k).update(v).digest() - - // Step H1/H2a, again, ignored as tlen === qlen (256 bit) - // Step H2b again - v = crypto.createHmac('sha256', k).update(v).digest() - T = BigInteger.fromBuffer(v) - } - - return T -} - -function sign (curve, hash, d) { - var r, s - - var e = BigInteger.fromBuffer(hash) - var n = curve.n - var G = curve.G - - deterministicGenerateK(curve, hash, d, function (k) { - var Q = G.multiply(k) - - if (curve.isInfinity(Q)) - return false - - r = Q.affineX.mod(n) - if (r.signum() === 0) - return false - - s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n) - if (s.signum() === 0) - return false - - return true - }) - - var N_OVER_TWO = n.shiftRight(1) - - // enforce low S values, see bip62: 'low s values in signatures' - if (s.compareTo(N_OVER_TWO) > 0) { - s = n.subtract(s) - } - - return new ECSignature(r, s) -} - -function verifyRaw (curve, e, signature, Q) { - var n = curve.n - var G = curve.G - - var r = signature.r - var s = signature.s - - // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] - if (r.signum() <= 0 || r.compareTo(n) >= 0) return false - if (s.signum() <= 0 || s.compareTo(n) >= 0) return false - - // c = s^-1 mod n - var c = s.modInverse(n) - - // 1.4.4 Compute u1 = es^−1 mod n - // u2 = rs^−1 mod n - var u1 = e.multiply(c).mod(n) - var u2 = r.multiply(c).mod(n) - - // 1.4.5 Compute R = (xR, yR) = u1G + u2Q - var R = G.multiplyTwo(u1, Q, u2) - var v = R.affineX.mod(n) - - // 1.4.5 (cont.) Enforce R is not at infinity - if (curve.isInfinity(R)) return false - - // 1.4.8 If v = r, output "valid", and if v != r, output "invalid" - return v.equals(r) -} - -function verify (curve, hash, signature, Q) { - // 1.4.2 H = Hash(M), already done by the user - // 1.4.3 e = H - var e = BigInteger.fromBuffer(hash) - - return verifyRaw(curve, e, signature, Q) -} - -/** - * Recover a public key from a signature. - * - * See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public - * Key Recovery Operation". - * - * http://www.secg.org/download/aid-780/sec1-v2.pdf - */ -function recoverPubKey (curve, e, signature, i) { - assert.strictEqual(i & 3, i, 'Recovery param is more than two bits') - - var n = curve.n - var G = curve.G - - var r = signature.r - var s = signature.s - - assert(r.signum() > 0 && r.compareTo(n) < 0, 'Invalid r value') - assert(s.signum() > 0 && s.compareTo(n) < 0, 'Invalid s value') - - // A set LSB signifies that the y-coordinate is odd - var isYOdd = i & 1 - - // The more significant bit specifies whether we should use the - // first or second candidate key. - var isSecondKey = i >> 1 - - // 1.1 Let x = r + jn - var x = isSecondKey ? r.add(n) : r - var R = curve.pointFromX(isYOdd, x) - - // 1.4 Check that nR is at infinity - var nR = R.multiply(n) - assert(curve.isInfinity(nR), 'nR is not a valid curve point') - - // Compute -e from e - var eNeg = e.negate().mod(n) - - // 1.6.1 Compute Q = r^-1 (sR - eG) - // Q = r^-1 (sR + -eG) - var rInv = r.modInverse(n) - - var Q = R.multiplyTwo(s, G, eNeg).multiply(rInv) - curve.validate(Q) - - return Q -} - -/** - * Calculate pubkey extraction parameter. - * - * When extracting a pubkey from a signature, we have to - * distinguish four different cases. Rather than putting this - * burden on the verifier, Bitcoin includes a 2-bit value with the - * signature. - * - * This function simply tries all four cases and returns the value - * that resulted in a successful pubkey recovery. - */ -function calcPubKeyRecoveryParam (curve, e, signature, Q) { - for (var i = 0; i < 4; i++) { - var Qprime = recoverPubKey(curve, e, signature, i) - - // 1.6.2 Verify Q - if (Qprime.equals(Q)) { - return i - } - } - - throw new Error('Unable to find valid recovery factor') -} - -module.exports = { - calcPubKeyRecoveryParam: calcPubKeyRecoveryParam, - deterministicGenerateK: deterministicGenerateK, - recoverPubKey: recoverPubKey, - sign: sign, - verify: verify, - verifyRaw: verifyRaw -} diff --git a/src/eckey.js b/src/eckey.js deleted file mode 100644 index af7fda20b..000000000 --- a/src/eckey.js +++ /dev/null @@ -1,84 +0,0 @@ -var assert = require('assert') -var base58check = require('bs58check') -var crypto = require('crypto') -var ecdsa = require('./ecdsa') -var typeForce = require('typeforce') -var networks = require('./networks') - -var BigInteger = require('bigi') -var ECPubKey = require('./ecpubkey') - -var ecurve = require('ecurve') -var secp256k1 = ecurve.getCurveByName('secp256k1') - -function ECKey (d, compressed) { - assert(d.signum() > 0, 'Private key must be greater than 0') - assert(d.compareTo(ECKey.curve.n) < 0, 'Private key must be less than the curve order') - - var Q = ECKey.curve.G.multiply(d) - - this.d = d - this.pub = new ECPubKey(Q, compressed) -} - -// Constants -ECKey.curve = secp256k1 - -// Static constructors -ECKey.fromWIF = function (string) { - var payload = base58check.decode(string) - var compressed = false - - // Ignore the version byte - payload = payload.slice(1) - - if (payload.length === 33) { - assert.strictEqual(payload[32], 0x01, 'Invalid compression flag') - - // Truncate the compression flag - payload = payload.slice(0, -1) - compressed = true - } - - assert.equal(payload.length, 32, 'Invalid WIF payload length') - - var d = BigInteger.fromBuffer(payload) - return new ECKey(d, compressed) -} - -ECKey.makeRandom = function (compressed, rng) { - rng = rng || crypto.randomBytes - - var buffer = rng(32) - typeForce('Buffer', buffer) - assert.equal(buffer.length, 32, 'Expected 256-bit Buffer from RNG') - - var d = BigInteger.fromBuffer(buffer) - d = d.mod(ECKey.curve.n) - - return new ECKey(d, compressed) -} - -// Export functions -ECKey.prototype.toWIF = function (network) { - network = network || networks.bitcoin - - var bufferLen = this.pub.compressed ? 34 : 33 - var buffer = new Buffer(bufferLen) - - buffer.writeUInt8(network.wif, 0) - this.d.toBuffer(32).copy(buffer, 1) - - if (this.pub.compressed) { - buffer.writeUInt8(0x01, 33) - } - - return base58check.encode(buffer) -} - -// Operations -ECKey.prototype.sign = function (hash) { - return ecdsa.sign(ECKey.curve, hash, this.d) -} - -module.exports = ECKey diff --git a/src/ecpair.js b/src/ecpair.js new file mode 100644 index 000000000..91fe3a94a --- /dev/null +++ b/src/ecpair.js @@ -0,0 +1,107 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const NETWORKS = require('./networks'); +const types = require('./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), + }), +); +class ECPair { + constructor(__D, __Q, options) { + this.__D = __D; + this.__Q = __Q; + this.lowR = false; + 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() { + 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) { + if (!this.__D) throw new Error('Missing private key'); + if (lowR === undefined) lowR = this.lowR; + 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) { + 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); +} +exports.fromPrivateKey = fromPrivateKey; +function fromPublicKey(buffer, options) { + typeforce(ecc.isPoint, buffer); + typeforce(isOptions, options); + return new ECPair(undefined, buffer, options); +} +exports.fromPublicKey = fromPublicKey; +function fromWIF(wifString, network) { + const decoded = wif.decode(wifString); + const version = decoded.version; + // list of networks? + if (types.Array(network)) { + network = network + .filter(x => { + return version === x.wif; + }) + .pop(); + 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.wif) throw new Error('Invalid network version'); + } + return fromPrivateKey(decoded.privateKey, { + compressed: decoded.compressed, + network: network, + }); +} +exports.fromWIF = fromWIF; +function makeRandom(options) { + 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); +} +exports.makeRandom = makeRandom; diff --git a/src/ecpubkey.js b/src/ecpubkey.js deleted file mode 100644 index ecc37bc51..000000000 --- a/src/ecpubkey.js +++ /dev/null @@ -1,56 +0,0 @@ -var crypto = require('./crypto') -var ecdsa = require('./ecdsa') -var typeForce = require('typeforce') -var networks = require('./networks') - -var Address = require('./address') - -var ecurve = require('ecurve') -var secp256k1 = ecurve.getCurveByName('secp256k1') - -function ECPubKey (Q, compressed) { - if (compressed === undefined) { - compressed = true - } - - typeForce('Point', Q) - typeForce('Boolean', compressed) - - this.compressed = compressed - this.Q = Q -} - -// Constants -ECPubKey.curve = secp256k1 - -// Static constructors -ECPubKey.fromBuffer = function (buffer) { - var Q = ecurve.Point.decodeFrom(ECPubKey.curve, buffer) - return new ECPubKey(Q, Q.compressed) -} - -ECPubKey.fromHex = function (hex) { - return ECPubKey.fromBuffer(new Buffer(hex, 'hex')) -} - -// Operations -ECPubKey.prototype.getAddress = function (network) { - network = network || networks.bitcoin - - return new Address(crypto.hash160(this.toBuffer()), network.pubKeyHash) -} - -ECPubKey.prototype.verify = function (hash, signature) { - return ecdsa.verify(ECPubKey.curve, hash, signature, this.Q) -} - -// Export functions -ECPubKey.prototype.toBuffer = function () { - return this.Q.getEncoded(this.compressed) -} - -ECPubKey.prototype.toHex = function () { - return this.toBuffer().toString('hex') -} - -module.exports = ECPubKey diff --git a/src/ecsignature.js b/src/ecsignature.js deleted file mode 100644 index abab16154..000000000 --- a/src/ecsignature.js +++ /dev/null @@ -1,130 +0,0 @@ -var assert = require('assert') -var typeForce = require('typeforce') - -var BigInteger = require('bigi') - -function ECSignature (r, s) { - typeForce('BigInteger', r) - typeForce('BigInteger', s) - - this.r = r - this.s = s -} - -ECSignature.parseCompact = function (buffer) { - assert.equal(buffer.length, 65, 'Invalid signature length') - var i = buffer.readUInt8(0) - 27 - - // At most 3 bits - assert.equal(i, i & 7, 'Invalid signature parameter') - var compressed = !!(i & 4) - - // Recovery param only - i = i & 3 - - var r = BigInteger.fromBuffer(buffer.slice(1, 33)) - var s = BigInteger.fromBuffer(buffer.slice(33)) - - return { - compressed: compressed, - i: i, - signature: new ECSignature(r, s) - } -} - -ECSignature.fromDER = function (buffer) { - assert.equal(buffer.readUInt8(0), 0x30, 'Not a DER sequence') - assert.equal(buffer.readUInt8(1), buffer.length - 2, 'Invalid sequence length') - assert.equal(buffer.readUInt8(2), 0x02, 'Expected a DER integer') - - var rLen = buffer.readUInt8(3) - assert(rLen > 0, 'R length is zero') - - var offset = 4 + rLen - assert.equal(buffer.readUInt8(offset), 0x02, 'Expected a DER integer (2)') - - var sLen = buffer.readUInt8(offset + 1) - assert(sLen > 0, 'S length is zero') - - var rB = buffer.slice(4, offset) - var sB = buffer.slice(offset + 2) - offset += 2 + sLen - - if (rLen > 1 && rB.readUInt8(0) === 0x00) { - assert(rB.readUInt8(1) & 0x80, 'R value excessively padded') - } - - if (sLen > 1 && sB.readUInt8(0) === 0x00) { - assert(sB.readUInt8(1) & 0x80, 'S value excessively padded') - } - - assert.equal(offset, buffer.length, 'Invalid DER encoding') - var r = BigInteger.fromDERInteger(rB) - var s = BigInteger.fromDERInteger(sB) - - assert(r.signum() >= 0, 'R value is negative') - assert(s.signum() >= 0, 'S value is negative') - - return new ECSignature(r, s) -} - -// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) -ECSignature.parseScriptSignature = function (buffer) { - var hashType = buffer.readUInt8(buffer.length - 1) - var hashTypeMod = hashType & ~0x80 - - assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType ' + hashType) - - return { - signature: ECSignature.fromDER(buffer.slice(0, -1)), - hashType: hashType - } -} - -ECSignature.prototype.toCompact = function (i, compressed) { - if (compressed) { - i += 4 - } - - i += 27 - - var buffer = new Buffer(65) - buffer.writeUInt8(i, 0) - - this.r.toBuffer(32).copy(buffer, 1) - this.s.toBuffer(32).copy(buffer, 33) - - return buffer -} - -ECSignature.prototype.toDER = function () { - var rBa = this.r.toDERInteger() - var sBa = this.s.toDERInteger() - - var sequence = [] - - // INTEGER - sequence.push(0x02, rBa.length) - sequence = sequence.concat(rBa) - - // INTEGER - sequence.push(0x02, sBa.length) - sequence = sequence.concat(sBa) - - // SEQUENCE - sequence.unshift(0x30, sequence.length) - - return new Buffer(sequence) -} - -ECSignature.prototype.toScriptSignature = function (hashType) { - var hashTypeMod = hashType & ~0x80 - assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType ' + hashType) - - var hashTypeBuffer = new Buffer(1) - hashTypeBuffer.writeUInt8(hashType, 0) - - return Buffer.concat([this.toDER(), hashTypeBuffer]) -} - -module.exports = ECSignature diff --git a/src/hdnode.js b/src/hdnode.js deleted file mode 100644 index f0aa61399..000000000 --- a/src/hdnode.js +++ /dev/null @@ -1,280 +0,0 @@ -var assert = require('assert') -var base58check = require('bs58check') -var bcrypto = require('./crypto') -var crypto = require('crypto') -var typeForce = require('typeforce') -var networks = require('./networks') - -var BigInteger = require('bigi') -var ECKey = require('./eckey') -var ECPubKey = require('./ecpubkey') - -var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') - -function findBIP32NetworkByVersion (version) { - for (var name in networks) { - var network = networks[name] - - if (version === network.bip32.private || version === network.bip32.public) { - return network - } - } - - assert(false, 'Could not find network for ' + version.toString(16)) -} - -function HDNode (K, chainCode, network) { - network = network || networks.bitcoin - - typeForce('Buffer', chainCode) - - assert.equal(chainCode.length, 32, 'Expected chainCode length of 32, got ' + chainCode.length) - assert(network.bip32, 'Unknown BIP32 constants for network') - - this.chainCode = chainCode - this.depth = 0 - this.index = 0 - this.parentFingerprint = 0x00000000 - this.network = network - - if (K instanceof BigInteger) { - this.privKey = new ECKey(K, true) - this.pubKey = this.privKey.pub - } else if (K instanceof ECKey) { - assert(K.pub.compressed, 'ECKey must be compressed') - this.privKey = K - } else if (K instanceof ECPubKey) { - assert(K.compressed, 'ECPubKey must be compressed') - this.pubKey = K - } else { - this.pubKey = new ECPubKey(K, true) - } -} - -HDNode.MASTER_SECRET = new Buffer('Bitcoin seed') -HDNode.HIGHEST_BIT = 0x80000000 -HDNode.LENGTH = 78 - -HDNode.fromSeedBuffer = function (seed, network) { - typeForce('Buffer', seed) - - assert(seed.length >= 16, 'Seed should be at least 128 bits') - assert(seed.length <= 64, 'Seed should be at most 512 bits') - - var I = crypto.createHmac('sha512', HDNode.MASTER_SECRET).update(seed).digest() - var IL = I.slice(0, 32) - var IR = I.slice(32) - - // In case IL is 0 or >= n, the master key is invalid - // This is handled by `new ECKey` in the HDNode constructor - var pIL = BigInteger.fromBuffer(IL) - - return new HDNode(pIL, IR, network) -} - -HDNode.fromSeedHex = function (hex, network) { - return HDNode.fromSeedBuffer(new Buffer(hex, 'hex'), network) -} - -HDNode.fromBase58 = function (string, network) { - var buffer = base58check.decode(string) - assert.strictEqual(buffer.length, HDNode.LENGTH, 'Invalid buffer length') - - // 4 byte: version bytes - var version = buffer.readUInt32BE(0) - - if (network) { - assert(version === network.bip32.private || version === network.bip32.public, "Network doesn't match") - - // auto-detect - } else { - network = findBIP32NetworkByVersion(version) - } - - // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... - var depth = buffer.readUInt8(4) - - // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) - var parentFingerprint = buffer.readUInt32BE(5) - if (depth === 0) { - assert.strictEqual(parentFingerprint, 0x00000000, 'Invalid parent fingerprint') - } - - // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. - // This is encoded in MSB order. (0x00000000 if master key) - var index = buffer.readUInt32BE(9) - assert(depth > 0 || index === 0, 'Invalid index') - - // 32 bytes: the chain code - var chainCode = buffer.slice(13, 45) - var data, hd - - // 33 bytes: private key data (0x00 + k) - if (version === network.bip32.private) { - assert.strictEqual(buffer.readUInt8(45), 0x00, 'Invalid private key') - data = buffer.slice(46, 78) - var d = BigInteger.fromBuffer(data) - hd = new HDNode(d, chainCode, network) - - // 33 bytes: public key data (0x02 + X or 0x03 + X) - } else { - data = buffer.slice(45, 78) - var Q = ecurve.Point.decodeFrom(curve, data) - assert.equal(Q.compressed, true, 'Invalid public key') - - // Verify that the X coordinate in the public point corresponds to a point on the curve. - // If not, the extended public key is invalid. - curve.validate(Q) - - hd = new HDNode(Q, chainCode, network) - } - - hd.depth = depth - hd.index = index - hd.parentFingerprint = parentFingerprint - - return hd -} - -HDNode.prototype.getIdentifier = function () { - return bcrypto.hash160(this.pubKey.toBuffer()) -} - -HDNode.prototype.getFingerprint = function () { - return this.getIdentifier().slice(0, 4) -} - -HDNode.prototype.getAddress = function () { - return this.pubKey.getAddress(this.network) -} - -HDNode.prototype.neutered = function () { - var neutered = new HDNode(this.pubKey.Q, this.chainCode, this.network) - neutered.depth = this.depth - neutered.index = this.index - neutered.parentFingerprint = this.parentFingerprint - - return neutered -} - -HDNode.prototype.toBase58 = function (__isPrivate) { - assert.strictEqual(__isPrivate, undefined, 'Unsupported argument in 2.0.0') - - // Version - var version = this.privKey ? this.network.bip32.private : this.network.bip32.public - var buffer = new Buffer(HDNode.LENGTH) - - // 4 bytes: version bytes - buffer.writeUInt32BE(version, 0) - - // Depth - // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, .... - buffer.writeUInt8(this.depth, 4) - - // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) - buffer.writeUInt32BE(this.parentFingerprint, 5) - - // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. - // This is encoded in Big endian. (0x00000000 if master key) - buffer.writeUInt32BE(this.index, 9) - - // 32 bytes: the chain code - this.chainCode.copy(buffer, 13) - - // 33 bytes: the private key, or - if (this.privKey) { - // 0x00 + k for private keys - buffer.writeUInt8(0, 45) - this.privKey.d.toBuffer(32).copy(buffer, 46) - - // 33 bytes: the public key - } else { - // X9.62 encoding for public keys - this.pubKey.toBuffer().copy(buffer, 45) - } - - return base58check.encode(buffer) -} - -// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions -HDNode.prototype.derive = function (index) { - var isHardened = index >= HDNode.HIGHEST_BIT - var indexBuffer = new Buffer(4) - indexBuffer.writeUInt32BE(index, 0) - - var data - - // Hardened child - if (isHardened) { - assert(this.privKey, 'Could not derive hardened child key') - - // data = 0x00 || ser256(kpar) || ser32(index) - data = Buffer.concat([ - this.privKey.d.toBuffer(33), - indexBuffer - ]) - - // Normal child - } else { - // data = serP(point(kpar)) || ser32(index) - // = serP(Kpar) || ser32(index) - data = Buffer.concat([ - this.pubKey.toBuffer(), - indexBuffer - ]) - } - - var I = crypto.createHmac('sha512', this.chainCode).update(data).digest() - var IL = I.slice(0, 32) - var IR = I.slice(32) - - var pIL = BigInteger.fromBuffer(IL) - - // In case parse256(IL) >= n, proceed with the next value for i - if (pIL.compareTo(curve.n) >= 0) { - return this.derive(index + 1) - } - - // Private parent key -> private child key - var hd - if (this.privKey) { - // ki = parse256(IL) + kpar (mod n) - var ki = pIL.add(this.privKey.d).mod(curve.n) - - // In case ki == 0, proceed with the next value for i - if (ki.signum() === 0) { - return this.derive(index + 1) - } - - hd = new HDNode(ki, IR, this.network) - - // Public parent key -> public child key - } else { - // Ki = point(parse256(IL)) + Kpar - // = G*IL + Kpar - var Ki = curve.G.multiply(pIL).add(this.pubKey.Q) - - // In case Ki is the point at infinity, proceed with the next value for i - if (curve.isInfinity(Ki)) { - return this.derive(index + 1) - } - - hd = new HDNode(Ki, IR, this.network) - } - - hd.depth = this.depth + 1 - hd.index = index - hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) - - return hd -} - -HDNode.prototype.deriveHardened = function (index) { - // Only derives hardened private keys by default - return this.derive(index + HDNode.HIGHEST_BIT) -} - -HDNode.prototype.toString = HDNode.prototype.toBase58 - -module.exports = HDNode diff --git a/src/index.js b/src/index.js index 3b5317bce..bc71f0284 100644 --- a/src/index.js +++ b/src/index.js @@ -1,18 +1,26 @@ -module.exports = { - Address: require('./address'), - Block: require('./block'), - bufferutils: require('./bufferutils'), - crypto: require('./crypto'), - ecdsa: require('./ecdsa'), - ECKey: require('./eckey'), - ECPubKey: require('./ecpubkey'), - ECSignature: require('./ecsignature'), - message: require('./message'), - opcodes: require('./opcodes'), - HDNode: require('./hdnode'), - Script: require('./script'), - scripts: require('./scripts'), - Transaction: require('./transaction'), - TransactionBuilder: require('./transaction_builder'), - networks: require('./networks') -} +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bip32 = require('bip32'); +exports.bip32 = bip32; +const address = require('./address'); +exports.address = address; +const crypto = require('./crypto'); +exports.crypto = crypto; +const ECPair = require('./ecpair'); +exports.ECPair = ECPair; +const networks = require('./networks'); +exports.networks = networks; +const payments = require('./payments'); +exports.payments = payments; +const script = require('./script'); +exports.script = script; +var block_1 = require('./block'); +exports.Block = block_1.Block; +var psbt_1 = require('./psbt'); +exports.Psbt = psbt_1.Psbt; +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; diff --git a/src/message.js b/src/message.js deleted file mode 100644 index ecb1f1858..000000000 --- a/src/message.js +++ /dev/null @@ -1,54 +0,0 @@ -var bufferutils = require('./bufferutils') -var crypto = require('./crypto') -var ecdsa = require('./ecdsa') -var networks = require('./networks') - -var BigInteger = require('bigi') -var ECPubKey = require('./ecpubkey') -var ECSignature = require('./ecsignature') - -var ecurve = require('ecurve') -var ecparams = ecurve.getCurveByName('secp256k1') - -function magicHash (message, network) { - var magicPrefix = new Buffer(network.magicPrefix) - var messageBuffer = new Buffer(message) - var lengthBuffer = bufferutils.varIntBuffer(messageBuffer.length) - - var buffer = Buffer.concat([magicPrefix, lengthBuffer, messageBuffer]) - return crypto.hash256(buffer) -} - -function sign (privKey, message, network) { - network = network || networks.bitcoin - - var hash = magicHash(message, network) - var signature = privKey.sign(hash) - var e = BigInteger.fromBuffer(hash) - var i = ecdsa.calcPubKeyRecoveryParam(ecparams, e, signature, privKey.pub.Q) - - return signature.toCompact(i, privKey.pub.compressed) -} - -// TODO: network could be implied from address -function verify (address, signature, message, network) { - if (!Buffer.isBuffer(signature)) { - signature = new Buffer(signature, 'base64') - } - - network = network || networks.bitcoin - - var hash = magicHash(message, network) - var parsed = ECSignature.parseCompact(signature) - var e = BigInteger.fromBuffer(hash) - var Q = ecdsa.recoverPubKey(ecparams, e, parsed.signature, parsed.i) - - var pubKey = new ECPubKey(Q, parsed.compressed) - return pubKey.getAddress(network).toString() === address.toString() -} - -module.exports = { - magicHash: magicHash, - sign: sign, - verify: verify -} diff --git a/src/networks.js b/src/networks.js index 7395aa4b3..0c31fe1c8 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,149 +1,35 @@ -// https://en.bitcoin.it/wiki/List_of_address_prefixes -// Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 - -var networks = { - bitcoin: { - magicPrefix: '\x18Bitcoin Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, - dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162 - feePerKb: 10000, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/main.cpp#L53 - estimateFee: estimateFee('bitcoin') - }, - testnet: { - magicPrefix: '\x18Bitcoin Signed Message:\n', - bip32: { - public: 0x043587cf, - private: 0x04358394 - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, - dustThreshold: 546, - feePerKb: 10000, - estimateFee: estimateFee('testnet') - }, - litecoin: { - magicPrefix: '\x19Litecoin Signed Message:\n', - bip32: { - public: 0x019da462, - private: 0x019d9cfe - }, - pubKeyHash: 0x30, - scriptHash: 0x05, - wif: 0xb0, - dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365 - dustSoftThreshold: 100000, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.h#L53 - feePerKb: 100000, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L56 - estimateFee: estimateFee('litecoin') - }, - dogecoin: { - magicPrefix: '\x19Dogecoin Signed Message:\n', - bip32: { - public: 0x02facafd, - private: 0x02fac398 - }, - pubKeyHash: 0x1e, - scriptHash: 0x16, - wif: 0x9e, - dustThreshold: 0, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160 - dustSoftThreshold: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.h#L62 - feePerKb: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.cpp#L58 - estimateFee: estimateFee('dogecoin') - }, - viacoin: { - magicPrefix: '\x18Viacoin Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x47, - scriptHash: 0x21, - wif: 0xc7, - dustThreshold: 560, - dustSoftThreshold: 100000, - feePerKb: 100000, // - estimateFee: estimateFee('viacoin') - }, - viacointestnet: { - magicPrefix: '\x18Viacoin Signed Message:\n', - bip32: { - public: 0x043587cf, - private: 0x04358394 - }, - pubKeyHash: 0x7f, - scriptHash: 0xc4, - wif: 0xff, - dustThreshold: 560, - dustSoftThreshold: 100000, - feePerKb: 100000, - estimateFee: estimateFee('viacointestnet') - }, - gamerscoin: { - magicPrefix: '\x19Gamerscoin Signed Message:\n', - bip32: { - public: 0x019da462, - private: 0x019d9cfe - }, - pubKeyHash: 0x26, - scriptHash: 0x05, - wif: 0xA6, - dustThreshold: 0, // https://github.com/gamers-coin/gamers-coinv3/blob/master/src/main.cpp#L358-L363 - dustSoftThreshold: 100000, // https://github.com/gamers-coin/gamers-coinv3/blob/master/src/main.cpp#L51 - feePerKb: 100000, // https://github.com/gamers-coin/gamers-coinv3/blob/master/src/main.cpp#L54 - estimateFee: estimateFee('gamerscoin') - }, - jumbucks: { - magicPrefix: '\x19Jumbucks Signed Message:\n', - bip32: { - public: 0x037a689a, - private: 0x037a6460 - }, - pubKeyHash: 0x2b, - scriptHash: 0x05, - wif: 0xab, - dustThreshold: 0, - dustSoftThreshold: 10000, - feePerKb: 10000, - estimateFee: estimateFee('jumbucks') - }, - zetacoin: { - magicPrefix: '\x18Zetacoin Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x50, - scriptHash: 0x09, - wif: 0xe0, - dustThreshold: 546, // https://github.com/zetacoin/zetacoin/blob/master/src/core.h#L159 - feePerKb: 10000, // https://github.com/zetacoin/zetacoin/blob/master/src/main.cpp#L54 - estimateFee: estimateFee('zetacoin') - } -} - -function estimateFee (type) { - return function (tx) { - var network = networks[type] - var baseFee = network.feePerKb - var byteSize = tx.toBuffer().length - - var fee = baseFee * Math.ceil(byteSize / 1000) - if (network.dustSoftThreshold === undefined) return fee - - tx.outs.forEach(function (e) { - if (e.value < network.dustSoftThreshold) { - fee += baseFee - } - }) - - return fee - } -} - -module.exports = networks +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.bitcoin = { + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, +}; +exports.regtest = { + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bcrt', + bip32: { + public: 0x043587cf, + 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, +}; diff --git a/src/opcodes.js b/src/opcodes.js deleted file mode 100644 index 2805dfb9b..000000000 --- a/src/opcodes.js +++ /dev/null @@ -1,138 +0,0 @@ -module.exports = { - // push value - OP_FALSE: 0, - OP_0: 0, - OP_PUSHDATA1: 76, - OP_PUSHDATA2: 77, - OP_PUSHDATA4: 78, - OP_1NEGATE: 79, - OP_RESERVED: 80, - OP_1: 81, - OP_TRUE: 81, - OP_2: 82, - OP_3: 83, - OP_4: 84, - OP_5: 85, - OP_6: 86, - OP_7: 87, - OP_8: 88, - OP_9: 89, - OP_10: 90, - OP_11: 91, - OP_12: 92, - OP_13: 93, - OP_14: 94, - OP_15: 95, - OP_16: 96, - - // control - OP_NOP: 97, - OP_VER: 98, - OP_IF: 99, - OP_NOTIF: 100, - OP_VERIF: 101, - OP_VERNOTIF: 102, - OP_ELSE: 103, - OP_ENDIF: 104, - OP_VERIFY: 105, - OP_RETURN: 106, - - // stack ops - OP_TOALTSTACK: 107, - OP_FROMALTSTACK: 108, - OP_2DROP: 109, - OP_2DUP: 110, - OP_3DUP: 111, - OP_2OVER: 112, - OP_2ROT: 113, - OP_2SWAP: 114, - OP_IFDUP: 115, - OP_DEPTH: 116, - OP_DROP: 117, - OP_DUP: 118, - OP_NIP: 119, - OP_OVER: 120, - OP_PICK: 121, - OP_ROLL: 122, - OP_ROT: 123, - OP_SWAP: 124, - OP_TUCK: 125, - - // splice ops - OP_CAT: 126, - OP_SUBSTR: 127, - OP_LEFT: 128, - OP_RIGHT: 129, - OP_SIZE: 130, - - // bit logic - OP_INVERT: 131, - OP_AND: 132, - OP_OR: 133, - OP_XOR: 134, - OP_EQUAL: 135, - OP_EQUALVERIFY: 136, - OP_RESERVED1: 137, - OP_RESERVED2: 138, - - // numeric - OP_1ADD: 139, - OP_1SUB: 140, - OP_2MUL: 141, - OP_2DIV: 142, - OP_NEGATE: 143, - OP_ABS: 144, - OP_NOT: 145, - OP_0NOTEQUAL: 146, - - OP_ADD: 147, - OP_SUB: 148, - OP_MUL: 149, - OP_DIV: 150, - OP_MOD: 151, - OP_LSHIFT: 152, - OP_RSHIFT: 153, - - OP_BOOLAND: 154, - OP_BOOLOR: 155, - OP_NUMEQUAL: 156, - OP_NUMEQUALVERIFY: 157, - OP_NUMNOTEQUAL: 158, - OP_LESSTHAN: 159, - OP_GREATERTHAN: 160, - OP_LESSTHANOREQUAL: 161, - OP_GREATERTHANOREQUAL: 162, - OP_MIN: 163, - OP_MAX: 164, - - OP_WITHIN: 165, - - // crypto - OP_RIPEMD160: 166, - OP_SHA1: 167, - OP_SHA256: 168, - OP_HASH160: 169, - OP_HASH256: 170, - OP_CODESEPARATOR: 171, - OP_CHECKSIG: 172, - OP_CHECKSIGVERIFY: 173, - OP_CHECKMULTISIG: 174, - OP_CHECKMULTISIGVERIFY: 175, - - // expansion - OP_NOP1: 176, - OP_NOP2: 177, - OP_NOP3: 178, - OP_NOP4: 179, - OP_NOP5: 180, - OP_NOP6: 181, - OP_NOP7: 182, - OP_NOP8: 183, - OP_NOP9: 184, - OP_NOP10: 185, - - // template matching params - OP_PUBKEYHASH: 253, - OP_PUBKEY: 254, - OP_INVALIDOPCODE: 255 -} diff --git a/src/payments/embed.js b/src/payments/embed.js new file mode 100644 index 000000000..19df35df6 --- /dev/null +++ b/src/payments/embed.js @@ -0,0 +1,49 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const typef = require('typeforce'); +const OPS = bscript.OPS; +function stacksEqual(a, b) { + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); +} +// output: OP_RETURN ... +function p2data(a, opts) { + 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 || networks_1.bitcoin; + const o = { name: 'embed', network }; + lazy.prop(o, 'output', () => { + if (!a.data) return; + return bscript.compile([OPS.OP_RETURN].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)) + throw new TypeError('Data mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2data = p2data; diff --git a/src/payments/index.js b/src/payments/index.js new file mode 100644 index 000000000..ddab97768 --- /dev/null +++ b/src/payments/index.js @@ -0,0 +1,18 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const embed_1 = require('./embed'); +exports.embed = embed_1.p2data; +const p2ms_1 = require('./p2ms'); +exports.p2ms = p2ms_1.p2ms; +const p2pk_1 = require('./p2pk'); +exports.p2pk = p2pk_1.p2pk; +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 +// witness commitment diff --git a/src/payments/lazy.js b/src/payments/lazy.js new file mode 100644 index 000000000..1a7152158 --- /dev/null +++ b/src/payments/lazy.js @@ -0,0 +1,31 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +function prop(object, name, f) { + Object.defineProperty(object, name, { + configurable: true, + enumerable: true, + get() { + const _value = f.call(this); + this[name] = _value; + return _value; + }, + set(_value) { + Object.defineProperty(this, name, { + configurable: true, + enumerable: true, + value: _value, + writable: true, + }); + }, + }); +} +exports.prop = prop; +function value(f) { + let _value; + return () => { + if (_value !== undefined) return _value; + _value = f(); + return _value; + }; +} +exports.value = value; diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js new file mode 100644 index 000000000..9fed7880a --- /dev/null +++ b/src/payments/p2ms.js @@ -0,0 +1,145 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const OPS = bscript.OPS; +const typef = require('typeforce'); +const ecc = require('tiny-secp256k1'); +const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 +function stacksEqual(a, b) { + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); +} +// input: OP_0 [signatures ...] +// output: m [pubKeys ...] n OP_CHECKMULTISIG +function p2ms(a, opts) { + if ( + !a.input && + !a.output && + !(a.pubkeys && a.m !== undefined) && + !a.signatures + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + function isAcceptableSignature(x) { + return ( + bscript.isCanonicalScriptSignature(x) || + (opts.allowIncomplete && x === OPS.OP_0) !== undefined + ); + } + typef( + { + network: typef.maybe(typef.Object), + m: typef.maybe(typef.Number), + n: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), + pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), + signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), + input: typef.maybe(typef.Buffer), + }, + a, + ); + const network = a.network || networks_1.bitcoin; + const o = { network }; + let chunks = []; + let decoded = false; + function decode(output) { + if (decoded) return; + decoded = true; + chunks = bscript.decompile(output); + o.m = chunks[0] - OP_INT_BASE; + o.n = chunks[chunks.length - 2] - OP_INT_BASE; + o.pubkeys = chunks.slice(1, -2); + } + lazy.prop(o, 'output', () => { + if (!a.m) return; + if (!o.n) return; + if (!a.pubkeys) return; + return bscript.compile( + [].concat( + OP_INT_BASE + a.m, + a.pubkeys, + OP_INT_BASE + o.n, + OPS.OP_CHECKMULTISIG, + ), + ); + }); + lazy.prop(o, 'm', () => { + if (!o.output) return; + decode(o.output); + return o.m; + }); + lazy.prop(o, 'n', () => { + if (!o.pubkeys) return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', () => { + if (!a.output) return; + decode(a.output); + return o.pubkeys; + }); + lazy.prop(o, 'signatures', () => { + if (!a.input) return; + return bscript.decompile(a.input).slice(1); + }); + lazy.prop(o, 'input', () => { + if (!a.signatures) return; + return bscript.compile([OPS.OP_0].concat(a.signatures)); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + lazy.prop(o, 'name', () => { + if (!o.m || !o.n) return; + return `p2ms(${o.m} of ${o.n})`; + }); + // extended validation + if (opts.validate) { + if (a.output) { + decode(a.output); + 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 (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) + throw new TypeError('Output is invalid'); + if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3) + 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.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) + throw new TypeError('Pubkeys mismatch'); + } + if (a.pubkeys) { + if (a.n !== undefined && a.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 (a.signatures) { + if (a.signatures.length < o.m) + 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[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 (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); +} +exports.p2ms = p2ms; diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js new file mode 100644 index 000000000..702669e21 --- /dev/null +++ b/src/payments/p2pk.js @@ -0,0 +1,72 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); +// input: {signature} +// output: {pubKey} OP_CHECKSIG +function p2pk(a, opts) { + if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer), + }, + a, + ); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const network = a.network || networks_1.bitcoin; + const o = { name: 'p2pk', network }; + lazy.prop(o, 'output', () => { + if (!a.pubkey) return; + return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.output) return; + return a.output.slice(1, -1); + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.signature) return; + return bscript.compile([a.signature]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + // extended validation + if (opts.validate) { + if (a.output) { + if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) + 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.input && !a.input.equals(o.input)) + throw new TypeError('Signature mismatch'); + } + if (a.input) { + if (_chunks().length !== 1) throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(o.signature)) + throw new TypeError('Input has invalid signature'); + } + } + return Object.assign(o, a); +} +exports.p2pk = p2pk; diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js new file mode 100644 index 000000000..94c801f93 --- /dev/null +++ b/src/payments/p2pkh.js @@ -0,0 +1,132 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); +const bs58check = require('bs58check'); +// input: {signature} {pubkey} +// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG +function p2pkh(a, opts) { + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + output: typef.maybe(typef.BufferN(25)), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer), + }, + a, + ); + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const network = a.network || networks_1.bitcoin; + const o = { name: 'p2pkh', network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(network.pubKeyHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + 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', () => { + if (!o.hash) return; + return bscript.compile([ + OPS.OP_DUP, + OPS.OP_HASH160, + o.hash, + OPS.OP_EQUALVERIFY, + OPS.OP_CHECKSIG, + ]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.input) return; + return _chunks()[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return bscript.compile([a.signature, a.pubkey]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().version !== network.pubKeyHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 25 || + a.output[0] !== OPS.OP_DUP || + a.output[1] !== OPS.OP_HASH160 || + a.output[2] !== 0x14 || + a.output[23] !== OPS.OP_EQUALVERIFY || + a.output[24] !== OPS.OP_CHECKSIG + ) + throw new TypeError('Output is invalid'); + 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) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + if (a.input) { + const chunks = _chunks(); + if (chunks.length !== 2) throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(chunks[0])) + throw new TypeError('Input has invalid signature'); + if (!ecc.isPoint(chunks[1])) + throw new TypeError('Input has invalid pubkey'); + if (a.signature && !a.signature.equals(chunks[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(chunks[1])) + 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); +} +exports.p2pkh = p2pkh; diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js new file mode 100644 index 000000000..42b1e23da --- /dev/null +++ b/src/payments/p2sh.js @@ -0,0 +1,183 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const bs58check = require('bs58check'); +function stacksEqual(a, b) { + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); +} +// input: [redeemScriptSig ...] {redeemScript} +// witness: +// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL +function p2sh(a, opts) { + if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + 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), + output: typef.maybe(typef.Buffer), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + } + const o = { network }; + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const _redeem = lazy.value(() => { + const chunks = _chunks(); + return { + network, + output: chunks[chunks.length - 1], + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [], + }; + }); + // output dependents + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(o.network.scriptHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + // in order of least effort + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().hash; + if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); + }); + // input dependents + lazy.prop(o, 'redeem', () => { + if (!a.input) return; + return _redeem(); + }); + lazy.prop(o, 'input', () => { + if (!a.redeem || !a.redeem.input || !a.redeem.output) return; + return bscript.compile( + [].concat(bscript.decompile(a.redeem.input), a.redeem.output), + ); + }); + lazy.prop(o, 'witness', () => { + if (o.redeem && o.redeem.witness) return o.redeem.witness; + if (o.input) return []; + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2sh']; + if (o.redeem !== undefined) nameParts.push(o.redeem.name); + return nameParts.join('-'); + }); + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().version !== network.scriptHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 23 || + a.output[0] !== OPS.OP_HASH160 || + a.output[1] !== 0x14 || + a.output[22] !== OPS.OP_EQUAL + ) + throw new TypeError('Output is invalid'); + 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 + const checkRedeem = redeem => { + // is the redeem output empty/invalid? + if (redeem.output) { + const decompile = bscript.decompile(redeem.output); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output too short'); + // match hash against other sources + const hash2 = bcrypto.hash160(redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (redeem.input) { + const hasInput = redeem.input.length > 0; + const hasWitness = redeem.witness && redeem.witness.length > 0; + if (!hasInput && !hasWitness) throw new TypeError('Empty input'); + if (hasInput && hasWitness) + throw new TypeError('Input and witness provided'); + if (hasInput) { + const richunks = bscript.decompile(redeem.input); + if (!bscript.isPushOnly(richunks)) + throw new TypeError('Non push-only scriptSig'); + } + } + }; + if (a.input) { + const chunks = _chunks(); + if (!chunks || chunks.length < 1) throw new TypeError('Input too short'); + if (!Buffer.isBuffer(_redeem().output)) + throw new TypeError('Input is invalid'); + checkRedeem(_redeem()); + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + if (a.input) { + 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); + } + if (a.witness) { + if ( + a.redeem && + a.redeem.witness && + !stacksEqual(a.redeem.witness, a.witness) + ) + throw new TypeError('Witness and redeem.witness mismatch'); + } + } + return Object.assign(o, a); +} +exports.p2sh = p2sh; diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js new file mode 100644 index 000000000..b32e8088c --- /dev/null +++ b/src/payments/p2wpkh.js @@ -0,0 +1,128 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); +const bech32 = require('bech32'); +const EMPTY_BUFFER = Buffer.alloc(0); +// witness: {signature} {pubKey} +// input: <> +// output: OP_0 {pubKeyHash} +function p2wpkh(a, opts) { + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + input: typef.maybe(typef.BufferN(0)), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.BufferN(22)), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + 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 { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const network = a.network || networks_1.bitcoin; + const o = { name: 'p2wpkh', network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().data; + if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (!a.witness) return; + return a.witness[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.witness) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return [a.signature, a.pubkey]; + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 20) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 22 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x14 + ) + 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) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + if (a.witness) { + 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 (!ecc.isPoint(a.witness[1])) + throw new TypeError('Witness has invalid pubkey'); + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(a.witness[1])) + 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); +} +exports.p2wpkh = p2wpkh; diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js new file mode 100644 index 000000000..6a4aa245a --- /dev/null +++ b/src/payments/p2wsh.js @@ -0,0 +1,181 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = require('../crypto'); +const networks_1 = require('../networks'); +const bscript = require('../script'); +const lazy = require('./lazy'); +const typef = require('typeforce'); +const OPS = bscript.OPS; +const bech32 = require('bech32'); +const EMPTY_BUFFER = Buffer.alloc(0); +function stacksEqual(a, b) { + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); +} +// input: <> +// witness: [redeemScriptSig ...] {redeemScript} +// output: OP_0 {sha256(redeemScript)} +function p2wsh(a, opts) { + if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + 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), + output: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }), + input: typef.maybe(typef.BufferN(0)), + 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 { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const _rchunks = lazy.value(() => { + return bscript.decompile(a.redeem.input); + }); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.bitcoin; + } + const o = { network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'redeem', () => { + if (!a.witness) return; + return { + output: a.witness[a.witness.length - 1], + input: EMPTY_BUFFER, + witness: a.witness.slice(0, -1), + }; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + // transform redeem input to witness stack? + if ( + a.redeem && + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.output && + a.redeem.output.length > 0 + ) { + const stack = bscript.toStack(_rchunks()); + // assign, and blank the existing input + o.redeem = Object.assign({ witness: stack }, a.redeem); + o.redeem.input = EMPTY_BUFFER; + return [].concat(stack, a.redeem.output); + } + if (!a.redeem) return; + if (!a.redeem.output) return; + if (!a.redeem.witness) return; + return [].concat(a.redeem.witness, a.redeem.output); + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2wsh']; + if (o.redeem !== undefined) nameParts.push(o.redeem.name); + return nameParts.join('-'); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + if (a.address) { + if (_address().prefix !== network.bech32) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + 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.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + // is there two redeem sources? + if ( + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.witness && + a.redeem.witness.length > 0 + ) + throw new TypeError('Ambiguous witness source'); + // is the redeem output non-empty? + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output).length === 0) + throw new TypeError('Redeem.output is invalid'); + // match hash against other sources + const hash2 = bcrypto.sha256(a.redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (a.redeem.input && !bscript.isPushOnly(_rchunks())) + 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) { + 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); +} +exports.p2wsh = p2wsh; diff --git a/src/psbt.js b/src/psbt.js new file mode 100644 index 000000000..051de6e57 --- /dev/null +++ b/src/psbt.js @@ -0,0 +1,1137 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bip174_1 = require('bip174'); +const varuint = require('bip174/src/lib/converter/varint'); +const utils_1 = require('bip174/src/lib/utils'); +const address_1 = require('./address'); +const bufferutils_1 = require('./bufferutils'); +const crypto_1 = require('./crypto'); +const ecpair_1 = require('./ecpair'); +const networks_1 = require('./networks'); +const payments = require('./payments'); +const bscript = require('./script'); +const transaction_1 = require('./transaction'); +/** + * These are the default arguments for a Psbt instance. + */ +const DEFAULT_OPTS = { + /** + * A bitcoinjs Network object. This is only used if you pass an `address` + * parameter to addOutput. Otherwise it is not needed and can be left default. + */ + network: networks_1.bitcoin, + /** + * When extractTransaction is called, the fee rate is checked. + * THIS IS NOT TO BE RELIED ON. + * It is only here as a last ditch effort to prevent sending a 500 BTC fee etc. + */ + maximumFeeRate: 5000, +}; +/** + * Psbt class can parse and generate a PSBT binary based off of the BIP174. + * There are 6 roles that this class fulfills. (Explained in BIP174) + * + * Creator: This can be done with `new Psbt()` + * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, + * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to + * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, + * `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)` + * addInput requires hash: Buffer | string; and index: number; as attributes + * and can also include any attributes that are used in updateInput method. + * addOutput requires script: Buffer; and value: number; and likewise can include + * data for updateOutput. + * For a list of what attributes should be what types. Check the bip174 library. + * Also, check the integration tests for some examples of usage. + * Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input + * information for your pubkey or pubkeyhash, and only sign inputs where it finds + * your info. Or you can explicitly sign a specific input with signInput and + * signInputAsync. For the async methods you can create a SignerAsync object + * and use something like a hardware wallet to sign with. (You must implement this) + * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` + * the psbt calling combine will always have precedence when a conflict occurs. + * Combine checks if the internal bitcoin transaction is the same, so be sure that + * all sequences, version, locktime, etc. are the same before combining. + * Input Finalizer: This role is fairly important. Not only does it need to construct + * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. + * Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()` + * Running any finalize method will delete any data in the input(s) that are no longer + * needed due to the finalized scripts containing the information. + * Transaction Extractor: This role will perform some checks before returning a + * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. + */ +class Psbt { + constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) { + this.data = data; + // set defaults + this.opts = Object.assign({}, DEFAULT_OPTS, opts); + this.__CACHE = { + __NON_WITNESS_UTXO_TX_CACHE: [], + __NON_WITNESS_UTXO_BUF_CACHE: [], + __TX_IN_CACHE: {}, + __TX: this.data.globalMap.unsignedTx.tx, + }; + if (this.data.inputs.length === 0) this.setVersion(2); + // Make data hidden when enumerating + const dpew = (obj, attr, enumerable, writable) => + Object.defineProperty(obj, attr, { + enumerable, + writable, + }); + dpew(this, '__CACHE', false, true); + dpew(this, 'opts', false, true); + } + static fromBase64(data, opts = {}) { + const buffer = Buffer.from(data, 'base64'); + return this.fromBuffer(buffer, opts); + } + static fromHex(data, opts = {}) { + const buffer = Buffer.from(data, 'hex'); + return this.fromBuffer(buffer, opts); + } + static fromBuffer(buffer, opts = {}) { + const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); + const psbt = new Psbt(opts, psbtBase); + checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); + return psbt; + } + get inputCount() { + return this.data.inputs.length; + } + combine(...those) { + this.data.combine(...those.map(o => o.data)); + return this; + } + clone() { + // TODO: more efficient cloning + const res = Psbt.fromBuffer(this.data.toBuffer()); + res.opts = JSON.parse(JSON.stringify(this.opts)); + return res; + } + setMaximumFeeRate(satoshiPerByte) { + check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw + this.opts.maximumFeeRate = satoshiPerByte; + } + setVersion(version) { + check32Bit(version); + checkInputsForPartialSig(this.data.inputs, 'setVersion'); + const c = this.__CACHE; + c.__TX.version = version; + c.__EXTRACTED_TX = undefined; + return this; + } + setLocktime(locktime) { + check32Bit(locktime); + checkInputsForPartialSig(this.data.inputs, 'setLocktime'); + const c = this.__CACHE; + c.__TX.locktime = locktime; + c.__EXTRACTED_TX = undefined; + return this; + } + setInputSequence(inputIndex, sequence) { + check32Bit(sequence); + checkInputsForPartialSig(this.data.inputs, 'setInputSequence'); + const c = this.__CACHE; + if (c.__TX.ins.length <= inputIndex) { + throw new Error('Input index too high'); + } + c.__TX.ins[inputIndex].sequence = sequence; + c.__EXTRACTED_TX = undefined; + return this; + } + addInputs(inputDatas) { + inputDatas.forEach(inputData => this.addInput(inputData)); + return this; + } + addInput(inputData) { + checkInputsForPartialSig(this.data.inputs, 'addInput'); + const c = this.__CACHE; + this.data.addInput(inputData); + const txIn = c.__TX.ins[c.__TX.ins.length - 1]; + checkTxInputCache(c, txIn); + const inputIndex = this.data.inputs.length - 1; + const input = this.data.inputs[inputIndex]; + if (input.nonWitnessUtxo) { + addNonWitnessTxCache(this.__CACHE, input, inputIndex); + } + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + addOutputs(outputDatas) { + outputDatas.forEach(outputData => this.addOutput(outputData)); + return this; + } + addOutput(outputData) { + checkInputsForPartialSig(this.data.inputs, 'addOutput'); + const { address } = outputData; + if (typeof address === 'string') { + const { network } = this.opts; + const script = address_1.toOutputScript(address, network); + outputData = Object.assign(outputData, { script }); + } + const c = this.__CACHE; + this.data.addOutput(outputData); + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + extractTransaction(disableFeeCheck) { + if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized'); + const c = this.__CACHE; + if (!disableFeeCheck) { + checkFees(this, c, this.opts); + } + if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX; + const tx = c.__TX.clone(); + inputFinalizeGetAmts(this.data.inputs, tx, c, true); + return tx; + } + getFeeRate() { + if (!this.data.inputs.every(isFinalized)) + throw new Error('PSBT must be finalized to calculate fee rate'); + const c = this.__CACHE; + if (c.__FEE_RATE) return c.__FEE_RATE; + let tx; + let mustFinalize = true; + if (c.__EXTRACTED_TX) { + tx = c.__EXTRACTED_TX; + mustFinalize = false; + } else { + tx = c.__TX.clone(); + } + inputFinalizeGetAmts(this.data.inputs, tx, c, mustFinalize); + return c.__FEE_RATE; + } + finalizeAllInputs() { + utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one + range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); + return this; + } + finalizeInput(inputIndex) { + const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( + inputIndex, + input, + this.__CACHE, + ); + if (!script) throw new Error(`No script found for input #${inputIndex}`); + const scriptType = classifyScript(script); + if (!canFinalize(input, script, scriptType)) + throw new Error(`Can not finalize input #${inputIndex}`); + checkPartialSigSighashes(input); + const { finalScriptSig, finalScriptWitness } = getFinalScripts( + script, + scriptType, + input.partialSig, + isSegwit, + isP2SH, + isP2WSH, + ); + if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); + if (finalScriptWitness) + this.data.updateInput(inputIndex, { finalScriptWitness }); + if (!finalScriptSig && !finalScriptWitness) + throw new Error(`Unknown error finalizing input #${inputIndex}`); + this.data.clearFinalizedInput(inputIndex); + return this; + } + validateSignaturesOfAllInputs() { + utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one + const results = range(this.data.inputs.length).map(idx => + this.validateSignaturesOfInput(idx), + ); + return results.reduce((final, res) => res === true && final, true); + } + validateSignaturesOfInput(inputIndex, pubkey) { + const input = this.data.inputs[inputIndex]; + const partialSig = (input || {}).partialSig; + if (!input || !partialSig || partialSig.length < 1) + throw new Error('No signatures to validate'); + const mySigs = pubkey + ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) + : partialSig; + if (mySigs.length < 1) throw new Error('No signatures for this pubkey'); + const results = []; + let hashCache; + let scriptCache; + let sighashCache; + for (const pSig of mySigs) { + const sig = bscript.signature.decode(pSig.signature); + const { hash, script } = + sighashCache !== sig.hashType + ? getHashForSig( + inputIndex, + Object.assign({}, input, { sighashType: sig.hashType }), + this.__CACHE, + ) + : { hash: hashCache, script: scriptCache }; + sighashCache = sig.hashType; + hashCache = hash; + scriptCache = script; + checkScriptForPubkey(pSig.pubkey, script, 'verify'); + const keypair = ecpair_1.fromPublicKey(pSig.pubkey); + results.push(keypair.verify(hash, sig.signature)); + } + return results.every(res => res === true); + } + signAllInputsHD( + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + const results = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInputHD(i, hdKeyPair, sighashTypes); + results.push(true); + } catch (err) { + results.push(false); + } + } + if (results.every(v => v === false)) { + throw new Error('No inputs were signed'); + } + return this; + } + signAllInputsHDAsync( + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const results = []; + const promises = []; + for (const i of range(this.data.inputs.length)) { + promises.push( + this.signInputHDAsync(i, hdKeyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); + } + resolve(); + }); + }); + } + signInputHD( + inputIndex, + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); + signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes)); + return this; + } + signInputHDAsync( + inputIndex, + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); + const promises = signers.map(signer => + this.signInputAsync(inputIndex, signer, sighashTypes), + ); + return Promise.all(promises) + .then(() => { + resolve(); + }) + .catch(reject); + }); + } + signAllInputs( + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInput(i, keyPair, sighashTypes); + results.push(true); + } catch (err) { + results.push(false); + } + } + if (results.every(v => v === false)) { + throw new Error('No inputs were signed'); + } + return this; + } + signAllInputsAsync( + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results = []; + const promises = []; + for (const [i] of this.data.inputs.entries()) { + promises.push( + this.signInputAsync(i, keyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); + } + resolve(); + }); + }); + } + signInput( + inputIndex, + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); + return this; + } + signInputAsync( + inputIndex, + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + Promise.resolve(keyPair.sign(hash)).then(signature => { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(signature, sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); + resolve(); + }); + }); + } + toBuffer() { + return this.data.toBuffer(); + } + toHex() { + return this.data.toHex(); + } + toBase64() { + return this.data.toBase64(); + } + updateGlobal(updateData) { + this.data.updateGlobal(updateData); + return this; + } + updateInput(inputIndex, updateData) { + this.data.updateInput(inputIndex, updateData); + if (updateData.nonWitnessUtxo) { + addNonWitnessTxCache( + this.__CACHE, + this.data.inputs[inputIndex], + inputIndex, + ); + } + return this; + } + updateOutput(outputIndex, updateData) { + this.data.updateOutput(outputIndex, updateData); + return this; + } + addUnknownKeyValToGlobal(keyVal) { + this.data.addUnknownKeyValToGlobal(keyVal); + return this; + } + addUnknownKeyValToInput(inputIndex, keyVal) { + this.data.addUnknownKeyValToInput(inputIndex, keyVal); + return this; + } + addUnknownKeyValToOutput(outputIndex, keyVal) { + this.data.addUnknownKeyValToOutput(outputIndex, keyVal); + return this; + } + clearFinalizedInput(inputIndex) { + this.data.clearFinalizedInput(inputIndex); + return this; + } +} +exports.Psbt = Psbt; +/** + * This function is needed to pass to the bip174 base class's fromBuffer. + * It takes the "transaction buffer" portion of the psbt buffer and returns a + * Transaction (From the bip174 library) interface. + */ +const transactionFromBuffer = buffer => new PsbtTransaction(buffer); +/** + * This class implements the Transaction interface from bip174 library. + * It contains a bitcoinjs-lib Transaction object. + */ +class PsbtTransaction { + constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { + this.tx = transaction_1.Transaction.fromBuffer(buffer); + checkTxEmpty(this.tx); + Object.defineProperty(this, 'tx', { + enumerable: false, + writable: true, + }); + } + getInputOutputCounts() { + return { + inputCount: this.tx.ins.length, + outputCount: this.tx.outs.length, + }; + } + addInput(input) { + if ( + input.hash === undefined || + input.index === undefined || + (!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') || + typeof input.index !== 'number' + ) { + throw new Error('Error adding input.'); + } + const hash = + typeof input.hash === 'string' + ? bufferutils_1.reverseBuffer(Buffer.from(input.hash, 'hex')) + : input.hash; + this.tx.addInput(hash, input.index, input.sequence); + } + addOutput(output) { + if ( + output.script === undefined || + output.value === undefined || + !Buffer.isBuffer(output.script) || + typeof output.value !== 'number' + ) { + throw new Error('Error adding output.'); + } + this.tx.addOutput(output.script, output.value); + } + toBuffer() { + return this.tx.toBuffer(); + } +} +function canFinalize(input, script, scriptType) { + switch (scriptType) { + case 'pubkey': + case 'pubkeyhash': + case 'witnesspubkeyhash': + return hasSigs(1, input.partialSig); + case 'multisig': + const p2ms = payments.p2ms({ output: script }); + return hasSigs(p2ms.m, input.partialSig); + default: + return false; + } +} +function hasSigs(neededSigs, partialSig) { + if (!partialSig) return false; + if (partialSig.length > neededSigs) throw new Error('Too many signatures'); + return partialSig.length === neededSigs; +} +function isFinalized(input) { + return !!input.finalScriptSig || !!input.finalScriptWitness; +} +function isPaymentFactory(payment) { + return script => { + try { + payment({ output: script }); + return true; + } catch (err) { + return false; + } + }; +} +const isP2MS = isPaymentFactory(payments.p2ms); +const isP2PK = isPaymentFactory(payments.p2pk); +const isP2PKH = isPaymentFactory(payments.p2pkh); +const isP2WPKH = isPaymentFactory(payments.p2wpkh); +const isP2WSHScript = isPaymentFactory(payments.p2wsh); +function check32Bit(num) { + if ( + typeof num !== 'number' || + num !== Math.floor(num) || + num > 0xffffffff || + num < 0 + ) { + throw new Error('Invalid 32 bit integer'); + } +} +function checkFees(psbt, cache, opts) { + const feeRate = cache.__FEE_RATE || psbt.getFeeRate(); + const vsize = cache.__EXTRACTED_TX.virtualSize(); + const satoshis = feeRate * vsize; + if (feeRate >= opts.maximumFeeRate) { + throw new Error( + `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` + + `fees, which is ${feeRate} satoshi per byte for a transaction ` + + `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` + + `byte). Use setMaximumFeeRate method to raise your threshold, or ` + + `pass true to the first arg of extractTransaction.`, + ); + } +} +function checkInputsForPartialSig(inputs, action) { + inputs.forEach(input => { + let throws = false; + let pSigs = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig; + } + pSigs.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + const whitelist = []; + const isAnyoneCanPay = + hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case transaction_1.Transaction.SIGHASH_ALL: + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + case transaction_1.Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + throws = true; + } + }); + if (throws) { + throw new Error('Can not modify transaction, signatures exist.'); + } + }); +} +function checkPartialSigSighashes(input) { + if (!input.sighashType || !input.partialSig) return; + const { partialSig, sighashType } = input; + partialSig.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + if (sighashType !== hashType) { + throw new Error('Signature sighash does not match input sighash type'); + } + }); +} +function checkScriptForPubkey(pubkey, script, action) { + const pubkeyHash = crypto_1.hash160(pubkey); + const decompiled = bscript.decompile(script); + if (decompiled === null) throw new Error('Unknown script error'); + const hasKey = decompiled.some(element => { + if (typeof element === 'number') return false; + return element.equals(pubkey) || element.equals(pubkeyHash); + }); + if (!hasKey) { + throw new Error( + `Can not ${action} for this input with the key ${pubkey.toString('hex')}`, + ); + } +} +function checkTxEmpty(tx) { + const isEmpty = tx.ins.every( + input => + input.script && + input.script.length === 0 && + input.witness && + input.witness.length === 0, + ); + if (!isEmpty) { + throw new Error('Format Error: Transaction ScriptSigs are not empty'); + } +} +function checkTxForDupeIns(tx, cache) { + tx.ins.forEach(input => { + checkTxInputCache(cache, input); + }); +} +function checkTxInputCache(cache, input) { + const key = + bufferutils_1.reverseBuffer(Buffer.from(input.hash)).toString('hex') + + ':' + + input.index; + if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.'); + cache.__TX_IN_CACHE[key] = 1; +} +function scriptCheckerFactory(payment, paymentScriptName) { + return (inputIndex, scriptPubKey, redeemScript) => { + const redeemScriptOutput = payment({ + redeem: { output: redeemScript }, + }).output; + if (!scriptPubKey.equals(redeemScriptOutput)) { + throw new Error( + `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, + ); + } + }; +} +const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); +const checkWitnessScript = scriptCheckerFactory( + payments.p2wsh, + 'Witness script', +); +function getFinalScripts( + script, + scriptType, + partialSig, + isSegwit, + isP2SH, + isP2WSH, +) { + let finalScriptSig; + let finalScriptWitness; + // Wow, the payments API is very handy + const payment = getPayment(script, scriptType, partialSig); + const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment }); + const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); + if (isSegwit) { + if (p2wsh) { + finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness); + } else { + finalScriptWitness = witnessStackToScriptWitness(payment.witness); + } + if (p2sh) { + finalScriptSig = p2sh.input; + } + } else { + if (p2sh) { + finalScriptSig = p2sh.input; + } else { + finalScriptSig = payment.input; + } + } + return { + finalScriptSig, + finalScriptWitness, + }; +} +function getHashAndSighashType( + inputs, + inputIndex, + pubkey, + cache, + sighashTypes, +) { + const input = utils_1.checkForInput(inputs, inputIndex); + const { hash, sighashType, script } = getHashForSig( + inputIndex, + input, + cache, + sighashTypes, + ); + checkScriptForPubkey(pubkey, script, 'sign'); + return { + hash, + sighashType, + }; +} +function getHashForSig(inputIndex, input, cache, sighashTypes) { + const unsignedTx = cache.__TX; + const sighashType = + input.sighashType || transaction_1.Transaction.SIGHASH_ALL; + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error( + `Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`, + ); + } + let hash; + let script; + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + const prevoutHash = unsignedTx.ins[inputIndex].hash; + const utxoHash = nonWitnessUtxoTx.getHash(); + // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout + if (!prevoutHash.equals(utxoHash)) { + throw new Error( + `Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`, + ); + } + const prevoutIndex = unsignedTx.ins[inputIndex].index; + const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript(inputIndex, prevout.script, input.redeemScript); + script = input.redeemScript; + } else { + script = prevout.script; + } + if (isP2WPKH(script) || isP2WSHScript(script)) { + throw new Error( + `Input #${inputIndex} has nonWitnessUtxo but segwit script: ` + + `${script.toString('hex')}`, + ); + } + hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); + } else if (input.witnessUtxo) { + let _script; // so we don't shadow the `let script` above + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript( + inputIndex, + input.witnessUtxo.script, + input.redeemScript, + ); + _script = input.redeemScript; + } else { + _script = input.witnessUtxo.script; + } + if (isP2WPKH(_script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output; + hash = unsignedTx.hashForWitnessV0( + inputIndex, + signingScript, + input.witnessUtxo.value, + sighashType, + ); + script = _script; + } else if (isP2WSHScript(_script)) { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, _script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0( + inputIndex, + input.witnessScript, + input.witnessUtxo.value, + sighashType, + ); + // want to make sure the script we return is the actual meaningful script + script = input.witnessScript; + } else { + throw new Error( + `Input #${inputIndex} has witnessUtxo but non-segwit script: ` + + `${_script.toString('hex')}`, + ); + } + } else { + throw new Error('Need a Utxo input item for signing'); + } + return { + script, + sighashType, + hash, + }; +} +function getPayment(script, scriptType, partialSig) { + let payment; + switch (scriptType) { + case 'multisig': + const sigs = getSortedSigs(script, partialSig); + payment = payments.p2ms({ + output: script, + signatures: sigs, + }); + break; + case 'pubkey': + payment = payments.p2pk({ + output: script, + signature: partialSig[0].signature, + }); + break; + case 'pubkeyhash': + payment = payments.p2pkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + case 'witnesspubkeyhash': + payment = payments.p2wpkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + } + return payment; +} +function getPsigsFromInputFinalScripts(input) { + const scriptItems = !input.finalScriptSig + ? [] + : bscript.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : bscript.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); + }) + .map(sig => ({ signature: sig })); +} +function getScriptFromInput(inputIndex, input, cache) { + const unsignedTx = cache.__TX; + const res = { + script: null, + isSegwit: false, + isP2SH: false, + isP2WSH: false, + }; + if (input.nonWitnessUtxo) { + if (input.redeemScript) { + res.isP2SH = true; + res.script = input.redeemScript; + } else { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + const prevoutIndex = unsignedTx.ins[inputIndex].index; + res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; + } + } else if (input.witnessUtxo) { + res.isSegwit = true; + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript; + if (input.witnessScript) { + res.script = input.witnessScript; + } else if (input.redeemScript) { + res.script = payments.p2wpkh({ + hash: input.redeemScript.slice(2), + }).output; + } else { + res.script = payments.p2wpkh({ + hash: input.witnessUtxo.script.slice(2), + }).output; + } + } + return res; +} +function getSignersFromHD(inputIndex, inputs, hdKeyPair) { + const input = utils_1.checkForInput(inputs, inputIndex); + if (!input.bip32Derivation || input.bip32Derivation.length === 0) { + throw new Error('Need bip32Derivation to sign with HD'); + } + const myDerivations = input.bip32Derivation + .map(bipDv => { + if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) { + return bipDv; + } else { + return; + } + }) + .filter(v => !!v); + if (myDerivations.length === 0) { + throw new Error( + 'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint', + ); + } + const signers = myDerivations.map(bipDv => { + const node = hdKeyPair.derivePath(bipDv.path); + if (!bipDv.pubkey.equals(node.publicKey)) { + throw new Error('pubkey did not match bip32Derivation'); + } + return node; + }); + return signers; +} +function getSortedSigs(script, partialSig) { + const p2ms = payments.p2ms({ output: script }); + // for each pubkey in order of p2ms script + return p2ms.pubkeys + .map(pk => { + // filter partialSig array by pubkey being equal + return ( + partialSig.filter(ps => { + return ps.pubkey.equals(pk); + })[0] || {} + ).signature; + // Any pubkey without a match will return undefined + // this last filter removes all the undefined items in the array. + }) + .filter(v => !!v); +} +function scriptWitnessToWitnessStack(buffer) { + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); + } + 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 (let i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; + } + return readVector(); +} +function sighashTypeToString(sighashType) { + let text = + sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY + ? 'SIGHASH_ANYONECANPAY | ' + : ''; + const sigMod = sighashType & 0x1f; + switch (sigMod) { + case transaction_1.Transaction.SIGHASH_ALL: + text += 'SIGHASH_ALL'; + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + text += 'SIGHASH_SINGLE'; + break; + case transaction_1.Transaction.SIGHASH_NONE: + text += 'SIGHASH_NONE'; + break; + } + return text; +} +function witnessStackToScriptWitness(witness) { + let buffer = Buffer.allocUnsafe(0); + function writeSlice(slice) { + buffer = Buffer.concat([buffer, Buffer.from(slice)]); + } + function writeVarInt(i) { + const currentLen = buffer.length; + const varintLen = varuint.encodingLength(i); + buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); + varuint.encode(i, buffer, currentLen); + } + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); + } + function writeVector(vector) { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + writeVector(witness); + return buffer; +} +function addNonWitnessTxCache(cache, input, inputIndex) { + cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; + const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo); + cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx; + const self = cache; + const selfIndex = inputIndex; + delete input.nonWitnessUtxo; + Object.defineProperty(input, 'nonWitnessUtxo', { + enumerable: true, + get() { + const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]; + const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]; + if (buf !== undefined) { + return buf; + } else { + const newBuf = txCache.toBuffer(); + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf; + return newBuf; + } + }, + set(data) { + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; + }, + }); +} +function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) { + let inputAmount = 0; + inputs.forEach((input, idx) => { + if (mustFinalize && input.finalScriptSig) + tx.ins[idx].script = input.finalScriptSig; + if (mustFinalize && input.finalScriptWitness) { + tx.ins[idx].witness = scriptWitnessToWitnessStack( + input.finalScriptWitness, + ); + } + if (input.witnessUtxo) { + inputAmount += input.witnessUtxo.value; + } else if (input.nonWitnessUtxo) { + const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx); + const vout = tx.ins[idx].index; + const out = nwTx.outs[vout]; + inputAmount += out.value; + } + }); + const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0); + const fee = inputAmount - outputAmount; + if (fee < 0) { + throw new Error('Outputs are spending more than Inputs'); + } + const bytes = tx.virtualSize(); + cache.__EXTRACTED_TX = tx; + cache.__FEE_RATE = Math.floor(fee / bytes); +} +function nonWitnessUtxoTxFromCache(cache, input, inputIndex) { + const c = cache.__NON_WITNESS_UTXO_TX_CACHE; + if (!c[inputIndex]) { + addNonWitnessTxCache(cache, input, inputIndex); + } + return c[inputIndex]; +} +function classifyScript(script) { + if (isP2WPKH(script)) return 'witnesspubkeyhash'; + if (isP2PKH(script)) return 'pubkeyhash'; + if (isP2MS(script)) return 'multisig'; + if (isP2PK(script)) return 'pubkey'; + return 'nonstandard'; +} +function range(n) { + return [...Array(n).keys()]; +} diff --git a/src/script.js b/src/script.js index b35bfdf14..39859dcb0 100644 --- a/src/script.js +++ b/src/script.js @@ -1,138 +1,177 @@ -var assert = require('assert') -var bufferutils = require('./bufferutils') -var crypto = require('./crypto') -var typeForce = require('typeforce') -var opcodes = require('./opcodes') - -function Script (buffer, chunks) { - typeForce('Buffer', buffer) - typeForce('Array', chunks) - - this.buffer = buffer - this.chunks = chunks +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const scriptNumber = require('./script_number'); +const scriptSignature = require('./script_signature'); +const types = require('./types'); +const bip66 = require('bip66'); +const ecc = require('tiny-secp256k1'); +const pushdata = require('pushdata-bitcoin'); +const typeforce = require('typeforce'); +exports.OPS = require('bitcoin-ops'); +const REVERSE_OPS = require('bitcoin-ops/map'); +const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1 +function isOPInt(value) { + return ( + types.Number(value) && + (value === exports.OPS.OP_0 || + (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || + value === exports.OPS.OP_1NEGATE) + ); } - -Script.fromASM = function (asm) { - var strChunks = asm.split(' ') - var chunks = strChunks.map(function (strChunk) { +function isPushOnlyChunk(value) { + return types.Buffer(value) || isOPInt(value); +} +function isPushOnly(value) { + return types.Array(value) && value.every(isPushOnlyChunk); +} +exports.isPushOnly = isPushOnly; +function asMinimalOP(buffer) { + if (buffer.length === 0) return exports.OPS.OP_0; + if (buffer.length !== 1) return; + if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; + if (buffer[0] === 0x81) return exports.OPS.OP_1NEGATE; +} +function chunksIsBuffer(buf) { + return Buffer.isBuffer(buf); +} +function chunksIsArray(buf) { + return types.Array(buf); +} +function singleChunkIsBuffer(buf) { + return Buffer.isBuffer(buf); +} +function compile(chunks) { + // TODO: remove me + if (chunksIsBuffer(chunks)) return chunks; + typeforce(types.Array, chunks); + const bufferSize = chunks.reduce((accum, chunk) => { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { + return accum + 1; + } + return accum + pushdata.encodingLength(chunk.length) + chunk.length; + } // opcode - if (strChunk in opcodes) { - return opcodes[strChunk] - + return accum + 1; + }, 0.0); + const buffer = Buffer.allocUnsafe(bufferSize); + let offset = 0; + chunks.forEach(chunk => { // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + const opcode = asMinimalOP(chunk); + if (opcode !== undefined) { + buffer.writeUInt8(opcode, offset); + offset += 1; + return; + } + offset += pushdata.encode(buffer, chunk.length, offset); + chunk.copy(buffer, offset); + offset += chunk.length; + // opcode } else { - return new Buffer(strChunk, 'hex') + buffer.writeUInt8(chunk, offset); + offset += 1; } - }) - - return Script.fromChunks(chunks) + }); + if (offset !== buffer.length) throw new Error('Could not decode chunks'); + return buffer; } - -Script.fromBuffer = function (buffer) { - var chunks = [] - var i = 0 - +exports.compile = compile; +function decompile(buffer) { + // TODO: remove me + if (chunksIsArray(buffer)) return buffer; + typeforce(types.Buffer, buffer); + const chunks = []; + let i = 0; while (i < buffer.length) { - var opcode = buffer.readUInt8(i) - + const opcode = buffer[i]; // data chunk - if ((opcode > opcodes.OP_0) && (opcode <= opcodes.OP_PUSHDATA4)) { - var d = bufferutils.readPushDataInt(buffer, i) - i += d.size - - var data = buffer.slice(i, i + d.number) - i += d.number - - chunks.push(data) - - // opcode + if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { + const d = pushdata.decode(buffer, i); + // did reading a pushDataInt fail? + if (d === null) return null; + i += d.size; + // attempt to read too much data? + if (i + d.number > buffer.length) return null; + const data = buffer.slice(i, i + d.number); + i += d.number; + // decompile minimally + const op = asMinimalOP(data); + if (op !== undefined) { + chunks.push(op); + } else { + chunks.push(data); + } + // opcode } else { - chunks.push(opcode) - - i += 1 + chunks.push(opcode); + i += 1; } } - - return new Script(buffer, chunks) + return chunks; } - -Script.fromChunks = function (chunks) { - typeForce('Array', chunks) - - var bufferSize = chunks.reduce(function (accum, chunk) { - // data chunk - if (Buffer.isBuffer(chunk)) { - return accum + bufferutils.pushDataSize(chunk.length) + chunk.length - } - - // opcode - return accum + 1 - }, 0.0) - - var buffer = new Buffer(bufferSize) - var offset = 0 - - chunks.forEach(function (chunk) { - // data chunk - if (Buffer.isBuffer(chunk)) { - offset += bufferutils.writePushDataInt(buffer, chunk.length, offset) - - chunk.copy(buffer, offset) - offset += chunk.length - - // opcode - } else { - buffer.writeUInt8(chunk, offset) - offset += 1 - } - }) - - assert.equal(offset, buffer.length, 'Could not decode chunks') - return new Script(buffer, chunks) -} - -Script.fromHex = function (hex) { - return Script.fromBuffer(new Buffer(hex, 'hex')) -} - -Script.EMPTY = Script.fromChunks([]) - -Script.prototype.getHash = function () { - return crypto.hash160(this.buffer) +exports.decompile = decompile; +function toASM(chunks) { + if (chunksIsBuffer(chunks)) { + chunks = decompile(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(' '); } - -// FIXME: doesn't work for data chunks, maybe time to use buffertools.compare... -Script.prototype.without = function (needle) { - return Script.fromChunks(this.chunks.filter(function (op) { - return op !== needle - })) +exports.toASM = toASM; +function fromASM(asm) { + typeforce(types.String, asm); + return compile( + asm.split(' ').map(chunkStr => { + // opcode? + if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr]; + typeforce(types.Hex, chunkStr); + // data! + return Buffer.from(chunkStr, 'hex'); + }), + ); } - -var reverseOps = [] -for (var op in opcodes) { - var code = opcodes[op] - reverseOps[code] = op +exports.fromASM = fromASM; +function toStack(chunks) { + chunks = decompile(chunks); + typeforce(isPushOnly, chunks); + return chunks.map(op => { + if (singleChunkIsBuffer(op)) return op; + if (op === exports.OPS.OP_0) return Buffer.allocUnsafe(0); + return scriptNumber.encode(op - OP_INT_BASE); + }); } - -Script.prototype.toASM = function () { - return this.chunks.map(function (chunk) { - // data chunk - if (Buffer.isBuffer(chunk)) { - return chunk.toString('hex') - - // opcode - } else { - return reverseOps[chunk] - } - }).join(' ') +exports.toStack = toStack; +function isCanonicalPubKey(buffer) { + return ecc.isPoint(buffer); } - -Script.prototype.toBuffer = function () { - return this.buffer +exports.isCanonicalPubKey = isCanonicalPubKey; +function isDefinedHashType(hashType) { + const hashTypeMod = hashType & ~0x80; + // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE + return hashTypeMod > 0x00 && hashTypeMod < 0x04; } - -Script.prototype.toHex = function () { - return this.toBuffer().toString('hex') +exports.isDefinedHashType = isDefinedHashType; +function isCanonicalScriptSignature(buffer) { + if (!Buffer.isBuffer(buffer)) return false; + if (!isDefinedHashType(buffer[buffer.length - 1])) return false; + return bip66.check(buffer.slice(0, -1)); } - -module.exports = Script +exports.isCanonicalScriptSignature = isCanonicalScriptSignature; +// tslint:disable-next-line variable-name +exports.number = scriptNumber; +exports.signature = scriptSignature; diff --git a/src/script_number.js b/src/script_number.js new file mode 100644 index 000000000..3f313af56 --- /dev/null +++ b/src/script_number.js @@ -0,0 +1,61 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +function decode(buffer, maxLength, minimal) { + maxLength = maxLength || 4; + minimal = minimal === undefined ? true : minimal; + const length = buffer.length; + if (length === 0) return 0; + if (length > maxLength) throw new TypeError('Script number overflow'); + if (minimal) { + if ((buffer[length - 1] & 0x7f) === 0) { + if (length <= 1 || (buffer[length - 2] & 0x80) === 0) + throw new Error('Non-minimally encoded script number'); + } + } + // 40-bit + if (length === 5) { + const a = buffer.readUInt32LE(0); + const b = buffer.readUInt8(4); + if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a); + return b * 0x100000000 + a; + } + // 32-bit / 24-bit / 16-bit / 8-bit + let result = 0; + for (let i = 0; i < length; ++i) { + result |= buffer[i] << (8 * i); + } + if (buffer[length - 1] & 0x80) + return -(result & ~(0x80 << (8 * (length - 1)))); + return result; +} +exports.decode = decode; +function scriptNumSize(i) { + return i > 0x7fffffff + ? 5 + : i > 0x7fffff + ? 4 + : i > 0x7fff + ? 3 + : i > 0x7f + ? 2 + : i > 0x00 + ? 1 + : 0; +} +function encode(_number) { + let value = Math.abs(_number); + const size = scriptNumSize(value); + const buffer = Buffer.allocUnsafe(size); + const negative = _number < 0; + for (let i = 0; i < size; ++i) { + buffer.writeUInt8(value & 0xff, i); + value >>= 8; + } + if (buffer[size - 1] & 0x80) { + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); + } else if (negative) { + buffer[size - 1] |= 0x80; + } + return buffer; +} +exports.encode = encode; diff --git a/src/script_signature.js b/src/script_signature.js new file mode 100644 index 000000000..fb52fe9e2 --- /dev/null +++ b/src/script_signature.js @@ -0,0 +1,52 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const types = require('./types'); +const bip66 = require('bip66'); +const typeforce = require('typeforce'); +const ZERO = Buffer.alloc(1, 0); +function toDER(x) { + let i = 0; + while (x[i] === 0) ++i; + if (i === x.length) return ZERO; + x = x.slice(i); + if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length); + return x; +} +function fromDER(x) { + if (x[0] === 0x00) x = x.slice(1); + const buffer = Buffer.alloc(32, 0); + const bstart = Math.max(0, 32 - x.length); + x.copy(buffer, bstart); + return buffer; +} +// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) +function decode(buffer) { + const hashType = buffer.readUInt8(buffer.length - 1); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const decoded = bip66.decode(buffer.slice(0, -1)); + const r = fromDER(decoded.r); + const s = fromDER(decoded.s); + const signature = Buffer.concat([r, s], 64); + return { signature, hashType }; +} +exports.decode = decode; +function encode(signature, hashType) { + typeforce( + { + signature: types.BufferN(64), + hashType: types.UInt8, + }, + { signature, hashType }, + ); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const hashTypeBuffer = Buffer.allocUnsafe(1); + hashTypeBuffer.writeUInt8(hashType, 0); + const r = toDER(signature.slice(0, 32)); + const s = toDER(signature.slice(32, 64)); + return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); +} +exports.encode = encode; diff --git a/src/scripts.js b/src/scripts.js deleted file mode 100644 index 387618429..000000000 --- a/src/scripts.js +++ /dev/null @@ -1,295 +0,0 @@ -var assert = require('assert') -var ops = require('./opcodes') -var typeForce = require('typeforce') - -var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') - -var ECSignature = require('./ecsignature') -var Script = require('./script') - -function isCanonicalPubKey (buffer) { - if (!Buffer.isBuffer(buffer)) return false - - try { - ecurve.Point.decodeFrom(curve, buffer) - } catch (e) { - if (!(e.message.match(/Invalid sequence (length|tag)/))) - throw e - - return false - } - - return true -} - -function isCanonicalSignature (buffer) { - if (!Buffer.isBuffer(buffer)) return false - - try { - ECSignature.parseScriptSignature(buffer) - } catch (e) { - if (!(e.message.match(/Not a DER sequence|Invalid sequence length|Expected a DER integer|R length is zero|S length is zero|R value excessively padded|S value excessively padded|R value is negative|S value is negative|Invalid hashType/))) { - throw e - } - - return false - } - - return true -} - -function isPubKeyHashInput (script) { - return script.chunks.length === 2 && - isCanonicalSignature(script.chunks[0]) && - isCanonicalPubKey(script.chunks[1]) -} - -function isPubKeyHashOutput (script) { - return script.chunks.length === 5 && - script.chunks[0] === ops.OP_DUP && - script.chunks[1] === ops.OP_HASH160 && - Buffer.isBuffer(script.chunks[2]) && - script.chunks[2].length === 20 && - script.chunks[3] === ops.OP_EQUALVERIFY && - script.chunks[4] === ops.OP_CHECKSIG -} - -function isPubKeyInput (script) { - return script.chunks.length === 1 && - isCanonicalSignature(script.chunks[0]) -} - -function isPubKeyOutput (script) { - return script.chunks.length === 2 && - isCanonicalPubKey(script.chunks[0]) && - script.chunks[1] === ops.OP_CHECKSIG -} - -function isScriptHashInput (script, allowIncomplete) { - if (script.chunks.length < 2) return false - - var lastChunk = script.chunks[script.chunks.length - 1] - if (!Buffer.isBuffer(lastChunk)) return false - - var scriptSig = Script.fromChunks(script.chunks.slice(0, -1)) - var scriptPubKey - - try { - scriptPubKey = Script.fromBuffer(lastChunk) - } catch (e) { - return false - } - - return classifyInput(scriptSig, allowIncomplete) === classifyOutput(scriptPubKey) -} - -function isScriptHashOutput (script) { - return script.chunks.length === 3 && - script.chunks[0] === ops.OP_HASH160 && - Buffer.isBuffer(script.chunks[1]) && - script.chunks[1].length === 20 && - script.chunks[2] === ops.OP_EQUAL -} - -// allowIncomplete is to account for combining signatures -// See https://github.com/bitcoin/bitcoin/blob/f425050546644a36b0b8e0eb2f6934a3e0f6f80f/src/script/sign.cpp#L195-L197 -function isMultisigInput (script, allowIncomplete) { - if (script.chunks.length < 2) return false - if (script.chunks[0] !== ops.OP_0) return false - - if (allowIncomplete) { - return script.chunks.slice(1).every(function (chunk) { - return chunk === ops.OP_0 || isCanonicalSignature(chunk) - }) - } - - return script.chunks.slice(1).every(isCanonicalSignature) -} - -function isMultisigOutput (script) { - if (script.chunks.length < 4) return false - if (script.chunks[script.chunks.length - 1] !== ops.OP_CHECKMULTISIG) return false - - var mOp = script.chunks[0] - if (mOp === ops.OP_0) return false - if (mOp < ops.OP_1) return false - if (mOp > ops.OP_16) return false - - var nOp = script.chunks[script.chunks.length - 2] - if (nOp === ops.OP_0) return false - if (nOp < ops.OP_1) return false - if (nOp > ops.OP_16) return false - - var m = mOp - (ops.OP_1 - 1) - var n = nOp - (ops.OP_1 - 1) - if (n < m) return false - - var pubKeys = script.chunks.slice(1, -2) - if (n < pubKeys.length) return false - - return pubKeys.every(isCanonicalPubKey) -} - -function isNullDataOutput (script) { - return script.chunks[0] === ops.OP_RETURN -} - -function classifyOutput (script) { - typeForce('Script', script) - - if (isPubKeyHashOutput(script)) { - return 'pubkeyhash' - } else if (isScriptHashOutput(script)) { - return 'scripthash' - } else if (isMultisigOutput(script)) { - return 'multisig' - } else if (isPubKeyOutput(script)) { - return 'pubkey' - } else if (isNullDataOutput(script)) { - return 'nulldata' - } - - return 'nonstandard' -} - -function classifyInput (script, allowIncomplete) { - typeForce('Script', script) - - if (isPubKeyHashInput(script)) { - return 'pubkeyhash' - } else if (isMultisigInput(script, allowIncomplete)) { - return 'multisig' - } else if (isScriptHashInput(script, allowIncomplete)) { - return 'scripthash' - } else if (isPubKeyInput(script)) { - return 'pubkey' - } - - return 'nonstandard' -} - -// Standard Script Templates -// {pubKey} OP_CHECKSIG -function pubKeyOutput (pubKey) { - return Script.fromChunks([ - pubKey.toBuffer(), - ops.OP_CHECKSIG - ]) -} - -// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG -function pubKeyHashOutput (hash) { - typeForce('Buffer', hash) - - return Script.fromChunks([ - ops.OP_DUP, - ops.OP_HASH160, - hash, - ops.OP_EQUALVERIFY, - ops.OP_CHECKSIG - ]) -} - -// OP_HASH160 {scriptHash} OP_EQUAL -function scriptHashOutput (hash) { - typeForce('Buffer', hash) - - return Script.fromChunks([ - ops.OP_HASH160, - hash, - ops.OP_EQUAL - ]) -} - -// m [pubKeys ...] n OP_CHECKMULTISIG -function multisigOutput (m, pubKeys) { - typeForce(['ECPubKey'], pubKeys) - - assert(pubKeys.length >= m, 'Not enough pubKeys provided') - - var pubKeyBuffers = pubKeys.map(function (pubKey) { - return pubKey.toBuffer() - }) - var n = pubKeys.length - - return Script.fromChunks([].concat( - (ops.OP_1 - 1) + m, - pubKeyBuffers, - (ops.OP_1 - 1) + n, - ops.OP_CHECKMULTISIG - )) -} - -// {signature} -function pubKeyInput (signature) { - typeForce('Buffer', signature) - - return Script.fromChunks([signature]) -} - -// {signature} {pubKey} -function pubKeyHashInput (signature, pubKey) { - typeForce('Buffer', signature) - - return Script.fromChunks([signature, pubKey.toBuffer()]) -} - -// {serialized scriptPubKey script} -function scriptHashInput (scriptSig, scriptPubKey) { - return Script.fromChunks([].concat( - scriptSig.chunks, - scriptPubKey.toBuffer() - )) -} - -// OP_0 [signatures ...] -function multisigInput (signatures, scriptPubKey) { - if (scriptPubKey) { - assert(isMultisigOutput(scriptPubKey)) - - var mOp = scriptPubKey.chunks[0] - var nOp = scriptPubKey.chunks[scriptPubKey.chunks.length - 2] - var m = mOp - (ops.OP_1 - 1) - var n = nOp - (ops.OP_1 - 1) - - var count = 0 - signatures.forEach(function (signature) { - count += (signature !== ops.OP_0) - }) - - assert(count >= m, 'Not enough signatures provided') - assert(count <= n, 'Too many signatures provided') - } - - return Script.fromChunks([].concat(ops.OP_0, signatures)) -} - -function nullDataOutput (data) { - return Script.fromChunks([ops.OP_RETURN, data]) -} - -module.exports = { - isCanonicalPubKey: isCanonicalPubKey, - isCanonicalSignature: isCanonicalSignature, - isPubKeyHashInput: isPubKeyHashInput, - isPubKeyHashOutput: isPubKeyHashOutput, - isPubKeyInput: isPubKeyInput, - isPubKeyOutput: isPubKeyOutput, - isScriptHashInput: isScriptHashInput, - isScriptHashOutput: isScriptHashOutput, - isMultisigInput: isMultisigInput, - isMultisigOutput: isMultisigOutput, - isNullDataOutput: isNullDataOutput, - classifyOutput: classifyOutput, - classifyInput: classifyInput, - pubKeyOutput: pubKeyOutput, - pubKeyHashOutput: pubKeyHashOutput, - scriptHashOutput: scriptHashOutput, - multisigOutput: multisigOutput, - pubKeyInput: pubKeyInput, - pubKeyHashInput: pubKeyHashInput, - scriptHashInput: scriptHashInput, - multisigInput: multisigInput, - nullDataOutput: nullDataOutput -} diff --git a/src/templates/multisig/index.js b/src/templates/multisig/index.js new file mode 100644 index 000000000..b8cd6c435 --- /dev/null +++ b/src/templates/multisig/index.js @@ -0,0 +1,6 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); +exports.input = input; +const output = require('./output'); +exports.output = output; diff --git a/src/templates/multisig/input.js b/src/templates/multisig/input.js new file mode 100644 index 000000000..403c2f720 --- /dev/null +++ b/src/templates/multisig/input.js @@ -0,0 +1,23 @@ +'use strict'; +// OP_0 [signatures ...] +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +function partialSignature(value) { + return ( + value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value) + ); +} +function check(script, allowIncomplete) { + const chunks = bscript.decompile(script); + if (chunks.length < 2) return false; + if (chunks[0] !== script_1.OPS.OP_0) return false; + if (allowIncomplete) { + return chunks.slice(1).every(partialSignature); + } + return chunks.slice(1).every(bscript.isCanonicalScriptSignature); +} +exports.check = check; +check.toJSON = () => { + return 'multisig input'; +}; diff --git a/src/templates/multisig/output.js b/src/templates/multisig/output.js new file mode 100644 index 000000000..089660549 --- /dev/null +++ b/src/templates/multisig/output.js @@ -0,0 +1,27 @@ +'use strict'; +// m [pubKeys ...] n OP_CHECKMULTISIG +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +const types = require('../../types'); +const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1 +function check(script, allowIncomplete) { + const chunks = bscript.decompile(script); + if (chunks.length < 4) return false; + if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) return false; + if (!types.Number(chunks[0])) return false; + if (!types.Number(chunks[chunks.length - 2])) return false; + const m = chunks[0] - OP_INT_BASE; + const n = chunks[chunks.length - 2] - OP_INT_BASE; + if (m <= 0) return false; + if (n > 16) return false; + if (m > n) return false; + if (n !== chunks.length - 3) return false; + if (allowIncomplete) return true; + const keys = chunks.slice(1, -2); + return keys.every(bscript.isCanonicalPubKey); +} +exports.check = check; +check.toJSON = () => { + return 'multi-sig output'; +}; diff --git a/src/templates/nulldata.js b/src/templates/nulldata.js new file mode 100644 index 000000000..50355d3ce --- /dev/null +++ b/src/templates/nulldata.js @@ -0,0 +1,15 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +// OP_RETURN {data} +const bscript = require('../script'); +const OPS = bscript.OPS; +function check(script) { + const buffer = bscript.compile(script); + return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; +} +exports.check = check; +check.toJSON = () => { + return 'null data output'; +}; +const output = { check }; +exports.output = output; diff --git a/src/templates/pubkey/index.js b/src/templates/pubkey/index.js new file mode 100644 index 000000000..b8cd6c435 --- /dev/null +++ b/src/templates/pubkey/index.js @@ -0,0 +1,6 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); +exports.input = input; +const output = require('./output'); +exports.output = output; diff --git a/src/templates/pubkey/input.js b/src/templates/pubkey/input.js new file mode 100644 index 000000000..9715b8010 --- /dev/null +++ b/src/templates/pubkey/input.js @@ -0,0 +1,12 @@ +'use strict'; +// {signature} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +function check(script) { + const chunks = bscript.decompile(script); + return chunks.length === 1 && bscript.isCanonicalScriptSignature(chunks[0]); +} +exports.check = check; +check.toJSON = () => { + return 'pubKey input'; +}; diff --git a/src/templates/pubkey/output.js b/src/templates/pubkey/output.js new file mode 100644 index 000000000..2edb7318e --- /dev/null +++ b/src/templates/pubkey/output.js @@ -0,0 +1,17 @@ +'use strict'; +// {pubKey} OP_CHECKSIG +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +function check(script) { + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalPubKey(chunks[0]) && + chunks[1] === script_1.OPS.OP_CHECKSIG + ); +} +exports.check = check; +check.toJSON = () => { + return 'pubKey output'; +}; diff --git a/src/templates/pubkeyhash/index.js b/src/templates/pubkeyhash/index.js new file mode 100644 index 000000000..b8cd6c435 --- /dev/null +++ b/src/templates/pubkeyhash/index.js @@ -0,0 +1,6 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); +exports.input = input; +const output = require('./output'); +exports.output = output; diff --git a/src/templates/pubkeyhash/input.js b/src/templates/pubkeyhash/input.js new file mode 100644 index 000000000..14d72cc37 --- /dev/null +++ b/src/templates/pubkeyhash/input.js @@ -0,0 +1,16 @@ +'use strict'; +// {signature} {pubKey} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +function check(script) { + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + bscript.isCanonicalPubKey(chunks[1]) + ); +} +exports.check = check; +check.toJSON = () => { + return 'pubKeyHash input'; +}; diff --git a/src/templates/pubkeyhash/output.js b/src/templates/pubkeyhash/output.js new file mode 100644 index 000000000..079e1ed67 --- /dev/null +++ b/src/templates/pubkeyhash/output.js @@ -0,0 +1,20 @@ +'use strict'; +// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +function check(script) { + const buffer = bscript.compile(script); + return ( + buffer.length === 25 && + buffer[0] === script_1.OPS.OP_DUP && + buffer[1] === script_1.OPS.OP_HASH160 && + buffer[2] === 0x14 && + buffer[23] === script_1.OPS.OP_EQUALVERIFY && + buffer[24] === script_1.OPS.OP_CHECKSIG + ); +} +exports.check = check; +check.toJSON = () => { + return 'pubKeyHash output'; +}; diff --git a/src/templates/scripthash/index.js b/src/templates/scripthash/index.js new file mode 100644 index 000000000..b8cd6c435 --- /dev/null +++ b/src/templates/scripthash/index.js @@ -0,0 +1,6 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); +exports.input = input; +const output = require('./output'); +exports.output = output; diff --git a/src/templates/scripthash/input.js b/src/templates/scripthash/input.js new file mode 100644 index 000000000..999cc8312 --- /dev/null +++ b/src/templates/scripthash/input.js @@ -0,0 +1,50 @@ +'use strict'; +// {serialized scriptPubKey script} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const p2ms = require('../multisig'); +const p2pk = require('../pubkey'); +const p2pkh = require('../pubkeyhash'); +const p2wpkho = require('../witnesspubkeyhash/output'); +const p2wsho = require('../witnessscripthash/output'); +function check(script, allowIncomplete) { + const chunks = bscript.decompile(script); + if (chunks.length < 1) return false; + const lastChunk = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(lastChunk)) return false; + const scriptSigChunks = bscript.decompile( + bscript.compile(chunks.slice(0, -1)), + ); + const redeemScriptChunks = bscript.decompile(lastChunk); + // is redeemScript a valid script? + if (!redeemScriptChunks) return false; + // is redeemScriptSig push only? + if (!bscript.isPushOnly(scriptSigChunks)) return false; + // is witness? + if (chunks.length === 1) { + return ( + p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks) + ); + } + // match types + if ( + p2pkh.input.check(scriptSigChunks) && + p2pkh.output.check(redeemScriptChunks) + ) + return true; + if ( + p2ms.input.check(scriptSigChunks, allowIncomplete) && + p2ms.output.check(redeemScriptChunks) + ) + return true; + if ( + p2pk.input.check(scriptSigChunks) && + p2pk.output.check(redeemScriptChunks) + ) + return true; + return false; +} +exports.check = check; +check.toJSON = () => { + return 'scriptHash input'; +}; diff --git a/src/templates/scripthash/output.js b/src/templates/scripthash/output.js new file mode 100644 index 000000000..37970037a --- /dev/null +++ b/src/templates/scripthash/output.js @@ -0,0 +1,18 @@ +'use strict'; +// OP_HASH160 {scriptHash} OP_EQUAL +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +function check(script) { + const buffer = bscript.compile(script); + return ( + buffer.length === 23 && + buffer[0] === script_1.OPS.OP_HASH160 && + buffer[1] === 0x14 && + buffer[22] === script_1.OPS.OP_EQUAL + ); +} +exports.check = check; +check.toJSON = () => { + return 'scriptHash output'; +}; diff --git a/src/templates/witnesscommitment/index.js b/src/templates/witnesscommitment/index.js new file mode 100644 index 000000000..099ac7233 --- /dev/null +++ b/src/templates/witnesscommitment/index.js @@ -0,0 +1,4 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const output = require('./output'); +exports.output = output; diff --git a/src/templates/witnesscommitment/output.js b/src/templates/witnesscommitment/output.js new file mode 100644 index 000000000..fb1d59cf6 --- /dev/null +++ b/src/templates/witnesscommitment/output.js @@ -0,0 +1,34 @@ +'use strict'; +// OP_RETURN {aa21a9ed} {commitment} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +const types = require('../../types'); +const typeforce = require('typeforce'); +const HEADER = Buffer.from('aa21a9ed', 'hex'); +function check(script) { + const buffer = bscript.compile(script); + return ( + buffer.length > 37 && + buffer[0] === script_1.OPS.OP_RETURN && + buffer[1] === 0x24 && + buffer.slice(2, 6).equals(HEADER) + ); +} +exports.check = check; +check.toJSON = () => { + return 'Witness commitment output'; +}; +function encode(commitment) { + typeforce(types.Hash256bit, commitment); + const buffer = Buffer.allocUnsafe(36); + HEADER.copy(buffer, 0); + commitment.copy(buffer, 4); + return bscript.compile([script_1.OPS.OP_RETURN, buffer]); +} +exports.encode = encode; +function decode(buffer) { + typeforce(check, buffer); + return bscript.decompile(buffer)[1].slice(4, 36); +} +exports.decode = decode; diff --git a/src/templates/witnesspubkeyhash/index.js b/src/templates/witnesspubkeyhash/index.js new file mode 100644 index 000000000..b8cd6c435 --- /dev/null +++ b/src/templates/witnesspubkeyhash/index.js @@ -0,0 +1,6 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); +exports.input = input; +const output = require('./output'); +exports.output = output; diff --git a/src/templates/witnesspubkeyhash/input.js b/src/templates/witnesspubkeyhash/input.js new file mode 100644 index 000000000..434358436 --- /dev/null +++ b/src/templates/witnesspubkeyhash/input.js @@ -0,0 +1,19 @@ +'use strict'; +// {signature} {pubKey} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +function isCompressedCanonicalPubKey(pubKey) { + return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; +} +function check(script) { + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + isCompressedCanonicalPubKey(chunks[1]) + ); +} +exports.check = check; +check.toJSON = () => { + return 'witnessPubKeyHash input'; +}; diff --git a/src/templates/witnesspubkeyhash/output.js b/src/templates/witnesspubkeyhash/output.js new file mode 100644 index 000000000..ea5ed1e09 --- /dev/null +++ b/src/templates/witnesspubkeyhash/output.js @@ -0,0 +1,17 @@ +'use strict'; +// OP_0 {pubKeyHash} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +function check(script) { + const buffer = bscript.compile(script); + return ( + buffer.length === 22 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x14 + ); +} +exports.check = check; +check.toJSON = () => { + return 'Witness pubKeyHash output'; +}; diff --git a/src/templates/witnessscripthash/index.js b/src/templates/witnessscripthash/index.js new file mode 100644 index 000000000..b8cd6c435 --- /dev/null +++ b/src/templates/witnessscripthash/index.js @@ -0,0 +1,6 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = require('./input'); +exports.input = input; +const output = require('./output'); +exports.output = output; diff --git a/src/templates/witnessscripthash/input.js b/src/templates/witnessscripthash/input.js new file mode 100644 index 000000000..f69a81062 --- /dev/null +++ b/src/templates/witnessscripthash/input.js @@ -0,0 +1,39 @@ +'use strict'; +// {serialized scriptPubKey script} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const typeforce = require('typeforce'); +const p2ms = require('../multisig'); +const p2pk = require('../pubkey'); +const p2pkh = require('../pubkeyhash'); +function check(chunks, allowIncomplete) { + typeforce(typeforce.Array, chunks); + if (chunks.length < 1) return false; + const witnessScript = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(witnessScript)) return false; + const witnessScriptChunks = bscript.decompile(witnessScript); + // is witnessScript a valid script? + if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false; + const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); + // match types + if ( + p2pkh.input.check(witnessRawScriptSig) && + p2pkh.output.check(witnessScriptChunks) + ) + return true; + if ( + p2ms.input.check(witnessRawScriptSig, allowIncomplete) && + p2ms.output.check(witnessScriptChunks) + ) + return true; + if ( + p2pk.input.check(witnessRawScriptSig) && + p2pk.output.check(witnessScriptChunks) + ) + return true; + return false; +} +exports.check = check; +check.toJSON = () => { + return 'witnessScriptHash input'; +}; diff --git a/src/templates/witnessscripthash/output.js b/src/templates/witnessscripthash/output.js new file mode 100644 index 000000000..f69a4294b --- /dev/null +++ b/src/templates/witnessscripthash/output.js @@ -0,0 +1,17 @@ +'use strict'; +// OP_0 {scriptHash} +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = require('../../script'); +const script_1 = require('../../script'); +function check(script) { + const buffer = bscript.compile(script); + return ( + buffer.length === 34 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x20 + ); +} +exports.check = check; +check.toJSON = () => { + return 'Witness scriptHash output'; +}; diff --git a/src/transaction.js b/src/transaction.js index fa1b18a7d..c4e6506d0 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,287 +1,478 @@ -var assert = require('assert') -var bufferutils = require('./bufferutils') -var crypto = require('./crypto') -var typeForce = require('typeforce') -var opcodes = require('./opcodes') - -var Script = require('./script') - -function Transaction () { - this.version = 1 - this.locktime = 0 - this.ins = [] - this.outs = [] +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils = require('./bufferutils'); +const bufferutils_1 = require('./bufferutils'); +const bcrypto = require('./crypto'); +const bscript = require('./script'); +const script_1 = require('./script'); +const types = require('./types'); +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); +function varSliceSize(someScript) { + const length = someScript.length; + return varuint.encodingLength(length) + length; } - -Transaction.DEFAULT_SEQUENCE = 0xffffffff -Transaction.SIGHASH_ALL = 0x01 -Transaction.SIGHASH_NONE = 0x02 -Transaction.SIGHASH_SINGLE = 0x03 -Transaction.SIGHASH_ANYONECANPAY = 0x80 - -Transaction.fromBuffer = function (buffer, __disableAssert) { - var offset = 0 - function readSlice (n) { - offset += n - return buffer.slice(offset - n, offset) +function vectorSize(someVector) { + const length = someVector.length; + return ( + varuint.encodingLength(length) + + someVector.reduce((sum, witness) => { + return sum + varSliceSize(witness); + }, 0) + ); +} +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 = { + script: EMPTY_SCRIPT, + valueBuffer: VALUE_UINT64_MAX, +}; +function isOutput(out) { + return out.value !== undefined; +} +class Transaction { + constructor() { + this.version = 1; + this.locktime = 0; + this.ins = []; + this.outs = []; } - - function readUInt32 () { - var i = buffer.readUInt32LE(offset) - offset += 4 - return i + static fromBuffer(buffer, _NO_STRICT) { + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); + } + function readUInt32() { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + } + 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 (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; } - - function readUInt64 () { - var i = bufferutils.readUInt64LE(buffer, offset) - offset += 8 - return i + static fromHex(hex) { + return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); } - - function readVarInt () { - var vi = bufferutils.readVarInt(buffer, offset) - offset += vi.size - return vi.number + static isCoinbaseHash(buffer) { + typeforce(types.Hash256bit, buffer); + for (let i = 0; i < 32; ++i) { + if (buffer[i] !== 0) return false; + } + return true; } - - function readScript () { - return Script.fromBuffer(readSlice(readVarInt())) + isCoinbase() { + return ( + this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) + ); } - - function readGenerationScript () { - return new Script(readSlice(readVarInt()), []) + addInput(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, + index, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence, + witness: EMPTY_WITNESS, + }) - 1 + ); } - - var tx = new Transaction() - tx.version = readUInt32() - - var vinLen = readVarInt() - for (var i = 0; i < vinLen; ++i) { - var hash = readSlice(32) - - if (Transaction.isCoinbaseHash(hash)) { - tx.ins.push({ - hash: hash, - index: readUInt32(), - script: readGenerationScript(), - sequence: readUInt32() - }) + addOutput(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, + }) - 1 + ); + } + hasWitnesses() { + return this.ins.some(x => { + return x.witness.length !== 0; + }); + } + weight() { + const base = this.__byteLength(false); + const total = this.__byteLength(true); + return base * 3 + total; + } + virtualSize() { + return Math.ceil(this.weight() / 4); + } + byteLength() { + return this.__byteLength(true); + } + clone() { + const newTx = new Transaction(); + newTx.version = this.version; + newTx.locktime = this.locktime; + newTx.ins = this.ins.map(txIn => { + return { + hash: txIn.hash, + 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 { - tx.ins.push({ - hash: hash, - index: readUInt32(), - script: readScript(), - sequence: readUInt32() - }) + // "blank" others input scripts + txTmp.ins.forEach(input => { + input.script = EMPTY_SCRIPT; + }); + txTmp.ins[inIndex].script = ourScript; } + // serialize and hash + const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4); + buffer.writeInt32LE(hashType, buffer.length - 4); + txTmp.__toBuffer(buffer, 0, false); + return bcrypto.hash256(buffer); } - - var voutLen = readVarInt() - for (i = 0; i < voutLen; ++i) { - tx.outs.push({ - value: readUInt64(), - script: readScript() - }) - } - - tx.locktime = readUInt32() - - if (!__disableAssert) { - assert.equal(offset, buffer.length, 'Transaction has unexpected data') + 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); } - - return tx -} - -Transaction.fromHex = function (hex) { - return Transaction.fromBuffer(new Buffer(hex, 'hex')) -} - -Transaction.isCoinbaseHash = function (buffer) { - return Array.prototype.every.call(buffer, function (x) { - return x === 0 - }) -} - -Transaction.prototype.addInput = function (hash, index, sequence, script) { - if (sequence === undefined || sequence === null) { - sequence = Transaction.DEFAULT_SEQUENCE + 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)); } - - script = script || Script.EMPTY - - typeForce('Buffer', hash) - typeForce('Number', index) - typeForce('Number', sequence) - typeForce('Script', script) - - assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) - - // Add the input and return the input's index - return (this.ins.push({ - hash: hash, - index: index, - script: script, - sequence: sequence - }) - 1) -} - -Transaction.prototype.addOutput = function (scriptPubKey, value) { - typeForce('Script', scriptPubKey) - typeForce('Number', value) - - // Add the output and return the output's index - return (this.outs.push({ - script: scriptPubKey, - value: value - }) - 1) -} - -Transaction.prototype.clone = function () { - var 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 - } - }) - - newTx.outs = this.outs.map(function (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. - */ -Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) { - typeForce('Number', inIndex) - typeForce('Script', prevOutScript) - typeForce('Number', hashType) - - assert(inIndex >= 0, 'Invalid vin index') - assert(inIndex < this.ins.length, 'Invalid vin index') - - var txTmp = this.clone() - var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR) - - // Blank out other inputs' signatures - txTmp.ins.forEach(function (txIn) { - txIn.script = Script.EMPTY - }) - txTmp.ins[inIndex].script = hashScript - - var hashTypeModifier = hashType & 0x1f - - if (hashTypeModifier === Transaction.SIGHASH_NONE) { - assert(false, 'SIGHASH_NONE not yet supported') - } else if (hashTypeModifier === Transaction.SIGHASH_SINGLE) { - assert(false, 'SIGHASH_SINGLE not yet supported') + getId() { + // transaction hash's are displayed in reverse order + return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); } - - if (hashType & Transaction.SIGHASH_ANYONECANPAY) { - assert(false, 'SIGHASH_ANYONECANPAY not yet supported') + toBuffer(buffer, initialOffset) { + return this.__toBuffer(buffer, initialOffset, true); } - - var hashTypeBuffer = new Buffer(4) - hashTypeBuffer.writeInt32LE(hashType, 0) - - var buffer = Buffer.concat([txTmp.toBuffer(), hashTypeBuffer]) - return crypto.hash256(buffer) -} - -Transaction.prototype.getHash = function () { - return crypto.hash256(this.toBuffer()) -} - -Transaction.prototype.getId = function () { - // TxHash is little-endian, we need big-endian - return bufferutils.reverse(this.getHash()).toString('hex') -} - -Transaction.prototype.toBuffer = function () { - function scriptSize (script) { - var length = script.buffer.length - - return bufferutils.varIntSize(length) + length + toHex() { + return this.toBuffer(undefined, undefined).toString('hex'); } - - var buffer = new Buffer( - 8 + - bufferutils.varIntSize(this.ins.length) + - bufferutils.varIntSize(this.outs.length) + - this.ins.reduce(function (sum, input) { return sum + 40 + scriptSize(input.script) }, 0) + - this.outs.reduce(function (sum, output) { return sum + 8 + scriptSize(output.script) }, 0) - ) - - var offset = 0 - function writeSlice (slice) { - slice.copy(buffer, offset) - offset += slice.length + setInputScript(index, scriptSig) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.ins[index].script = scriptSig; } - - function writeUInt32 (i) { - buffer.writeUInt32LE(i, offset) - offset += 4 + setWitness(index, witness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + this.ins[index].witness = witness; } - - function writeUInt64 (i) { - bufferutils.writeUInt64LE(buffer, i, offset) - offset += 8 + __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) + ); } - - function writeVarInt (i) { - var n = bufferutils.writeVarInt(buffer, i, offset) - offset += n + __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.version) - writeVarInt(this.ins.length) - - this.ins.forEach(function (txIn) { - writeSlice(txIn.hash) - writeUInt32(txIn.index) - writeVarInt(txIn.script.buffer.length) - writeSlice(txIn.script.buffer) - writeUInt32(txIn.sequence) - }) - - writeVarInt(this.outs.length) - this.outs.forEach(function (txOut) { - writeUInt64(txOut.value) - writeVarInt(txOut.script.buffer.length) - writeSlice(txOut.script.buffer) - }) - - writeUInt32(this.locktime) - - return buffer -} - -Transaction.prototype.toHex = function () { - return this.toBuffer().toString('hex') -} - -Transaction.prototype.setInputScript = function (index, script) { - typeForce('Number', index) - typeForce('Script', script) - - this.ins[index].script = script } - -module.exports = Transaction +Transaction.DEFAULT_SEQUENCE = 0xffffffff; +Transaction.SIGHASH_ALL = 0x01; +Transaction.SIGHASH_NONE = 0x02; +Transaction.SIGHASH_SINGLE = 0x03; +Transaction.SIGHASH_ANYONECANPAY = 0x80; +Transaction.ADVANCED_TRANSACTION_MARKER = 0x00; +Transaction.ADVANCED_TRANSACTION_FLAG = 0x01; +exports.Transaction = Transaction; diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 924f177e5..a13c4818e 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -1,380 +1,1065 @@ -var assert = require('assert') -var ops = require('./opcodes') -var scripts = require('./scripts') - -var Address = require('./address') -var ECPubKey = require('./ecpubkey') -var ECSignature = require('./ecsignature') -var Script = require('./script') -var Transaction = require('./transaction') - -function extractInput (txIn) { - var redeemScript - var scriptSig = txIn.script - var prevOutScript - var prevOutType = scripts.classifyInput(scriptSig, true) - var scriptType - - // Re-classify if scriptHash - if (prevOutType === 'scripthash') { - redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0]) - prevOutScript = scripts.scriptHashOutput(redeemScript.getHash()) - - scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1)) - scriptType = scripts.classifyInput(scriptSig, true) - } else { - scriptType = prevOutType - } - - // Extract hashType, pubKeys and signatures - var hashType, parsed, pubKeys, signatures - - switch (scriptType) { - case 'pubkeyhash': { - parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) - hashType = parsed.hashType - pubKeys = [ECPubKey.fromBuffer(scriptSig.chunks[1])] - signatures = [parsed.signature] - prevOutScript = pubKeys[0].getAddress().toOutputScript() - - break - } - - case 'pubkey': { - parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) - hashType = parsed.hashType - signatures = [parsed.signature] - - if (redeemScript) { - pubKeys = [ECPubKey.fromBuffer(redeemScript.chunks[0])] - } - - break - } - - case 'multisig': { - signatures = scriptSig.chunks.slice(1).map(function (chunk) { - if (chunk === ops.OP_0) return chunk - - var parsed = ECSignature.parseScriptSignature(chunk) - hashType = parsed.hashType - - return parsed.signature - }) - - if (redeemScript) { - pubKeys = redeemScript.chunks.slice(1, -2).map(ECPubKey.fromBuffer) - } - - break - } - } - - return { - hashType: hashType, - prevOutScript: prevOutScript, - prevOutType: prevOutType, - pubKeys: pubKeys, - redeemScript: redeemScript, - scriptType: scriptType, - signatures: signatures +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const baddress = require('./address'); +const bufferutils_1 = require('./bufferutils'); +const classify = require('./classify'); +const bcrypto = require('./crypto'); +const ECPair = require('./ecpair'); +const networks = require('./networks'); +const payments = require('./payments'); +const bscript = require('./script'); +const script_1 = require('./script'); +const transaction_1 = require('./transaction'); +const types = require('./types'); +const typeforce = require('typeforce'); +const SCRIPT_TYPES = classify.types; +const PREVOUT_TYPES = new Set([ + // Raw + 'p2pkh', + 'p2pk', + 'p2wpkh', + 'p2ms', + // P2SH wrapped + 'p2sh-p2pkh', + 'p2sh-p2pk', + 'p2sh-p2wpkh', + 'p2sh-p2ms', + // P2WSH wrapped + 'p2wsh-p2pkh', + 'p2wsh-p2pk', + 'p2wsh-p2ms', + // P2SH-P2WSH wrapper + 'p2sh-p2wsh-p2pkh', + 'p2sh-p2wsh-p2pk', + 'p2sh-p2wsh-p2ms', +]); +function tfMessage(type, value, message) { + try { + typeforce(type, value); + } catch (err) { + throw new Error(message); } } - -function TransactionBuilder () { - this.prevTxMap = {} - this.prevOutScripts = {} - this.prevOutTypes = {} - - this.inputs = [] - this.tx = new Transaction() +function txIsString(tx) { + return typeof tx === 'string' || tx instanceof String; } - -TransactionBuilder.fromTransaction = function (transaction) { - var txb = new TransactionBuilder() - - // Copy other transaction fields - txb.tx.version = transaction.version - txb.tx.locktime = transaction.locktime - - // Extract/add inputs - transaction.ins.forEach(function (txIn) { - txb.addInput(txIn.hash, txIn.index, txIn.sequence) - }) - - // Extract/add outputs - transaction.outs.forEach(function (txOut) { - txb.addOutput(txOut.script, txOut.value) - }) - - // Extract/add signatures - txb.inputs = transaction.ins.map(function (txIn) { - // TODO: remove me after testcase added - assert(!Transaction.isCoinbaseHash(txIn.hash), 'coinbase inputs not supported') - - // Ignore empty scripts - if (txIn.script.buffer.length === 0) return - - return extractInput(txIn) - }) - - return txb +function txIsTransaction(tx) { + return tx instanceof transaction_1.Transaction; } - -TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) { - // is it a txId? - if (typeof txHash === 'string') { - // a txId is big-endian hex, we want a little-endian Buffer - txHash = new Buffer(txHash, 'hex') - Array.prototype.reverse.call(txHash) - - // is it a Transaction? - } else if (txHash instanceof Transaction) { - prevOutScript = txHash.outs[vout].script - txHash = txHash.getHash() - } - - var input = {} - if (prevOutScript) { - var prevOutType = scripts.classifyOutput(prevOutScript) - - // if we can, extract pubKey information - switch (prevOutType) { - case 'multisig': { - input.pubKeys = prevOutScript.chunks.slice(1, -2).map(ECPubKey.fromBuffer) - break +class TransactionBuilder { + // WARNING: maximumFeeRate is __NOT__ to be relied on, + // it's just another potential safety mechanism (safety in-depth) + constructor(network = networks.bitcoin, maximumFeeRate = 2500) { + this.network = network; + this.maximumFeeRate = maximumFeeRate; + this.__PREV_TX_SET = {}; + this.__INPUTS = []; + this.__TX = new transaction_1.Transaction(); + this.__TX.version = 2; + this.__USE_LOW_R = false; + console.warn( + 'Deprecation Warning: TransactionBuilder will be removed in the future. ' + + '(v6.x.x or later) Please use the Psbt class instead. Examples of usage ' + + 'are available in the transactions-psbt.js integration test file on our ' + + 'Github. A high level explanation is available in the psbt.ts and psbt.js ' + + 'files as well.', + ); + } + static fromTransaction(transaction, network) { + const txb = new TransactionBuilder(network); + // Copy transaction fields + txb.setVersion(transaction.version); + txb.setLockTime(transaction.locktime); + // Copy outputs (done first to avoid signature invalidation) + transaction.outs.forEach(txOut => { + txb.addOutput(txOut.script, txOut.value); + }); + // Copy inputs + transaction.ins.forEach(txIn => { + txb.__addInputUnsafe(txIn.hash, txIn.index, { + sequence: txIn.sequence, + script: txIn.script, + witness: txIn.witness, + }); + }); + // fix some things not possible through the public API + txb.__INPUTS.forEach((input, i) => { + fixMultisigOrder(input, transaction, i); + }); + return txb; + } + setLowR(setting) { + typeforce(typeforce.maybe(typeforce.Boolean), setting); + if (setting === undefined) { + setting = true; + } + this.__USE_LOW_R = setting; + return setting; + } + setLockTime(locktime) { + typeforce(types.UInt32, locktime); + // if any signatures exist, throw + if ( + this.__INPUTS.some(input => { + if (!input.signatures) return false; + return input.signatures.some(s => s !== undefined); + }) + ) { + throw new Error('No, this would invalidate signatures'); + } + this.__TX.locktime = locktime; + } + setVersion(version) { + typeforce(types.UInt32, version); + // XXX: this might eventually become more complex depending on what the versions represent + this.__TX.version = version; + } + addInput(txHash, vout, sequence, prevOutScript) { + if (!this.__canModifyInputs()) { + throw new Error('No, this would invalidate signatures'); + } + let value; + // is it a hex string? + if (txIsString(txHash)) { + // transaction hashs's are displayed in reverse order, un-reverse it + txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); + // is it a Transaction object? + } else if (txIsTransaction(txHash)) { + const txOut = txHash.outs[vout]; + prevOutScript = txOut.script; + value = txOut.value; + txHash = txHash.getHash(false); + } + return this.__addInputUnsafe(txHash, vout, { + sequence, + prevOutScript, + value, + }); + } + addOutput(scriptPubKey, value) { + if (!this.__canModifyOutputs()) { + throw new Error('No, this would invalidate signatures'); + } + // Attempt to get a script if it's a base58 or bech32 address string + if (typeof scriptPubKey === 'string') { + scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network); + } + return this.__TX.addOutput(scriptPubKey, value); + } + build() { + return this.__build(false); + } + buildIncomplete() { + return this.__build(true); + } + sign( + signParams, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + ) { + trySign( + getSigningData( + this.network, + this.__INPUTS, + this.__needsOutputs.bind(this), + this.__TX, + signParams, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + this.__USE_LOW_R, + ), + ); + } + __addInputUnsafe(txHash, vout, options) { + if (transaction_1.Transaction.isCoinbaseHash(txHash)) { + throw new Error('coinbase inputs not supported'); + } + const prevTxOut = txHash.toString('hex') + ':' + vout; + if (this.__PREV_TX_SET[prevTxOut] !== undefined) + throw new Error('Duplicate TxOut: ' + prevTxOut); + let input = {}; + // derive what we can from the scriptSig + if (options.script !== undefined) { + input = expandInput(options.script, options.witness || []); + } + // if an input value was given, retain it + if (options.value !== undefined) { + input.value = options.value; + } + // derive what we can from the previous transactions output script + if (!input.prevOutScript && options.prevOutScript) { + let prevOutType; + if (!input.pubkeys && !input.signatures) { + const expanded = expandOutput(options.prevOutScript); + if (expanded.pubkeys) { + input.pubkeys = expanded.pubkeys; + input.signatures = expanded.signatures; + } + prevOutType = expanded.type; } - - case 'pubkey': { - input.pubKeys = prevOutScript.chunks.slice(0, 1).map(ECPubKey.fromBuffer) - break + input.prevOutScript = options.prevOutScript; + input.prevOutType = prevOutType || classify.output(options.prevOutScript); + } + const vin = this.__TX.addInput( + txHash, + vout, + options.sequence, + options.scriptSig, + ); + this.__INPUTS[vin] = input; + this.__PREV_TX_SET[prevTxOut] = true; + return vin; + } + __build(allowIncomplete) { + if (!allowIncomplete) { + if (!this.__TX.ins.length) throw new Error('Transaction has no inputs'); + if (!this.__TX.outs.length) throw new Error('Transaction has no outputs'); + } + const tx = this.__TX.clone(); + // create script signatures from inputs + this.__INPUTS.forEach((input, i) => { + if (!input.prevOutType && !allowIncomplete) + throw new Error('Transaction is not complete'); + const result = build(input.prevOutType, input, allowIncomplete); + if (!result) { + if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) + throw new Error('Unknown input type'); + if (!allowIncomplete) throw new Error('Not enough information'); + return; + } + tx.setInputScript(i, result.input); + tx.setWitness(i, result.witness); + }); + if (!allowIncomplete) { + // do not rely on this, its merely a last resort + if (this.__overMaximumFees(tx.virtualSize())) { + throw new Error('Transaction has absurd fees'); } } - - if (prevOutType !== 'scripthash') { - input.scriptType = prevOutType - } - - input.prevOutScript = prevOutScript - input.prevOutType = prevOutType - } - - assert(this.inputs.every(function (input2) { - if (input2.hashType === undefined) return true - - return input2.hashType & Transaction.SIGHASH_ANYONECANPAY - }), 'No, this would invalidate signatures') - - var prevOut = txHash.toString('hex') + ':' + vout - assert(!(prevOut in this.prevTxMap), 'Transaction is already an input') - - var vin = this.tx.addInput(txHash, vout, sequence) - this.inputs[vin] = input - this.prevTxMap[prevOut] = vin - - return vin -} - -TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) { - assert(this.inputs.every(function (input) { - if (input.hashType === undefined) return true - - return (input.hashType & 0x1f) === Transaction.SIGHASH_SINGLE - }), 'No, this would invalidate signatures') - - // Attempt to get a valid address if it's a base58 address string - if (typeof scriptPubKey === 'string') { - scriptPubKey = Address.fromBase58Check(scriptPubKey) - } - - // Attempt to get a valid script if it's an Address object - if (scriptPubKey instanceof Address) { - scriptPubKey = scriptPubKey.toOutputScript() - } - - return this.tx.addOutput(scriptPubKey, value) + return tx; + } + __canModifyInputs() { + return this.__INPUTS.every(input => { + if (!input.signatures) return true; + return input.signatures.every(signature => { + if (!signature) return true; + const hashType = signatureHashType(signature); + // if SIGHASH_ANYONECANPAY is set, signatures would not + // be invalidated by more inputs + return ( + (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0 + ); + }); + }); + } + __needsOutputs(signingHashType) { + if (signingHashType === transaction_1.Transaction.SIGHASH_ALL) { + return this.__TX.outs.length === 0; + } + // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs + // .build() will fail, but .buildIncomplete() is OK + return ( + this.__TX.outs.length === 0 && + this.__INPUTS.some(input => { + if (!input.signatures) return false; + return input.signatures.some(signature => { + if (!signature) return false; // no signature, no issue + const hashType = signatureHashType(signature); + if (hashType & transaction_1.Transaction.SIGHASH_NONE) return false; // SIGHASH_NONE doesn't care about outputs + return true; // SIGHASH_* does care + }); + }) + ); + } + __canModifyOutputs() { + const nInputs = this.__TX.ins.length; + const nOutputs = this.__TX.outs.length; + return this.__INPUTS.every(input => { + if (input.signatures === undefined) return true; + return input.signatures.every(signature => { + if (!signature) return true; + const hashType = signatureHashType(signature); + const hashTypeMod = hashType & 0x1f; + if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) return true; + if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { + // if SIGHASH_SINGLE is set, and nInputs > nOutputs + // some signatures would be invalidated by the addition + // of more outputs + return nInputs <= nOutputs; + } + return false; + }); + }); + } + __overMaximumFees(bytes) { + // not all inputs will have .value defined + const incoming = this.__INPUTS.reduce((a, x) => a + (x.value >>> 0), 0); + // but all outputs do, and if we have any input value + // we can immediately determine if the outputs are too small + const outgoing = this.__TX.outs.reduce((a, x) => a + x.value, 0); + const fee = incoming - outgoing; + const feeRate = fee / bytes; + return feeRate > this.maximumFeeRate; + } } - -TransactionBuilder.prototype.build = function () { - return this.__build(false) +exports.TransactionBuilder = TransactionBuilder; +function expandInput(scriptSig, witnessStack, type, scriptPubKey) { + if (scriptSig.length === 0 && witnessStack.length === 0) return {}; + if (!type) { + let ssType = classify.input(scriptSig, true); + let wsType = classify.witness(witnessStack, true); + if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined; + if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined; + type = ssType || wsType; + } + switch (type) { + case SCRIPT_TYPES.P2WPKH: { + const { output, pubkey, signature } = payments.p2wpkh({ + witness: witnessStack, + }); + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WPKH, + pubkeys: [pubkey], + signatures: [signature], + }; + } + case SCRIPT_TYPES.P2PKH: { + const { output, pubkey, signature } = payments.p2pkh({ + input: scriptSig, + }); + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2PKH, + pubkeys: [pubkey], + signatures: [signature], + }; + } + case SCRIPT_TYPES.P2PK: { + const { signature } = payments.p2pk({ input: scriptSig }); + return { + prevOutType: SCRIPT_TYPES.P2PK, + pubkeys: [undefined], + signatures: [signature], + }; + } + case SCRIPT_TYPES.P2MS: { + const { m, pubkeys, signatures } = payments.p2ms( + { + input: scriptSig, + output: scriptPubKey, + }, + { allowIncomplete: true }, + ); + return { + prevOutType: SCRIPT_TYPES.P2MS, + pubkeys, + signatures, + maxSignatures: m, + }; + } + } + if (type === SCRIPT_TYPES.P2SH) { + const { output, redeem } = payments.p2sh({ + input: scriptSig, + witness: witnessStack, + }); + const outputType = classify.output(redeem.output); + const expanded = expandInput( + redeem.input, + redeem.witness, + outputType, + redeem.output, + ); + if (!expanded.prevOutType) return {}; + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2SH, + redeemScript: redeem.output, + redeemScriptType: expanded.prevOutType, + witnessScript: expanded.witnessScript, + witnessScriptType: expanded.witnessScriptType, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + }; + } + if (type === SCRIPT_TYPES.P2WSH) { + const { output, redeem } = payments.p2wsh({ + input: scriptSig, + witness: witnessStack, + }); + const outputType = classify.output(redeem.output); + let expanded; + if (outputType === SCRIPT_TYPES.P2WPKH) { + expanded = expandInput(redeem.input, redeem.witness, outputType); + } else { + expanded = expandInput( + bscript.compile(redeem.witness), + [], + outputType, + redeem.output, + ); + } + if (!expanded.prevOutType) return {}; + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WSH, + witnessScript: redeem.output, + witnessScriptType: expanded.prevOutType, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + }; + } + return { + prevOutType: SCRIPT_TYPES.NONSTANDARD, + prevOutScript: scriptSig, + }; } -TransactionBuilder.prototype.buildIncomplete = function () { - return this.__build(true) +// could be done in expandInput, but requires the original Transaction for hashForSignature +function fixMultisigOrder(input, transaction, vin) { + if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) + return; + if (input.pubkeys.length === input.signatures.length) return; + const unmatched = input.signatures.concat(); + input.signatures = input.pubkeys.map(pubKey => { + const keyPair = ECPair.fromPublicKey(pubKey); + let match; + // check for a signature + unmatched.some((signature, i) => { + // skip if undefined || OP_0 + if (!signature) return false; + // TODO: avoid O(n) hashForSignature + const parsed = bscript.signature.decode(signature); + const hash = transaction.hashForSignature( + vin, + input.redeemScript, + parsed.hashType, + ); + // skip if signature does not match pubKey + if (!keyPair.verify(hash, parsed.signature)) return false; + // remove matched signature from unmatched + unmatched[i] = undefined; + match = signature; + return true; + }); + return match; + }); } - -var canSignTypes = { - 'pubkeyhash': true, - 'multisig': true, - 'pubkey': true +function expandOutput(script, ourPubKey) { + typeforce(types.Buffer, script); + const type = classify.output(script); + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (!ourPubKey) return { type }; + // does our hash160(pubKey) match the output scripts? + const pkh1 = payments.p2pkh({ output: script }).hash; + const pkh2 = bcrypto.hash160(ourPubKey); + if (!pkh1.equals(pkh2)) return { type }; + return { + type, + pubkeys: [ourPubKey], + signatures: [undefined], + }; + } + case SCRIPT_TYPES.P2WPKH: { + if (!ourPubKey) return { type }; + // does our hash160(pubKey) match the output scripts? + const wpkh1 = payments.p2wpkh({ output: script }).hash; + const wpkh2 = bcrypto.hash160(ourPubKey); + if (!wpkh1.equals(wpkh2)) return { type }; + return { + type, + pubkeys: [ourPubKey], + signatures: [undefined], + }; + } + case SCRIPT_TYPES.P2PK: { + const p2pk = payments.p2pk({ output: script }); + return { + type, + pubkeys: [p2pk.pubkey], + signatures: [undefined], + }; + } + case SCRIPT_TYPES.P2MS: { + const p2ms = payments.p2ms({ output: script }); + return { + type, + pubkeys: p2ms.pubkeys, + signatures: p2ms.pubkeys.map(() => undefined), + maxSignatures: p2ms.m, + }; + } + } + return { type }; } - -TransactionBuilder.prototype.__build = function (allowIncomplete) { - if (!allowIncomplete) { - assert(this.tx.ins.length > 0, 'Transaction has no inputs') - assert(this.tx.outs.length > 0, 'Transaction has no outputs') - } - - var tx = this.tx.clone() - - // Create script signatures from signature meta-data - this.inputs.forEach(function (input, index) { - var scriptType = input.scriptType - var scriptSig - - if (!allowIncomplete) { - assert(!!scriptType, 'Transaction is not complete') - assert(scriptType in canSignTypes, scriptType + ' not supported') - assert(input.signatures, 'Transaction is missing signatures') - } - - if (input.signatures) { - switch (scriptType) { - case 'pubkeyhash': { - var pkhSignature = input.signatures[0].toScriptSignature(input.hashType) - scriptSig = scripts.pubKeyHashInput(pkhSignature, input.pubKeys[0]) - break - } - - case 'multisig': { - // Array.prototype.map is sparse-compatible - var msSignatures = input.signatures.map(function (signature) { - return signature.toScriptSignature(input.hashType) - }) - - // fill in blanks with OP_0 - for (var i = 0; i < msSignatures.length; ++i) { - if (msSignatures[i]) continue - - msSignatures[i] = ops.OP_0 - } - - var redeemScript = allowIncomplete ? undefined : input.redeemScript - scriptSig = scripts.multisigInput(msSignatures, redeemScript) - break - } - - case 'pubkey': { - var pkSignature = input.signatures[0].toScriptSignature(input.hashType) - scriptSig = scripts.pubKeyInput(pkSignature) - break - } +function prepareInput(input, ourPubKey, redeemScript, witnessScript) { + if (redeemScript && witnessScript) { + const p2wsh = payments.p2wsh({ + redeem: { output: witnessScript }, + }); + const p2wshAlt = payments.p2wsh({ output: redeemScript }); + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); + const p2shAlt = payments.p2sh({ redeem: p2wsh }); + // enforces P2SH(P2WSH(...)) + if (!p2wsh.hash.equals(p2wshAlt.hash)) + throw new Error('Witness script inconsistent with prevOutScript'); + if (!p2sh.hash.equals(p2shAlt.hash)) + throw new Error('Redeem script inconsistent with prevOutScript'); + const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as witnessScript (' + + bscript.toASM(witnessScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + const signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure'); + return { + redeemScript, + redeemScriptType: SCRIPT_TYPES.P2WSH, + witnessScript, + witnessScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + hasWitness: true, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + if (redeemScript) { + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }); + if (input.prevOutScript) { + let p2shAlt; + try { + p2shAlt = payments.p2sh({ output: input.prevOutScript }); + } catch (e) { + throw new Error('PrevOutScript must be P2SH'); } + if (!p2sh.hash.equals(p2shAlt.hash)) + throw new Error('Redeem script inconsistent with prevOutScript'); + } + const expanded = expandOutput(p2sh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as redeemScript (' + + bscript.toASM(redeemScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = redeemScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; + } + return { + redeemScript, + redeemScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + if (witnessScript) { + const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); + if (input.prevOutScript) { + const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }); + if (!p2wsh.hash.equals(p2wshAlt.hash)) + throw new Error('Witness script inconsistent with prevOutScript'); + } + const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as witnessScript (' + + bscript.toASM(witnessScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; } - - // did we build a scriptSig? - if (scriptSig) { - // wrap as scriptHash if necessary - if (input.prevOutType === 'scripthash') { - scriptSig = scripts.scriptHashInput(scriptSig, input.redeemScript) + const signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2WSH(P2WPKH) is a consensus failure'); + return { + witnessScript, + witnessScriptType: expanded.type, + prevOutType: SCRIPT_TYPES.P2WSH, + prevOutScript: p2wsh.output, + hasWitness: true, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + if (input.prevOutType && input.prevOutScript) { + // embedded scripts are not possible without extra information + if (input.prevOutType === SCRIPT_TYPES.P2SH) + throw new Error( + 'PrevOutScript is ' + input.prevOutType + ', requires redeemScript', + ); + if (input.prevOutType === SCRIPT_TYPES.P2WSH) + throw new Error( + 'PrevOutScript is ' + input.prevOutType + ', requires witnessScript', + ); + if (!input.prevOutScript) throw new Error('PrevOutScript is missing'); + const expanded = expandOutput(input.prevOutScript, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported (' + + bscript.toASM(input.prevOutScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + let signScript = input.prevOutScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; + } + return { + prevOutType: expanded.type, + prevOutScript: input.prevOutScript, + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; + return { + prevOutType: SCRIPT_TYPES.P2PKH, + prevOutScript, + hasWitness: false, + signScript: prevOutScript, + signType: SCRIPT_TYPES.P2PKH, + pubkeys: [ourPubKey], + signatures: [undefined], + }; +} +function build(type, input, allowIncomplete) { + const pubkeys = input.pubkeys || []; + let signatures = input.signatures || []; + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); + } + case SCRIPT_TYPES.P2WPKH: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); + } + case SCRIPT_TYPES.P2PK: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + return payments.p2pk({ signature: signatures[0] }); + } + case SCRIPT_TYPES.P2MS: { + const m = input.maxSignatures; + if (allowIncomplete) { + signatures = signatures.map(x => x || script_1.OPS.OP_0); + } else { + signatures = signatures.filter(x => x); } - - tx.setInputScript(index, scriptSig) + // if the transaction is not not complete (complete), or if signatures.length === m, validate + // otherwise, the number of OP_0's may be >= m, so don't validate (boo) + const validate = !allowIncomplete || m === signatures.length; + return payments.p2ms( + { m, pubkeys, signatures }, + { allowIncomplete, validate }, + ); + } + case SCRIPT_TYPES.P2SH: { + const redeem = build(input.redeemScriptType, input, allowIncomplete); + if (!redeem) return; + return payments.p2sh({ + redeem: { + output: redeem.output || input.redeemScript, + input: redeem.input, + witness: redeem.witness, + }, + }); } - }) - - return tx + case SCRIPT_TYPES.P2WSH: { + const redeem = build(input.witnessScriptType, input, allowIncomplete); + if (!redeem) return; + return payments.p2wsh({ + redeem: { + output: input.witnessScript, + input: redeem.input, + witness: redeem.witness, + }, + }); + } + } } - -TransactionBuilder.prototype.sign = function (index, privKey, redeemScript, hashType) { - assert(index in this.inputs, 'No input at index: ' + index) - hashType = hashType || Transaction.SIGHASH_ALL - - var input = this.inputs[index] - var canSign = input.hashType && - input.prevOutScript && - input.prevOutType && - input.pubKeys && - input.scriptType && - input.signatures - - // are we almost ready to sign? - if (canSign) { - // if redeemScript was provided, enforce consistency - if (redeemScript) { - assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript') - } - - assert.equal(input.hashType, hashType, 'Inconsistent hashType') - - // no? prepare - } else { - // must be pay-to-scriptHash? - if (redeemScript) { - // if we have a prevOutScript, enforce scriptHash equality to the redeemScript - if (input.prevOutScript) { - assert.equal(input.prevOutType, 'scripthash', 'PrevOutScript must be P2SH') - - var scriptHash = input.prevOutScript.chunks[1] - assert.deepEqual(scriptHash, redeemScript.getHash(), 'RedeemScript does not match ' + scriptHash.toString('hex')) +function canSign(input) { + return ( + input.signScript !== undefined && + input.signType !== undefined && + input.pubkeys !== undefined && + input.signatures !== undefined && + input.signatures.length === input.pubkeys.length && + input.pubkeys.length > 0 && + (input.hasWitness === false || input.value !== undefined) + ); +} +function signatureHashType(buffer) { + return buffer.readUInt8(buffer.length - 1); +} +function checkSignArgs(inputs, signParams) { + if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) { + throw new TypeError( + `Unknown prevOutScriptType "${signParams.prevOutScriptType}"`, + ); + } + tfMessage( + typeforce.Number, + signParams.vin, + `sign must include vin parameter as Number (input index)`, + ); + tfMessage( + types.Signer, + signParams.keyPair, + `sign must include keyPair parameter as Signer interface`, + ); + tfMessage( + typeforce.maybe(typeforce.Number), + signParams.hashType, + `sign hashType parameter must be a number`, + ); + const prevOutType = (inputs[signParams.vin] || []).prevOutType; + const posType = signParams.prevOutScriptType; + switch (posType) { + case 'p2pkh': + if (prevOutType && prevOutType !== 'pubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pkh: ${prevOutType}`, + ); } - - var scriptType = scripts.classifyOutput(redeemScript) - assert(scriptType in canSignTypes, 'RedeemScript not supported (' + scriptType + ')') - - var pubKeys = [] - switch (scriptType) { - case 'multisig': { - pubKeys = redeemScript.chunks.slice(1, -2).map(ECPubKey.fromBuffer) - break - } - - case 'pubkeyhash': { - var pkh1 = redeemScript.chunks[2] - var pkh2 = privKey.pub.getAddress().hash - - assert.deepEqual(pkh1, pkh2, 'privateKey cannot sign for this input') - pubKeys = [privKey.pub] - break - } - - case 'pubkey': { - pubKeys = redeemScript.chunks.slice(0, 1).map(ECPubKey.fromBuffer) - break - } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2pk': + if (prevOutType && prevOutType !== 'pubkey') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pk: ${prevOutType}`, + ); } - - if (!input.prevOutScript) { - input.prevOutScript = scripts.scriptHashOutput(redeemScript.getHash()) - input.prevOutType = 'scripthash' + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wpkh': + if (prevOutType && prevOutType !== 'witnesspubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2wpkh: ${prevOutType}`, + ); } - - input.pubKeys = pubKeys - input.redeemScript = redeemScript - input.scriptType = scriptType - - // cannot be pay-to-scriptHash - } else { - assert.notEqual(input.prevOutType, 'scripthash', 'PrevOutScript is P2SH, missing redeemScript') - - // can we otherwise sign this? - if (input.scriptType) { - assert(input.pubKeys, input.scriptType + ' not supported') - - // we know nothin' Jon Snow, assume pubKeyHash - } else { - input.prevOutScript = privKey.pub.getAddress().toOutputScript() - input.prevOutType = 'pubkeyhash' - input.pubKeys = [privKey.pub] - input.scriptType = input.prevOutType + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2ms': + if (prevOutType && prevOutType !== 'multisig') { + throw new TypeError( + `input #${signParams.vin} is not of type p2ms: ${prevOutType}`, + ); } - } - - input.hashType = hashType - input.signatures = input.signatures || [] + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2sh-p2wpkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2sh-p2wpkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2ms': + case 'p2sh-p2pk': + case 'p2sh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wsh-p2ms': + case 'p2wsh-p2pk': + case 'p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'witnessscripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2wsh-p2ms': + case 'p2sh-p2wsh-p2pk': + case 'p2sh-p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires witnessScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessScript`, + ); + break; } - +} +function trySign({ + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR, +}) { // enforce in order signing of public keys - assert(input.pubKeys.some(function (pubKey, i) { - if (!privKey.pub.Q.equals(pubKey.Q)) return false - - assert(!input.signatures[i], 'Signature already exists') - var signatureScript = input.redeemScript || input.prevOutScript - var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) - var signature = privKey.sign(signatureHash) - input.signatures[i] = signature - - return true - }, this), 'privateKey cannot sign for this input') + let signed = false; + for (const [i, pubKey] of input.pubkeys.entries()) { + if (!ourPubKey.equals(pubKey)) continue; + if (input.signatures[i]) throw new Error('Signature already exists'); + // TODO: add tests + if (ourPubKey.length !== 33 && input.hasWitness) { + throw new Error( + 'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', + ); + } + const signature = keyPair.sign(signatureHash, useLowR); + input.signatures[i] = bscript.signature.encode(signature, hashType); + signed = true; + } + if (!signed) throw new Error('Key pair cannot sign for this input'); +} +function getSigningData( + network, + inputs, + needsOutputs, + tx, + signParams, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + useLowR, +) { + let vin; + if (typeof signParams === 'number') { + console.warn( + 'DEPRECATED: TransactionBuilder sign method arguments ' + + 'will change in v6, please use the TxbSignArg interface', + ); + vin = signParams; + } else if (typeof signParams === 'object') { + checkSignArgs(inputs, signParams); + ({ + vin, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + } = signParams); + } else { + throw new TypeError( + 'TransactionBuilder sign first arg must be TxbSignArg or number', + ); + } + if (keyPair === undefined) { + throw new Error('sign requires keypair'); + } + // TODO: remove keyPair.network matching in 4.0.0 + if (keyPair.network && keyPair.network !== network) + throw new TypeError('Inconsistent network'); + if (!inputs[vin]) throw new Error('No input at index: ' + vin); + hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; + if (needsOutputs(hashType)) throw new Error('Transaction needs outputs'); + const input = inputs[vin]; + // if redeemScript was previously provided, enforce consistency + if ( + input.redeemScript !== undefined && + redeemScript && + !input.redeemScript.equals(redeemScript) + ) { + throw new Error('Inconsistent redeemScript'); + } + const ourPubKey = + keyPair.publicKey || (keyPair.getPublicKey && keyPair.getPublicKey()); + if (!canSign(input)) { + if (witnessValue !== undefined) { + if (input.value !== undefined && input.value !== witnessValue) + throw new Error('Input did not match witnessValue'); + typeforce(types.Satoshi, witnessValue); + input.value = witnessValue; + } + if (!canSign(input)) { + const prepared = prepareInput( + input, + ourPubKey, + redeemScript, + witnessScript, + ); + // updates inline + Object.assign(input, prepared); + } + if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); + } + // ready to sign + let signatureHash; + if (input.hasWitness) { + signatureHash = tx.hashForWitnessV0( + vin, + input.signScript, + input.value, + hashType, + ); + } else { + signatureHash = tx.hashForSignature(vin, input.signScript, hashType); + } + return { + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR: !!useLowR, + }; } - -module.exports = TransactionBuilder diff --git a/src/types.js b/src/types.js new file mode 100644 index 000000000..8bcee2c5a --- /dev/null +++ b/src/types.js @@ -0,0 +1,58 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const typeforce = require('typeforce'); +const UINT31_MAX = Math.pow(2, 31) - 1; +function UInt31(value) { + return typeforce.UInt32(value) && value <= UINT31_MAX; +} +exports.UInt31 = UInt31; +function BIP32Path(value) { + return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); +} +exports.BIP32Path = BIP32Path; +BIP32Path.toJSON = () => { + return 'BIP32 derivation path'; +}; +function Signer(obj) { + return ( + (typeforce.Buffer(obj.publicKey) || + typeof obj.getPublicKey === 'function') && + typeof obj.sign === 'function' + ); +} +exports.Signer = Signer; +const SATOSHI_MAX = 21 * 1e14; +function Satoshi(value) { + return typeforce.UInt53(value) && value <= SATOSHI_MAX; +} +exports.Satoshi = Satoshi; +// external dependent types +exports.ECPoint = typeforce.quacksLike('Point'); +// exposed, external API +exports.Network = typeforce.compile({ + messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), + bip32: { + public: typeforce.UInt32, + private: typeforce.UInt32, + }, + pubKeyHash: typeforce.UInt8, + scriptHash: typeforce.UInt8, + wif: typeforce.UInt8, +}); +exports.Buffer256bit = typeforce.BufferN(32); +exports.Hash160bit = typeforce.BufferN(20); +exports.Hash256bit = typeforce.BufferN(32); +exports.Number = typeforce.Number; // tslint:disable-line variable-name +exports.Array = typeforce.Array; +exports.Boolean = typeforce.Boolean; // tslint:disable-line variable-name +exports.String = typeforce.String; // tslint:disable-line variable-name +exports.Buffer = typeforce.Buffer; +exports.Hex = typeforce.Hex; +exports.maybe = typeforce.maybe; +exports.tuple = typeforce.tuple; +exports.UInt8 = typeforce.UInt8; +exports.UInt32 = typeforce.UInt32; +exports.Function = typeforce.Function; +exports.BufferN = typeforce.BufferN; +exports.Null = typeforce.Null; +exports.oneOf = typeforce.oneOf; diff --git a/test/address.js b/test/address.js index 82230cae7..be168790d 100644 --- a/test/address.js +++ b/test/address.js @@ -1,95 +1,133 @@ -/* global describe, it */ +const { describe, it } = require('mocha') +const assert = require('assert') +const baddress = require('../src/address') +const bscript = require('../src/script') +const fixtures = require('./fixtures/address.json') +const NETWORKS = Object.assign({ + litecoin: { + messagePrefix: '\x19Litecoin Signed Message:\n', + bip32: { + public: 0x019da462, + private: 0x019d9cfe + }, + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0 + } +}, require('../src/networks')) + +describe('address', () => { + describe('fromBase58Check', () => { + fixtures.standard.forEach(f => { + if (!f.base58check) return + + it('decodes ' + f.base58check, () => { + const decode = baddress.fromBase58Check(f.base58check) + + assert.strictEqual(decode.version, f.version) + assert.strictEqual(decode.hash.toString('hex'), f.hash) + }) + }) -var assert = require('assert') -var networks = require('../src/networks') + fixtures.invalid.fromBase58Check.forEach(f => { + it('throws on ' + f.exception, () => { + assert.throws(() => { + baddress.fromBase58Check(f.address) + }, new RegExp(f.address + ' ' + f.exception)) + }) + }) + }) -var Address = require('../src/address') -var Script = require('../src/script') + describe('fromBech32', () => { + fixtures.standard.forEach(f => { + if (!f.bech32) return -var fixtures = require('./fixtures/address.json') + it('decodes ' + f.bech32, () => { + const actual = baddress.fromBech32(f.bech32) -describe('Address', function () { - describe('Constructor', function () { - it('does not mutate the input', function () { - fixtures.valid.forEach(function (f) { - var hash = new Buffer(f.hash, 'hex') - var addr = new Address(hash, f.version) + assert.strictEqual(actual.version, f.version) + assert.strictEqual(actual.prefix, NETWORKS[f.network].bech32) + assert.strictEqual(actual.data.toString('hex'), f.data) + }) + }) - assert.equal(addr.version, f.version) - assert.equal(addr.hash.toString('hex'), f.hash) + fixtures.invalid.bech32.forEach((f, i) => { + it('decode fails for ' + f.bech32 + '(' + f.exception + ')', () => { + assert.throws(() => { + baddress.fromBech32(f.address) + }, new RegExp(f.exception)) }) }) }) - describe('fromBase58Check', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.script + ' (' + f.network + ') correctly', function () { - var addr = Address.fromBase58Check(f.base58check) + describe('fromOutputScript', () => { + fixtures.standard.forEach(f => { + it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => { + const script = bscript.fromASM(f.script) + const address = baddress.fromOutputScript(script, NETWORKS[f.network]) - assert.equal(addr.version, f.version) - assert.equal(addr.hash.toString('hex'), f.hash) + assert.strictEqual(address, f.base58check || f.bech32.toLowerCase()) }) }) - fixtures.invalid.fromBase58Check.forEach(function (f) { - it('throws on ' + f.description, function () { - assert.throws(function () { - Address.fromBase58Check(f.base58check) + fixtures.invalid.fromOutputScript.forEach(f => { + it('throws when ' + f.script.slice(0, 30) + '... ' + f.exception, () => { + const script = bscript.fromASM(f.script) + + assert.throws(() => { + baddress.fromOutputScript(script) }, new RegExp(f.exception)) }) }) }) - describe('fromOutputScript', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.script + ' (' + f.network + ') correctly', function () { - var script = Script.fromASM(f.script) - var addr = Address.fromOutputScript(script, networks[f.network]) + describe('toBase58Check', () => { + fixtures.standard.forEach(f => { + if (!f.base58check) return - assert.equal(addr.version, f.version) - assert.equal(addr.hash.toString('hex'), f.hash) + it('encodes ' + f.hash + ' (' + f.network + ')', () => { + const address = baddress.toBase58Check(Buffer.from(f.hash, 'hex'), f.version) + + assert.strictEqual(address, f.base58check) }) }) + }) - fixtures.invalid.fromOutputScript.forEach(function (f) { - it('throws when ' + f.description, function () { - var script = Script.fromASM(f.script) + describe('toBech32', () => { + fixtures.bech32.forEach((f, i) => { + if (!f.bech32) return + const data = Buffer.from(f.data, 'hex') - assert.throws(function () { - Address.fromOutputScript(script) - }, new RegExp(f.description)) + it('encode ' + f.address, () => { + assert.deepStrictEqual(baddress.toBech32(data, f.version, f.prefix), f.address) }) }) - }) - describe('toBase58Check', function () { - fixtures.valid.forEach(function (f) { - it('exports ' + f.script + ' (' + f.network + ') correctly', function () { - var addr = Address.fromBase58Check(f.base58check) - var result = addr.toBase58Check() + fixtures.invalid.bech32.forEach((f, i) => { + if (!f.prefix || f.version === undefined || f.data === undefined) return - assert.equal(result, f.base58check) + it('encode fails (' + f.exception, () => { + assert.throws(() => { + baddress.toBech32(Buffer.from(f.data, 'hex'), f.version, f.prefix) + }, new RegExp(f.exception)) }) }) }) - describe('toOutputScript', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.script + ' (' + f.network + ') correctly', function () { - var addr = Address.fromBase58Check(f.base58check) - var script = addr.toOutputScript() + describe('toOutputScript', () => { + fixtures.standard.forEach(f => { + it('decodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => { + const script = baddress.toOutputScript(f.base58check || f.bech32, NETWORKS[f.network]) - assert.equal(script.toASM(), f.script) + assert.strictEqual(bscript.toASM(script), f.script) }) }) - fixtures.invalid.toOutputScript.forEach(function (f) { - it('throws when ' + f.description, function () { - var addr = new Address(new Buffer(f.hash, 'hex'), f.version) - - assert.throws(function () { - addr.toOutputScript() - }, new RegExp(f.description)) + fixtures.invalid.toOutputScript.forEach(f => { + it('throws when ' + f.exception, () => { + assert.throws(() => { + baddress.toOutputScript(f.address, f.network) + }, new RegExp(f.address + ' ' + f.exception)) }) }) }) diff --git a/test/bitcoin.core.js b/test/bitcoin.core.js index ee7d4e88e..734c9a918 100644 --- a/test/bitcoin.core.js +++ b/test/bitcoin.core.js @@ -1,234 +1,225 @@ -/* global describe, it */ - -var assert = require('assert') -var base58 = require('bs58') -var base58check = require('bs58check') - -var Bitcoin = require('../') -var Address = Bitcoin.Address -var networks = Bitcoin.networks -var ECKey = Bitcoin.ECKey -var ECSignature = Bitcoin.ECSignature -var Transaction = Bitcoin.Transaction -var Script = Bitcoin.Script - -var base58_encode_decode = require('./fixtures/core/base58_encode_decode.json') -var base58_keys_invalid = require('./fixtures/core/base58_keys_invalid.json') -var base58_keys_valid = require('./fixtures/core/base58_keys_valid.json') -var sig_canonical = require('./fixtures/core/sig_canonical.json') -var sig_noncanonical = require('./fixtures/core/sig_noncanonical.json') -var sighash = require('./fixtures/core/sighash.json') -var tx_valid = require('./fixtures/core/tx_valid.json') - -describe('Bitcoin-core', function () { - // base58_encode_decode - describe('base58', function () { - base58_encode_decode.forEach(function (f) { - var fhex = f[0] - var fb58 = f[1] - - it('can decode ' + fb58, function () { - var buffer = base58.decode(fb58) - var actual = new Buffer(buffer).toString('hex') - - assert.equal(actual, fhex) +const { describe, it } = require('mocha') +const assert = require('assert') +const base58 = require('bs58') +const bitcoin = require('../') + +const base58EncodeDecode = require('./fixtures/core/base58_encode_decode.json') +const base58KeysInvalid = require('./fixtures/core/base58_keys_invalid.json') +const base58KeysValid = require('./fixtures/core/base58_keys_valid.json') +const blocksValid = require('./fixtures/core/blocks.json') +const sigCanonical = require('./fixtures/core/sig_canonical.json') +const sigHash = require('./fixtures/core/sighash.json') +const sigNoncanonical = require('./fixtures/core/sig_noncanonical.json') +const txValid = require('./fixtures/core/tx_valid.json') + +describe('Bitcoin-core', () => { + // base58EncodeDecode + describe('base58', () => { + base58EncodeDecode.forEach(f => { + const fhex = f[0] + const fb58 = f[1] + + it('can decode ' + fb58, () => { + const buffer = base58.decode(fb58) + const actual = buffer.toString('hex') + + assert.strictEqual(actual, fhex) }) - it('can encode ' + fhex, function () { - var buffer = new Buffer(fhex, 'hex') - var actual = base58.encode(buffer) + it('can encode ' + fhex, () => { + const buffer = Buffer.from(fhex, 'hex') + const actual = base58.encode(buffer) - assert.equal(actual, fb58) + assert.strictEqual(actual, fb58) }) }) }) - // base58_keys_valid - describe('Address', function () { - var typeMap = { + // base58KeysValid + describe('address.toBase58Check', () => { + const typeMap = { 'pubkey': 'pubKeyHash', 'script': 'scriptHash' } - base58_keys_valid.forEach(function (f) { - var string = f[0] - var hex = f[1] - var params = f[2] - var network = networks.bitcoin + base58KeysValid.forEach(f => { + const expected = f[0] + const hash = Buffer.from(f[1], 'hex') + const params = f[2] if (params.isPrivkey) return - if (params.isTestnet) - network = networks.testnet - it('can import ' + string, function () { - var address = Address.fromBase58Check(string) + const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + const version = network[typeMap[params.addrType]] - assert.equal(address.hash.toString('hex'), hex) - assert.equal(address.version, network[typeMap[params.addrType]]) + it('can export ' + expected, () => { + assert.strictEqual(bitcoin.address.toBase58Check(hash, version), expected) }) }) }) - // base58_keys_invalid - describe('Address', function () { - var allowedNetworks = [ - networks.bitcoin.pubkeyhash, - networks.bitcoin.scripthash, - networks.testnet.pubkeyhash, - networks.testnet.scripthash + // base58KeysInvalid + describe('address.fromBase58Check', () => { + const allowedNetworks = [ + bitcoin.networks.bitcoin.pubkeyhash, + bitcoin.networks.bitcoin.scripthash, + bitcoin.networks.testnet.pubkeyhash, + bitcoin.networks.testnet.scripthash ] - base58_keys_invalid.forEach(function (f) { - var string = f[0] + base58KeysInvalid.forEach(f => { + const string = f[0] - it('throws on ' + string, function () { - assert.throws(function () { - var address = Address.fromBase58Check(string) + it('throws on ' + string, () => { + assert.throws(() => { + const address = bitcoin.address.fromBase58Check(string) - assert.notEqual(allowedNetworks.indexOf(address.version), -1, 'Invalid network') - }, /Invalid (checksum|hash length|network)/) + assert.notStrictEqual(allowedNetworks.indexOf(address.version), -1, 'Invalid network') + }, /(Invalid (checksum|network))|(too (short|long))/) }) }) }) - // base58_keys_valid - describe('ECKey', function () { - base58_keys_valid.forEach(function (f) { - var string = f[0] - var hex = f[1] - var params = f[2] - var network = params.isTestnet ? networks.testnet : networks.bitcoin + // base58KeysValid + describe('ECPair', () => { + base58KeysValid.forEach(f => { + const string = f[0] + const hex = f[1] + const params = f[2] if (!params.isPrivkey) return - var privKey = ECKey.fromWIF(string) - it('imports ' + string + ' correctly', function () { - assert.equal(privKey.d.toHex(), hex) - assert.equal(privKey.pub.compressed, params.isCompressed) + const network = params.isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin + const keyPair = bitcoin.ECPair.fromWIF(string, network) + + it('fromWIF imports ' + string, () => { + assert.strictEqual(keyPair.privateKey.toString('hex'), hex) + assert.strictEqual(keyPair.compressed, params.isCompressed) }) - it('exports ' + hex + ' to ' + string, function () { - assert.equal(privKey.toWIF(network), string) + it('toWIF exports ' + hex + ' to ' + string, () => { + assert.strictEqual(keyPair.toWIF(), string) }) }) }) - // base58_keys_invalid - describe('ECKey', function () { - var allowedNetworks = [ - networks.bitcoin.wif, - networks.testnet.wif + // base58KeysInvalid + describe('ECPair.fromWIF', () => { + const allowedNetworks = [ + bitcoin.networks.bitcoin, + bitcoin.networks.testnet ] - base58_keys_invalid.forEach(function (f) { - var string = f[0] + base58KeysInvalid.forEach(f => { + const string = f[0] + + it('throws on ' + string, () => { + assert.throws(() => { + bitcoin.ECPair.fromWIF(string, allowedNetworks) + }, /(Invalid|Unknown) (checksum|compression flag|network version|WIF length)/) + }) + }) + }) - it('throws on ' + string, function () { - assert.throws(function () { - ECKey.fromWIF(string) - var version = base58check.decode(string).readUInt8(0) + describe('Block.fromHex', () => { + blocksValid.forEach(f => { + it('can parse ' + f.id, () => { + const block = bitcoin.Block.fromHex(f.hex) - assert.notEqual(allowedNetworks.indexOf(version), -1, 'Invalid network') - }, /Invalid (checksum|compression flag|network|WIF payload)/) + assert.strictEqual(block.getId(), f.id) + assert.strictEqual(block.transactions.length, f.transactions) }) }) }) - // tx_valid - describe('Transaction', function () { - tx_valid.forEach(function (f) { + // txValid + describe('Transaction.fromHex', () => { + txValid.forEach(f => { // Objects that are only a single string are ignored if (f.length === 1) return - var inputs = f[0] - var fhex = f[1] - // var verifyFlags = f[2] // TODO: do we need to test this? - - it('can decode ' + fhex, function () { - var transaction = Transaction.fromHex(fhex) + const inputs = f[0] + const fhex = f[1] + // const verifyFlags = f[2] // TODO: do we need to test this? - transaction.ins.forEach(function (txin, i) { - var input = inputs[i] - var prevOutHash = input[0] - var prevOutIndex = input[1] - // var prevOutScriptPubKey = input[2] // TODO: we don't have a ASM parser + it('can decode ' + fhex, () => { + const transaction = bitcoin.Transaction.fromHex(fhex) - var actualHash = txin.hash + transaction.ins.forEach((txIn, i) => { + const input = inputs[i] - // Test data is big-endian - Array.prototype.reverse.call(actualHash) + // reverse because test data is reversed + const prevOutHash = Buffer.from(input[0], 'hex').reverse() + const prevOutIndex = input[1] - assert.equal(actualHash.toString('hex'), prevOutHash) + assert.deepStrictEqual(txIn.hash, prevOutHash) // we read UInt32, not Int32 - assert.equal(txin.index & 0xffffffff, prevOutIndex) + assert.strictEqual(txIn.index & 0xffffffff, prevOutIndex) }) }) }) }) // sighash - describe('Transaction', function () { - sighash.forEach(function (f) { + describe('Transaction', () => { + sigHash.forEach(f => { // Objects that are only a single string are ignored if (f.length === 1) return - var txHex = f[0] - var scriptHex = f[1] - var inIndex = f[2] - var hashType = f[3] - var expectedHash = f[4] - - it('should hash ' + txHex + ' correctly', function () { - var transaction = Transaction.fromHex(txHex) - assert.equal(transaction.toHex(), txHex) - - var script = Script.fromHex(scriptHex) - assert.equal(script.toHex(), scriptHex) - - var actualHash - try { - actualHash = transaction.hashForSignature(inIndex, script, hashType) - } catch (e) { - // don't fail if we don't support it yet, TODO - if (!e.message.match(/not yet supported/)) - throw e - } - - if (actualHash !== undefined) { - // Test data is big-endian - Array.prototype.reverse.call(actualHash) - - assert.equal(actualHash.toString('hex'), expectedHash) - } + const txHex = f[0] + const scriptHex = f[1] + const inIndex = f[2] + const hashType = f[3] + const expectedHash = f[4] + + const hashTypes = [] + if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_NONE) hashTypes.push('SIGHASH_NONE') + else if ((hashType & 0x1f) === bitcoin.Transaction.SIGHASH_SINGLE) hashTypes.push('SIGHASH_SINGLE') + else hashTypes.push('SIGHASH_ALL') + if (hashType & bitcoin.Transaction.SIGHASH_ANYONECANPAY) hashTypes.push('SIGHASH_ANYONECANPAY') + + const hashTypeName = hashTypes.join(' | ') + + it('should hash ' + txHex.slice(0, 40) + '... (' + hashTypeName + ')', () => { + const transaction = bitcoin.Transaction.fromHex(txHex) + assert.strictEqual(transaction.toHex(), txHex) + + const script = Buffer.from(scriptHex, 'hex') + const scriptChunks = bitcoin.script.decompile(script) + assert.strictEqual(bitcoin.script.compile(scriptChunks).toString('hex'), scriptHex) + + const hash = transaction.hashForSignature(inIndex, script, hashType) + + // reverse because test data is reversed + assert.strictEqual(hash.reverse().toString('hex'), expectedHash) }) }) }) - describe('ECSignature', function () { - sig_canonical.forEach(function (hex) { - var buffer = new Buffer(hex, 'hex') + describe('script.signature.decode', () => { + sigCanonical.forEach(hex => { + const buffer = Buffer.from(hex, 'hex') - it('can parse ' + hex, function () { - var parsed = ECSignature.parseScriptSignature(buffer) - var actual = parsed.signature.toScriptSignature(parsed.hashType) - assert.equal(actual.toString('hex'), hex) + it('can parse ' + hex, () => { + const parsed = bitcoin.script.signature.decode(buffer) + const actual = bitcoin.script.signature.encode(parsed.signature, parsed.hashType) + + assert.strictEqual(actual.toString('hex'), hex) }) }) - sig_noncanonical.forEach(function (hex, i) { + sigNoncanonical.forEach((hex, i) => { if (i === 0) return if (i % 2 !== 0) return - var description = sig_noncanonical[i - 1].slice(0, -1) - if (description === 'too long') return // we support non secp256k1 signatures - - var buffer = new Buffer(hex, 'hex') + const description = sigNoncanonical[i - 1].slice(0, -1) + const buffer = Buffer.from(hex, 'hex') - it('throws on ' + description, function () { - assert.throws(function () { - ECSignature.parseScriptSignature(buffer) - }) + it('throws on ' + description, () => { + assert.throws(() => { + 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/) }) }) }) diff --git a/test/block.js b/test/block.js index a91e24877..d5e41af7b 100644 --- a/test/block.js +++ b/test/block.js @@ -1,89 +1,156 @@ -/* global describe, it, beforeEach */ - -var assert = require('assert') - -var Block = require('../src/block') +const { describe, it, beforeEach } = require('mocha') +const assert = require('assert') +const Block = require('..').Block + +const fixtures = require('./fixtures/block') + +describe('Block', () => { + describe('version', () => { + it('should be interpreted as an int32le', () => { + const blockHex = 'ffffffff0000000000000000000000000000000000000000000000000000000000000000414141414141414141414141414141414141414141414141414141414141414101000000020000000300000000' + const block = Block.fromHex(blockHex) + assert.strictEqual(-1, block.version) + assert.strictEqual(1, block.timestamp) + }) + }) -var fixtures = require('./fixtures/block') + describe('calculateTarget', () => { + fixtures.targets.forEach(f => { + it('returns ' + f.expected + ' for 0x' + f.bits, () => { + const bits = parseInt(f.bits, 16) -describe('Block', function () { - describe('fromBuffer/fromHex', function () { - fixtures.valid.forEach(function (f) { - it('imports the block: ' + f.description + ' correctly', function () { - var block = Block.fromHex(f.hex) + assert.strictEqual(Block.calculateTarget(bits).toString('hex'), f.expected) + }) + }) + }) - assert.equal(block.version, f.version) - assert.equal(block.prevHash.toString('hex'), f.prevHash) - assert.equal(block.merkleRoot.toString('hex'), f.merkleRoot) - assert.equal(block.timestamp, f.timestamp) - assert.equal(block.bits, f.bits) - assert.equal(block.nonce, f.nonce) + describe('fromBuffer/fromHex', () => { + fixtures.valid.forEach(f => { + it('imports ' + f.description, () => { + const block = Block.fromHex(f.hex) + + assert.strictEqual(block.version, f.version) + assert.strictEqual(block.prevHash.toString('hex'), f.prevHash) + 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.bits, f.bits) + assert.strictEqual(block.nonce, f.nonce) + assert.strictEqual(!block.transactions, f.hex.length === 160) }) }) - fixtures.invalid.forEach(function (f) { - it('throws on ' + f.exception, function () { - assert.throws(function () { + fixtures.invalid.forEach(f => { + it('throws on ' + f.exception, () => { + assert.throws(() => { Block.fromHex(f.hex) }, new RegExp(f.exception)) }) }) }) - describe('toBuffer/toHex', function () { - fixtures.valid.forEach(function (f) { - var block + describe('toBuffer/toHex', () => { + fixtures.valid.forEach(f => { + let block + + beforeEach(() => { + block = Block.fromHex(f.hex) + }) + + it('exports ' + f.description, () => { + assert.strictEqual(block.toHex(true), f.hex.slice(0, 160)) + assert.strictEqual(block.toHex(), f.hex) + }) + }) + }) + + describe('getHash/getId', () => { + fixtures.valid.forEach(f => { + let block - beforeEach(function () { + beforeEach(() => { block = Block.fromHex(f.hex) }) - it('exports the block: ' + f.description + ' correctly', function () { - assert.equal(block.toHex(), f.hex) + it('returns ' + f.id + ' for ' + f.description, () => { + assert.strictEqual(block.getHash().toString('hex'), f.hash) + assert.strictEqual(block.getId(), f.id) }) }) }) - describe('getHash', function () { - fixtures.valid.forEach(function (f) { - var block + describe('getUTCDate', () => { + fixtures.valid.forEach(f => { + let block - beforeEach(function () { + beforeEach(() => { block = Block.fromHex(f.hex) }) - it('calculates ' + f.hash + ' for the block: ' + f.description, function () { - assert.equal(block.getHash().toString('hex'), f.hash) + it('returns UTC date of ' + f.id, () => { + const utcDate = block.getUTCDate().getTime() + + assert.strictEqual(utcDate, f.timestamp * 1e3) }) }) }) - describe('getId', function () { - fixtures.valid.forEach(function (f) { - var block + describe('calculateMerkleRoot', () => { + it('should throw on zero-length transaction array', () => { + assert.throws(() => { + Block.calculateMerkleRoot([]) + }, /Cannot compute merkle root for zero transactions/) + }) + + fixtures.valid.forEach(f => { + if (f.hex.length === 160) return - beforeEach(function () { + let block + + beforeEach(() => { block = Block.fromHex(f.hex) }) - it('calculates ' + f.id + ' for the block: ' + f.description, function () { - assert.equal(block.getId(), f.id) + it('returns ' + f.merkleRoot + ' for ' + f.id, () => { + 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('getUTCDate', function () { - fixtures.valid.forEach(function (f) { - var block + describe('checkTxRoots', () => { + fixtures.valid.forEach(f => { + if (f.hex.length === 160) return + + let block - beforeEach(function () { + beforeEach(() => { block = Block.fromHex(f.hex) }) - it('returns UTC date of ' + f.id, function () { - var utcDate = block.getUTCDate().getTime() + it('returns ' + f.valid + ' for ' + f.id, () => { + assert.strictEqual(block.checkTxRoots(), true) + }) + }) + }) + + describe('checkProofOfWork', () => { + fixtures.valid.forEach(f => { + let block + + beforeEach(() => { + block = Block.fromHex(f.hex) + }) - assert.equal(utcDate, f.timestamp * 1e3) + it('returns ' + f.valid + ' for ' + f.id, () => { + assert.strictEqual(block.checkProofOfWork(), f.valid) }) }) }) diff --git a/test/bufferutils.js b/test/bufferutils.js index 485447b2e..1a3840616 100644 --- a/test/bufferutils.js +++ b/test/bufferutils.js @@ -1,188 +1,49 @@ -/* global describe, it */ +const { describe, it } = require('mocha') +const assert = require('assert') +const bufferutils = require('../src/bufferutils') -var assert = require('assert') -var bufferutils = require('../src/bufferutils') +const fixtures = require('./fixtures/bufferutils.json') -var fixtures = require('./fixtures/bufferutils.json') +describe('bufferutils', () => { + describe('readUInt64LE', () => { + fixtures.valid.forEach(f => { + it('decodes ' + f.hex, () => { + const buffer = Buffer.from(f.hex, 'hex') + const number = bufferutils.readUInt64LE(buffer, 0) -describe('bufferutils', function () { - describe('pushDataSize', function () { - fixtures.valid.forEach(function (f) { - it('determines the pushDataSize of ' + f.dec + ' correctly', function () { - if (!f.hexPD) return - - var size = bufferutils.pushDataSize(f.dec) - - assert.equal(size, f.hexPD.length / 2) + assert.strictEqual(number, f.dec) }) }) - }) - - describe('readPushDataInt', function () { - fixtures.valid.forEach(function (f) { - if (!f.hexPD) return - it('decodes ' + f.hexPD + ' correctly', function () { - var buffer = new Buffer(f.hexPD, 'hex') - var d = bufferutils.readPushDataInt(buffer, 0) - var fopcode = parseInt(f.hexPD.substr(0, 2), 16) - - assert.equal(d.opcode, fopcode) - assert.equal(d.number, f.dec) - assert.equal(d.size, buffer.length) - }) - }) - }) + fixtures.invalid.readUInt64LE.forEach(f => { + it('throws on ' + f.description, () => { + const buffer = Buffer.from(f.hex, 'hex') - describe('readUInt64LE', function () { - fixtures.valid.forEach(function (f) { - it('decodes ' + f.hex64 + ' correctly', function () { - var buffer = new Buffer(f.hex64, 'hex') - var number = bufferutils.readUInt64LE(buffer, 0) - - assert.equal(number, f.dec) - }) - }) - - fixtures.invalid.forEach(function (f) { - it('throws on ' + f.description, function () { - var buffer = new Buffer(f.hex64, 'hex') - - assert.throws(function () { + assert.throws(() => { bufferutils.readUInt64LE(buffer, 0) }, new RegExp(f.exception)) }) }) }) - describe('readVarInt', function () { - fixtures.valid.forEach(function (f) { - it('decodes ' + f.hexVI + ' correctly', function () { - var buffer = new Buffer(f.hexVI, 'hex') - var d = bufferutils.readVarInt(buffer, 0) - - assert.equal(d.number, f.dec) - assert.equal(d.size, buffer.length) - }) - }) - - fixtures.invalid.forEach(function (f) { - it('throws on ' + f.description, function () { - var buffer = new Buffer(f.hexVI, 'hex') - - assert.throws(function () { - bufferutils.readVarInt(buffer, 0) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('equal', function () { - fixtures.valid.forEach(function (f) { - describe('for ' + f.hexVI, function () { - fixtures.valid.forEach(function (f2) { - it('equates the string comparison: ' + f.hexVI + ' === ' + f2.hexVI, function () { - var a = new Buffer(f.hexVI, 'hex') - var b = new Buffer(f2.hexVI, 'hex') - var expected = f.hexVI === f2.hexVI - - assert.equal(bufferutils.equal(a, b), expected) - }) - }) - }) - }) - }) - - describe('reverse', function () { - fixtures.valid.forEach(function (f) { - it('reverses ' + f.hex64 + ' correctly', function () { - var buffer = new Buffer(f.hex64, 'hex') - var buffer2 = bufferutils.reverse(buffer) - - Array.prototype.reverse.call(buffer) - - assert.deepEqual(buffer, buffer2) - }) - }) - }) - - describe('varIntBuffer', function () { - fixtures.valid.forEach(function (f) { - it('encodes ' + f.dec + ' correctly', function () { - var buffer = bufferutils.varIntBuffer(f.dec) - - assert.equal(buffer.toString('hex'), f.hexVI) - }) - }) - }) - - describe('varIntSize', function () { - fixtures.valid.forEach(function (f) { - it('determines the varIntSize of ' + f.dec + ' correctly', function () { - var size = bufferutils.varIntSize(f.dec) - - assert.equal(size, f.hexVI.length / 2) - }) - }) - }) - - describe('writePushDataInt', function () { - fixtures.valid.forEach(function (f) { - if (!f.hexPD) return - - it('encodes ' + f.dec + ' correctly', function () { - var buffer = new Buffer(5) - buffer.fill(0) - - var n = bufferutils.writePushDataInt(buffer, f.dec, 0) - assert.equal(buffer.slice(0, n).toString('hex'), f.hexPD) - }) - }) - }) - - describe('writeUInt64LE', function () { - fixtures.valid.forEach(function (f) { - it('encodes ' + f.dec + ' correctly', function () { - var buffer = new Buffer(8) - buffer.fill(0) + describe('writeUInt64LE', () => { + fixtures.valid.forEach(f => { + it('encodes ' + f.dec, () => { + const buffer = Buffer.alloc(8, 0) bufferutils.writeUInt64LE(buffer, f.dec, 0) - assert.equal(buffer.toString('hex'), f.hex64) + assert.strictEqual(buffer.toString('hex'), f.hex) }) }) - fixtures.invalid.forEach(function (f) { - it('throws on ' + f.description, function () { - var buffer = new Buffer(8) - buffer.fill(0) + fixtures.invalid.readUInt64LE.forEach(f => { + it('throws on ' + f.description, () => { + const buffer = Buffer.alloc(8, 0) - assert.throws(function () { + assert.throws(() => { bufferutils.writeUInt64LE(buffer, f.dec, 0) }, new RegExp(f.exception)) }) }) }) - - describe('writeVarInt', function () { - fixtures.valid.forEach(function (f) { - it('encodes ' + f.dec + ' correctly', function () { - var buffer = new Buffer(9) - buffer.fill(0) - - var n = bufferutils.writeVarInt(buffer, f.dec, 0) - assert.equal(buffer.slice(0, n).toString('hex'), f.hexVI) - }) - }) - - fixtures.invalid.forEach(function (f) { - it('throws on ' + f.description, function () { - var buffer = new Buffer(9) - buffer.fill(0) - - assert.throws(function () { - bufferutils.writeVarInt(buffer, f.dec, 0) - }, new RegExp(f.exception)) - }) - }) - }) }) diff --git a/test/classify.js b/test/classify.js new file mode 100644 index 000000000..86da74d6a --- /dev/null +++ b/test/classify.js @@ -0,0 +1,156 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const bscript = require('../src/script') +const classify = require('../src/classify') + +const fixtures = require('./fixtures/templates.json') + +const multisig = require('../src/templates/multisig') +const nullData = require('../src/templates/nulldata') +const pubKey = require('../src/templates/pubkey') +const pubKeyHash = require('../src/templates/pubkeyhash') +const scriptHash = require('../src/templates/scripthash') +const witnessPubKeyHash = require('../src/templates/witnesspubkeyhash') +const witnessScriptHash = require('../src/templates/witnessscripthash') +const witnessCommitment = require('../src/templates/witnesscommitment') + +const tmap = { + pubKey, + pubKeyHash, + scriptHash, + witnessPubKeyHash, + witnessScriptHash, + multisig, + nullData, + witnessCommitment +} + +describe('classify', () => { + describe('input', () => { + fixtures.valid.forEach(f => { + if (!f.input) return + + it('classifies ' + f.input + ' as ' + f.type, () => { + const input = bscript.fromASM(f.input) + const type = classify.input(input) + + assert.strictEqual(type, f.type) + }) + }) + + fixtures.valid.forEach(f => { + if (!f.input) return + if (!f.typeIncomplete) return + + it('classifies incomplete ' + f.input + ' as ' + f.typeIncomplete, () => { + const input = bscript.fromASM(f.input) + const type = classify.input(input, true) + + assert.strictEqual(type, f.typeIncomplete) + }) + }) + }) + + describe('classifyOutput', () => { + fixtures.valid.forEach(f => { + if (!f.output) return + + it('classifies ' + f.output + ' as ' + f.type, () => { + const output = bscript.fromASM(f.output) + const type = classify.output(output) + + assert.strictEqual(type, f.type) + }) + }) + }) + + ;[ + 'pubKey', + 'pubKeyHash', + 'scriptHash', + 'witnessPubKeyHash', + 'witnessScriptHash', + 'multisig', + 'nullData', + 'witnessCommitment' + ].forEach(name => { + const inputType = tmap[name].input + const outputType = tmap[name].output + + describe(name + '.input.check', () => { + fixtures.valid.forEach(f => { + if (name.toLowerCase() === classify.types.P2WPKH) return + if (name.toLowerCase() === classify.types.P2WSH) return + const expected = name.toLowerCase() === f.type.toLowerCase() + + if (inputType && f.input) { + const input = bscript.fromASM(f.input) + + it('returns ' + expected + ' for ' + f.input, () => { + assert.strictEqual(inputType.check(input), expected) + }) + + if (f.typeIncomplete) { + const expectedIncomplete = name.toLowerCase() === f.typeIncomplete + + it('returns ' + expected + ' for ' + f.input, () => { + assert.strictEqual(inputType.check(input, true), expectedIncomplete) + }) + } + } + }) + + if (!(fixtures.invalid[name])) return + + fixtures.invalid[name].inputs.forEach(f => { + if (!f.input && !f.inputHex) return + + it('returns false for ' + f.description + ' (' + (f.input || f.inputHex) + ')', () => { + let input + + if (f.input) { + input = bscript.fromASM(f.input) + } else { + input = Buffer.from(f.inputHex, 'hex') + } + + assert.strictEqual(inputType.check(input), false) + }) + }) + }) + + describe(name + '.output.check', () => { + fixtures.valid.forEach(f => { + const expected = name.toLowerCase() === f.type + + if (outputType && f.output) { + it('returns ' + expected + ' for ' + f.output, () => { + const output = bscript.fromASM(f.output) + + if (name.toLowerCase() === 'nulldata' && f.type === classify.types.WITNESS_COMMITMENT) return + if (name.toLowerCase() === 'witnesscommitment' && f.type === classify.types.NULLDATA) return + assert.strictEqual(outputType.check(output), expected) + }) + } + }) + + if (!(fixtures.invalid[name])) return + + fixtures.invalid[name].outputs.forEach(f => { + if (!f.output && !f.outputHex) return + + it('returns false for ' + f.description + ' (' + (f.output || f.outputHex) + ')', () => { + let output + + if (f.output) { + output = bscript.fromASM(f.output) + } else { + output = Buffer.from(f.outputHex, 'hex') + } + + assert.strictEqual(outputType.check(output), false) + }) + }) + }) + }) +}) diff --git a/test/crypto.js b/test/crypto.js index 91827ca3a..14b4f3357 100644 --- a/test/crypto.js +++ b/test/crypto.js @@ -1,22 +1,21 @@ -/* global describe, it */ +const { describe, it } = require('mocha') +const assert = require('assert') +const bcrypto = require('../src/crypto') -var assert = require('assert') -var crypto = require('../src/crypto') +const fixtures = require('./fixtures/crypto') -var fixtures = require('./fixtures/crypto') +describe('crypto', () => { + ['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(algorithm => { + describe(algorithm, () => { + fixtures.forEach(f => { + const fn = bcrypto[algorithm] + const expected = f[algorithm] -describe('Crypto', function () { - ['hash160', 'hash256', 'ripemd160', 'sha1', 'sha256'].forEach(function (algorithm) { - describe(algorithm, function () { - fixtures.valid.forEach(function (f) { - var fn = crypto[algorithm] - var expected = f[algorithm] + it('returns ' + expected + ' for ' + f.hex, () => { + const data = Buffer.from(f.hex, 'hex') + const actual = fn(data).toString('hex') - it('returns ' + expected + ' for ' + f.hex, function () { - var data = new Buffer(f.hex, 'hex') - var actual = fn(data).toString('hex') - - assert.equal(actual, expected) + assert.strictEqual(actual, expected) }) }) }) diff --git a/test/ecdsa.js b/test/ecdsa.js deleted file mode 100644 index a5ff8a5a4..000000000 --- a/test/ecdsa.js +++ /dev/null @@ -1,188 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') -var crypto = require('../src/crypto') -var ecdsa = require('../src/ecdsa') -var message = require('../src/message') -var networks = require('../src/networks') -var sinon = require('sinon') - -var BigInteger = require('bigi') -var ECSignature = require('../src/ecsignature') - -var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') - -var fixtures = require('./fixtures/ecdsa.json') - -describe('ecdsa', function () { - describe('deterministicGenerateK', function () { - function checkSig () { - return true - } - - fixtures.valid.ecdsa.forEach(function (f) { - it('for "' + f.message + '"', function () { - var d = BigInteger.fromHex(f.d) - var h1 = crypto.sha256(f.message) - - var k = ecdsa.deterministicGenerateK(curve, h1, d, checkSig) - assert.equal(k.toHex(), f.k) - }) - }) - - it('loops until an appropriate k value is found', sinon.test(function () { - this.mock(BigInteger).expects('fromBuffer') - .exactly(3) - .onCall(0).returns(new BigInteger('0')) // < 1 - .onCall(1).returns(curve.n) // > n-1 - .onCall(2).returns(new BigInteger('42')) // valid - - var d = new BigInteger('1') - var h1 = new Buffer(32) - var k = ecdsa.deterministicGenerateK(curve, h1, d, checkSig) - - assert.equal(k.toString(), '42') - })) - - it('loops until a suitable signature is found', sinon.test(function () { - this.mock(BigInteger).expects('fromBuffer') - .exactly(4) - .onCall(0).returns(new BigInteger('0')) // < 1 - .onCall(1).returns(curve.n) // > n-1 - .onCall(2).returns(new BigInteger('42')) // valid, but 'bad' signature - .onCall(3).returns(new BigInteger('53')) // valid, good signature - - var checkSig = this.mock() - checkSig.exactly(2) - checkSig.onCall(0).returns(false) // bad signature - checkSig.onCall(1).returns(true) // good signature - - var d = new BigInteger('1') - var h1 = new Buffer(32) - var k = ecdsa.deterministicGenerateK(curve, h1, d, checkSig) - - assert.equal(k.toString(), '53') - })) - - fixtures.valid.rfc6979.forEach(function (f) { - it('produces the expected k values for ' + f.message + " if k wasn't suitable", function () { - var d = BigInteger.fromHex(f.d) - var h1 = crypto.sha256(f.message) - - var results = [] - ecdsa.deterministicGenerateK(curve, h1, d, function (k) { - results.push(k) - - return results.length === 16 - }) - - assert.equal(results[0].toHex(), f.k0) - assert.equal(results[1].toHex(), f.k1) - assert.equal(results[15].toHex(), f.k15) - }) - }) - }) - - describe('recoverPubKey', function () { - fixtures.valid.ecdsa.forEach(function (f) { - it('recovers the pubKey for ' + f.d, function () { - var d = BigInteger.fromHex(f.d) - var Q = curve.G.multiply(d) - var signature = { - r: new BigInteger(f.signature.r), - s: new BigInteger(f.signature.s) - } - var h1 = crypto.sha256(f.message) - var e = BigInteger.fromBuffer(h1) - var Qprime = ecdsa.recoverPubKey(curve, e, signature, f.i) - - assert(Qprime.equals(Q)) - }) - }) - - describe('with i ∈ {0,1,2,3}', function () { - var hash = message.magicHash('1111', networks.bitcoin) - var e = BigInteger.fromBuffer(hash) - - var signatureBuffer = new Buffer('INcvXVVEFyIfHLbDX+xoxlKFn3Wzj9g0UbhObXdMq+YMKC252o5RHFr0/cKdQe1WsBLUBi4morhgZ77obDJVuV0=', 'base64') - var signature = ECSignature.parseCompact(signatureBuffer).signature - var points = [ - '03e3a8c44a8bf712f1fbacee274fb19c0239b1a9e877eff0075ea335f2be8ff380', - '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - '03d49e765f0bc27525c51a1b98fb1c99dacd59abe85a203af90f758260550b56c5', - '027eea09d46ac7fb6aa2e96f9c576677214ffdc238eb167734a9b39d1eb4c3d30d' - ] - - points.forEach(function (expectedHex, i) { - it('recovers an expected point for i of ' + i, function () { - var Qprime = ecdsa.recoverPubKey(curve, e, signature, i) - var QprimeHex = Qprime.getEncoded().toString('hex') - - assert.equal(QprimeHex, expectedHex) - }) - }) - }) - - fixtures.invalid.recoverPubKey.forEach(function (f) { - it('throws on ' + f.description, function () { - var e = BigInteger.fromHex(f.e) - var signature = new ECSignature(new BigInteger(f.signature.r), new BigInteger(f.signature.s)) - - assert.throws(function () { - ecdsa.recoverPubKey(curve, e, signature, f.i) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('sign', function () { - fixtures.valid.ecdsa.forEach(function (f) { - it('produces a deterministic signature for "' + f.message + '"', function () { - var d = BigInteger.fromHex(f.d) - var hash = crypto.sha256(f.message) - var signature = ecdsa.sign(curve, hash, d) - - assert.equal(signature.r.toString(), f.signature.r) - assert.equal(signature.s.toString(), f.signature.s) - }) - }) - - it('should sign with low S value', function () { - var hash = crypto.sha256('Vires in numeris') - var sig = ecdsa.sign(curve, hash, BigInteger.ONE) - - // See BIP62 for more information - var N_OVER_TWO = curve.n.shiftRight(1) - assert(sig.s.compareTo(N_OVER_TWO) <= 0) - }) - }) - - describe('verify/verifyRaw', function () { - fixtures.valid.ecdsa.forEach(function (f) { - it('verifies a valid signature for "' + f.message + '"', function () { - var d = BigInteger.fromHex(f.d) - var H = crypto.sha256(f.message) - var e = BigInteger.fromBuffer(H) - var signature = new ECSignature(new BigInteger(f.signature.r), new BigInteger(f.signature.s)) - var Q = curve.G.multiply(d) - - assert(ecdsa.verify(curve, H, signature, Q)) - assert(ecdsa.verifyRaw(curve, e, signature, Q)) - }) - }) - - fixtures.invalid.verifyRaw.forEach(function (f) { - it('fails to verify with ' + f.description, function () { - var H = crypto.sha256(f.message) - var e = BigInteger.fromBuffer(H) - var d = BigInteger.fromHex(f.d) - var signature = new ECSignature(new BigInteger(f.signature.r), new BigInteger(f.signature.s)) - var Q = curve.G.multiply(d) - - assert.equal(ecdsa.verify(curve, H, signature, Q), false) - assert.equal(ecdsa.verifyRaw(curve, e, signature, Q), false) - }) - }) - }) -}) diff --git a/test/eckey.js b/test/eckey.js deleted file mode 100644 index 16ce3d0cb..000000000 --- a/test/eckey.js +++ /dev/null @@ -1,150 +0,0 @@ -/* global describe, it, beforeEach, afterEach */ -/* eslint-disable no-new */ - -var assert = require('assert') -var crypto = require('crypto') -var ecurve = require('ecurve') -var networks = require('../src/networks') -var sinon = require('sinon') - -var BigInteger = require('bigi') -var ECKey = require('../src/eckey') - -var fixtures = require('./fixtures/eckey.json') - -describe('ECKey', function () { - describe('constructor', function () { - it('defaults to compressed', function () { - var privKey = new ECKey(BigInteger.ONE) - - assert.equal(privKey.pub.compressed, true) - }) - - it('supports the uncompressed flag', function () { - var privKey = new ECKey(BigInteger.ONE, false) - - assert.equal(privKey.pub.compressed, false) - }) - - fixtures.valid.forEach(function (f) { - it('calculates the matching pubKey for ' + f.d, function () { - var d = new BigInteger(f.d) - var privKey = new ECKey(d) - - assert.equal(privKey.pub.Q.toString(), f.Q) - }) - }) - - fixtures.invalid.constructor.forEach(function (f) { - it('throws on ' + f.d, function () { - var d = new BigInteger(f.d) - - assert.throws(function () { - new ECKey(d) - }, new RegExp(f.exception)) - }) - }) - }) - - it('uses the secp256k1 curve by default', function () { - var secp256k1 = ecurve.getCurveByName('secp256k1') - - for (var property in secp256k1) { - // FIXME: circular structures in ecurve - if (property === 'G') continue - if (property === 'infinity') continue - - var actual = ECKey.curve[property] - var expected = secp256k1[property] - - assert.deepEqual(actual, expected) - } - }) - - describe('fromWIF', function () { - fixtures.valid.forEach(function (f) { - f.WIFs.forEach(function (wif) { - it('imports ' + wif.string + ' correctly', function () { - var privKey = ECKey.fromWIF(wif.string) - - assert.equal(privKey.d.toString(), f.d) - assert.equal(privKey.pub.compressed, wif.compressed) - }) - }) - }) - - fixtures.invalid.WIF.forEach(function (f) { - it('throws on ' + f.string, function () { - assert.throws(function () { - ECKey.fromWIF(f.string) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('toWIF', function () { - fixtures.valid.forEach(function (f) { - f.WIFs.forEach(function (wif) { - it('exports ' + wif.string + ' correctly', function () { - var privKey = ECKey.fromWIF(wif.string) - var network = networks[wif.network] - var result = privKey.toWIF(network) - - assert.equal(result, wif.string) - }) - }) - }) - }) - - describe('makeRandom', function () { - var exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv' - var exPrivKey = ECKey.fromWIF(exWIF) - var exBuffer = exPrivKey.d.toBuffer(32) - - describe('uses default crypto RNG', function () { - beforeEach(function () { - sinon.stub(crypto, 'randomBytes').returns(exBuffer) - }) - - afterEach(function () { - crypto.randomBytes.restore() - }) - - it('generates a ECKey', function () { - var privKey = ECKey.makeRandom() - - assert.equal(privKey.toWIF(), exWIF) - }) - - it('supports compression', function () { - assert.equal(ECKey.makeRandom(true).pub.compressed, true) - assert.equal(ECKey.makeRandom(false).pub.compressed, false) - }) - }) - - it('allows a custom RNG to be used', function () { - function rng (size) { - return exBuffer.slice(0, size) - } - - var privKey = ECKey.makeRandom(undefined, rng) - assert.equal(privKey.toWIF(), exWIF) - }) - }) - - describe('signing', function () { - var hash = crypto.randomBytes(32) - var priv = ECKey.makeRandom() - var signature = priv.sign(hash) - - it('should verify against the public key', function () { - assert(priv.pub.verify(hash, signature)) - }) - - it('should not verify against the wrong public key', function () { - var priv2 = ECKey.makeRandom() - - assert(!priv2.pub.verify(hash, signature)) - }) - }) -}) diff --git a/test/ecpair.js b/test/ecpair.js new file mode 100644 index 000000000..e067dddbc --- /dev/null +++ b/test/ecpair.js @@ -0,0 +1,284 @@ +const { describe, it, beforeEach } = require('mocha') +const assert = require('assert') +const proxyquire = require('proxyquire') +const hoodwink = require('hoodwink') + +const ECPair = require('../src/ecpair') +const tinysecp = require('tiny-secp256k1') + +const fixtures = require('./fixtures/ecpair.json') + +const NETWORKS = require('../src/networks') +const NETWORKS_LIST = [] // Object.values(NETWORKS) +for (let networkName in NETWORKS) { + NETWORKS_LIST.push(NETWORKS[networkName]) +} + +const ZERO = Buffer.alloc(32, 0) +const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex') +const GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex') +const GROUP_ORDER_LESS_1 = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', 'hex') + +describe('ECPair', () => { + describe('getPublicKey', () => { + let keyPair + + beforeEach(() => { + keyPair = ECPair.fromPrivateKey(ONE) + }) + + it('calls pointFromScalar lazily', hoodwink(() => { + assert.strictEqual(keyPair.__Q, undefined) + + // .publicKey forces the memoization + assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') + assert.strictEqual(keyPair.__Q.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') + })) + }) + + describe('fromPrivateKey', () => { + it('defaults to compressed', () => { + const keyPair = ECPair.fromPrivateKey(ONE) + + assert.strictEqual(keyPair.compressed, true) + }) + + it('supports the uncompressed option', () => { + const keyPair = ECPair.fromPrivateKey(ONE, { + compressed: false + }) + + assert.strictEqual(keyPair.compressed, false) + }) + + it('supports the network option', () => { + const keyPair = ECPair.fromPrivateKey(ONE, { + compressed: false, + network: NETWORKS.testnet + }) + + assert.strictEqual(keyPair.network, NETWORKS.testnet) + }) + + fixtures.valid.forEach(f => { + it('derives public key for ' + f.WIF, () => { + const d = Buffer.from(f.d, 'hex') + const keyPair = ECPair.fromPrivateKey(d, { + compressed: f.compressed + }) + + assert.strictEqual(keyPair.publicKey.toString('hex'), f.Q) + }) + }) + + fixtures.invalid.fromPrivateKey.forEach(f => { + it('throws ' + f.exception, () => { + const d = Buffer.from(f.d, 'hex') + assert.throws(() => { + ECPair.fromPrivateKey(d, f.options) + }, new RegExp(f.exception)) + }) + }) + }) + + describe('fromPublicKey', () => { + fixtures.invalid.fromPublicKey.forEach(f => { + it('throws ' + f.exception, () => { + const Q = Buffer.from(f.Q, 'hex') + assert.throws(() => { + ECPair.fromPublicKey(Q, f.options) + }, new RegExp(f.exception)) + }) + }) + }) + + describe('fromWIF', () => { + fixtures.valid.forEach(f => { + it('imports ' + f.WIF + ' (' + f.network + ')', () => { + const network = NETWORKS[f.network] + const keyPair = ECPair.fromWIF(f.WIF, network) + + assert.strictEqual(keyPair.privateKey.toString('hex'), f.d) + assert.strictEqual(keyPair.compressed, f.compressed) + assert.strictEqual(keyPair.network, network) + }) + }) + + fixtures.valid.forEach(f => { + it('imports ' + f.WIF + ' (via list of networks)', () => { + const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST) + + assert.strictEqual(keyPair.privateKey.toString('hex'), f.d) + assert.strictEqual(keyPair.compressed, f.compressed) + assert.strictEqual(keyPair.network, NETWORKS[f.network]) + }) + }) + + fixtures.invalid.fromWIF.forEach(f => { + it('throws on ' + f.WIF, () => { + assert.throws(() => { + const networks = f.network ? NETWORKS[f.network] : NETWORKS_LIST + + ECPair.fromWIF(f.WIF, networks) + }, new RegExp(f.exception)) + }) + }) + }) + + describe('toWIF', () => { + fixtures.valid.forEach(f => { + it('exports ' + f.WIF, () => { + const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST) + const result = keyPair.toWIF() + assert.strictEqual(result, f.WIF) + }) + }) + }) + + describe('makeRandom', () => { + const d = Buffer.alloc(32, 4) + const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv' + + describe('uses randombytes RNG', () => { + it('generates a ECPair', () => { + const stub = { randombytes: () => { return d } } + const ProxiedECPair = proxyquire('../src/ecpair', stub) + + const keyPair = ProxiedECPair.makeRandom() + assert.strictEqual(keyPair.toWIF(), exWIF) + }) + }) + + it('allows a custom RNG to be used', () => { + const keyPair = ECPair.makeRandom({ + rng: size => { return d.slice(0, size) } + }) + + assert.strictEqual(keyPair.toWIF(), exWIF) + }) + + it('retains the same defaults as ECPair constructor', () => { + const keyPair = ECPair.makeRandom() + + assert.strictEqual(keyPair.compressed, true) + assert.strictEqual(keyPair.network, NETWORKS.bitcoin) + }) + + it('supports the options parameter', () => { + const keyPair = ECPair.makeRandom({ + compressed: false, + network: NETWORKS.testnet + }) + + assert.strictEqual(keyPair.compressed, false) + assert.strictEqual(keyPair.network, NETWORKS.testnet) + }) + + it('throws if d is bad length', () => { + function rng () { + return Buffer.alloc(28) + } + + assert.throws(() => { + ECPair.makeRandom({ rng: rng }) + }, /Expected Buffer\(Length: 32\), got Buffer\(Length: 28\)/) + }) + + it('loops until d is within interval [1, n) : 1', hoodwink(function () { + const rng = this.stub(() => { + if (rng.calls === 0) return ZERO // 0 + return ONE // >0 + }, 2) + + ECPair.makeRandom({ rng: rng }) + })) + + it('loops until d is within interval [1, n) : n - 1', hoodwink(function () { + const rng = this.stub(() => { + if (rng.calls === 0) return ZERO // <1 + if (rng.calls === 1) return GROUP_ORDER // >n-1 + return GROUP_ORDER_LESS_1 // n-1 + }, 3) + + ECPair.makeRandom({ rng: rng }) + })) + }) + + describe('.network', () => { + fixtures.valid.forEach(f => { + it('returns ' + f.network + ' for ' + f.WIF, () => { + const network = NETWORKS[f.network] + const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST) + + assert.strictEqual(keyPair.network, network) + }) + }) + }) + + describe('tinysecp wrappers', () => { + let keyPair + let hash + let signature + + beforeEach(() => { + keyPair = ECPair.makeRandom() + hash = ZERO + signature = Buffer.alloc(64, 1) + }) + + describe('signing', () => { + it('wraps tinysecp.sign', hoodwink(function () { + this.mock(tinysecp, 'sign', (h, d) => { + assert.strictEqual(h, hash) + assert.strictEqual(d, keyPair.privateKey) + return signature + }, 1) + + assert.strictEqual(keyPair.sign(hash), signature) + })) + + it('throws if no private key is found', () => { + delete keyPair.__D + + assert.throws(() => { + keyPair.sign(hash) + }, /Missing private key/) + }) + }) + + describe('verify', () => { + it('wraps tinysecp.verify', hoodwink(function () { + this.mock(tinysecp, 'verify', (h, q, s) => { + assert.strictEqual(h, hash) + assert.strictEqual(q, keyPair.publicKey) + assert.strictEqual(s, signature) + return true + }, 1) + + assert.strictEqual(keyPair.verify(hash, signature), true) + })) + }) + }) + 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) + }) + }) +}) diff --git a/test/ecpubkey.js b/test/ecpubkey.js deleted file mode 100644 index 76943ed3e..000000000 --- a/test/ecpubkey.js +++ /dev/null @@ -1,120 +0,0 @@ -/* global describe, it, beforeEach */ - -var assert = require('assert') -var crypto = require('../src/crypto') -var networks = require('../src/networks') - -var BigInteger = require('bigi') -var ECPubKey = require('../src/ecpubkey') - -var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') - -var fixtures = require('./fixtures/ecpubkey.json') - -describe('ECPubKey', function () { - var Q - - beforeEach(function () { - Q = ecurve.Point.fromAffine( - curve, - new BigInteger(fixtures.Q.x), - new BigInteger(fixtures.Q.y) - ) - }) - - describe('constructor', function () { - it('defaults to compressed', function () { - var pubKey = new ECPubKey(Q) - - assert.equal(pubKey.compressed, true) - }) - - it('supports the uncompressed flag', function () { - var pubKey = new ECPubKey(Q, false) - - assert.equal(pubKey.compressed, false) - }) - }) - - it('uses the secp256k1 curve by default', function () { - var secp256k1 = ecurve.getCurveByName('secp256k1') - - for (var property in secp256k1) { - // FIXME: circular structures in ecurve - if (property === 'G') continue - if (property === 'infinity') continue - - var actual = ECPubKey.curve[property] - var expected = secp256k1[property] - - assert.deepEqual(actual, expected) - } - }) - - describe('fromHex/toHex', function () { - it('supports compressed points', function () { - var pubKey = ECPubKey.fromHex(fixtures.compressed.hex) - - assert(pubKey.Q.equals(Q)) - assert.equal(pubKey.toHex(), fixtures.compressed.hex) - assert.equal(pubKey.compressed, true) - }) - - it('supports uncompressed points', function () { - var pubKey = ECPubKey.fromHex(fixtures.uncompressed.hex) - - assert(pubKey.Q.equals(Q)) - assert.equal(pubKey.toHex(), fixtures.uncompressed.hex) - assert.equal(pubKey.compressed, false) - }) - }) - - describe('getAddress', function () { - it('calculates the expected hash (compressed)', function () { - var pubKey = new ECPubKey(Q, true) - var address = pubKey.getAddress() - - assert.equal(address.hash.toString('hex'), fixtures.compressed.hash160) - }) - - it('calculates the expected hash (uncompressed)', function () { - var pubKey = new ECPubKey(Q, false) - var address = pubKey.getAddress() - - assert.equal(address.hash.toString('hex'), fixtures.uncompressed.hash160) - }) - - it('supports alternative networks', function () { - var pubKey = new ECPubKey(Q) - var address = pubKey.getAddress(networks.testnet) - - assert.equal(address.version, networks.testnet.pubKeyHash) - assert.equal(address.hash.toString('hex'), fixtures.compressed.hash160) - }) - }) - - describe('verify', function () { - var pubKey, signature - beforeEach(function () { - pubKey = new ECPubKey(Q) - - signature = { - r: new BigInteger(fixtures.signature.r), - s: new BigInteger(fixtures.signature.s) - } - }) - - it('verifies a valid signature', function () { - var hash = crypto.sha256(fixtures.message) - - assert(pubKey.verify(hash, signature)) - }) - - it("doesn't verify the wrong signature", function () { - var hash = crypto.sha256('mushrooms') - - assert(!pubKey.verify(hash, signature)) - }) - }) -}) diff --git a/test/ecsignature.js b/test/ecsignature.js deleted file mode 100644 index 908876f4b..000000000 --- a/test/ecsignature.js +++ /dev/null @@ -1,122 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') - -var BigInteger = require('bigi') -var ECSignature = require('../src/ecsignature') - -var fixtures = require('./fixtures/ecsignature.json') - -describe('ECSignature', function () { - describe('toCompact', function () { - fixtures.valid.forEach(function (f) { - it('exports ' + f.compact.hex + ' correctly', function () { - var signature = new ECSignature(new BigInteger(f.signature.r), new BigInteger(f.signature.s)) - - var buffer = signature.toCompact(f.compact.i, f.compact.compressed) - assert.equal(buffer.toString('hex'), f.compact.hex) - }) - }) - }) - - describe('parseCompact', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.compact.hex + ' correctly', function () { - var buffer = new Buffer(f.compact.hex, 'hex') - var parsed = ECSignature.parseCompact(buffer) - - assert.equal(parsed.compressed, f.compact.compressed) - assert.equal(parsed.i, f.compact.i) - assert.equal(parsed.signature.r.toString(), f.signature.r) - assert.equal(parsed.signature.s.toString(), f.signature.s) - }) - }) - - fixtures.invalid.compact.forEach(function (f) { - it('throws on ' + f.hex, function () { - var buffer = new Buffer(f.hex, 'hex') - - assert.throws(function () { - ECSignature.parseCompact(buffer) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('toDER', function () { - fixtures.valid.forEach(function (f) { - it('exports ' + f.DER + ' correctly', function () { - var signature = new ECSignature(new BigInteger(f.signature.r), new BigInteger(f.signature.s)) - - var DER = signature.toDER() - assert.equal(DER.toString('hex'), f.DER) - }) - }) - }) - - describe('fromDER', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.DER + ' correctly', function () { - var buffer = new Buffer(f.DER, 'hex') - var signature = ECSignature.fromDER(buffer) - - assert.equal(signature.r.toString(), f.signature.r) - assert.equal(signature.s.toString(), f.signature.s) - }) - }) - - fixtures.invalid.DER.forEach(function (f) { - it('throws on ' + f.hex, function () { - var buffer = new Buffer(f.hex, 'hex') - - assert.throws(function () { - ECSignature.fromDER(buffer) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('toScriptSignature', function () { - fixtures.valid.forEach(function (f) { - it('exports ' + f.scriptSignature.hex + ' correctly', function () { - var signature = new ECSignature(new BigInteger(f.signature.r), new BigInteger(f.signature.s)) - - var scriptSignature = signature.toScriptSignature(f.scriptSignature.hashType) - assert.equal(scriptSignature.toString('hex'), f.scriptSignature.hex) - }) - }) - - fixtures.invalid.scriptSignature.forEach(function (f) { - it('throws ' + f.exception, function () { - var signature = new ECSignature(new BigInteger(f.signature.r), new BigInteger(f.signature.s)) - - assert.throws(function () { - signature.toScriptSignature(f.hashType) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('parseScriptSignature', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.scriptSignature.hex + ' correctly', function () { - var buffer = new Buffer(f.scriptSignature.hex, 'hex') - var parsed = ECSignature.parseScriptSignature(buffer) - - assert.equal(parsed.signature.r.toString(), f.signature.r) - assert.equal(parsed.signature.s.toString(), f.signature.s) - assert.equal(parsed.hashType, f.scriptSignature.hashType) - }) - }) - - fixtures.invalid.scriptSignature.forEach(function (f) { - it('throws on ' + f.hex, function () { - var buffer = new Buffer(f.hex, 'hex') - - assert.throws(function () { - ECSignature.parseScriptSignature(buffer) - }, new RegExp(f.exception)) - }) - }) - }) -}) diff --git a/test/fixtures/address.json b/test/fixtures/address.json index fff8d9a59..2668a08c0 100644 --- a/test/fixtures/address.json +++ b/test/fixtures/address.json @@ -1,5 +1,5 @@ { - "valid": [ + "standard": [ { "network": "bitcoin", "version": 0, @@ -14,6 +14,20 @@ "base58check": "3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr", "script": "OP_HASH160 cd7b44d0b03f2d026d1e586d7ae18903b0d385f6 OP_EQUAL" }, + { + "network": "litecoin", + "version": 50, + "hash": "332bdfb31f688c0be0137c7c038a6d0fea0de0b6", + "base58check": "MCZjFcwYJwwYqXAbd3bbnxaCVGs81cp43Z", + "script": "OP_HASH160 332bdfb31f688c0be0137c7c038a6d0fea0de0b6 OP_EQUAL" + }, + { + "network": "litecoin", + "version": 48, + "hash": "6ac624143d19a3c91d2ac5605f0aebdfeac5b826", + "base58check": "LUxXFcwXFPpRZdMv4aYu6bDwPdC2skQ5YW", + "script": "OP_DUP OP_HASH160 6ac624143d19a3c91d2ac5605f0aebdfeac5b826 OP_EQUALVERIFY OP_CHECKSIG" + }, { "network": "testnet", "version": 111, @@ -27,40 +41,154 @@ "hash": "cd7b44d0b03f2d026d1e586d7ae18903b0d385f6", "base58check": "2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w", "script": "OP_HASH160 cd7b44d0b03f2d026d1e586d7ae18903b0d385f6 OP_EQUAL" + }, + { + "network": "bitcoin", + "version": 0, + "bech32": "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", + "data": "751e76e8199196d454941c45d1b3a323f1433bd6", + "script": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6" + + }, + { + "network": "testnet", + "version": 0, + "bech32": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", + "data": "1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + "script": "OP_0 1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262" + }, + { + "network": "testnet", + "version": 0, + "bech32": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", + "data": "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": [ + { + "address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", + "version": 1, + "prefix": "bc", + "data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6" + }, + { + "address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", + "version": 2, + "prefix": "bc", + "data": "751e76e8199196d454941c45d1b3a323" + }, + { + "address": "BC1SW50QA3JX3S", + "version": 16, + "prefix": "bc", + "data": "751e" } ], "invalid": { + "bech32": [ + { + "address": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", + "exception": "Invalid checksum" + }, + { + "address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", + "exception": "Mixed-case string" + }, + { + "address": "tb1pw508d6qejxtdg4y5r3zarqfsj6c3", + "exception": "Excess padding" + }, + { + "address": "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", + "exception": "Excess padding" + }, + { + "address": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", + "exception": "Non-zero padding" + } + ], "fromBase58Check": [ { - "description": "hash too short", - "base58check": "7SeEnXWPaCCALbVrTnszCVGfRU8cGfx", - "exception": "Invalid hash length" + "address": "7SeEnXWPaCCALbVrTnszCVGfRU8cGfx", + "exception": "is too short" }, { - "description": "hash too long", - "base58check": "j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe", - "exception": "Invalid hash length" + "address": "j9ywUkWg2fTQrouxxh5rSZhRvrjMkEUfuiKe", + "exception": "is too long" } ], "fromOutputScript": [ { - "description": "has no matching Address", + "exception": "has no matching Address", "script": "031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG" }, { - "description": "has no matching Address", + "exception": "has no matching Address", "script": "OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG" }, { - "description": "has no matching Address", + "exception": "has no matching Address", "script": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" + }, + { + "exception": "has no matching Address", + "script": "OP_0 75" + }, + { + "exception": "has no matching Address", + "script": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675" } ], "toOutputScript": [ { - "description": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE has no matching Script", - "hash": "751e76e8199196d454941c45d1b3a323f1433bd6", - "version": 153 + "exception": "has no matching Script", + "address": "24kPZCmVgzfkpGdXExy56234MRHrsqQxNWE" + }, + { + "exception": "has an invalid prefix", + "address": "BC1SW50QA3JX3S", + "network": { + "bech32": "foo" + } + }, + { + "exception": "has no matching Script", + "address": "bc1rw5uspcuh" + }, + { + "exception": "has no matching Script", + "address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx" + }, + { + "exception": "has no matching Script", + "address": "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj" + }, + { + "exception": "has no matching Script", + "address": "BC1SW50QA3JX3S" + }, + { + "exception": "has no matching Script", + "address": "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90" + }, + { + "exception": "has no matching Script", + "address": "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2" } ] } diff --git a/test/fixtures/block.json b/test/fixtures/block.json index 147a7ca2d..c60685e2f 100644 --- a/test/fixtures/block.json +++ b/test/fixtures/block.json @@ -1,7 +1,33 @@ { + "targets": [ + { + "bits": "1d00ffff", + "expected": "00000000ffff0000000000000000000000000000000000000000000000000000" + }, + { + "bits": "1b80ffff", + "expected": "000000000000ffff000000000000000000000000000000000000000000000000" + }, + { + "bits": "1b0404cb", + "expected": "00000000000404cb000000000000000000000000000000000000000000000000" + }, + { + "bits": "1814dd04", + "expected": "000000000000000014dd04000000000000000000000000000000000000000000" + }, + { + "bits": "cffca00", + "expected": "00000000000000000000000000000000000000007fca00000000000000000000" + }, + { + "bits": "207fffff", + "expected": "7fffff0000000000000000000000000000000000000000000000000000000000" + } + ], "valid": [ { - "description": "Coinbase only - Headers only", + "description": "Headers only", "hash": "55388f8f9b326bd0b8e50fbe44c1903d4be14febcfad4dffa50c846c00000000", "id": "000000006c840ca5ff4dadcfeb4fe14b3d90c144be0fe5b8d06b329b8f8f3855", "version": 2, @@ -10,10 +36,25 @@ "timestamp": 1413391595, "bits": 486604799, "nonce": 3760981266, - "hex": "020000003385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8eba43e54ffff001d12052ce0" + "hex": "020000003385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8eba43e54ffff001d12052ce0", + "valid": true + }, + { + "description": "Headers only", + "bits": 404020484, + "hash": "20f7df301c756a285af5298543d7e26e66bfba3229730f110000000000000000", + "height": 370090, + "hex": "03000000af7b8278e9c67a6d6b3453239d83406a1b21aaed2bfeb10e0000000000000000a4a4dda66b46547ff41d668543f1f20a5974488257a1f35a07c1815187b30ab9913dd05504dd14189f370acd", + "id": "0000000000000000110f732932babf666ee2d7438529f55a286a751c30dff720", + "merkleRoot": "a4a4dda66b46547ff41d668543f1f20a5974488257a1f35a07c1815187b30ab9", + "nonce": 3439998879, + "prevHash": "af7b8278e9c67a6d6b3453239d83406a1b21aaed2bfeb10e0000000000000000", + "timestamp": 1439710609, + "valid": true, + "version": 3 }, { - "description": "Coinbase only", + "description": "Headers + Coinbase", "hash": "55388f8f9b326bd0b8e50fbe44c1903d4be14febcfad4dffa50c846c00000000", "id": "000000006c840ca5ff4dadcfeb4fe14b3d90c144be0fe5b8d06b329b8f8f3855", "version": 2, @@ -22,10 +63,11 @@ "timestamp": 1413391595, "bits": 486604799, "nonce": 3760981266, - "hex": "020000003385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8eba43e54ffff001d12052ce00101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2403089904174b6e434d696e65724251521defe5cdcf04ad543ea4eb0101000000165e0000ffffffff0100f90295000000001976a9149e8985f82bc4e0f753d0492aa8d11cc39925774088ac00000000" + "hex": "020000003385c4b2a3499669987f5d04fa4127b59dbf2ee625694fa0bf08000000000000cf52f0ed6571367818a801a169e64030d8cab1a9f17e27170a6924127e19dbb8eba43e54ffff001d12052ce00101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2403089904174b6e434d696e65724251521defe5cdcf04ad543ea4eb0101000000165e0000ffffffff0100f90295000000001976a9149e8985f82bc4e0f753d0492aa8d11cc39925774088ac00000000", + "valid": true }, { - "description": "Low number of transactions", + "description": "Block (~3 KiB)", "hash": "f0ca57cf84cc953194cd87de4bd9142720a056dc6f27484767f3e85e00000000", "id": "000000005ee8f3674748276fdc56a0202714d94bde87cd943195cc84cf57caf0", "version": 2, @@ -34,10 +76,11 @@ "timestamp": 1413393997, "bits": 486604799, "nonce": 3126400832, - "hex": "020000000cccf0b884a20113ea2c53a381dacc92a68ae9db1cf86525eb259f0c000000000ebdaf5341d911e69ab53928e3f9f46e5ece27b950f3b43eae521a602bde41d34dae3e54ffff001d401759ba0a01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e030a9904026309062f503253482fffffffff01700a049500000000232103b441d74dccfe4d9a3b343149557107a68feacbe95b5ea3af63f4259b4f278b24ac0000000001000000014c959784efb5b2e8d19e2aaca588f7591c10d8f9833dfaa70db9fcaaca123ce6010000008b48304502200d4e79a0aaa162413b812aa9a5a9af18933bb9ee8adabbbe745785ecd0a41d7d022100909f15d27633127727b02a36c2986c37fda5df7ac686002dc9d0b892678b42b40141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914ff6d383fc1eb0560b5bf63bac7988763098ee7b688acb10abba80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000012568651e80bd1f2c08fde0b10ef507c19ca95e0f7249d6d8b473eada36963866010000006b4830450221008be9aff4a081f7c734e2b679ca3483e205711fad2733c5f5afb8c0bb5213930a02206322d9eb1808e5633da787bf56966418169fcb0a2b964b9a35053c4859cfd35f012103bb318b00de944086fad67ab78a832eb1bf26916053ecd3b14a3f48f9fbe0821fffffffff02a8610000000000001976a91452bc36b0497a027d1c5637d8df7389a48b34245a88ac101aff03000000001976a9148e8c1d4adef86c11154fd04b5012306715fd4baf88ac00000000010000000103642c8bfac609738fb1b17f270ba2baf5df8e87108fc795070c8bf2e415210c010000006b4830450221009714a186283b0d97ae5b24f538618f96e0cca42269e69c389c9c5211bb095bcf02200e3992501e90f44a8b03b26cf9c3f2b16d73d150ddefcae607fc1e7c2dcc02c40121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5ffffffff03781e0000000000001976a914a4547646725696134124fdf5b465438b940d43aa88ac781e000000000000475121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5211c434e5452505254590000000000f0a6c9ae7ec80100000000000000030000000052ae035cad01000000001976a91421cd98f4b804dc07f992559cb551158a078472f688ac00000000010000000183f2c7c9ccba010eeb72a53f83a41970c2cf2e2f2deac895e917fc1a8da11aec000000006a4730440220068e182f5528c1752414bb8db48637b8e329b700018d02056d4ed01721daef9f02202bb66702d586e4ece02e70ebac251b5e5e7af0914ceda2f5efd1ed0e7fbe51030121031765feada1e5d93645b514bced2ac7135bb4d14c41c5e1f9f973803537299c20ffffffff02a0860100000000001976a91436e875fe44ff4a4b13838d73ed32b5c62722096588acb09a9600000000001976a9141d6131f1ffad58a7d091f1fab29ee36b27b091c288ac000000000100000001387daacc7861110fc52535e8a9733d40fb9c56b281708993e1678c79ed927677000000006a47304402205b082eea0f426dc753b7489df44f2b979d1cccd3b6f0ec9bb2ca002cae4cf78a02203733f049ac70354ffa8eb290c7c470352802339ddcb940659340d15fac89c880012103e1a574d02cd9d0231cf53cb2ea5bba86d570f0edc9b5cdff613a46d381103fb0ffffffff01e054e111000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac000000000100000001be200e8543914878d90b147f5a43bfc3828af510a043a4e0512293df5ea4ad60010000006b483045022025f5128d9d2c66a32e8fb80c733054a3ab74fcba6499a84f55918e52551e8d01022100a11f183fb8da767fb16b71fbe737f062983793509fc6b8286a6a2c84f6553a690121037beaee4dcb1fdf673665256608d3ad0beacde433b117ea8f0a8d23b8a0ee8307ffffffff01e092f505000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac0000000001000000010f5ed77b8816242642a5ba39464e48fcd1cacce6dfc27862d922407b760fadaf010000008b483045022019737d8bebf06ae5588888dcf403b456484cd8a1eef73e5890f980dc9ef88aa002210088e8d47f7c719482ae74ba6bea8a70a76bbfd72f751bb78e49a9499e931608c50141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a9145bd3695ed80d96ad480bcc3e252030e30b1c2b1c88ac015db9a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000016f367591e290ecc8d36a27e00d03a789df873dda395a1c5dfc6e52ecd6d8630f020000006c493046022100e1a59d90352d278499ed252fe5318bef60aa4e6c12aea5b6308014db01370983022100e583dad2651f1cc4769e533d5f398fc49856f1d8500b75bb1970dc893231ceba0121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5ffffffff0338570900000000001976a9141b0247a0ec998ce4774193e6629708a5112f3cee88ac10980200000000001976a914a4547646725696134124fdf5b465438b940d43aa88acab45a101000000001976a91421cd98f4b804dc07f992559cb551158a078472f688ac00000000010000000187bbd96b486f78f8f80a6c19d60c50637c3c56a08d96d61e941fdf7b30aa9a9c010000006b483045022100aaf951927b28c66f32e26c1ba29563fe04ba245cd5f30af7887b3881c30914320220692f588b0a31f730a70e99cf143ef09b29b2216268f15f71d7f0255842a4fd65012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff0280969800000000001976a9140bccecf71c0b232d9f89dabc3f2b5c4ee7170e7b88ac00000000000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac00000000" + "hex": "020000000cccf0b884a20113ea2c53a381dacc92a68ae9db1cf86525eb259f0c000000000ebdaf5341d911e69ab53928e3f9f46e5ece27b950f3b43eae521a602bde41d34dae3e54ffff001d401759ba0a01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e030a9904026309062f503253482fffffffff01700a049500000000232103b441d74dccfe4d9a3b343149557107a68feacbe95b5ea3af63f4259b4f278b24ac0000000001000000014c959784efb5b2e8d19e2aaca588f7591c10d8f9833dfaa70db9fcaaca123ce6010000008b48304502200d4e79a0aaa162413b812aa9a5a9af18933bb9ee8adabbbe745785ecd0a41d7d022100909f15d27633127727b02a36c2986c37fda5df7ac686002dc9d0b892678b42b40141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914ff6d383fc1eb0560b5bf63bac7988763098ee7b688acb10abba80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000012568651e80bd1f2c08fde0b10ef507c19ca95e0f7249d6d8b473eada36963866010000006b4830450221008be9aff4a081f7c734e2b679ca3483e205711fad2733c5f5afb8c0bb5213930a02206322d9eb1808e5633da787bf56966418169fcb0a2b964b9a35053c4859cfd35f012103bb318b00de944086fad67ab78a832eb1bf26916053ecd3b14a3f48f9fbe0821fffffffff02a8610000000000001976a91452bc36b0497a027d1c5637d8df7389a48b34245a88ac101aff03000000001976a9148e8c1d4adef86c11154fd04b5012306715fd4baf88ac00000000010000000103642c8bfac609738fb1b17f270ba2baf5df8e87108fc795070c8bf2e415210c010000006b4830450221009714a186283b0d97ae5b24f538618f96e0cca42269e69c389c9c5211bb095bcf02200e3992501e90f44a8b03b26cf9c3f2b16d73d150ddefcae607fc1e7c2dcc02c40121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5ffffffff03781e0000000000001976a914a4547646725696134124fdf5b465438b940d43aa88ac781e000000000000475121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5211c434e5452505254590000000000f0a6c9ae7ec80100000000000000030000000052ae035cad01000000001976a91421cd98f4b804dc07f992559cb551158a078472f688ac00000000010000000183f2c7c9ccba010eeb72a53f83a41970c2cf2e2f2deac895e917fc1a8da11aec000000006a4730440220068e182f5528c1752414bb8db48637b8e329b700018d02056d4ed01721daef9f02202bb66702d586e4ece02e70ebac251b5e5e7af0914ceda2f5efd1ed0e7fbe51030121031765feada1e5d93645b514bced2ac7135bb4d14c41c5e1f9f973803537299c20ffffffff02a0860100000000001976a91436e875fe44ff4a4b13838d73ed32b5c62722096588acb09a9600000000001976a9141d6131f1ffad58a7d091f1fab29ee36b27b091c288ac000000000100000001387daacc7861110fc52535e8a9733d40fb9c56b281708993e1678c79ed927677000000006a47304402205b082eea0f426dc753b7489df44f2b979d1cccd3b6f0ec9bb2ca002cae4cf78a02203733f049ac70354ffa8eb290c7c470352802339ddcb940659340d15fac89c880012103e1a574d02cd9d0231cf53cb2ea5bba86d570f0edc9b5cdff613a46d381103fb0ffffffff01e054e111000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac000000000100000001be200e8543914878d90b147f5a43bfc3828af510a043a4e0512293df5ea4ad60010000006b483045022025f5128d9d2c66a32e8fb80c733054a3ab74fcba6499a84f55918e52551e8d01022100a11f183fb8da767fb16b71fbe737f062983793509fc6b8286a6a2c84f6553a690121037beaee4dcb1fdf673665256608d3ad0beacde433b117ea8f0a8d23b8a0ee8307ffffffff01e092f505000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac0000000001000000010f5ed77b8816242642a5ba39464e48fcd1cacce6dfc27862d922407b760fadaf010000008b483045022019737d8bebf06ae5588888dcf403b456484cd8a1eef73e5890f980dc9ef88aa002210088e8d47f7c719482ae74ba6bea8a70a76bbfd72f751bb78e49a9499e931608c50141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a9145bd3695ed80d96ad480bcc3e252030e30b1c2b1c88ac015db9a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000016f367591e290ecc8d36a27e00d03a789df873dda395a1c5dfc6e52ecd6d8630f020000006c493046022100e1a59d90352d278499ed252fe5318bef60aa4e6c12aea5b6308014db01370983022100e583dad2651f1cc4769e533d5f398fc49856f1d8500b75bb1970dc893231ceba0121027ccca0e0a9c86180431340aa14038f22428b196a7a97f4c63b4afe142afb55e5ffffffff0338570900000000001976a9141b0247a0ec998ce4774193e6629708a5112f3cee88ac10980200000000001976a914a4547646725696134124fdf5b465438b940d43aa88acab45a101000000001976a91421cd98f4b804dc07f992559cb551158a078472f688ac00000000010000000187bbd96b486f78f8f80a6c19d60c50637c3c56a08d96d61e941fdf7b30aa9a9c010000006b483045022100aaf951927b28c66f32e26c1ba29563fe04ba245cd5f30af7887b3881c30914320220692f588b0a31f730a70e99cf143ef09b29b2216268f15f71d7f0255842a4fd65012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff0280969800000000001976a9140bccecf71c0b232d9f89dabc3f2b5c4ee7170e7b88ac00000000000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac00000000", + "valid": true }, { - "description": "Medium number of transactions", + "description": "Block (~32 KiB)", "hash": "0cccf0b884a20113ea2c53a381dacc92a68ae9db1cf86525eb259f0c00000000", "id": "000000000c9f25eb2565f81cdbe98aa692ccda81a3532cea1301a284b8f0cc0c", "version": 2, @@ -46,7 +89,51 @@ "timestamp": 1413392796, "bits": 486604799, "nonce": 1810450624, + "valid": true, "hex": "0200000055388f8f9b326bd0b8e50fbe44c1903d4be14febcfad4dffa50c846c000000000c40f497466fe67a94dd9dd6851844097ec0e30656959ccc26efde12e119f7709ca93e54ffff001dc048e96b6701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0309990402db00062f503253482fffffffff0110801f9500000000232103a552615caaf1a5f824a47fffede99bc845d182d66daf5eba55a881bb596c6530ac000000000100000001d0fe6ff5a3cdd1ebaafec5489e7cffd5e15269fc06492a1b948bc3d503725621010000006b4830450221008dbd58f7d83223f7354e6c6c4201bf0723a107b2472294c51c0e8633df026ae502201a625322146f421562e139735ad960baf084335e7df445105838ac2d4b591af2012103c1d0ed4e6a4ed6a60e87021a98bbeb838cad9474fbc3bb45df1b2152f1ecc79dffffffff0200ac23fc060000001976a914b6c4f76334fa9c78ab90a136495366d4dfd6a8bb88ac40e179ca0d0000001976a9145001e1cceab6a9a5d29360c9e38e30010638437688ac000000000100000001e4dacf5f5d9856e5cc5503639f03030cb1325e5957d45fa018de00892379f680010000006a473044022075fe3a93ee65b18b1f2c7b32051a54b55e4497e8413da313b79f134bd562c5f4022002cfd7e72c81e7e7a4d7cc20e7a357ee9bf1ea837820968126d8880b98027bb8012103f5ae0228e424648dea53ed5e913ce2627d5b920ad4154816b0778a23dec0eb74ffffffff03781e0000000000001976a9149864a73e6d46d4a0ca10c693b450a868cd72a2a188ac781e00000000000047512103f5ae0228e424648dea53ed5e913ce2627d5b920ad4154816b0778a23dec0eb74211c434e5452505254590000000000000000000000010000002650170ae70000000052ae6821052a010000001976a914460e23da200cbaab6c3ced3f4e28bfc98a63be8288ac00000000010000000c9538dede63932e2fe2982c545ea6998718d1b5605090dd58519616b453db7e5e020000006b483045022100bbd78f28b3865fc2e4f9cd803a25d479fd0d9327a4ad96a99d1d47ca3f15094e022054173cba9e535f413552dacba5c1a50c52ac48466ae01638437716ae49d2ef960121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffffb59e4f1a7ebdbdff556b31302dd8a91a33d77b3957ae6ca72b489748afdbe9b2010000006b4830450221009c28408dd0164c3bd5c1b79540c916e851625826de9086fb8450e7bd439715b602207f90e0e898bd4c83c78cb4c1c9374e5257ace1ec854ffe8ac40198f2581bf74d0121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff91dd9ba2209e55d744ddee54086dc30593841c27f1103afbbecc27fd39e0fa36010000006b483045022062dae6848b1a56a90f4ef78f820685c4502bf265db2df71217f9473115f56fd8022100eba07d1665f5d51a9aafeffe2f6fc8a14088d638480718bbfaafed724b6c4e050121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff805e418a77068a845f6f9fc1b70e786cd69fb7ba0a377b732fac7468ac5f38fe010000006c493046022100a4bf26f55c6ab6ed7a93d2b2ea7d61fffef97240d2aa898bc1e66a23ee4e9c420221009b4d8e4105d7d7d0e518b8588f241c3768406747a16238d19ca3ac9be91a6e790121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff352e5fee82e0bc6602f384cf70955516a7704408c0ffe1e9cf8653f83834df0a000000006b483045022009bf389f60989afc68ce3c95bbbb101cc459e8563560726a2ad96fb3d78c28e6022100ca37cb90c653202004ec55df1719714ecc2d93ac4a79e1d01ce6e28499edada00121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff7dd3e25567c59fb11ff5d51e1df5721e791ce45177ffe1b03bde5f6f381530a6000000006c493046022100c1c4cd0032c8b492f8cb1cee933119ffe3af098ac9a83af6dff8beddb9f025770221009f397a74a31e2864b49ab52ba05df6a91434a4796aee8615854f5fab38645e330121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff4f1dccb3840dc9b283f40bcedde013952b5f63de3cd55637bdb107cddde18e90010000006c493046022100acca38436760d65b1255001c8e28e4b56a465ae0ced00339dc4624a9ace4ad8e022100d41a6a2176b0448ac0a42bd1c9c66b1b1d2fffbe27aeb78815f3ebe66fd40bc60121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff55f0f7040e86dc63abb338fe084aef5eb6212a94eee96511dd1e7dc1a0276e25000000006a4730440220249ba7396f8c00bde4c1175b6d23ddd6b0b316472a09a7c257d692305de7c97502206f8700e708155ea0ea7f21666b849c8c458555d6eba1f2efa33402619bef61a00121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffffad66cade8d08afb220204659a195c8b5d0ee3835d943b5f1e696ddbca24e6a46000000006c493046022100e7c30711e1824bbac03e4797fb989b1de1097868714933734fcbb5bf64861b5f022100892eddbb854a0415628a1b03f0adb45cb47b397cba03ff00536cab86c3f4c6e90121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff6c2fd410949b573b0f9ecf05f93c1e5bd23333d25a219dcf59e9422049d04369000000006b483045022100f38eb4802ae149ca498b6d0f9398e1724d659f75f6ce953868d3761b0b1bc4c50220067a29a68bc1ac0c85489949e069ed336d33b0b5c437bfb85733dcf971362e0a0121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff3d78262970277034a0d6fe72724be0901875e5a6998d52e9d086d4548adbb194000000006b483045022100d0f74329ebbc2377707143ac5f6a9c88e99cbd610fdd60a5d79229a69fadfaff022033c07ab93c23a41b9fc88a330e9c5a7b904f80085400ee21bdd217675de68cdb0121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffffa5ab972cc8ca1f10df69bc3ca1b05f68b23414b6f9efc486d8732a1ede6da29c000000006a47304402206d3d945226f41fee7f266c57089c96e1d75aa5c03675357617cd6781e38e5c3b022064f3f32221c26a9b4e33d3d78625210942c508d679c999e385d0d58fefa330350121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff02009ce4a6000000001976a9149864a73e6d46d4a0ca10c693b450a868cd72a2a188ac20114e00000000001976a9146c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a15288ac000000000100000009f09b59178c8494f17e829c5c06295492024cfc97cd5286435bf3ffc8c0420809020000006c493046022100f0cacaeded00fb8f4c84f999f2052b6c4ab6ec5b41e5ef6223beb7fb33adfc1a02210084a5ca4e74ee0ff13d1e6840f6cc8d210e6d2411cec6b336a968ab62bc6ebe04012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff46b64a680c290c0eeddabc01d4de0dc736400b615324262348bfa0ba9fba249d000000006c4930460221008e6d830527e941d79a6fafc832a58173e7de92614275263b2746753f605a9d69022100baf17b1a870e0ee617aeffe726fe02a45c8fe19c09840b01851c64be2363fcd1012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dfffffffffe5db21f92158086e8a062f5d458c8652895815b0b550f8847e1c544b59a205f000000006a473044022017e1890c25400567aedc52fd0c87eb1411bd19b47e44411438eef2c7cfffb6bb022077f93849ed0fad655b48ddbab1c0b0dc0c704184000bcef7b22b6303956a9571012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff794c2f1b57dd6f54fe53bdb7220ec59b57f4114786e820355ecdb16f6d66dfbf000000006b483045022100b3cdad56c0451610957f474f1e21f30440cfbc4e8da8bf1e0e6b2df7df268f13022000f17c04a04d0b5f1daf4a11e78d581387d8371b690a435deafd9e5d425a57e4012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff09b1e86b31fd61037056116b96fd7e09edf83c414808d50975cf307cf1859c53000000006a473044022012887712278e9ec9fe936bdab5b46fbe4aa7232a37201e6f4871949ac32b47470220427b8ca31f60d45dbe892d5dad571c3e9844827894d0816520ff59e15ff47e03012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff559bda6c35d2bea892e02d1bd1a8ceb6cfb8ec6da4c5e64095aa6ece16da9fa2000000006b4830450220376f947fd4f6a34a6962af5f6a6b90aae828ff68a2312f0ed4abbdbb10311455022100ac3d53e42df05c62cf1874744a70d9f9a88a20cbb7fa9e3bc2f008fc0cf747a8012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff3bb1a45cb905a09a7fa335455e4a2403bdd3e2e07aa9de8e858f2df282e841ae000000006b4830450220058a3cda301167c4030eb8793870f2fc8b62b17ccbf04e35e733e2db1d9a33410221009163811a84710f73d043e6c72b932d257f566760752d13abeddcda878f4bffc1012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff73e038fd3ca63a87cb5f754e87151d5d4690005907d1fec8a585ac01269ed3ec000000006a47304402207c0f6cbce98127749da512ee7368dcb8973c5b5fe254a2b667a08636acda487f02202be9c33b4d49e6c3615116a0633cbd6fcfb8606dab768b66baae83069a6a5f79012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff9538dede63932e2fe2982c545ea6998718d1b5605090dd58519616b453db7e5e000000006b483045022100e2212e01176bced817130de9031785a25e41fb8f40449ea4b8ff74ef59badaa102207458caeafa0d38b6e9f5d13c8bf95b16c7a075a2b0e6225b025954a2e71a72e2012103a4ab022465616cca59be2fabce9601fd8226b681b05d62cfd61a1653da0cb59dffffffff01d8defd09000000001976a9147f6394009ab59c0ff5f5cda27ddf2fddfae4167988ac000000000100000004cbaef29b3dadd2016ef2e3e646dc75ef97438bbd22ca1ccc775172519d22962a010000006a4730440220329e18e737bc060d43596d2326edb43ed445b89e7ecb19682a2a59f4c50589ef022005fef3ad6601070ccaff7bae890259b6a7754284a54d00598c2d46e0c0c4a1730121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffffa634945cbb12e719a9997387202aa589a4318f8416804aac1674d14ee5ad1e1c000000006b483045022100ed0d21ee6bf1a288525f8beb39596f844201bc2287aa1993bc3d6a67d246d33602206130710bf1ef50655c9dfa1713f28eba4e03d3084e2d317802e1a15347fa647b0121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffff1fe00780bc3e272f758f6892b7eebc58798e2c253685720e944c484657b045db000000006a47304402200df0e0c3bab3e8d9c784237fb81ffd7fffe1dd56b4d026e1f423a0658732836b0220209c70ef792bb0912b4cad295ea53075f224f8121d0204fce5e3ed16b40b39c10121039e3480be13ca86714045e1d28da159e0b3647d4bfb51dcebd8b33985a47a51c9ffffffff06d9254a072fb767bf04fbc22f76332826f5b63532b1d11daa731605ef7a54ed000000006a4730440220636fc2e4e2ab68dbfacce1bc0741d6f2106a581b65077a2e2e08e3536a0436a302201a2a1967b4e990832fc03d5647e6f2512d12dc99fdf6a0a5fc125787a5305e490121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffff01e84a867d000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac000000000100000001b57bd3ac69dcbc285633cb4cf12c765c019e51f910c631fc1c74ad660dc5e7b201000000da0048304502204aa9b763abccd2409d63c618381c87b844dd7e7db093e14e37c8301272cd0be9022100fb59b59c39c2c4314ff2f08d1ab3fed224caf141e29605973b45c24a2540ccd50147304402202bc760c7f98446731f6aa2bbc0e3913229724e228bf8acc7f42f16107be158290220336e90a92a5aa6cd0d386895f4cdf8ec9ad313d9a458b7ca9d9193fee92abf210147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9145bee13302ba496c3b2d3ccc89202fd76831228dc8768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000010000000149c6ab2ace8003a6a1b5bf67e6e0fa897e1d2d2608172255f45bfb324532a09e01000000da004730440220214317cad717a1cd6baa040c52e697d4cd2b17ac4c85e18c6f258e412778bdb202205707884f09b4afb60063520004e8af694c6dbc7f6e111cf40ec0e0079041cb8601483045022100e2c9b18f26892a97b0008b8176b1d68f464994640d0208761e321390c4ce47b0022016e9d87c32d36e2add90a89abaa585eb164a756ded0136937e93fef3982ec5a70147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914c4c9057cfe2b9083b6533f51f747ae3c77c5c3da87405e97280100000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000017a22d9a45dc865bee7642eb16c60ec0a6717d8554772e0640a705c12e549018501000000dc00483045022100839f66f053917dd03a84a949b4bb539f8fc06c95ac0aafc9e78f6cc040ab419f022001eac1a450ca2613d6ce47db4a548565bb70886dcc555b495b77ed2a7140fd5901493046022100d35e6c8f71eed0114f8787d078a17310483a36100eb6cd7c9a0fb7eefce4e0b5022100d7640cdf302734a1a8e2ea81f3bb6319652134234859725a4f16ba780495f4e00147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914d1e450d11ce446fc45d57001351842aa3b810cde8768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef4781094548487000000000100000001ac7c2b917cf245c06be4d59250bd433f367387c727b46616ad2594de8396105601000000dc00493046022100dc62b5a197b6bf7cc90d5d0f87fa97499e3bd456a7f8bdfef40df55d6a41b6d802210081db312d90ef3353aefdab24c58391c2337412b889a0160982e6312a9430c77001483045022100b5e89e670ad5aaa51ab412fbaa6718586fdb1b298fb3920ea90b4732c38063050220307721a03a54b4362abfdfc4d994b020e42a5b869c85fc089b9498d9d5ddc7550147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914a49d822d44e0ef8d20022843032de6bc420b2f5e8768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000018f30d8ad625b6605299ed2f66a6b28e2912b4391937f7329ce2424ed178d7a3101000000dc00483045022005bcbe24a3511f401be2f94f5ec9414050d7b5d4c8a9c262d6a438a080559c09022100f6c5f4422bd702ba4fdc403c342c0796a6c4b34b236f15362827636448f02a1c01493046022100aef5108bafd191e519bd633460d334bc3ce662f513fecaf3a8d48ee93c3c2c04022100df30ec6fecf165cc187194b82c534b102d62c66e97c21291d8192b5b00a3b2aa0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9141ce06aa7fb344bfffa8252abb5100777a8baa5068768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef4781094548487000000000100000001054b9393e69e0d71bfd194558ec2182e38167e0a7256ceb0869ccacc3a5acf8d01000000dc0048304502207d21bee5933283111d4af205e3366e0ec8497e77553570737de9631c9554ef7f022100d284bd8ec48c58b879ac1e7f39ddc9e62ceae440636e9179602a666b6bd1a36d01493046022100af4c6387b30b0050af7ee32d7b900ba6e6051aaa41c876d54fbab95bde62d583022100c3893f9bea39253ec1997717ed50f4797522b41ca5732364d506be31342a82e40147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914edb50dd706eba79f59fed2980843190799abc74187405e97280100000017a9148ce5408cfeaddb7ccb2545ded41ef4781094548487000000000100000001dfbc1c58a49e3a41a6d3ac9891124f49631f8a590a8abf788e971a59a155352c01000000dc00493046022100e04d51aed1ceee5b3dd16977f32fcb0ecca486b15483b792ccbf72fd444e93ea022100c6c1ef150716597a57d5efb3859c3a519b6099f8fcd76fd88dee51d9de6deefd01483045022100a0adc0d0ee2f333a0377ed97f233944b5017be97869f1ad3c0a4f63b7583bff0022019f9033576e8a1557e8c341290e04378ce8c11b59451aa1c99b36875f04b4a760147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148821c06dd300e0c5e715a7c4764bcff9725447778768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000016fd309077d56f72e8e479c41c20181edabef98b64a6b67412edbf24025d5a53a01000000db00483045022067574c4d4ab1a0c8ec6a8af7ece7ea7239612f447e06c7332049692694dc3bd0022100a04b4c0d3159c6b32087971d6ec50818d74136472cd671df9b3991ab72a127e101483045022011357745d9e0516b8d11f38959e1d16fb6135f43c52235c541bd50f0ab4af3e5022100a2b34b41173d7c2de383559264aaf67fdf4cb3a59de1774e809a9c362da72f2e0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914765d0d258d3d0e6a59bfee9bfbb20596518b95d08768a4a6280100000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000010000000284f9f2e4e397cce58c844dcf5ec258dcae775d5bb6c8e1bb9bd0f53f127f7cea010000006b483045022100a6e72fc5e8a0e07853b9015874d8e40e6861185ba440fa4d79ffe925206133d5022006fb2812553d16e46559ba2260536e5809c9e8e420ea98033b85d91f3726a3ae012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7fffffffff93a9a177906a0ef7ef0232ccc9a2933a99b3369b996c39f8afdff5a0b95188d000000006b483045022100dca4ecdf7c3449c434fa0cf32193afe1aced32c7491e9cdaac592a61bb3ad8180220143cfc0cf38b68531699bbb8a6723205376cecdd254e33711b73c7b5fac9db0d012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7ffffffff020065cd1d000000001976a91472b06fb7c4eadb3c789cf719f7799baeca7e760788acbf874711000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac00000000010000000240a16cc443545b1ae809148704511609154d4a9d8dbe31dcd54bdee791eecf7f010000006a47304402207efad34302f84d50c02836b684670f4a94fd13983e23351de3b29441234204d40220242f0522da0602877f7d25f71b8a261db67eee197bd991961714eed2a63121f4012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7ffffffff12aacb63b4b92d7ed823e660c33ca21e68322a2c6e7aebbcf7b1213f545baca3010000006b483045022100ab60c956a9ba34c406833c9311ab09ef7970ffafcb06ffa8d8a92f31a74a983d02200eaebf14c4b5ba4c8f48a4771dfd8e03ec2d14ae11d8ad3d9d487a738946344a012103e5481d0ff7e3d8ce9190647281f5475724b349444ada4bbe8faacebf92ee94f7ffffffff020065cd1d000000001976a914ee2af5db7aa20a3ba7626a94cdb2da48c0701b4188acd753ee35000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac0000000001000000015adb3c14dbd5a07721af2b787c85a95c4cb133dd17f9063dc129ac849067813b01000000dc00483045022100c8fad8eece220e207e2dbdda58b8952a1dbc704d65665af4de527fb01b3734d60220085531162e2448ac58c769c60187a013aef46d26288dd97113e71e0b888ebd3401493046022100a55e1af65f9cdcfccf05c15e536c3d491831a3eda3fba686c7eeaec276f1d3cd022100ca4e818f811d3635710226623c892c0b31cd7740b8e74d4a0d7a59dfc1a76c0b0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a914c07e34dff7613b421fd17ae714ac2915cdcc54de8780a5e2ce0000000017a9148ce5408cfeaddb7ccb2545ded41ef47810945484870000000001000000010b121a3f08fbdf2e8ba32c5f60953eecf80dd67e121fb14d4209ffa0c4c2ec0f010000008c493046022100875bf2629f2cf6acdc2302f657ba7aca44eddd5fe37dd754b8a6fba8c47f4fd7022100bfcf0ed84925391c96c75caa5a914f786430bdc29ded3634e043aff339fa08ee0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914db2045e8f7c6327732dd242c865e0e418910644188ac9162c9a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000017419cb2cdb61340f43882c2e034bcc919fcc742444ec41406e72f7897ce64aa0000000006a47304402205144fb84c7af6981c727ee6866ddc9a37a5ed49557dcd1042721ec2d78b7c71f02207968261f681fc3a0531bebf366cb92c88e454325a4a2c98cf3dd66dacf0363920121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833ffffffff03781e0000000000001976a9149864a73e6d46d4a0ca10c693b450a868cd72a2a188ac781e000000000000475121037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833211c434e545250525459000000000000000000000001000000dd0aff99340000000052ae80329800000000001976a9146c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a15288ac000000000100000001b4702c4f28336cff691596e14170507ed9d64b732cf519a61287acb2edd773c2010000006a4730440220050fc133e55a1bd033b84931e1b62569f98737f099593c784609e03bacf231a1022029afb388fbe52e7819d9dac0d73cd0ed2035d9f917bae304ff224ce144198f40012103e86cf84b12237867a327cfa3f34cee931db055a06312b4bef003bb784b4ca74fffffffff0200688909000000001976a91449eee8f4d96e60d8bbfcc9f82747c4496ce33b9888ac406c026c030000001976a91443dd90e9647903ea668e4ea3f5feb8469630d5a188ac000000000100000001d22f69c205c88c4f52f7e148ca579f5cb3a93cc5e52689594e0f33306ac8694b000000006a473044022021fe708e2fc6d0dd2bbf7d022860f5e8c68e6b1fd6306fc3079360a48afbbd6d02203fd0d67f89e3cced810028ee8b48c47b9078438e99b0bfb4386f57afe9d3d23e012102ddda164bba2b1435b04a8205ca5eefb5115270e6d19f6daf8960211b17df086cffffffff0100e1f505000000001976a914eba91c185a67b207f461c7661ff36890fd43e38888ac0000000001000000017aaef574e423163a2fffa556544bd9483ba415e023d56bb40a9e3a9f805e934e000000006b4830450221008db13e8a38e3c10b41f740eff8b5f5789c1c134d68d97cf4b3ab1fd72d3c8a76022061972edc83147ac6cce1d03272a0afbcceec3fe840b43170e6acd427d6423653012103c117f27c03a87e09ae1b0c6f49d2ab79c1c199580a1d0ff2fd682e227a60c427ffffffff0100e1f505000000001976a914ebf067bf6053b3834f6154fecdf31ba28bcb0e7e88ac000000000100000001f0ba13339fb4d5f722a02bb31383427014d967cca259fc51ba6b1209129d09eb000000006a47304402201382d8ed4c3948a33da4131910d060b79e74afd2d890a9f99bcd5358bb6d5f6802204720beb06bb6f1a51528941e587d547e2efbe0b3f96c8d19edf71ca09dddff0c01210222e624e056d0de15bb97e28c7fb8c149dee686f3027f17a032617152c7676d09ffffffff0100e1f505000000001976a9146d2276d2ff9bdc3e3da97c2761bdac3d3104241388ac0000000001000000010df8c6a9c06786b269b6db1dcff3b766fbb13532352d48ae175245594178f949000000006a47304402204715e1ba8c869d7574cd4f4ad4ffe7eda44b3e4c11640e03dbcdf538afcb2bc80220614d5a17c60e84e1bf38e0d2d2a4c7fef7a91a2a42466bc8cbfcf0b48a650761012102c9bf3691bbd97c2a6bcb8610f547cac3ee169f5d59e947069d85f2f35df1a6beffffffff0100e1f505000000001976a9140693c924d0650fd0cbe51d87de977faf13c6a29088ac000000000100000003bd698ace3161ce9e9bd493c24dd43e78fb61f079c4aa047932244d8c9948c2b8010000006b48304502206e82747317df2734e5f2c74514c575ddb7d79ec928649fd1734ae11d53703c4402210094f7f303c2ead5b0fb33b9d7663e4a0bc2711390ac5b8d742bf0bc747b16cf100121025c25273e23fc94e9bdef68279ce4b2eda49dbf09635af064b8bc89061c2d334cffffffff06d9254a072fb767bf04fbc22f76332826f5b63532b1d11daa731605ef7a54ed010000006c493046022100a67df7840a93b3f19d8fbad4383557585017fcff853b488292c626ed155e3b350221009b3aecea5f29b7420ec53b0d119996a569e421a752b1fe30efdebb9f158b498d0121022ee4f8d242ad07274e4deccf0523dcbf6da2e55cdac0f92e8a79e40c721a7350ffffffffcbaef29b3dadd2016ef2e3e646dc75ef97438bbd22ca1ccc775172519d22962a000000006b483045022045df4b9c83159061ba910a07886a5eb312fd244faa963cddf6dd5af20b685bf3022100dc29ca5952f74934d589b838bf785042e78b638756fc92e71d3ef8e4bec8aa8c0121039b1a29bea54d0482295ebef1d023c686dc156722bba16bf317b38636bb1717b2ffffffff01a834f20d000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac00000000010000000136e0ee46b394e3e665ad264d6b0e813f510ebd49c9cf4ee9fea66394a3cdd516000000006a4730440220547205d8dc30ae833b82cf0acc2c76079e9344ceea410bef3dba06b96ae2f0fc02206a3b175b1229079b64892d99bccffbe8cccf9ce634a11e6cdccb2d740a89c938012102c6052392f63433a8874a85e632960df44f901bbcb81e75e91c502eafe8437b88ffffffff0239660902000000001976a914e31b53d30d08216f5b7e8b8fb153f424f0cf2c2d88ac7493f901000000001976a914474a84925c770102eaa92c509e95812507f3f7d188ac0000000001000000015559373139aeb2fa33fcf78ba3e571663b20486cfb7d896528f97091975c5605000000006b4830450221009112b1958627956e05b5468f60931317b83dccdb9c91a195f49ceac2fd42bba7022005b57df3119060ffe82d478d834dc3d6b4c743787aee2801dc3cc504578ef3af0121020f4f9616ef316d44aed4f0fa971cf32e455b8f82e90a7a9c54dfe37d5c3ec135ffffffff02c9658b01000000001976a914066d59ecda788373e3206282d5255777b92a893188ac69256002000000001976a91418db620c4ec4593d76fdc6d4a30a531d08ca959a88ac00000000010000000262ee525e057cd877f7ceca8b3d3dcbd977fcfc993e6c1edb8577e707b4b5f61f000000006a4730440220292a063728cadfadd7cba2599cf5cd0460e9295fac92f50fe73da9c1c949979d022079794d70e84787a720b535cb1122cdf8c76486c7466ea6d12032784cd2776e230121030fdb4faabae84b19a7b192ed101ea5c8bbe5067acef3caf3553d23d1328fe74bffffffff2eae185b608e4598ee77e575f2ed39cd1cea745549aaf0288bc4e9cf696abeaf000000006b483045022100d338cf96525cf590a9f67046bb669f0636614b2ed9b85f07ae4db15c8d03ad8b022001ccf28b1733b03a218a5a6f1aeba5e35f424e39578730e50d927a574a6835c2012103612de64d269ed8a44f4fd3b5584ae7e5620909b5991d20b534525ed1b54ebc1cffffffff0220c15500000000001976a914f091a6cb3afa861e0dea0139dd26c60ea21cb8f388ac50690f00000000001976a914e8255ec318662f530c71ecb9ec1bfdb98ae7e45088ac0000000001000000016ad4fa5b8f91a88aed9fe60c2954b839d0d3f8345deb5f31ca6abb66e0213d3e000000006b48304502210093612fd6c14ff3f01f88e1f96d579b20af951c38fc3f96334c7f55a9f2df2d2b02201279f695bcd933bc2bd89a6849111a9987058a24cdc11b2838e0ebfdd702c9a3012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff0280969800000000001976a91421620cd6d05d98cd15d2c7957d3c07bcf9731f8d88ac80969800000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac00000000010000000214b9b3fd8986001e9dffafcbd08b5cf96d893b326eca48e1a6e600875ba7c1d6000000006a4730440220771ac11af98c9f3356c72c57e6d7b61d0ec6b1fd472c879e72f3b65adef3bd9c022006184aadbd78eb46494771b7a8636273c4a43b2369e57a921fe1966a4f684fd9012103e4b47eb68ccad5cf770836ba84d7450767432eda7ad62aea7c593db7a133cea8fffffffff53570346a73aa9ceed9ccc525985b06f412babf2d7441e52db9db6cc83551d2610000006b483045022100a67e312945d4b56a559352c743e4a981aba2856469697dc629db85f0e4f2e54302206ed43173e5e65af5df9e55303ac9689d63f458d9554e4fd37035aa8547736ef501210253244eaa74d1b79c722e062a98a7ca2be46e7a26edd948b5b5b141c5f1d15475ffffffff0296ef0800000000001976a91414da5dde04bc5bf7358984bcb227ed431419ef4d88acaa520600000000001976a91491859a64a061038dee95e6be293c7a9849db22ff88ac000000000100000002a186000a255c2cf7b6ccb4ec2774365629a6687c55643387e89ada264c5fd65c000000006a47304402203e1d65edfa06295a66899d5f3c5494fbe109c473afdb699ecda484e70f77e1e0022063a0c24b65b5c54aaaaac006161ac77bb4cfc402fe955654e993c1bb39e82fc0012102a7fc839c0427d33a1158f5b1d60d6d88fe6a790a838c6255cdb15f1a417af601ffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934570000006a473044022008d5f4cca8362a1735a62e6ca1c95626fc9e9add2bbd24ec19d3eadca1791c9f02205ca558bd7cc3131ca6a4f89a86f78e83298dc98bc7a3ce1b109d020a398dffa1012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff02908e0800000000001976a9147605eab76b8020350291bd3d22d8999d991a757688acb0b30600000000001976a91414da5dde04bc5bf7358984bcb227ed431419ef4d88ac0000000001000000010bb8f053cdf88a3048c9dc92f014cecf514e1841c5cbc6eb21218ffe60b4706c010000006a473044022071eb972e731146779d208e378a521e098bd063143ddd3b10271f1d1a94e934ef022068a48334b2a92c52e97de4c1a41acf777ad68fa76fd2917cbe32eefcd057ae2a012103f29b04e608b2e2247969d0488dfec01a2ad4b83ba7bbf3c5ba4460c5782acf41ffffffff0210270000000000001976a91417a149e8da478715292c372a8dee19453f72293888ac605af405000000001976a914dae4b5b55ef3219a9a79cd0143379e73ad3185e288ac00000000010000000195fb54d349c2d732001dd6f3b373cd3880e28c9e421af34801f1a6d2b88a41a8010000006b483045022100cab333e2c20a2003b410a2f013800b3107fdf0d6c42bd3235369beabae73ed6102205ce3c234ff199a2d742e83a00420a3f071d68f6732a6dfc141e2b86f8b988afe012103f54d81de4eec9215803c88d4097bd6990523e5a69ca6c20aff080f94d8925f39ffffffff023074f105000000001976a91400a469197c35da3fa892ff221c22840c2d15ce8788ac10270000000000001976a914f1c5145f86267d64199a22e2c420d022eb56575d88ac00000000010000000136e0ee46b394e3e665ad264d6b0e813f510ebd49c9cf4ee9fea66394a3cdd516010000006b4830450221008eddddd407132af0fdebbdd756db68f3440a3d41047bce5fd30a08a6fe5adbb502200825ca373a426ac1471c44eeebd29f10f1459d949a3cea09c7b95d1ff269cf3e012103c70fe3608fd1a6cfb79a66552a2ead99a3d20716ac20b8988ca87636f9c422a8ffffffff02a0860100000000001976a9140172c2761cef7a82f63cd1bd9921306d66fbb93d88ac9312f101000000001976a91424eb20998407878b5d704e54a8c0ace7b1be0eb888ac0000000001000000014fb19d79fb214b86b0fb0a6577c4250b2cba5358a14d0afd7acfdd7b2b055bfa010000006a47304402200692f6d951f2cd66c7a0d9aaa530fd6a07689c65cb5cd5b6d04faaddf75408bb02201e9c0e192ea7d59e1ac9ec2be449f3706fb0f9227c743defed62b3ba5b47f3880121021225b15301c1facaacac0104d6bd05a33046b2ba4731edaa565da41d7b4c2715ffffffff02f0490200000000001976a9142591497f043482f83f30aeeac043291dffebbd0188acfd03ea01000000001976a91457207e70a8aabb9479c5b4716fdbdf0c4238a9b088ac000000000100000001600f3a2aef32b8275f72e356af3cb94ac371c2d40464f899cef86390282f16f1000000006a4730440220521bda4b96e98e37a734ea9ae79c22f2da9b2ba4b1888d1af544b1172ba13b670220609121425bc54c61390bc9a73c92ef4c8e041c5da5d01de72d56d95340845e20012102f543b68df48df748df773d7c6359cf1ff90f81552c513572602733408c3f27eeffffffff0290f60e00000000001976a914b00e35e1b56764175f33e7caa14ccf14f94b5f3088accb85dc01000000001976a91478b58ed969d5df656b5a2bc42c8355656dd354ed88ac000000000100000001287a6ba0a3a61b4132b374c62ebf576790b80c1679accd0b8a655fe2daf9f282010000006a4730440220325c6e862d0c076cefdbe9bc768be8c417a63289aa12c85218e574a979577b3f022042288b09b904764e16019ce51cd3ee9bfbf9e20349f458f27319ae4df67d22b5012103c8fad558718b44e388695b2969d17cf7daaa1fb872ba072d0dd4b97fa1c8b594ffffffff0253c6a501000000001976a914aa2a1b4b7cb551c663c91280e83b11ef25bf488b88ac40420f00000000001976a914fde75662f6e3aaec042bf3670345e070286b6e5b88ac00000000010000000189f749d3a7c6ef70b549a3f7502bdeadc3ab0c98bf80b716d961bf3830150012010000006b483045022100fe8ed68b63c84e3a251f8717caeae551117f52187cf6d591f1d448ce68a57fa202206182a016acaa14438bb25a87345b55340098f4ed6d7833b51f1b2364ae2b328e012103cc3e7ded16d0d2a09b40faf7ba90da7ee77f71ed8086b6bb8d5a97ed287740a9ffffffff026e3b9401000000001976a914a4a2e8d23729124e04dd84b6abfeb53f1a3e22d588ac20a10700000000001976a914b5fdc18f8d82cf96dfea700f7a4dfc434a07fd1788ac00000000010000000129160bfe0aad2936e867fb84f49b484ce6d0c8831cfa88d214641acbbb0ed3a2000000006b483045022100fe49582a0906dc7ae9a167a67249ab22e54b91c4e08335e2924724afafb415df02201f557f540f20ecae35a06f40a1bea9ee7563bfb5f9738fe56bb2673e11690af5012102fab3570778a8b802b52d2fdb583f4d800bc0321b707a3b5b2e78cb4167da4e11ffffffff029df58001000000001976a914cfb467700301f9406e82047b49043907e9b9036b88ac20a10700000000001976a914b5fdc18f8d82cf96dfea700f7a4dfc434a07fd1788ac0000000001000000010fcf7953a6e8f88c430c24b630a61d5a49181afe2e767ce76119ade9fe07c231000000006a47304402201bdc04089297f83336c6e75db11eda95e417d816566f8b89a692f032127d771c02204cfd078b03f01916b5104c44e33067c38338c15e7f243c45e0c8204553f096c10121039b790ac2e936f461b9aa4c93ad40aaf715c800e35c7280f8f71ad222c4aec018ffffffff0253607101000000001976a9145ffd1ab701aaf92210c9125a3e8631505db4ce2088ac20a10700000000001976a914a54a4680732fb4ab0a5893690f8606cbe24fbc5588ac000000000100000001918ca179557f97ca12aadbfb9ba1c18dee32afac64e5ea292790b468c7edabde000000006a47304402206ecdde1cb4c048911c1f271e60edd7d2892181fe262b50b12cf55971e288ee3202205cdf0b06ed964700860c6c266004a9ee5199d80835145c6f153e78056996a1bf012102c28e3e4e6e25d5d7590f4a7ce318c32c0a98c380aab803873a9628c7e0d5c8f4ffffffff025b227000000000001976a9142baef9004079d373df20c22b0dfe40017433db0c88ac154d2800000000001976a914ec0e8c940a0d3c9a786268e2dcae2e3d641b632288ac000000000100000001d9fc92b02970e558021a15e5f6597ed3075a3972705db1990e6eb22d8f21d8fc010000006b483045022100828a4eac23959ec1574f060a7207161e49e4267807bc9744bcc190b5d8caf39a022007c8eb27f14b7f5670f5611a9e6f30a8a206763eccee0b7a5331d0ad552befb001210317f6cc241d8f0a0123378475e52267b6f6870f134b66e9135ecdbde79df4dce5ffffffff0240cc5705000000001976a914b29f68300e3e489fd301a7a8e01d25ea6f1320f588ac10270000000000001976a914f1c5145f86267d64199a22e2c420d022eb56575d88ac000000000100000002d04ddebd5d7076fb60a39b977208d67fefa431a91f47628304f913605c25a68c000000006a473044022050fd6cde2008b312900692a3928d4bcf1b7d150c22fc77598752a75b94d5dbc0022045715e024c59b5060e8246fbd0b9e86ff2408a6cf4b79695a457d131543afa87012103b5ebafa7223a8b4dcebf89d14fba39f5393ecd839e02e09c691180d29974795cffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934520000006b483045022100a2e0690726c6a8f45a3d4685676c81a43deae953e0a3069e8045cdebafa1fbfa02206327b98b5da80b494830b5654f6844878a7ea06161279fa879cab1d1b337f01d012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff02b5a20100000000001976a914665f2d69bd3aeff8581cf66e2764b849a63ab81f88accb770400000000001976a91414da5dde04bc5bf7358984bcb227ed431419ef4d88ac000000000100000001048f282287120e94e0a87bcb4c4c4c07c8c2f8f6e09ced38d861e0331789f14b000000006b48304502202fd11730a933bf595803f10b8c634aea441263bff45200fe29982ee663c89acd022100f51c76447481326553925c1becd5eb86c0fae7ec2fd2ebf7b93ab6ad86a9ce5501210234eece87d535b56c12df83f70e479fc409957df7929b0c4b08eabaacde1b0a82ffffffff0240420f00000000001976a914153a6b7f423b4e51c69039635260eedfe2b584e088ac307eae02000000001976a9145481ed9fdde635476f602b9c9816fe2ce6d5c28588ac000000000100000002e33ed9cab58586d309378728d5644c1913b919c7a1b8675a603f48d2df256631010000006a4730440220015a7513ef719c26f32c97ecde8d570ab3d2196add683430d64a8302dfec8bc002200d9a65d86419813df213a95070731412b6fb2615aab1a495c256046ee40f6a00012102073132dc08c82ec0b01dd90494b27311cc8062cdd543e1ccd25ea9a042e10e6cffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934100000006a47304402207d850910a0a6766f59338a4d9bf7c735a54c1ab0b6355bac08e668260a3e85f90220586e1043c5a68c7ffe35271cee0aee1b20272e6e722f18f0f5d532b4b027b73f012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff023aa00300000000001976a9146ac3dedcb939dd161cf1cbb79d476e54acf811fb88ac7e111400000000001976a914c3f5faffd3dc6f58e0accdc372e14beaa16e3d3088ac00000000010000000241504c537ef2c3e8a8e967d1c560bbadf257c6287fb248c99ea8c87d7d93973a000000006b48304502210094742457c5afa6e809e502e120bfdce94df70a84346916a1d3058c1ed722ca9202201cedf2acc9b309a20c2c4c6f5ebb3fdbae48a81002de7fc4379ef327af53db74012103f0260d1e5394ecaacbeffbd3081d48ad882a83dd1f6e2911a494af3760a879ceffffffffe0f8c952e4dfe597864519f34b292bb4b0094978b8f7bcbf0066d55b5198e934180000006a47304402200cfa32de673d5e505348b0132f09dd3932a0d6950d64a635726808bf27cd13f102200f0985fe3a11f4c0d80bdb8f58e7ed182b154f611162e11cae353778c875b6fa012103f9b986f00ef7410d06f50eaf324deb4513f83079ab7a42acdf470c12bb078adaffffffff02dac40300000000001976a914b18e3c27cda57acac294c7dcd0fa4af5f03a143888ac51401400000000001976a91479450defec6479b2f87fce094c215749528474b688ac00000000010000000272bd1b874963895687dcd5858f9e623c5274e662a95ddb5965cbdeae95028ec1000000006a473044022049f4841b790589647ad484b55e5c8c2c52be4900e492b7e38b1aa2532a76571202200ebaf2ea0a81eca80de1664d9a24c4f38471bebefba0308e380ceb4e872d6a980121022467cc3e4628dbb9ae6fefb534db67c6856adf85bd9843596260b16f1a394fe8ffffffffe90d2e51a4374b3975923f4f1acc37aff14288a40a4593f79742a382cb81465f590000006b483045022100c92e92776ac2884d6543faf2715f8914cc1950afc22e369a752e731fd6ae65ae022078234809f091f7ea344e54c3e77c6397275a1e7d50131991e3b36825648b5235012103f1b4d21e0b344248185408c1af96bdf05f8ee8921dfe400fb1873add6c6e1cadffffffff020dbf0300000000001976a914a642ba657b0dc95190d7067ff2539594f118d01988ac111b1400000000001976a914a2f8422e68f88d6e5fefd3c3c67b6ce8ed4f8e7788ac000000000100000001204a4dc8fed37cfa57e18b042563b311ebe380e6961c65babd7f3c782d0df3fa000000006b483045022100bdb2b7f8c4391a3567d5d5723188d5f3e385aed4c0681e88a0565fa49401ff5f02202153542de0c016fc041cf0078186ce0014997b0134a031d2c4756fb0e1118f4c012103b9d19bda4f288d75fd43add98605285948554aa94c45a3a5e011ad2ffcae07c0ffffffff0240420f00000000001976a91446840e50aaf1b883089b042e4273fbb5ce2b9c4f88acfce4f101000000001976a91454cb919bcc7807e505b1e4ccad4117c4fadc316c88ac000000000100000001e9acf2718a51f0342117707862a54d4e88dc4687602f10c10c7349a7f0776358010000006b483045022100e80e54812e6f2861bfbf1ac81e7bf3ee6dadfc29a66a90ebaf8a5697e030e4be02203be9e88ddf7c60a350468fc45cdd6bd0b282520720f9c4e920e61e7c6d0509ac012102736a6327cc55da03a66dc9191f05f75f79aeb478a45690f5073f3fa6d25f8df2ffffffff029ce2c201000000001976a9146de811dd2dcf0b5bb946a9674a55a65e97fbe36388ac40420f00000000001976a914a27ce59535bf7df916438477091db364acf7497b88ac000000000100000001cdf2b8d3a53caf43e95573519e367727afaa6b3ca56b81f488a672fe3530dfa9000000006b483045022100c17f5d83727c41cc5e1d6f344543d3608af8e0e601d9e58a6662471bf6691b6d02206ab1fc3af73117c68e3a45298f5eca826abdffc3bd2e45051518e35e2800c14e01210206b70a4bc82d2440036bd60dbac15f86d37c7371d59b7b319bd6070808bde174ffffffff010e4223000000000017a91420a28c44599f373d0634cfc252a687aaf173921e87000000000100000001d1ee1f739f2fb1858c4f3718a6d921e907b4f5e297d824f154ee439c4e928f20000000006a473044022067fbda22575e4350242bc38ad56b96c2f9e218110ff43b5f9ed91cd3b9d97ff5022052ff6569053c08c28db9380abe0454e0210c0b97d86ddb8c1fed6543011c5bc3012103bd9ec3c65e09ab45889e362be3272c407c12c643b3eae3b2ac811d7ad30a1025ffffffff0140420f00000000001976a914276c786a1b5b683e67ca92a7d9f80ddf7a65c90c88ac0000000001000000013fd68ae235d1348d4f53445593744a8983dfdda5381ef8f38597097a27a7ade4010000006a47304402200c4160c7a8c145ac41502326071938cc6c5a4b1504f22c6e0687f7ce7686ef3b022014aea6e911fe99eb7c45b8f3408666713c768987e4605e5e0ce14d75b79e5d1c012103addc0d975ec1447c0dd7c0c5eccb3e090b4da22ffea512b1dd1e52ae45491deeffffffff0271991900000000001976a914b9c5b763d69ac9423bde2514c9c84ea206d45a5188ac7ea10200000000001976a9140df5cdbd67f672f755e2610ce35b574f630f0d0188ac0000000001000000014967704cecca8b905658a6049da6e289b6a1ab3b801766d2ec36378257636f3c010000006a473044022010b961d5a7777595b5237eaf85ded87fa59fad12ee150b35143f9edd9b6097de02204af3dd8d2510bfc7e52cca75612054df417e6754f3b16c023551de5680438fc6012103b403c42ab014e8f24bd8f87bdfa685f3a18efb61a9c1a29992feb259d6b22c5bffffffff0230c80700000000001976a914062ba22c85d9460833e8ffb67d4119c95428136688acdc997d05000000001976a91414c402f2c30b8d80c3719d090f7f8a2316f103e688ac000000000100000001deb965b7a5839de16d9c0142838205b623cc3b8bd7e3cb4b1851330bca7150b4010000006a47304402207120969cc910ef2dade86d1a705e1b7bde0de5837879b4805acdf9226e910530022027807755fbecc227f50613559a528addd58f6c9e9c1ea73b6b4e2ebcce0e0a99012102666ffbffcd2c9987e267dc625cb91bdede1636b2bb744dfb0b5e93e11703d77cffffffff0240420f00000000001976a914145ea67c48eb66b0156b13d2305034e27163f53388ac08c43601000000001976a914f3972c052c64a5b3f05b02c4acad74be76c908c588ac000000000100000001294947ed1ef282bb674293138130056dfb4078fa0b82d34ec1e3dabb88fa7430000000006b483045022100957129e2616f81701f469e80bc264076ffcf7ed6ce1f69c736580c1404095b1f0220549d845704eb8eef6f1a0e77b14d34319b930a82aa7c4320ab48a347e2599254012103eb6cbbcf67da9fd27ebe2ef28b94b0ed0e9006f13a3d4b5948ecdc7c2c8d6d60ffffffff026b9e7b01000000001976a914e5ab2575d010b9648f1a54c9cd8f3fb75785728488ac40420f00000000001976a914dd903bb6103c60287e830caaa39295240e90528e88ac000000000100000001a409a263f66a4d28c5be76d8221db433c1de8616555b1847c446eecfcac37ef8010000006b483045022100ba4592ef246a1eccc09a5c7c8a2468e56dea93561661feaa1303fce25c18eb6702206ed78d93cf92fc9315460a0c059ea60f4d3fdf2755773716b80cab5bb44ad170012103f7f2362d11e2b082387b6802a773adfa3c252cb21485c85f737e7cf62e95249affffffff02905f0100000000001976a914a55e832067cdd5c1e1e59288afd7b2b0f59be37a88ac30629500000000001976a914efe4eebd29f5b7a86492584993e05fac65ed6f5688ac000000000100000001e84d537ccdddbe145c8f872abeb9461067426cb6b6eff86a3859b18bb9d05d5f000000006c493046022100810396dbef50bf434b1ab80feb0b2d46f4180da583302f1324de61e5c25a0d9d0221009f84dd37b27f6663937088034a2b726ba6effc107396653270849a91ab44a632012103166654d5cbdc31d65b81e34cf0631a5a0a29019841b20cbf9ea599e049e5f0b2ffffffff02b8a51000000000001976a914630d1618b712c0404d6068985b80f6c1cf86cbd888ac28230000000000001976a91490fb956fb515c4a1695851d083b92142426d13b488ac000000000100000001d7134cb635b71a79f0b9cfdcac9ef9d6832115864c04683b7c577574c168507f010000008c493046022100a63d1a3a217243f50487f1a86c0217676bf846be2fa59bdee6f5f81dd966a143022100a331c9734918d427167514628a84e0d4d9c2899a3e4288ba188ae78abf1530e40141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02204e00000000000017a91422c4270d877177c1dd33037fb5b7bb3fd76cbb8b8761edc8a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001d0d7fa24b502e8a08472047696daf53d08807494734fd8459b67eeebfe13f63b010000008b483045022100ac7686b9768ef38d86123e7416ec05558795b2aa1603087f364a433bf9515e4002204656b488276b84cd2c05fdc775b3daf890d4d69d5ce62383580e41b78a6d3bcf0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02204e00000000000017a91422c4270d877177c1dd33037fb5b7bb3fd76cbb8b873178c8a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001e1798c22f27e5ba948fe9c5e4de74046191b8723309063ef60f8198bdf67930c010000008b483045022022d734a8eeffdaebd54f42586679e75b428c6f4f230d3f587d3f9019ac25f4350221008efe331e7b69696bab645ac85c305b9f9d2b35419e59c2d0dc89ab12f4b49daf0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a9146f743423659b1a2b5b52bd3ffaa2f6c30c9b6cc288ac81cac6a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000019347ffb287b7205e817f096b0d4d424dc54bcfd9cf7412b289090cdc16d78eda010000008b483045022100c5537059373bc3cd04bf559970dc25d83bb4338c2e0f2b321b412873ae0b64430220343358577015b688515b4f950f2d5f8ea4053615558e11552929a0552a4bc3210141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914ce5da921afe91686f53affbea099dfc7626a64e688acd11cc5a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001d66a7959dbbcdd5a0b2e089008452227074013fa4030ca947ee74d54f06f2490010000008c49304602210087a40aa7da76d295a133c570757b42aa015b212b548556af3e2f3fb196ccc450022100d14bb406b48694d7da93bb8eb551a5ac9dcfe9bf744d1760b0b3b2c482a2c7960141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a91409154d402a0cf4cf0ccb474e806671409604c13388ac216fc3a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001416c037d85e6f29c3a60509aedc44bffb6ebfa433b6b17629f1f449f6a8a420b010000008b48304502200f5a1836326e868e3489564ff3ec1ceab1f2b1c833a116a5080fb3af4b1e2880022100bfb66b1f5ee9aa74b453fb06d1241653986bc754368fdd125ce1c0867622aefc0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914a602986d920c4f2e95d9a0711eed72883181e22888ac71c1c1a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac00000000010000000109a635ac99aaa207c92dca0903b6db615f1f2e612286f6af75cd7c9558d608b5010000008c493046022100ac53716a177736b1144ca67722e94ca94bdc5645701908eae0d3f142b4530858022100975419f603798b46555ca0d996f4ddbdaabe689d81c7a274af54c823dbbb9b2c0141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a9146ec46e89f8dcce3e4e714b82b64ca8f5fab551af88acc113c0a80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac00000000010000000175e44081f8cf0b6bb3cd1a93946f54484ab1bed18acd94217bd1d94e6408a640010000008c493046022100fdd199b1b921c272db35ec2ca3bf3e555885d0f9917855be169d2520bb9127eb022100fbf87fc8dbb685079a3c7f09c5cfe57e4c4cd9c574f6c6b03080942b3bdd54d60141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a91468e6a1c0f63f1d62c964da9aa16c393f9198bb7288ac1166bea80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac000000000100000001280e2a0c3b17dbac7ded07a5e029cb84c4db6ecaeda742bc241f024b6e757ad8010000008a473044022022cea724cd06f841d78522bd936724b89bf3f3e0340bf4820a619612d45e2f4102201a55dfceeecb157ec1c36565322ce2f7ebb512de7cb9c85d89842a186a55ddc20141040cfa3dfb357bdff37c8748c7771e173453da5d7caa32972ab2f5c888fff5bbaeb5fc812b473bf808206930fade81ef4e373e60039886b51022ce68902d96ef70ffffffff02a0860100000000001976a914afb415d51ee8df29280c362356aacf8198cf1e7488ac61b8bca80a0000001976a91461b469ada61f37c620010912a9d5d56646015f1688ac0000000001000000017a0c07421a1e3cf12aa73cc193154a2095079b82bb3edf80eb4c32aa2ebcb119000000006a47304402206845ebe0b479193a19cc2360845440680e3258933b03c54019589891840761a2022032366f67ba41bdfe00b41fe95ffc371b2d1146f6de6a0cdb011ebb787a09651d0121024f74d640e1f02b40e9bcba20e72fc8c0d7842429d42dfa16fcb52fd15f3284bcffffffff03781e0000000000001976a9143bb7cb7944b25a8a7b394078311fee351efb170188ac781e000000000000475121024f74d640e1f02b40e9bcba20e72fc8c0d7842429d42dfa16fcb52fd15f3284bc211c434e545250525459000000000053d3fedcce940100000000000000050000000052ae805fc901000000001976a914393971070071930ccd8d1248cf8c7de28cbfdea588ac00000000010000000100e2a34591dad16e1de7259cceee8ea6497b1f3fdf26e412fcf15e70dcd03407020000006a473044022058b96022f23049c347d4bc8f371fff98d768dbb1a43d5becf03a85ff16a6675102204113231cc11471246af502c79686b248dbee3eb392654693caac740382d4fe320121024f74d640e1f02b40e9bcba20e72fc8c0d7842429d42dfa16fcb52fd15f3284bcffffffff0290d00300000000001976a9143bb7cb7944b25a8a7b394078311fee351efb170188ace067c501000000001976a914393971070071930ccd8d1248cf8c7de28cbfdea588ac000000000100000001a50c3619d797f67d512435740d826c651d547d76046cd9415b1bd1d5cd326fb4000000006b48304502210098a9cce4ec26f205d9f0251763abd449c302b9eafcd3b4fae414ab8094ae79220220064f7f6739266e8c19746b063a5d8886a84b41ed76a677bcfed389f6e26f9ba8012103f7f2362d11e2b082387b6802a773adfa3c252cb21485c85f737e7cf62e95249affffffff03781e0000000000001976a914a55e832067cdd5c1e1e59288afd7b2b0f59be37a88ac781e00000000000047512103f7f2362d11e2b082387b6802a773adfa3c252cb21485c85f737e7cf62e95249a211c434e5452505254590000000000000000765c056c0000000005f5e1000000000052ae80329800000000001976a914efe4eebd29f5b7a86492584993e05fac65ed6f5688ac0000000001000000012e286507e0ee9e50dbb9994c0b901122e064797f5399204409ae89f5a2e2d797010000006b483045022100b0eae1db5ada154a646e8b651c554978435f74de527cfb11225e11fc5c05d193022037842a670b6a7428f9b876d61341c3fd8a5f9383eba34e2167cca9d9933c34f20121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffff03781e0000000000001976a914b59e93e7dad549bc109245ca27935d06625d703588ac781e000000000000475121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5b211c434e5452505254590000000000000000765c056c0000000005f5e1000000000052ae4021a913000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac000000000100000001d0e4440e74cd457503e07431bbbc8759cfffd261b610ecc9c9bde6d11dfe121b020000006b48304502207132240141ce3483cd13a8061ee5b9ec3bc722f195f55238e20f22ee83956ebf022100ee7a635ac62a824a95dd6e5b60f2295dbe1217f44a9cf5d25aab1bca13d1d00f0121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffff02905f0100000000001976a914b59e93e7dad549bc109245ca27935d06625d703588aca09aa713000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac0000000001000000012e286507e0ee9e50dbb9994c0b901122e064797f5399204409ae89f5a2e2d797000000006c493046022100ec31bde14bae7ccfc6cf01bef40c91914c5d5adbb1b3ea8de7a5dbf1603666fe022100ab36b5fed2db1ec66a1b34fa9b246dfb6322e09245eaebaf2058ff138632d75d0121030787462dda0f7a77337453482ad9789b6cec5993cbb4275255b731f7dfda3281ffffffff03781e0000000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac781e000000000000475121030787462dda0f7a77337453482ad9789b6cec5993cbb4275255b731f7dfda3281211c434e5452505254590000000000f0a6c9ae7ec80100000000000000040000000052aea0220100000000001976a914d80cb565a29d5b5c56595b7f1cd332abad7be5e888ac000000000100000001c3e7aafba938dd71b9819421a490e9c12fbe1841297ec92ea24ab94dea929a06010000006c493046022100c1f207f20c2f29175d6cdb5ffb1fa9654cf499cbaa9a316909341e7fa427321c022100f642360759d1e90a5ddf48901de6f9bd7627ebbd7ea9cb422a0944eaf4483a8b012102754dc3568d9ae70e8fcfcff6508b9e70119b1b17ab3090f7335c46d15fb15779ffffffff03781e00000000000047512102754dc3568d9ae70e8fcfcff6508b9e70119b1b17ab3090f7335c46d15fb157792120434e545250525459000000140000001d4c4105ae00005af3107a40000100000052ae781e00000000000047512102754dc3568d9ae70e8fcfcff6508b9e70119b1b17ab3090f7335c46d15fb1577921120000000000000b50726976617465206b2031000000000000000000000000000052aef0822607000000001976a9140d7ec8cf14b0ea73621e9c831338a7921653b6cd88ac000000000100000001b6f0d9b6197830a4c06df6f4467fb99e87f63bfcea55030dc55863d11758276f00000000fd1b0100483045022100e2a0104a7ac38bec2f6a1fd82402d55272e2355d2456dc5a2dd29dbb50dd98ea02205153cf827bdf8a9304cc14ef40cff099b0ebc4d85a118883d9af54ed0ce4fad3014730440220108371557cb344c7725132d04ebaeeb4c6f7e878051068698a92452970d2b44302204a4497706bf6bd791b63753ad3ad5b326781557a96ad7202b91a011265637809014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a9141f29cee3509e8268d9500b9b23cc85da1c3b4e5688ac000000000100000002e6c757e91edc7c913416e2306f8cdb3d2e21c49d8f8623e0d92823b9819cbc98000000006a4730440220211e50bb3111449f331f296e2f56558f53a92762ee743d7cbc35b54a5bde2ef1022012783fef6ff4f8586fefe6c4d2bd6f63cdca175332908d494a2db0e10c02f8f60121039c07e5148dbacdf029b3a0fbfc67351b60cc0a347ab4be269a1504318bd26267ffffffff4179331d95be875383f3fcc8d6c280a455064d64328fedf03d1930d4e581e756260000006b48304502210086de5a7d428ba954357f7888c43614533609d5f3a6810ee9395b673f911d88ee022027f6fcd1ffaea6461ef2825c4943f2bc29eada728ddf9751f4dbdd90838e1a6b012103f909a8dc00e2085dabd71213674c090ab0405b903993218eec92bc7c80dfdc54ffffffff02c23b0200000000001976a9147558fc21c84a2aeb097c82489afbc3294608247688ac7fb00600000000001976a914d6f9e26b826174e180fd3f57ce7fecf10a74c7a288ac00000000010000000229e0a2630e8d0070e35f90fc61b77c5d1a67fd3cf7fa870ffeae8e3a4c202f46010000006a473044022031c22aed1b4f58aacfab2a4af552b2d74bae8042d03845e2cf56867748d962a402202cdf57d88dc679461128fec07cd7dc91ca7a87d6e578c65ac817ed30c44841780121030f25bb21ff95d1cf698f0c8b300258d41c64063a2ca81ed68ffdadda803c4bd9ffffffffe90d2e51a4374b3975923f4f1acc37aff14288a40a4593f79742a382cb81465f0a0000006b483045022100901e7427547385db1156b1d823042b88ee8a89e44b92796e9f8587c529cd14c002204786800018ef9c0c39079e08a768fbbe01b02568ea3bbd4c8b670b41b5310c40012103f1b4d21e0b344248185408c1af96bdf05f8ee8921dfe400fb1873add6c6e1cadffffffff027fb00600000000001976a91495267bdeedeb4a5974f46b3dea5f21f81eaa610d88ac51070200000000001976a914311a5aefdb4085af5853d880c8390ea6a1615b4588ac0000000001000000018f631fa01a6797c8fb406b01d2f2dfb9c4c3e2928e77048bce829b0e287c87be00000000fd1c0100483045022100dfd6c4112bbe482a960cffaa6bc534139fc8c035b482610faee2a1ce372728520220257d43ba08c16122601281aea02f2afd501ce67ada619c30583881982483957d01483045022100e0cdfbf616cdffc5a580cd802899daf0cff68a16d7637ae9b80f18b2c46cc8d402203dc2679a41469ef89d037d6bcbc0be5c0fe73de7c3e3c268e2b47aa6ce54ef63014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a9146fbccef8c5e7553abc4cffa28baf0a604262248488ac0000000001000000024fb19d79fb214b86b0fb0a6577c4250b2cba5358a14d0afd7acfdd7b2b055bfa000000006a47304402204bfc5e7e31f233a2d2ec9e3a72b40f05788ced58783c8f49ba341be427229aae02204ce55d0cceeee999e788fccff011514ac4dc3b776a68a687b84cfb9c5feda1c0012102b5a5c192d56c23f98a057b8f78caaca88363030e50de36452db77d012cf8f3c0ffffffffba76e2bd51af115649e9ee6dee6e89114ab8526749e0a977214aef78c07b090f000000006a47304402202f41bae81d296b5280a25874e5ef6c8dca05a749ed90082ea77aab824a837a06022055f73d4a2c7aad0b90e27d324d9ca91791a79e76552773cacf882a85d9f31af201210282d40b989ce5de98c89e3b240cee339bbee4a086c8520839a4bf3c11b8a18d23ffffffff0240420f00000000001976a9149b99fea987e53dbaea95816444d90975733b3d6e88ac399f1600000000001976a914b48cb92c7c89edf19643d54989cb54608bfaa4e488ac00000000010000000241d4b068fbc46dab6de7b3c6dc3b5b770b37b981b993d06b4333b8a643f10d7f000000006a473044022100bb259144bf62fa224757ae18877b27865dc27ff61d04b76315040666386dadea021f2d66e54f5a6856a2c91a71f05952650c9d5ae92ad29c5896a8963f629fc32201210321182abbb202e124e62864a66396d34d2492bbd08edf6686382dcc6e04d85b45fffffffffc68edf481ba98601dada97a71d6cf7583a36e1e135f082de6f0f7d0eda70b1b010000006a473044022034ee4482a0ce32c87644081dd423c80840081ebd47a0359e5fa29a717b658da10220018401a23671472033d4b963cfafd2c9c333e319baa676b79ced8bd1e362657d0121025313094076052c8ba1884cf800018d1e9371b6ba6819bfc5f1c66a2566d6c433ffffffff0240420f00000000001976a9144546c0d280d292c8fd2bbee8419ef05fb4e385fa88aca02c1000000000001976a91412eaf230e8991ce934e3050d5c6006c050d7b82a88ac000000000100000002ed9934a2a3b72442fd5e1946ebfc9f230bbc8012722ab828e3f028ed76e8c9d8010000006a47304402200614fe73c43fe79d0bfc793e7887d0a4e5507386199e42182b27fd6cfac49f6f0220759322264b80d45ec676dd7225d0d27ed413e0e049744dbcc76d3fd998f54c7b0121029a6793c92d2859d20204ba1738af4aaaefff1113d95668d0fdf1fc268c17a31effffffffc45fde728eec6c6e103e371d36c91a0d743b44557aeaec1b68d485db2223d0a3010000006a473044022020b63c7729c84a7e800ab94fa1c4a05fd15ec2e93da3089193b09f681d54123f02207192509619af256b9f81fe1205ac57d65c98306cc862eb4bb7e0722f2c0aa4e80121037f6f4e51362069dd7380bb1ac09dd430e85bd796d3e41eb88e4058414e5e94b1ffffffff0250da1100000000001976a9142de3d38ba5d6ef95cd1ec2fd025b4873b779fbb888ac40420f00000000001976a914e4d16ca2b60dc603ae9590672e05cfabed2d7cf588ac0000000001000000025559373139aeb2fa33fcf78ba3e571663b20486cfb7d896528f97091975c5605010000006b483045022100afeae347a631c6cf353f75f4898126845d94c45702ee72ebac3928f1a1403e350220038a6dd69dd4d0ae2438aff188e2b05fd108f79b247e6429f1e5ab6b304d322801210208e3d272bc6b00638268d8b192e20fa56415e4522c567ed84ffca8ad2b004bbbffffffff38161aa5e73aaa5bcaa96dbc120fd8019710542c1015ac7fe5067459492c07da010000006a4730440220381e5b8979b34e0ea5aaf5083b5f84090c7a39786a2a774a38b37bdb7387627d0220622787d68e383296cc4c1953f83670881fd64ce478e8a03b5b27e95fb505aa47012102f6052c249e6de26e1f27d36c795ffb17d47d3210cd4ee103c1586f60b2fcdc61ffffffff0240420f00000000001976a914ceb65b1b5b7d14f8ed093cfd7213a8855c7bf94888ac50bc1600000000001976a914aae22d7d8641a83d5c2420ff7c2082a2908d5fb388ac000000000100000002ba0b4f41996673c775527a6cafe9eab0bd4554e8be55d12048837e53f79e5f6b010000006b4830450221009fe8275ddf2dbbeae2a693b4d4468b166611b5ee94c8fe306cb7a54e527bde9302207278a8b5f74dc7ba662040369e397bc41f6651bf6c9f0a0a8dd2e9b0918f6a5501210332c45c0a00234618027fa964a5be59f690bd860cc23c49fefb75d0fd8a2073a9ffffffffba76e2bd51af115649e9ee6dee6e89114ab8526749e0a977214aef78c07b090f010000006a47304402203eac533197a2658d16313906ff9728753f3fdcb0f5aaa2ea30ce813372ba5571022076bb0965ae283df0de9cc5b025aa0f5429051cfcb4108095b7985f3898766de5012102841ff04be8832522f6cda876a21165c6f751df17d11c604ac8ed4b51292d572effffffff02c0c11600000000001976a914c24947532dc8bcb742419c25dd73d25e429b01ba88ac20a10700000000001976a914a54a4680732fb4ab0a5893690f8606cbe24fbc5588ac00000000010000000213dfa0edbf17bea78fa7c124f6f54fad33fc5274892ba95a653d6fe436861313000000006b483045022100dcfd73f39e2261f22fd1a240f55a52cbfa22a6fbb74bbd49abbeb0421a99fd3d0220557e3890c9db89880c5714c88b470f54d59ad986b4cfb93491ead3322101d7ba0121035dba8a054a8ea2d65977fcff2c0c5b875c32a3efcfc8fe01934e2563e0210fc0ffffffffd1ee1f739f2fb1858c4f3718a6d921e907b4f5e297d824f154ee439c4e928f20010000006a473044022060cabd516735ab67636fa62eb3928f3797ad168f8baa713070242d1b0492036102206047245ce7a60bd0310aefd3f49862baaf1dc59cd71a19e5e9716b8611cddee50121020b3a10541b08f1b637f077ff6f2932b0bfb11829ecc8bcff39728ad47327d9e6ffffffff0250bc1600000000001976a914f01d73bde764f82a6bd101d4f361e4a1e789853f88ac20a10700000000001976a914ceb65b1b5b7d14f8ed093cfd7213a8855c7bf94888ac0000000001000000029b6b975a0b360bdd9b5bd4b9a80f73bc2f11b5a637aa2dd8c1bff635795d3fe5000000006a47304402201be59c88a100439f3546508721f189b8d1b6b7e335bb8accdcd5a66729f1f0fa02201e0793e95f44dc1d90af05ad5ce477aa26cb324f58a0e15f5bbd858ded38d037012102b76651433e6c776213507d5c0c83e6a98a6fa6779ddddf78dfbb152fb510f4c2ffffffff81ea989407217d690564d59d9e6d82303842df3a56d0703fc96d39cc563e4ab8010000006b483045022100ace96baaebee4bb4f2365c73bf87ab66c880a725b842d97d17afbc4d63650a42022069850a754204808e5218d94029a458ba41690cb6a32f189f958bcadf35bd39de012103b9a14855a4c79bcd753274b6d8e00c42a20fd584a6d86c48990e032d7a594eeeffffffff0240420f00000000001976a9143740f53b25dd242cf19eb0bb518e4d84633e2b5488acf0601300000000001976a914ec6f79c75930c650ec0a2b018d8d6535dd5c0b1c88ac000000000100000002f18f2189eb6623167a70bb99325b934d9affafe83b6c9cb4cdc93ad8bb9ec2cf010000006b48304502210086340c01a8dd74b983b54f61edd0d8e0c1631877afb9a17dd60174a3d96b1c4102203b3ca5a9c24c7004c18c0844745e38f92f45211484e7d6a177c7f6a844bebbef01210255d19ddbad95e5c05687c5a6a1e6a63fe9f474877601a2339543d372857ecb22ffffffff5397951f23d44e270d3a40178f0beed922c876bd2007bad9d6620d4a451ac7b4000000006a4730440220017efb6435e7bcde15b846727067675a4c3fa046e0bfea1c9acc543a499e848a02203a7d765e9d8a815f6cb2a15cbe2c6fbd3b65ca35d8f2ffd9a1302dd2081fce46012103c6661178c98e7e9d301a0c6939bdc62da7e0f0dad8afa34f1a1d11fad4b546e8ffffffff0240420f00000000001976a9142709334a6bb7493012424df2712989501b87099688acd0a11000000000001976a914a9d84f0654b8131196be4982d9d6e88d4253872388ac0000000001000000027ecc038b50f20ee0f3032fc7b5caa7740339e7851abcb64b810e3a26cbe676e1010000006a473044022075a9dc1c3d8a5a2a8f1edd1e30088533acad905b83bead4d585ed124fd52f679022074a84fdd5a1e0365d6df9dde0fc18c4a681518bc1aed29a9ce42b155edbbe8870121039e060af0c8c0b3af4a4a980bcc19e4c277565705fbe0dbe648b3235cd0201f5fffffffff25d03e44e8983694a421e551facd631ec6b8f7cfcaccf8eda8fba1762db006d0000000006b483045022100b42c8a06c066a827869343d374c9520b7b7c84e11e75aa68df3d42e5b52865c402203b1aeaf33c7c3b0012fde9910ef811ef243bd5f103a7157519369e160c208b0b012102843a5d13cb9b19441ce8e871d39f821c2dba0599be5066e24ccbb22379676ecaffffffff0250da1100000000001976a91497cc9c5302fbcfd8b8d4cf98539499674fb1bba088ac40420f00000000001976a914f34c5ac23e9f429f127c2c2df80d47df470359dc88ac000000000100000002fe61b8289c169cb90feb37e8b00210618764f090798aa3ff606e21832adbb6b2000000006b483045022100e750d15b7673525643f26ffb6ab014616ffb40eca397c96a4767df625ea1a3a40220141282de969b92e3c06301637b0eb5c7f2088b4c6327c562055230ca355407450121038f88fc8b8b0a05b19aa975e51a4e5744b629dbe789918ef2d7c7b4cbf4e53872ffffffffb6e58f2a4270845a5756bb90e4fae997715217143618f7b8e3f494545e2d0cab010000006a4730440220536361adf93c952b14462f4afdf498ed9cb280c84f6ae329bb7f9bd56155cb7c022015397abeec9088b7f2f68c6822a9ba868016deb47cc13143599af92d23f9399501210307c106b32cb3429706df12b46aab8bbea8299ba86f19c8ac047f729ea0712b8cffffffff0240b31100000000001976a91481d09c73c10beee8414d8e6b2f46d219db9ed9b488ac40420f00000000001976a9146abafcc33f777900180fe642440b781f47bd9be888ac000000000100000002deddeb747d0f9a92dc10c981aa473d6b45dcf65bd6f6ca45971f83a7c1ebedcf000000006b483045022100c8d09b2f037c33ee2c25b4c7623cb354af6c134147fa5b2680792b2728cf7b6302202ccf5c97c0b220ba01519eb307e17d76ac788f199670ce8b155ec2f52325d8f201210345004779de4fcb7677310a210849beb5118a149af6607b65e0e41cff77d5d2c3ffffffffd60177b4e5e63c9c466b628c4e9033590b25f23f0d83a933f4935c30dfa7a533000000006a47304402206ad7dfc4e7eb9c8f988c77162e67f296f9e07e9ffbbcf2bb9cdbf5157210586202202ce81877bdcf6106ceff9e9e02b7ed2936583770a2ea690b075d576ef7b198940121024bba329b6047900251f9eb90f54c1a14960a8efc3cf4fee620c5c6e2b9633e2cffffffff02308c1100000000001976a9140621ba5066df4ef72087082967f8f0488545674588ac006a1800000000001976a914f339a2e7e2adb584f07415b84ab7268226e340f688ac000000000100000002d22091c47e75c4b4c113f3c5e334a19cda256809955bd49f416bc95a0d6b8303000000006b483045022100be8bf43aad3d0011ce275ac230e550466235cad09ef5c741e7bfd46ce43dc2560220011ac7da44c43db4a12e7351d7924ddb3ef1fc70bcf521c19c6c0401fb520824012102dff4ded472831dce0e4870cc6546b726a7659fb8ddd93b3abba82e96bef94d6affffffffd22091c47e75c4b4c113f3c5e334a19cda256809955bd49f416bc95a0d6b8303010000006b483045022100ad5f38fc133c9d7a222ff37c4e244b06d22f923e04daad84de20227759aba27002200193f056a202bd224ebad0683f798c234c4f938cd2e515c95428415762595a16012103d5b11e96151847cbe29062265f92d29efbd62c1ad06a2b3b5e478f7282c42cc7ffffffff0240420f00000000001976a9145af5e14c39eb6f41f873e2f14c44a8cb8f0bfd2f88acd0d61c00000000001976a9147b807912b8099aaa2876c921ee48d57d4184561288ac000000000100000002d8fb1ade0575c53bde1c1caff080ed3af2de1da95ac078dd309e77ccf8e55fdf000000006b483045022100e2eb212aa293d02e8547cb1a763e4f798d667fde3997e5876456debb876ce15e02205db9df52ed1185f0e925a594ef315006fc40317b133348ac6572d71a66fef41b0121030249f823cdfcfc2bdc72ca2f75c04c739af3c5c22cbdc03866739d30b0f968beffffffff5faa04057e21f08df130728030585623b316081908175cbabd9a668bda6bd82a000000006b483045022100f0bf6b0ed5e82a14739a62dbe5c2273ae7e268856f8fe99eec2167032b172e97022046a52017f2cb376fd5dd50301a876876ed2e5123682185e4302740efd126d83c0121024146e08c6d6d6ea2c0270c3277d831259de638d27b5be4e265f3ed6305091d4fffffffff0270281200000000001976a914319d0899408cb8a79e5772d7f113fbe25992dbba88ac40420f00000000001976a914c7290c0a5688926f1e30a23b2cf0ec2b4e456a3188ac00000000010000000266e3d4ba8989e71decb07927e8ef373ee8b6c205496eee0e838a9b6cb1753b83000000006b483045022100f314e2775e6d220b492b6fe35515291b041743b46d0e3b3307cc95167bbb6b010220325ed68888406e1bd7e1e5ccfd592bdee4486307f22c3e382d48d6b831ccbddd012103c7f26643421de501c0a1fa9766a2693e153f29df258cb813ba62864fc956dd99ffffffff20c40362cc8e89914af42a74bdff742686efc0ac3479bf4b431d1e6a587a40b8010000006b483045022100cd934bc91a5c48fb71b8607bd7026f99602041dac596ff9086d3553ce49a88af022050e5b94aa701edaa055da221e64dc09d49b3374c094d1fe0d835d93737d669e60121023ba5487e3278e4abdd44b1ee011124909577480de2eff99c1c889cd84ae8f31effffffff0240061900000000001976a9147ca41603764079bf62c4a4e3f8f66baa669d66fc88ac40420f00000000001976a9149f3f25b06eda2318869d40c6af69eba67137221b88ac00000000010000000250be20e7e9f25515c6af564c41ba49b3ff95fa476768801a943ee0bcc8eed476000000006b483045022100d6b8121fa93efe013daecfa6804603758a35931fbbd6435c9fba43c093f3d16902200744263cbe0975fc0eeb2b7d08363fd3f6e62a072dda7a7e9bab56bd1e620b9f0121028d83f9b1491994790ec76ef598c8ba0436183b466b32bddabb1a462c70e29ea6ffffffff40a16cc443545b1ae809148704511609154d4a9d8dbe31dcd54bdee791eecf7f000000006b48304502205d0911a8ac32ed04d602111ef5e664e2bd146c9d7fad88f6388ff008664f7682022100982367a9b3f72d178ed2e7c2cba3ebb51386d6b2ce8df92b797d3767984ebb7a01210325f1f40c9bd38dcdaabe19034c59caf578a8457ca0d6ab88be63f583d2ff1559ffffffff0200e9a435000000001976a914df073d4a974f8134a21608e19dddbb6cf5bfad2f88acf0b9f505000000001976a9143df52b2dc885ba0bc17ccd77cd2146d7bb52f06c88ac000000000100000001be200e8543914878d90b147f5a43bfc3828af510a043a4e0512293df5ea4ad60000000006a4730440220126b0742ea548b8ae80c708f2978563bf0aad9bd44056b585a4d7dc4e7d05bc902205ad040b48dd756f409b921f6bb0e58ea9913215b26b8e1a90a3fd10026810e840121037dcd7819ce02547f96a93deaf7ea5c6244d79f13c21a855be672eb28af964c16ffffffff02f07be111000000001976a914c0ee975d1a949a6bb1161611dcdba5a13b798f8088ac0046c323000000001976a91467e955c5c4a7489f549bd401f003fdc44d0dd93b88ac000000000100000001387daacc7861110fc52535e8a9733d40fb9c56b281708993e1678c79ed927677010000006b483045022018ee4a970e5410210c715e127d289427dca1ab3fae4d06ecaac1c86887fb0132022100cffa070278a90cb98eed8f0bcc18972153073c9360f6ea1b6e4bc7b65917105d01210387a2e1288a74d1b6824bedf9058a788fc84be8bf549f2f091f4618ed95e23d7dffffffff01f01ec323000000001976a9140744f9fd5a3b73a656694171412182a32f2971b788ac0000000001000000025960889040faa5f580fa8ef80023163878bc3be210f6d0684fcebf5f7885fb62000000006b483045022100c3065feaa69ae01789dc2926205bacabc1687df6c0eda39c13f605ad25970b55022012c5e38ba14efa243ed043d9f984707b301efaf2ef84471059b76a45cc3526a90121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffffe22128623c5c237568cec96afa71054779a91aa5737d1f915e10ddf563c4f662010000006c493046022100e598d9f46a70537e27c04bd6bcf5f69e3a50fc638bbf6de3e7cbe00ad707160a022100b15daa637999657d26bd5fe6ca87cc3fd5de9948f2fb8d3167ac2a377a049c520121035b7c88183988d374c281c222a6bf0dbcb19dc4dcb946f36fb097efeffd62fc5bffffffff0240420f00000000001976a914d43ced8367edd29adc6c364fe31b3651046e287788acc84f9813000000001976a9142bc7076ced236341c9baef7bd618e7bbfa87675f88ac00000000010000000298dd0309ce613a9fcf77ec93e5a00202dc91dc383a2f8676e07188df62c50277000000006b483045022100a6559c0df204031a8d9cb2ba05361b4f370887b2558dff3f01e69511e2f82fe002205961193e15f0b1257686ea5019b706538a6fa54dde545352180102420a4a49fb012103c1c329eea1fb6a6eb10da4394b147b2ddc3c8984dda3006933d17edb214be0eafffffffffa2333d87762d7f47a9cb376c1c6b2eed6441d0cebfb7883b14a1b18b6303d20000000006a47304402206772ff2ad24ae7b5cd9c1c723a141bffc84bfa03fc3ae05c90615b7f7075941402204b0f149a190a450b8c08084169f277a9b11d38b3738413915717778bed3aaf8c0121030ae4453a932caeff36af7a222f5e3a78e06c4a41fbd32872e76afa2f5ddc0b32ffffffff0440420f00000000001976a9149a1349d4d28bd2c7eb295578a8db7e900a88dbef88aca02c1000000000001976a9145ea1c5883f45c0f739052222186de2a97d2b477f88ac40420f00000000001976a914b61ff592088a0848653d10f1ee64934e5048320488ac40420f00000000001976a9142ae828b6dace8aa95f7e427406117f3ad979f31e88ac00000000010000000338161aa5e73aaa5bcaa96dbc120fd8019710542c1015ac7fe5067459492c07da000000006a4730440220488cabf142f1f5ec3ba43423fb527319a4ce24e1151429ecd46d2345cb5382f602203304608e4341d6174f91f72b9936482c442ae8f448e846b41ca7d88b9e1d9e0b012103d733c28935de6c3ffb2bb672642b7ad3e40eefdfc4a9a99bc8fc2c71eca165bcffffffff1279ba15d5879005507f98e4817b559c7197365908a289983702976413748147010000006b483045022100cf35ac9a86679ab434f70e6ae77a01afd7794c180813a25b4d2b3f1b7489a897022004171d5fe92535e16e9b5b584437c00b9c0f6fa61da691186fe5f30d4701ea4901210291b5f1bf2ea4f3606c7ed34d573a015f5720b9286b2f8fb3e1ac6bd08eab2f42ffffffff287a6ba0a3a61b4132b374c62ebf576790b80c1679accd0b8a655fe2daf9f282000000006b483045022100db8692c3731454de56c26068ef24bacb339503229c5f07341e5c984bd66914e402202f12b313e94bd3256b0a00f02ca0667d729deeda53e0f46cad19b6aeb5b9fd76012102ed3c03c968b75d5845cd59fbe31f93d4d73f19f375ff3e4de3ab062755fb42f5ffffffff0280841e00000000001976a914f370186a729515e8621421ee5093730b2535d76988ac103e1100000000001976a914477aa18bae7b229784a7857f27f0c4491337674888ac00000000010000000343a4217ec03a86dbd666e2d50a4f81e6ec78888501d9fe8be407315105e327e4010000006a473044022035ad18774bb1ea6ce5851c3453fa4d1bbb919c152bd70c5846c8dae8fd5768d702201cbf6085713047f0ef706d916ca02b5a64485364a2c15cb22c7b8be5585fafc8012103c8a1ce8275752d7fce768b8e2575f6ed8d770282dca77c0e92edc728e9a52660ffffffff9f3a39e94c21c9badce0da020823e1731ce83b9463f67df74cf60e67b0f1380a000000006b483045022100d2b742a1846f5ef083ad196faad1ee2ba65d1880c09070291a166c7075b5ce5a0220070410a486fef2907ae683d51b16b58367bb2e63ae83da0be06436972a5b8953012103fac77292a178d2a13e9c1cd34b62a3e97c9b67f85dc5ecf2cdc00ea30070ead6ffffffffd342b7c9bc1b211cd022e66d639a4f2529aa29e9cd144c65136cefbdf641a23e000000006b483045022100a961bd3d95a8185d81605b348fd2ec26327cb2bb8a0912cdaebd53d67325d4a902204738f384eb47aa8b30fd3255244ddf12d4e9ceadf781f909414c589f4c033f9901210242b91c415dead69340de6589ffc960181773aa85d79561d1bbe628b320a77913ffffffff0240420f00000000001976a914fe22a31bfb0d128784ec50e9bd23a5ab17ece81988ac006d1000000000001976a9141212aa3479493b3783d632cebb8b1b7f85e0f08e88ac00000000010000000363696517922487df9fafdcd2dfa016271bc74a20ff9981bb07309f74fa2ebf77010000006b483045022100800f701f92e9019ce8799c5e34d34bdff474c95fa1ee208bc42a4db94ce3a2b4022015afdf9c6abf97cf10f71f6938c1e14ec24c0ad48a029262f6f83dd228e7a935012103a6e9d75fed339936435f7b934ab24597ac47979619d6024491bc6b3ce7a18a72ffffffffd4676e4b2debcaeb558c34f9bac6b599085b3e872a18e321d59af6012438d91a010000006a473044022071e14e8ef73cf9e0aab6da408e1a03b0b14e5c58b7f7b2e6a85782ee894cc3f402201921f77148d2f88d9bb2c5fd75eb22e71f17b1b31d502519db78e1791703553801210337122f84beb41ee6b26ad030e1161363b2f6bbd46dd4147960ce7203aa4d2d1fffffffff3fd68ae235d1348d4f53445593744a8983dfdda5381ef8f38597097a27a7ade4000000006b483045022100862a808bac6390a0835d2e1ba9a2686d74d6258456df24c3256db2d75bafe96202207e59df20b49da81977ce7fab71a43ff113c8c7adc47b8e9096a42c1b6e27842601210337122f84beb41ee6b26ad030e1161363b2f6bbd46dd4147960ce7203aa4d2d1fffffffff02f2860f00000000001976a914ba9b1adb0717ed39ba70f6017f7d1ba233dcddb588ac7ea10200000000001976a9140df5cdbd67f672f755e2610ce35b574f630f0d0188ac000000000100000004ba0b4f41996673c775527a6cafe9eab0bd4554e8be55d12048837e53f79e5f6b000000006b483045022100e9f596937f73fbf760b8812d2949239013ccf689d210f344a4799bde555b8b3102205eb9f820510cfdbc1c5d14e069649d4ef107448f7dfc43abe4eb789d3db5efdb0121025efb6cf542a84987701be92bbbcb2b7da729a91e46fc82412de2c3121e29c1afffffffff89f749d3a7c6ef70b549a3f7502bdeadc3ab0c98bf80b716d961bf3830150012000000006a4730440220790dc4db98320dd0a5f5bdce54c24ff1aa22bfceafad0a78b7888c367ab4142d02203f2f55e3a6bca3908da221d23d7f7f103254d9cc6bdde398a7c3de6e4f5824c3012102c55d1894bb30e518dc8f04cbf0a839eea6fdd2f84cb7ee8dfb4df2df543d0081ffffffff0fcf7953a6e8f88c430c24b630a61d5a49181afe2e767ce76119ade9fe07c231010000006b4830450221009e5f130a7a3a1b38e69ee04467aae9974ec16323c6931d1143322ad365de2277022034ae51f6bc8683874fca0cc042ffaf10ec2c0843fca16cb817195c0b2e52678a012102c55d1894bb30e518dc8f04cbf0a839eea6fdd2f84cb7ee8dfb4df2df543d0081ffffffff3373ce2c64e6d6ff443dd48d493401b5ef2c7d1cc6838de0bd157492546f2570010000006b483045022100b94000fc2d47ada82eb93b214a715b0269c1fc1f27ef5de03bdde0bfa867a1f202206994d434eb63e9398da5e42d0ddd1991863416e93d8cf3f56606456fe89e813001210208e3d272bc6b00638268d8b192e20fa56415e4522c567ed84ffca8ad2b004bbbffffffff0260e31600000000001976a9143db4f4e6c67e1540ff43420b226d79dc7d8e168f88ac50bc1600000000001976a914cf3b5cc3a0aa4e24de74d38f7643500a73d6c07f88ac0000000001000000015c9cf5021b59f07ef2ed5970b410bfe6470690d309e03f495b4874a50b372b96010000006b483045022100b633d5fa3838c6753fe9fd5a656719a109ff0579cca9bb3272d5614715ba2202022040089f8af701ce448ce69217f731b013a917d9da6f23d9a408779b7a485b0c6d012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff02a0860100000000001976a91421620cd6d05d98cd15d2c7957d3c07bcf9731f8d88acc89f2e00000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac000000000100000001fa16fe96c11d1541c39de3acee0ddf222a4093b4554ec0b79c3cb230b0105faa010000006b483045022100a19ebbf182c5c26605070915d5c93698fc7145e2f96a7e1981d7eaef99b18c9b02204bd5f9729790f2b7073440c71af9e781b10b1be6a20f5295ca10a9cbc390dc260121038463a2dc022b91a5db8c88b115c2beb647ee226f3876818d81a301cc682b49ceffffffff0200688909000000001976a9141cd4c2807911d65d5ff4c575ddf9141ad022458988ac40047962030000001976a9149dec6baca88fd80aa04d2320fbff60225eb8e9e588ac0000000001000000014e417cfa0ddc6a5627e5a312a7a3300a1e96e9f32489b3faaa35317c907d36a5010000006b483045022100fc52565e014c25cccb61913a08e66106791c3dad63d41e4ea8c20fda5631509e02200e495fd33a06809be1965fb2e9cb757c46b3b0aab6cbe4ce775b9b87123654fb012103d850746fc0287d8550dfba1ec81c340d3c192d10fa9c918908fc913910667f01ffffffff0210270000000000001976a91421620cd6d05d98cd15d2c7957d3c07bcf9731f8d88acb8782e00000000001976a91418751b7839491fb642b370760c5887037d30aefc88ac00000000" + }, + { + "description": "Headers only - nonce changed", + "bits": 404020484, + "hash": "0fec37f0c7eb77e7f395cc1d51c41c2a3f9940e65d69cbc443bf4413a8967e3d", + "height": 370090, + "hex": "03000000af7b8278e9c67a6d6b3453239d83406a1b21aaed2bfeb10e0000000000000000a4a4dda66b46547ff41d668543f1f20a5974488257a1f35a07c1815187b30ab9913dd05504dd141800000000", + "id": "3d7e96a81344bf43c4cb695de640993f2a1cc4511dcc95f3e777ebc7f037ec0f", + "merkleRoot": "a4a4dda66b46547ff41d668543f1f20a5974488257a1f35a07c1815187b30ab9", + "nonce": 0, + "prevHash": "af7b8278e9c67a6d6b3453239d83406a1b21aaed2bfeb10e0000000000000000", + "timestamp": 1439710609, + "valid": false, + "version": 3 + }, + { + "description": "Genesis Block", + "bits": 486604799, + "hash": "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", + "height": 0, + "hex": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "id": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + "merkleRoot": "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a", + "nonce": 2083236893, + "prevHash": "0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": 1231006505, + "valid": true, + "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": [ diff --git a/test/fixtures/bufferutils.json b/test/fixtures/bufferutils.json index 8c6881d1b..42c145dfc 100644 --- a/test/fixtures/bufferutils.json +++ b/test/fixtures/bufferutils.json @@ -2,100 +2,75 @@ "valid": [ { "dec": 0, - "hex64": "0000000000000000", - "hexVI": "00", - "hexPD": "00" + "hex": "0000000000000000" }, { "dec": 1, - "hex64": "0100000000000000", - "hexVI": "01", - "hexPD": "01" + "hex": "0100000000000000" }, { "dec": 252, - "hex64": "fc00000000000000", - "hexVI": "fc", - "hexPD": "4cfc" + "hex": "fc00000000000000" }, { "dec": 253, - "hex64": "fd00000000000000", - "hexVI": "fdfd00", - "hexPD": "4cfd" + "hex": "fd00000000000000" }, { "dec": 254, - "hex64": "fe00000000000000", - "hexVI": "fdfe00", - "hexPD": "4cfe" + "hex": "fe00000000000000" }, { "dec": 255, - "hex64": "ff00000000000000", - "hexVI": "fdff00", - "hexPD": "4dff00" + "hex": "ff00000000000000" }, { "dec": 65534, - "hex64": "feff000000000000", - "hexVI": "fdfeff", - "hexPD": "4dfeff" + "hex": "feff000000000000" }, { "dec": 65535, - "hex64": "ffff000000000000", - "hexVI": "fdffff", - "hexPD": "4effff0000" + "hex": "ffff000000000000" }, { "dec": 65536, - "hex64": "0000010000000000", - "hexVI": "fe00000100", - "hexPD": "4e00000100" + "hex": "0000010000000000" }, { "dec": 65537, - "hex64": "0100010000000000", - "hexVI": "fe01000100", - "hexPD": "4e01000100" + "hex": "0100010000000000" }, { "dec": 4294967295, - "hex64": "ffffffff00000000", - "hexVI": "feffffffff", - "hexPD": "4effffffff" + "hex": "ffffffff00000000" }, { "dec": 4294967296, - "hex64": "0000000001000000", - "hexVI": "ff0000000001000000" + "hex": "0000000001000000" }, { "dec": 4294967297, - "hex64": "0100000001000000", - "hexVI": "ff0100000001000000" + "hex": "0100000001000000" }, { "dec": 9007199254740991, - "hex64": "ffffffffffff1f00", - "hexVI": "ffffffffffffff1f00" + "hex": "ffffffffffff1f00" } ], - "invalid": [ - { - "description": "n === 2^53", - "exception": "value is larger than maximum value for type", - "hex64": "0000000000002000", - "hexVI": "ff0000000000000020", - "dec": 9007199254740992 - }, - { - "description": "n > 2^53", - "exception": "value is larger than maximum value for type", - "hex64": "0100000000002000", - "hexVI": "ff0100000000000020", - "dec": 9007199254740993 - } - ] + "invalid": { + "readUInt64LE": [ + { + "description": "n === 2^53", + "exception": "RangeError: value out of range", + "hex": "0000000000002000", + "dec": 9007199254740992 + }, + { + "description": "n > 2^53", + "exception": "RangeError: value out of range", + "hex": "0100000000002000", + "dec": 9007199254740993 + } + ] + } } diff --git a/test/fixtures/core/blocks.json b/test/fixtures/core/blocks.json new file mode 100644 index 000000000..1a769ae62 --- /dev/null +++ b/test/fixtures/core/blocks.json @@ -0,0 +1,27 @@ +[ + { + "id": "0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af", + "transactions": 9, + "hex": "0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb6800000000000005275289558f51c9966699404ae2294730c3c9f9bda53523ce50e9b95e558da2fdb261b4d4c86041b1ab1bf930901000000010000000000000000000000000000000000000000000000000000000000000000ffffffff07044c86041b0146ffffffff0100f2052a01000000434104e18f7afbe4721580e81e8414fc8c24d7cfacf254bb5c7b949450c3e997c2dc1242487a8169507b631eb3771f2b425483fb13102c4eb5d858eef260fe70fbfae0ac00000000010000000196608ccbafa16abada902780da4dc35dafd7af05fa0da08cf833575f8cf9e836000000004a493046022100dab24889213caf43ae6adc41cf1c9396c08240c199f5225acf45416330fd7dbd022100fe37900e0644bf574493a07fc5edba06dbc07c311b947520c2d514bc5725dcb401ffffffff0100f2052a010000001976a914f15d1921f52e4007b146dfa60f369ed2fc393ce288ac000000000100000001fb766c1288458c2bafcfec81e48b24d98ec706de6b8af7c4e3c29419bfacb56d000000008c493046022100f268ba165ce0ad2e6d93f089cfcd3785de5c963bb5ea6b8c1b23f1ce3e517b9f022100da7c0f21adc6c401887f2bfd1922f11d76159cbc597fbd756a23dcbb00f4d7290141042b4e8625a96127826915a5b109852636ad0da753c9e1d5606a50480cd0c40f1f8b8d898235e571fe9357d9ec842bc4bba1827daaf4de06d71844d0057707966affffffff0280969800000000001976a9146963907531db72d0ed1a0cfb471ccb63923446f388ac80d6e34c000000001976a914f0688ba1c0d1ce182c7af6741e02658c7d4dfcd388ac000000000100000002c40297f730dd7b5a99567eb8d27b78758f607507c52292d02d4031895b52f2ff010000008b483045022100f7edfd4b0aac404e5bab4fd3889e0c6c41aa8d0e6fa122316f68eddd0a65013902205b09cc8b2d56e1cd1f7f2fafd60a129ed94504c4ac7bdc67b56fe67512658b3e014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffffca5065ff9617cbcba45eb23726df6498a9b9cafed4f54cbab9d227b0035ddefb000000008a473044022068010362a13c7f9919fa832b2dee4e788f61f6f5d344a7c2a0da6ae740605658022006d1af525b9a14a35c003b78b72bd59738cd676f845d1ff3fc25049e01003614014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffff01001ec4110200000043410469ab4181eceb28985b9b4e895c13fa5e68d85761b7eee311db5addef76fa8621865134a221bd01f28ec9999ee3e021e60766e9d1f3458c115fb28650605f11c9ac000000000100000001cdaf2f758e91c514655e2dc50633d1e4c84989f8aa90a0dbc883f0d23ed5c2fa010000008b48304502207ab51be6f12a1962ba0aaaf24a20e0b69b27a94fac5adf45aa7d2d18ffd9236102210086ae728b370e5329eead9accd880d0cb070aea0c96255fae6c4f1ddcce1fd56e014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff02404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac002d3101000000001976a9141befba0cdc1ad56529371864d9f6cb042faa06b588ac000000000100000001b4a47603e71b61bc3326efd90111bf02d2f549b067f4c4a8fa183b57a0f800cb010000008a4730440220177c37f9a505c3f1a1f0ce2da777c339bd8339ffa02c7cb41f0a5804f473c9230220585b25a2ee80eb59292e52b987dad92acb0c64eced92ed9ee105ad153cdb12d001410443bd44f683467e549dae7d20d1d79cbdb6df985c6e9c029c8d0c6cb46cc1a4d3cf7923c5021b27f7a0b562ada113bc85d5fda5a1b41e87fe6e8802817cf69996ffffffff0280651406000000001976a9145505614859643ab7b547cd7f1f5e7e2a12322d3788ac00aa0271000000001976a914ea4720a7a52fc166c55ff2298e07baf70ae67e1b88ac00000000010000000586c62cd602d219bb60edb14a3e204de0705176f9022fe49a538054fb14abb49e010000008c493046022100f2bc2aba2534becbdf062eb993853a42bbbc282083d0daf9b4b585bd401aa8c9022100b1d7fd7ee0b95600db8535bbf331b19eed8d961f7a8e54159c53675d5f69df8c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff03ad0e58ccdac3df9dc28a218bcf6f1997b0a93306faaa4b3a28ae83447b2179010000008b483045022100be12b2937179da88599e27bb31c3525097a07cdb52422d165b3ca2f2020ffcf702200971b51f853a53d644ebae9ec8f3512e442b1bcb6c315a5b491d119d10624c83014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff2acfcab629bbc8685792603762c921580030ba144af553d271716a95089e107b010000008b483045022100fa579a840ac258871365dd48cd7552f96c8eea69bd00d84f05b283a0dab311e102207e3c0ee9234814cfbb1b659b83671618f45abc1326b9edcc77d552a4f2a805c0014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffffdcdc6023bbc9944a658ddc588e61eacb737ddf0a3cd24f113b5a8634c517fcd2000000008b4830450221008d6df731df5d32267954bd7d2dda2302b74c6c2a6aa5c0ca64ecbabc1af03c75022010e55c571d65da7701ae2da1956c442df81bbf076cdbac25133f99d98a9ed34c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffffe15557cd5ce258f479dfd6dc6514edf6d7ed5b21fcfa4a038fd69f06b83ac76e010000008b483045022023b3e0ab071eb11de2eb1cc3a67261b866f86bf6867d4558165f7c8c8aca2d86022100dc6e1f53a91de3efe8f63512850811f26284b62f850c70ca73ed5de8771fb451014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff01404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac00000000010000000166d7577163c932b4f9690ca6a80b6e4eb001f0a2fa9023df5595602aae96ed8d000000008a4730440220262b42546302dfb654a229cefc86432b89628ff259dc87edd1154535b16a67e102207b4634c020a97c3e7bbd0d4d19da6aa2269ad9dded4026e896b213d73ca4b63f014104979b82d02226b3a4597523845754d44f13639e3bf2df5e82c6aab2bdc79687368b01b1ab8b19875ae3c90d661a3d0a33161dab29934edeb36aa01976be3baf8affffffff02404b4c00000000001976a9144854e695a02af0aeacb823ccbc272134561e0a1688ac40420f00000000001976a914abee93376d6b37b5c2940655a6fcaf1c8e74237988ac0000000001000000014e3f8ef2e91349a9059cb4f01e54ab2597c1387161d3da89919f7ea6acdbb371010000008c49304602210081f3183471a5ca22307c0800226f3ef9c353069e0773ac76bb580654d56aa523022100d4c56465bdc069060846f4fbf2f6b20520b2a80b08b168b31e66ddb9c694e240014104976c79848e18251612f8940875b2b08d06e6dc73b9840e8860c066b7e87432c477e9a59a453e71e6d76d5fe34058b800a098fc1740ce3012e8fc8a00c96af966ffffffff02c0e1e400000000001976a9144134e75a6fcb6042034aab5e18570cf1f844f54788ac404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac00000000" + }, + { + "id": "000000005a4ded781e667e06ceefafb71410b511fe0d5adc3e5a27ecbec34ae6", + "transactions": 4, + "hex": "0100000075616236cc2126035fadb38deb65b9102cc2c41c09cdf29fc051906800000000fe7d5e12ef0ff901f6050211249919b1c0653771832b3a80c66cea42847f0ae1d4d26e49ffff001d00f0a4410401000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d029105ffffffff0100f2052a010000004341046d8709a041d34357697dfcb30a9d05900a6294078012bf3bb09c6f9b525f1d16d5503d7905db1ada9501446ea00728668fc5719aa80be2fdfc8a858a4dbdd4fbac00000000010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac0000000001000000025f9a06d3acdceb56be1bfeaa3e8a25e62d182fa24fefe899d1c17f1dad4c2028000000004847304402205d6058484157235b06028c30736c15613a28bdb768ee628094ca8b0030d4d6eb0220328789c9a2ec27ddaec0ad5ef58efded42e6ea17c2e1ce838f3d6913f5e95db601ffffffff5f9a06d3acdceb56be1bfeaa3e8a25e62d182fa24fefe899d1c17f1dad4c2028010000004a493046022100c45af050d3cea806cedd0ab22520c53ebe63b987b8954146cdca42487b84bdd6022100b9b027716a6b59e640da50a864d6dd8a0ef24c76ce62391fa3eabaf4d2886d2d01ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac000000000100000002e2274e5fea1bf29d963914bd301aa63b64daaf8a3e88f119b5046ca5738a0f6b0000000048473044022016e7a727a061ea2254a6c358376aaa617ac537eb836c77d646ebda4c748aac8b0220192ce28bf9f2c06a6467e6531e27648d2b3e2e2bae85159c9242939840295ba501ffffffffe2274e5fea1bf29d963914bd301aa63b64daaf8a3e88f119b5046ca5738a0f6b010000004a493046022100b7a1a755588d4190118936e15cd217d133b0e4a53c3c15924010d5648d8925c9022100aaef031874db2114f2d869ac2de4ae53908fbfea5b2b1862e181626bb9005c9f01ffffffff0200e1f505000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00180d8f000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00000000" + }, + { + "id": "000000005a4ded781e667e06ceefafb71410b511fe0d5adc3e5a27ecbec34ae6", + "transactions": 4, + "hex": "0100000075616236cc2126035fadb38deb65b9102cc2c41c09cdf29fc051906800000000fe7d5e12ef0ff901f6050211249919b1c0653771832b3a80c66cea42847f0ae1d4d26e49ffff001d00f0a4410401000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d029105ffffffff0100f2052a010000004341046d8709a041d34357697dfcb30a9d05900a6294078012bf3bb09c6f9b525f1d16d5503d7905db1ada9501446ea00728668fc5719aa80be2fdfc8a858a4dbdd4fbac00000000010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac0000000001000000025f9a06d3acdceb56be1bfeaa3e8a25e62d182fa24fefe899d1c17f1dad4c2028000000004847304402205d6058484157235b06028c30736c15613a28bdb768ee628094ca8b0030d4d6eb0220328789c9a2ec27ddaec0ad5ef58efded42e6ea17c2e1ce838f3d6913f5e95db601ffffffff5f9a06d3acdceb56be1bfeaa3e8a25e62d182fa24fefe899d1c17f1dad4c2028010000004a493046022100c45af050d3cea806cedd0ab22520c53ebe63b987b8954146cdca42487b84bdd6022100b9b027716a6b59e640da50a864d6dd8a0ef24c76ce62391fa3eabaf4d2886d2d01ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac000000000100000002e2274e5fea1bf29d963914bd301aa63b64daaf8a3e88f119b5046ca5738a0f6b0000000048473044022016e7a727a061ea2254a6c358376aaa617ac537eb836c77d646ebda4c748aac8b0220192ce28bf9f2c06a6467e6531e27648d2b3e2e2bae85159c9242939840295ba501ffffffffe2274e5fea1bf29d963914bd301aa63b64daaf8a3e88f119b5046ca5738a0f6b010000004a493046022100b7a1a755588d4190118936e15cd217d133b0e4a53c3c15924010d5648d8925c9022100aaef031874db2114f2d869ac2de4ae53908fbfea5b2b1862e181626bb9005c9f01ffffffff0200e1f505000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00180d8f000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00000000" + }, + { + "id": "000000000000dab0130bbcc991d3d7ae6b81aa6f50a798888dfe62337458dc45", + "transactions": 1, + "hex": "0100000079cda856b143d9db2c1caff01d1aecc8630d30625d10e8b4b8b0000000000000b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f196367291b4d4c86041b8fa45d630101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff08044c86041b020a02ffffffff0100f2052a01000000434104ecd3229b0571c3be876feaac0442a9f13c5a572742927af1dc623353ecf8c202225f64868137a18cdd85cbbb4c74fbccfd4f49639cf1bdc94a5672bb15ad5d4cac00000000" + }, + { + "id": "000000000000b731f2eef9e8c63173adfb07e41bd53eb0ef0a6b720d6cb6dea4", + "transactions": 7, + "hex": "0100000082bb869cf3a793432a66e826e05a6fc37469f8efb7421dc880670100000000007f16c5962e8bd963659c793ce370d95f093bc7e367117b3c30c1f8fdd0d9728776381b4d4c86041b554b85290701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff07044c86041b0136ffffffff0100f2052a01000000434104eaafc2314def4ca98ac970241bcab022b9c1e1f4ea423a20f134c876f2c01ec0f0dd5b2e86e7168cefe0d81113c3807420ce13ad1357231a2252247d97a46a91ac000000000100000001bcad20a6a29827d1424f08989255120bf7f3e9e3cdaaa6bb31b0737fe048724300000000494830450220356e834b046cadc0f8ebb5a8a017b02de59c86305403dad52cd77b55af062ea10221009253cd6c119d4729b77c978e1e2aa19f5ea6e0e52b3f16e32fa608cd5bab753901ffffffff02008d380c010000001976a9142b4b8072ecbba129b6453c63e129e643207249ca88ac0065cd1d000000001976a9141b8dd13b994bcfc787b32aeadf58ccb3615cbd5488ac000000000100000003fdacf9b3eb077412e7a968d2e4f11b9a9dee312d666187ed77ee7d26af16cb0b000000008c493046022100ea1608e70911ca0de5af51ba57ad23b9a51db8d28f82c53563c56a05c20f5a87022100a8bdc8b4a8acc8634c6b420410150775eb7f2474f5615f7fccd65af30f310fbf01410465fdf49e29b06b9a1582287b6279014f834edc317695d125ef623c1cc3aaece245bd69fcad7508666e9c74a49dc9056d5fc14338ef38118dc4afae5fe2c585caffffffff309e1913634ecb50f3c4f83e96e70b2df071b497b8973a3e75429df397b5af83000000004948304502202bdb79c596a9ffc24e96f4386199aba386e9bc7b6071516e2b51dda942b3a1ed022100c53a857e76b724fc14d45311eac5019650d415c3abb5428f3aae16d8e69bec2301ffffffff2089e33491695080c9edc18a428f7d834db5b6d372df13ce2b1b0e0cbcb1e6c10000000049483045022100d4ce67c5896ee251c810ac1ff9ceccd328b497c8f553ab6e08431e7d40bad6b5022033119c0c2b7d792d31f1187779c7bd95aefd93d90a715586d73801d9b47471c601ffffffff0100714460030000001976a914c7b55141d097ea5df7a0ed330cf794376e53ec8d88ac0000000001000000045bf0e214aa4069a3e792ecee1e1bf0c1d397cde8dd08138f4b72a00681743447000000008b48304502200c45de8c4f3e2c1821f2fc878cba97b1e6f8807d94930713aa1c86a67b9bf1e40221008581abfef2e30f957815fc89978423746b2086375ca8ecf359c85c2a5b7c88ad01410462bb73f76ca0994fcb8b4271e6fb7561f5c0f9ca0cf6485261c4a0dc894f4ab844c6cdfb97cd0b60ffb5018ffd6238f4d87270efb1d3ae37079b794a92d7ec95ffffffffd669f7d7958d40fc59d2253d88e0f248e29b599c80bbcec344a83dda5f9aa72c000000008a473044022078124c8beeaa825f9e0b30bff96e564dd859432f2d0cb3b72d3d5d93d38d7e930220691d233b6c0f995be5acb03d70a7f7a65b6bc9bdd426260f38a1346669507a3601410462bb73f76ca0994fcb8b4271e6fb7561f5c0f9ca0cf6485261c4a0dc894f4ab844c6cdfb97cd0b60ffb5018ffd6238f4d87270efb1d3ae37079b794a92d7ec95fffffffff878af0d93f5229a68166cf051fd372bb7a537232946e0a46f53636b4dafdaa4000000008c493046022100c717d1714551663f69c3c5759bdbb3a0fcd3fab023abc0e522fe6440de35d8290221008d9cbe25bffc44af2b18e81c58eb37293fd7fe1c2e7b46fc37ee8c96c50ab1e201410462bb73f76ca0994fcb8b4271e6fb7561f5c0f9ca0cf6485261c4a0dc894f4ab844c6cdfb97cd0b60ffb5018ffd6238f4d87270efb1d3ae37079b794a92d7ec95ffffffff27f2b668859cd7f2f894aa0fd2d9e60963bcd07c88973f425f999b8cbfd7a1e2000000008c493046022100e00847147cbf517bcc2f502f3ddc6d284358d102ed20d47a8aa788a62f0db780022100d17b2d6fa84dcaf1c95d88d7e7c30385aecf415588d749afd3ec81f6022cecd701410462bb73f76ca0994fcb8b4271e6fb7561f5c0f9ca0cf6485261c4a0dc894f4ab844c6cdfb97cd0b60ffb5018ffd6238f4d87270efb1d3ae37079b794a92d7ec95ffffffff0100c817a8040000001976a914b6efd80d99179f4f4ff6f4dd0a007d018c385d2188ac000000000100000001834537b2f1ce8ef9373a258e10545ce5a50b758df616cd4356e0032554ebd3c4000000008b483045022100e68f422dd7c34fdce11eeb4509ddae38201773dd62f284e8aa9d96f85099d0b002202243bd399ff96b649a0fad05fa759d6a882f0af8c90cf7632c2840c29070aec20141045e58067e815c2f464c6a2a15f987758374203895710c2d452442e28496ff38ba8f5fd901dc20e29e88477167fe4fc299bf818fd0d9e1632d467b2a3d9503b1aaffffffff0280d7e636030000001976a914f34c3e10eb387efe872acb614c89e78bfca7815d88ac404b4c00000000001976a914a84e272933aaf87e1715d7786c51dfaeb5b65a6f88ac00000000010000000143ac81c8e6f6ef307dfe17f3d906d999e23e0189fda838c5510d850927e03ae7000000008c4930460221009c87c344760a64cb8ae6685a3eec2c1ac1bed5b88c87de51acd0e124f266c16602210082d07c037359c3a257b5c63ebd90f5a5edf97b2ac1c434b08ca998839f346dd40141040ba7e521fa7946d12edbb1d1e95a15c34bd4398195e86433c92b431cd315f455fe30032ede69cad9d1e1ed6c3c4ec0dbfced53438c625462afb792dcb098544bffffffff0240420f00000000001976a9144676d1b820d63ec272f1900d59d43bc6463d96f888ac40420f00000000001976a914648d04341d00d7968b3405c034adc38d4d8fb9bd88ac00000000010000000248cc917501ea5c55f4a8d2009c0567c40cfe037c2e71af017d0a452ff705e3f1000000008b483045022100bf5fdc86dc5f08a5d5c8e43a8c9d5b1ed8c65562e280007b52b133021acd9acc02205e325d613e555f772802bf413d36ba807892ed1a690a77811d3033b3de226e0a01410429fa713b124484cb2bd7b5557b2c0b9df7b2b1fee61825eadc5ae6c37a9920d38bfccdc7dc3cb0c47d7b173dbc9db8d37db0a33ae487982c59c6f8606e9d1791ffffffff41ed70551dd7e841883ab8f0b16bf04176b7d1480e4f0af9f3d4c3595768d068000000008b4830450221008513ad65187b903aed1102d1d0c47688127658c51106753fed0151ce9c16b80902201432b9ebcb87bd04ceb2de66035fbbaf4bf8b00d1cfe41f1a1f7338f9ad79d210141049d4cf80125bf50be1709f718c07ad15d0fc612b7da1f5570dddc35f2a352f0f27c978b06820edca9ef982c35fda2d255afba340068c5035552368bc7200c1488ffffffff0100093d00000000001976a9148edb68822f1ad580b043c7b3df2e400f8699eb4888ac00000000" + } +] diff --git a/test/fixtures/core/tx_valid.json b/test/fixtures/core/tx_valid.json index 80e26e54e..ccfc3ec71 100644 --- a/test/fixtures/core/tx_valid.json +++ b/test/fixtures/core/tx_valid.json @@ -16,7 +16,7 @@ ["It is an OP_CHECKMULTISIG with an arbitrary extra byte stuffed into the signature at pos length - 2"], ["The dummy byte is fine however, so the NULLDUMMY flag should be happy"], [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], -"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004A0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH,NULLDUMMY"], +"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH,NULLDUMMY"], ["The following is a tweaked form of 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"], ["It is an OP_CHECKMULTISIG with the dummy value set to something other than an empty string"], @@ -150,6 +150,15 @@ [[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 1"]], "010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000", "P2SH"], +["CODESEPARATOR in an unexecuted IF block does not change what is hashed"], +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "IF CODESEPARATOR ENDIF 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 CHECKSIGVERIFY CODESEPARATOR 1"]], +"010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a48304502207a6974a77c591fa13dff60cabbb85a0de9e025c09c65a4b2285e47ce8e22f761022100f0efaac9ff8ac36b10721e0aae1fb975c90500b50c56e8a0cc52b0403f0425dd0100ffffffff010000000000000000016a00000000", "P2SH"], + +["As above, with the IF block executed"], +[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "IF CODESEPARATOR ENDIF 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 CHECKSIGVERIFY CODESEPARATOR 1"]], +"010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a9000000004a483045022100fa4a74ba9fd59c59f46c3960cf90cbe0d2b743c471d24a3d5d6db6002af5eebb02204d70ec490fd0f7055a7c45f86514336e3a7f03503dacecabb247fc23f15c83510151ffffffff010000000000000000016a00000000", "P2SH"], + + ["CHECKSIG is legal in scriptSigs"], [[["ccf7f4053a02e653c36ac75c891b7496d0dc5ce5214f6c913d9cf8f1329ebee0", 0, "DUP HASH160 0x14 0xee5a6aa40facefb2655ac23c0c28c57c65c41f9b EQUALVERIFY CHECKSIG"]], "0100000001e0be9e32f1f89c3d916c4f21e55cdcd096741b895cc76ac353e6023a05f4f7cc00000000d86149304602210086e5f736a2c3622ebb62bd9d93d8e5d76508b98be922b97160edc3dcca6d8c47022100b23c312ac232a4473f19d2aeb95ab7bdf2b65518911a0d72d50e38b5dd31dc820121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ac4730440220508fa761865c8abd81244a168392876ee1d94e8ed83897066b5e2df2400dad24022043f5ee7538e87e9c6aef7ef55133d3e51da7cc522830a9c4d736977a76ef755c0121038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041ffffffff010000000000000000016a00000000", "P2SH"], @@ -179,5 +188,328 @@ "0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000", "P2SH"], +["CHECKLOCKTIMEVERIFY tests"], + +["By-height locks, with argument == 0 and == tx nLockTime"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 CHECKLOCKTIMEVERIFY 1"]], +"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], +"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], + +["By-time locks, with argument just beyond tx nLockTime (but within numerical boundaries)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 CHECKLOCKTIMEVERIFY 1"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKLOCKTIMEVERIFY 1"]], +"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff", "P2SH,CHECKLOCKTIMEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "500000000 CHECKLOCKTIMEVERIFY 1"]], +"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ffffffff", "P2SH,CHECKLOCKTIMEVERIFY"], + +["Any non-maxint nSequence is fine"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKLOCKTIMEVERIFY 1"]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], + +["The argument can be calculated rather than created directly by a PUSHDATA"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 1ADD CHECKLOCKTIMEVERIFY 1"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000065cd1d", "P2SH,CHECKLOCKTIMEVERIFY"], + +["Perhaps even by an ADD producing a 5-byte result that is out of bounds for other opcodes"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483647 2147483647 ADD CHECKLOCKTIMEVERIFY 1"]], +"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000feffffff", "P2SH,CHECKLOCKTIMEVERIFY"], + +["5 byte non-minimally-encoded arguments are valid"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x05 0x0000000000 CHECKLOCKTIMEVERIFY 1"]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKLOCKTIMEVERIFY"], + +["Valid CHECKLOCKTIMEVERIFY in scriptSig"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1"]], +"01000000010001000000000000000000000000000000000000000000000000000000000000000000000251b1000000000100000000000000000001000000", "P2SH,CHECKLOCKTIMEVERIFY"], + +["Valid CHECKLOCKTIMEVERIFY in redeemScript"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xc5b93064159b3b2d6ab506a41b1f50463771b988 EQUAL"]], +"0100000001000100000000000000000000000000000000000000000000000000000000000000000000030251b1000000000100000000000000000001000000", "P2SH,CHECKLOCKTIMEVERIFY"], + +["A transaction with a non-standard DER signature."], +[[["b1dbc81696c8a9c0fccd0693ab66d7c368dbc38c0def4e800685560ddd1b2132", 0, "DUP HASH160 0x14 0x4b3bd7eba3bc0284fd3007be7f3be275e94f5826 EQUALVERIFY CHECKSIG"]], +"010000000132211bdd0d568506804eef0d8cc3db68c3d766ab9306cdfcc0a9c89616c8dbb1000000006c493045022100c7bb0faea0522e74ff220c20c022d2cb6033f8d167fb89e75a50e237a35fd6d202203064713491b1f8ad5f79e623d0219ad32510bfaa1009ab30cbee77b59317d6e30001210237af13eb2d84e4545af287b919c2282019c9691cc509e78e196a9d8274ed1be0ffffffff0100000000000000001976a914f1b3ed2eda9a2ebe5a9374f692877cdf87c0f95b88ac00000000", "P2SH"], + +["CHECKSEQUENCEVERIFY tests"], + +["By-height locks, with argument == 0 and == txin.nSequence"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "65535 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "65535 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["By-time locks, with argument == 0 and == txin.nSequence"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4194304 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4259839 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff40000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4259839 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4194304 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Upper sequence with upper sequence is fine"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000feffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument 2^31 with various nSequence"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483648 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument 2^32-1 with various nSequence"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4294967295 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Argument 3<<31 with various nSequence"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "6442450944 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffbf7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "6442450944 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffff7f0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "6442450944 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["5 byte non-minimally-encoded operandss are valid"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x05 0x0000000000 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["The argument can be calculated rather than created directly by a PUSHDATA"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4194303 1ADD CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "4194304 1SUB CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["An ADD producing a 5-byte result that sets CTxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483647 65536 CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "2147483647 4259840 ADD CHECKSEQUENCEVERIFY 1"]], +"020000000100010000000000000000000000000000000000000000000000000000000000000000000000000040000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Valid CHECKSEQUENCEVERIFY in scriptSig"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "1"]], +"02000000010001000000000000000000000000000000000000000000000000000000000000000000000251b2010000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Valid CHECKSEQUENCEVERIFY in redeemScript"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7c17aff532f22beb54069942f9bf567a66133eaf EQUAL"]], +"0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"], + +["Valid P2WPKH (Private key of segwit tests is L5AQtV2HDm4xGsseLokK2VAT2EtYKcTm3c7HwqnJBFt9LdaQULsM)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1000]], +"0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000", "P2SH,WITNESS"], + +["Valid P2WSH"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x00 0x20 0xff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db", 1000]], +"0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000", "P2SH,WITNESS"], + +["Valid P2SH(P2WPKH)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xfe9c7dacc9fcfbf7e3b7d5ad06aa2b28c5a7b7e3 EQUAL", 1000]], +"01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000", "P2SH,WITNESS"], + +["Valid P2SH(P2WSH)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x2135ab4f0981830311e35600eebc7376dce3a914 EQUAL", 1000]], +"0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000", "P2SH,WITNESS"], + +["Witness with SigHash Single|AnyoneCanPay"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3100], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1100], +["0000000000000000000000000000000000000000000000000000000000000100", 3, "0x51", 4100]], +"0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000", "P2SH,WITNESS"], + +["Witness with SigHash Single|AnyoneCanPay (same signature as previous)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash Single"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash Single (same signature as previous)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash None|AnyoneCanPay"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3100], +["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1100], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 3, "0x51", 4100]], +"0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash None|AnyoneCanPay (same signature as previous)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash None"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash None (same signature as previous)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash None (same signature, only sequences changed)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash All|AnyoneCanPay"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3100], +["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1100], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 3, "0x51", 4100]], +"0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with SigHash All|AnyoneCanPay (same signature as previous)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Unknown witness program version (without DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x60 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 2000], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "0x51", 3000]], +"0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Witness with a push of 520 bytes"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x00 0x20 0x33198a9bfef674ebddb9ffaa52928017b8472791e54c609cb95f278ac6b1e349", 1000]], +"0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000", "P2SH,WITNESS"], + +["Transaction mixing all SigHash, segwit and normal inputs"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1001], +["0000000000000000000000000000000000000000000000000000000000000100", 2, "DUP HASH160 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f EQUALVERIFY CHECKSIG", 1002], +["0000000000000000000000000000000000000000000000000000000000000100", 3, "DUP HASH160 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f EQUALVERIFY CHECKSIG", 1003], +["0000000000000000000000000000000000000000000000000000000000000100", 4, "DUP HASH160 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f EQUALVERIFY CHECKSIG", 1004], +["0000000000000000000000000000000000000000000000000000000000000100", 5, "DUP HASH160 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f EQUALVERIFY CHECKSIG", 1005], +["0000000000000000000000000000000000000000000000000000000000000100", 6, "DUP HASH160 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f EQUALVERIFY CHECKSIG", 1006], +["0000000000000000000000000000000000000000000000000000000000000100", 7, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1007], +["0000000000000000000000000000000000000000000000000000000000000100", 8, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1008], +["0000000000000000000000000000000000000000000000000000000000000100", 9, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1009], +["0000000000000000000000000000000000000000000000000000000000000100", 10, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1010], +["0000000000000000000000000000000000000000000000000000000000000100", 11, "DUP HASH160 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f EQUALVERIFY CHECKSIG", 1011]], +"0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "P2SH,WITNESS"], + +["Unknown version witness program with empty witness"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x60 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1000]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000", "P2SH,WITNESS"], + +["Witness SIGHASH_SINGLE with output out of bound"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x51", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x20 0x4d6c2a32c87821d68fc016fca70797abdb80df6cd84651d40a9300c6bad79e62", 1000]], +"0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000", "P2SH,WITNESS"], + +["1 byte push should not be considered a witness scriptPubKey"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x60 0x01 0x01", 1000]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000", "P2SH,WITNESS,DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"], + +["41 bytes push should not be considered a witness scriptPubKey"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x60 0x29 0xff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbff0000000000000000", 1000]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000", "P2SH,WITNESS,DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"], + +["The witness version must use OP_1 to OP_16 only"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x01 0x10 0x02 0x0001", 1000]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000", "P2SH,WITNESS,DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"], + +["The witness program push must be canonical"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x60 0x4c02 0x0001", 1000]], +"010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000", "P2SH,WITNESS,DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"], + +["Witness Single|AnyoneCanPay does not hash input's position"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1001]], +"0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000", "P2SH,WITNESS"], + +["Witness Single|AnyoneCanPay does not hash input's position (permutation)"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1001], +["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x00 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f", 1000]], +"0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000", "P2SH,WITNESS"], + +["Non witness Single|AnyoneCanPay hash input's position"], +[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x21 0x03596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71 CHECKSIG", 1000], +["0000000000000000000000000000000000000000000000000000000000000100", 1, "0x21 0x03596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71 CHECKSIG", 1001]], +"01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000", "P2SH,WITNESS"], + +["BIP143 examples: details and private keys are available in BIP143"], +["BIP143 example: P2WSH with OP_CODESEPARATOR and out-of-range SIGHASH_SINGLE."], +[[["6eb316926b1c5d567cd6f5e6a84fec606fc53d7b474526d1fff3948020c93dfe", 0, "0x21 0x036d5c20fa14fb2f635474c1dc4ef5909d4568e5569b79fc94d3448486e14685f8 CHECKSIG", 156250000], +["f825690aee1b3dc247da796cacb12687a5e802429fd291cfd63e010f02cf1508", 0, "0x00 0x20 0x5d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0", 4900000000]], +"01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000", "P2SH,WITNESS"], + +["BIP143 example: P2WSH with unexecuted OP_CODESEPARATOR and SINGLE|ANYONECANPAY"], +[[["01c0cf7fba650638e55eb91261b183251fbb466f90dff17f10086817c542b5e9", 0, "0x00 0x20 0xba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d", 16777215], +["1b2a9a426ba603ba357ce7773cb5805cb9c7c2b386d100d1fc9263513188e680", 0, "0x00 0x20 0xd9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537", 16777215]], +"01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS"], + +["BIP143 example: Same as the previous example with input-output paris swapped"], +[[["1b2a9a426ba603ba357ce7773cb5805cb9c7c2b386d100d1fc9263513188e680", 0, "0x00 0x20 0xd9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537", 16777215], +["01c0cf7fba650638e55eb91261b183251fbb466f90dff17f10086817c542b5e9", 0, "0x00 0x20 0xba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d", 16777215]], +"0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000", "P2SH,WITNESS"], + +["BIP143 example: P2SH-P2WSH 6-of-6 multisig signed with 6 different SIGHASH types"], +[[["6eb98797a21c6c10aa74edf29d618be109f48a8e94c694f3701e08ca69186436", 1, "HASH160 0x14 0x9993a429037b5d912407a71c252019287b8d27a5 EQUAL", 987654321]], +"0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000", "P2SH,WITNESS"], + +["FindAndDelete tests"], +["This is a test of FindAndDelete. The first tx is a spend of normal P2SH and the second tx is a spend of bare P2WSH."], +["The redeemScript/witnessScript is CHECKSIGVERIFY <0x30450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01>."], +["The signature is <0x30450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01> ,"], +["where the pubkey is obtained through key recovery with sig and correct sighash."], +["This is to show that FindAndDelete is applied only to non-segwit scripts"], +["Non-segwit: correct sighash (with FindAndDelete) = 1ba1fe3bc90c5d1265460e684ce6774e324f0fabdf67619eda729e64e8b6bc08"], +[[["f18783ace138abac5d3a7a5cf08e88fe6912f267ef936452e0c27d090621c169", 7000, "HASH160 0x14 0x0c746489e2d83cdbb5b90b432773342ba809c134 EQUAL", 200000]], +"010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000", "P2SH,WITNESS"], +["BIP143: correct sighash (without FindAndDelete) = 71c9cd9b2869b9c70b01b1f0360c148f42dee72297db312638df136f43311f23"], +[[["f18783ace138abac5d3a7a5cf08e88fe6912f267ef936452e0c27d090621c169", 7500, "0x00 0x20 0x9e1be07558ea5cc8e02ed1d80c0911048afad949affa36d5c3951e3159dbea19", 200000]], +"0100000000010169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f14c1d000000ffffffff01010000000000000000034830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e012102a9781d66b61fb5a7ef00ac5ad5bc6ffc78be7b44a566e3c87870e1079368df4c4aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0100000000", "P2SH,WITNESS"], +["This is multisig version of the FindAndDelete tests"], +["Script is 2 CHECKMULTISIGVERIFY DROP"], +["52af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175"], +["Signature is 0 2 "], +["Non-segwit: correct sighash (with FindAndDelete) = 1d50f00ba4db2917b903b0ec5002e017343bb38876398c9510570f5dce099295"], +[[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7000, "HASH160 0x14 0x5748407f5ca5cdca53ba30b79040260770c9ee1b EQUAL", 200000]], +"01000000019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a662896581b0000fd6f01004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c03959601522102cd74a2809ffeeed0092bc124fd79836706e41f048db3f6ae9df8708cefb83a1c2102e615999372426e46fd107b76eaf007156a507584aa2cc21de9eee3bdbd26d36c4c9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960175ffffffff0101000000000000000000000000", "P2SH,WITNESS"], +["BIP143: correct sighash (without FindAndDelete) = c1628a1e7c67f14ca0c27c06e4fdeec2e6d1a73c7a91d7c046ff83e835aebb72"], +[[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7500, "0x00 0x20 0x9b66c15b4e0b4eb49fa877982cafded24859fe5b0e2dbfbe4f0df1de7743fd52", 200000]], +"010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/test/fixtures/crypto.json b/test/fixtures/crypto.json index aa66fec1c..102b50a3c 100644 --- a/test/fixtures/crypto.json +++ b/test/fixtures/crypto.json @@ -1,36 +1,34 @@ -{ - "valid": [ - { - "hex": "0000000000000001", - "hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8", - "hash256": "3ae5c198d17634e79059c2cd735491553d22c4e09d1d9fea3ecf214565df2284", - "ripemd160": "8d1a05d1bc08870968eb8a81ad4393fd3aac6633", - "sha1": "cb473678976f425d6ec1339838f11011007ad27d", - "sha256": "cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50" - }, - { - "hex": "0101010101010101", - "hash160": "abaf1119f83e384210fe8e222eac76e2f0da39dc", - "hash256": "728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23", - "ripemd160": "5825701b4b9767fd35063b286dca3582853e0630", - "sha1": "c0357a32ed1f6a03be92dd094476f7f1a2e214ec", - "sha256": "04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061" - }, - { - "hex": "ffffffffffffffff", - "hash160": "f86221f5a1fca059a865c0b7d374dfa9d5f3aeb4", - "hash256": "752adad0a7b9ceca853768aebb6965eca126a62965f698a0c1bc43d83db632ad", - "ripemd160": "cb760221600ed34337ca3ab70016b5f58c838120", - "sha1": "be673e8a56eaa9d8c1d35064866701c11ef8e089", - "sha256": "12a3ae445661ce5dee78d0650d33362dec29c4f82af05e7e57fb595bbbacf0ca" - }, - { - "hex": "4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e65632061742066617563696275732073617069656e2c2076656c20666163696c6973697320617263752e20536564207574206d61737361206e6962682e205574206d6f6c6c69732070756c76696e6172206d617373612e20557420756c6c616d636f7270657220646f6c6f7220656e696d2c20696e206d6f6c657374696520656e696d20636f6e64696d656e74756d2061632e20416c697175616d206572617420766f6c75747061742e204e756c6c6120736f64616c657320617420647569206e656320", - "hash160": "9763e6b367c363bd6b88a7b361c98e6beee243a5", - "hash256": "033588797115feb3545052670cac2a46584ab3cb460de63756ee0275e66b5799", - "ripemd160": "cad8593dcdef12ee334c97bab9787f07b3f3a1a5", - "sha1": "10d96fb43aca84e342206887bbeed3065d4e4344", - "sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da" - } - ] -} \ No newline at end of file +[ + { + "hex": "0000000000000001", + "hash160": "cdb00698f02afd929ffabea308340fa99ac2afa8", + "hash256": "3ae5c198d17634e79059c2cd735491553d22c4e09d1d9fea3ecf214565df2284", + "ripemd160": "8d1a05d1bc08870968eb8a81ad4393fd3aac6633", + "sha1": "cb473678976f425d6ec1339838f11011007ad27d", + "sha256": "cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50" + }, + { + "hex": "0101010101010101", + "hash160": "abaf1119f83e384210fe8e222eac76e2f0da39dc", + "hash256": "728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23", + "ripemd160": "5825701b4b9767fd35063b286dca3582853e0630", + "sha1": "c0357a32ed1f6a03be92dd094476f7f1a2e214ec", + "sha256": "04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061" + }, + { + "hex": "ffffffffffffffff", + "hash160": "f86221f5a1fca059a865c0b7d374dfa9d5f3aeb4", + "hash256": "752adad0a7b9ceca853768aebb6965eca126a62965f698a0c1bc43d83db632ad", + "ripemd160": "cb760221600ed34337ca3ab70016b5f58c838120", + "sha1": "be673e8a56eaa9d8c1d35064866701c11ef8e089", + "sha256": "12a3ae445661ce5dee78d0650d33362dec29c4f82af05e7e57fb595bbbacf0ca" + }, + { + "hex": "4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e65632061742066617563696275732073617069656e2c2076656c20666163696c6973697320617263752e20536564207574206d61737361206e6962682e205574206d6f6c6c69732070756c76696e6172206d617373612e20557420756c6c616d636f7270657220646f6c6f7220656e696d2c20696e206d6f6c657374696520656e696d20636f6e64696d656e74756d2061632e20416c697175616d206572617420766f6c75747061742e204e756c6c6120736f64616c657320617420647569206e656320", + "hash160": "9763e6b367c363bd6b88a7b361c98e6beee243a5", + "hash256": "033588797115feb3545052670cac2a46584ab3cb460de63756ee0275e66b5799", + "ripemd160": "cad8593dcdef12ee334c97bab9787f07b3f3a1a5", + "sha1": "10d96fb43aca84e342206887bbeed3065d4e4344", + "sha256": "a7fb8276035057ed6479c5f2305a96da100ac43f0ac10f277e5ab8c5457429da" + } +] diff --git a/test/fixtures/ecdsa.json b/test/fixtures/ecdsa.json index 57814c280..780ad74bb 100644 --- a/test/fixtures/ecdsa.json +++ b/test/fixtures/ecdsa.json @@ -5,70 +5,63 @@ "d": "01", "k": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5", "message": "Everything should be made as simple as possible, but not simpler.", - "i": 0, "signature": { - "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", - "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" + "r": "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c9", + "s": "6f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262" } }, { "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "k": "9dc74cbfd383980fb4ae5d2680acddac9dac956dca65a28c80ac9c847c2374e4", "message": "Equations are more important to me, because politics is for the present, but an equation is something for eternity.", - "i": 0, "signature": { - "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", - "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" + "r": "54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed", + "s": "07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5" } }, { "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "k": "fd27071f01648ebbdd3e1cfbae48facc9fa97edc43bbbc9a7fdc28eae13296f5", "message": "Not only is the Universe stranger than we think, it is stranger than we can think.", - "i": 0, "signature": { - "r": "115464191557905790016094131873849783294273568009648050793030031933291767741904", - "s": "50562520307781850052192542766631199590053690478900449960232079510155113443971" + "r": "ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd0", + "s": "6fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283" } }, { "d": "0000000000000000000000000000000000000000000000000000000000000001", "k": "f0cd2ba5fc7c183de589f6416220a36775a146740798756d8d949f7166dcc87f", "message": "How wonderful that we have met with a paradox. Now we have some hope of making progress.", - "i": 1, "signature": { - "r": "87230998027579607140680851455601772643840468630989315269459846730712163783123", - "s": "53231320085894623106179381504478252331065330583563809963303318469380290929875" + "r": "c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3", + "s": "75afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3" } }, { "d": "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64", "k": "6bb4a594ad57c1aa22dbe991a9d8501daf4688bf50a4892ef21bd7c711afda97", "message": "Computer science is no more about computers than astronomy is about telescopes.", - "i": 0, "signature": { - "r": "51348483531757779992459563033975330355971795607481991320287437101831125115997", - "s": "6277080015686056199074771961940657638578000617958603212944619747099038735862" + "r": "7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d", + "s": "0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6" } }, { "d": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637", "k": "097b5c8ee22c3ea78a4d3635e0ff6fe85a1eb92ce317ded90b9e71aab2b861cb", "message": "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough", - "i": 1, "signature": { - "r": "113979859486826658566290715281614250298918272782414232881639314569529560769671", - "s": "6517071009538626957379450615706485096874328019806177698938278220732027419959" + "r": "fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda487", + "s": "0e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37" } }, { "d": "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3", "k": "19355c36c8cbcdfb2382e23b194b79f8c97bf650040fc7728dfbf6b39a97c25b", "message": "The question of whether computers can think is like the question of whether submarines can swim.", - "i": 1, "signature": { - "r": "93122007060065279508564838030979550535085999589142852106617159184757394422777", - "s": "3078539468410661027472930027406594684630312677495124015420811882501887769839" + "r": "cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9", + "s": "06ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef" } } ], @@ -146,86 +139,14 @@ ] }, "invalid": { - "recoverPubKey": [ - { - "description": "Invalid r value (< 0)", - "exception": "Invalid r value", - "e": "01", - "signature": { - "r": "-01", - "s": "02" - }, - "i": 0 - }, - { - "description": "Invalid r value (== 0)", - "exception": "Invalid r value", - "e": "01", - "signature": { - "r": "00", - "s": "02" - }, - "i": 0 - }, - { - "description": "Invalid s value (< 0)", - "exception": "Invalid s value", - "e": "01", - "signature": { - "r": "02", - "s": "-01" - }, - "i": 0 - }, - { - "description": "Invalid s value (== 0)", - "exception": "Invalid s value", - "e": "01", - "signature": { - "r": "02", - "s": "00" - }, - "i": 0 - }, - { - "description": "Invalid r value (nR is infinity)", - "exception": "nR is not a valid curve point", - "e": "01", - "signature": { - "r": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", - "s": "01" - }, - "i": 0 - }, - { - "description": "Invalid curve point", - "exception": "Point is not on the curve", - "e": "01", - "signature": { - "r": "99999999999999999999999999999999999999", - "s": "01" - }, - "i": 0 - }, - { - "description": "Invalid i value (> 3)", - "exception": "Recovery param is more than two bits", - "e": "01", - "signature": { - "r": "00", - "s": "02" - }, - "i": 4 - } - ], - "verifyRaw": [ + "verify": [ { "description": "The wrong signature", "d": "01", "message": "foo", "signature": { - "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", - "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" + "r": "54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed", + "s": "07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5" } }, { @@ -287,8 +208,8 @@ "d": "01", "message": "foo", "signature": { - "r": "-115792089237316195423570985008687907852837564279074904382605163141518161494337", - "s": "-115792089237316195423570985008687907852837564279074904382605163141518161494337" + "r": "-fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "s": "-fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" } } ] diff --git a/test/fixtures/eckey.json b/test/fixtures/eckey.json deleted file mode 100644 index 7d1c4a4c0..000000000 --- a/test/fixtures/eckey.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "valid": [ - { - "d": "1", - "Q": "(55066263022277343669578718895168534326250603453777594175500187360389116729240,32670510020758816978083085130507043184471273380659243275938904335757337482424)", - "WIFs": [ - { - "network": "bitcoin", - "string": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", - "compressed": true - }, - { - "network": "bitcoin", - "string": "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf", - "compressed": false - } - ] - }, - { - "d": "19898843618908353587043383062236220484949425084007183071220218307100305431102", - "Q": "(83225686012142088543596389522774768397204444195709443235253141114409346958144,23739058578904784236915560265041168694780215705543362357495033621678991351768)", - "WIFs": [ - { - "network": "bitcoin", - "string": "KxhEDBQyyEFymvfJD96q8stMbJMbZUb6D1PmXqBWZDU2WvbvVs9o", - "compressed": true - } - ] - }, - { - "d": "48968302285117906840285529799176770990048954789747953886390402978935544927851", - "Q": "(30095590000961171681152428142595206241714764354580127609094760797518133922356,93521207164355458151597931319591130635754976513751247168472016818884561919702)", - "WIFs": [ - { - "network": "bitcoin", - "string": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", - "compressed": true - }, - { - "network": "bitcoin", - "string": "5JdxzLtFPHNe7CAL8EBC6krdFv9pwPoRo4e3syMZEQT9srmK8hh", - "compressed": false - }, - { - "network": "testnet", - "string": "cRD9b1m3vQxmwmjSoJy7Mj56f4uNFXjcWMCzpQCEmHASS4edEwXv", - "compressed": true - }, - { - "network": "testnet", - "string": "92Qba5hnyWSn5Ffcka56yMQauaWY6ZLd91Vzxbi4a9CCetaHtYj", - "compressed": false - } - ] - }, - { - "d": "115792089237316195423570985008687907852837564279074904382605163141518161494336", - "Q": "(55066263022277343669578718895168534326250603453777594175500187360389116729240,83121579216557378445487899878180864668798711284981320763518679672151497189239)", - "WIFs": [ - { - "network": "bitcoin", - "string": "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9", - "compressed": true - } - ] - } - ], - "invalid": { - "constructor": [ - { - "exception": "Private key must be greater than 0", - "d": "-1" - }, - { - "exception": "Private key must be greater than 0", - "d": "0" - }, - { - "exception": "Private key must be less than the curve order", - "d": "115792089237316195423570985008687907852837564279074904382605163141518161494337" - }, - { - "exception": "Private key must be less than the curve order", - "d": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "WIF": [ - { - "exception": "Invalid compression flag", - "string": "ju9rooVsmagsb4qmNyTysUSFB1GB6MdpD7eoGjUTPmZRAApJxRz" - }, - { - "exception": "Invalid WIF payload length", - "string": "7ZEtRQLhCsDQrd6ZKfmcESdXgas8ggZPN24ByEi5ey6VJW" - }, - { - "exception": "Invalid WIF payload length", - "string": "5qibUKwsnMo1qDiNp3prGaQkD2JfVJa8F8Na87H2CkMHvuVg6uKhw67Rh" - } - ] - } -} diff --git a/test/fixtures/ecpair.json b/test/fixtures/ecpair.json new file mode 100644 index 000000000..cd8354409 --- /dev/null +++ b/test/fixtures/ecpair.json @@ -0,0 +1,141 @@ +{ + "valid": [ + { + "d": "0000000000000000000000000000000000000000000000000000000000000001", + "Q": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "compressed": true, + "network": "bitcoin", + "address": "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH", + "WIF": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + }, + { + "d": "0000000000000000000000000000000000000000000000000000000000000001", + "Q": "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + "compressed": false, + "network": "bitcoin", + "address": "1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm", + "WIF": "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf" + }, + { + "d": "2bfe58ab6d9fd575bdc3a624e4825dd2b375d64ac033fbc46ea79dbab4f69a3e", + "Q": "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", + "compressed": true, + "network": "bitcoin", + "address": "1MasfEKgSiaSeri2C6kgznaqBNtyrZPhNq", + "WIF": "KxhEDBQyyEFymvfJD96q8stMbJMbZUb6D1PmXqBWZDU2WvbvVs9o" + }, + { + "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", + "Q": "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34", + "compressed": true, + "network": "bitcoin", + "address": "1LwwMWdSEMHJ2dMhSvAHZ3g95tG2UBv9jg", + "WIF": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx" + }, + { + "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", + "Q": "044289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34cec320a0565fb7caf11b1ca2f445f9b7b012dda5718b3cface369ee3a034ded6", + "compressed": false, + "network": "bitcoin", + "address": "1zXcfvKCLgsFdJDYPuqpu1sF3q92tnnUM", + "WIF": "5JdxzLtFPHNe7CAL8EBC6krdFv9pwPoRo4e3syMZEQT9srmK8hh" + }, + { + "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", + "Q": "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34", + "compressed": true, + "network": "testnet", + "address": "n1TteZiR3NiYojqKAV8fNxtTwsrjM7kVdj", + "WIF": "cRD9b1m3vQxmwmjSoJy7Mj56f4uNFXjcWMCzpQCEmHASS4edEwXv" + }, + { + "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", + "Q": "044289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34cec320a0565fb7caf11b1ca2f445f9b7b012dda5718b3cface369ee3a034ded6", + "compressed": false, + "network": "testnet", + "address": "mgWUuj1J1N882jmqFxtDepEC73Rr22E9GU", + "WIF": "92Qba5hnyWSn5Ffcka56yMQauaWY6ZLd91Vzxbi4a9CCetaHtYj" + }, + { + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "Q": "0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "compressed": true, + "network": "bitcoin", + "address": "1GrLCmVQXoyJXaPJQdqssNqwxvha1eUo2E", + "WIF": "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9" + } + ], + "invalid": { + "fromPrivateKey": [ + { + "exception": "Private key not in range \\[1, n\\)", + "d": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "exception": "Private key not in range \\[1, n\\)", + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" + }, + { + "exception": "Private key not in range \\[1, n\\)", + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142" + }, + { + "exception": "Expected property \"compressed\" of type \\?Boolean, got Number 2", + "d": "0000000000000000000000000000000000000000000000000000000000000001", + "options": { + "compressed": 2 + } + }, + { + "exception": "Expected property \"network.messagePrefix\" of type Buffer|String, got undefined", + "d": "0000000000000000000000000000000000000000000000000000000000000001", + "options": { + "network": {} + } + } + ], + "fromPublicKey": [ + { + "exception": "Expected isPoint, got Buffer", + "Q": "", + "options": {} + }, + { + "exception": "Expected property \"network.messagePrefix\" of type Buffer|String, got undefined", + "Q": "044289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34cec320a0565fb7caf11b1ca2f445f9b7b012dda5718b3cface369ee3a034ded6", + "options": { + "network": {} + } + }, + { + "description": "Bad X coordinate (== P)", + "exception": "Expected isPoint, got Buffer", + "Q": "040000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "options": {} + } + ], + "fromWIF": [ + { + "exception": "Invalid network version", + "network": "bitcoin", + "WIF": "92Qba5hnyWSn5Ffcka56yMQauaWY6ZLd91Vzxbi4a9CCetaHtYj" + }, + { + "exception": "Unknown network version", + "WIF": "brQnSed3Fia1w9VcbbS6ZGDgJ6ENkgwuQY2LS7pEC5bKHD1fMF" + }, + { + "exception": "Invalid compression flag", + "WIF": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sfZr2ym" + }, + { + "exception": "Invalid WIF length", + "WIF": "3tq8Vmhh9SN5XhjTGSWgx8iKk59XbKG6UH4oqpejRuJhfYD" + }, + { + "exception": "Invalid WIF length", + "WIF": "38uMpGARR2BJy5p4dNFKYg9UsWNoBtkpbdrXDjmfvz8krCtw3T1W92ZDSR" + } + ] + } +} diff --git a/test/fixtures/ecpubkey.json b/test/fixtures/ecpubkey.json deleted file mode 100644 index 87b8aa2ed..000000000 --- a/test/fixtures/ecpubkey.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Q": { - "x": "55066263022277343669578718895168534326250603453777594175500187360389116729240", - "y": "32670510020758816978083085130507043184471273380659243275938904335757337482424" - }, - "compressed": { - "hex": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "hash160": "751e76e8199196d454941c45d1b3a323f1433bd6" - }, - "uncompressed": { - "hex": "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", - "hash160": "91b24bf9f5288532960ac687abb035127b1d28a5" - }, - "message": "vires in numeris", - "signature": { - "r": "68972263025625296948424563184904289678530916807200550828762374724416876919710", - "s": "43478152510424186005054433052302509227777805602212468112169549534899266476898" - } -} diff --git a/test/fixtures/ecsignature.json b/test/fixtures/ecsignature.json deleted file mode 100644 index 2c72182cd..000000000 --- a/test/fixtures/ecsignature.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "valid": [ - { - "compact": { - "hex": "1f33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c96f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262", - "compressed": true, - "i": 0 - }, - "scriptSignature": { - "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa5434226201", - "hashType": 1 - }, - "DER": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262", - "signature": { - "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", - "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" - } - }, - { - "compact": { - "hex": "1b54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5", - "compressed": false, - "i": 0 - }, - "DER": "3044022054c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed022007082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5", - "scriptSignature": { - "hex": "3044022054c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed022007082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a502", - "hashType": 2 - }, - "signature": { - "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", - "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" - } - }, - { - "compact": { - "hex": "1fff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd06fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283", - "compressed": true, - "i": 0 - }, - "scriptSignature": { - "hex": "3045022100ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd002206fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b28303", - "hashType": 3 - }, - "DER": "3045022100ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd002206fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283", - "signature": { - "r": "115464191557905790016094131873849783294273568009648050793030031933291767741904", - "s": "50562520307781850052192542766631199590053690478900449960232079510155113443971" - } - }, - { - "compact": { - "hex": "1cc0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d375afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3", - "compressed": false, - "i": 1 - }, - "scriptSignature": { - "hex": "3045022100c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3022075afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d381", - "hashType": 129 - }, - "DER": "3045022100c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3022075afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3", - "signature": { - "r": "87230998027579607140680851455601772643840468630989315269459846730712163783123", - "s": "53231320085894623106179381504478252331065330583563809963303318469380290929875" - } - }, - { - "compact": { - "hex": "1f7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6", - "compressed": true, - "i": 0 - }, - "scriptSignature": { - "hex": "304402207186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d02200de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df682", - "hashType": 130 - }, - "DER": "304402207186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d02200de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6", - "signature": { - "r": "51348483531757779992459563033975330355971795607481991320287437101831125115997", - "s": "6277080015686056199074771961940657638578000617958603212944619747099038735862" - } - }, - { - "compact": { - "hex": "1cfbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda4870e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37", - "compressed": false, - "i": 1 - }, - "scriptSignature": { - "hex": "3045022100fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda48702200e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd3783", - "hashType": 131 - }, - "DER": "3045022100fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda48702200e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37", - "signature": { - "r": "113979859486826658566290715281614250298918272782414232881639314569529560769671", - "s": "6517071009538626957379450615706485096874328019806177698938278220732027419959" - } - }, - { - "compact": { - "hex": "20cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf906ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef", - "compressed": true, - "i": 1 - }, - "scriptSignature": { - "hex": "3045022100cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9022006ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef81", - "hashType": 129 - }, - "DER": "3045022100cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9022006ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef", - "signature": { - "r": "93122007060065279508564838030979550535085999589142852106617159184757394422777", - "s": "3078539468410661027472930027406594684630312677495124015420811882501887769839" - } - } - ], - "invalid": { - "compact": [ - { - "exception": "Invalid signature parameter", - "hex": "23987ceade6a304fc5823ab38f99fc3c5f772a2d3e89ea05931e2726105fc53b9e601fc3231f35962c714fcbce5c95b427496edc7ae8b3d12e93791d7629795b62" - }, - { - "exception": "Invalid signature length", - "hex": "1c987ceade6a304fc5823ab38f99fc3c5f772a2d3e89ea05931e2726105fc53b9e601fc3231f35962c714fcbce5c95b427496edc7ae8b3d12e93791d7629795b62000000" - }, - { - "exception": "Invalid signature length", - "hex": "1c987ceade6a304fc5823ab38f99fc3c5f772a2d3e89ea05931e2726105fc53b9e601fc3231f35962c714fcbce5c95b427496edc7ae8b3d12e9379" - } - ], - "DER": [ - { - "exception": "Invalid sequence length", - "hex": "30ff020400ffffff020400ffffff" - }, - { - "exception": "Invalid sequence length", - "hex": "300c030400ffffff030400ffffff0000" - }, - { - "exception": "Expected a DER integer", - "hex": "300cff0400ffffff020400ffffff" - }, - { - "exception": "Expected a DER integer \\(2\\)", - "hex": "300c020200ffffff020400ffffff" - }, - { - "exception": "Invalid DER encoding", - "hex": "300c020400ffffff020200ffffff" - }, - { - "exception": "R length is zero", - "hex": "30080200020400ffffff" - }, - { - "exception": "S length is zero", - "hex": "3008020400ffffff0200" - }, - { - "exception": "R value is negative", - "hex": "300c0204ffffffff020400ffffff" - }, - { - "exception": "S value is negative", - "hex": "300c020400ffffff0204ffffffff" - }, - { - "exception": "R value excessively padded", - "hex": "300c02040000ffff020400ffffff" - }, - { - "exception": "S value excessively padded", - "hex": "300c020400ffffff02040000ffff" - } - ], - "scriptSignature": [ - { - "exception": "Invalid hashType 7", - "hashType": 7, - "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa5434226207", - "signature": { - "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", - "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" - } - }, - { - "exception": "Invalid hashType 140", - "hashType": 140, - "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa543422628c", - "signature": { - "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", - "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" - } - } - ] - } -} diff --git a/test/fixtures/embed.json b/test/fixtures/embed.json new file mode 100644 index 000000000..40e43cdc7 --- /dev/null +++ b/test/fixtures/embed.json @@ -0,0 +1,72 @@ +{ + "valid": [ + { + "description": "output from output", + "arguments": { + "output": "OP_RETURN a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4" + }, + "options": {}, + "expected": { + "data": [ + "a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4" + ], + "output": "OP_RETURN a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4", + "input": null, + "witness": null + } + }, + { + "description": "output from data", + "arguments": { + "data": [ + "a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4" + ] + }, + "expected": { + "data": [ + "a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4" + ], + "output": "OP_RETURN a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4", + "input": null, + "witness": null + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "description": "First OP is not OP_RETURN", + "exception": "Output is invalid", + "options": {}, + "arguments": { + "output": "OP_1 OP_2 OP_ADD" + } + } + ], + "dynamic": { + "depends": { + "data": [ "data", "output" ], + "output": [ "output", "data" ] + }, + "details": [ + { + "description": "embed", + "data": [ + "a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4" + ], + "output": "OP_RETURN a3b147dbe4a85579fc4b5a1811e76620560e07267e62b9a0d6858f9127735cadd82f67e06c24dbc4" + }, + { + "description": "embed", + "data": [ + "a3b147dbe4a85579fc4b5a1811e76620560e0726", + "7e62b9a0d6858f9127735cadd82f67e06c24dbc4" + ], + "output": "OP_RETURN a3b147dbe4a85579fc4b5a1811e76620560e0726 7e62b9a0d6858f9127735cadd82f67e06c24dbc4" + } + ] + } +} diff --git a/test/fixtures/hdnode.json b/test/fixtures/hdnode.json deleted file mode 100644 index 797f940bd..000000000 --- a/test/fixtures/hdnode.json +++ /dev/null @@ -1,238 +0,0 @@ -{ - "valid": [ - { - "network": "bitcoin", - "master": { - "seed": "000102030405060708090a0b0c0d0e0f", - "wif": "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW", - "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", - "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", - "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", - "hexPriv": "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", - "base58": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", - "base58Priv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", - "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", - "fingerprint": "3442193e", - "address": "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma" - }, - "children": [ - { - "description": "m/0'", - "m": 0, - "hardened": true, - "wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT", - "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", - "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", - "base58": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", - "base58Priv": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", - "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", - "fingerprint": "5c1bd648", - "address": "19Q2WoS5hSS6T8GjhK8KZLMgmWaq4neXrh" - }, - { - "description": "m/0'/1", - "m": 1, - "wif": "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM", - "pubKey": "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", - "chainCode": "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19", - "base58": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", - "base58Priv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", - "identifier": "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe", - "fingerprint": "bef5a2f9", - "address": "1JQheacLPdM5ySCkrZkV66G2ApAXe1mqLj" - }, - { - "description": "m/0'/1/2'", - "m": 2, - "hardened": true, - "wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU", - "pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", - "chainCode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", - "base58": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", - "base58Priv": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", - "identifier": "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72", - "fingerprint": "ee7ab90c", - "address": "1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x" - }, - { - "description": "m/0'/1/2'/2", - "m": 2, - "wif": "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR", - "pubKey": "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", - "chainCode": "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd", - "base58": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", - "base58Priv": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", - "identifier": "d880d7d893848509a62d8fb74e32148dac68412f", - "fingerprint": "d880d7d8", - "address": "1LjmJcdPnDHhNTUgrWyhLGnRDKxQjoxAgt" - }, - { - "description": "m/0'/1/2'/2/1000000000", - "m": 1000000000, - "wif": "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs", - "pubKey": "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", - "chainCode": "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e", - "base58": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", - "base58Priv": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", - "identifier": "d69aa102255fed74378278c7812701ea641fdf32", - "fingerprint": "d69aa102", - "address": "1LZiqrop2HGR4qrH1ULZPyBpU6AUP49Uam" - } - ] - }, - { - "network": "bitcoin", - "master": { - "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", - "wif": "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy", - "pubKey": "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", - "chainCode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", - "base58": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", - "base58Priv": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", - "hex": "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", - "hexPriv": "0488ade400000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689004b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e", - "identifier": "bd16bee53961a47d6ad888e29545434a89bdfe95", - "fingerprint": "bd16bee5", - "address": "1JEoxevbLLG8cVqeoGKQiAwoWbNYSUyYjg" - }, - "children": [ - { - "description": "m/0", - "m": 0, - "wif": "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj", - "pubKey": "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", - "chainCode": "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c", - "base58": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", - "base58Priv": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", - "identifier": "5a61ff8eb7aaca3010db97ebda76121610b78096", - "fingerprint": "5a61ff8e", - "address": "19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ" - }, - { - "description": "m/0/2147483647", - "m": 2147483647, - "hardened": true, - "wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr", - "pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", - "chainCode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", - "base58": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", - "base58Priv": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", - "identifier": "d8ab493736da02f11ed682f88339e720fb0379d1", - "fingerprint": "d8ab4937", - "address": "1Lke9bXGhn5VPrBuXgN12uGUphrttUErmk" - }, - { - "description": "m/0/2147483647'/1", - "m": 1, - "wif": "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2", - "pubKey": "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", - "chainCode": "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb", - "base58": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", - "base58Priv": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", - "identifier": "78412e3a2296a40de124307b6485bd19833e2e34", - "fingerprint": "78412e3a", - "address": "1BxrAr2pHpeBheusmd6fHDP2tSLAUa3qsW" - }, - { - "description": "m/0/2147483647'/1/2147483646", - "m": 2147483646, - "hardened": true, - "wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF", - "pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", - "chainCode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", - "base58": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", - "base58Priv": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", - "identifier": "31a507b815593dfc51ffc7245ae7e5aee304246e", - "fingerprint": "31a507b8", - "address": "15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R" - }, - { - "description": "m/0/2147483647'/1/2147483646'/2", - "m": 2, - "wif": "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK", - "pubKey": "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", - "chainCode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271", - "base58": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", - "base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", - "identifier": "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220", - "fingerprint": "26132fdb", - "address": "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt" - } - ] - }, - { - "network": "litecoin", - "master": { - "seed": "000102030405060708090a0b0c0d0e0f", - "wif": "TAroS5Knm8GZcnpPycBgzjwwDLWMyQjDrcuGPPoArgrbW7Ln22qp", - "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", - "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", - "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", - "hexPriv": "019d9cfe000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", - "base58": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491", - "base58Priv": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k", - "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", - "fingerprint": "3442193e", - "address": "LPzGaoLUtXFkmNo3u1chDxGxDnSaBQTTxm" - }, - "children": [ - { - "description": "m/0'", - "m": 0, - "hardened": true, - "wif": "TB22qU2V9EJCVKJ8cdYaTfvDhnYcCzthcWgFm1k6hbvbKM1NLxoL", - "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", - "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", - "base58": "Ltub2UhtRiSfp82berwLEKkB34QBEt2TUdCDCu4WNzGumvAMwYsxfWjULKsXhADxqy3cuDu3TnqoKJr1xmB8Wb2qzthWAtbb4CutpXPuSU1YMgG", - "base58Priv": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx", - "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", - "fingerprint": "5c1bd648", - "address": "LTcyn1jun6g9hvxtsT7cqMRSyix7AULC76" - } - ] - } - ], - "invalid": { - "fromBase58": [ - { - "exception": "Invalid checksum", - "string": "xprvQQQQQQQQQQQQQQQQCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" - }, - { - "exception": "Could not find network for 0", - "string": "1111111111111adADjFaSNPxwXqLjHLj4mBfYxuewDPbw9hEj1uaXCzMxRPXDFF3cUoezTFYom4sEmEVSQmENPPR315cFk9YUFVek73wE9" - }, - { - "exception": "Network doesn\\'t match", - "string": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx", - "network": "bitcoin" - } - ], - "fromBuffer": [ - { - "exception": "Invalid buffer length", - "hex": "0488b21e0000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee4" - }, - { - "exception": "Invalid buffer length", - "hex": "0488b21e0000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee35500000000" - }, - { - "exception": "Invalid parent fingerprint", - "hex": "0488b21e00ffffffff000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" - }, - { - "exception": "Invalid index", - "hex": "0488b21e0000000000ffffffff7ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" - }, - { - "exception": "Could not find network for 22222222", - "hex": "222222220000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355" - }, - { - "exception": "Point is not on the curve", - "hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508020045400697100007000037899988826500030092003000016366806305909050" - } - ] - } -} diff --git a/test/fixtures/message.json b/test/fixtures/message.json deleted file mode 100644 index efedb4d1f..000000000 --- a/test/fixtures/message.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "valid": { - "magicHash": [ - { - "network": "bitcoin", - "message": "", - "magicHash": "80e795d4a4caadd7047af389d9f7f220562feb6196032e2131e10563352c4bcc" - }, - { - "network": "bitcoin", - "message": "Vires is Numeris", - "magicHash": "f8a5affbef4a3241b19067aa694562f64f513310817297089a8929a930f4f933" - }, - { - "network": "dogecoin", - "message": "Vires is Numeris", - "magicHash": "c0963d20d0accd0ea0df6c1020bf85a7e629a40e7b5363f2c3e9dcafd5638f12" - } - ], - "verify": [ - { - "message": "vires is numeris", - "network": "bitcoin", - "address": "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", - "signature": "G8JawPtQOrybrSP1WHQnQPr67B9S3qrxBrl1mlzoTJOSHEpmnF7D3+t+LX0Xei9J20B5AIdPbeL3AaTBZ4N3bY0=", - "compressed": { - "address": "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs", - "signature": "H8JawPtQOrybrSP1WHQnQPr67B9S3qrxBrl1mlzoTJOSHEpmnF7D3+t+LX0Xei9J20B5AIdPbeL3AaTBZ4N3bY0=" - } - }, - { - "message": "vires is numeris", - "network": "dogecoin", - "address": "DFpN6QqFfUm3gKNaxN6tNcab1FArL9cZLE", - "signature": "H6k+dZwJ8oOei3PCSpdj603fDvhlhQ+sqaFNIDvo/bI+Xh6zyIKGzZpyud6YhZ1a5mcrwMVtTWL+VXq/hC5Zj7s=" - } - ], - "signing": [ - { - "description": "gives equal r, s values irrespective of point compression", - "message": "vires is numeris", - "network": "bitcoin", - "d": "1", - "signature": "HF8nHqFr3K2UKYahhX3soVeoW8W1ECNbr0wfck7lzyXjCS5Q16Ek45zyBuy1Fiy9sTPKVgsqqOuPvbycuVSSVl8=", - "compressed": { - "signature": "IF8nHqFr3K2UKYahhX3soVeoW8W1ECNbr0wfck7lzyXjCS5Q16Ek45zyBuy1Fiy9sTPKVgsqqOuPvbycuVSSVl8=" - } - }, - { - "description": "supports alternative networks", - "message": "vires is numeris", - "network": "dogecoin", - "d": "1", - "signature": "G6k+dZwJ8oOei3PCSpdj603fDvhlhQ+sqaFNIDvo/bI+Xh6zyIKGzZpyud6YhZ1a5mcrwMVtTWL+VXq/hC5Zj7s=" - } - ] - }, - "invalid": { - "verify": [ - { - "description": "will fail for the wrong message", - "message": "foobar", - "address": "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", - "signature": "G8JawPtQOrybrSP1WHQnQPr67B9S3qrxBrl1mlzoTJOSHEpmnF7D3+t+LX0Xei9J20B5AIdPbeL3AaTBZ4N3bY0=" - }, - { - "description": "will fail for the wrong address", - "message": "vires is numeris", - "address": "1111111111111111111114oLvT2", - "signature": "H8JawPtQOrybrSP1WHQnQPr67B9S3qrxBrl1mlzoTJOSHEpmnF7D3+t+LX0Xei9J20B5AIdPbeL3AaTBZ4N3bY0=" - }, - { - "description": "does not cross verify (uncompressed address, compressed signature)", - "message": "vires is numeris", - "address": "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", - "signature": "H8JawPtQOrybrSP1WHQnQPr67B9S3qrxBrl1mlzoTJOSHEpmnF7D3+t+LX0Xei9J20B5AIdPbeL3AaTBZ4N3bY0=" - }, - { - "description": "does not cross verify (compressed address, uncompressed signature)", - "message": "vires is numeris", - "address": "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs", - "signature": "G8JawPtQOrybrSP1WHQnQPr67B9S3qrxBrl1mlzoTJOSHEpmnF7D3+t+LX0Xei9J20B5AIdPbeL3AaTBZ4N3bY0=" - } - ] - } -} diff --git a/test/fixtures/network.json b/test/fixtures/network.json deleted file mode 100644 index b638221c9..000000000 --- a/test/fixtures/network.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "valid": { - "constants": [ - { - "network": "bitcoin", - "bip32": { - "private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", - "public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" - } - }, - { - "network": "testnet", - "bip32": { - "private": "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", - "public": "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp" - } - }, - { - "network": "litecoin", - "bip32": { - "private": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k", - "public": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491" - } - }, - { - "network": "dogecoin", - "bip32": { - "private": "dgpv51eADS3spNJh9Gjth94XcPwAczvQaDJs9rqx11kvxKs6r3Ek8AgERHhjLs6mzXQFHRzQqGwqdeoDkZmr8jQMBfi43b7sT3sx3cCSk5fGeUR", - "public": "dgub8kXBZ7ymNWy2S8Q3jNgVjFUm5ZJ3QLLaSTdAA89ukSv7Q6MSXwE14b7Nv6eDpE9JJXinTKc8LeLVu19uDPrm5uJuhpKNzV2kAgncwo6bNpP" - } - }, - { - "network": "viacoin", - "bip32": { - "private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", - "public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" - } - }, - { - "network": "viacointestnet", - "bip32": { - "private": "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m", - "public": "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp" - } - }, - { - "network": "gamerscoin", - "bip32": { - "private": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k", - "public": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491" - } - }, - { - "network": "jumbucks", - "bip32": { - "private": "jprv5eCacBgN4Bz4zYxgVQ7RDt1a3eREhEaj8KjAcJ7YwogxGo2rmBF5kvAQS53JwZpo5wnUmJ9Q7kB6b2gQ1MzC6yaTc188hr6hXZ5t8Ruria1", - "public": "jpub1sBw1hDFtZYND339bReRb1xJbgFj6hJaVYemQgXAW9Dw9bN1JiZLJiUtHLgcTTEs1UgRGFAYm3XQPYsYJbpqj1aYPhrMsNcJHfgdAhvFZBB" - } - }, - { - "network": "zetacoin", - "bip32": { - "private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", - "public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" - } - } - ], - "estimateFee": [ - { - "description": "when txSize < 1kb", - "network": "bitcoin", - "txSize": 1, - "fee": 10000 - }, - { - "description": "when txSize >= 1kb", - "network": "bitcoin", - "txSize": 1000, - "fee": 10000 - }, - { - "description": "rounding", - "network": "bitcoin", - "txSize": 2800, - "fee": 30000 - }, - { - "description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used", - "network": "dogecoin", - "txSize": 1000, - "outputs": [ - { - "value": 100000000 - } - ], - "fee": 100000000 - }, - { - "description": "when not every outputs.value > DUST_SOFT_LIMIT", - "network": "dogecoin", - "txSize": 1000, - "outputs": [ - { - "value": 99999999 - }, - { - "value": 99999999 - } - ], - "fee": 300000000 - }, - { - "description": "rounding", - "network": "dogecoin", - "txSize": 2800, - "fee": 300000000 - }, - { - "description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used", - "network": "litecoin", - "txSize": 1000, - "outputs": [ - { - "value": 100000 - } - ], - "fee": 100000 - }, - { - "description": "when not every outputs.value > DUST_SOFT_LIMIT", - "network": "litecoin", - "txSize": 1000, - "outputs": [ - { - "value": 99999 - }, - { - "value": 99999 - } - ], - "fee": 300000 - }, - { - "description": "rounding", - "network": "litecoin", - "txSize": 2800, - "fee": 300000 - } - ] - } -} diff --git a/test/fixtures/p2ms.json b/test/fixtures/p2ms.json new file mode 100644 index 000000000..f84b4d5db --- /dev/null +++ b/test/fixtures/p2ms.json @@ -0,0 +1,403 @@ +{ + "valid": [ + { + "description": "output from output", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG" + }, + "options": {}, + "expected": { + "m": 2, + "n": 2, + "name": "p2ms(2 of 2)", + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": null, + "input": null, + "witness": null + } + }, + { + "description": "output from m/pubkeys", + "arguments": { + "m": 1, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + }, + "expected": { + "m": 1, + "n": 2, + "name": "p2ms(1 of 2)", + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": null, + "input": null, + "witness": null + } + }, + { + "description": "input/output from m/pubkeys/signatures", + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ] + }, + "expected": { + "m": 2, + "n": 3, + "name": "p2ms(2 of 3)", + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from output/signatures", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "signatures": [ + "300602010002010001", + "300602010102010001" + ] + }, + "expected": { + "m": 2, + "n": 3, + "name": "p2ms(2 of 3)", + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from input/output", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "input": "OP_0 300602010002010001 300602010102010001" + }, + "expected": { + "m": 2, + "n": 3, + "name": "p2ms(2 of 3)", + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from input/output, even if incomplete", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "input": "OP_0 OP_0 300602010102010001" + }, + "options": { + "allowIncomplete": true + }, + "expected": { + "m": 2, + "n": 2, + "name": "p2ms(2 of 2)", + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": [ + 0, + "300602010102010001" + ], + "input": "OP_0 OP_0 300602010102010001", + "witness": [] + } + }, + { + "description": "input/output from output/signatures, even if incomplete", + "arguments": { + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "signatures": [ + 0, + "300602010102010001" + ] + }, + "options": { + "allowIncomplete": true + }, + "expected": { + "m": 2, + "n": 2, + "name": "p2ms(2 of 2)", + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 OP_2 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ], + "signatures": [ + 0, + "300602010102010001" + ], + "input": "OP_0 OP_0 300602010102010001", + "witness": [] + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "m": 2 + } + }, + { + "exception": "Not enough data", + "arguments": { + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + } + }, + { + "description": "Non OP_INT chunk (m)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_RESERVED" + } + }, + { + "description": "Non OP_INT chunk (n)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_1 OP_RESERVED" + } + }, + { + "description": "Missing OP_CHECKMULTISIG", + "exception": "Output is invalid", + "arguments": { + "output": "OP_1 OP_2 OP_RESERVED" + } + }, + { + "description": "m is 0", + "exception": "Output is invalid", + "arguments": { + "output": "OP_0 OP_2 OP_CHECKMULTISIG" + } + }, + { + "description": "n is 0 (m > n)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_2 OP_0 OP_CHECKMULTISIG" + } + }, + { + "description": "m > n", + "exception": "Output is invalid", + "arguments": { + "output": "OP_3 OP_2 OP_CHECKMULTISIG" + } + }, + { + "description": "n !== output pubkeys", + "exception": "Output is invalid", + "options": {}, + "arguments": { + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_2 OP_CHECKMULTISIG" + } + }, + { + "description": "Non-canonical output public key", + "exception": "Output is invalid", + "arguments": { + "output": "OP_1 ffff OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "n mismatch", + "arguments": { + "n": 2, + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "m mismatch", + "arguments": { + "m": 2, + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000001 OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "Pubkeys mismatch", + "options": {}, + "arguments": { + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "output": "OP_1 030000000000000000000000000000000000000000000000000000000000000002 OP_1 OP_CHECKMULTISIG" + } + }, + { + "exception": "Pubkey count mismatch", + "arguments": { + "m": 2, + "n": 3, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + } + }, + { + "exception": "Pubkey count cannot be less than m", + "arguments": { + "m": 4, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "exception": "Not enough signatures provided", + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "signatures": [ + "300602010002010001" + ] + } + }, + { + "exception": "Signature mismatch", + "arguments": { + "m": 1, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "signatures": [ + "300602010002010001" + ], + "input": "OP_0 300602010002010101" + } + }, + { + "exception": "Too many signatures provided", + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "signatures": [ + "300602010002010001", + "300602010002010001", + "300602010002010001" + ] + } + }, + { + "description": "Missing OP_0", + "exception": "Input is invalid", + "options": {}, + "arguments": { + "m": 2, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "input": "OP_RESERVED" + } + }, + { + "exception": "Input has invalid signature\\(s\\)", + "arguments": { + "m": 1, + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "input": "OP_0 ffffffffffffffff" + } + } + ], + "dynamic": { + "depends": { + "m": [ "output" ], + "n": [ "output", [ "m", "pubkeys" ] ], + "output": [ "output", [ "m", "pubkeys" ] ], + "pubkeys": [ "output" ], + "signatures": [ ["input", "output"] ], + "input": [ ["signatures", "output"] ], + "witness": [ ["input", "output"] ] + }, + "details": [ + { + "description": "p2ms", + "m": 2, + "n": 3, + "output": "OP_2 030000000000000000000000000000000000000000000000000000000000000001 030000000000000000000000000000000000000000000000000000000000000002 030000000000000000000000000000000000000000000000000000000000000003 OP_3 OP_CHECKMULTISIG", + "pubkeys": [ + "030000000000000000000000000000000000000000000000000000000000000001", + "030000000000000000000000000000000000000000000000000000000000000002", + "030000000000000000000000000000000000000000000000000000000000000003" + ], + "signatures": [ + "300602010002010001", + "300602010102010001" + ], + "input": "OP_0 300602010002010001 300602010102010001", + "witness": [] + } + ] + } +} diff --git a/test/fixtures/p2pk.json b/test/fixtures/p2pk.json new file mode 100644 index 000000000..f3982d327 --- /dev/null +++ b/test/fixtures/p2pk.json @@ -0,0 +1,160 @@ +{ + "valid": [ + { + "description": "output from output", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG" + }, + "options": {}, + "expected": { + "name": "p2pk", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from pubkey", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "name": "p2pk", + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "input/output from output/signature", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "signature": "300602010002010001" + }, + "expected": { + "name": "p2pk", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "300602010002010001", + "witness": [] + } + }, + { + "description": "input/output from pubkey/signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001" + }, + "expected": { + "name": "p2pk", + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "input": "300602010002010001", + "witness": [] + } + }, + { + "description": "input/output from input/output", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "input": "300602010002010001" + }, + "expected": { + "name": "p2pk", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "witness": [] + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "description": "Non-canonical signature", + "exception": "Expected property \"signature\" of type \\?isCanonicalScriptSignature, got Buffer", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "3044" + } + }, + { + "description": "Unexpected OP_RESERVED", + "exception": "Output is invalid", + "arguments": { + "output": "OP_RESERVED" + } + }, + { + "description": "Non-canonical output public key", + "exception": "Output pubkey is invalid", + "arguments": { + "output": "ffff OP_CHECKSIG" + } + }, + { + "description": "Unexpected OP_0 (at end)", + "exception": "Output is invalid", + "arguments": { + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG OP_0" + } + }, + { + "exception": "Pubkey mismatch", + "options": {}, + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "output": "030000000000000000000000000000000000000000000000000000000000000002 OP_CHECKSIG" + } + }, + { + "description": "Too many chunks", + "exception": "Input is invalid", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "300602010002010001 OP_RESERVED" + } + }, + { + "exception": "Input has invalid signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "ffffffffffffffff" + } + }, + { + "exception": "Input has invalid signature", + "arguments": { + "input": "30060201ff0201ff01" + } + }, + { + "exception": "Signature mismatch", + "arguments": { + "signature": "300602010002010001", + "input": "300602010302010301" + } + } + ], + "dynamic": { + "depends": { + "output": [ "pubkey" ], + "pubkey": [ "output" ], + "signature": [ ["input", "output"] ], + "input": [ ["signature", "output"] ], + "witness": [ ["input", "output"] ] + }, + "details": [ + { + "description": "p2pk", + "output": "030000000000000000000000000000000000000000000000000000000000000001 OP_CHECKSIG", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "300602010002010001", + "witness": [] + } + ] + } +} diff --git a/test/fixtures/p2pkh.json b/test/fixtures/p2pkh.json new file mode 100644 index 000000000..4efbb5990 --- /dev/null +++ b/test/fixtures/p2pkh.json @@ -0,0 +1,247 @@ +{ + "valid": [ + { + "description": "output from address", + "arguments": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh" + }, + "options": {}, + "expected": { + "name": "p2pkh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from hash", + "arguments": { + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1" + }, + "expected": { + "name": "p2pkh", + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from output", + "arguments": { + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG" + }, + "expected": { + "name": "p2pkh", + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from pubkey", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "name": "p2pkh", + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "input/output from pubkey/signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001" + }, + "expected": { + "name": "p2pkh", + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001", + "witness": [] + } + }, + { + "description": "input/output from input", + "arguments": { + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "name": "p2pkh", + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "witness": [] + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "signature": "300602010002010001" + } + }, + { + "description": "Unexpected OP_RESERVED", + "exception": "Output is invalid", + "arguments": { + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_RESERVED" + } + }, + { + "description": "Unexpected OP_DUP", + "exception": "Output is invalid", + "options": {}, + "arguments": { + "output": "OP_DUP OP_DUP 168b992bcfc44050310b3a94bd0771136d0b28d137 OP_EQUALVERIFY" + } + }, + { + "description": "Hash too short (too many chunks)", + "exception": "Output is invalid", + "arguments": { + "output": "OP_DUP OP_DUP 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_TRUE OP_EQUALVERIFY" + } + }, + { + "description": "Non-minimally encoded (non BIP62 compliant)", + "exception": "Expected property \"output\" of type Buffer\\(Length: 25\\), got Buffer\\(Length: 26\\)", + "arguments": { + "outputHex": "76a94c14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac" + } + }, + { + "exception": "Invalid version or Network mismatch", + "arguments": { + "address": "3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr" + } + }, + { + "exception": "Invalid address", + "arguments": { + "address": "111111111111111111117K4nzc" + } + }, + { + "exception": "Invalid address", + "arguments": { + "address": "111111111111111111111111133izVn" + } + }, + { + "exception": "Pubkey mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000002" + } + }, + { + "exception": "Input has invalid signature", + "arguments": { + "input": "ffffffffffffffffff 030000000000000000000000000000000000000000000000000000000000000001" + } + }, + { + "exception": "Input has invalid pubkey", + "arguments": { + "input": "300602010002010001 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "Input has unexpected data", + "exception": "Input is invalid", + "arguments": { + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001 ffff" + } + }, + { + "description": "H(pubkey) != H", + "exception": "Hash mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != H", + "exception": "Hash mismatch", + "arguments": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != output.hash", + "exception": "Hash mismatch", + "arguments": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "output": "OP_DUP OP_HASH160 ffffffffffffffffffffffffffffffffffffffff OP_EQUALVERIFY OP_CHECKSIG" + } + }, + { + "description": "output.hash != H", + "exception": "Hash mismatch", + "arguments": { + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "H(input.pubkey) != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001" + } + }, + { + "exception": "Signature mismatch", + "arguments": { + "signature": "300602010002010001", + "input": "300602010302010301 030000000000000000000000000000000000000000000000000000000000000001" + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "pubkey", "input" ], + "hash": [ "address", "output", "hash", "pubkey", "input" ], + "output": [ "address", "output", "hash", "pubkey", "input" ], + "pubkey": [ "input" ], + "signature": [ "input" ], + "input": [ [ "pubkey", "signature" ] ], + "witness": [ "input" ] + }, + "details": [ + { + "description": "p2pkh", + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_DUP OP_HASH160 168b992bcfc44050310b3a94bd0771136d0b28d1 OP_EQUALVERIFY OP_CHECKSIG", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "300602010002010001 030000000000000000000000000000000000000000000000000000000000000001", + "witness": [] + } + ] + } +} diff --git a/test/fixtures/p2sh.json b/test/fixtures/p2sh.json new file mode 100644 index 000000000..21b63cc2e --- /dev/null +++ b/test/fixtures/p2sh.json @@ -0,0 +1,428 @@ +{ + "valid": [ + { + "description": "p2sh-*, out (from address)", + "arguments": { + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT" + }, + "options": {}, + "expected": { + "name": "p2sh", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2sh-*, out (from hash)", + "arguments": { + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086" + }, + "expected": { + "name": "p2sh", + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2sh-*, out (from output)", + "arguments": { + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL" + }, + "expected": { + "name": "p2sh", + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2pkh, out (from redeem)", + "arguments": { + "redeem": { + "address": "this is P2PKH context, unknown and ignored by P2SH", + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG" + } + }, + "expected": { + "name": "p2sh-p2pkh", + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2wpkh, out (from redeem)", + "arguments": { + "redeem": { + "hash": "this is P2WPKH context, unknown and ignored by P2SH", + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400" + } + }, + "expected": { + "name": "p2sh-p2wpkh", + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2pk, out (from redeem)", + "arguments": { + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "pubkey": "this is P2WPKH context, unknown and ignored by P2SH" + } + }, + "expected": { + "name": "p2sh-p2pk", + "address": "36TibC8RrPB9WrBdPoGXhHqDHJosyFVtVQ", + "hash": "3454c084887afe854e80221c69d6282926f809c4", + "output": "OP_HASH160 3454c084887afe854e80221c69d6282926f809c4 OP_EQUAL", + "input": null, + "witness": null + } + }, + { + "description": "p2sh-p2pkh, in and out (from redeem)", + "arguments": { + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + } + }, + "expected": { + "name": "p2sh-p2pkh", + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac", + "witness": [] + } + }, + { + "description": "p2sh-p2wpkh, in and out (from redeem w/ witness)", + "arguments": { + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + }, + "expected": { + "name": "p2sh-p2wpkh", + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "input": "0014c30afa58ae0673b00a45b5c17dff4633780f1400", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + }, + { + "description": "p2sh-p2pk, in and out (from input)", + "arguments": { + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 2103e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058ac" + }, + "expected": { + "name": "p2sh-p2pk", + "address": "36TibC8RrPB9WrBdPoGXhHqDHJosyFVtVQ", + "hash": "3454c084887afe854e80221c69d6282926f809c4", + "output": "OP_HASH160 3454c084887afe854e80221c69d6282926f809c4 OP_EQUAL", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "witness": [] + }, + "witness": [] + } + }, + { + "description": "p2sh-p2wpkh, in and out (from input AND witness)", + "arguments": { + "input": "0014c30afa58ae0673b00a45b5c17dff4633780f1400", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + }, + "expected": { + "name": "p2sh-p2wpkh", + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + } + }, + { + "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": { + "name": "p2sh-p2pkh", + "address": "2N7nfc7zeWuADtpdR4MrR7Wq3dzr7LxTCgS", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "input": null, + "witness": null, + "network": "testnet" + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "description": "Non-minimally encoded (non BIP62 compliant)", + "exception": "Expected property \"output\" of type Buffer\\(Length: 23\\), got Buffer\\(Length: 24\\)", + "arguments": { + "outputHex": "a94c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187" + } + }, + { + "description": "Expected OP_HASH160", + "exception": "Output is invalid", + "options": {}, + "arguments": { + "output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL" + } + }, + { + "description": "Unexpected OP_RESERVED", + "exception": "Output is invalid", + "arguments": { + "output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffff OP_EQUAL OP_RESERVED" + } + }, + { + "description": "address.hash != H", + "exception": "Hash mismatch", + "arguments": { + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != output.hash", + "exception": "Hash mismatch", + "arguments": { + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "output": "OP_HASH160 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL" + } + }, + { + "description": "output.hash != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL" + } + }, + { + "description": "H(redeem.output) != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG" + } + } + }, + { + "exception": "Invalid version or Network mismatch", + "arguments": { + "address": "134D6gYy8DsR5m4416BnmgASuMBqKvogQh" + } + }, + { + "exception": "Invalid address", + "arguments": { + "address": "TYPjCiGbKiwP6r12cdkmVjySbQr7mb3r" + } + }, + { + "exception": "Invalid address", + "arguments": { + "address": "EDaBpuERpLssFzbCV1kgy8tKJsHrcwmzY7HDMF2" + } + }, + { + "exception": "Input too short", + "arguments": { + "input": "" + } + }, + { + "exception": "Input too short", + "arguments": { + "inputHex": "01ff02ff" + } + }, + { + "exception": "Input is invalid", + "arguments": { + "input": "OP_0 OP_0" + } + }, + { + "exception": "Redeem.input mismatch", + "arguments": { + "input": "OP_0 02ffff", + "redeem": { + "input": "OP_CHECKSIG", + "output": "ffff" + } + } + }, + { + "exception": "Redeem.output mismatch", + "arguments": { + "input": "OP_0 02ffff", + "redeem": { + "input": "OP_0", + "output": "fff3" + } + } + }, + { + "exception": "Redeem.output too short", + "arguments": { + "redeem": { + "input": "OP_0", + "output": "" + } + } + }, + { + "exception": "Redeem.output too short", + "arguments": { + "inputHex": "021000" + } + }, + { + "exception": "Witness and redeem.witness mismatch", + "arguments": { + "witness": [ + "3045ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "030000000000000000000000000000000000000000000000000000000000000001" + ], + "redeem": { + "witness": [ + "3045dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "redeem": { + "input": "OP_0", + "output": "ffff" + } + } + }, + { + "exception": "Network mismatch", + "arguments": { + "network": "bitcoin", + "redeem": { + "network": "testnet" + } + } + }, + { + "exception": "Network mismatch", + "arguments": { + "network": "testnet", + "redeem": { + "network": "bitcoin" + } + } + }, + { + "exception": "Empty input", + "arguments": { + "inputHex": "01ff" + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "redeem.output", [ "input", "witness" ] ], + "hash": [ "address", "output", "hash", "redeem.output", [ "input", "witness" ] ], + "output": [ "address", "output", "hash", "redeem.output", [ "input", "witness" ] ], + "redeem.output": [ [ "input", "witness" ] ], + "redeem.input": [ [ "input", "witness" ] ], + "redeem.witness": [ [ "input", "witness" ] ], + "input": [ "redeem" ], + "witness": [ "redeem" ] + }, + "details": [ + { + "description": "p2sh-p2pkh", + "address": "3GETYP4cuSesh2zsPEEYVZqnRedwe4FwUT", + "hash": "9f840a5fc02407ef0ad499c2ec0eb0b942fb0086", + "output": "OP_HASH160 9f840a5fc02407ef0ad499c2ec0eb0b942fb0086 OP_EQUAL", + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "witness": [] + }, + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac", + "witness": [] + }, + { + "description": "p2sh-p2wpkh", + "address": "325CuTNSYmvurXaBmhNFer5zDkKnDXZggu", + "hash": "0432515d8fe8de31be8207987fc6d67b29d5e7cc", + "output": "OP_HASH160 0432515d8fe8de31be8207987fc6d67b29d5e7cc OP_EQUAL", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + }, + "input": "0014c30afa58ae0673b00a45b5c17dff4633780f1400", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + ] + } +} diff --git a/test/fixtures/p2wpkh.json b/test/fixtures/p2wpkh.json new file mode 100644 index 000000000..4d62b6093 --- /dev/null +++ b/test/fixtures/p2wpkh.json @@ -0,0 +1,234 @@ +{ + "valid": [ + { + "description": "output from address", + "arguments": { + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d" + }, + "options": {}, + "expected": { + "name": "p2wpkh", + "hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from hash", + "arguments": { + "hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727" + }, + "expected": { + "name": "p2wpkh", + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from output", + "arguments": { + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727" + }, + "expected": { + "name": "p2wpkh", + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", + "hash": "ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "output from pubkey", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001" + }, + "expected": { + "name": "p2wpkh", + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "signature": null, + "input": null, + "witness": null + } + }, + { + "description": "witness/output from pubkey/signature", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001" + }, + "expected": { + "name": "p2wpkh", + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "input": "", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "description": "witness/output from witness", + "arguments": { + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + }, + "expected": { + "name": "p2wpkh", + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "" + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "exception": "Not enough data", + "arguments": { + "signature": "300602010002010001" + } + }, + { + "exception": "Output is invalid", + "description": "Unexpected OP", + "arguments": { + "output": "OP_RESERVED ea6d525c0c955d90d3dbd29a81ef8bfb79003727" + } + }, + { + "exception": "Pubkey mismatch", + "options": {}, + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000002" + ] + } + }, + { + "exception": "Invalid prefix or Network mismatch", + "arguments": { + "address": "foo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqs30dvv" + } + }, + { + "exception": "Invalid address version", + "arguments": { + "address": "bc1pqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5us4ke" + } + }, + { + "exception": "Invalid address data", + "arguments": { + "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "address": "bc1qafk4yhqvj4wep57m62dgrmutldusqde8adh20d", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "output": "OP_0 ea6d525c0c955d90d3dbd29a81ef8bfb79003727", + "hash": "ffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001" + } + }, + { + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffff", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "exception": "Witness is invalid", + "arguments": { + "witness": [] + } + }, + { + "exception": "Witness has invalid signature", + "arguments": { + "witness": [ + "ffffffffffffffffff", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + }, + { + "exception": "Witness has invalid pubkey", + "arguments": { + "witness": [ + "300602010002010001", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ] + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "pubkey", "witness" ], + "hash": [ "address", "output", "hash", "pubkey", "witness" ], + "output": [ "address", "output", "hash", "pubkey", "witness" ], + "pubkey": [ "witness" ], + "signature": [ "witness" ], + "input": [ "witness" ], + "witness": [ [ "pubkey", "signature" ] ] + }, + "details": [ + { + "description": "p2wpkh", + "address": "bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm", + "hash": "168b992bcfc44050310b3a94bd0771136d0b28d1", + "output": "OP_0 168b992bcfc44050310b3a94bd0771136d0b28d1", + "pubkey": "030000000000000000000000000000000000000000000000000000000000000001", + "signature": "300602010002010001", + "input": "", + "witness": [ + "300602010002010001", + "030000000000000000000000000000000000000000000000000000000000000001" + ] + } + ] + } +} diff --git a/test/fixtures/p2wsh.json b/test/fixtures/p2wsh.json new file mode 100644 index 000000000..0350fe9fb --- /dev/null +++ b/test/fixtures/p2wsh.json @@ -0,0 +1,419 @@ +{ + "valid": [ + { + "description": "p2wsh-*, out (from address)", + "arguments": { + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q" + }, + "options": {}, + "expected": { + "name": "p2wsh", + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-*, out (from hash)", + "arguments": { + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0" + }, + "expected": { + "name": "p2wsh", + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-*, out (from output)", + "arguments": { + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0" + }, + "expected": { + "name": "p2wsh", + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": null, + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2pkh, out (from redeem)", + "arguments": { + "redeem": { + "address": "this is P2PKH context, unknown and ignored by p2wsh", + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG" + } + }, + "expected": { + "name": "p2wsh-p2pkh", + "address": "bc1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ss2cq5ar", + "hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2wpkh, out (from redeem)", + "arguments": { + "redeem": { + "hash": "this is P2WPKH context, unknown and ignored by p2wsh", + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400" + } + }, + "expected": { + "name": "p2wsh-p2wpkh", + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2pk, out (from redeem)", + "arguments": { + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "pubkey": "this is P2WPKH context, unknown and ignored by p2wsh" + } + }, + "expected": { + "name": "p2wsh-p2pk", + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "input": null, + "witness": null + } + }, + { + "description": "p2wsh-p2pkh, in and out (from redeem, transformed to witness)", + "arguments": { + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + } + }, + "expected": { + "name": "p2wsh-p2pkh", + "address": "bc1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ss2cq5ar", + "hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "redeem": { + "input": "" + }, + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac" + ] + } + }, + { + "description": "p2wsh-p2wpkh, in and out (from redeem w/ witness)", + "arguments": { + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + }, + "expected": { + "name": "p2wsh-p2wpkh", + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "0014c30afa58ae0673b00a45b5c17dff4633780f1400" + ] + } + }, + { + "description": "p2wsh-p2pk, in and out (from witness)", + "arguments": { + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "2103e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058ac" + ] + }, + "expected": { + "name": "p2wsh-p2pk", + "address": "bc1q6rgl33d3s9dugudw7n68yrryajkr3ha9q8q24j20zs62se4q9tsqdy0t2q", + "hash": "d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501" + ] + }, + "input": "" + } + }, + { + "description": "p2wsh-p2wpkh, in and out (from witness)", + "arguments": { + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "0014c30afa58ae0673b00a45b5c17dff4633780f1400" + ] + }, + "expected": { + "name": "p2wsh-p2wpkh", + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + } + } + }, + { + "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": { + "name": "p2wsh-p2pkh", + "address": "tb1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ssaskm8v", + "hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "input": null, + "witness": null, + "network": "testnet" + } + } + ], + "invalid": [ + { + "exception": "Not enough data", + "arguments": {} + }, + { + "description": "address.hash != H", + "exception": "Hash mismatch", + "arguments": { + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "address.hash != output.hash", + "exception": "Hash mismatch", + "arguments": { + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "output": "OP_0 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + } + }, + { + "description": "output.hash != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "output": "OP_0 d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0" + } + }, + { + "description": "H(redeem.output) != H", + "exception": "Hash mismatch", + "arguments": { + "hash": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "redeem": { + "output": "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058 OP_CHECKSIG" + } + } + }, + { + "exception": "Output is invalid", + "options": {}, + "arguments": { + "output": "OP_HASH256 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff OP_EQUAL" + } + }, + { + "exception": "Redeem.output is invalid", + "arguments": { + "redeem": { + "output": "" + } + } + }, + { + "exception": "Non push-only scriptSig", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "input": "OP_HASH256" + } + } + }, + { + "exception": "Witness and redeem.output mismatch", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "input": "OP_0" + }, + "witness": [ + "02ffff" + ] + } + }, + { + "exception": "Witness and redeem.witness mismatch", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "witness": [ + "01" + ] + }, + "witness": [ + "00", + "02ffff" + ] + } + }, + { + "exception": "Witness and redeem.witness mismatch", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "witness": [ + "04000000ff" + ] + }, + "witness": [ + "04000000ee" + ] + } + }, + { + "exception": "Ambiguous witness source", + "arguments": { + "redeem": { + "output": "OP_TRUE", + "input": "01", + "witness": [ + "01" + ] + } + } + }, + { + "exception": "Network mismatch", + "arguments": { + "network": "bitcoin", + "redeem": { + "network": "testnet" + } + } + }, + { + "exception": "Network mismatch", + "arguments": { + "network": "testnet", + "redeem": { + "network": "bitcoin" + } + } + }, + { + "exception": "Invalid prefix or Network mismatch", + "arguments": { + "address": "foo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqs30dvv" + } + }, + { + "exception": "Invalid address version", + "arguments": { + "address": "bc1pqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5us4ke" + } + }, + { + "exception": "Invalid address data", + "arguments": { + "address": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8" + } + } + ], + "dynamic": { + "depends": { + "address": [ "address", "output", "hash", "redeem.output", "witness" ], + "hash": [ "address", "output", "hash", "redeem.output", "witness" ], + "output": [ "address", "output", "hash", "redeem.output", "witness" ], + "redeem.output": [ "witness" ], + "redeem.input": [ [ "input", "witness" ], "witness" ], + "input": [ "witness" ], + "witness": [ "redeem" ] + }, + "details": [ + { + "description": "p2wsh-p2pkh", + "disabled": [ + "redeem.input" + ], + "address": "bc1qusxlgq9quu27ucxs7a2fg8nv0pycdzvxsjk9npyupupxw3y892ss2cq5ar", + "hash": "e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "output": "OP_0 e40df400a0e715ee60d0f754941e6c784986898684ac59849c0f026744872aa1", + "redeem": { + "output": "OP_DUP OP_HASH160 c30afa58ae0673b00a45b5c17dff4633780f1400 OP_EQUALVERIFY OP_CHECKSIG", + "input": "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501 03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "witness": null + }, + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "76a914c30afa58ae0673b00a45b5c17dff4633780f140088ac" + ] + }, + { + "description": "p2wsh-p2wpkh", + "address": "bc1qpsl7el8wcx22f3fpdt3lm2wmzug7yyx2q3n8wzgtf37kps9tqy7skc7m3e", + "hash": "0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "output": "OP_0 0c3fecfceec194a4c5216ae3fda9db1711e210ca046677090b4c7d60c0ab013d", + "redeem": { + "output": "OP_0 c30afa58ae0673b00a45b5c17dff4633780f1400", + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058" + ] + }, + "input": "", + "witness": [ + "3045022100e4fce9ec72b609a2df1dc050c20dcf101d27faefb3e686b7a4cb067becdd5e8e022071287fced53806b08cf39b5ad58bbe614775b3776e98a9f8760af0d4d1d47a9501", + "03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058", + "0014c30afa58ae0673b00a45b5c17dff4633780f1400" + ] + } + ] + } +} diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json new file mode 100644 index 000000000..22655da32 --- /dev/null +++ b/test/fixtures/psbt.json @@ -0,0 +1,550 @@ +{ + "bip174": { + "invalid": [ + { + "description": "Network transaction, not PSBT format", + "errorMessage": "Format Error: Invalid Magic Number", + "psbt": "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==" + }, + { + "description": "PSBT missing outputs", + "errorMessage": "Format Error: Unexpected End of PSBT", + "psbt": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==" + }, + { + "description": "PSBT where one input has a filled scriptSig in the unsigned tx", + "errorMessage": "Format Error: Transaction ScriptSigs are not empty", + "psbt": "cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA=" + }, + { + "description": "PSBT where inputs and outputs are provided but without an unsigned tx", + "errorMessage": "Format Error: Only one UNSIGNED_TX allowed", + "psbt": "cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==" + }, + { + "description": "PSBT with duplicate keys in an input", + "errorMessage": "Format Error: Keys must be unique for each input: input index 0 key 00", + "psbt": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQA/AgAAAAH//////////////////////////////////////////wAAAAAA/////wEAAAAAAAAAAANqAQAAAAAAAAAA" + }, + { + "description": "PSBT With invalid global transaction typed key", + "errorMessage": "Format Error: Invalid global key: 0001", + "psbt": "cHNidP8CAAFVAgAAAAEnmiMjpd+1H8RfIg+liw/BPh4zQnkqhdfjbNYzO1y8OQAAAAAA/////wGgWuoLAAAAABl2qRT/6cAGEJfMO2NvLLBGD6T8Qn0rRYisAAAAAAABASCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA" + }, + { + "description": "PSBT With invalid input witness utxo typed key", + "errorMessage": "Format Error: Invalid input key: 0100", + "psbt": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAIBACCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA" + }, + { + "description": "PSBT With invalid pubkey length for input partial signature typed key", + "errorMessage": "Format Error: invalid pubkey in key 0x0203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd", + "psbt": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIQIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYwQwIgBCS1jv+qppThVZ6lyTu/1KiQZCJAVc3wcLZ3FGlELQcCH1yOsP6mUW1guKyzOtZO3mDoeFv7OqlLmb34YVHbmpoBAQQiACB3H9GK1FlmbdSfPVZOPbxC9MhHdONgraFoFqjtSI1WgQEFR1IhA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GIQPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvVKuIgYDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYQtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA==" + }, + { + "description": "PSBT With invalid redeemscript typed key", + "errorMessage": "Format Error: Invalid input key: 0400", + "psbt": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQIEACIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA" + }, + { + "description": "PSBT With invalid witnessscript typed key", + "errorMessage": "Format Error: Invalid input key: 0500", + "psbt": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoECBQBHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA" + }, + { + "description": "PSBT With invalid bip32 typed key", + "errorMessage": "Format Error: invalid pubkey in key 0x0603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd", + "psbt": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriEGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb0QtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA==" + }, + { + "description": "PSBT With invalid non-witness utxo typed key", + "errorMessage": "Format Error: Invalid input key: 0000", + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAIAALsCAAAAAarXOTEBi9JfhK5AC2iEi+CdtwbqwqwYKYur7nGrZW+LAAAAAEhHMEQCIFj2/HxqM+GzFUjUgcgmwBW9MBNarULNZ3kNq2bSrSQ7AiBKHO0mBMZzW2OT5bQWkd14sA8MWUL7n3UYVvqpOBV9ugH+////AoDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSH0PIKJwEAAAAXqRQpynT4oI+BmZQoGFyXtdhS5AY/YYdlAAAAAQfaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + }, + { + "description": "PSBT With invalid final scriptsig typed key", + "errorMessage": "Format Error: Invalid input key: 0700", + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAACBwDaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + }, + { + "description": "PSBT With invalid final script witness typed key", + "errorMessage": "Format Error: Invalid input key: 0800", + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAggA2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + }, + { + "description": "PSBT With invalid pubkey in output BIP 32 derivation paths typed key", + "errorMessage": "Format Error: invalid pubkey in key 0x0203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca587", + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" + }, + { + "description": "PSBT With invalid input sighash type typed key", + "errorMessage": "Format Error: Invalid input key: 0300", + "psbt": "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A" + }, + { + "description": "PSBT With invalid output redeemScript typed key", + "errorMessage": "Format Error: Invalid output key: 0000", + "psbt": "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A" + }, + { + "description": "PSBT With invalid output witnessScript typed key", + "errorMessage": "Format Error: Unexpected End of PSBT", + "psbt": "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A" + } + ], + "valid": [ + { + "description": "PSBT with one P2PKH input. Outputs are empty", + "psbt": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" + }, + { + "description": "PSBT with one P2PKH input and one P2SH-P2WPKH input. First input is signed and finalized. Outputs are empty", + "psbt": "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA" + }, + { + "description": "PSBT with one P2PKH input which has a non-final scriptSig and has a sighash type specified. Outputs are empty", + "psbt": "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==" + }, + { + "description": "PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled.", + "psbt": "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" + }, + { + "description": "PSBT with one P2SH-P2WSH input of a 2-of-2 multisig, redeemScript, witnessScript, and keypaths are available. Contains one signature.", + "psbt": "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=" + }, + { + "description": "PSBT with unknown types in the inputs.", + "psbt": "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=" + } + ], + "failSignChecks": [ + { + "description": "A Witness UTXO is provided for a non-witness input", + "errorMessage": "Input #0 has witnessUtxo but non-segwit script", + "psbt": "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEBItPf9QUAAAAAGXapFNSO0xELlAFMsRS9Mtb00GbcdCVriKwAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=", + "inputToCheck": 0 + }, + { + "description": "redeemScript with non-witness UTXO does not match the scriptPubKey", + "errorMessage": "Redeem script for input #0 doesn't match the scriptPubKey in the prevout", + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq8iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", + "inputToCheck": 0 + }, + { + "description": "redeemScript with witness UTXO does not match the scriptPubKey", + "errorMessage": "Redeem script for input #1 doesn't match the scriptPubKey in the prevout", + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", + "inputToCheck": 1 + }, + { + "description": "witnessScript with witness UTXO does not match the redeemScript", + "errorMessage": "Witness script for input #1 doesn't match the scriptPubKey in the prevout", + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrSIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", + "inputToCheck": 1 + } + ], + "creator": [ + { + "inputs": [ + { + "hash": "75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858", + "index": 0 + }, + { + "hash": "1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83", + "index": 1 + } + ], + "outputs": [ + { + "script": "0014d85c2b71d0060b09c9886aeb815e50991dda124d", + "value": 149990000 + }, + { + "script": "001400aea9a2e5f0f876a588df5546e8742d1d87008f", + "value": 100000000 + } + ], + "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA=" + } + ], + "updater": [ + { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA=", + "inputData": [ + { + "nonWitnessUtxo": "Buffer.from('0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000', 'hex')", + "redeemScript": "Buffer.from('5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae', 'hex')", + "bip32Derivation": [ + { + "masterFingerprint": "Buffer.from('d90c6a4f', 'hex')", + "pubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f', 'hex')", + "path": "m/0'/0'/0'" + }, + { + "masterFingerprint": "Buffer.from('d90c6a4f', 'hex')", + "pubkey": "Buffer.from('02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7', 'hex')", + "path": "m/0'/0'/1'" + } + ] + }, + { + "witnessUtxo": { + "script": "Buffer.from('a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887', 'hex')", + "value": 200000000 + }, + "redeemScript": "Buffer.from('00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903', 'hex')", + "witnessScript": "Buffer.from('522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae', 'hex')", + "bip32Derivation": [ + { + "masterFingerprint": "Buffer.from('d90c6a4f', 'hex')", + "pubkey": "Buffer.from('023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73', 'hex')", + "path": "m/0'/0'/3'" + }, + { + "masterFingerprint": "Buffer.from('d90c6a4f', 'hex')", + "pubkey": "Buffer.from('03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc', 'hex')", + "path": "m/0'/0'/2'" + } + ] + } + ], + "outputData": [ + { + "bip32Derivation": [ + { + "masterFingerprint": "Buffer.from('d90c6a4f', 'hex')", + "pubkey": "Buffer.from('03a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58771', 'hex')", + "path": "m/0'/0'/4'" + } + ] + }, + { + "bip32Derivation": [ + { + "masterFingerprint": "Buffer.from('d90c6a4f', 'hex')", + "pubkey": "Buffer.from('027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b50051096', 'hex')", + "path": "m/0'/0'/5'" + } + ] + } + ], + "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" + }, + { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", + "inputData": [ + { + "sighashType": 1 + }, + { + "sighashType": 1 + } + ], + "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" + } + ], + "signer": [ + { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "keys": [ + { + "inputToSign": 0, + "WIF": "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr" + }, + { + "inputToSign": 1, + "WIF": "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d" + } + ], + "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" + }, + { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "keys": [ + { + "inputToSign": 0, + "WIF": "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au" + }, + { + "inputToSign": 1, + "WIF": "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" + } + ], + "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + } + ], + "combiner": [ + { + "psbts": [ + "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", + "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" + ], + "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" + }, + { + "psbts": [ + "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwA=", + "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA=" + ], + "result": "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8KDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PCg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA=" + } + ], + "finalizer": [ + { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" + } + ], + "extractor": [ + { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", + "transaction": "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000" + } + ] + }, + "addInput": { + "checks": [ + { + "description": "checks for hash and index", + "inputData": { + "hash": 42 + }, + "exception": "Error adding input." + }, + { + "description": "should be equal", + "inputData": { + "hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')", + "index": 2 + }, + "equals": "cHNidP8BADMCAAAAAQABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4PAgAAAAD/////AAAAAAAAAAA=" + } + ] + }, + "addOutput": { + "checks": [ + { + "description": "Checks value is number", + "outputData": { + "address": "1P2NFEBp32V2arRwZNww6tgXEV58FG94mr", + "value": "xyz" + }, + "exception": "Error adding output." + }, + { + "description": "Adds output normally", + "outputData": { + "address": "1P2NFEBp32V2arRwZNww6tgXEV58FG94mr", + "value": 42 + } + } + ] + }, + "signInput": { + "checks": [ + { + "description": "checks the input exists", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + }, + "shouldThrow": { + "errorMessage": "No input #1", + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=", + "inputToCheck": 1, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + } + }, + { + "description": "checks a UTXO value exists for the input", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + }, + "shouldThrow": { + "errorMessage": "Need a Utxo input item for signing", + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + } + }, + { + "description": "checks privkey matches the input it's signing", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + }, + "shouldThrow": { + "errorMessage": "Can not sign for this input with the key 02e717fee6be913148f9fd676c0876b7e4574118542c6758b4a9fb9f38f171842b", + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=", + "inputToCheck": 0, + "WIF": "Kz4mjzErKCH5eQ97RXNQd3Wv7WsLA83BjynfQk4N7BB8J5xuUjAv" + } + }, + { + "description": "checks non-witness UTXO matches the hash specified in the prevout", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + }, + "shouldThrow": { + "errorMessage": "Non-witness UTXO hash for input #0 doesn't match the hash specified in the prevout", + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQD4AQAAAAABAbD7u8z1SxTjfvhwmkQQvdbbWA+n3GKBBmGecSIAaM5jBQAAABcWABS0SqIhdn2LbW4TAJc3GVh7SnD/eP////8CNg8AAAAAAAAWABSmNm8WWVF+wq+QAeRo9d763jEXhRAnAAAAAAAAGXapFNpkc+03Pgj0bdgAP8p7py++nFVeiKwCRzBEAiB/u0BLwdeerqWf0JH33wwMv8Nn3sKblFvj+CntdC4B9gIgKVVHBH1c9ewnzkuyW6dnz1YARujBJnle1eBNSBAJD9IBIQOmYxHmd2Yz53FpC9+nv+pKdM+5OyEAW3BAN2cccQ0LkgAAAAAAAA==", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + } + }, + { + "description": "checks redeem script matches the scriptPubKey in a non-witness prevout", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAAR2dq8JwBaxnbWHZGw0HdxuUGFcg6dvx3pgjWMm+Pzf2AAAAAAD/////AAAAAAAAAQC9AgAAAAH//////////////////////////////////////////wAAAABqRzBEAiAf7N+IK1uuxTxvEOoVGabNsiT7jMlfSDCd0VYxv+sQTQIgQVYM7ig9TIx1LzrX2RXgw2zW2fMKuRs/bT9eZx6jmYwBIQJpKKFOB6PrPJhRAtaQ+cHHryY5QYIi5dxZtkMwCtuFYf////8BAOH1BQAAAAAXqRRdh8wk5NRiF7VGQ4Zb4i8Vl1YFMocAAAAAAQRpUiECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWEhAgHGF3SgP82qhvqMptNluTLHhLtzDsmc0pNWEDETNj/rIQIFIl+T3Z90vBFGN8uYJHCrUO4DvrOGVWkVDsBeEzBUi1OuAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + }, + "shouldThrow": { + "errorMessage": "Redeem script for input #0 doesn't match the scriptPubKey in the prevout", + "psbt": "cHNidP8BADMBAAAAAR2dq8JwBaxnbWHZGw0HdxuUGFcg6dvx3pgjWMm+Pzf2AAAAAAD/////AAAAAAAAAQC9AgAAAAH//////////////////////////////////////////wAAAABqRzBEAiAf7N+IK1uuxTxvEOoVGabNsiT7jMlfSDCd0VYxv+sQTQIgQVYM7ig9TIx1LzrX2RXgw2zW2fMKuRs/bT9eZx6jmYwBIQJpKKFOB6PrPJhRAtaQ+cHHryY5QYIi5dxZtkMwCtuFYf////8BAOH1BQAAAAAXqRRdh8wk5NRiF7VGQ4Zb4i8Vl1YFMocAAAAAAQRpUiEDGMZFrWWJBIIu33FdV9Q+Zit0fcoBOdgS7ooA2h2QlbAhAuAzQeDZh730hBbfTPzlaXJgCh2Jyui/ufS0k8wqJ55FIQKMg6lgEnyRnGIZ90eP4MmuRdT3EcO4+irJEm5yTCiko1OuAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + } + }, + { + "description": "checks redeem script matches the scriptPubKey in a witness prevout", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAAYaq+PdOUY2PnV9kZKa82XlqrPByOqwH2TRg2+LQdqs2AAAAAAD/////AAAAAAAAAQEgAOH1BQAAAAAXqRSTNeWHqa9INvSnQ120SZeJc+6JSocBBBYAFC8spHIOpiw9giaEPd5RGkMYvXRHAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + }, + "shouldThrow": { + "errorMessage": "Redeem script for input #0 doesn't match the scriptPubKey in the prevout", + "psbt": "cHNidP8BADMBAAAAAYaq+PdOUY2PnV9kZKa82XlqrPByOqwH2TRg2+LQdqs2AAAAAAD/////AAAAAAAAAQEgAOH1BQAAAAAXqRSTNeWHqa9INvSnQ120SZeJc+6JSocBBBYAFA3zpl6FMnlgCviVJgbcnBj01iLgAAA=", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + } + }, + { + "description": "checks the sighash type", + "shouldThrow": { + "errorMessage": "Sighash type is not allowed. Retry the sign method passing the sighashTypes array of whitelisted types. Sighash type: SIGHASH_ANYONECANPAY | SIGHASH_ALL", + "psbt": "cHNidP8BADMBAAAAAYaq+PdOUY2PnV9kZKa82XlqrPByOqwH2TRg2+LQdqs2AAAAAAD/////AAAAAAAAAQEgAOH1BQAAAAAXqRSTNeWHqa9INvSnQ120SZeJc+6JSocBAwSBAAAAAQQWABQvLKRyDqYsPYImhD3eURpDGL10RwAA", + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + } + }, + { + "description": "allows signing non-whitelisted sighashtype when explicitly passed in", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAAYaq+PdOUY2PnV9kZKa82XlqrPByOqwH2TRg2+LQdqs2AAAAAAD/////AAAAAAAAAQEgAOH1BQAAAAAXqRSTNeWHqa9INvSnQ120SZeJc+6JSocBAwSBAAAAAQQWABQvLKRyDqYsPYImhD3eURpDGL10RwAA", + "sighashTypes": [129], + "inputToCheck": 0, + "WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni" + } + } + ] + }, + "signInputHD": { + "checks": [ + { + "description": "checks the bip32Derivation exists", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYDn85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=", + "inputToCheck": 0, + "xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev" + }, + "shouldThrow": { + "errorMessage": "Need bip32Derivation to sign with HD", + "psbt": "cHNidP8BADMBAAAAAXVa+rWvBGNyifYXEMlTten9+qC0xuHcAMxQYrQTwX1dAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFC8spHIOpiw9giaEPd5RGkMYvXRHiKwAAAAAAAA=", + "inputToCheck": 0, + "xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev" + } + }, + { + "description": "checks the bip32Derivation exists", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYDn85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=", + "inputToCheck": 0, + "xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev" + }, + "shouldThrow": { + "errorMessage": "Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint", + "psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYD/85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kY/////ywAAIAAAACAAAAAgAAAAAAAAAAAAAA=", + "inputToCheck": 0, + "xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev" + } + }, + { + "description": "checks the bip32Derivation exists", + "shouldSign": { + "psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYDn85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=", + "inputToCheck": 0, + "xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev" + }, + "shouldThrow": { + "errorMessage": "pubkey did not match bip32Derivation", + "psbt": "cHNidP8BADMBAAAAARtEptsZNydT9Bh9A5ptwIZz87yH8NXwzr1bjJorAZEAAAAAAAD/////AAAAAAAAAQDAAgAAAAH//////////////////////////////////////////wAAAABrSDBFAiEAjtCPUj0vx3I5HFQKAUWHN0vCnT17jd41/omb4nobq/sCIAilSeQVi4mqykgBbs+Wz6PyqdMThi2gT463v4kPWk6cASECaSihTgej6zyYUQLWkPnBx68mOUGCIuXcWbZDMArbhWH/////AQDh9QUAAAAAGXapFJWXNY2Vp7E5pNOey64rnhhgUlohiKwAAAAAIgYD/85vSg5rlR25fd4MOU2ANsFoO+q828zuOI/5b8tj89kYBCppsiwAAIAAAACAAAAAgAAAAAAAAAAAAAA=", + "inputToCheck": 0, + "xprv": "xprv9s21ZrQH143K2XNCa3o3tii6nbyJAET6GjTfzcF6roTjAMzLUBe8nt7QHNYqKah8JBv8V67MTWBCqPptRr6khjTSvCUVru78KHW13Viwnev" + } + } + ] + }, + "finalizeAllInputs": [ + { + "type": "P2PK", + "psbt": "cHNidP8BAFUCAAAAAZfdvR7JvbVsuXunG1P19tTYi8Z0l6WHiz+0jpzTNTt+AAAAAAD/////AYA4AQAAAAAAGXapFMjtQ0xCK7OPd32ZJTY2UcdpFIaJiKwAAAAAAAEAygIAAAABb7TMmNs5UwEEpccLHlaaM0w3HEHhh85NjrUObLDMuZsBAAAAa0gwRQIhANWUevBBcqfRpH67rDuFqI+KHqxViKXxmSM+6/jE5ZHjAiBgO/vQg2EsYhaOD5lQbmZYaymRLukA6SCoXhkN21hiOwEhAoKzWGkj1LJ2iOt5wj6jMOtAMGJH50ZjAYZU81uUu6y+/////wGQXwEAAAAAACMhAzpK4NeYX+xyBOKynjD3BEPDq7EN8brZXje488gxshXwrAAAAAAiAgM6SuDXmF/scgTisp4w9wRDw6uxDfG62V43uPPIMbIV8EgwRQIhAL3H/XEPRAZEbpjBwkuLqUKBSu1Inpb2rganXNFcY2JsAiASrXTM2xODEKp7m7RTzYqBchqlvbl88zO/CGW9SePj2gEAAA==", + "result": "cHNidP8BAFUCAAAAAZfdvR7JvbVsuXunG1P19tTYi8Z0l6WHiz+0jpzTNTt+AAAAAAD/////AYA4AQAAAAAAGXapFMjtQ0xCK7OPd32ZJTY2UcdpFIaJiKwAAAAAAAEAygIAAAABb7TMmNs5UwEEpccLHlaaM0w3HEHhh85NjrUObLDMuZsBAAAAa0gwRQIhANWUevBBcqfRpH67rDuFqI+KHqxViKXxmSM+6/jE5ZHjAiBgO/vQg2EsYhaOD5lQbmZYaymRLukA6SCoXhkN21hiOwEhAoKzWGkj1LJ2iOt5wj6jMOtAMGJH50ZjAYZU81uUu6y+/////wGQXwEAAAAAACMhAzpK4NeYX+xyBOKynjD3BEPDq7EN8brZXje488gxshXwrAAAAAABB0lIMEUCIQC9x/1xD0QGRG6YwcJLi6lCgUrtSJ6W9q4Gp1zRXGNibAIgEq10zNsTgxCqe5u0U82KgXIapb25fPMzvwhlvUnj49oBAAA=" + }, + { + "type": "P2PKH", + "psbt": "cHNidP8BAFUCAAAAAaTbj9mE+B5Z8PLsuGUNGOzDqrtwdB08vvSccSCrezh+AAAAAAD/////AYA4AQAAAAAAGXapFDuK+0mR+qtL9tyadI72bKMwH+vuiKwAAAAAAAEAwAIAAAABks7XL87tkSusFA3L2u2CdJeNkqTMobehPNm/wkGQUzoBAAAAa0gwRQIhAMtzqC7axSA7/7nbio9NKQZz2ePuKeaF5T4c8JSXWEdgAiAID39I05hbJMNGSomrF7XVWEsEEHHzX/lkL3vnOOu4cAEhAvu+bfurbDCzxfOLxEhvz9ZyxPLdI1h9wMT8gl2nyLYV/////wGQXwEAAAAAABl2qRTH5239BfS9zrbwUtvpATwjua4LGYisAAAAACICA1oNX0U/6GwAuVI7JHhneD94sm/o+YrfTnRAhdRUMjoISDBFAiEAhk+HbvY6YShBCUmBVCk42sFWH9LTwUv2wbRC/tIuEAwCIED/UkklY3fpdDBN7qSBHFDyEOeHMUzXD0bvtFTAiKP7AQAA", + "result": "cHNidP8BAFUCAAAAAaTbj9mE+B5Z8PLsuGUNGOzDqrtwdB08vvSccSCrezh+AAAAAAD/////AYA4AQAAAAAAGXapFDuK+0mR+qtL9tyadI72bKMwH+vuiKwAAAAAAAEAwAIAAAABks7XL87tkSusFA3L2u2CdJeNkqTMobehPNm/wkGQUzoBAAAAa0gwRQIhAMtzqC7axSA7/7nbio9NKQZz2ePuKeaF5T4c8JSXWEdgAiAID39I05hbJMNGSomrF7XVWEsEEHHzX/lkL3vnOOu4cAEhAvu+bfurbDCzxfOLxEhvz9ZyxPLdI1h9wMT8gl2nyLYV/////wGQXwEAAAAAABl2qRTH5239BfS9zrbwUtvpATwjua4LGYisAAAAAAEHa0gwRQIhAIZPh272OmEoQQlJgVQpONrBVh/S08FL9sG0Qv7SLhAMAiBA/1JJJWN36XQwTe6kgRxQ8hDnhzFM1w9G77RUwIij+wEhA1oNX0U/6GwAuVI7JHhneD94sm/o+YrfTnRAhdRUMjoIAAA=" + }, + { + "type": "P2MS", + "psbt": "cHNidP8BAFUCAAAAAbmLTo7GeyF7IT5PJznDiTVjhv5pQKcKe8Qa+8rqUpXuAAAAAAD/////AYA4AQAAAAAAGXapFF2G82oAzUpx/kcDUPy58goX16gkiKwAAAAAAAEAvgIAAAABMBwIKVKOFbas6tBD24LPchtrweE2jiXkPgSHdJndEfoAAAAAa0gwRQIhAJNchwx4je/ZqtZXSCD6BKJBQkpkTBGGdLoFZTcPKlDtAiAZefY/giaVw5rcEoJk4TRnFCyMG0dYLV5IVnFWkR38fQEhAi5yX8RMW6plVrMY26hithJ6j1w2mgqh/bOCAEVnbn/Z/////wGQXwEAAAAAABepFIoQ3seh+nGYzkI7MDw5zuSCIxEyhwAAAAAiAgLFmuo7gG8OjgmaO5AHuC/1OvDarQt16R7VEKU8dkRRCEgwRQIhAOqqlnUwmc+PqKZVwAAMLPNCKskHhVgk1pXR9d7nmB6SAiA0C24idVdtgl9+sIBR8A3Za5WcswNJ3i8PWTMWR10C4QEiAgPnartRgvSQnSJ2ydRuwSPXDdU3P1YQwhYsK7XzfGuUREgwRQIhANnDBUQWjS8hDTPidRZ95rGZK3LWCLIZB1NYx9++eBT8AiASupLR+KNa1Se7eoBkWQcDny+BaBfMQ+p1RTCAXd7/GAEBBItSIQLFmuo7gG8OjgmaO5AHuC/1OvDarQt16R7VEKU8dkRRCCEC4X74b+vRF3XLgyXyGejmE5VB1f3RY/IReUggyNHgdLYhA+dqu1GC9JCdInbJ1G7BI9cN1Tc/VhDCFiwrtfN8a5REIQO94OZv6yPD4kEZSZBquYA7FPsABZZ6UAteVuEw7fVxzlSuAAA=", + "result": "cHNidP8BAFUCAAAAAbmLTo7GeyF7IT5PJznDiTVjhv5pQKcKe8Qa+8rqUpXuAAAAAAD/////AYA4AQAAAAAAGXapFF2G82oAzUpx/kcDUPy58goX16gkiKwAAAAAAAEAvgIAAAABMBwIKVKOFbas6tBD24LPchtrweE2jiXkPgSHdJndEfoAAAAAa0gwRQIhAJNchwx4je/ZqtZXSCD6BKJBQkpkTBGGdLoFZTcPKlDtAiAZefY/giaVw5rcEoJk4TRnFCyMG0dYLV5IVnFWkR38fQEhAi5yX8RMW6plVrMY26hithJ6j1w2mgqh/bOCAEVnbn/Z/////wGQXwEAAAAAABepFIoQ3seh+nGYzkI7MDw5zuSCIxEyhwAAAAABB/0gAQBIMEUCIQDqqpZ1MJnPj6imVcAADCzzQirJB4VYJNaV0fXe55gekgIgNAtuInVXbYJffrCAUfAN2WuVnLMDSd4vD1kzFkddAuEBSDBFAiEA2cMFRBaNLyENM+J1Fn3msZkrctYIshkHU1jH3754FPwCIBK6ktH4o1rVJ7t6gGRZBwOfL4FoF8xD6nVFMIBd3v8YAUyLUiECxZrqO4BvDo4JmjuQB7gv9Trw2q0Ldeke1RClPHZEUQghAuF++G/r0Rd1y4Ml8hno5hOVQdX90WPyEXlIIMjR4HS2IQPnartRgvSQnSJ2ydRuwSPXDdU3P1YQwhYsK7XzfGuURCEDveDmb+sjw+JBGUmQarmAOxT7AAWWelALXlbhMO31cc5UrgAA" + }, + { + "type": "P2SH-P2WPKH", + "psbt": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAAiAgKj88rhJwk3Zxm0p0Rp+xC/6cxmj+I741DHPWPWN7iA+0cwRAIgTRhd9WUpoHYl9tUVmoJ336fJAJInIjdYsoatvRiW8hgCIGOYMlpKRHiHA428Sfa2CdAIIGGQCGhuIgIzj2FN6USnAQEEFgAU4sZupXPxqhcsOB1ghJxBvH4XcesAAA==", + "result": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAABBxcWABTixm6lc/GqFyw4HWCEnEG8fhdx6wAA" + }, + { + "type": "P2WPKH", + "psbt": "cHNidP8BAFICAAAAAb5UIQFSoXPjqxSmuiSZkM5PjaycnrXQ6VB+u2+MzbSGAAAAAAD/////ASBOAAAAAAAAFgAUXsVBEaHSlhycDORbHHtBKwDo4zIAAAAAAAEBHzB1AAAAAAAAFgAUvIgag7HZu7Rjd/ugmJC/MHlZmAYiAgLN1zezMD4c4uegTbgfJ1GCtN5/hlJYaJt7e8mt1BVzIEcwRAIgXhgL5G81tXP7MAaKJtA0QaFu17bLocOGqxXmDoIfYUACIAOIynpoPS/dTz9Omg2h7v5kiql7ab0SPzWDdxpvpsUcAQAA", + "result": "cHNidP8BAFICAAAAAb5UIQFSoXPjqxSmuiSZkM5PjaycnrXQ6VB+u2+MzbSGAAAAAAD/////ASBOAAAAAAAAFgAUXsVBEaHSlhycDORbHHtBKwDo4zIAAAAAAAEBHzB1AAAAAAAAFgAUvIgag7HZu7Rjd/ugmJC/MHlZmAYBCGsCRzBEAiBeGAvkbzW1c/swBoom0DRBoW7Xtsuhw4arFeYOgh9hQAIgA4jKemg9L91PP06aDaHu/mSKqXtpvRI/NYN3Gm+mxRwBIQLN1zezMD4c4uegTbgfJ1GCtN5/hlJYaJt7e8mt1BVzIAAA" + }, + { + "type": "P2WSH-P2PK", + "psbt": "cHNidP8BAKYCAAAAAlwKQ3suPWwEJ/zQ9sZsIioOcHKU1KoLMxlMNSXVIkEWAAAAAAD/////YYDJMap+mYgbTrCNAdpWHN+EkKvl+XYao/6co/EQfwMAAAAAAP////8CkF8BAAAAAAAWABRnPBAmVHz2HL+8/1U+QG5L2thjmjhKAAAAAAAAIgAg700yfFRyhWzQnPHIUb/XQqsjlpf4A0uw682pCVWuQ8IAAAAAAAEBKzB1AAAAAAAAIgAgth9oE4cDfC5aV58VgkW5CptHsIxppYzJV8C5kT6aTo8iAgNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06UgwRQIhALO0xRpuqP3aVkm+DPykrtqe6fPNSgNblp9K9MAwmtHJAiAHV5ZvZN8Vi49n/o9ISFyvtHsPXXPKqBxC9m2m2HlpYgEBBSMhA182CcWcQ9L2zg9j8jlt7OlaIATNDgNFMBKB1J14wzTprAABASuAOAEAAAAAACIAILYfaBOHA3wuWlefFYJFuQqbR7CMaaWMyVfAuZE+mk6PIgIDXzYJxZxD0vbOD2PyOW3s6VogBM0OA0UwEoHUnXjDNOlIMEUCIQC6XN6tpo9mYlZej4BXSSh5D1K6aILBfQ4WBWXUrISx6wIgVaxFUsz8y59xJ1V4sZ1qarHX9pZ+MJmLKbl2ZSadisoBAQUjIQNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06awAAAA=", + "result": "cHNidP8BAKYCAAAAAlwKQ3suPWwEJ/zQ9sZsIioOcHKU1KoLMxlMNSXVIkEWAAAAAAD/////YYDJMap+mYgbTrCNAdpWHN+EkKvl+XYao/6co/EQfwMAAAAAAP////8CkF8BAAAAAAAWABRnPBAmVHz2HL+8/1U+QG5L2thjmjhKAAAAAAAAIgAg700yfFRyhWzQnPHIUb/XQqsjlpf4A0uw682pCVWuQ8IAAAAAAAEBKzB1AAAAAAAAIgAgth9oE4cDfC5aV58VgkW5CptHsIxppYzJV8C5kT6aTo8BCG4CSDBFAiEAs7TFGm6o/dpWSb4M/KSu2p7p881KA1uWn0r0wDCa0ckCIAdXlm9k3xWLj2f+j0hIXK+0ew9dc8qoHEL2babYeWliASMhA182CcWcQ9L2zg9j8jlt7OlaIATNDgNFMBKB1J14wzTprAABASuAOAEAAAAAACIAILYfaBOHA3wuWlefFYJFuQqbR7CMaaWMyVfAuZE+mk6PAQhuAkgwRQIhALpc3q2mj2ZiVl6PgFdJKHkPUrpogsF9DhYFZdSshLHrAiBVrEVSzPzLn3EnVXixnWpqsdf2ln4wmYspuXZlJp2KygEjIQNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06awAAAA=" + } + ], + "validateSignaturesOfInput": { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "index": 0, + "pubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f', 'hex')", + "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')", + "nonExistantIndex": 42 + }, + "getFeeRate": { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "fee": 21 + }, + "cache": { + "nonWitnessUtxo": { + "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", + "nonWitnessUtxo": "Buffer.from('0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000', 'hex')", + "inputIndex": 0 + } + }, + "clone": { + "psbt": "cHNidP8BAKYCAAAAAlwKQ3suPWwEJ/zQ9sZsIioOcHKU1KoLMxlMNSXVIkEWAAAAAAD/////YYDJMap+mYgbTrCNAdpWHN+EkKvl+XYao/6co/EQfwMAAAAAAP////8CkF8BAAAAAAAWABRnPBAmVHz2HL+8/1U+QG5L2thjmjhKAAAAAAAAIgAg700yfFRyhWzQnPHIUb/XQqsjlpf4A0uw682pCVWuQ8IAAAAAAAEBKzB1AAAAAAAAIgAgth9oE4cDfC5aV58VgkW5CptHsIxppYzJV8C5kT6aTo8iAgNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06UgwRQIhALO0xRpuqP3aVkm+DPykrtqe6fPNSgNblp9K9MAwmtHJAiAHV5ZvZN8Vi49n/o9ISFyvtHsPXXPKqBxC9m2m2HlpYgEBBSMhA182CcWcQ9L2zg9j8jlt7OlaIATNDgNFMBKB1J14wzTprAABASuAOAEAAAAAACIAILYfaBOHA3wuWlefFYJFuQqbR7CMaaWMyVfAuZE+mk6PIgIDXzYJxZxD0vbOD2PyOW3s6VogBM0OA0UwEoHUnXjDNOlIMEUCIQC6XN6tpo9mYlZej4BXSSh5D1K6aILBfQ4WBWXUrISx6wIgVaxFUsz8y59xJ1V4sZ1qarHX9pZ+MJmLKbl2ZSadisoBAQUjIQNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06awAAAA=" + } +} diff --git a/test/fixtures/script.json b/test/fixtures/script.json index e06f9f077..504675771 100644 --- a/test/fixtures/script.json +++ b/test/fixtures/script.json @@ -1,86 +1,208 @@ { "valid": [ { - "description": "pay-to-PubKey", - "hex": "21031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95ac", - "type": "pubkey", - "hash": "26e645ab170255f2a0a82d29e48f35b14ae7c826", - "pubKey": "031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95", - "asm": "031f1e68f82112b373f0fe980b3a89d212d2b5c01fb51eb25acb8b4c4b4299ce95 OP_CHECKSIG", - "scriptPubKey": true - }, - { - "description": "P2SH ScriptPubKey", - "hex": "a914e8c300c87986efa84c37c0519929019ef86eb5b487", - "type": "scripthash", - "hash": "0ba47b56a573bab4b430ad6ed3ec79270e04b066", - "asm": "OP_HASH160 e8c300c87986efa84c37c0519929019ef86eb5b4 OP_EQUAL", - "scriptPubKey": true - }, - { - "description": "PubKeyHash ScriptPubKey", - "hex": "76a9145a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a888ac", - "type": "pubkeyhash", - "hash": "a5313f33d5c7b81674b35f7f3febc3522ef234db", - "asm": "OP_DUP OP_HASH160 5a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a8 OP_EQUALVERIFY OP_CHECKSIG", - "scriptPubKey": true - }, - { - "description": "pubKeyHash scriptSig", - "hex": "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f30141040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8", - "type": "pubkeyhash", - "hash": "b9bac2a5c5c29bb27c382d41fa3d179c646c78fd", - "asm": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301 040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8", - "scriptPubKey": false - }, - { - "description": "pubKey scriptSig", - "hex": "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301", - "type": "pubkey", - "hash": "44d9982c3e79452e02ef5816976a0e20a0ec1cba", - "signature": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301", - "asm": "304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f301", - "scriptPubKey": false - }, - { - "description": "Valid multisig script", - "hex": "5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae", - "type": "multisig", - "hash": "f1c98f0b74ecabcf78ae20dfa224bb6666051fbe", - "asm": "OP_TRUE 032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca33016 02308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a OP_2 OP_CHECKMULTISIG", - "scriptPubKey": true - }, - { - "description": "mutisig scriptSig", - "hex": "0047304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101483045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601", - "type": "multisig", - "hash": "b1ef3ae2c77b356eff81049aad7dfd2eeb34c6f5", - "asm": "OP_0 304402202b29881db1b4cc128442d955e906d41c77365ed9a8392b584be12d980b236459022009da4bc60d09280aa26f4f981bfbed94eb7263d92920961e48a7f3f0991895b101 3045022100871708a7597c1dbebff2a5527a56a1f2b49d73e35cd825a07285f5f29f5766d8022003bd7ac25334e9a6d6020cc8ba1be67a8c70dca8e7063ea0547d79c45b9bc12601", - "scriptPubKey": false - }, - { - "description": "OP_RETURN script", - "hex": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", - "type": "nulldata", - "hash": "ec88f016655477663455fe6a8e83508c348ea145", + "asm": "OP_1NEGATE", + "script": "4f", + "stack": [ + "81" + ] + }, + { + "asm": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "script": "47304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "stack": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" + ] + }, + { + "asm": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 OP_CHECKSIG", + "script": "2102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1ac" + }, + { + "asm": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "script": "47304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28012102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "stack": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1" + ] + }, + { + "asm": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "script": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac" + }, + { + "asm": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "script": "0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "stack": [ + "", + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ] + }, + { + "asm": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "script": "522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae" + }, + { + "asm": "OP_0 3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "script": "00483045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01473044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901483045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "stack": [ + "", + "3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01", + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ] + }, + { + "asm": "OP_3 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_3 OP_CHECKMULTISIG", + "script": "53210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f84834021024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a3453ae" + }, + { + "asm": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", + "script": "0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", + "stack": [ + "", + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae" + ] + }, + { + "asm": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL", + "script": "a914722ff0bc2c3f47b35c20df646c395594da24e90e87" + }, + { + "asm": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "script": "0014aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "stack": [ + "", + "aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5" + ] + }, + { + "asm": "OP_0 32447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80", + "script": "002032447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80", + "stack": [ + "", + "32447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80" + ] + }, + { "asm": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", - "scriptPubKey": true + "script": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" + }, + { + "asm": "OP_RETURN deadffffffffffffffffffffffffffffffffbeef", + "script": "6a14deadffffffffffffffffffffffffffffffffbeef" + }, + { + "asm": "OP_RETURN deadffffffffffffffffffffffffffffffffbeef deadffffffffffffffffffffffffffffffffbeef", + "script": "6a14deadffffffffffffffffffffffffffffffffbeef14deadffffffffffffffffffffffffffffffffbeef" + }, + { + "asm": "OP_0 OP_0 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "script": "0000473044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901483045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "stack": [ + "", + "", + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ] + }, + { + "asm": "OP_0 OP_0 OP_0 OP_0", + "script": "00000000", + "stack": [ + "", + "", + "", + "" + ] + }, + { + "asm": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae", + "script": "00004830450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a8014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae", + "stack": [ + "", + "", + "30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801", + "52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + ] }, { - "description": "Non standard script", - "hex": "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087", - "type": "nonstandard", - "hash": "3823382e70d1930989813d3459988e0d7c2861d8", "asm": "OP_HASH256 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 OP_EQUAL", - "scriptPubKey": true + "script": "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087" }, { - "description": "Invalid multisig script", - "hex": "000000ae", - "type": "nonstandard", - "hash": "62ede8963f9387544935f168745262f703dab1fb", "asm": "OP_0 OP_0 OP_0 OP_CHECKMULTISIG", - "scriptPubKey": true + "script": "000000ae" + }, + { + "asm": "OP_0", + "script": "00", + "stack": [ + "" + ] + }, + { + "asm": "OP_0 00", + "script": "000100", + "stack": [ + "", + "00" + ] + }, + { + "asm": "OP_6", + "script": "56", + "stack": [ + "06" + ], + "nonstandard": { + "scriptSig": "OP_6", + "scriptSigHex": "0106" + } + }, + { + "asm": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "script": "4cff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "stack": [ + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + }, + { + "asm": "OP_RETURN aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd", + "script": "6a24aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd" } - ] + ], + "invalid": { + "decompile": [ + { + "description": "Not enough data: OP_1", + "script": "01" + }, + { + "description": "Not enough data: OP_2", + "script": "0201" + }, + { + "description": "Not enough data: OP_PUSHDATA1 0x02", + "script": "4c0201" + }, + { + "description": "Not enough data: OP_PUSHDATA2 0xffff", + "script": "4dffff01" + }, + { + "description": "Not enough data: OP_PUSHDATA4 0xffffffff", + "script": "4effffffff01" + } + ], + "fromASM": [ + { + "description": "Expected Hex, got String \"0xff\"", + "script": "0xff OP_CHECKSIG" + } + ] + } } diff --git a/test/fixtures/script_number.json b/test/fixtures/script_number.json new file mode 100644 index 000000000..f934740dc --- /dev/null +++ b/test/fixtures/script_number.json @@ -0,0 +1,225 @@ +[ + { + "hex": "", + "number": 0 + }, + { + "hex": "01", + "number": 1 + }, + { + "hex": "02", + "number": 2 + }, + { + "hex": "03", + "number": 3 + }, + { + "hex": "7e", + "number": 126 + }, + { + "hex": "7f", + "number": 127 + }, + { + "hex": "8000", + "number": 128 + }, + { + "hex": "8100", + "number": 129 + }, + { + "hex": "8200", + "number": 130 + }, + { + "hex": "ff00", + "number": 255 + }, + { + "hex": "fe7f", + "number": 32766 + }, + { + "hex": "ff7f", + "number": 32767 + }, + { + "hex": "008000", + "number": 32768 + }, + { + "hex": "ffff00", + "number": 65535 + }, + { + "hex": "018000", + "number": 32769 + }, + { + "hex": "028000", + "number": 32770 + }, + { + "hex": "ffffff00", + "number": 16777215 + }, + { + "hex": "feff7f", + "number": 8388606 + }, + { + "hex": "ffff7f", + "number": 8388607 + }, + { + "hex": "00008000", + "number": 8388608 + }, + { + "hex": "01008000", + "number": 8388609 + }, + { + "hex": "02008000", + "number": 8388610 + }, + { + "hex": "feffff7f", + "number": 2147483646 + }, + { + "hex": "ffffff7f", + "number": 2147483647 + }, + { + "bytes": 5, + "hex": "0000008000", + "number": 2147483648 + }, + { + "bytes": 5, + "hex": "0100008000", + "number": 2147483649 + }, + { + "bytes": 5, + "hex": "0200008000", + "number": 2147483650 + }, + { + "bytes": 5, + "hex": "ffffffff00", + "number": 4294967295 + }, + { + "bytes": 5, + "hex": "0200008080", + "number": -2147483650 + }, + { + "bytes": 5, + "hex": "0100008080", + "number": -2147483649 + }, + { + "bytes": 5, + "hex": "0000008080", + "number": -2147483648 + }, + { + "hex": "ffffffff", + "number": -2147483647 + }, + { + "hex": "feffffff", + "number": -2147483646 + }, + { + "hex": "fdffffff", + "number": -2147483645 + }, + { + "hex": "ffffff80", + "number": -16777215 + }, + { + "hex": "01008080", + "number": -8388609 + }, + { + "hex": "00008080", + "number": -8388608 + }, + { + "hex": "ffffff", + "number": -8388607 + }, + { + "hex": "feffff", + "number": -8388606 + }, + { + "hex": "fdffff", + "number": -8388605 + }, + { + "hex": "ffff80", + "number": -65535 + }, + { + "hex": "018080", + "number": -32769 + }, + { + "hex": "008080", + "number": -32768 + }, + { + "hex": "ffff", + "number": -32767 + }, + { + "hex": "feff", + "number": -32766 + }, + { + "hex": "fdff", + "number": -32765 + }, + { + "hex": "ff80", + "number": -255 + }, + { + "hex": "8180", + "number": -129 + }, + { + "hex": "8080", + "number": -128 + }, + { + "hex": "ff", + "number": -127 + }, + { + "hex": "fe", + "number": -126 + }, + { + "hex": "fd", + "number": -125 + }, + { + "hex": "82", + "number": -2 + }, + { + "hex": "81", + "number": -1 + } +] diff --git a/test/fixtures/scripts.json b/test/fixtures/scripts.json deleted file mode 100644 index e79a6e76a..000000000 --- a/test/fixtures/scripts.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "valid": [ - { - "type": "pubkey", - "pubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", - "signature": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", - "scriptPubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 OP_CHECKSIG", - "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" - }, - { - "type": "pubkeyhash", - "pubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", - "signature": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", - "scriptPubKey": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", - "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1" - }, - { - "type": "multisig", - "pubKeys": [ - "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", - "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" - ], - "signatures": [ - "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", - "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" - ], - "scriptPubKey": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", - "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" - }, - { - "type": "multisig", - "pubKeys": [ - "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", - "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" - ], - "signatures": [ - "3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01", - "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", - "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" - ], - "scriptPubKey": "OP_3 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_3 OP_CHECKMULTISIG", - "scriptSig": "OP_0 3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" - }, - { - "type": "scripthash", - "redeemScript": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", - "redeemScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", - "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", - "scriptPubKey": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL" - }, - { - "type": "nulldata", - "data": "deadffffffffffffffffffffffffffffffffbeef", - "scriptPubKey": "OP_RETURN deadffffffffffffffffffffffffffffffffbeef" - }, - { - "type": "nonstandard", - "typeIncomplete": "multisig", - "pubKeys": [ - "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", - "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" - ], - "signatures": [ - null, - "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", - "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" - ], - "scriptSig": "OP_0 OP_0 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" - }, - { - "type": "nonstandard", - "typeIncomplete": "multisig", - "pubKeys": [ - "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", - "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" - ], - "signatures": [ - null, - null, - null - ], - "scriptSig": "OP_0 OP_0 OP_0 OP_0" - }, - { - "type": "nonstandard", - "typeIncomplete": "scripthash", - "pubKeys": [ - "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", - "04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a" - ], - "signatures": [ - null, - "30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801" - ], - "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", - "redeemScriptSig": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801", - "scriptSig": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" - } - ], - "invalid": { - "isPubKeyHashInput": [ - { - "description": "pubKeyHash input : extraneous data", - "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 ffffffff" - } - ], - "isScriptHashInput": [ - { - "description": "redeemScript not data", - "scriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 OP_RESERVED" - }, - { - "description": "signature forms invalid script", - "scriptSig": "OP_0 3045022100e12b17b3a4c80c401a1687487bd2bafee9e5f1f8f1ffc6180ce186672ad7b43a02205e316d1e5e71822f5ef301b694e578fa9c94af4f5f098c952c833f4691307f4e01" - } - ], - "isPubKeyInput": [ - { - "description": "non-canonical signature", - "scriptSig": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf7593ffffffffffffffff" - } - ], - "isMultisigOutput": [ - { - "description": "OP_CHECKMULTISIG not found", - "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_HASH160" - }, - { - "description": "less than 4 chunks", - "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_HASH160" - }, - { - "description": "m === 0", - "scriptPubKey": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" - }, - { - "description": "m < OP_1", - "scriptPubKey": "OP_1NEGATE 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" - }, - { - "description": "m > OP_16", - "scriptPubKey": "OP_NOP 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" - }, - { - "description": "n === 0", - "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG" - }, - { - "description": "n < OP_1", - "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1NEGATE OP_CHECKMULTISIG" - }, - { - "description": "n > OP_16", - "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_NOP OP_CHECKMULTISIG" - }, - { - "description": "n < m", - "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG" - }, - { - "description": "n < len(pubKeys)", - "scriptPubKey": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG" - }, - { - "description": "non-canonical pubKey (bad length)", - "scriptPubKey": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffff OP_1 OP_CHECKMULTISIG" - } - ], - "multisigInput": [ - { - "description": "Not enough signatures provided", - "type": "multisig", - "pubKeys": [ - "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", - "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340" - ], - "signatures": [ - null, - null - ] - }, - { - "exception": "Not enough signatures provided", - "pubKeys": [ - "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", - "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" - ], - "signatures": [ - "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" - ] - }, - { - "exception": "Too many signatures provided", - "pubKeys": [ - "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", - "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" - ], - "signatures": [ - "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", - "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", - "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" - ] - } - ], - "multisigOutput": [ - { - "exception": "Not enough pubKeys provided", - "m": 4, - "pubKeys": [ - "02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f", - "02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f", - "036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19" - ], - "signatures": [ - "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" - ] - } - ] - } -} diff --git a/test/fixtures/signature.json b/test/fixtures/signature.json new file mode 100644 index 000000000..e7949c5e7 --- /dev/null +++ b/test/fixtures/signature.json @@ -0,0 +1,140 @@ +{ + "valid": [ + { + "hashType": 1, + "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa5434226201", + "raw": { + "r": "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c9", + "s": "6f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262" + } + }, + { + "hashType": 2, + "hex": "3044022054c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed022007082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a502", + "raw": { + "r": "54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed", + "s": "07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5" + } + }, + { + "hashType": 3, + "hex": "3045022100ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd002206fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b28303", + "raw": { + "r": "ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd0", + "s": "6fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283" + } + }, + { + "hashType": 129, + "hex": "3045022100c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3022075afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d381", + "raw": { + "r": "c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3", + "s": "75afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3" + } + }, + { + "hashType": 130, + "hex": "304402207186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d02200de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df682", + "raw": { + "r": "7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d", + "s": "0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6" + } + }, + { + "hashType": 131, + "hex": "3045022100fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda48702200e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd3783", + "raw": { + "r": "fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda487", + "s": "0e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37" + } + }, + { + "hashType": 129, + "hex": "3045022100cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9022006ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef81", + "raw": { + "r": "cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9", + "s": "06ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef" + } + } + ], + "invalid": [ + { + "exception": "DER sequence length is too short", + "hex": "ffffffffffffff01" + }, + { + "exception": "DER sequence length is too long", + "hex": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01" + }, + { + "exception": "Expected DER sequence", + "hex": "00ffff0400ffffff020400ffffff01" + }, + { + "exception": "DER sequence length is invalid", + "hex": "30ff020400ffffff020400ffffff01" + }, + { + "exception": "DER sequence length is invalid", + "hex": "300c030400ffffff030400ffffff000001" + }, + { + "exception": "Expected DER integer", + "hex": "300cff0400ffffff020400ffffff01" + }, + { + "exception": "Expected DER integer \\(2\\)", + "hex": "300c020200ffffff020400ffffff01" + }, + { + "exception": "R length is zero", + "hex": "30080200020400ffffff01" + }, + { + "exception": "S length is zero", + "hex": "3008020400ffffff020001" + }, + { + "exception": "R length is too long", + "hex": "300c02dd00ffffff020400ffffff01" + }, + { + "exception": "S length is invalid", + "hex": "300c020400ffffff02dd00ffffff01" + }, + { + "exception": "R value is negative", + "hex": "300c020480000000020400ffffff01" + }, + { + "exception": "S value is negative", + "hex": "300c020400ffffff02048000000001" + }, + { + "exception": "R value excessively padded", + "hex": "300c02040000ffff020400ffffff01" + }, + { + "exception": "S value excessively padded", + "hex": "300c020400ffffff02040000ffff01" + }, + { + "exception": "Invalid hashType 7", + "hashType": 7, + "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa5434226207", + "raw": { + "r": "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c9", + "s": "6f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262" + } + }, + { + "exception": "Invalid hashType 140", + "hashType": 140, + "hex": "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa543422628c", + "raw": { + "r": "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c9", + "s": "6f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262" + } + } + ] +} diff --git a/test/fixtures/templates.json b/test/fixtures/templates.json new file mode 100644 index 000000000..61728c09b --- /dev/null +++ b/test/fixtures/templates.json @@ -0,0 +1,530 @@ +{ + "valid": [ + { + "type": "pubkey", + "pubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "signature": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "output": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 OP_CHECKSIG", + "input": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "inputHex": "47304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "outputHex": "2102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1ac", + "inputStack": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" + ] + }, + { + "type": "pubkeyhash", + "pubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "signature": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "output": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "input": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "inputHex": "47304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28012102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "outputHex": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac", + "inputStack": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1" + ] + }, + { + "type": "multisig", + "pubKeys": [ + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" + ], + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ], + "output": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "input": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "inputHex": "0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "outputHex": "522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", + "inputStack": [ + "", + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ] + }, + { + "type": "multisig", + "pubKeys": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", + "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" + ], + "signatures": [ + "3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01", + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ], + "output": "OP_3 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_3 OP_CHECKMULTISIG", + "input": "OP_0 3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "inputHex": "00483045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01473044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901483045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "outputHex": "53210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f84834021024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a3453ae", + "inputStack": [ + "", + "3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01", + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ] + }, + { + "type": "scripthash", + "redeemScript": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "redeemScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "input": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", + "output": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL", + "inputHex": "0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", + "outputHex": "a914722ff0bc2c3f47b35c20df646c395594da24e90e87", + "inputStack": [ + "", + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae" + ] + }, + { + "type": "witnesspubkeyhash", + "pubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "signature": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "output": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "outputHex": "0014aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "inputStack": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1" + ] + }, + { + "type": "witnessscripthash", + "witnessScript": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "witnessData": [ + "", + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ], + "output": "OP_0 32447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80", + "outputHex": "002032447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80", + "inputStack": [ + "", + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae" + ] + }, + { + "type": "nulldata", + "data": [ + "06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" + ], + "output": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", + "outputHex": "6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474" + }, + { + "type": "nulldata", + "data": [ + "deadffffffffffffffffffffffffffffffffbeef", + "deadffffffffffffffffffffffffffffffffbeef" + ], + "output": "OP_RETURN deadffffffffffffffffffffffffffffffffbeef deadffffffffffffffffffffffffffffffffbeef", + "outputHex": "6a14deadffffffffffffffffffffffffffffffffbeef14deadffffffffffffffffffffffffffffffffbeef" + }, + { + "type": "nulldata", + "data": [ + "deadffffffffffffffffffffffffffffffffbeef" + ], + "output": "OP_RETURN deadffffffffffffffffffffffffffffffffbeef", + "outputHex": "6a14deadffffffffffffffffffffffffffffffffbeef" + }, + { + "type": "nonstandard", + "typeIncomplete": "multisig", + "pubKeys": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", + "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" + ], + "signatures": [ + null, + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ], + "input": "OP_0 OP_0 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "inputHex": "0000473044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901483045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "inputStack": [ + "", + "", + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ] + }, + { + "type": "nonstandard", + "typeIncomplete": "multisig", + "pubKeys": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", + "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" + ], + "signatures": [ + null, + null, + null + ], + "input": "OP_0 OP_0 OP_0 OP_0", + "inputHex": "00000000", + "inputStack": [ + "", + "", + "", + "" + ] + }, + { + "type": "scripthash", + "redeemScript": "OP_2 0327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a49372 03251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f 02cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde47 OP_3 OP_CHECKMULTISIG", + "redeemScriptSig": "OP_0 304502210093efc26facedc5f51e304aa270a7b4f1a911b2d912c3674e5c6e2ad4ac7a410402201cf0b62c240461902f9f16d8a0bc3a210b7bfcd2c06523dd4b4b63be22e8525201", + "input": "OP_0 304502210093efc26facedc5f51e304aa270a7b4f1a911b2d912c3674e5c6e2ad4ac7a410402201cf0b62c240461902f9f16d8a0bc3a210b7bfcd2c06523dd4b4b63be22e8525201 52210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753ae", + "inputHex": "0048304502210093efc26facedc5f51e304aa270a7b4f1a911b2d912c3674e5c6e2ad4ac7a410402201cf0b62c240461902f9f16d8a0bc3a210b7bfcd2c06523dd4b4b63be22e85252014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753ae", + "output": "OP_HASH160 fcc42dd4aa770d75cb6796bbd7853a325e746659 OP_EQUAL", + "outputHex": "a914fcc42dd4aa770d75cb6796bbd7853a325e74665987" + }, + { + "type": "nonstandard", + "typeIncomplete": "scripthash", + "pubKeys": [ + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", + "04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a" + ], + "signatures": [ + null, + "30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801" + ], + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "redeemScriptSig": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801", + "input": "OP_0 OP_0 30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae", + "inputHex": "00004830450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a8014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae", + "inputStack": [ + "", + "", + "30450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a801", + "52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + ] + }, + { + "type": "nonstandard", + "output": "OP_HASH256 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 OP_EQUAL", + "outputHex": "aa206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000087" + }, + { + "type": "nonstandard", + "output": "OP_0 OP_0 OP_0 OP_CHECKMULTISIG", + "outputHex": "000000ae" + }, + { + "type": "nonstandard", + "output": "OP_0", + "outputHex": "00" + }, + { + "type": "nonstandard", + "input": "OP_0 00", + "inputHex": "000100", + "inputStack": [ + "", + "00" + ] + }, + { + "type": "nonstandard", + "input": "OP_6", + "inputHex": "56", + "nonstandard": { + "input": "06", + "inputHex": "0106" + }, + "inputStack": [ + "06" + ] + }, + { + "type": "nonstandard", + "input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "inputHex": "4cff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "inputStack": [ + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "invalid": { + "pubKey": { + "inputs": [ + { + "description": "non-canonical signature (too short)", + "input": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf7593" + }, + { + "description": "non-canonical signature (too long)", + "input": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28ffffffff01" + }, + { + "description": "non-canonical signature (invalid hashType)", + "input": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28ff" + } + ], + "outputs": [ + { + "description": "non-canonical pubkey (too short)", + "output": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce OP_CHECKSIG" + }, + { + "description": "non-canonical pubkey (too long)", + "output": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1ffffff OP_CHECKSIG" + }, + { + "description": "last operator is wrong for pubkey-output", + "outputHex": "21027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752ae" + }, + { + "description": "missing OP_CHECKSIG", + "outputHex": "21027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752" + }, + { + "description": "non-canonical pubkey (bad prefix)", + "output": "427a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752 OP_CHECKSIG" + }, + { + "description": "has extra opcode at the end isPubKeyOutput", + "output": "027a71801ab59336de37785c50005b6abd8ea859eecce1edbe8e81afa74ee5c752 OP_CHECKSIG OP_0" + } + ] + }, + "pubKeyHash": { + "inputs": [ + { + "description": "pubKeyHash input : extraneous data", + "input": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 ffffffff" + } + ], + "outputs": [ + { + "description": "non-minimal encoded isPubKeyHashOutput (non BIP62 compliant)", + "outputHex": "76a94c14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac" + }, + { + "description": "bad OP_DUP isPubKeyHashOutput", + "outputHex": "aca914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac" + }, + { + "description": "bad OP_HASH160 isPubKeyHashOutput", + "outputHex": "76ac14aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac" + }, + { + "description": "bad OP_EQUALVERIFY isPubKeyHashOutput", + "outputHex": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5acac" + }, + { + "description": "bad OP_CHECKSIG isPubKeyHashOutput", + "outputHex": "76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c58888" + }, + { + "description": "bad length isPubKeyHashOutput", + "outputHex": "76a920aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac" + }, + { + "description": "has something at the end isPubKeyHashOutput", + "outputHex": "76a920aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00" + }, + { + "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 2\\)", + "hash": "ffff" + } + ] + }, + "scriptHash": { + "inputs": [ + { + "description": "redeemScript not data", + "input": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 OP_RESERVED" + }, + { + "description": "redeemScript is a signature, therefore not a valid script", + "input": "OP_0 3045022100e12b17b3a4c80c401a1687487bd2bafee9e5f1f8f1ffc6180ce186672ad7b43a02205e316d1e5e71822f5ef301b694e578fa9c94af4f5f098c952c833f4691307f4e01" + } + ], + "outputs": [ + { + "description": "non-minimal encoded isScriptHashOutput (non BIP62 compliant)", + "outputHex": "a94c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187" + }, + { + "description": "wrong OP_HASH160 opcode", + "outputHex": "ac4c14c286a1af0947f58d1ad787385b1c2c4a976f9e7187" + }, + { + "description": "wrong length marker", + "outputHex": "a916c286a1af0947f58d1ad787385b1c2c4a976f9e7187" + }, + { + "description": "wrong OP_EQUAL opcode", + "outputHex": "a914c286a1af0947f58d1ad787385b1c2c4a976f9e7188" + }, + { + "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 3\\)", + "hash": "ffffff" + } + ] + }, + "multisig": { + "inputs": [ + { + "description": "Not enough signatures provided", + "type": "multisig", + "output": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG", + "signatures": [] + }, + { + "exception": "Not enough signatures provided", + "output": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" + ] + }, + { + "exception": "Too many signatures provided", + "output": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ] + } + ], + "outputs": [ + { + "description": "OP_CHECKMULTISIG not found", + "output": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_HASH160" + }, + { + "description": "less than 4 chunks", + "output": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_HASH160" + }, + { + "description": "m === 0", + "output": "OP_0 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "m < OP_1", + "output": "OP_1NEGATE 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "m > OP_16", + "output": "OP_NOP 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "n === 0", + "output": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_0 OP_CHECKMULTISIG" + }, + { + "description": "n < OP_1", + "output": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1NEGATE OP_CHECKMULTISIG" + }, + { + "description": "n > OP_16", + "output": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_NOP OP_CHECKMULTISIG" + }, + { + "description": "n < m", + "output": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 OP_1 OP_CHECKMULTISIG" + }, + { + "description": "n < len(pubKeys)", + "output": "OP_2 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "n > len(pubKeys)", + "output": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_2 OP_CHECKMULTISIG" + }, + { + "description": "m is data", + "output": "ffff 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_1 OP_CHECKMULTISIG" + }, + { + "description": "n is data", + "output": "OP_1 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 ffff OP_CHECKMULTISIG" + }, + { + "description": "non-canonical pubKey (bad length)", + "output": "OP_1 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffff OP_1 OP_CHECKMULTISIG" + }, + { + "exception": "Not enough pubKeys provided", + "m": 4, + "pubKeys": [ + "02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f", + "02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f", + "036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19" + ], + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" + ] + } + ] + }, + "witnessPubKeyHash": { + "inputs": [], + "outputs": [ + { + "description": "wrong version", + "outputHex": "51149090909090909090909090909090909090909090" + }, + { + "description": "wrong length marker", + "outputHex": "00209090909090909090909090909090909090909090" + }, + { + "exception": "Expected Buffer\\(Length: 20\\), got Buffer\\(Length: 3\\)", + "hash": "ffffff" + } + ] + }, + "witnessScriptHash": { + "inputs": [], + "outputs": [ + { + "description": "wrong version", + "outputHex": "51209090909090909090909090909090909090909090909090909090909090909090" + }, + { + "description": "wrong length marker", + "outputHex": "00219090909090909090909090909090909090909090909090909090909090909090" + }, + { + "exception": "Expected Buffer\\(Length: 32\\), got Buffer\\(Length: 3\\)", + "hash": "ffffff" + } + ] + }, + "witnessCommitment": { + "inputs": [], + "outputs": [ + { + "exception": "", + "commitment": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd" + }, + { + "description": "wrong OPCODE at the start", + "scriptPubKeyHex": "6024aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd" + }, + { + "description": "wrong length marker", + "scriptPubKeyHex": "6a23aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd" + }, + { + "description": "commitment of wrong length", + "scriptPubKeyHex": "6a23aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4d" + } + ] + } + } +} diff --git a/test/fixtures/transaction.json b/test/fixtures/transaction.json index 043dfed03..6bf60906e 100644 --- a/test/fixtures/transaction.json +++ b/test/fixtures/transaction.json @@ -4,6 +4,7 @@ "description": "Standard transaction (1:1)", "id": "a0ff943d3f644d8832b1fa74be4d0ad2577615dc28a7ef74ff8c271b603a082a", "hash": "2a083a601b278cff74efa728dc157657d20a4dbe74fab132884d643f3d94ffa0", + "hex": "0100000001f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b4830450221008732a460737d956fd94d49a31890b2908f7ed7025a9c1d0f25e43290f1841716022004fa7d608a291d44ebbbebbadaac18f943031e7de39ef3bf9920998c43e60c0401210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000", "raw": { "version": 1, "ins": [ @@ -21,12 +22,15 @@ ], "locktime": 0 }, - "hex": "0100000001f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b4830450221008732a460737d956fd94d49a31890b2908f7ed7025a9c1d0f25e43290f1841716022004fa7d608a291d44ebbbebbadaac18f943031e7de39ef3bf9920998c43e60c0401210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000" + "coinbase": false, + "virtualSize": 192, + "weight": 768 }, { "description": "Standard transaction (2:2)", "id": "fcdd6d89c43e76dcff94285d9b6e31d5c60cb5e397a76ebc4920befad30907bc", "hash": "bc0709d3fabe2049bc6ea797e3b50cc6d5316e9b5d2894ffdc763ec4896dddfc", + "hex": "0100000002f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe0100000083483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687000000800250c30000000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88acf04902000000000017a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc768700000000", "raw": { "version": 1, "ins": [ @@ -55,12 +59,15 @@ ], "locktime": 0 }, - "hex": "0100000002f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe0100000083483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687000000800250c30000000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88acf04902000000000017a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc768700000000" + "coinbase": false, + "virtualSize": 396, + "weight": 1584 }, { "description": "Standard transaction (14:2)", "id": "39d57bc27f72e904d81f6b5ef7b4e6e17fa33a06b11e5114a43435830d7b5563", "hash": "63557b0d833534a414511eb1063aa37fe1e6b4f75e6b1fd804e9727fc27bd539", + "hex": "010000000ee7b73e229790c1e79a02f0c871813b3cf26a4156c5b8d942e88b38fe8d3f43a0000000008c493046022100fd3d8fef44fb0962ba3f07bee1d4cafb84e60e38e6c7d9274504b3638a8d2f520221009fce009044e615b6883d4bf62e04c48f9fe236e19d644b082b2f0ae5c98e045c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff7bfc005f3880a606027c7cd7dd02a0f6a6572eeb84a91aa158311be13695a7ea010000008b483045022100e2e61c40f26e2510b76dc72ea2f568ec514fce185c719e18bca9caaef2b20e9e02207f1100fc79eb0584e970c7f18fb226f178951d481767b4092d50d13c50ccba8b014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0e0f8e6bf951fbb84d7d8ef833a1cbf5bb046ea7251973ac6e7661c755386ee3010000008a473044022048f1611e403710f248f7caf479965a6a5f63cdfbd9a714fef4ec1b68331ade1d022074919e79376c363d4575b2fc21513d5949471703efebd4c5ca2885e810eb1fa4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b000000008b483045022100886c07cad489dfcf4b364af561835d5cf985f07adf8bd1d5bd6ddea82b0ce6b2022045bdcbcc2b5fc55191bb997039cf59ff70e8515c56b62f293a9add770ba26738014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b010000008a4730440220535d49b819fdf294d27d82aff2865ed4e18580f0ca9796d793f611cb43a44f47022019584d5e300c415f642e37ba2a814a1e1106b4a9b91dc2a30fb57ceafe041181014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffd3051677216ea53baa2e6d7f6a75434ac338438c59f314801c8496d1e6d1bf6d010000008b483045022100bf612b0fa46f49e70ab318ca3458d1ed5f59727aa782f7fac5503f54d9b43a590220358d7ed0e3cee63a5a7e972d9fad41f825d95de2fd0c5560382468610848d489014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff1e751ccc4e7d973201e9174ec78ece050ef2fadd6a108f40f76a9fa314979c31010000008b483045022006e263d5f73e05c48a603e3bd236e8314e5420721d5e9020114b93e8c9220e1102210099d3dead22f4a792123347a238c87e67b55b28a94a0bb7793144cc7ad94a0168014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff25c4cf2c61743b3f4252d921d937cca942cf32e4f3fa4a544d0b26f014337084010000008a47304402207d6e87588be47bf2d97eaf427bdd992e9d6b306255711328aee38533366a88b50220623099595ae442cb77eaddb3f91753a4fc9df56fde69cfec584c7f97e05533c8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffecd93c87eb43c48481e6694904305349bdea94b01104579fa9f02bff66c89663010000008a473044022020f59498aee0cf82cb113768ef3cb721000346d381ff439adb4d405f791252510220448de723aa59412266fabbc689ec25dc94b1688c27a614982047513a80173514014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa1fdc0a79ff98d5b6154176e321c22f4f8450dbd950bd013ad31135f5604411e010000008b48304502210088167867f87327f9c0db0444267ff0b6a026eedd629d8f16fe44a34c18e706bf0220675c8baebf89930e2d6e4463adefc50922653af99375242e38f5ee677418738a014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffb89e8249c3573b58bf1ec7433185452dd57ab8e1daab01c3cc6ddc8b66ad3de8000000008b4830450220073d50ac5ec8388d5b3906921f9368c31ad078c8e1fb72f26d36b533f35ee327022100c398b23e6692e11dca8a1b64aae2ff70c6a781ed5ee99181b56a2f583a967cd4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff45ee07e182084454dacfad1e61b04ffdf9c7b01003060a6c841a01f4fff8a5a0010000008b483045022100991d1bf60c41358f08b20e53718a24e05ac0608915df4f6305a5b47cb61e5da7022003f14fc1cc5b737e2c3279a4f9be1852b49dbb3d9d6cc4c8af6a666f600dced8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff4cba12549f1d70f8e60aea8b546c8357f7c099e7c7d9d8691d6ee16e7dfa3170010000008c493046022100f14e2b0ef8a8e206db350413d204bc0a5cd779e556b1191c2d30b5ec023cde6f022100b90b2d2bf256c98a88f7c3a653b93cec7d25bb6a517db9087d11dbd189e8851c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa4b3aed39eb2a1dc6eae4609d9909724e211c153927c230d02bd33add3026959010000008b483045022100a8cebb4f1c58f5ba1af91cb8bd4a2ed4e684e9605f5a9dc8b432ed00922d289d0220251145d2d56f06d936fd0c51fa884b4a6a5fafd0c3318f72fb05a5c9aa372195014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0240d52303000000001976a914167c3e1f10cc3b691c73afbdb211e156e3e3f25c88ac15462e00000000001976a914290f7d617b75993e770e5606335fa0999a28d71388ac00000000", "raw": { "version": 1, "locktime": 0, @@ -148,7 +155,9 @@ } ] }, - "hex": "010000000ee7b73e229790c1e79a02f0c871813b3cf26a4156c5b8d942e88b38fe8d3f43a0000000008c493046022100fd3d8fef44fb0962ba3f07bee1d4cafb84e60e38e6c7d9274504b3638a8d2f520221009fce009044e615b6883d4bf62e04c48f9fe236e19d644b082b2f0ae5c98e045c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff7bfc005f3880a606027c7cd7dd02a0f6a6572eeb84a91aa158311be13695a7ea010000008b483045022100e2e61c40f26e2510b76dc72ea2f568ec514fce185c719e18bca9caaef2b20e9e02207f1100fc79eb0584e970c7f18fb226f178951d481767b4092d50d13c50ccba8b014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0e0f8e6bf951fbb84d7d8ef833a1cbf5bb046ea7251973ac6e7661c755386ee3010000008a473044022048f1611e403710f248f7caf479965a6a5f63cdfbd9a714fef4ec1b68331ade1d022074919e79376c363d4575b2fc21513d5949471703efebd4c5ca2885e810eb1fa4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b000000008b483045022100886c07cad489dfcf4b364af561835d5cf985f07adf8bd1d5bd6ddea82b0ce6b2022045bdcbcc2b5fc55191bb997039cf59ff70e8515c56b62f293a9add770ba26738014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b010000008a4730440220535d49b819fdf294d27d82aff2865ed4e18580f0ca9796d793f611cb43a44f47022019584d5e300c415f642e37ba2a814a1e1106b4a9b91dc2a30fb57ceafe041181014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffd3051677216ea53baa2e6d7f6a75434ac338438c59f314801c8496d1e6d1bf6d010000008b483045022100bf612b0fa46f49e70ab318ca3458d1ed5f59727aa782f7fac5503f54d9b43a590220358d7ed0e3cee63a5a7e972d9fad41f825d95de2fd0c5560382468610848d489014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff1e751ccc4e7d973201e9174ec78ece050ef2fadd6a108f40f76a9fa314979c31010000008b483045022006e263d5f73e05c48a603e3bd236e8314e5420721d5e9020114b93e8c9220e1102210099d3dead22f4a792123347a238c87e67b55b28a94a0bb7793144cc7ad94a0168014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff25c4cf2c61743b3f4252d921d937cca942cf32e4f3fa4a544d0b26f014337084010000008a47304402207d6e87588be47bf2d97eaf427bdd992e9d6b306255711328aee38533366a88b50220623099595ae442cb77eaddb3f91753a4fc9df56fde69cfec584c7f97e05533c8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffecd93c87eb43c48481e6694904305349bdea94b01104579fa9f02bff66c89663010000008a473044022020f59498aee0cf82cb113768ef3cb721000346d381ff439adb4d405f791252510220448de723aa59412266fabbc689ec25dc94b1688c27a614982047513a80173514014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa1fdc0a79ff98d5b6154176e321c22f4f8450dbd950bd013ad31135f5604411e010000008b48304502210088167867f87327f9c0db0444267ff0b6a026eedd629d8f16fe44a34c18e706bf0220675c8baebf89930e2d6e4463adefc50922653af99375242e38f5ee677418738a014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffb89e8249c3573b58bf1ec7433185452dd57ab8e1daab01c3cc6ddc8b66ad3de8000000008b4830450220073d50ac5ec8388d5b3906921f9368c31ad078c8e1fb72f26d36b533f35ee327022100c398b23e6692e11dca8a1b64aae2ff70c6a781ed5ee99181b56a2f583a967cd4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff45ee07e182084454dacfad1e61b04ffdf9c7b01003060a6c841a01f4fff8a5a0010000008b483045022100991d1bf60c41358f08b20e53718a24e05ac0608915df4f6305a5b47cb61e5da7022003f14fc1cc5b737e2c3279a4f9be1852b49dbb3d9d6cc4c8af6a666f600dced8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff4cba12549f1d70f8e60aea8b546c8357f7c099e7c7d9d8691d6ee16e7dfa3170010000008c493046022100f14e2b0ef8a8e206db350413d204bc0a5cd779e556b1191c2d30b5ec023cde6f022100b90b2d2bf256c98a88f7c3a653b93cec7d25bb6a517db9087d11dbd189e8851c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa4b3aed39eb2a1dc6eae4609d9909724e211c153927c230d02bd33add3026959010000008b483045022100a8cebb4f1c58f5ba1af91cb8bd4a2ed4e684e9605f5a9dc8b432ed00922d289d0220251145d2d56f06d936fd0c51fa884b4a6a5fafd0c3318f72fb05a5c9aa372195014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0240d52303000000001976a914167c3e1f10cc3b691c73afbdb211e156e3e3f25c88ac15462e00000000001976a914290f7d617b75993e770e5606335fa0999a28d71388ac00000000" + "coinbase": false, + "virtualSize": 2596, + "weight": 10384 }, { "description": "Coinbase transaction", @@ -161,8 +170,7 @@ { "hash": "0000000000000000000000000000000000000000000000000000000000000000", "index": 4294967295, - "data": "032832051c4d696e656420627920416e74506f6f6c20626a343a45ef0454c5de8d5e5300004e2c0000", - "sequence": 4294967295 + "data": "032832051c4d696e656420627920416e74506f6f6c20626a343a45ef0454c5de8d5e5300004e2c0000" } ], "outs": [ @@ -172,18 +180,643 @@ } ], "locktime": 0 - } + }, + "coinbase": true, + "virtualSize": 126, + "weight": 504 + }, + { + "description": "Transaction that ignores script chunking rules - Bug #367", + "id": "ebc9fa1196a59e192352d76c0f6e73167046b9d37b8302b6bb6968dfd279b767", + "hash": "67b779d2df6869bbb602837bd3b9467016736e0f6cd75223199ea59611fac9eb", + "hex": "01000000019ac03d5ae6a875d970128ef9086cef276a1919684a6988023cc7254691d97e6d010000006b4830450221009d41dc793ba24e65f571473d40b299b6459087cea1509f0d381740b1ac863cb6022039c425906fcaf51b2b84d8092569fb3213de43abaff2180e2a799d4fcb4dd0aa012102d5ede09a8ae667d0f855ef90325e27f6ce35bbe60a1e6e87af7f5b3c652140fdffffffff080100000000000000010101000000000000000202010100000000000000014c0100000000000000034c02010100000000000000014d0100000000000000044dffff010100000000000000014e0100000000000000064effffffff0100000000", + "raw": { + "version": 1, + "locktime": 0, + "ins": [ + { + "hash": "9ac03d5ae6a875d970128ef9086cef276a1919684a6988023cc7254691d97e6d", + "index": 1, + "script": "30450221009d41dc793ba24e65f571473d40b299b6459087cea1509f0d381740b1ac863cb6022039c425906fcaf51b2b84d8092569fb3213de43abaff2180e2a799d4fcb4dd0aa01 02d5ede09a8ae667d0f855ef90325e27f6ce35bbe60a1e6e87af7f5b3c652140fd" + } + ], + "outs": [ + { + "data": "01", + "value": 1 + }, + { + "data": "0201", + "value": 1 + }, + { + "data": "4c", + "value": 1 + }, + { + "data": "4c0201", + "value": 1 + }, + { + "data": "4d", + "value": 1 + }, + { + "data": "4dffff01", + "value": 1 + }, + { + "data": "4e", + "value": 1 + }, + { + "data": "4effffffff01", + "value": 1 + } + ] + }, + "coinbase": false, + "virtualSize": 249, + "weight": 996 + }, + { + "description": "P2PK", + "id": "8d4733995be9b3331a65eeb31bce24b213d4fabcbadf7f6634a8fea442c13e6a", + "hash": "6a3ec142a4fea834667fdfbabcfad413b224ce1bb3ee651a33b3e95b9933478d", + "hex": "010000000193aef40ae141694895e99e18e49d0181b086dd7c011c0241175c6eaf320099970000000049483045022100e57eba5380dcc8a7bdb5370b423dadd43070e1ca268f94bc97b2ded55ca45e9502206a43151c8af03a00f0ac86526d07981e303fc0daea8c6ed435abe8961533046d01ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "", + "raw": { + "version": 1, + "ins": [ + { + "hash": "93aef40ae141694895e99e18e49d0181b086dd7c011c0241175c6eaf32009997", + "index": 0, + "script": "3045022100e57eba5380dcc8a7bdb5370b423dadd43070e1ca268f94bc97b2ded55ca45e9502206a43151c8af03a00f0ac86526d07981e303fc0daea8c6ed435abe8961533046d01", + "value": 80000 + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 158, + "weight": 632 + }, + { + "description": "P2SH P2PK", + "id": "d4ebb5e09c06dd375f265b51eb7f1a14d12deba71329500e2a00a905fdfdc27d", + "hash": "7dc2fdfd05a9002a0e502913a7eb2dd1141a7feb515b265f37dd069ce0b5ebd4", + "hex": "0100000001a30e865fa60f6c25a8b218bb5a6b9acc7cf3f1db2f2e3a7114b51af5d6ae811f000000006c473044022026d2b56b6cb0269bf4e80dd655b9e917019e2ccef57f4b858d03bb45a2da59d9022010519a7f327f03e7c9613e0694f929544af29d3682e7ec8f19147e7a86651ecd012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bacffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "", + "raw": { + "version": 1, + "ins": [ + { + "hash": "a30e865fa60f6c25a8b218bb5a6b9acc7cf3f1db2f2e3a7114b51af5d6ae811f", + "index": 0, + "script": "3044022026d2b56b6cb0269bf4e80dd655b9e917019e2ccef57f4b858d03bb45a2da59d9022010519a7f327f03e7c9613e0694f929544af29d3682e7ec8f19147e7a86651ecd01 21038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac", + "value": 80000 + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "scriptHex": "76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 193, + "weight": 772 + }, + { + "description": "P2WSH P2PK", + "id": "60f04167a0c94d6439102f7fde9ad77c08a8716419805e77ef66b286cc5c7f87", + "hash": "877f5ccc86b266ef775e80196471a8087cd79ade7f2f1039644dc9a06741f060", + "hex": "01000000014533a3bc1e039bd787656068e135aaee10aee95a64776bfc047ee6a7c1ebdd2f0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "010000000001014533a3bc1e039bd787656068e135aaee10aee95a64776bfc047ee6a7c1ebdd2f0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02473044022039725bb7291a14dd182dafdeaf3ea0d5c05c34f4617ccbaa46522ca913995c4e02203b170d072ed2e489e7424ad96d8fa888deb530be2d4c5d9aaddf111a7efdb2d3012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "4533a3bc1e039bd787656068e135aaee10aee95a64776bfc047ee6a7c1ebdd2f", + "index": 0, + "witness": [ + "3044022039725bb7291a14dd182dafdeaf3ea0d5c05c34f4617ccbaa46522ca913995c4e02203b170d072ed2e489e7424ad96d8fa888deb530be2d4c5d9aaddf111a7efdb2d301", + "21038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "scriptHex": "76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 113, + "weight": 451 + }, + { + "description": "P2SH P2WSH P2PK", + "id": "da24fd5a422f72e3bde593628c2b47ff9ca7454e1f3ba5fb916344c218d31052", + "hash": "5210d318c2446391fba53b1f4e45a79cff472b8c6293e5bde3722f425afd24da", + "hex": "0100000001e0779d448aaa203a96b3de14d0482e26dd75a4278ae5bb6d7cc18e6874f3866000000000232200200f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "01000000000101e0779d448aaa203a96b3de14d0482e26dd75a4278ae5bb6d7cc18e6874f3866000000000232200200f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac024730440220014207a5f0601ed7b3c3f9d82309b32e8f76dd6776a55cb5f8684b9ff029e0850220693afd7b69471b51d9354cc1a956b68b8d48e32f6b0ad7a19bb5dd3e4499179a012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "e0779d448aaa203a96b3de14d0482e26dd75a4278ae5bb6d7cc18e6874f38660", + "index": 0, + "script": "00200f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210", + "witness": [ + "30440220014207a5f0601ed7b3c3f9d82309b32e8f76dd6776a55cb5f8684b9ff029e0850220693afd7b69471b51d9354cc1a956b68b8d48e32f6b0ad7a19bb5dd3e4499179a01", + "21038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 148, + "weight": 591 + }, + { + "description": "P2PKH", + "id": "c7a77aeadf529759c8db9b3f205d690cdaed3df0eaf441ead648086e85a6a280", + "hash": "80a2a6856e0848d6ea41f4eaf03dedda0c695d203f9bdbc8599752dfea7aa7c7", + "hex": "010000000176d7b05b96e69d9760bacf14e496ea01085eff32be8f4e08b299eb92057826e5000000006b4830450221009bd6ff2561437155913c289923175d3f114cca1c0e2bc5989315d5261502c2c902201b71ad90dce076a5eb872330ed729e7c2c4bc2d0513efff099dbefb3b62eab4f0121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "", + "raw": { + "version": 1, + "ins": [ + { + "hash": "76d7b05b96e69d9760bacf14e496ea01085eff32be8f4e08b299eb92057826e5", + "index": 0, + "script": "30450221009bd6ff2561437155913c289923175d3f114cca1c0e2bc5989315d5261502c2c902201b71ad90dce076a5eb872330ed729e7c2c4bc2d0513efff099dbefb3b62eab4f01 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b" + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 192, + "weight": 768 + }, + { + "description": "P2SH P2PKH", + "id": "0b8fa64f89b95666a23e424fe94e9114806726be9753e140dbd74c0d673c9593", + "hash": "93953c670d4cd7db40e15397be26678014914ee94f423ea26656b9894fa68f0b", + "hex": "01000000014b9ffc17c3cce03ee66980bf32d36aaa13462980c3af9d9d29ec6b97ab1c91650000000084473044022003d738d855d0c54a419ac62ebe1a1c0bf2dc6993c9585adb9a8666736658107002204d57ff62ee7efae6df73430bba62494faeba8c125a4abcf2488757a4f8877dd50121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b1976a914851a33a5ef0d4279bd5854949174e2c65b1d450088acffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "", + "raw": { + "version": 1, + "ins": [ + { + "hash": "4b9ffc17c3cce03ee66980bf32d36aaa13462980c3af9d9d29ec6b97ab1c9165", + "index": 0, + "script": "3044022003d738d855d0c54a419ac62ebe1a1c0bf2dc6993c9585adb9a8666736658107002204d57ff62ee7efae6df73430bba62494faeba8c125a4abcf2488757a4f8877dd501 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b 76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 217, + "weight": 868 + }, + { + "description": "P2WSH P2PKH", + "id": "62c62316f80beb10d9b1ce9183eb66e7e18a18324ca5f5e6f839be03f64aba83", + "hash": "83ba4af603be39f8e6f5a54c32188ae1e766eb8391ceb1d910eb0bf81623c662", + "hex": "010000000123539877e39a273819006de1c433e09f9e9af201fc178dd0f2cf2eaa5ad53b480000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "0100000000010123539877e39a273819006de1c433e09f9e9af201fc178dd0f2cf2eaa5ad53b480000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac03483045022100f02a82b0a94a5d5dc4d2127ac34be62cb066713d71d56bdf5ef7810ab57a157302205f24abdde1dab554a02edcf378e98828024e57272e5e474a5b04accdca080a030121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b1976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "23539877e39a273819006de1c433e09f9e9af201fc178dd0f2cf2eaa5ad53b48", + "index": 0, + "script": "", + "witness": [ + "3045022100f02a82b0a94a5d5dc4d2127ac34be62cb066713d71d56bdf5ef7810ab57a157302205f24abdde1dab554a02edcf378e98828024e57272e5e474a5b04accdca080a0301", + "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b", + "76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "scriptHex": "76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 119, + "weight": 476 + }, + { + "description": "P2SH P2WSH P2PKH", + "id": "2ef1431ec41a75791b75d16526f23534ae66d8d4abfb483d98d14bcb259311c0", + "hash": "c0119325cb4bd1983d48fbabd4d866ae3435f22665d1751b79751ac41e43f12e", + "hex": "0100000001363dfbfe2566db77e3b1195bedf1d0daeb9ce526cd7611ba81759b2654ce415c0000000023220020578db4b54a6961060b71385c17d3280379a557224c52b11b19a3a1c1eef606a0ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "01000000000101363dfbfe2566db77e3b1195bedf1d0daeb9ce526cd7611ba81759b2654ce415c0000000023220020578db4b54a6961060b71385c17d3280379a557224c52b11b19a3a1c1eef606a0ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac03483045022100c8bd5ebb26ba6719158650c3e7c5e80be4c886ba025c44cc41f5149b3114705a02203ac6e1f38f6c081d506f28f1b5e38ebec9e0f0fa911d0e3f68d48d8b0e77b34b0121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b1976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "363dfbfe2566db77e3b1195bedf1d0daeb9ce526cd7611ba81759b2654ce415c", + "index": 0, + "script": "0020578db4b54a6961060b71385c17d3280379a557224c52b11b19a3a1c1eef606a0", + "witness": [ + "3045022100c8bd5ebb26ba6719158650c3e7c5e80be4c886ba025c44cc41f5149b3114705a02203ac6e1f38f6c081d506f28f1b5e38ebec9e0f0fa911d0e3f68d48d8b0e77b34b01", + "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b", + "76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 154, + "weight": 616 + }, + { + "description": "Multisig", + "id": "7f5d96a866a815f5a1896fcd3b49cd00ac4c366a371fe8555f124e628e977a5e", + "hash": "5e7a978e624e125f55e81f376a364cac00cd493bcd6f89a1f515a866a8965d7f", + "hex": "010000000179310ec46e734b3490ee839c5ae4a09d28561ee9fff2d051f733d201f958d6d2000000004a00483045022100d269531f120f377ed2f94f42bef893ff2fe6544ac97fb477fa291bc6cfb7647e02200983f6a5bbd4ce6cf97f571995634805a7324cc5d8353ed954fa62477b0fcd0901ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "", + "raw": { + "version": 1, + "ins": [ + { + "hash": "79310ec46e734b3490ee839c5ae4a09d28561ee9fff2d051f733d201f958d6d2", + "index": 0, + "script": "OP_0 3045022100d269531f120f377ed2f94f42bef893ff2fe6544ac97fb477fa291bc6cfb7647e02200983f6a5bbd4ce6cf97f571995634805a7324cc5d8353ed954fa62477b0fcd0901" + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 159, + "weight": 636 + }, + { + "description": "P2SH Multisig", + "id": "3aef827e4306bab01f118a8eccad74713bcd1695362caf9fed55202ed54ef88b", + "hash": "8bf84ed52e2055ed9faf2c369516cd3b7174adcc8e8a111fb0ba06437e82ef3a", + "hex": "010000000152882c661c49dd2f53bd9ced7e9f44b184888ad2fe7d86737f0efaa7aecdced1000000006f00473044022025f2e161f0a97888df948f4dcc7c04fe502510b8d8260ca9920f38d55e4d17720220271b6843224b3a34542a4df31781d048da56ee46b8c5fb99043e30abd527b2d801255121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51aeffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "", + "raw": { + "version": 1, + "ins": [ + { + "hash": "52882c661c49dd2f53bd9ced7e9f44b184888ad2fe7d86737f0efaa7aecdced1", + "index": 0, + "script": "OP_0 3044022025f2e161f0a97888df948f4dcc7c04fe502510b8d8260ca9920f38d55e4d17720220271b6843224b3a34542a4df31781d048da56ee46b8c5fb99043e30abd527b2d801 5121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51ae" + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "scriptHex": "76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 196, + "weight": 784 + }, + { + "description": "P2WSH Multisig", + "id": "03e6b741d25cbd061b8d0999a521788ef90263dd8ba96d91c2136a4386afdf8e", + "hash": "8edfaf86436a13c2916da98bdd6302f98e7821a599098d1b06bd5cd241b7e603", + "hex": "0100000001c1eced6216de0889d4629ff64a8af8e8ec6d0b414de0c57b46c02cc303d321fe0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "01000000000101c1eced6216de0889d4629ff64a8af8e8ec6d0b414de0c57b46c02cc303d321fe0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac0300483045022100d4c0cbdb45915b8a3162362fa5f74556de919aeda5337fc44a7fb000e833460d022017742c37d7a061e2ae3a086c7c585c9c85e5d31af468d3e00045c0f35b8f8eb601255121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51ae00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "c1eced6216de0889d4629ff64a8af8e8ec6d0b414de0c57b46c02cc303d321fe", + "index": 0, + "script": "", + "witness": [ + "", + "3045022100d4c0cbdb45915b8a3162362fa5f74556de919aeda5337fc44a7fb000e833460d022017742c37d7a061e2ae3a086c7c585c9c85e5d31af468d3e00045c0f35b8f8eb601", + "5121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51ae" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 114, + "weight": 455 + }, + { + "description": "P2SH P2WSH Multisig", + "id": "e77fd9ea35346e295e1e15de9724fff42b5c127da4c5447a4ffddd361f66b1c1", + "hash": "c1b1661f36ddfd4f7a44c5a47d125c2bf4ff2497de151e5e296e3435ead97fe7", + "hex": "01000000013a5a2ab0223d3b504b52af76d650329750666fbf1be13d4cb08d0d9fc550a47d00000000232200201b8c0c2878c5634c3ce738cdc568c592e99783dbd28ff4c6cb5b7b4675d9ee99ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "010000000001013a5a2ab0223d3b504b52af76d650329750666fbf1be13d4cb08d0d9fc550a47d00000000232200201b8c0c2878c5634c3ce738cdc568c592e99783dbd28ff4c6cb5b7b4675d9ee99ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac0300483045022100c97a5e205ce0023d3d44f846abf1f0e21b6f2646bd2496bbe92e4333fe4401be02201247e047d669f713582713e35d2eba430abc3d75a924bb500362bf47d6234ed501255121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51ae00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "3a5a2ab0223d3b504b52af76d650329750666fbf1be13d4cb08d0d9fc550a47d", + "index": 0, + "script": "00201b8c0c2878c5634c3ce738cdc568c592e99783dbd28ff4c6cb5b7b4675d9ee99", + "witness": [ + "", + "3045022100c97a5e205ce0023d3d44f846abf1f0e21b6f2646bd2496bbe92e4333fe4401be02201247e047d669f713582713e35d2eba430abc3d75a924bb500362bf47d6234ed501", + "5121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51ae" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "scriptHex": "76a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 149, + "weight": 595 + }, + { + "description": "P2WKH", + "id": "10bbdf7e9301bc21f1da8684f25fd66bc1314904c30da25a8ebf967c58c89269", + "hash": "6992c8587c96bf8e5aa20dc3044931c16bd65ff28486daf121bc01937edfbb10", + "hex": "010000000133defbe3e28860007ff3e21222774c220cb35d554fa3e3796d25bf8ee983e1080000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "0100000000010133defbe3e28860007ff3e21222774c220cb35d554fa3e3796d25bf8ee983e1080000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac0248304502210097c3006f0b390982eb47f762b2853773c6cedf83668a22d710f4c13c4fd6b15502205e26ef16a81fc818a37f3a34fc6d0700e61100ea6c6773907c9c046042c440340121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "33defbe3e28860007ff3e21222774c220cb35d554fa3e3796d25bf8ee983e108", + "index": 0, + "script": "", + "witness": [ + "304502210097c3006f0b390982eb47f762b2853773c6cedf83668a22d710f4c13c4fd6b15502205e26ef16a81fc818a37f3a34fc6d0700e61100ea6c6773907c9c046042c4403401", + "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 113, + "weight": 450 + }, + { + "description": "P2SH P2WKH", + "id": "a0dd6714722a577c5db08f740cd1525f5383f8771dd0ebecd9ae96eb6623408a", + "hash": "8a402366eb96aed9ecebd01d77f883535f52d10c748fb05d7c572a721467dda0", + "hex": "01000000015df9a0b9ade2d835881704e0f53b51a4b19ecfc794ea1f3555783dd7f68659ce0000000017160014851a33a5ef0d4279bd5854949174e2c65b1d4500ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "whex": "010000000001015df9a0b9ade2d835881704e0f53b51a4b19ecfc794ea1f3555783dd7f68659ce0000000017160014851a33a5ef0d4279bd5854949174e2c65b1d4500ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02483045022100cb3929c128fec5108071b662e5af58e39ac8708882753a421455ca80462956f6022030c0f4738dd1a13fc7a34393002d25c6e8a6399f29c7db4b98f53a9475d94ca20121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000", + "raw": { + "version": 1, + "ins": [ + { + "hash": "5df9a0b9ade2d835881704e0f53b51a4b19ecfc794ea1f3555783dd7f68659ce", + "index": 0, + "script": "0014851a33a5ef0d4279bd5854949174e2c65b1d4500", + "witness": [ + "3045022100cb3929c128fec5108071b662e5af58e39ac8708882753a421455ca80462956f6022030c0f4738dd1a13fc7a34393002d25c6e8a6399f29c7db4b98f53a9475d94ca201", + "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b" + ] + } + ], + "outs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + "coinbase": false, + "virtualSize": 136, + "weight": 542 + }, + { + "description": "Coinbase transaction w/ witness", + "id": "c881f7b084a367b0603abbcb9c5c639318e6166770e3f9b27a1ee3f8b6a16517", + "hash": "1765a1b6f8e31e7ab2f9e3706716e61893635c9ccbbb3a60b067a384b0f781c8", + "hex": "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05022b020101ffffffff02c0cf402500000000232103c6c5964853fd00fb3271ac002831c66825102d223c706ce0ee99e73db3be4aa1ac0000000000000000266a24aa21a9edff828eb21f40ab251d9f107792670aba9299028b894a364fda570f6a089dcfe900000000", + "whex": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff05022b020101ffffffff02c0cf402500000000232103c6c5964853fd00fb3271ac002831c66825102d223c706ce0ee99e73db3be4aa1ac0000000000000000266a24aa21a9edff828eb21f40ab251d9f107792670aba9299028b894a364fda570f6a089dcfe90120000000000000000000000000000000000000000000000000000000000000000000000000", + "raw": { + "version": 2, + "ins": [ + { + "hash": "0000000000000000000000000000000000000000000000000000000000000000", + "index": 4294967295, + "data": "022b020101", + "sequence": 4294967295, + "witness": [ + "0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "outs": [ + { + "script": "03c6c5964853fd00fb3271ac002831c66825102d223c706ce0ee99e73db3be4aa1 OP_CHECKSIG", + "value": 625004480 + }, + { + "script": "OP_RETURN aa21a9edff828eb21f40ab251d9f107792670aba9299028b894a364fda570f6a089dcfe9", + "value": 0 + } + ], + "locktime": 0 + }, + "coinbase": true, + "virtualSize": 156, + "weight": 624 + } + ], + "hashForSignature": [ + { + "description": "Out of range inIndex", + "txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000", + "inIndex": 2, + "script": "OP_0", + "type": 0, + "hash": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "description": "inIndex > nOutputs (SIGHASH_SINGLE)", + "txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000", + "inIndex": 2, + "script": "OP_0", + "type": 3, + "hash": "0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000", + "inIndex": 0, + "script": "OP_0 OP_3", + "type": 0, + "hash": "3d56a632462b9fc9b89eeddcad7dbe476297f34aff7e5f9320e2a99fb5e97136" + }, + { + "txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000", + "inIndex": 0, + "script": "OP_0 OP_CODESEPARATOR OP_3", + "type": 0, + "hash": "3d56a632462b9fc9b89eeddcad7dbe476297f34aff7e5f9320e2a99fb5e97136" + }, + { + "txHex": "010000000200000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000000000000000", + "inIndex": 0, + "script": "OP_0 OP_CODESEPARATOR OP_4", + "type": 0, + "hash": "fa075877cb54916236806a6562e4a8cdad48adf1268e73d72d1f9fdd867df463" + } + ], + "hashForWitnessV0": [ + { + "description": "Native P2WPKH with SIGHASH_ALL", + "txHex": "0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000", + "inIndex": 1, + "script": "OP_DUP OP_HASH160 1d0f172a0ecb48aee1be1f2687d2963ae33f71a1 OP_EQUALVERIFY OP_CHECKSIG", + "type": 1, + "hash": "c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670", + "value": 600000000 + }, + { + "description": "P2WPKH with SIGHASH_ALL", + "txHex": "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000", + "inIndex": 0, + "script": "OP_DUP OP_HASH160 79091972186c449eb1ded22b78e40d009bdf0089 OP_EQUALVERIFY OP_CHECKSIG", + "type": 1, + "hash": "64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6", + "value": 1000000000 + }, + { + "description": "P2WSH with SIGHASH_ALL", + "txHex": "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000", + "inIndex": 0, + "script": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "type": 1, + "hash": "185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c", + "value": 987654321 + }, + { + "description": "P2WSH with SIGHASH_NONE", + "txHex": "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000", + "inIndex": 0, + "script": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "type": 2, + "hash": "e9733bc60ea13c95c6527066bb975a2ff29a925e80aa14c213f686cbae5d2f36", + "value": 987654321 + }, + { + "description": "P2WSH with SIGHASH_SINGLE", + "txHex": "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000", + "inIndex": 0, + "script": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "type": 3, + "hash": "1e1f1c303dc025bd664acb72e583e933fae4cff9148bf78c157d1e8f78530aea", + "value": 987654321 + }, + { + "description": "P2WSH with SIGHASH_ALL | SIGHASH_ANYONECANPAY", + "txHex": "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000", + "inIndex": 0, + "script": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "type": 129, + "hash": "2a67f03e63a6a422125878b40b82da593be8d4efaafe88ee528af6e5a9955c6e", + "value": 987654321 + }, + { + "description": "P2WSH with SIGHASH_NONE | SIGHASH_ANYONECANPAY", + "txHex": "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000", + "inIndex": 0, + "script": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "type": 130, + "hash": "781ba15f3779d5542ce8ecb5c18716733a5ee42a6f51488ec96154934e2c890a", + "value": 987654321 + }, + { + "description": "P2WSH with SIGHASH_SINGLE | SIGHASH_ANYONECANPAY", + "txHex": "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000", + "inIndex": 0, + "script": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "type": 131, + "hash": "511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b", + "value": 987654321 } ], "invalid": { "addInput": [ { - "exception": "Expected hash length of 32, got 30", + "exception": "Expected property \"0\" of type Buffer\\(Length: 32\\), got Buffer\\(Length: 30\\)", "hash": "0aed1366a73b6057ee7800d737bff1bdf8c448e98d86bc0998f2b009816d", "index": 0 }, { - "exception": "Expected hash length of 32, got 34", + "exception": "Expected property \"0\" of type Buffer\\(Length: 32\\), got Buffer\\(Length: 34\\)", "hash": "0aed1366a73b6057ee7800d737bff1bdf8c448e98d86bc0998f2b009816da9b0ffff", "index": 0 } @@ -192,6 +825,10 @@ { "exception": "Transaction has unexpected data", "hex": "0100000002f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe0100000083483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687ffffffff0250c30000000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88acf04902000000000017a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc768700000000ffffffff" + }, + { + "exception": "Transaction has superfluous witness data", + "hex": "0100000000010113ae35a2063ba413c3a1bb9b3820c76291e40e83bd3f23c8ff83333f0c64d623000000004a00483045022100e332e8367d5fee22c205ce1bf4e01e39f1a8decb3ba20d1336770cf38b8ee72d022076b5f83b3ee15390133b7ebf526ec189eb73cc6ee0a726f70b939bc51fa18d8001ffffffff0180969800000000001976a914b1ae3ceac136e4bdb733663e7a1e2f0961198a1788ac0000000000" } ] } diff --git a/test/fixtures/transaction_builder.json b/test/fixtures/transaction_builder.json index a1c5529f3..07226b00f 100644 --- a/test/fixtures/transaction_builder.json +++ b/test/fixtures/transaction_builder.json @@ -2,15 +2,17 @@ "valid": { "build": [ { - "description": "Transaction w/ pubKeyHash -> pubKeyHash", + "description": "Transaction w/ P2PKH -> P2PKH", "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006b483045022100a3b254e1c10b5d039f36c05f323995d6e5a367d98dd78a13d5bbc3991b35720e022022fccea3897d594de0689601fbd486588d5bfa6915be2386db0397ee9a6e80b601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] } @@ -23,8 +25,9 @@ ] }, { - "description": "Transaction w/ pubKey -> pubKeyHash", + "description": "Transaction w/ P2PK -> P2PKH", "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000494830450221009833abb3ab49d7004c06bcc79eafd6905ada3eee91f3376ad388548034acd9a702202e84dda6ef2678c82256afcfc459aaa68e179b2bb0e6b2dc3f1410e132c5e6c301ffffffff0100f90295000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -32,7 +35,8 @@ "prevTxScript": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_CHECKSIG", "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + "prevOutScriptType": "p2pk", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" } ] } @@ -45,15 +49,17 @@ ] }, { - "description": "Transaction w/ scriptHash(pubKeyHash) -> pubKeyHash", + "description": "Transaction w/ P2SH(P2PKH) -> P2PKH", "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000085483045022100a3b254e1c10b5d039f36c05f323995d6e5a367d98dd78a13d5bbc3991b35720e022022fccea3897d594de0689601fbd486588d5bfa6915be2386db0397ee9a6e80b601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817981976a914751e76e8199196d454941c45d1b3a323f1433bd688acffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "prevOutScriptType": "p2sh-p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "redeemScript": "OP_DUP OP_HASH160 751e76e8199196d454941c45d1b3a323f1433bd6 OP_EQUALVERIFY OP_CHECKSIG" } ] @@ -67,113 +73,2118 @@ ] }, { - "description": "Transaction w/ scriptHash(multisig 2-of-2) -> pubKeyHash", + "description": "Transaction w/ P2SH(P2MS 2/2) -> P2PKH", + "network": "testnet", "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000fd1b0100483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a4014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "version": 1, "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, "signs": [ { - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG" }, { - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ P2MS 2/2 -> P2PKH", + "network": "testnet", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000009200483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a401ffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + }, + { + "prevOutScriptType": "p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ P2MS 2/3 -> P2PKH", + "network": "testnet", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000910047304402206b2fc7d3182e2853cab5bcffb85c3ef5470d2d05c496295538c9947af3bfd0ec0220300aa705a985c74f76c26c6d68da9b61b5c4cd5432e8c6a54623f376c8bf8cde01473044022031059c4dd6a97d84e3a4eb1ca21a9870bd1762fbd5db7c1932d75e56da78794502200f22d85be3c5f7035e89a147ee2619a066df19aff14a62e6bb3f649b6da19edf01ffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + }, + { + "prevOutScriptType": "p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ P2MS 2/2 (reverse order) -> P2PKH", + "network": "testnet", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000009200483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a401ffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + }, + { + "prevOutScriptType": "p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ P2SH(P2MS 2/3)", + "network": "testnet", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000fd5e0100483045022100eec19e061cad41610f9b42d2b06638b6b0fec3da0de9c6858e7f8c06053979900220622936dd47e202b2ad17639cda680e52334d407149252959936bb1f38e4acc52014830450221009aac215157a74a18234fd06be27448dccee809986bbf93be457a9262f0c69a9402203ff41d7c757f0e8951e4471f205087ecff499f986400ab18210eaad9a628e33c014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Transaction w/ P2SH(P2MS 2/3), different hash types", + "network": "testnet", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000fd5e0100483045022100eec19e061cad41610f9b42d2b06638b6b0fec3da0de9c6858e7f8c06053979900220622936dd47e202b2ad17639cda680e52334d407149252959936bb1f38e4acc5201483045022100d404fb6de6cf42efb9d7948d2e8fb6618f8eba55ecd25907d18d576d9aa6f39d02205ec2e7fa7c5f8a9793732ca9d2f9aba3b2bb04ca6d467ba36940e0f695e48de5024cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "hashType": 1 + }, + { + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "hashType": 2 + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Transaction w/ P2SH(P2PK) -> P2PKH", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006c47304402201115644b134932c8a7a8e925769d130a801288d477130e2bf6fadda20b33754d02202ecefbf63844d7cb2d5868539c39f973fe019f72e5c31a707836c0d61ef317db012321033e29aea1168a835d5e386c292082db7b7807172a10ec634ad34226f36d79e70facffffffff0100f90295000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_HASH160 e89677d91455e541630d62c63718bef738b478b1 OP_EQUAL", + "signs": [ + { + "prevOutScriptType": "p2sh-p2pk", + "keyPair": "KxLDMPtVM7sLSu2v5n1LybDibw6P9FFbL4pUwJ51UDm7rp5AmXWW", + "redeemScript": "033e29aea1168a835d5e386c292082db7b7807172a10ec634ad34226f36d79e70f OP_CHECKSIG" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 2500000000 + } + ] + }, + { + "description": "Transaction w/ non-zero vin inputs", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205c80bbb5125b35d5e5a8324b1336832d29a6fc004859c8a9ff6bef47ba7fc348022018612216e57a521b2c4543f1f4fd738a76814c37c074e88adfe12464fff31cf901210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ non-default input sequence numbers, version and locktime", + "txHex": "0400000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000006a47304402200e7c0330f39c04e3c1b9e3daf71d106c7129c095f6ebb494d06f0ef8013b74ea022003fc0fe05e71a2a4f8434feb0632cdb6883676d0ccb9b19d7a8a9e5fc02a616401210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798b9c220000110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac09990400", + "version": 4, + "locktime": 301321, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 2, + "sequence": 2147001, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ nLockTime, P2PKH -> P2PKH", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006b483045022100e31ef1bcc6f060cb6a53675a11606b9180f4f8b1ec823113fb4c0bf1c5b99b8e02204234690c19cd89e544002d26dbcbd49bf9d1b4cfc5a617fd8ab2607acfd869b001210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588acffff0000", + "version": 1, + "locktime": 65535, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ 1 P2PKH transaction input (Issue #644)", + "network": "testnet", + "txHex": "010000000132595835c74fccf097db4ccae9dc2de621e58e0d3f697a27b469b61c7a223b39000000006a47304402202fd41f18f1d7915bc811610236e1d708f9cd3515734abd5db7ac607f030728af02206bee7d163f04c470ce55561523824eb8b3abce80c214aabf7dfb78a073ea4a70012103f29374a4c2c218a4077db9ba0b9d674cde3719560460af4eb3190d512dd5de92ffffffff01808d5b00000000001976a914ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a88ac00000000", + "version": 1, + "inputs": [ + { + "txHex": "0100000001f7e6430096cd2790bac115aaab22c0a50fb0a1794305302e1a399e81d8d354f4020000006a47304402205793a862d193264afc32713e2e14541e1ff9ebb647dd7e7e6a0051d0faa87de302205216653741ecbbed573ea2fc053209dd6980616701c27be5b958a159fc97f45a012103e877e7deb32d19250dcfe534ea82c99ad739800295cd5429a7f69e2896c36fcdfeffffff0340420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac8bda2702000000001976a9145a0ef60784137d03e7868d063b05424f2f43799f88ac40420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac2fcc0e00", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "cQ6483mDWwoG8o4tn6nU9Jg52RKMjPUWXSY1vycAyPRXQJ1Pn2Rq" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a OP_EQUALVERIFY OP_CHECKSIG", + "value": 6000000 + } + ] + }, + { + "description": "Transaction w/ P2PKH -> P2WPKH", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006b4830450221009a524a3257b8bd0c77ba4cba65fc00954f2030243f2eb16571838a3b951c8c07022008f5af9de672b365fd257377db1cf6da4da1b49b9637ceb651ac0eb4181dc3ca01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff011027000000000000160014aa4d7985c57e011a8b3dd8e0e5a73aaef41629c500000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ P2PKH -> P2WSH", + "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006a473044022056c99ba23eb15b3e666b188f87b04d5ef23eeda5298939cdaec35a3bddf3835602205887a5a460f299819b0c93948fafab8b2d64d8c051934431e3bb9acebef5d1b001210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01102700000000000022002032447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b8000000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_0 32447752937d355ca2defddcd1f6b4fc53d182f8901cebbcff42f5e381bf0b80", + "value": 10000 + } + ] + }, + { + "description": "P2WPKH -> P2WPKH", + "txHex": "01000000000101ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff011027000000000000160014aa4d7985c57e011a8b3dd8e0e5a73aaef41629c502483045022100a8fc5e4c6d7073474eff2af5d756966e75be0cdfbba299518526080ce8b584be02200f26d41082764df89e3c815b8eaf51034a3b68a25f1be51208f54222c1bb6c1601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", + "version": 1, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6", + "signs": [ + { + "prevOutScriptType": "p2wpkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "value": 10000 + } + ] + } + ], + "outputs": [ + { + "script": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "value": 10000 + } + ] + }, + { + "description": "Transaction w/ P2WSH(P2PK) -> P2PKH", + "txHex": "010000000001014533a3bc1e039bd787656068e135aaee10aee95a64776bfc047ee6a7c1ebdd2f0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02473044022039725bb7291a14dd182dafdeaf3ea0d5c05c34f4617ccbaa46522ca913995c4e02203b170d072ed2e489e7424ad96d8fa888deb530be2d4c5d9aaddf111a7efdb2d3012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac00000000", + "version": 1, + "inputs": [ + { + "txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345", + "vout": 0, + "prevTxScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210", + "signs": [ + { + "prevOutScriptType": "p2wsh-p2pk", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "witnessScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG", + "value": 80000 + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "value": 60000 + } + ] + }, + { + "description": "SIGHASH SINGLE (random)", + "txHex": "01000000012ffb29d53528ad30c37c267fbbeda3c6fce08f5f6f5d3b1eab22193599a3612a010000006b483045022100f963f1d9564075a934d7c3cfa333bd1378859b84cba947e149926fc9ec89b5ae02202b5b912e507bae65002aff972f9752e2aeb2e22c5fdbaaad672090378184df37032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff0260a62f01000000001976a9140de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b888ac80969800000000001976a91454d0e925d5ee0ee26768a237067dee793d01a70688ac00000000", + "version": 1, + "inputs": [ + { + "txId": "2a61a399351922ab1e3b5d6f5f8fe0fcc6a3edbb7f267cc330ad2835d529fb2f", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 3 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 19900000, + "script": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 10000000, + "script": "OP_DUP OP_HASH160 54d0e925d5ee0ee26768a237067dee793d01a706 OP_EQUALVERIFY OP_CHECKSIG" + } + ] + }, + { + "description": "SIGHASH ALL", + "txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006a47304402206abb0622b8b6ca83f1f4de84830cf38bf4615dc9e47a7dcdcc489905f26aa9cb02201d2d8a7815242b88e4cd66390ca46da802238f9b1395e0d118213d30dad38184012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006b483045022100de13b42804f87a09bb46def12ab4608108d8c2db41db4bc09064f9c46fcf493102205e5c759ab7b2895c9b0447e56029f6895ff7bb20e0847c564a88a3cfcf080c4f012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b4830450221009100a3f5b30182d1cb0172792af6947b6d8d42badb0539f2c209aece5a0628f002200ae91702ca63347e344c85fcb536f30ee97b75cdf4900de534ed5e040e71a548012102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000", + "version": 1, + "inputs": [ + { + "txId": "043f61697d1b48d69394879a3e94a2957a7d1a21a38df2ddd6de45a3b2f0b77d", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 1 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "057ef664f0e1108f270729e53a62e4cb1a7480d9a87f543c6a1a785a1e492c65", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 1 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "35db95c66634c3497a277c1f08ed71a0cb53195ffecbf9798cdde4a30f27fab9", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 1 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 20000, + "script": "OP_DUP OP_HASH160 9ed1f577c60e4be1dbf35318ec12f51d25e85773 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 30000, + "script": "OP_DUP OP_HASH160 fb407e88c48921d5547d899e18a7c0a36919f54d OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 50000, + "script": "OP_DUP OP_HASH160 04ccb4eed8cfa9f6e394e945178960f5ccddb387 OP_EQUALVERIFY OP_CHECKSIG" + } + ] + }, + { + "description": "SIGHASH ALL | ANYONECANPAY", + "txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100bd2829550e9b3a081747281029b5f5a96bbd83bb6a92fa2f8310f1bd0d53abc90220071b469417c55cdb3b04171fd7900d2768981b7ab011553d84d24ea85d277079812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402206295e17c45c6356ffb20365b696bcbb869db7e8697f4b8a684098ee2bff85feb02202905c441abe39ec9c480749236b84fdd3ebd91ecd25b559136370aacfcf2815c812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b483045022100f58e7c98ac8412944d575bcdece0e5966d4018f05988b5b60b6f46b8cb7a543102201c5854d3361e29b58123f34218cec2c722f5ec7a08235ebd007ec637b07c193a812102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000", + "version": 1, + "inputs": [ + { + "txId": "043f61697d1b48d69394879a3e94a2957a7d1a21a38df2ddd6de45a3b2f0b77d", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 129 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "057ef664f0e1108f270729e53a62e4cb1a7480d9a87f543c6a1a785a1e492c65", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 129 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "35db95c66634c3497a277c1f08ed71a0cb53195ffecbf9798cdde4a30f27fab9", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 129 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 20000, + "script": "OP_DUP OP_HASH160 9ed1f577c60e4be1dbf35318ec12f51d25e85773 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 30000, + "script": "OP_DUP OP_HASH160 fb407e88c48921d5547d899e18a7c0a36919f54d OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 50000, + "script": "OP_DUP OP_HASH160 04ccb4eed8cfa9f6e394e945178960f5ccddb387 OP_EQUALVERIFY OP_CHECKSIG" + } + ] + }, + { + "description": "SIGHASH SINGLE", + "txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100e822f152bb15a1d623b91913cd0fb915e9f85a8dc6c26d51948208bbc0218e800220255f78549d9614c88eac9551429bc00224f22cdcb41a3af70d52138f7e98d333032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402206f37f79adeb86e0e2da679f79ff5c3ba206c6d35cd9a21433f0de34ee83ddbc00220118cabbac5d83b3aa4c2dc01b061e4b2fe83750d85a72ae6a1752300ee5d9aff032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006a473044022042ac843d220a56b3de05f24c85a63e71efa7e5fc7c2ec766a2ffae82a88572b0022051a816b317313ea8d90010a77c3e02d41da4a500e67e6a5347674f836f528d82032102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000", + "version": 1, + "inputs": [ + { + "txId": "043f61697d1b48d69394879a3e94a2957a7d1a21a38df2ddd6de45a3b2f0b77d", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 3 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "057ef664f0e1108f270729e53a62e4cb1a7480d9a87f543c6a1a785a1e492c65", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 3 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "35db95c66634c3497a277c1f08ed71a0cb53195ffecbf9798cdde4a30f27fab9", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 3 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 20000, + "script": "OP_DUP OP_HASH160 9ed1f577c60e4be1dbf35318ec12f51d25e85773 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 30000, + "script": "OP_DUP OP_HASH160 fb407e88c48921d5547d899e18a7c0a36919f54d OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 50000, + "script": "OP_DUP OP_HASH160 04ccb4eed8cfa9f6e394e945178960f5ccddb387 OP_EQUALVERIFY OP_CHECKSIG" + } + ] + }, + { + "description": "SIGHASH SINGLE|ANYONECANPAY", + "txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100d05a3b6cf2f0301000b0e45c09054f2c61570ce8798ebf571eef72da3b1c94a1022016d7ef3c133fa703bae2c75158ea08d335ac698506f99b3c369c37a9e8fc4beb832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006b483045022100ee6bf07b051001dcbfa062692a40adddd070303286b714825b3fb4693dd8fcdb022056610885e5053e5d47f2be3433051305abe7978ead8f7cf2d0368947aff6b307832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b483045022100cfc930d5b5272d0220d9da98fabec97b9e66306f735efa837f43f6adc675cad902202f9dff76b8b9ec8f613d46094f17f64d875804292d8804aa59fd295b6fc1416b832102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000", + "version": 1, + "inputs": [ + { + "txId": "043f61697d1b48d69394879a3e94a2957a7d1a21a38df2ddd6de45a3b2f0b77d", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 131 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "057ef664f0e1108f270729e53a62e4cb1a7480d9a87f543c6a1a785a1e492c65", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 131 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "35db95c66634c3497a277c1f08ed71a0cb53195ffecbf9798cdde4a30f27fab9", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 131 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 20000, + "script": "OP_DUP OP_HASH160 9ed1f577c60e4be1dbf35318ec12f51d25e85773 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 30000, + "script": "OP_DUP OP_HASH160 fb407e88c48921d5547d899e18a7c0a36919f54d OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 50000, + "script": "OP_DUP OP_HASH160 04ccb4eed8cfa9f6e394e945178960f5ccddb387 OP_EQUALVERIFY OP_CHECKSIG" + } + ] + }, + { + "description": "SIGHASH NONE", + "txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006b483045022100e7f0a1ddd2c0b81e093e029b8a503afa27fe43549b0668d2141abf35eb3a63be022037f12d12cd50fc94a135f933406a8937557de9b9566a8841ff1548c1b6984531022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a473044022008451123ec2535dab545ade9d697519e63b28df5e311ea05e0ce28d39877a7c8022061ce5dbfb7ab478dd9e05b0acfd959ac3eb2641f61958f5d352f37621073d7c0022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006a47304402205c001bcdfb35c70d8aa3bdbc75399afb72eb7cf1926ca7c1dfcddcb4d4d3e0f8022028992fffdcd4e9f34ab726f97c24157917641c2ef99361f588e3d4147d46eea5022102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000", + "version": 1, + "inputs": [ + { + "txId": "043f61697d1b48d69394879a3e94a2957a7d1a21a38df2ddd6de45a3b2f0b77d", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 2 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "057ef664f0e1108f270729e53a62e4cb1a7480d9a87f543c6a1a785a1e492c65", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 2 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "35db95c66634c3497a277c1f08ed71a0cb53195ffecbf9798cdde4a30f27fab9", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 2 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 20000, + "script": "OP_DUP OP_HASH160 9ed1f577c60e4be1dbf35318ec12f51d25e85773 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 30000, + "script": "OP_DUP OP_HASH160 fb407e88c48921d5547d899e18a7c0a36919f54d OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 50000, + "script": "OP_DUP OP_HASH160 04ccb4eed8cfa9f6e394e945178960f5ccddb387 OP_EQUALVERIFY OP_CHECKSIG" + } + ] + }, + { + "description": "SIGHASH NONE | ANYONECANPAY", + "txHex": "01000000037db7f0b2a345ded6ddf28da3211a7d7a95a2943e9a879493d6481b7d69613f04010000006a47304402204ed272952177aaa5a1b171c2ca5a7a3d300ffcd7e04b040c0baaa4e3561862a502207e65a5b8f99c8a632b186c8a60496a12bf3116f51909b7497413aefdc3be7bf6822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff652c491e5a781a6a3c547fa8d980741acbe4623ae52907278f10e1f064f67e05000000006a47304402203ec365300cc67602f4cc5be027959d3667b48db34c6c87d267c94a7e210d5c1f02204843350311c0a9711cad1960b17ce9e323a1ce6f37deefc3ffe63082d480be92822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffffb9fa270fa3e4dd8c79f9cbfe5f1953cba071ed081f7c277a49c33466c695db35000000006b48304502210084f86f905c36372eff9c54ccd509a519a3325bcace8abfeed7ed3f0d579979e902201ff330dd2402e5ca9989a8a294fa36d6cf3a093edb18d29c9d9644186a3efeb4822102f1c7eac9200f8dee7e34e59318ff2076c8b3e3ac7f43121e57569a1aec1803d4ffffffff03204e0000000000001976a9149ed1f577c60e4be1dbf35318ec12f51d25e8577388ac30750000000000001976a914fb407e88c48921d5547d899e18a7c0a36919f54d88ac50c30000000000001976a91404ccb4eed8cfa9f6e394e945178960f5ccddb38788ac00000000", + "version": 1, + "inputs": [ + { + "txId": "043f61697d1b48d69394879a3e94a2957a7d1a21a38df2ddd6de45a3b2f0b77d", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 130 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "057ef664f0e1108f270729e53a62e4cb1a7480d9a87f543c6a1a785a1e492c65", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 130 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "txId": "35db95c66634c3497a277c1f08ed71a0cb53195ffecbf9798cdde4a30f27fab9", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzRGFiqhXB7SyX6idHQkt77B8mX7adnujdg3VG47jdVK2x4wbUYg", + "hashType": 130 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 0de1f9b92d2ab6d8ead83f9a0ff5cf518dcb03b8 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 20000, + "script": "OP_DUP OP_HASH160 9ed1f577c60e4be1dbf35318ec12f51d25e85773 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 30000, + "script": "OP_DUP OP_HASH160 fb407e88c48921d5547d899e18a7c0a36919f54d OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 50000, + "script": "OP_DUP OP_HASH160 04ccb4eed8cfa9f6e394e945178960f5ccddb387 OP_EQUALVERIFY OP_CHECKSIG" + } + ] + }, + { + "description": "SIGHASH V0+V1, (P2PKH, P2WPKH) -> 2x P2PKH", + "txHex": "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000484730440220691a19d365c8d75f921346c70271506bde136f13a4b566dd796902c262e2ec6d02202b00c4aa030eedf294552bdfc163936d2f4e91c59e7798c4471250cf07cb859501eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff0230f45e13000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac00e9a435000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac0002483045022100fddd014889f18d489b5400bfa8cb0a32301a768d934b1a0e2b55398119f26cab02207676c64c16ffa7ffaaf8e16b3b74e916687eebdfdb36b9b7997e838384d464640121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000", + "version": 1, + "inputs": [ + { + "txId": "9f96ade4b41d5433f4eda31e1738ec2b36f6e7d1420d94a6af99801a88f7f7ff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pk", + "keyPair": "L3Wh2WPg21MWqzMFYsVC7PeBXcq1ow32KRccRihnTUnAhJaZUvg1", + "hashType": 1 + } + ], + "sequence": 4294967278, + "prevTxScript": "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432 OP_CHECKSIG" + }, + { + "txId": "8ac60eb9575db5b2d987e29f301b5b819ea83a5c6579d282d189cc04b8e151ef", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2wpkh", + "keyPair": "KzVTBhbMaKrAYagJ11VdTaBrb6yzLykLGyuMBkf9sCFPDxdT8shL", + "hashType": 1, + "value": 600000000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_0 1d0f172a0ecb48aee1be1f2687d2963ae33f71a1" + } + ], + "outputs": [ + { + "value": 324990000, + "script": "OP_DUP OP_HASH160 8280b37df378db99f66f85c95a783a76ac7a6d59 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 900000000, + "script": "OP_DUP OP_HASH160 3bde42dbee7e4dbe6a21b2d50ce2f0167faa8159 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 17 + }, + { + "description": "SIGHASH V0+V1, P2SH(P2WPKH) -> P2PKH", + "txHex": "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000", + "version": 1, + "inputs": [ + { + "txId": "77541aeb3c4dac9260b68f74f44c973081a9d4cb2ebe8038b2d70faa201b6bdb", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wpkh", + "keyPair": "L57KYn5isHFThD4cohjJgLTZA2vaxnMMKWngnzbttF159yH9dARf", + "hashType": 1, + "redeemScript": "OP_0 79091972186c449eb1ded22b78e40d009bdf0089", + "value": 1000000000 + } + ], + "sequence": 4294967294, + "prevTxScript": "OP_HASH160 4733f37cf4db86fbc2efed2500b4f4e49f312023 OP_EQUAL" + } + ], + "outputs": [ + { + "value": 199996600, + "script": "OP_DUP OP_HASH160 a457b684d7f0d539a46a45bbc043f35b59d0d963 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 800000000, + "script": "OP_DUP OP_HASH160 fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 1170 + }, + { + "description": "Sighash V1: P2SH(P2WSH(P2MS 6/6))", + "txHex": "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff02e6312761010000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688ac583e0f00000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac0800483045022100f902f491c4df15199e584790ae8c7202569a977accac0a09fa3f4f3b6ec3517602205961a951c4a12fa966da67b6fd75975b9de156b9895f8ab5f289ecaee12b9b3501473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502483045022100bd5294e145d729e9593f49079b74e6e4b8aeba63440408595ce0949d5c6450a702207f9c9fb45907fe0180d3f4bee499006007bb90894b5f824a26dfa5d3afec543303483045022100febf9409d7f3c091ddc4d296a483aae7b3d2a91d38f6ea2a153f7ff085fe7766022078d11972c74cd78f816152463a5e1a5d986dfb94b55cf5f7242e4f6d5df000ff81483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a088247304402201a0e125aed6a700e45d6c86017d5a9d2264c8079319d868f3f163f5d63cb5bfe02200887608f2322ca0d82df67316275371028b0b21750417d594117963fe23b67ec83cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000", + "version": 1, + "inputs": [ + { + "txId": "6eb98797a21c6c10aa74edf29d618be109f48a8e94c694f3701e08ca69186436", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "L15NqbRvcqso8ZCqD8aFaZV3CTypw6svjk8oCWsAfMmNViahS2Mw", + "hashType": 1, + "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "redeemScript": "OP_0 a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54", + "value": 987654321 + }, + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "Kwpf3fycToLH1ymZUkezFrYwTjhKaucHD861Ft5A4Tih855LBxVx", + "hashType": 2, + "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "redeemScript": "OP_0 a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54", + "value": 987654321 + }, + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "L1EV111k2WzNTapY2etd1TaB2aWbjUgouko9YyipS2S8H8WdGkQi", + "hashType": 3, + "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "redeemScript": "OP_0 a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54", + "value": 987654321 + }, + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "KwuvEmpBtJaw8SQLnpi3CoEHZJvv33EnYBHn13VcDuwprJqmkfSH", + "hashType": 129, + "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "redeemScript": "OP_0 a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54", + "value": 987654321 + }, + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "L5kdM8eWyfj8pdRDWA8j5SmBwAQt2yyhqjb2ZZQxtRGJfCquC6TB", + "hashType": 130, + "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "redeemScript": "OP_0 a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54", + "value": 987654321 + }, + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "KyT4JbJVRy5FZ6ZEZhkaocP2JSBXiF7X3Cx6DBAGLrydR9fiXQUK", + "hashType": 131, + "witnessScript": "OP_6 0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3 03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b 034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a 033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4 03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16 02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b OP_6 OP_CHECKMULTISIG", + "redeemScript": "OP_0 a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54", + "value": 987654321 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 9993a429037b5d912407a71c252019287b8d27a5 OP_EQUAL" + } + ], + "outputs": [ + { + "value": 5924925926, + "script": "OP_DUP OP_HASH160 389ffce9cd9ae88dcc0631e88a821ffdbe9bfe26 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "value": 999000, + "script": "OP_DUP OP_HASH160 7480a33f950689af511e6e84c138dbbd3c3ee415 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2PK", + "txHex": "010000000193aef40ae141694895e99e18e49d0181b086dd7c011c0241175c6eaf320099970000000049483045022100e57eba5380dcc8a7bdb5370b423dadd43070e1ca268f94bc97b2ded55ca45e9502206a43151c8af03a00f0ac86526d07981e303fc0daea8c6ed435abe8961533046d01ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "97990032af6e5c1741021c017cdd86b081019de4189ee995486941e10af4ae93", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pk", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1 + } + ], + "sequence": 4294967295, + "prevTxScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2PK)", + "txHex": "0100000001a30e865fa60f6c25a8b218bb5a6b9acc7cf3f1db2f2e3a7114b51af5d6ae811f000000006c473044022026d2b56b6cb0269bf4e80dd655b9e917019e2ccef57f4b858d03bb45a2da59d9022010519a7f327f03e7c9613e0694f929544af29d3682e7ec8f19147e7a86651ecd012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bacffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "1f81aed6f51ab514713a2e2fdbf1f37ccc9a6b5abb18b2a8256c0fa65f860ea3", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2pk", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "redeemScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG" + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 c99d9ebb5a4828e4e1b606dd6a51a2babebbdc09 OP_EQUAL" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2WSH(P2PK)", + "txHex": "010000000001014533a3bc1e039bd787656068e135aaee10aee95a64776bfc047ee6a7c1ebdd2f0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02473044022039725bb7291a14dd182dafdeaf3ea0d5c05c34f4617ccbaa46522ca913995c4e02203b170d072ed2e489e7424ad96d8fa888deb530be2d4c5d9aaddf111a7efdb2d3012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac00000000", + "version": 1, + "inputs": [ + { + "txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2wsh-p2pk", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "witnessScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG", + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2WSH(P2PK))", + "txHex": "01000000000101e0779d448aaa203a96b3de14d0482e26dd75a4278ae5bb6d7cc18e6874f3866000000000232200200f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac024730440220014207a5f0601ed7b3c3f9d82309b32e8f76dd6776a55cb5f8684b9ff029e0850220693afd7b69471b51d9354cc1a956b68b8d48e32f6b0ad7a19bb5dd3e4499179a012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac00000000", + "version": 1, + "inputs": [ + { + "txId": "6086f374688ec17c6dbbe58a27a475dd262e48d014deb3963a20aa8a449d77e0", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2pk", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "redeemScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210", + "witnessScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG", + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 6d185c7042d01ea8276dc6be6603101dc441d8a4 OP_EQUAL" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2PKH", + "txHex": "010000000176d7b05b96e69d9760bacf14e496ea01085eff32be8f4e08b299eb92057826e5000000006b4830450221009bd6ff2561437155913c289923175d3f114cca1c0e2bc5989315d5261502c2c902201b71ad90dce076a5eb872330ed729e7c2c4bc2d0513efff099dbefb3b62eab4f0121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "e526780592eb99b2084e8fbe32ff5e0801ea96e414cfba60979de6965bb0d776", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2PKH)", + "txHex": "01000000014b9ffc17c3cce03ee66980bf32d36aaa13462980c3af9d9d29ec6b97ab1c91650000000084473044022003d738d855d0c54a419ac62ebe1a1c0bf2dc6993c9585adb9a8666736658107002204d57ff62ee7efae6df73430bba62494faeba8c125a4abcf2488757a4f8877dd50121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b1976a914851a33a5ef0d4279bd5854949174e2c65b1d450088acffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "65911cab976bec299d9dafc380294613aa6ad332bf8069e63ee0ccc317fc9f4b", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2pkh", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "redeemScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 2162ff7c23d47a0c331f95c67d7c3e22abb12a02 OP_EQUAL" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2WSH(P2PKH)", + "txHex": "0100000000010123539877e39a273819006de1c433e09f9e9af201fc178dd0f2cf2eaa5ad53b480000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac03483045022100f02a82b0a94a5d5dc4d2127ac34be62cb066713d71d56bdf5ef7810ab57a157302205f24abdde1dab554a02edcf378e98828024e57272e5e474a5b04accdca080a030121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b1976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "483bd55aaa2ecff2d08d17fc01f29a9e9fe033c4e16d001938279ae377985323", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2wsh-p2pkh", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "witnessScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_0 578db4b54a6961060b71385c17d3280379a557224c52b11b19a3a1c1eef606a0" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2WSH(P2PKH))", + "txHex": "01000000000101363dfbfe2566db77e3b1195bedf1d0daeb9ce526cd7611ba81759b2654ce415c0000000023220020578db4b54a6961060b71385c17d3280379a557224c52b11b19a3a1c1eef606a0ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac03483045022100c8bd5ebb26ba6719158650c3e7c5e80be4c886ba025c44cc41f5149b3114705a02203ac6e1f38f6c081d506f28f1b5e38ebec9e0f0fa911d0e3f68d48d8b0e77b34b0121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b1976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "5c41ce54269b7581ba1176cd26e59cebdad0f1ed5b19b1e377db6625fefb3d36", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2pkh", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "witnessScript": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "redeemScript": "OP_0 578db4b54a6961060b71385c17d3280379a557224c52b11b19a3a1c1eef606a0", + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 44a641c4e06eb6118c99e5ed29954b705b50fb6a OP_EQUAL" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2MS 1/1", + "txHex": "010000000179310ec46e734b3490ee839c5ae4a09d28561ee9fff2d051f733d201f958d6d2000000004a00483045022100d269531f120f377ed2f94f42bef893ff2fe6544ac97fb477fa291bc6cfb7647e02200983f6a5bbd4ce6cf97f571995634805a7324cc5d8353ed954fa62477b0fcd0901ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "d2d658f901d233f751d0f2ffe91e56289da0e45a9c83ee90344b736ec40e3179", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2ms", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2MS)", + "txHex": "010000000152882c661c49dd2f53bd9ced7e9f44b184888ad2fe7d86737f0efaa7aecdced1000000006f00473044022025f2e161f0a97888df948f4dcc7c04fe502510b8d8260ca9920f38d55e4d17720220271b6843224b3a34542a4df31781d048da56ee46b8c5fb99043e30abd527b2d801255121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51aeffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac00000000", + "version": 1, + "inputs": [ + { + "txId": "d1cecdaea7fa0e7f73867dfed28a8884b1449f7eed9cbd532fdd491c662c8852", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "redeemScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG" + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 38c064c6387d1071eeb5c3d90350054aea0b3fc1 OP_EQUAL" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2WSH(P2MS)", + "txHex": "01000000000101c1eced6216de0889d4629ff64a8af8e8ec6d0b414de0c57b46c02cc303d321fe0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac0300483045022100d4c0cbdb45915b8a3162362fa5f74556de919aeda5337fc44a7fb000e833460d022017742c37d7a061e2ae3a086c7c585c9c85e5d31af468d3e00045c0f35b8f8eb601255121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51ae00000000", + "version": 1, + "inputs": [ + { + "txId": "fe21d303c32cc0467bc5e04d410b6dece8f88a4af69f62d48908de1662edecc1", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2wsh-p2ms", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "witnessScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG", + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_0 1b8c0c2878c5634c3ce738cdc568c592e99783dbd28ff4c6cb5b7b4675d9ee99" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2WSH(P2MS))", + "txHex": "010000000001013a5a2ab0223d3b504b52af76d650329750666fbf1be13d4cb08d0d9fc550a47d00000000232200201b8c0c2878c5634c3ce738cdc568c592e99783dbd28ff4c6cb5b7b4675d9ee99ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac0300483045022100c97a5e205ce0023d3d44f846abf1f0e21b6f2646bd2496bbe92e4333fe4401be02201247e047d669f713582713e35d2eba430abc3d75a924bb500362bf47d6234ed501255121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b51ae00000000", + "version": 1, + "inputs": [ + { + "txId": "7da450c59f0d8db04c3de11bbf6f6650973250d676af524b503b3d22b02a5a3a", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "witnessScript": "OP_1 038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_1 OP_CHECKMULTISIG", + "redeemScript": "OP_0 1b8c0c2878c5634c3ce738cdc568c592e99783dbd28ff4c6cb5b7b4675d9ee99", + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 cc6ea17c33de7996471e40892acdd6e5f61b9b6f OP_EQUAL" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2WPKH -> P2PKH", + "txHex": "0100000000010133defbe3e28860007ff3e21222774c220cb35d554fa3e3796d25bf8ee983e1080000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac0248304502210097c3006f0b390982eb47f762b2853773c6cedf83668a22d710f4c13c4fd6b15502205e26ef16a81fc818a37f3a34fc6d0700e61100ea6c6773907c9c046042c440340121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000", + "version": 1, + "inputs": [ + { + "txId": "08e183e98ebf256d79e3a34f555db30c224c772212e2f37f006088e2e3fbde33", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2wpkh", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_0 851a33a5ef0d4279bd5854949174e2c65b1d4500" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2WPKH) -> P2PKH", + "txHex": "010000000001015df9a0b9ade2d835881704e0f53b51a4b19ecfc794ea1f3555783dd7f68659ce0000000017160014851a33a5ef0d4279bd5854949174e2c65b1d4500ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02483045022100cb3929c128fec5108071b662e5af58e39ac8708882753a421455ca80462956f6022030c0f4738dd1a13fc7a34393002d25c6e8a6399f29c7db4b98f53a9475d94ca20121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000", + "version": 1, + "inputs": [ + { + "txId": "ce5986f6d73d7855351fea94c7cf9eb1a4513bf5e004178835d8e2adb9a0f95d", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wpkh", + "keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN", + "hashType": 1, + "redeemScript": "OP_0 851a33a5ef0d4279bd5854949174e2c65b1d4500", + "value": 80000 + } + ], + "sequence": 4294967295, + "prevTxScript": "OP_HASH160 0d061ae2c8ad224a81142a2e02181f5173b576b3 OP_EQUAL" + } + ], + "outputs": [ + { + "value": 60000, + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "locktime": 0 + }, + { + "description": "Defaults to version 2", + "txHex": "0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006b483045022100d090736fce4387d25f9d7ba85270a3859947dbd0a19f347467c3a596e06c132402204b14c29d128f824bee953bdaf2ed4e16c7cff0b24bddb4add94b4b6c42773d8801210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": null, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 10000 + } + ], + "locktime": 0 + }, + { + "description": "P2SH(P2WSH(P2MS 2/2)), incomplete", + "network": "testnet", + "txHex": "010000000001012915794541ffa77ca795ec7c23ee989a63ccd1a71fab73e1c27ed20c4b6c69a4010000002322002024376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5afffffffff01b8820100000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac040047304402203b334650f1f13574a1c2edc76421867f7252950968bf0293c8b3ed086ab89e3d0220565cafab0a5044617e94756b948241525b2483a52504e1064d29f641fb18129e010047522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae00000000", + "version": 1, + "incomplete": true, + "inputs": [ + { + "txId": "a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS", + "witnessScript": "OP_2 02bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e2 02d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea19 OP_2 OP_CHECKMULTISIG", + "redeemScript": "OP_0 24376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af", + "value": 100000 + } + ], + "prevTxScript": "OP_HASH160 b64f1a3eacc1c8515592a6f10457e8ff90e4db6a OP_EQUAL" + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 99000 + } + ] + }, + { + "description": "P2SH(P2WSH(P2MS 2/2))", + "network": "testnet", + "txHex": "010000000001012915794541ffa77ca795ec7c23ee989a63ccd1a71fab73e1c27ed20c4b6c69a4010000002322002024376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5afffffffff01b8820100000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac040047304402203b334650f1f13574a1c2edc76421867f7252950968bf0293c8b3ed086ab89e3d0220565cafab0a5044617e94756b948241525b2483a52504e1064d29f641fb18129e0148304502210096e859827fb629b6547658c613f7c8298de151513d74b224560aa8608d521d600220736fb5564322237716ec940de44c67c428198adf5dedfda183c17aa77cd28d640147522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae00000000", + "version": 1, + "incomplete": true, + "inputs": [ + { + "txId": "a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS", + "witnessScript": "OP_2 02bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e2 02d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea19 OP_2 OP_CHECKMULTISIG", + "redeemScript": "OP_0 24376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af", + "value": 100000 + }, + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "cTUFsNeVd8TKU4yREN8nMdViNnHyNvCCYVRmRUmkMLgomiMWTiii", + "witnessScript": "OP_2 02bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e2 02d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea19 OP_2 OP_CHECKMULTISIG", + "redeemScript": "OP_0 24376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af", + "value": 100000 + } + ], + "prevTxScript": "OP_HASH160 b64f1a3eacc1c8515592a6f10457e8ff90e4db6a OP_EQUAL" + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 99000 + } + ] + }, + { + "description": "P2SH(P2WSH(P2MS 2/3)) -> P2PKH", + "network": "testnet", + "txHex": "01000000000101ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01000000232200201b48bf145648b9492ecd6d76754ea3def4b90e22e4ef7aee9ca291b2de455701ffffffff01f07e0e00000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac0400473044022036c9ecb03cb04c09be1f52766725dcfe9a815973bd2f34ce19a345f2d925a45502207b90737852d2508db104ad17612de473687e67928c045555a1ed8d495c0570d901483045022100aec0e58e4e597b35ca5a727702a0da3d4f2ef4759914da7fc80aecb3c479a6d902201ec27ea8dcca4b73ee81e4b627f52f9e627c3497f61e4beeb98f86e02979640a0169522103c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc5787721020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b02102d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc914953ae00000000", + "version": 1, + "stages": [ + "01000000000101ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01000000232200201b48bf145648b9492ecd6d76754ea3def4b90e22e4ef7aee9ca291b2de455701ffffffff01f07e0e00000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac0500473044022036c9ecb03cb04c09be1f52766725dcfe9a815973bd2f34ce19a345f2d925a45502207b90737852d2508db104ad17612de473687e67928c045555a1ed8d495c0570d901000069522103c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc5787721020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b02102d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc914953ae00000000" + ], + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "cUxccFVBdJRq6HnyxiFMd8Z15GLThXaNLcnPBgoXLEv9iX6wuV2b", + "witnessScript": "OP_2 03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877 020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0 02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149 OP_3 OP_CHECKMULTISIG", + "redeemScript": "OP_0 1b48bf145648b9492ecd6d76754ea3def4b90e22e4ef7aee9ca291b2de455701", + "value": 1000000, + "stage": true + }, + { + "prevOutScriptType": "p2sh-p2wsh-p2ms", + "keyPair": "cVSNe9ZdZRsRvEBL8YRR7YiZmH4cLsf5FthgERWkZezJVrGseaXy", + "witnessScript": "OP_2 03c411cf39aca4395c81c35921dc832a0d1585d652ab1b52ccc619ff9fbbc57877 020636d944458a4663b75a912c37dc1cd59b11f9a00106783a65ba230d929b96b0 02d1448cbf19528a1a27e5958ba73d930b5b3facdbe5c30c7094951a287fcc9149 OP_3 OP_CHECKMULTISIG", + "redeemScript": "OP_0 1b48bf145648b9492ecd6d76754ea3def4b90e22e4ef7aee9ca291b2de455701", + "value": 1000000 + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 950000 + } + ] + } + ], + "fromTransaction": [ + { + "description": "Transaction w/ P2SH(P2MS 2/2) -> OP_RETURN | 1 OP_0 fixes to 2 OP_0, no signatures", + "network": "testnet", + "incomplete": true, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "scriptSig": "OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae", + "scriptSigAfter": "OP_0 OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Transaction w/ P2SH(P2MS 2/2) -> OP_RETURN | missing OP_0, 1 signature", + "network": "testnet", + "incomplete": true, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "scriptSig": "OP_0 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae", + "scriptSigAfter": "OP_0 OP_0 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Transaction w/ P2SH(P2MS 2/2) -> OP_RETURN | no OP_0, 2 signatures", + "network": "testnet", + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae", + "scriptSigAfter": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + } + ], + "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": [ + { + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "cTkcnMZoFYH1UgumzCFHv2veLMNN1PaJyHHUxFT127zhNGBqqEZ2", + "redeemScript": "OP_2 0258db1bb3801f1ecde47602143beaeb9cac93251724b8e589fae5c08c1a399a91 038e803e3d84cfc821cc8bf46233a9c2bb359d529db0bcdd3f1a4f38678dd02d7f 03b83e59d848407d7f62a82c99905f5ca3e8e8f5d6400eb78a0b4b067aea0720d9 OP_3 OP_CHECKMULTISIG" + } + ] + } + ] + } + ], + "classification": { + "hex": "01000000059c06fb641a8cd69be81ca91e68d8a115cb698396876ecd77120ec1e4ab9002279f000000b500483045022100d58f828ab39cfac592f89fe372fb520992975218698c683a893f29e39cf0080302207cc0485dab5ce621089bdd15e1f15db0ecbde8dd4bb661bcf0e3af6ecab075e6014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff0821dc00213d2b7993f8f2a1553800c6f2f31106da176505d0ade467b68401d795000000b400473044022028e937a7bba888fe3428f442f6e22d92ce2ddba01548c38780d40890fa6cc305022043204d0bcfb1150b045d54cf9b13462e44e2ef47fee03d3cea08e84a8060fc30014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffffaa997ac385dc666af1f5947ef615431024eb314cac2308d5e1b903e28ca466f499000000b50048304502210093efc26facedc5f51e304aa270a7b4f1a911b2d912c3674e5c6e2ad4ac7a410402201cf0b62c240461902f9f16d8a0bc3a210b7bfcd2c06523dd4b4b63be22e85252014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffffd9f61bf98a021ee144f33ba5a6b04274de8fcb5c05f1ff7c12367fb7a608b2dd9e000000b4004730440220456e1201c1fa727288cba7fa0054dc02d8dd6c7418cae1e97006ef0652891c9202201192d0fbf3a9c00afb99a415f2bf515509e1150805acd8de95c496c27cb6570f014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff1f8119e3bc7c2f451feaa79f42ec5a63502afb425c253c935e43d217d5c29bdea1000000b500483045022100f669004f770490093eba4ac4903cb7581f7d18ea9245c538585ef5367e520e4702205485fafe0be178563a599d41e0cc172bb01314ed65d0e48df19a5258f17bdbc4014c6952210327e023a353d111808f61d554c2e1934721eaf87f33b7a771e807006908a493722103251670bb6a179a0d43b75476c7e580c0ba274378a18077e8de0832c870e5381f2102cca7f9a64425a0466d26d5c7e9eb3ad6b64cd48ea89edb38bc08f58a792dde4753aeffffffff0380f0fa02000000001976a91439692085cf9d27e8c1cf63e76bd32d9bd15cab8b88ac50c300000000000017a9147204bb26950ce1595255897f63d205779f033f3e875b5409000000000017a9142538893d984a4b5695e4bfde1a90a9f02fabf8e38700000000" + }, + "multisig": [ + { + "description": "P2SH(P2MS 2/2), signed in correct order", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd1c0100483045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001483045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b06576014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 1, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "P2SH(P2MS 2/2), signed in shuffled order", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd1c0100483045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001483045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b06576014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 1, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "scriptSig": "OP_0 OP_0 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "P2SH(P2MS 2/2), manually messed up order of signatures", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd1c0100483045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001483045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b06576014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 1, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "scriptSig": "OP_0 3045022100e37e33a4fe5fccfb87afb0e951e83fcea4820d73b327d21edc1adec3b916c437022061c5786908b674e323a1863cc2feeb60e1679f1892c10ee08ac21e51fd50ba9001 3045022100aa0c323bc639d3d71591be98ccaf7b8cb8c86aa95f060aef5e36fc3f04c4d029022010e2b18de17e307a12ae7e0e88518fe814f18a0ca1ee4510ba23a65628b0657601 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "P2SH(P2MS 2/3), signed by key 1 and 2", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd5e0100483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01483045022100ae06d8269b79b5cfa0297d1d88261b0061e52fc2814948c3aa05fa78ee76894302206e0c79a5c90569d8c72a542ef9a06471cbbcd2c651b312339983dfba4f8ff463014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 1, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100ae06d8269b79b5cfa0297d1d88261b0061e52fc2814948c3aa05fa78ee76894302206e0c79a5c90569d8c72a542ef9a06471cbbcd2c651b312339983dfba4f8ff46301 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "P2SH(P2MS 2/3), signed by key 1 and 3", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd5e0100483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01483045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af0014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 2, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "P2SH(P2MS 2/3), signed by key 3 and 1", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd5e0100483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01483045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af0014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 2, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "scriptSig": "OP_0 OP_0 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "P2SH(P2MS 2/3), signed by key 1 and 3, manually messed up order of signatures", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd5e0100483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01483045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af0014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 OP_0 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 2, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "P2SH(P2MS 2/3), signed by key 3 and 1, manually removing OP_0s", + "network": "testnet", + "txHex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd5e0100483045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01483045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af0014cc952410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253aeffffffff01e8030000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "version": 1, + "inputs": [ + { + "txId": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf", + "vout": 0, + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 2, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe", + "scriptSig": "OP_0 OP_0 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "pubKeyIndex": 0, + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "scriptSigBefore": "OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae", + "scriptSig": "OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 OP_0 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + } + ] + }, + "invalid": { + "build": [ + { + "exception": "Transaction has no inputs", + "inputs": [], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "exception": "Transaction has no outputs", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0 + } + ], + "outputs": [] + }, + { + "exception": "Transaction has absurd fees", + "inputs": [ + { + "txRaw": { + "inputs": [], + "outputs": [ + { + "address": "1C5XhB1UkFuyCV1CG9dmXaXGu3xDL4nAjv", + "value": 1000000000 + } + ], + "incomplete": true + }, + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KzBQVXYUGDAvqG7VeU3C7ZMRYiwtsxSVVFcYGzKU9E4aUVDUquZU" + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a OP_EQUALVERIFY OP_CHECKSIG", + "value": 200000 + } + ] + }, + { + "description": "Incomplete transaction, nothing assumed", + "exception": "Transaction is not complete", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0 + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Incomplete transaction, known prevTxScript, thereby throws for missing signatures", + "exception": "Not enough information", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + } + ] + }, + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Incomplete Transaction P2SH(P2MS 2/3), missing signature", + "exception": "Not enough signatures provided", + "network": "testnet", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "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", + "exception": "Duplicate TxOut: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff:0", + "incomplete": true, + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0 + }, + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0 + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, + { + "description": "Transaction w/ invalid scripts", + "exception": "Invalid script", + "incomplete": true, + "txHex": "010000000100000000171a0000e028f2000000000050178500000000000d0000000e000000000000002009f691b2263260e71f363d1db51ff3100d285956a40cc0e4f8c8c2c4a80559b1ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000" + }, + { + "description": "Complete transaction w/ non-standard inputs", + "exception": "Unknown input type", + "txHex": "010000000100000000171a0000e028f2000000000050178500000000000d0000000e00000000000000201ff691b2263260e71f363d1db51ff3100d285956a40cc0e4f8c8c2c4a80559b1ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000" + } + ], + "sign": [ + { + "description": "Transaction w/ witness value mismatch", + "exception": "Input did not match witnessValue", + "network": "testnet", + "inputs": [ + { + "txHex": "01000000000101f7e6430096cd2790bac115aaab22c0a50fb0a1794305302e1a399e81d8d354f40200000000feffffff0340420f00000000001600145c7b8d623fba952d2387703d051d8e931a6aa0a18bda2702000000001976a9145a0ef60784137d03e7868d063b05424f2f43799f88ac40420f00000000001976a9145c7b8d623fba952d2387703d051d8e931a6aa0a188ac0247304402205793a862d193264afc32713e2e14541e1ff9ebb647dd7e7e6a0051d0faa87de302205216653741ecbbed573ea2fc053209dd6980616701c27be5b958a159fc97f45a012103e877e7deb32d19250dcfe534ea82c99ad739800295cd5429a7f69e2896c36fcd2fcc0e00", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2wpkh", + "keyPair": "cQ6483mDWwoG8o4tn6nU9Jg52RKMjPUWXSY1vycAyPRXQJ1Pn2Rq", + "throws": true, + "value": 22500000000 } ] } ], "outputs": [ { - "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", - "value": 10000 + "script": "OP_DUP OP_HASH160 ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a OP_EQUALVERIFY OP_CHECKSIG", + "value": 6000000 } ] }, { - "description": "Transaction w/ multisig 2-of-2 -> pubKeyHash", - "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000009200483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a401ffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "description": "Too many signatures - P2PKH", + "exception": "Signature already exists", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "vout": 0, - "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "vout": 1, "signs": [ { - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" }, { - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true } ] } ], "outputs": [ { - "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", - "value": 10000 + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 } ] }, { - "description": "Transaction w/ multisig 2-of-2 (reverse order) -> pubKeyHash", - "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000009200483045022100b7a9bab60c4307349de9571ce0bd26ebb9d68d4e9ab3f9173e1f736f1390a04a022020931ff70e87033cdd94bdf434e865993b2258065c5c222a53f29d077bcfa4480147304402206d79ad83f1ab12fc9feee9e66412de842fcbf8de0632beb4433d469f24f0fb4e022079e6df186582f2686a3292bde8e50dac36cb9bec3991995fe331e1daef7df8a401ffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000", + "description": "Transaction w/ P2WPKH, signing with uncompressed public key", + "exception": "BIP143 rejects uncompressed public keys in P2WPKH or P2WSH", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, - "prevTxScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "prevTxScript": "OP_0 15a71ffa7b5bb70cddefcf364494071022efe390", "signs": [ { - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT" - }, - { - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + "prevOutScriptType": "p2wpkh", + "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", + "value": 10000, + "throws": true } ] } ], "outputs": [ { - "script": "OP_DUP OP_HASH160 faf1d99bf040ea9c7f8cc9f14ac6733ad75ce246 OP_EQUALVERIFY OP_CHECKSIG", + "script": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", "value": 10000 } ] }, { - "description": "Transaction w/ scriptHash(pubKey) -> pubKeyHash", - "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006c47304402201115644b134932c8a7a8e925769d130a801288d477130e2bf6fadda20b33754d02202ecefbf63844d7cb2d5868539c39f973fe019f72e5c31a707836c0d61ef317db012321033e29aea1168a835d5e386c292082db7b7807172a10ec634ad34226f36d79e70facffffffff0100f90295000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "description": "Transaction w/ P2WSH(P2PK), signing with uncompressed public key", + "exception": "BIP143 rejects uncompressed public keys in P2WPKH or P2WSH", "inputs": [ { - "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345", "vout": 0, - "prevTxScript": "OP_HASH160 e89677d91455e541630d62c63718bef738b478b1 OP_EQUAL", + "prevTxScript": "OP_0 5339df4de3854c4208376443ed075014ad996aa349ad6b5abf6c4d20f604d348", "signs": [ { - "privKey": "KxLDMPtVM7sLSu2v5n1LybDibw6P9FFbL4pUwJ51UDm7rp5AmXWW", - "redeemScript": "033e29aea1168a835d5e386c292082db7b7807172a10ec634ad34226f36d79e70f OP_CHECKSIG" + "prevOutScriptType": "p2wsh-p2pk", + "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", + "witnessScript": "04f56d09b32cefc818735150bf8560eefdaf30d2edb3fe557bf27682aedaed81bf9aaff7eeb496e088058ec548826c12b521dbb566a862d9b67677910c2b421e06 OP_CHECKSIG", + "value": 80000, + "throws": true } ] } ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", - "value": 2500000000 + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "value": 60000 } ] }, { - "description": "Transaction w/ non-zero vin inputs", - "txHex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205c80bbb5125b35d5e5a8324b1336832d29a6fc004859c8a9ff6bef47ba7fc348022018612216e57a521b2c4543f1f4fd738a76814c37c074e88adfe12464fff31cf901210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000", + "description": "Transaction w/ P2SH(P2WSH(P2PK)), signing with uncompressed public key", + "exception": "BIP143 rejects uncompressed public keys in P2WPKH or P2WSH", + "inputs": [ + { + "txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345", + "vout": 0, + "prevTxScript": "OP_HASH160 5afe12b2827e3eac05fe3f17c59406ef262aa177 OP_EQUAL", + "signs": [ + { + "prevOutScriptType": "p2sh-p2wsh-p2pk", + "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", + "redeemScript": "OP_0 5339df4de3854c4208376443ed075014ad996aa349ad6b5abf6c4d20f604d348", + "witnessScript": "04f56d09b32cefc818735150bf8560eefdaf30d2edb3fe557bf27682aedaed81bf9aaff7eeb496e088058ec548826c12b521dbb566a862d9b67677910c2b421e06 OP_CHECKSIG", + "value": 80000, + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG", + "value": 60000 + } + ] + }, + { + "exception": "nulldata not supported as redeemScript \\(OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474\\)", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 1, "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + "prevOutScriptType": "p2sh-p2pk", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "redeemScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", + "throws": true } ] } @@ -181,23 +2192,22 @@ "outputs": [ { "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", - "value": 10000 + "value": 1000 } ] }, { - "description": "Transaction w/ non-default input sequence numbers, version and locktime", - "txHex": "0400000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000006b483045022100c5bcd521df085481e2dcc2c0f14173043f0fa2001dca582b45186a95d248d28002204c571eabcec1410bd53a7da29b9da6b4c858c3fdabbfdb110a030c507ff5bc0501210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798b9c220000110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac09990400", - "version": 4, - "locktime": 301321, + "exception": "p2sh-p2pkh requires redeemScript", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "vout": 2, - "sequence": 2147001, + "vout": 1, + "prevTxScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + "prevOutScriptType": "p2sh-p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true } ] } @@ -205,17 +2215,26 @@ "outputs": [ { "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", - "value": 10000 + "value": 1000 } ] - } - ] - }, - "invalid": { - "build": [ + }, { - "exception": "Transaction has no inputs", - "inputs": [], + "exception": "p2wsh-p2pk requires witnessScript", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "prevTxScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210", + "signs": [ + { + "prevOutScriptType": "p2wsh-p2pk", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true + } + ] + } + ], "outputs": [ { "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", @@ -224,52 +2243,99 @@ ] }, { - "exception": "Transaction has no outputs", + "exception": "Inconsistent redeemScript", + "network": "testnet", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, - "signs": [] + "signs": [ + { + "prevOutScriptType": "p2sh-p2ms", + "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" + }, + { + "prevOutScriptType": "p2sh-p2ms", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "throws": true + } + ] } ], - "outputs": [] + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] }, { - "description": "Incomplete transaction, nothing assumed", - "exception": "Transaction is not complete", + "description": "Inconsistent RedeemScript hash", + "exception": "Redeem script inconsistent with prevOutScript", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, - "signs": [] + "prevTxScript": "OP_HASH160 ffffffffffffffffffffffffffffffffffffffff OP_EQUAL", + "signs": [ + { + "prevOutScriptType": "p2sh-p2pkh", + "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", + "redeemScript": "OP_1", + "throws": true + } + ] } ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", - "value": 1000 + "script": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "value": 10000 } ] }, { - "description": "Incomplete transaction w/ prevTxScript defined", - "exception": "Transaction is missing signatures", - "alwaysThrows": true, + "description": "Inconsistent WitnessScript hash", + "exception": "Witness script inconsistent with prevOutScript", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, + "prevTxScript": "OP_0 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + "prevOutScriptType": "p2wsh-p2pkh", + "keyPair": "5JiHJJjdufSiMxbvnyNcKtQNLYH6SvUpQnRv9yZENFDWTQKQkzC", + "witnessScript": "OP_1", + "value": 10000, + "throws": true } ] - }, + } + ], + "outputs": [ + { + "script": "OP_0 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5", + "value": 10000 + } + ] + }, + { + "exception": "scripthash not supported as redeemScript \\(OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL\\)", + "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 1, - "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", - "signs": [] + "signs": [ + { + "prevOutScriptType": "p2sh-p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "redeemScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", + "throws": true + } + ] } ], "outputs": [ @@ -280,14 +2346,31 @@ ] }, { - "description": "Complete transaction w/ non-standard inputs", - "exception": "nonstandard not supported", - "txHex": "010000000100000000171a0000e028f2000000000050178500000000000d0000000e000000000000002009f691b2263260e71f363d1db51ff3100d285956a40cc0e4f8c8c2c4a80559b1ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000" - } - ], - "sign": [ + "exception": "input #0 is not of type p2sh-p2pkh: pubkeyhash", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "signs": [ + { + "prevOutScriptType": "p2sh-p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "value": 1000 + } + ] + }, { - "description": "Too many signatures - pubKeyHash", + "description": "Too many signatures - P2SH(P2MS 1/1)", "exception": "Signature already exists", "inputs": [ { @@ -295,10 +2378,14 @@ "vout": 1, "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn" + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG" }, { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", "throws": true } ] @@ -312,15 +2399,18 @@ ] }, { - "exception": "RedeemScript not supported \\(nulldata\\)", + "description": "Wrong network for keyPair", + "exception": "Inconsistent network", + "network": "bitcoin", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "vout": 1, + "vout": 0, "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", - "redeemScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", + "prevOutScriptType": "p2pkh", + "keyPair": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", + "network": "testnet", "throws": true } ] @@ -334,15 +2424,17 @@ ] }, { - "exception": "PrevOutScript is P2SH, missing redeemScript", + "description": "Wrong key pair for P2MS redeemScript", + "exception": "Key pair cannot sign for this input", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 1, - "prevTxScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "prevOutScriptType": "p2sh-p2ms", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", "throws": true } ] @@ -356,19 +2448,16 @@ ] }, { - "exception": "Inconsistent redeemScript", + "exception": "input #0 is not of type p2pkh: nulldata", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, + "prevTxScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", "signs": [ { - "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx" - }, - { - "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", + "prevOutScriptType": "p2pkh", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } ] @@ -382,20 +2471,39 @@ ] }, { - "exception": "Inconsistent hashType", + "exception": "input #0 is not of type p2pk: nulldata", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", "signs": [ { - "redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG", - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", - "hashType": 4 - }, + "prevOutScriptType": "p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2wpkh: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ { - "privKey": "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT", - "hashType": 2, + "prevOutScriptType": "p2wpkh", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } ] @@ -403,21 +2511,22 @@ ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "script": "OP_RETURN deadbeef", "value": 1000 } ] }, { - "exception": "RedeemScript not supported \\(scripthash\\)", + "exception": "input #0 is not of type p2ms: nulldata", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "vout": 1, + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", - "redeemScript": "OP_HASH160 7f67f0521934a57d3039f77f9f32cf313f3ac74b OP_EQUAL", + "prevOutScriptType": "p2ms", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } ] @@ -425,22 +2534,22 @@ ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "script": "OP_RETURN deadbeef", "value": 1000 } ] }, { - "exception": "PrevOutScript must be P2SH", + "exception": "input #0 is not of type p2sh-p2wpkh: nulldata", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "vout": 1, - "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", - "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", + "prevOutScriptType": "p2sh-p2wpkh", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } ] @@ -448,25 +2557,45 @@ ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "script": "OP_RETURN deadbeef", "value": 1000 } ] }, { - "description": "Too many signatures - scriptHash(multisig 1-of-1)", - "exception": "Signature already exists", + "exception": "input #0 is not of type p2sh-p2pk: nulldata", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "vout": 1, + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", - "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG" - }, + "prevOutScriptType": "p2sh-p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "throws": true + } + ] + } + ], + "outputs": [ + { + "script": "OP_RETURN deadbeef", + "value": 1000 + } + ] + }, + { + "exception": "input #0 is not of type p2wsh-p2pk: nulldata", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", + "signs": [ { - "privKey": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "prevOutScriptType": "p2wsh-p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } ] @@ -474,22 +2603,22 @@ ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "script": "OP_RETURN deadbeef", "value": 1000 } ] }, { - "description": "Wrong private key for multisig redeemScript", - "exception": "privateKey cannot sign for this input", + "exception": "input #0 is not of type p2sh-p2wsh-p2pk: nulldata", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "vout": 1, + "vout": 0, + "prevTxScript": "OP_RETURN deadbeef", "signs": [ { - "privKey": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", - "redeemScript": "OP_1 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 OP_1 OP_CHECKMULTISIG", + "prevOutScriptType": "p2sh-p2wsh-p2pk", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } ] @@ -497,21 +2626,22 @@ ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "script": "OP_RETURN deadbeef", "value": 1000 } ] }, { - "exception": "nulldata not supported", + "exception": "Unknown prevOutScriptType \"notvalidtype\"", "inputs": [ { "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "vout": 0, - "prevTxScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474", + "prevTxScript": "OP_RETURN deadbeef", "signs": [ { - "privKey": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", + "prevOutScriptType": "notvalidtype", + "keyPair": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", "throws": true } ] @@ -519,10 +2649,57 @@ ], "outputs": [ { - "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "script": "OP_RETURN deadbeef", "value": 1000 } ] + }, + { + "description": "Transaction w/ no outputs (but 1 SIGHASH_NONE)", + "exception": "Transaction needs outputs", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "hashType": 2 + } + ] + }, + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 1, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true + } + ] + } + ], + "outputs": [] + }, + { + "description": "Transaction w/ no outputs", + "exception": "Transaction needs outputs", + "inputs": [ + { + "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "vout": 0, + "signs": [ + { + "prevOutScriptType": "p2pkh", + "keyPair": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", + "throws": true + } + ] + } + ], + "outputs": [] } ], "fromTransaction": [ diff --git a/test/hdnode.js b/test/hdnode.js deleted file mode 100644 index 342f0f5ff..000000000 --- a/test/hdnode.js +++ /dev/null @@ -1,290 +0,0 @@ -/* global describe, it */ -/* eslint-disable no-new */ - -var assert = require('assert') -var networks = require('../src/networks') - -var BigInteger = require('bigi') -var ECKey = require('../src/eckey') -var ECPubKey = require('../src/ecpubkey') -var HDNode = require('../src/hdnode') - -var ecurve = require('ecurve') -var curve = ecurve.getCurveByName('secp256k1') - -var fixtures = require('./fixtures/hdnode.json') - -describe('HDNode', function () { - describe('Constructor', function () { - var d = BigInteger.ONE - var Q = curve.G.multiply(d) - var chainCode = new Buffer(32) - chainCode.fill(1) - - it('calculates the publicKey from a BigInteger', function () { - var hd = new HDNode(d, chainCode) - - assert(hd.pubKey.Q.equals(Q)) - }) - - it('allows initialization directly from an ECKey', function () { - var ek = new ECKey(d) - var hd = new HDNode(ek, chainCode) - - assert.equal(hd.privKey, ek) - }) - - it('allows initialization directly from an ECPubKey', function () { - var ek = new ECPubKey(Q) - var hd = new HDNode(ek, chainCode) - - assert.equal(hd.pubKey, ek) - }) - - it('throws if ECKey is not compressed', function () { - var ek = new ECKey(d, false) - - assert.throws(function () { - new HDNode(ek, chainCode) - }, /ECKey must be compressed/) - }) - - it('throws if ECPubKey is not compressed', function () { - var ek = new ECPubKey(Q, false) - - assert.throws(function () { - new HDNode(ek, chainCode) - }, /ECPubKey must be compressed/) - }) - - it('only uses compressed points', function () { - var hd = new HDNode(Q, chainCode) - var hdP = new HDNode(d, chainCode) - - assert.strictEqual(hd.pubKey.compressed, true) - assert.strictEqual(hdP.pubKey.compressed, true) - }) - - it('has a default depth/index of 0', function () { - var hd = new HDNode(Q, chainCode) - - assert.strictEqual(hd.depth, 0) - assert.strictEqual(hd.index, 0) - }) - - it('defaults to the bitcoin network', function () { - var hd = new HDNode(Q, chainCode) - - assert.equal(hd.network, networks.bitcoin) - }) - - it('supports alternative networks', function () { - var hd = new HDNode(Q, chainCode, networks.testnet) - - assert.equal(hd.network, networks.testnet) - }) - - it('throws when an invalid length chain code is given', function () { - assert.throws(function () { - new HDNode(d, chainCode.slice(0, 20), networks.testnet) - }, /Expected chainCode length of 32, got 20/) - }) - - it('throws when an unknown network is given', function () { - assert.throws(function () { - new HDNode(d, chainCode, {}) - }, /Unknown BIP32 constants for network/) - }) - }) - - describe('fromSeed*', function () { - fixtures.valid.forEach(function (f) { - it('calculates privKey and chainCode for ' + f.master.fingerprint, function () { - var network = networks[f.network] - var hd = HDNode.fromSeedHex(f.master.seed, network) - - assert.equal(hd.privKey.toWIF(network), f.master.wif) - assert.equal(hd.chainCode.toString('hex'), f.master.chainCode) - }) - }) - - it('throws on low entropy seed', function () { - assert.throws(function () { - HDNode.fromSeedHex('ffffffffff') - }, /Seed should be at least 128 bits/) - }) - - it('throws on too high entropy seed', function () { - assert.throws(function () { - HDNode.fromSeedHex('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') - }, /Seed should be at most 512 bits/) - }) - }) - - describe('toBase58', function () { - fixtures.valid.forEach(function (f) { - it('exports ' + f.master.base58 + ' (public) correctly', function () { - var network = networks[f.network] - var hd = HDNode.fromSeedHex(f.master.seed, network).neutered() - - assert.equal(hd.toBase58(), f.master.base58) - }) - }) - - fixtures.valid.forEach(function (f) { - it('exports ' + f.master.base58Priv + ' (private) correctly', function () { - var network = networks[f.network] - var hd = HDNode.fromSeedHex(f.master.seed, network) - - assert.equal(hd.toBase58(), f.master.base58Priv) - }) - }) - }) - - describe('fromBase58', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.master.base58 + ' (public) correctly', function () { - var hd = HDNode.fromBase58(f.master.base58) - - assert.equal(hd.toBase58(), f.master.base58) - }) - }) - - fixtures.valid.forEach(function (f) { - it('imports ' + f.master.base58Priv + ' (private) correctly', function () { - var hd = HDNode.fromBase58(f.master.base58Priv) - - assert.equal(hd.toBase58(), f.master.base58Priv) - }) - }) - - fixtures.invalid.fromBase58.forEach(function (f) { - it('throws on ' + f.string, function () { - assert.throws(function () { - var network = networks[f.network] - - HDNode.fromBase58(f.string, network) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('getIdentifier', function () { - var f = fixtures.valid[0] - - it('returns the identifier for ' + f.master.fingerprint, function () { - var hd = HDNode.fromBase58(f.master.base58) - - assert.equal(hd.getIdentifier().toString('hex'), f.master.identifier) - }) - }) - - describe('getFingerprint', function () { - var f = fixtures.valid[0] - - it('returns the fingerprint for ' + f.master.fingerprint, function () { - var hd = HDNode.fromBase58(f.master.base58) - - assert.equal(hd.getFingerprint().toString('hex'), f.master.fingerprint) - }) - }) - - describe('getAddress', function () { - fixtures.valid.forEach(function (f) { - it('returns ' + f.master.address + ' for ' + f.master.fingerprint, function () { - var hd = HDNode.fromBase58(f.master.base58) - - assert.equal(hd.getAddress().toString(), f.master.address) - }) - }) - }) - - describe('neutered', function () { - var f = fixtures.valid[0] - - it('strips all private information', function () { - var hd = HDNode.fromBase58(f.master.base58) - var hdn = hd.neutered() - - assert.equal(hdn.privKey, undefined) - assert.equal(hdn.pubKey.toHex(), hd.pubKey.toHex()) - assert.equal(hdn.chainCode, hd.chainCode) - assert.equal(hdn.depth, hd.depth) - assert.equal(hdn.index, hd.index) - }) - }) - - describe('derive', function () { - function verifyVector (hd, network, v, depth) { - assert.equal(hd.privKey.toWIF(network), v.wif) - assert.equal(hd.pubKey.toHex(), v.pubKey) - assert.equal(hd.chainCode.toString('hex'), v.chainCode) - assert.equal(hd.depth, depth || 0) - - if (v.hardened) { - assert.equal(hd.index, v.m + HDNode.HIGHEST_BIT) - } else { - assert.equal(hd.index, v.m) - } - } - - fixtures.valid.forEach(function (f) { - var network = networks[f.network] - var hd = HDNode.fromSeedHex(f.master.seed, network) - - // FIXME: test data is only testing Private -> private for now - f.children.forEach(function (c, i) { - it(c.description + ' from ' + f.master.fingerprint, function () { - if (c.hardened) { - hd = hd.deriveHardened(c.m) - } else { - hd = hd.derive(c.m) - } - - verifyVector(hd, network, c, i + 1) - }) - }) - }) - - it('works for Private -> public (neutered)', function () { - var f = fixtures.valid[1] - var c = f.children[0] - - var master = HDNode.fromBase58(f.master.base58Priv) - var child = master.derive(c.m).neutered() - - assert.equal(child.toBase58(), c.base58) - }) - - it('works for Private -> public (neutered, hardened)', function () { - var f = fixtures.valid[0] - var c = f.children[0] - - var master = HDNode.fromBase58(f.master.base58Priv) - var child = master.deriveHardened(c.m).neutered() - - assert.equal(child.toBase58(), c.base58) - }) - - it('works for Public -> public', function () { - var f = fixtures.valid[1] - var c = f.children[0] - - var master = HDNode.fromBase58(f.master.base58) - var child = master.derive(c.m) - - assert.equal(child.toBase58(), c.base58) - }) - - it('throws on Public -> public (hardened)', function () { - var f = fixtures.valid[0] - var c = f.children[0] - - var master = HDNode.fromBase58(f.master.base58) - - assert.throws(function () { - master.deriveHardened(c.m) - }, /Could not derive hardened child key/) - }) - }) -}) diff --git a/test/integration/_regtest.js b/test/integration/_regtest.js new file mode 100644 index 000000000..c50ef2f38 --- /dev/null +++ b/test/integration/_regtest.js @@ -0,0 +1,8 @@ +const { RegtestUtils } = require('regtest-client') + +const APIPASS = process.env.APIPASS || 'satoshi' +const APIURL = process.env.APIURL || 'https://regtest.bitbank.cc/1' + +const regtestUtils = new RegtestUtils({ APIPASS, APIURL }) + +module.exports = regtestUtils; diff --git a/test/integration/addresses.js b/test/integration/addresses.js new file mode 100644 index 000000000..1d2802091 --- /dev/null +++ b/test/integration/addresses.js @@ -0,0 +1,117 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const bitcoin = require('../../') +const dhttp = require('./_regtest').dhttp +const TESTNET = bitcoin.networks.testnet + +describe('bitcoinjs-lib (addresses)', () => { + it('can generate a random address [and support the retrieval of transactions for that address (via 3PBP)', async () => { + const keyPair = bitcoin.ECPair.makeRandom() + const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }) + + // 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 import an address via WIF', () => { + const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn') + const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }) + + assert.strictEqual(address, '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH') + }) + + it('can generate a P2SH, pay-to-multisig (2-of-3) address', () => { + const pubkeys = [ + '026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01', + '02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9', + '03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9' + ].map((hex) => Buffer.from(hex, 'hex')) + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2ms({ m: 2, pubkeys }) + }) + + assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7') + }) + + it('can generate a SegWit address', () => { + const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn') + const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }) + + assert.strictEqual(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4') + }) + + it('can generate a SegWit address (via P2SH)', () => { + const keyPair = bitcoin.ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn') + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }) + }) + + assert.strictEqual(address, '3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN') + }) + + it('can generate a P2WSH (SegWit), pay-to-multisig (3-of-4) address', () => { + const pubkeys = [ + '026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01', + '02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9', + '023e4740d0ba639e28963f3476157b7cf2fb7c6fdf4254f97099cf8670b505ea59', + '03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9' + ].map((hex) => Buffer.from(hex, 'hex')) + const { address } = bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: 3, pubkeys }) + }) + + assert.strictEqual(address, 'bc1q75f6dv4q8ug7zhujrsp5t0hzf33lllnr3fe7e2pra3v24mzl8rrqtp3qul') + }) + + it('can generate a P2SH(P2WSH(...)), pay-to-multisig (2-of-2) address', () => { + const pubkeys = [ + '026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01', + '02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9' + ].map((hex) => Buffer.from(hex, 'hex')) + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2ms({ m: 2, pubkeys }) + }) + }) + + assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN') + }) + + // examples using other network information + it('can generate a Testnet address', () => { + const keyPair = bitcoin.ECPair.makeRandom({ network: TESTNET }) + const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: TESTNET }) + + // bitcoin testnet P2PKH addresses start with a 'm' or 'n' + assert.strictEqual(address.startsWith('m') || address.startsWith('n'), true) + }) + + it('can generate a Litecoin address', () => { + // WARNING: although possible, bitcoinjs is NOT necessarily compatible with Litecoin + const LITECOIN = { + messagePrefix: '\x19Litecoin Signed Message:\n', + bip32: { + public: 0x019da462, + private: 0x019d9cfe + }, + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0 + } + + const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN }) + const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: LITECOIN }) + + assert.strictEqual(address.startsWith('L'), true) + }) +}) diff --git a/test/integration/advanced.js b/test/integration/advanced.js deleted file mode 100644 index 3d2022851..000000000 --- a/test/integration/advanced.js +++ /dev/null @@ -1,68 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') -var bitcoin = require('../../') -var blockchain = new (require('cb-helloblock'))('testnet') - -describe('bitcoinjs-lib (advanced)', function () { - it('can sign a Bitcoin message', function () { - var key = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') - var message = 'This is an example of a signed message.' - - var signature = bitcoin.message.sign(key, message) - assert.equal(signature.toString('base64'), 'G9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=') - }) - - it('can verify a Bitcoin message', function () { - var address = '1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN' - var signature = 'HJLQlDWLyb1Ef8bQKEISzFbDAKctIlaqOpGbrk3YVtRsjmC61lpE5ErkPRUFtDKtx98vHFGUWlFhsh3DiW6N0rE' - var message = 'This is an example of a signed message.' - - assert(bitcoin.message.verify(address, signature, message)) - }) - - it('can create an OP_RETURN transaction', function (done) { - this.timeout(20000) - - var key = bitcoin.ECKey.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy') - var address = key.pub.getAddress(bitcoin.networks.testnet).toString() - - blockchain.addresses.__faucetWithdraw(address, 2e4, function (err) { - if (err) return done(err) - - blockchain.addresses.unspents(address, function (err, unspents) { - if (err) return done(err) - - var tx = new bitcoin.TransactionBuilder() - var data = new Buffer('bitcoinjs-lib') - var dataScript = bitcoin.scripts.nullDataOutput(data) - - var unspent = unspents.pop() - - tx.addInput(unspent.txId, unspent.vout) - tx.addOutput(dataScript, 1000) - tx.sign(0, key) - - var txBuilt = tx.build() - - blockchain.transactions.propagate(txBuilt.toHex(), function (err) { - if (err) return done(err) - - // check that the message was propagated - blockchain.transactions.get(txBuilt.getId(), function (err, transaction) { - if (err) return done(err) - - var actual = bitcoin.Transaction.fromHex(transaction.txHex) - var dataScript2 = actual.outs[0].script - var data2 = dataScript2.chunks[1] - - assert.deepEqual(dataScript, dataScript2) - assert.deepEqual(data, data2) - - done() - }) - }) - }) - }) - }) -}) diff --git a/test/integration/basic.js b/test/integration/basic.js deleted file mode 100644 index 069a535e0..000000000 --- a/test/integration/basic.js +++ /dev/null @@ -1,48 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') -var bigi = require('bigi') -var bitcoin = require('../../') -var crypto = require('crypto') -var sinon = require('sinon') - -describe('bitcoinjs-lib (basic)', function () { - it('can generate a random bitcoin address', sinon.test(function () { - // for testing only - this.mock(crypto).expects('randomBytes') - .onCall(0).returns(new Buffer('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz')) - - // generate random key - var key = bitcoin.ECKey.makeRandom() - var address = key.pub.getAddress().toString() - - assert.equal(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64') - })) - - it('can generate an address from a SHA256 hash', function () { - var hash = bitcoin.crypto.sha256('correct horse battery staple') - var d = bigi.fromBuffer(hash) - - var key = new bitcoin.ECKey(d) - - assert.equal(key.pub.getAddress().toString(), '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8') - }) - - it('can import an address via WIF', function () { - var key = bitcoin.ECKey.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct') - var address = key.pub.getAddress().toString() - - assert.equal(address, '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31') - }) - - it('can create a Transaction', function () { - var key = bitcoin.ECKey.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy') - var tx = new bitcoin.TransactionBuilder() - - tx.addInput('aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31', 0) - tx.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) - tx.sign(0, key) - - assert.equal(tx.build().toHex(), '0100000001313eb630b128102b60241ca895f1d0ffca2170d5a0990e094f2182c102ab94aa000000006b483045022100aefbcf847900b01dd3e3debe054d3b6d03d715d50aea8525f5ea3396f168a1fb022013d181d05b15b90111808b22ef4f9ebe701caf2ab48db269691fdf4e9048f4f60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01983a0000000000001976a914ad618cf4333b3b248f9744e8e81db2964d0ae39788ac00000000') - }) -}) diff --git a/test/integration/bip32.js b/test/integration/bip32.js new file mode 100644 index 000000000..255669c62 --- /dev/null +++ b/test/integration/bip32.js @@ -0,0 +1,101 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const bip32 = require('bip32') +const bip39 = require('bip39') +const bitcoin = require('../../') + +function getAddress (node, network) { + return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address +} + +describe('bitcoinjs-lib (BIP32)', () => { + it('can import a BIP32 testnet xpriv and export to WIF', () => { + const xpriv = 'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK' + const node = bip32.fromBase58(xpriv, bitcoin.networks.testnet) + + assert.strictEqual(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7') + }) + + it('can export a BIP32 xpriv, then import it', () => { + const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost' + const seed = bip39.mnemonicToSeed(mnemonic) + const node = bip32.fromSeed(seed) + const string = node.toBase58() + const restored = bip32.fromBase58(string) + + assert.strictEqual(getAddress(node), getAddress(restored)) // same public key + assert.strictEqual(node.toWIF(), restored.toWIF()) // same private key + }) + + it('can export a BIP32 xpub', () => { + const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost' + const seed = bip39.mnemonicToSeed(mnemonic) + const node = bip32.fromSeed(seed) + const string = node.neutered().toBase58() + + assert.strictEqual(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n') + }) + + it('can create a BIP32, bitcoin, account 0, external address', () => { + const path = "m/0'/0/0" + const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex')) + + const child1 = root.derivePath(path) + + // option 2, manually + const child1b = root.deriveHardened(0) + .derive(0) + .derive(0) + + assert.strictEqual(getAddress(child1), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7') + assert.strictEqual(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7') + }) + + it('can create a BIP44, bitcoin, account 0, external address', () => { + const root = bip32.fromSeed(Buffer.from('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', 'hex')) + + const child1 = root.derivePath("m/44'/0'/0'/0/0") + + // option 2, manually + const child1b = root.deriveHardened(44) + .deriveHardened(0) + .deriveHardened(0) + .derive(0) + .derive(0) + + assert.strictEqual(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au') + assert.strictEqual(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au') + }) + + 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 seed = bip39.mnemonicToSeed(mnemonic) + const root = bip32.fromSeed(seed) + + const path = "m/49'/1'/0'/0/0" + const child = root.derivePath(path) + + const { address } = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }), + network: bitcoin.networks.testnet + }) + assert.strictEqual(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2') + }) + + it('can use BIP39 to generate BIP32 addresses', () => { + // var mnemonic = bip39.generateMnemonic() + const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost' + assert(bip39.validateMnemonic(mnemonic)) + + const seed = bip39.mnemonicToSeed(mnemonic) + const root = bip32.fromSeed(seed) + + // receive addresses + assert.strictEqual(getAddress(root.derivePath("m/0'/0/0")), '1AVQHbGuES57wD68AJi7Gcobc3RZrfYWTC') + assert.strictEqual(getAddress(root.derivePath("m/0'/0/1")), '1Ad6nsmqDzbQo5a822C9bkvAfrYv9mc1JL') + + // change addresses + assert.strictEqual(getAddress(root.derivePath("m/0'/1/0")), '1349KVc5NgedaK7DvuD4xDFxL86QN1Hvdn') + assert.strictEqual(getAddress(root.derivePath("m/0'/1/1")), '1EAvj4edpsWcSer3duybAd4KiR4bCJW5J6') + }) +}) diff --git a/test/integration/blocks.js b/test/integration/blocks.js new file mode 100644 index 000000000..044862d4d --- /dev/null +++ b/test/integration/blocks.js @@ -0,0 +1,22 @@ +'use strict' + +const { describe, it } = require('mocha') +const assert = require('assert') +const bitcoin = require('../../') + +describe('bitcoinjs-lib (blocks)', () => { + it('can extract a height from a CoinBase transaction', () => { + // from 00000000000000000097669cdca131f24d40c4cc7d80eaa65967a2d09acf6ce6 + const txHex = '010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff50037f9a07174d696e656420627920416e74506f6f6c685b205a2b1f7bfabe6d6d36afe1910eca9405b66f97750940a656e38e2c0312958190ff8e98fd16761d220400000000000000aa340000d49f0000ffffffff02b07fc366000000001976a9148349212dc27ce3ab4c5b29b85c4dec643d764b1788ac0000000000000000266a24aa21a9ed72d9432948505e3d3062f1307a3f027a5dea846ff85e47159680919c12bf1e400120000000000000000000000000000000000000000000000000000000000000000000000000' + const tx = bitcoin.Transaction.fromHex(txHex) + + assert.strictEqual(tx.ins.length, 1) + const script = tx.ins[0].script + // bitcoin.script.decompile(script) // returns [] :( + + assert.strictEqual(script[0], 0x03) + const heightBuffer = script.slice(1, 4) + const height = bitcoin.script.number.decode(heightBuffer) + assert.strictEqual(height, 498303) + }) +}) diff --git a/test/integration/cltv.js b/test/integration/cltv.js new file mode 100644 index 000000000..e123652d1 --- /dev/null +++ b/test/integration/cltv.js @@ -0,0 +1,198 @@ +const { describe, it, before } = require('mocha') +const assert = require('assert') +const bitcoin = require('../../') +const regtestUtils = require('./_regtest') +const regtest = regtestUtils.network +const bip65 = require('bip65') + +const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest) +const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest) +console.warn = () => {} // Silence the Deprecation Warning + +describe('bitcoinjs-lib (transactions w/ CLTV)', () => { + // force update MTP + before(async () => { + await regtestUtils.mine(11) + }) + + const hashType = bitcoin.Transaction.SIGHASH_ALL + + function cltvCheckSigOutput (aQ, bQ, lockTime) { + return bitcoin.script.compile([ + bitcoin.opcodes.OP_IF, + bitcoin.script.number.encode(lockTime), + bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY, + bitcoin.opcodes.OP_DROP, + + bitcoin.opcodes.OP_ELSE, + bQ.publicKey, + bitcoin.opcodes.OP_CHECKSIGVERIFY, + bitcoin.opcodes.OP_ENDIF, + + aQ.publicKey, + bitcoin.opcodes.OP_CHECKSIG + ]) + } + + function utcNow () { + return Math.floor(Date.now() / 1000) + } + + // 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)', async () => { + // 3 hours ago + const lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) }) + const redeemScript = cltvCheckSigOutput(alice, bob, lockTime) + const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest }) + + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address, 1e5) + 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) + + // {Alice's signature} OP_TRUE + 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) + + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }) + }) + + // 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)', async () => { + const height = await regtestUtils.height() + // 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 }) + + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address, 1e5) + 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) + + // {Alice's signature} OP_TRUE + 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) + + // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently + // ... + // into the future! + await regtestUtils.mine(5) + await regtestUtils.broadcast(tx.toHex()) + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }) + }) + + // 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', async () => { + // two hours ago + const lockTime = bip65.encode({ utc: utcNow() - (3600 * 2) }) + const redeemScript = cltvCheckSigOutput(alice, bob, lockTime) + const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest }) + + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address, 2e5) + 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) + + // {Alice's signature} {Bob's signature} OP_FALSE + 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.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_FALSE + ]), + output: redeemScript + } + }).input + tx.setInputScript(0, redeemScriptSig) + + await regtestUtils.broadcast(tx.toHex()) + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 8e4 + }) + }) + + // 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', async () => { + // two hours from now + const lockTime = bip65.encode({ utc: utcNow() + (3600 * 2) }) + const redeemScript = cltvCheckSigOutput(alice, bob, lockTime) + const { address } = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network: regtest }, network: regtest }) + + // fund the P2SH(CLTV) address + const unspent = await regtestUtils.faucet(address, 2e4) + 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) + + // {Alice's signature} OP_TRUE + 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.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE + ]), + output: redeemScript + } + }).input + tx.setInputScript(0, redeemScriptSig) + + await regtestUtils.broadcast(tx.toHex()).catch(err => { + assert.throws(() => { + if (err) throw err + }, /Error: non-final \(code 64\)/) + }) + }) +}) diff --git a/test/integration/crypto.js b/test/integration/crypto.js deleted file mode 100644 index 631809235..000000000 --- a/test/integration/crypto.js +++ /dev/null @@ -1,188 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') -var async = require('async') -var bigi = require('bigi') -var bitcoin = require('../../') -var blockchain = new (require('cb-helloblock'))('bitcoin') -var crypto = require('crypto') - -describe('bitcoinjs-lib (crypto)', function () { - it('can generate a single-key stealth address', function () { - var receiver = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') - - // XXX: ephemeral, must be random (and secret to sender) to preserve privacy - var sender = bitcoin.ECKey.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct') - - var G = bitcoin.ECKey.curve.G - var d = receiver.d // secret (receiver only) - var Q = receiver.pub.Q // shared - - var e = sender.d // secret (sender only) - var P = sender.pub.Q // shared - - // derived shared secret - var eQ = Q.multiply(e) // sender - var dP = P.multiply(d) // receiver - assert.deepEqual(eQ.getEncoded(), dP.getEncoded()) - - var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded())) - var cG = G.multiply(c) - - // derived public key - var QprimeS = Q.add(cG) - var QprimeR = G.multiply(d.add(c)) - assert.deepEqual(QprimeR.getEncoded(), QprimeS.getEncoded()) - - // derived shared-secret address - var address = new bitcoin.ECPubKey(QprimeS).getAddress().toString() - - assert.equal(address, '1EwCNJNZM5q58YPPTnjR1H5BvYRNeyZi47') - }) - - // TODO - it.skip('can generate a dual-key stealth address', function () {}) - - it("can recover a parent private key from the parent's public key and a derived non-hardened child private key", function () { - function recoverParent (master, child) { - assert(!master.privKey, 'You already have the parent private key') - assert(child.privKey, 'Missing child private key') - - var curve = bitcoin.ECKey.curve - var QP = master.pubKey.toBuffer() - var QP64 = QP.toString('base64') - var d1 = child.privKey.d - var d2 - var indexBuffer = new Buffer(4) - - // search index space until we find it - for (var i = 0; i < bitcoin.HDNode.HIGHEST_BIT; ++i) { - indexBuffer.writeUInt32BE(i, 0) - - // calculate I - var data = Buffer.concat([QP, indexBuffer]) - var I = crypto.createHmac('sha512', master.chainCode).update(data).digest() - var IL = I.slice(0, 32) - var pIL = bigi.fromBuffer(IL) - - // See hdnode.js:273 to understand - d2 = d1.subtract(pIL).mod(curve.n) - - var Qp = new bitcoin.ECKey(d2, true).pub.toBuffer() - if (Qp.toString('base64') === QP64) break - } - - var node = new bitcoin.HDNode(d2, master.chainCode, master.network) - node.depth = master.depth - node.index = master.index - node.masterFingerprint = master.masterFingerprint - return node - } - - var seed = crypto.randomBytes(32) - var master = bitcoin.HDNode.fromSeedBuffer(seed) - var child = master.derive(6) // m/6 - - // now for the recovery - var neuteredMaster = master.neutered() - var recovered = recoverParent(neuteredMaster, child) - assert.equal(recovered.toBase58(), master.toBase58()) - }) - - it('can recover a private key from duplicate R values', function () { - var inputs = [ - { - txId: 'f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50', - vout: 0 - }, - { - txId: 'f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50', - vout: 1 - } - ] - - var txIds = inputs.map(function (x) { - return x.txId - }) - - // first retrieve the relevant transactions - blockchain.transactions.get(txIds, function (err, results) { - assert.ifError(err) - - var transactions = {} - results.forEach(function (tx) { - transactions[tx.txId] = bitcoin.Transaction.fromHex(tx.txHex) - }) - - var tasks = [] - - // now we need to collect/transform a bit of data from the selected inputs - inputs.forEach(function (input) { - var transaction = transactions[input.txId] - var script = transaction.ins[input.vout].script - assert(bitcoin.scripts.isPubKeyHashInput(script), 'Expected pubKeyHash script') - - var prevOutTxId = bitcoin.bufferutils.reverse(transaction.ins[input.vout].hash).toString('hex') - var prevVout = transaction.ins[input.vout].index - - tasks.push(function (callback) { - blockchain.transactions.get(prevOutTxId, function (err, result) { - if (err) return callback(err) - - var prevOut = bitcoin.Transaction.fromHex(result.txHex) - var prevOutScript = prevOut.outs[prevVout].script - - var scriptSignature = bitcoin.ECSignature.parseScriptSignature(script.chunks[0]) - var publicKey = bitcoin.ECPubKey.fromBuffer(script.chunks[1]) - - var m = transaction.hashForSignature(input.vout, prevOutScript, scriptSignature.hashType) - assert(publicKey.verify(m, scriptSignature.signature), 'Invalid m') - - // store the required information - input.signature = scriptSignature.signature - input.z = bigi.fromBuffer(m) - - return callback() - }) - }) - }) - - // finally, run the tasks, then on to the math - async.parallel(tasks, function (err) { - if (err) - throw err - var n = bitcoin.ECKey.curve.n - - for (var i = 0; i < inputs.length; ++i) { - for (var j = i + 1; j < inputs.length; ++j) { - var inputA = inputs[i] - var inputB = inputs[j] - - // enforce matching r values - assert.equal(inputA.signature.r.toString(), inputB.signature.r.toString()) - var r = inputA.signature.r - var rInv = r.modInverse(n) - - var s1 = inputA.signature.s - var s2 = inputB.signature.s - var z1 = inputA.z - var z2 = inputB.z - - var zz = z1.subtract(z2).mod(n) - var ss = s1.subtract(s2).mod(n) - - // k = (z1 - z2) / (s1 - s2) - // d1 = (s1 * k - z1) / r - // d2 = (s2 * k - z2) / r - var k = zz.multiply(ss.modInverse(n)).mod(n) - var d1 = ((s1.multiply(k).mod(n)).subtract(z1).mod(n)).multiply(rInv).mod(n) - var d2 = ((s2.multiply(k).mod(n)).subtract(z2).mod(n)).multiply(rInv).mod(n) - - // enforce matching private keys - assert.equal(d1.toString(), d2.toString()) - } - } - }) - }) - }) -}) diff --git a/test/integration/csv.js b/test/integration/csv.js new file mode 100644 index 000000000..2e133f06e --- /dev/null +++ b/test/integration/csv.js @@ -0,0 +1,318 @@ +const { describe, it, before } = require('mocha') +const assert = require('assert') +const bitcoin = require('../../') +const regtestUtils = require('./_regtest') +const regtest = regtestUtils.network +const bip68 = require('bip68') + +const alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest) +const bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest) +const charles = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf', regtest) +const dave = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest) +console.warn = () => {} // Silence the Deprecation Warning + +describe('bitcoinjs-lib (transactions w/ CSV)', () => { + // force update MTP + before(async () => { + await regtestUtils.mine(11) + }) + + const hashType = bitcoin.Transaction.SIGHASH_ALL + + // IF MTP (from when confirmed) > seconds, _alice can redeem + function csvCheckSigOutput (_alice, _bob, sequence) { + return bitcoin.script.fromASM(` + OP_IF + ${bitcoin.script.number.encode(sequence).toString('hex')} + OP_CHECKSEQUENCEVERIFY + OP_DROP + OP_ELSE + ${_bob.publicKey.toString('hex')} + OP_CHECKSIGVERIFY + OP_ENDIF + ${_alice.publicKey.toString('hex')} + OP_CHECKSIG + `.trim().replace(/\s+/g, ' ')) + } + + // 2 of 3 multisig of _bob, _charles, _dave, + // but after sequence1 time, _alice can allow the multisig to become 1 of 3. + // but after sequence2 time, _alice can sign for the output all by themself. + // Ref: https://github.com/bitcoinbook/bitcoinbook/blob/f8b883dcd4e3d1b9adf40fed59b7e898fbd9241f/ch07.asciidoc#complex-script-example + // Note: bitcoinjs-lib will not offer specific support for problems with + // advanced script usages such as below. Use at your own risk. + function complexCsvOutput (_alice, _bob, _charles, _dave, sequence1, sequence2) { + return bitcoin.script.fromASM(` + OP_IF + OP_IF + OP_2 + OP_ELSE + ${bitcoin.script.number.encode(sequence1).toString('hex')} + OP_CHECKSEQUENCEVERIFY + OP_DROP + ${_alice.publicKey.toString('hex')} + OP_CHECKSIGVERIFY + OP_1 + OP_ENDIF + ${_bob.publicKey.toString('hex')} + ${_charles.publicKey.toString('hex')} + ${_dave.publicKey.toString('hex')} + OP_3 + OP_CHECKMULTISIG + OP_ELSE + ${bitcoin.script.number.encode(sequence2).toString('hex')} + OP_CHECKSEQUENCEVERIFY + OP_DROP + ${_alice.publicKey.toString('hex')} + OP_CHECKSIG + OP_ENDIF + `.trim().replace(/\s+/g, ' ')) + } + + // 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)', async () => { + // 5 blocks from now + const sequence = bip68.encode({ blocks: 5 }) + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: csvCheckSigOutput(alice, bob, sequence) + }, + network: regtest + }) + + // fund the P2SH(CSV) address + const unspent = await regtestUtils.faucet(p2sh.address, 1e5) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, sequence) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) + + // {Alice's signature} OP_TRUE + const tx = txb.buildIncomplete() + 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) + + // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently + // ... + // into the future! + await regtestUtils.mine(10) + + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }) + }) + + // 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)', async () => { + // two hours after confirmation + const sequence = bip68.encode({ seconds: 7168 }) + const p2sh = bitcoin.payments.p2sh({ + network: regtest, + redeem: { + output: csvCheckSigOutput(alice, bob, sequence) + } + }) + + // fund the P2SH(CSV) address + const unspent = await regtestUtils.faucet(p2sh.address, 2e4) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, sequence) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4) + + // {Alice's signature} OP_TRUE + const tx = txb.buildIncomplete() + 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.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE + ]) + } + }).input + tx.setInputScript(0, redeemScriptSig) + + await regtestUtils.broadcast(tx.toHex()).catch(err => { + assert.throws(() => { + if (err) throw err + }, /Error: non-BIP68-final \(code 64\)/) + }) + }) + + // 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)', async () => { + const height = await regtestUtils.height() + + // 2 blocks from now + const sequence1 = bip68.encode({ blocks: 2 }) + // 5 blocks from now + const sequence2 = bip68.encode({ blocks: 5 }) + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2) + }, + network: regtest + }) + + // fund the P2SH(CCSV) address + const unspent = await regtestUtils.faucet(p2sh.address, 1e5) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) + + // OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE + const tx = txb.buildIncomplete() + 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.opcodes.OP_0, + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.script.signature.encode(charles.sign(signatureHash), hashType), + bitcoin.opcodes.OP_TRUE, + bitcoin.opcodes.OP_TRUE + ]) + } + }).input + tx.setInputScript(0, redeemScriptSig) + + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }) + }) + + // 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)', async () => { + const height = await regtestUtils.height() + + // 2 blocks from now + const sequence1 = bip68.encode({ blocks: 2 }) + // 5 blocks from now + const sequence2 = bip68.encode({ blocks: 5 }) + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2) + }, + network: regtest + }) + + // fund the P2SH(CCSV) address + const unspent = await regtestUtils.faucet(p2sh.address, 1e5) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, sequence1) // Set sequence1 for input + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) + + // OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE + const tx = txb.buildIncomplete() + 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.opcodes.OP_0, + bitcoin.script.signature.encode(bob.sign(signatureHash), hashType), + bitcoin.script.signature.encode(alice.sign(signatureHash), hashType), + bitcoin.opcodes.OP_0, + bitcoin.opcodes.OP_TRUE + ]) + } + }).input + tx.setInputScript(0, redeemScriptSig) + + // Wait 2 blocks + await regtestUtils.mine(2) + + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }) + }) + + // 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)', async () => { + const height = await regtestUtils.height() + + // 2 blocks from now + const sequence1 = bip68.encode({ blocks: 2 }) + // 5 blocks from now + const sequence2 = bip68.encode({ blocks: 5 }) + const p2sh = bitcoin.payments.p2sh({ + redeem: { + output: complexCsvOutput(alice, bob, charles, dave, sequence1, sequence2) + }, + network: regtest + }) + + // fund the P2SH(CCSV) address + const unspent = await regtestUtils.faucet(p2sh.address, 1e5) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, sequence2) // Set sequence2 for input + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) + + // {Alice mediator sig} OP_FALSE + const tx = txb.buildIncomplete() + 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_0 + ]) + } + }).input + tx.setInputScript(0, redeemScriptSig) + + // Wait 5 blocks + await regtestUtils.mine(5) + + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }) + }) +}) diff --git a/test/integration/multisig.js b/test/integration/multisig.js deleted file mode 100644 index fabaad998..000000000 --- a/test/integration/multisig.js +++ /dev/null @@ -1,80 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') -var bitcoin = require('../../') -var blockchain = new (require('cb-helloblock'))('testnet') - -describe('bitcoinjs-lib (multisig)', function () { - it('can create a 2-of-3 multisig P2SH address', function () { - var pubKeys = [ - '026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01', - '02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9', - '03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9' - ].map(bitcoin.ECPubKey.fromHex) - - var redeemScript = bitcoin.scripts.multisigOutput(2, pubKeys) // 2 of 3 - var scriptPubKey = bitcoin.scripts.scriptHashOutput(redeemScript.getHash()) - var address = bitcoin.Address.fromOutputScript(scriptPubKey).toString() - - assert.equal(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7') - }) - - it('can spend from a 2-of-2 multsig P2SH address', function (done) { - this.timeout(20000) - - var privKeys = [ - '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx', - '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT' - ].map(bitcoin.ECKey.fromWIF) - var pubKeys = privKeys.map(function (x) { - return x.pub - }) - - var redeemScript = bitcoin.scripts.multisigOutput(2, pubKeys) // 2 of 2 - var scriptPubKey = bitcoin.scripts.scriptHashOutput(redeemScript.getHash()) - var address = bitcoin.Address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet).toString() - - // Attempt to send funds to the source address - blockchain.addresses.__faucetWithdraw(address, 2e4, function (err) { - if (err) return done(err) - - // get latest unspents from the address - blockchain.addresses.unspents(address, function (err, unspents) { - if (err) return done(err) - - // filter small unspents - unspents = unspents.filter(function (unspent) { - return unspent.value > 1e4 - }) - - // use the oldest unspent - var unspent = unspents.pop() - - // make a random destination address - var targetAddress = bitcoin.ECKey.makeRandom().pub.getAddress(bitcoin.networks.testnet).toString() - - var txb = new bitcoin.TransactionBuilder() - txb.addInput(unspent.txId, unspent.vout) - txb.addOutput(targetAddress, 1e4) - - // sign w/ each private key - privKeys.forEach(function (privKey) { - txb.sign(0, privKey, redeemScript) - }) - - // broadcast our transaction - blockchain.transactions.propagate(txb.build().toHex(), function (err) { - if (err) return done(err) - - // check that the funds (1e4 Satoshis) indeed arrived at the intended address - blockchain.addresses.summary(targetAddress, function (err, result) { - if (err) return done(err) - - assert.equal(result.balance, 1e4) - done() - }) - }) - }) - }) - }) -}) diff --git a/test/integration/payments.js b/test/integration/payments.js new file mode 100644 index 000000000..905eda838 --- /dev/null +++ b/test/integration/payments.js @@ -0,0 +1,89 @@ +const bitcoin = require('../../') + +const { describe, it } = require('mocha') +const regtestUtils = require('./_regtest') +const NETWORK = regtestUtils.network +const keyPairs = [ + bitcoin.ECPair.makeRandom({ network: NETWORK }), + bitcoin.ECPair.makeRandom({ network: NETWORK }) +] +console.warn = () => {} // Silence the Deprecation Warning + +async function buildAndSign (depends, prevOutput, redeemScript, witnessScript) { + const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4) + + const txb = new bitcoin.TransactionBuilder(NETWORK) + txb.addInput(unspent.txId, unspent.vout, null, prevOutput) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + + const posType = depends.prevOutScriptType + const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh' + + if (depends.signatures) { + keyPairs.forEach(keyPair => { + txb.sign({ + prevOutScriptType: posType, + vin: 0, + keyPair, + redeemScript, + witnessValue: needsValue ? unspent.value : undefined, + witnessScript, + }) + }) + } else if (depends.signature) { + txb.sign({ + prevOutScriptType: posType, + vin: 0, + keyPair: keyPairs[0], + redeemScript, + witnessValue: needsValue ? unspent.value : undefined, + witnessScript, + }) + } + + return regtestUtils.broadcast(txb.build().toHex()) +} + +;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => { + const fixtures = require('../fixtures/' + k) + const { depends } = fixtures.dynamic + const fn = bitcoin.payments[k] + + const base = {} + if (depends.pubkey) base.pubkey = keyPairs[0].publicKey + if (depends.pubkeys) base.pubkeys = keyPairs.map(x => x.publicKey) + if (depends.m) base.m = base.pubkeys.length + + const { output } = fn(base) + if (!output) throw new TypeError('Missing output') + + describe('bitcoinjs-lib (payments - ' + k + ')', () => { + it('can broadcast as an output, and be spent as an input', async () => { + Object.assign(depends, { prevOutScriptType: k }) + await buildAndSign(depends, output, undefined, undefined) + }) + + 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 }) + Object.assign(depends, { prevOutScriptType: 'p2sh-' + k }) + await buildAndSign(depends, p2sh.output, p2sh.redeem.output, undefined) + }) + + // NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail + if (k === 'p2wpkh') return + + 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 }) + Object.assign(depends, { prevOutScriptType: 'p2wsh-' + k }) + await buildAndSign(depends, p2wsh.output, undefined, p2wsh.redeem.output) + }) + + 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 p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK }) + + Object.assign(depends, { prevOutScriptType: 'p2sh-p2wsh-' + k }) + await buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output) + }) + }) +}) diff --git a/test/integration/transactions-psbt.js b/test/integration/transactions-psbt.js new file mode 100644 index 000000000..7b9d9c56b --- /dev/null +++ b/test/integration/transactions-psbt.js @@ -0,0 +1,550 @@ +const { describe, it } = require('mocha'); +const assert = require('assert'); +const bitcoin = require('../../'); +const bip32 = require('bip32'); +const rng = require('randombytes'); +const regtestUtils = require('./_regtest'); +const regtest = regtestUtils.network; + +// See bottom of file for some helper functions used to make the payment objects needed. + +describe('bitcoinjs-lib (transactions with psbt)', () => { + it('can create a 1-to-1 Transaction', () => { + const alice = bitcoin.ECPair.fromWIF( + 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr', + ); + const psbt = new bitcoin.Psbt(); + psbt.setVersion(2); // These are defaults. This line is not needed. + psbt.setLocktime(0); // These are defaults. This line is not needed. + psbt.addInput({ + // if hash is string, txid, if hash is Buffer, is reversed compared to txid + hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e', + index: 0, + sequence: 0xffffffff, // These are defaults. This line is not needed. + + // non-segwit inputs now require passing the whole previous tx as Buffer + nonWitnessUtxo: Buffer.from( + '0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' + + '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' + + 'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' + + '9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' + + '631e5e1e66009ce3710ceea5b1ad13ffffffff01' + + // value in satoshis (Int64LE) = 0x015f90 = 90000 + '905f010000000000' + + // scriptPubkey length + '19' + + // scriptPubkey + '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' + + // locktime + '00000000', + 'hex', + ), + + // // If this input was segwit, instead of nonWitnessUtxo, you would add + // // a witnessUtxo as follows. The scriptPubkey and the value only are needed. + // witnessUtxo: { + // script: Buffer.from( + // '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac', + // 'hex', + // ), + // value: 90000, + // }, + + // Not featured here: + // redeemScript. A Buffer of the redeemScript for P2SH + // witnessScript. A Buffer of the witnessScript for P2WSH + }); + psbt.addOutput({ + address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp', + value: 80000, + }); + psbt.signInput(0, alice); + psbt.validateSignaturesOfInput(0); + psbt.finalizeAllInputs(); + assert.strictEqual( + psbt.extractTransaction().toHex(), + '02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' + + 'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' + + 'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' + + '9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' + + 'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' + + '08a22724efa6f6a07b0ec4c79aa88ac00000000', + ); + }); + + it('can create (and broadcast via 3PBP) a typical Transaction', async () => { + // these are { payment: Payment; keys: ECPair[] } + const alice1 = createPayment('p2pkh'); + const alice2 = createPayment('p2pkh'); + + // give Alice 2 unspent outputs + const inputData1 = await getInputData( + 5e4, + alice1.payment, + false, + 'noredeem', + ); + const inputData2 = await getInputData( + 7e4, + alice2.payment, + false, + 'noredeem', + ); + { + const { + hash, // string of txid or Buffer of tx hash. (txid and hash are reverse order) + index, // the output index of the txo you are spending + nonWitnessUtxo, // the full previous transaction as a Buffer + } = inputData1; + assert.deepStrictEqual({ hash, index, nonWitnessUtxo }, inputData1); + } + + // network is only needed if you pass an address to addOutput + // using script (Buffer of scriptPubkey) instead will avoid needed network. + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData1) // alice1 unspent + .addInput(inputData2) // alice2 unspent + .addOutput({ + address: 'mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', + value: 8e4, + }) // the actual "spend" + .addOutput({ + address: alice2.payment.address, // OR script, which is a Buffer. + value: 1e4, + }); // Alice's change + // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee + + // Let's show a new feature with PSBT. + // We can have multiple signers sign in parrallel and combine them. + // (this is not necessary, but a nice feature) + + // encode to send out to the signers + const psbtBaseText = psbt.toBase64(); + + // each signer imports + const signer1 = bitcoin.Psbt.fromBase64(psbtBaseText); + const signer2 = bitcoin.Psbt.fromBase64(psbtBaseText); + + // Alice signs each input with the respective private keys + // signInput and signInputAsync are better + // (They take the input index explicitly as the first arg) + signer1.signAllInputs(alice1.keys[0]); + signer2.signAllInputs(alice2.keys[0]); + + // If your signer object's sign method returns a promise, use the following + // await signer2.signAllInputsAsync(alice2.keys[0]) + + // encode to send back to combiner (signer 1 and 2 are not near each other) + const s1text = signer1.toBase64(); + const s2text = signer2.toBase64(); + + const final1 = bitcoin.Psbt.fromBase64(s1text); + const final2 = bitcoin.Psbt.fromBase64(s2text); + + // final1.combine(final2) would give the exact same result + psbt.combine(final1, final2); + + // Finalizer wants to check all signatures are valid before finalizing. + // If the finalizer wants to check for specific pubkeys, the second arg + // can be passed. See the first multisig example below. + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(1), true); + + // This step it new. Since we separate the signing operation and + // the creation of the scriptSig and witness stack, we are able to + psbt.finalizeAllInputs(); + + // build and broadcast our RegTest network + await regtestUtils.broadcast(psbt.extractTransaction().toHex()); + // 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', async () => { + const alice1 = createPayment('p2pkh'); + const inputData1 = await getInputData( + 2e5, + alice1.payment, + false, + 'noredeem', + ); + + const data = Buffer.from('bitcoinjs-lib', 'utf8'); + const embed = bitcoin.payments.embed({ data: [data] }); + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData1) + .addOutput({ + script: embed.output, + value: 1000, + }) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 1e5, + }) + .signInput(0, alice1.keys[0]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + psbt.finalizeAllInputs(); + + // build and broadcast to the RegTest network + await regtestUtils.broadcast(psbt.extractTransaction().toHex()); + }); + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => { + const multisig = createPayment('p2sh-p2ms(2 of 4)'); + const inputData1 = await getInputData(2e4, multisig.payment, false, 'p2sh'); + { + const { + hash, + index, + nonWitnessUtxo, + redeemScript, // NEW: P2SH needs to give redeemScript when adding an input. + } = inputData1; + assert.deepStrictEqual( + { hash, index, nonWitnessUtxo, redeemScript }, + inputData1, + ); + } + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData1) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 1e4, + }) + .signInput(0, multisig.keys[0]) + .signInput(0, multisig.keys[2]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual( + psbt.validateSignaturesOfInput(0, multisig.keys[0].publicKey), + true, + ); + assert.throws(() => { + psbt.validateSignaturesOfInput(0, multisig.keys[3].publicKey); + }, new RegExp('No signatures for this pubkey')); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 1e4, + }); + }); + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => { + const p2sh = createPayment('p2sh-p2wpkh'); + const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh'); + const inputData2 = await getInputData(5e4, p2sh.payment, true, 'p2sh'); + { + const { + hash, + index, + witnessUtxo, // NEW: this is an object of the output being spent { script: Buffer; value: Satoshis; } + redeemScript, + } = inputData; + assert.deepStrictEqual( + { hash, index, witnessUtxo, redeemScript }, + inputData, + ); + } + const keyPair = p2sh.keys[0]; + const outputData = { + script: p2sh.payment.output, // sending to myself for fun + value: 2e4, + }; + const outputData2 = { + script: p2sh.payment.output, // sending to myself for fun + value: 7e4, + }; + + const tx = new bitcoin.Psbt() + .addInputs([inputData, inputData2]) + .addOutputs([outputData, outputData2]) + .signAllInputs(keyPair) + .finalizeAllInputs() + .extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: p2sh.payment.address, + vout: 0, + value: 2e4, + }); + }); + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => { + // the only thing that changes is you don't give a redeemscript for input data + + const p2wpkh = createPayment('p2wpkh'); + const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem'); + { + const { hash, index, witnessUtxo } = inputData; + assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData); + } + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2wpkh.keys[0]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, + }); + }); + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => { + const p2wsh = createPayment('p2wsh-p2pk'); + const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh'); + { + const { + hash, + index, + witnessUtxo, + witnessScript, // NEW: A Buffer of the witnessScript + } = inputData; + assert.deepStrictEqual( + { hash, index, witnessUtxo, witnessScript }, + inputData, + ); + } + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2wsh.keys[0]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, + }); + }); + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => { + const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)'); + const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh'); + { + const { + hash, + index, + witnessUtxo, + redeemScript, + witnessScript, + } = inputData; + assert.deepStrictEqual( + { hash, index, witnessUtxo, redeemScript, witnessScript }, + inputData, + ); + } + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2sh.keys[0]) + .signInput(0, p2sh.keys[2]) + .signInput(0, p2sh.keys[3]); + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual( + psbt.validateSignaturesOfInput(0, p2sh.keys[3].publicKey), + true, + ); + assert.throws(() => { + psbt.validateSignaturesOfInput(0, p2sh.keys[1].publicKey); + }, new RegExp('No signatures for this pubkey')); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, + }); + }); + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => { + const hdRoot = bip32.fromSeed(rng(64)); + const masterFingerprint = hdRoot.fingerprint; + const path = "m/84'/0'/0'/0/0"; + const childNode = hdRoot.derivePath(path); + const pubkey = childNode.publicKey; + + // This information should be added to your input via updateInput + // You can add multiple bip32Derivation objects for multisig, but + // each must have a unique pubkey. + // + // This is useful because as long as you store the masterFingerprint on + // the PSBT Creator's server, you can have the PSBT Creator do the heavy + // lifting with derivation from your m/84'/0'/0' xpub, (deriving only 0/0 ) + // and your signer just needs to pass in an HDSigner interface (ie. bip32 library) + const updateData = { + bip32Derivation: [ + { + masterFingerprint, + path, + pubkey, + } + ] + } + const p2wpkh = createPayment('p2wpkh', [childNode]); + const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem'); + { + const { hash, index, witnessUtxo } = inputData; + assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData); + } + + // You can add extra attributes for updateData into the addInput(s) object(s) + Object.assign(inputData, updateData) + + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + // .updateInput(0, updateData) // if you didn't merge the bip32Derivation with inputData + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInputHD(0, hdRoot); // must sign with root!!! + + assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, childNode.publicKey), true); + psbt.finalizeAllInputs(); + + const tx = psbt.extractTransaction(); + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()); + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, + }); + }); +}); + +function createPayment(_type, myKeys, network) { + network = network || regtest; + const splitType = _type.split('-').reverse(); + const isMultisig = splitType[0].slice(0, 4) === 'p2ms'; + const keys = myKeys || []; + let m; + if (isMultisig) { + const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/); + m = parseInt(match[1]); + let n = parseInt(match[2]); + if (keys.length > 0 && keys.length !== n) { + throw new Error('Need n keys for multisig') + } + while (!myKeys && n > 1) { + keys.push(bitcoin.ECPair.makeRandom({ network })); + n--; + } + } + if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network })); + + let payment; + splitType.forEach(type => { + if (type.slice(0, 4) === 'p2ms') { + payment = bitcoin.payments.p2ms({ + m, + pubkeys: keys.map(key => key.publicKey).sort(), + network, + }); + } else if (['p2sh', 'p2wsh'].indexOf(type) > -1) { + payment = bitcoin.payments[type]({ + redeem: payment, + network, + }); + } else { + payment = bitcoin.payments[type]({ + pubkey: keys[0].publicKey, + network, + }); + } + }); + + return { + payment, + keys, + }; +} + +function getWitnessUtxo(out) { + delete out.address; + out.script = Buffer.from(out.script, 'hex'); + return out; +} + +async function getInputData(amount, payment, isSegwit, redeemType) { + const unspent = await regtestUtils.faucetComplex(payment.output, amount); + const utx = await regtestUtils.fetch(unspent.txId); + // for non segwit inputs, you must pass the full transaction buffer + const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex'); + // for segwit inputs, you only need the output script and value as an object. + const witnessUtxo = getWitnessUtxo(utx.outs[unspent.vout]); + const mixin = isSegwit ? { witnessUtxo } : { nonWitnessUtxo }; + const mixin2 = {}; + switch (redeemType) { + case 'p2sh': + mixin2.redeemScript = payment.redeem.output; + break; + case 'p2wsh': + mixin2.witnessScript = payment.redeem.output; + break; + case 'p2sh-p2wsh': + mixin2.witnessScript = payment.redeem.redeem.output; + mixin2.redeemScript = payment.redeem.output; + break; + } + return { + hash: unspent.txId, + index: unspent.vout, + ...mixin, + ...mixin2, + }; +} diff --git a/test/integration/transactions.js b/test/integration/transactions.js new file mode 100644 index 000000000..173fc4a13 --- /dev/null +++ b/test/integration/transactions.js @@ -0,0 +1,361 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const bitcoin = require('../../') +const regtestUtils = require('./_regtest') +const regtest = regtestUtils.network +console.warn = () => {} // Silence the Deprecation Warning + +function rng () { + return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64') +} + +describe('bitcoinjs-lib (transactions)', () => { + it('can create a 1-to-1 Transaction', () => { + const alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy') + const txb = new bitcoin.TransactionBuilder() + + txb.setVersion(1) + txb.addInput('61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', 0) // Alice's previous transaction output, has 15000 satoshis + txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000) + // (in)15000 - (out)12000 = (fee)3000, this is the miner fee + + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: alice + }) + + // prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below + assert.strictEqual(txb.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000') + }) + + it('can create a 2-to-2 Transaction', () => { + const alice = bitcoin.ECPair.fromWIF('L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1') + const bob = bitcoin.ECPair.fromWIF('KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z') + + const txb = new bitcoin.TransactionBuilder() + txb.setVersion(1) + txb.addInput('b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c', 6) // Alice's previous transaction output, has 200000 satoshis + txb.addInput('7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730', 0) // Bob's previous transaction output, has 300000 satoshis + txb.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000) + txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000) + // (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee + + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 1, + keyPair: bob + }) // Bob signs his input, which was the second input (1th) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: alice + }) // Alice signs her input, which was the first input (0th) + + // prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below + assert.strictEqual(txb.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000') + }) + + it('can create (and broadcast via 3PBP) a typical Transaction', async () => { + const alice1 = bitcoin.ECPair.makeRandom({ network: regtest }) + const alice2 = bitcoin.ECPair.makeRandom({ network: regtest }) + const aliceChange = bitcoin.ECPair.makeRandom({ network: regtest, rng: rng }) + + const alice1pkh = bitcoin.payments.p2pkh({ pubkey: alice1.publicKey, network: regtest }) + const alice2pkh = bitcoin.payments.p2pkh({ pubkey: alice2.publicKey, network: regtest }) + const aliceCpkh = bitcoin.payments.p2pkh({ pubkey: aliceChange.publicKey, network: regtest }) + + // give Alice 2 unspent outputs + const unspent0 = await regtestUtils.faucet(alice1pkh.address, 5e4) + + const unspent1 = await regtestUtils.faucet(alice2pkh.address, 7e4) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent0.txId, unspent0.vout) // alice1 unspent + txb.addInput(unspent1.txId, unspent1.vout) // alice2 unspent + txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4) // the actual "spend" + txb.addOutput(aliceCpkh.address, 1e4) // Alice's change + // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee + + // Alice signs each input with the respective private keys + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: alice1 + }) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 1, + keyPair: alice2 + }) + + // build and broadcast our RegTest network + await regtestUtils.broadcast(txb.build().toHex()) + // 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', async () => { + const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }) + const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: regtest }) + + const unspent = await regtestUtils.faucet(p2pkh.address, 2e5) + + const txb = new bitcoin.TransactionBuilder(regtest) + const data = Buffer.from('bitcoinjs-lib', 'utf8') + const embed = bitcoin.payments.embed({ data: [data] }) + txb.addInput(unspent.txId, unspent.vout) + txb.addOutput(embed.output, 1000) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + + // build and broadcast to the RegTest network + await regtestUtils.broadcast(txb.build().toHex()) + }) + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => { + const keyPairs = [ + bitcoin.ECPair.makeRandom({ network: regtest }), + bitcoin.ECPair.makeRandom({ network: regtest }), + bitcoin.ECPair.makeRandom({ network: regtest }), + bitcoin.ECPair.makeRandom({ network: regtest }) + ] + const pubkeys = keyPairs.map(x => x.publicKey) + const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network: regtest }) + const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest }) + + const unspent = await regtestUtils.faucet(p2sh.address, 2e4) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4) + + txb.sign({ + prevOutScriptType: 'p2sh-p2ms', + vin: 0, + keyPair: keyPairs[0], + redeemScript: p2sh.redeem.output, + }) + txb.sign({ + prevOutScriptType: 'p2sh-p2ms', + vin: 0, + keyPair: keyPairs[2], + redeemScript: p2sh.redeem.output, + }) + const tx = txb.build() + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 1e4 + }) + }) + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => { + const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }) + const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest }) + const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest }) + + const unspent = await regtestUtils.faucet(p2sh.address, 5e4) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + txb.sign({ + prevOutScriptType: 'p2sh-p2wpkh', + vin: 0, + keyPair: keyPair, + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + }) + + const tx = txb.build() + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4 + }) + }) + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => { + const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }) + const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest }) + + const unspent = await regtestUtils.faucetComplex(p2wpkh.output, 5e4) + + // XXX: build the Transaction w/ a P2WPKH input + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript! + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + txb.sign({ + prevOutScriptType: 'p2wpkh', + vin: 0, + keyPair: keyPair, + witnessValue: unspent.value, + }) // NOTE: no redeem script + const tx = txb.build() + + // build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4 + }) + }) + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => { + const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }) + const p2pk = bitcoin.payments.p2pk({ pubkey: keyPair.publicKey, network: regtest }) + const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest }) + + const unspent = await regtestUtils.faucetComplex(p2wsh.output, 5e4) + + // XXX: build the Transaction w/ a P2WSH input + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript! + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + txb.sign({ + prevOutScriptType: 'p2wsh-p2pk', + vin: 0, + keyPair: keyPair, + witnessValue: 5e4, + witnessScript: p2wsh.redeem.output, + }) // NOTE: provide a witnessScript! + const tx = txb.build() + + // build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4 + }) + }) + + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => { + const keyPairs = [ + bitcoin.ECPair.makeRandom({ network: regtest }), + bitcoin.ECPair.makeRandom({ network: regtest }), + bitcoin.ECPair.makeRandom({ network: regtest }), + bitcoin.ECPair.makeRandom({ network: regtest }) + ] + const pubkeys = keyPairs.map(x => x.publicKey) + + const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest }) + const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest }) + const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest }) + + const unspent = await regtestUtils.faucet(p2sh.address, 6e4) + + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, null, p2sh.output) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair: keyPairs[0], + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + witnessScript: p2wsh.redeem.output, + }) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair: keyPairs[2], + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + witnessScript: p2wsh.redeem.output, + }) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair: keyPairs[3], + redeemScript: p2sh.redeem.output, + witnessValue: unspent.value, + witnessScript: p2wsh.redeem.output, + }) + + const tx = txb.build() + + // build and broadcast to the Bitcoin RegTest network + await regtestUtils.broadcast(tx.toHex()) + + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 3e4 + }) + }) + + it('can verify Transaction (P2PKH) signatures', () => { + const txHex = '010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700' + const keyPairs = [ + '032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d', + '0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a', + '039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f' + ].map(q => { return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')) }) + + const tx = bitcoin.Transaction.fromHex(txHex) + + tx.ins.forEach((input, i) => { + const keyPair = keyPairs[i] + const p2pkh = bitcoin.payments.p2pkh({ + pubkey: keyPair.publicKey, + input: input.script + }) + + const ss = bitcoin.script.signature.decode(p2pkh.signature) + const hash = tx.hashForSignature(i, p2pkh.output, ss.hashType) + + 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) + }) + }) +}) diff --git a/test/message.js b/test/message.js deleted file mode 100644 index e982a4533..000000000 --- a/test/message.js +++ /dev/null @@ -1,71 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') -var message = require('../src/message') -var networks = require('../src/networks') - -var Address = require('../src/address') -var BigInteger = require('bigi') -var ECKey = require('../src/eckey') - -var fixtures = require('./fixtures/message.json') - -describe('message', function () { - describe('magicHash', function () { - fixtures.valid.magicHash.forEach(function (f) { - it('produces the correct magicHash for "' + f.message + '" (' + f.network + ')', function () { - var network = networks[f.network] - var actual = message.magicHash(f.message, network) - - assert.equal(actual.toString('hex'), f.magicHash) - }) - }) - }) - - describe('verify', function () { - it('accepts an Address object', function () { - var f = fixtures.valid.verify[0] - var network = networks[f.network] - - var address = Address.fromBase58Check(f.address) - assert(message.verify(address, f.signature, f.message, network)) - }) - - fixtures.valid.verify.forEach(function (f) { - it('verifies a valid signature for "' + f.message + '" (' + f.network + ')', function () { - var network = networks[f.network] - - assert(message.verify(f.address, f.signature, f.message, network)) - - if (f.compressed) { - assert(message.verify(f.compressed.address, f.compressed.signature, f.message, network)) - } - }) - }) - - fixtures.invalid.verify.forEach(function (f) { - it(f.description, function () { - assert(!message.verify(f.address, f.signature, f.message)) - }) - }) - }) - - describe('signing', function () { - fixtures.valid.signing.forEach(function (f) { - it(f.description, function () { - var network = networks[f.network] - - var privKey = new ECKey(new BigInteger(f.d), false) - var signature = message.sign(privKey, f.message, network) - assert.equal(signature.toString('base64'), f.signature) - - if (f.compressed) { - var compressedPrivKey = new ECKey(new BigInteger(f.d)) - var compressedSignature = message.sign(compressedPrivKey, f.message) - - assert.equal(compressedSignature.toString('base64'), f.compressed.signature) - } - }) - }) - }) -}) diff --git a/test/network.js b/test/network.js deleted file mode 100644 index aef09e0e2..000000000 --- a/test/network.js +++ /dev/null @@ -1,54 +0,0 @@ -/* global describe, it, before, after */ - -var assert = require('assert') -var networks = require('../src/networks') -var sinon = require('sinon') - -var HDNode = require('../src/hdnode') -var Transaction = require('../src/transaction') - -var fixtures = require('./fixtures/network') - -describe('networks', function () { - var txToBuffer - before(function () { - txToBuffer = sinon.stub(Transaction.prototype, 'toBuffer') - }) - - after(function () { - Transaction.prototype.toBuffer.restore() - }) - - describe('constants', function () { - fixtures.valid.constants.forEach(function (f) { - var network = networks[f.network] - - Object.keys(f.bip32).forEach(function (name) { - var extb58 = f.bip32[name] - - it('resolves ' + extb58 + ' to ' + f.network, function () { - assert.equal(HDNode.fromBase58(extb58, network).network, network) - }) - }) - }) - }) - - describe('estimateFee', function () { - fixtures.valid.estimateFee.forEach(function (f) { - describe('(' + f.network + ')', function () { - var network = networks[f.network] - - it('calculates the fee correctly for ' + f.description, function () { - var buffer = new Buffer(f.txSize) - txToBuffer.returns(buffer) - - var estimateFee = network.estimateFee - var tx = new Transaction() - tx.outs = f.outputs || [] - - assert.equal(estimateFee(tx), f.fee) - }) - }) - }) - }) -}) diff --git a/test/payments.js b/test/payments.js new file mode 100644 index 000000000..1386ea4df --- /dev/null +++ b/test/payments.js @@ -0,0 +1,92 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const u = require('./payments.utils') + +;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(p => { + describe(p, () => { + let fn + let payment = require('../src/payments/' + p) + if (p === 'embed') { + fn = payment.p2data + } else { + fn = payment[p] + } + const fixtures = require('./fixtures/' + p) + + fixtures.valid.forEach((f, i) => { + it(f.description + ' as expected', () => { + const args = u.preform(f.arguments) + const actual = fn(args, f.options) + + u.equate(actual, f.expected, f.arguments) + }) + + it(f.description + ' as expected (no validation)', () => { + const args = u.preform(f.arguments) + const actual = fn(args, Object.assign({}, f.options, { + validate: false + })) + + u.equate(actual, f.expected, f.arguments) + }) + }) + + fixtures.invalid.forEach(f => { + it('throws ' + f.exception + (f.description ? ('for ' + f.description) : ''), () => { + const args = u.preform(f.arguments) + + assert.throws(() => { + fn(args, f.options) + }, new RegExp(f.exception)) + }) + }) + + if (p === 'p2sh') { + const p2wsh = require('../src/payments/p2wsh').p2wsh + const p2pk = require('../src/payments/p2pk').p2pk + it('properly assembles nested p2wsh with names', () => { + const actual = fn({ + redeem: p2wsh({ + redeem: p2pk({ + pubkey: Buffer.from( + '03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058', + 'hex', + ) + }) + }) + }) + assert.strictEqual(actual.address, '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw') + assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk') + assert.strictEqual(actual.redeem.name, 'p2wsh-p2pk') + assert.strictEqual(actual.redeem.redeem.name, 'p2pk') + }) + } + + // cross-verify dynamically too + if (!fixtures.dynamic) return + const { depends, details } = fixtures.dynamic + + details.forEach(f => { + const detail = u.preform(f) + const disabled = {} + if (f.disabled) f.disabled.forEach(k => { disabled[k] = true }) + + for (let key in depends) { + if (key in disabled) continue + const dependencies = depends[key] + + dependencies.forEach(dependency => { + if (!Array.isArray(dependency)) dependency = [dependency] + + const args = {} + dependency.forEach(d => { u.from(d, detail, args) }) + const expected = u.from(key, detail) + + it(f.description + ', ' + key + ' derives from ' + JSON.stringify(dependency), () => { + u.equate(fn(args), expected) + }) + }) + } + }) + }) +}) diff --git a/test/payments.utils.js b/test/payments.utils.js new file mode 100644 index 000000000..15414c40f --- /dev/null +++ b/test/payments.utils.js @@ -0,0 +1,134 @@ +const t = require('assert') +const bscript = require('../src/script') +const BNETWORKS = require('../src/networks') + +function tryHex (x) { + if (Buffer.isBuffer(x)) return x.toString('hex') + if (Array.isArray(x)) return x.map(tryHex) + return x +} + +function fromHex (x) { + if (typeof x === 'string') return Buffer.from(x, 'hex') + if (Array.isArray(x)) return x.map(fromHex) + return x +} +function tryASM (x) { + if (Buffer.isBuffer(x)) return bscript.toASM(x) + return x +} +function asmToBuffer (x) { + if (x === '') return Buffer.alloc(0) + return bscript.fromASM(x) +} +function carryOver (a, b) { + for (let k in b) { + if (k in a && k === 'redeem') { + carryOver(a[k], b[k]) + continue + } + + // don't, the value was specified + if (k in a) continue + + // otherwise, expect match + a[k] = b[k] + } +} + +function equateBase (a, b, context) { + 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 ('witness' in b) t.deepStrictEqual(tryHex(a.witness), tryHex(b.witness), `Inequal ${context}witness`) +} + +function equate (a, b, args) { + b = Object.assign({}, b) + carryOver(b, args) + + // by null, we mean 'undefined', but JSON + if (b.input === null) b.input = undefined + if (b.output === null) b.output = undefined + if (b.witness === null) b.witness = undefined + if (b.redeem) { + if (b.redeem.input === null) b.redeem.input = undefined + if (b.redeem.output === null) b.redeem.output = undefined + if (b.redeem.witness === null) b.redeem.witness = undefined + } + + equateBase(a, b, '') + if (b.redeem) equateBase(a.redeem, b.redeem, 'redeem.') + if (b.network) t.deepStrictEqual(a.network, BNETWORKS[b.network], 'Inequal *.network') + + // contextual + 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 ('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 ('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 ('n' in b) t.strictEqual(a.n, b.n, 'Inequal *.n') + if ('pubkeys' in b) t.deepStrictEqual(tryHex(a.pubkeys), tryHex(b.pubkeys), 'Inequal *.pubkeys') + if ('signatures' in b) t.deepStrictEqual(tryHex(a.signatures), tryHex(b.signatures), 'Inequal *.signatures') + if ('data' in b) t.deepStrictEqual(tryHex(a.data), tryHex(b.data), 'Inequal *.data') +} + +function preform (x) { + x = Object.assign({}, x) + + if (x.network) x.network = BNETWORKS[x.network] + if (typeof x.inputHex === 'string') { + x.input = Buffer.from(x.inputHex, 'hex') + delete x.inputHex + } + if (typeof x.outputHex === 'string') { + x.output = Buffer.from(x.outputHex, 'hex') + delete x.outputHex + } + if (typeof x.output === 'string') x.output = asmToBuffer(x.output) + if (typeof x.input === 'string') x.input = asmToBuffer(x.input) + if (Array.isArray(x.witness)) x.witness = x.witness.map(fromHex) + + if (x.data) x.data = x.data.map(fromHex) + if (x.hash) x.hash = Buffer.from(x.hash, 'hex') + if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex') + if (x.signature) x.signature = Buffer.from(x.signature, 'hex') + if (x.pubkeys) x.pubkeys = x.pubkeys.map(fromHex) + if (x.signatures) x.signatures = x.signatures.map(y => { return Number.isFinite(y) ? y : Buffer.from(y, 'hex') }) + 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.output === 'string') x.redeem.output = asmToBuffer(x.redeem.output) + 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] + } + + return x +} + +function from (path, object, result) { + path = path.split('.') + result = result || {} + + let r = result + path.forEach((k, i) => { + if (i < path.length - 1) { + r[k] = r[k] || {} + + // recurse + r = r[k] + object = object[k] + } else { + r[k] = object[k] + } + }) + + return result +} + +module.exports = { + from, + equate, + preform +} diff --git a/test/psbt.js b/test/psbt.js new file mode 100644 index 000000000..467e4260c --- /dev/null +++ b/test/psbt.js @@ -0,0 +1,683 @@ +const { describe, it } = require('mocha') +const assert = require('assert') + +const bip32 = require('bip32') +const ECPair = require('../src/ecpair') +const Psbt = require('..').Psbt +const NETWORKS = require('../src/networks') + +const initBuffers = object => JSON.parse(JSON.stringify(object), (key, value) => { + const regex = new RegExp(/^Buffer.from\(['"](.*)['"], ['"](.*)['"]\)$/) + const result = regex.exec(value) + if (!result) return value + + const data = result[1] + const encoding = result[2] + + return Buffer.from(data, encoding) +}) + +const fixtures = initBuffers(require('./fixtures/psbt')) + +const upperCaseFirstLetter = str => str.replace(/^./, s => s.toUpperCase()) + +const b = hex => Buffer.from(hex, 'hex'); + +describe(`Psbt`, () => { + describe('BIP174 Test Vectors', () => { + fixtures.bip174.invalid.forEach(f => { + it(`Invalid: ${f.description}`, () => { + assert.throws(() => { + Psbt.fromBase64(f.psbt) + }, new RegExp(f.errorMessage)) + }) + }) + + fixtures.bip174.valid.forEach(f => { + it(`Valid: ${f.description}`, () => { + assert.doesNotThrow(() => { + Psbt.fromBase64(f.psbt) + }) + }) + }) + + fixtures.bip174.failSignChecks.forEach(f => { + const keyPair = ECPair.makeRandom() + it(`Fails Signer checks: ${f.description}`, () => { + const psbt = Psbt.fromBase64(f.psbt) + assert.throws(() => { + psbt.signInput(f.inputToCheck, keyPair) + }, new RegExp(f.errorMessage)) + }) + }) + + fixtures.bip174.creator.forEach(f => { + it('Creates expected PSBT', () => { + const psbt = new Psbt() + for (const input of f.inputs) { + psbt.addInput(input) + } + for (const output of f.outputs) { + const script = Buffer.from(output.script, 'hex'); + psbt.addOutput({...output, script}) + } + assert.strictEqual(psbt.toBase64(), f.result) + }) + }) + + fixtures.bip174.updater.forEach(f => { + it('Updates PSBT to the expected result', () => { + const psbt = Psbt.fromBase64(f.psbt) + + for (const inputOrOutput of ['input', 'output']) { + const fixtureData = f[`${inputOrOutput}Data`] + if (fixtureData) { + for (const [i, data] of fixtureData.entries()) { + const txt = upperCaseFirstLetter(inputOrOutput) + psbt[`update${txt}`](i, data) + } + } + } + + assert.strictEqual(psbt.toBase64(), f.result) + }) + }) + + fixtures.bip174.signer.forEach(f => { + it('Signs PSBT to the expected result', () => { + const psbt = Psbt.fromBase64(f.psbt) + + f.keys.forEach(({inputToSign, WIF}) => { + const keyPair = ECPair.fromWIF(WIF, NETWORKS.testnet); + psbt.signInput(inputToSign, keyPair); + }) + + assert.strictEqual(psbt.toBase64(), f.result) + }) + }) + + fixtures.bip174.combiner.forEach(f => { + it('Combines two PSBTs to the expected result', () => { + const psbts = f.psbts.map(psbt => Psbt.fromBase64(psbt)) + + psbts[0].combine(psbts[1]) + + // Produces a different Base64 string due to implemetation specific key-value ordering. + // That means this test will fail: + // assert.strictEqual(psbts[0].toBase64(), f.result) + // However, if we compare the actual PSBT properties we can see they are logically identical: + assert.deepStrictEqual(psbts[0], Psbt.fromBase64(f.result)) + }) + }) + + fixtures.bip174.finalizer.forEach(f => { + it("Finalizes inputs and gives the expected PSBT", () => { + const psbt = Psbt.fromBase64(f.psbt) + + psbt.finalizeAllInputs() + + assert.strictEqual(psbt.toBase64(), f.result) + }) + }) + + fixtures.bip174.extractor.forEach(f => { + it('Extracts the expected transaction from a PSBT', () => { + const psbt1 = Psbt.fromBase64(f.psbt) + const transaction1 = psbt1.extractTransaction(true).toHex() + + const psbt2 = Psbt.fromBase64(f.psbt) + const transaction2 = psbt2.extractTransaction().toHex() + + assert.strictEqual(transaction1, transaction2) + assert.strictEqual(transaction1, f.transaction) + + const psbt3 = Psbt.fromBase64(f.psbt) + delete psbt3.data.inputs[0].finalScriptSig + delete psbt3.data.inputs[0].finalScriptWitness + assert.throws(() => { + psbt3.extractTransaction() + }, new RegExp('Not finalized')) + + const psbt4 = Psbt.fromBase64(f.psbt) + psbt4.setMaximumFeeRate(1) + assert.throws(() => { + psbt4.extractTransaction() + }, new RegExp('Warning: You are paying around [\\d.]+ in fees')) + + const psbt5 = Psbt.fromBase64(f.psbt) + psbt5.extractTransaction(true) + const fr1 = psbt5.getFeeRate() + const fr2 = psbt5.getFeeRate() + assert.strictEqual(fr1, fr2) + }) + }) + }) + + describe('signInputAsync', () => { + fixtures.signInput.checks.forEach(f => { + it(f.description, async () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotReject(async () => { + await psbtThatShouldsign.signInputAsync( + f.shouldSign.inputToCheck, + ECPair.fromWIF(f.shouldSign.WIF), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.rejects(async () => { + await psbtThatShouldThrow.signInputAsync( + f.shouldThrow.inputToCheck, + ECPair.fromWIF(f.shouldThrow.WIF), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp(f.shouldThrow.errorMessage)) + assert.rejects(async () => { + await psbtThatShouldThrow.signInputAsync( + f.shouldThrow.inputToCheck, + ) + }, new RegExp('Need Signer to sign input')) + } + }) + }) + }) + + describe('signInput', () => { + fixtures.signInput.checks.forEach(f => { + it(f.description, () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotThrow(() => { + psbtThatShouldsign.signInput( + f.shouldSign.inputToCheck, + ECPair.fromWIF(f.shouldSign.WIF), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.throws(() => { + psbtThatShouldThrow.signInput( + f.shouldThrow.inputToCheck, + ECPair.fromWIF(f.shouldThrow.WIF), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp(f.shouldThrow.errorMessage)) + assert.throws(() => { + psbtThatShouldThrow.signInput( + f.shouldThrow.inputToCheck, + ) + }, new RegExp('Need Signer to sign input')) + } + }) + }) + }) + + describe('signAllInputsAsync', () => { + fixtures.signInput.checks.forEach(f => { + if (f.description === 'checks the input exists') return + it(f.description, async () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotReject(async () => { + await psbtThatShouldsign.signAllInputsAsync( + ECPair.fromWIF(f.shouldSign.WIF), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.rejects(async () => { + await psbtThatShouldThrow.signAllInputsAsync( + ECPair.fromWIF(f.shouldThrow.WIF), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp('No inputs were signed')) + assert.rejects(async () => { + await psbtThatShouldThrow.signAllInputsAsync() + }, new RegExp('Need Signer to sign input')) + } + }) + }) + }) + + describe('signAllInputs', () => { + fixtures.signInput.checks.forEach(f => { + if (f.description === 'checks the input exists') return + it(f.description, () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotThrow(() => { + psbtThatShouldsign.signAllInputs( + ECPair.fromWIF(f.shouldSign.WIF), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.throws(() => { + psbtThatShouldThrow.signAllInputs( + ECPair.fromWIF(f.shouldThrow.WIF), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp('No inputs were signed')) + assert.throws(() => { + psbtThatShouldThrow.signAllInputs() + }, new RegExp('Need Signer to sign input')) + } + }) + }) + }) + + describe('signInputHDAsync', () => { + fixtures.signInputHD.checks.forEach(f => { + it(f.description, async () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotReject(async () => { + await psbtThatShouldsign.signInputHDAsync( + f.shouldSign.inputToCheck, + bip32.fromBase58(f.shouldSign.xprv), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.rejects(async () => { + await psbtThatShouldThrow.signInputHDAsync( + f.shouldThrow.inputToCheck, + bip32.fromBase58(f.shouldThrow.xprv), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp(f.shouldThrow.errorMessage)) + assert.rejects(async () => { + await psbtThatShouldThrow.signInputHDAsync( + f.shouldThrow.inputToCheck, + ) + }, new RegExp('Need HDSigner to sign input')) + } + }) + }) + }) + + describe('signInputHD', () => { + fixtures.signInputHD.checks.forEach(f => { + it(f.description, () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotThrow(() => { + psbtThatShouldsign.signInputHD( + f.shouldSign.inputToCheck, + bip32.fromBase58(f.shouldSign.xprv), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.throws(() => { + psbtThatShouldThrow.signInputHD( + f.shouldThrow.inputToCheck, + bip32.fromBase58(f.shouldThrow.xprv), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp(f.shouldThrow.errorMessage)) + assert.throws(() => { + psbtThatShouldThrow.signInputHD( + f.shouldThrow.inputToCheck, + ) + }, new RegExp('Need HDSigner to sign input')) + } + }) + }) + }) + + describe('signAllInputsHDAsync', () => { + fixtures.signInputHD.checks.forEach(f => { + it(f.description, async () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotReject(async () => { + await psbtThatShouldsign.signAllInputsHDAsync( + bip32.fromBase58(f.shouldSign.xprv), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.rejects(async () => { + await psbtThatShouldThrow.signAllInputsHDAsync( + bip32.fromBase58(f.shouldThrow.xprv), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp('No inputs were signed')) + assert.rejects(async () => { + await psbtThatShouldThrow.signAllInputsHDAsync() + }, new RegExp('Need HDSigner to sign input')) + } + }) + }) + }) + + describe('signAllInputsHD', () => { + fixtures.signInputHD.checks.forEach(f => { + it(f.description, () => { + if (f.shouldSign) { + const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt) + assert.doesNotThrow(() => { + psbtThatShouldsign.signAllInputsHD( + bip32.fromBase58(f.shouldSign.xprv), + f.shouldSign.sighashTypes || undefined, + ) + }) + } + + if (f.shouldThrow) { + const psbtThatShouldThrow = Psbt.fromBase64(f.shouldThrow.psbt) + assert.throws(() => { + psbtThatShouldThrow.signAllInputsHD( + bip32.fromBase58(f.shouldThrow.xprv), + f.shouldThrow.sighashTypes || undefined, + ) + }, new RegExp('No inputs were signed')) + assert.throws(() => { + psbtThatShouldThrow.signAllInputsHD() + }, new RegExp('Need HDSigner to sign input')) + } + }) + }) + }) + + describe('finalizeAllInputs', () => { + fixtures.finalizeAllInputs.forEach(f => { + it(`Finalizes inputs of type "${f.type}"`, () => { + const psbt = Psbt.fromBase64(f.psbt) + + psbt.finalizeAllInputs() + + assert.strictEqual(psbt.toBase64(), f.result) + }) + }) + it('fails if no script found', () => { + const psbt = new Psbt() + psbt.addInput({ + hash: '0000000000000000000000000000000000000000000000000000000000000000', + index: 0 + }) + assert.throws(() => { + psbt.finalizeAllInputs() + }, new RegExp('No script found for input #0')) + psbt.updateInput(0, { + witnessUtxo: { + script: Buffer.from('0014d85c2b71d0060b09c9886aeb815e50991dda124d', 'hex'), + value: 2e5 + } + }) + assert.throws(() => { + psbt.finalizeAllInputs() + }, new RegExp('Can not finalize input #0')) + }) + }) + + describe('addInput', () => { + fixtures.addInput.checks.forEach(f => { + it(f.description, () => { + const psbt = new Psbt() + + if (f.exception) { + assert.throws(() => { + psbt.addInput(f.inputData) + }, new RegExp(f.exception)) + assert.throws(() => { + psbt.addInputs([f.inputData]) + }, new RegExp(f.exception)) + } else { + assert.doesNotThrow(() => { + psbt.addInputs([f.inputData]) + if (f.equals) { + assert.strictEqual(psbt.toBase64(), f.equals) + } + }) + assert.throws(() => { + psbt.addInput(f.inputData) + }, new RegExp('Duplicate input detected.')) + } + }) + }) + }) + + describe('addOutput', () => { + fixtures.addOutput.checks.forEach(f => { + it(f.description, () => { + const psbt = new Psbt() + + if (f.exception) { + assert.throws(() => { + psbt.addOutput(f.outputData) + }, new RegExp(f.exception)) + assert.throws(() => { + psbt.addOutputs([f.outputData]) + }, new RegExp(f.exception)) + } else { + assert.doesNotThrow(() => { + psbt.addOutput(f.outputData) + }) + assert.doesNotThrow(() => { + psbt.addOutputs([f.outputData]) + }) + } + }) + }) + }) + + describe('setVersion', () => { + it('Sets the version value of the unsigned transaction', () => { + const psbt = new Psbt() + + assert.strictEqual(psbt.extractTransaction().version, 2) + psbt.setVersion(1) + assert.strictEqual(psbt.extractTransaction().version, 1) + }) + }) + + describe('setLocktime', () => { + it('Sets the nLockTime value of the unsigned transaction', () => { + const psbt = new Psbt() + + assert.strictEqual(psbt.extractTransaction().locktime, 0) + psbt.setLocktime(1) + assert.strictEqual(psbt.extractTransaction().locktime, 1) + }) + }) + + describe('setInputSequence', () => { + it('Sets the sequence number for a given input', () => { + const psbt = new Psbt() + psbt.addInput({ + hash: '0000000000000000000000000000000000000000000000000000000000000000', + index: 0 + }); + + assert.strictEqual(psbt.inputCount, 1) + assert.strictEqual(psbt.__CACHE.__TX.ins[0].sequence, 0xffffffff) + psbt.setInputSequence(0, 0) + assert.strictEqual(psbt.__CACHE.__TX.ins[0].sequence, 0) + }) + + it('throws if input index is too high', () => { + const psbt = new Psbt() + psbt.addInput({ + hash: '0000000000000000000000000000000000000000000000000000000000000000', + index: 0 + }); + + assert.throws(() => { + psbt.setInputSequence(1, 0) + }, new RegExp('Input index too high')) + }) + }) + + describe('clone', () => { + it('Should clone a psbt exactly with no reference', () => { + const f = fixtures.clone + const psbt = Psbt.fromBase64(f.psbt) + const notAClone = Object.assign(new Psbt(), psbt) // references still active + const clone = psbt.clone() + + assert.strictEqual(psbt.validateSignaturesOfAllInputs(), true) + + assert.strictEqual(clone.toBase64(), psbt.toBase64()) + assert.strictEqual(clone.toBase64(), notAClone.toBase64()) + assert.strictEqual(psbt.toBase64(), notAClone.toBase64()) + psbt.__CACHE.__TX.version |= 0xff0000 + assert.notStrictEqual(clone.toBase64(), psbt.toBase64()) + assert.notStrictEqual(clone.toBase64(), notAClone.toBase64()) + assert.strictEqual(psbt.toBase64(), notAClone.toBase64()) + }) + }) + + describe('setMaximumFeeRate', () => { + it('Sets the maximumFeeRate value', () => { + const psbt = new Psbt() + + assert.strictEqual(psbt.opts.maximumFeeRate, 5000) + psbt.setMaximumFeeRate(6000) + assert.strictEqual(psbt.opts.maximumFeeRate, 6000) + }) + }) + + describe('validateSignaturesOfInput', () => { + const f = fixtures.validateSignaturesOfInput + it('Correctly validates a signature', () => { + const psbt = Psbt.fromBase64(f.psbt) + + assert.strictEqual(psbt.validateSignaturesOfInput(f.index), true) + assert.throws(() => { + psbt.validateSignaturesOfInput(f.nonExistantIndex) + }, new RegExp('No signatures to validate')) + }) + + it('Correctly validates a signature against a pubkey', () => { + const psbt = Psbt.fromBase64(f.psbt) + assert.strictEqual(psbt.validateSignaturesOfInput(f.index, f.pubkey), true) + assert.throws(() => { + psbt.validateSignaturesOfInput(f.index, f.incorrectPubkey) + }, new RegExp('No signatures for this pubkey')) + }) + }) + + describe('getFeeRate', () => { + it('Throws error if called before inputs are finalized', () => { + const f = fixtures.getFeeRate + const psbt = Psbt.fromBase64(f.psbt) + + assert.throws(() => { + psbt.getFeeRate() + }, new RegExp('PSBT must be finalized to calculate fee rate')) + + psbt.finalizeAllInputs() + + assert.strictEqual(psbt.getFeeRate(), f.fee) + psbt.__CACHE.__FEE_RATE = undefined + assert.strictEqual(psbt.getFeeRate(), f.fee) + }) + }) + + describe('create 1-to-1 transaction', () => { + const alice = ECPair.fromWIF('L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr') + const psbt = new Psbt() + psbt.addInput({ + hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e', + index: 0, + nonWitnessUtxo: Buffer.from( + '0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' + + '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' + + 'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' + + '9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' + + '631e5e1e66009ce3710ceea5b1ad13ffffffff01905f0100000000001976a9148bb' + + 'c95d2709c71607c60ee3f097c1217482f518d88ac00000000', + 'hex', + ), + sighashType: 1, + }) + psbt.addOutput({ + address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp', + value: 80000 + }) + psbt.signInput(0, alice) + assert.throws(() => { + psbt.setVersion(3) + }, new RegExp('Can not modify transaction, signatures exist.')) + psbt.validateSignaturesOfInput(0) + psbt.finalizeAllInputs() + assert.throws(() => { + psbt.setVersion(3) + }, new RegExp('Can not modify transaction, signatures exist.')) + assert.strictEqual( + psbt.extractTransaction().toHex(), + '02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' + + 'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' + + 'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' + + '9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' + + 'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' + + '08a22724efa6f6a07b0ec4c79aa88ac00000000', + ) + }) + + describe('Method return types', () => { + it('fromBuffer returns Psbt type (not base class)', () => { + const psbt = Psbt.fromBuffer(Buffer.from( + '70736274ff01000a01000000000000000000000000', 'hex' //cHNidP8BAAoBAAAAAAAAAAAAAAAA + )); + assert.strictEqual(psbt instanceof Psbt, true); + assert.ok(psbt.__CACHE.__TX); + }) + it('fromBase64 returns Psbt type (not base class)', () => { + const psbt = Psbt.fromBase64('cHNidP8BAAoBAAAAAAAAAAAAAAAA'); + assert.strictEqual(psbt instanceof Psbt, true); + assert.ok(psbt.__CACHE.__TX); + }) + it('fromHex returns Psbt type (not base class)', () => { + const psbt = Psbt.fromHex('70736274ff01000a01000000000000000000000000'); + assert.strictEqual(psbt instanceof Psbt, true); + assert.ok(psbt.__CACHE.__TX); + }) + }) + + describe('Cache', () => { + it('non-witness UTXOs are cached', () => { + const f = fixtures.cache.nonWitnessUtxo; + const psbt = Psbt.fromBase64(f.psbt) + const index = f.inputIndex; + + // Cache is empty + assert.strictEqual(psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index], undefined) + + // Cache is populated + psbt.updateInput(index, { nonWitnessUtxo: f.nonWitnessUtxo }) + const value = psbt.data.inputs[index].nonWitnessUtxo + assert.ok(psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index].equals(value)) + assert.ok(psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index].equals(f.nonWitnessUtxo)) + + // Cache is rebuilt from internal transaction object when cleared + psbt.data.inputs[index].nonWitnessUtxo = Buffer.from([1,2,3]) + psbt.__CACHE.__NON_WITNESS_UTXO_BUF_CACHE[index] = undefined + assert.ok(psbt.data.inputs[index].nonWitnessUtxo.equals(value)) + }) + }) +}) diff --git a/test/script.js b/test/script.js index 7017522e4..d003558fd 100644 --- a/test/script.js +++ b/test/script.js @@ -1,87 +1,157 @@ -/* global describe, it */ -/* eslint-disable no-new */ - -var assert = require('assert') -var opcodes = require('../src/opcodes') - -var Script = require('../src/script') - -var fixtures = require('./fixtures/script.json') - -describe('Script', function () { - describe('constructor', function () { - it('accepts valid parameters', function () { - var buffer = new Buffer([1]) - var chunks = [1] - var script = new Script(buffer, chunks) +const { describe, it } = require('mocha') +const assert = require('assert') +const bscript = require('../src/script') +const minimalData = require('minimaldata') + +const fixtures = require('./fixtures/script.json') +const fixtures2 = require('./fixtures/templates.json') + +describe('script', () => { + // TODO + describe('isCanonicalPubKey', () => { + it('rejects if not provided a Buffer', () => { + assert.strictEqual(false, bscript.isCanonicalPubKey(0)) + }) + it('rejects smaller than 33', () => { + for (var i = 0; i < 33; i++) { + assert.strictEqual(false, bscript.isCanonicalPubKey(Buffer.from('', i))) + } + }) + }) + describe.skip('isCanonicalScriptSignature', () => { + }) - assert.equal(script.buffer, buffer) - assert.equal(script.chunks, chunks) + describe('fromASM/toASM', () => { + fixtures.valid.forEach(f => { + it('encodes/decodes ' + f.asm, () => { + const script = bscript.fromASM(f.asm) + assert.strictEqual(bscript.toASM(script), f.asm) + }) }) - it('throws an error when input is not an array', function () { - assert.throws(function () { - new Script({}) - }, /Expected Buffer, got/) + fixtures.invalid.fromASM.forEach(f => { + it('throws ' + f.description, () => { + assert.throws(() => { + bscript.fromASM(f.script) + }, new RegExp(f.description)) + }) }) }) - describe('fromASM/toASM', function () { - fixtures.valid.forEach(function (f) { - it('decodes/encodes ' + f.description, function () { - assert.equal(Script.fromASM(f.asm).toASM(), f.asm) - }) + describe('fromASM/toASM (templates)', () => { + fixtures2.valid.forEach(f => { + if (f.inputHex) { + const ih = bscript.toASM(Buffer.from(f.inputHex, 'hex')) + + it('encodes/decodes ' + ih, () => { + const script = bscript.fromASM(f.input) + assert.strictEqual(script.toString('hex'), f.inputHex) + assert.strictEqual(bscript.toASM(script), f.input) + }) + } + + if (f.outputHex) { + it('encodes/decodes ' + f.output, () => { + const script = bscript.fromASM(f.output) + assert.strictEqual(script.toString('hex'), f.outputHex) + assert.strictEqual(bscript.toASM(script), f.output) + }) + } }) }) - describe('fromHex/toHex', function () { - fixtures.valid.forEach(function (f) { - it('decodes/encodes ' + f.description, function () { - assert.equal(Script.fromHex(f.hex).toHex(), f.hex) + describe('isPushOnly', () => { + fixtures.valid.forEach(f => { + it('returns ' + !!f.stack + ' for ' + f.asm, () => { + const script = bscript.fromASM(f.asm) + const chunks = bscript.decompile(script) + + assert.strictEqual(bscript.isPushOnly(chunks), !!f.stack) }) }) }) - describe('getHash', function () { - fixtures.valid.forEach(function (f) { - it('produces a HASH160 of "' + f.asm + '"', function () { - var script = Script.fromHex(f.hex) + describe('toStack', () => { + fixtures.valid.forEach(f => { + it('returns ' + !!f.stack + ' for ' + f.asm, () => { + if (!f.stack || !f.asm) return - assert.equal(script.getHash().toString('hex'), f.hash) + const script = bscript.fromASM(f.asm) + + const stack = bscript.toStack(script) + assert.deepStrictEqual(stack.map(x => { + return x.toString('hex') + }), f.stack) + + assert.strictEqual(bscript.toASM(bscript.compile(stack)), f.asm, 'should rebuild same script from stack') }) }) }) - describe('fromChunks', function () { - it('should match expected behaviour', function () { - var hash = new Buffer(32) - hash.fill(0) + describe('compile (via fromASM)', () => { + fixtures.valid.forEach(f => { + it('(' + f.type + ') compiles ' + f.asm, () => { + const scriptSig = bscript.fromASM(f.asm) + + assert.strictEqual(scriptSig.toString('hex'), f.script) - var script = Script.fromChunks([ - opcodes.OP_HASH160, - hash, - opcodes.OP_EQUAL - ]) + if (f.nonstandard) { + const scriptSigNS = bscript.fromASM(f.nonstandard.scriptSig) - assert.equal(script.toHex(), 'a920000000000000000000000000000000000000000000000000000000000000000087') + assert.strictEqual(scriptSigNS.toString('hex'), f.script) + } + }) }) }) - describe('without', function () { - var hex = 'a914e8c300c87986efa94c37c0519929019ef86eb5b487' - var script = Script.fromHex(hex) + describe('decompile', () => { + fixtures.valid.forEach(f => { + it('decompiles ' + f.asm, () => { + const chunks = bscript.decompile(Buffer.from(f.script, 'hex')) + + assert.strictEqual(bscript.compile(chunks).toString('hex'), f.script) + assert.strictEqual(bscript.toASM(chunks), f.asm) + + if (f.nonstandard) { + const chunksNS = bscript.decompile(Buffer.from(f.nonstandard.scriptSigHex, 'hex')) + + assert.strictEqual(bscript.compile(chunksNS).toString('hex'), f.script) + + // toASM converts verbatim, only `compile` transforms the script to a minimalpush compliant script + assert.strictEqual(bscript.toASM(chunksNS), f.nonstandard.scriptSig) + } + }) + }) - it('should return a script without the given value', function () { - var subScript = script.without(opcodes.OP_HASH160) + fixtures.invalid.decompile.forEach(f => { + it('fails to decompile ' + f.script + ', because "' + f.description + '"', () => { + const chunks = bscript.decompile(Buffer.from(f.script, 'hex')) - assert.equal(subScript.toHex(), '14e8c300c87986efa94c37c0519929019ef86eb5b487') + assert.strictEqual(chunks, null) + }) }) + }) - it('shouldnt mutate the original script', function () { - var subScript = script.without(opcodes.OP_EQUAL) + describe('SCRIPT_VERIFY_MINIMALDATA policy', () => { + fixtures.valid.forEach(f => { + it('compliant for ' + f.type + ' scriptSig ' + f.asm, () => { + const script = Buffer.from(f.script, 'hex') - assert.notEqual(subScript.toHex(), hex) - assert.equal(script.toHex(), hex) + assert(minimalData(script)) + }) }) + + function testEncodingForSize (i) { + it('compliant for data PUSH of length ' + i, () => { + const buffer = Buffer.alloc(i) + const script = bscript.compile([buffer]) + + assert(minimalData(script), 'Failed for ' + i + ' length script: ' + script.toString('hex')) + }) + } + + for (var i = 0; i < 520; ++i) { + testEncodingForSize(i) + } }) }) diff --git a/test/script_number.js b/test/script_number.js new file mode 100644 index 000000000..d5b97e2a8 --- /dev/null +++ b/test/script_number.js @@ -0,0 +1,26 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const scriptNumber = require('../src/script_number') +const fixtures = require('./fixtures/script_number.json') + +describe('script-number', () => { + describe('decode', () => { + fixtures.forEach(f => { + it(f.hex + ' returns ' + f.number, () => { + const actual = scriptNumber.decode(Buffer.from(f.hex, 'hex'), f.bytes) + + assert.strictEqual(actual, f.number) + }) + }) + }) + + describe('encode', () => { + fixtures.forEach(f => { + it(f.number + ' returns ' + f.hex, () => { + const actual = scriptNumber.encode(f.number) + + assert.strictEqual(actual.toString('hex'), f.hex) + }) + }) + }) +}) diff --git a/test/script_signature.js b/test/script_signature.js new file mode 100644 index 000000000..6888ca55f --- /dev/null +++ b/test/script_signature.js @@ -0,0 +1,64 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const bscriptSig = require('../src/script').signature +const Buffer = require('safe-buffer').Buffer +const fixtures = require('./fixtures/signature.json') + +describe('Script Signatures', () => { + function fromRaw (signature) { + return Buffer.concat([ + Buffer.from(signature.r, 'hex'), + Buffer.from(signature.s, 'hex') + ], 64) + } + + function toRaw (signature) { + return { + r: signature.slice(0, 32).toString('hex'), + s: signature.slice(32, 64).toString('hex') + } + } + + describe('encode', () => { + fixtures.valid.forEach(f => { + it('encodes ' + f.hex, () => { + const buffer = bscriptSig.encode(fromRaw(f.raw), f.hashType) + + assert.strictEqual(buffer.toString('hex'), f.hex) + }) + }) + + fixtures.invalid.forEach(f => { + if (!f.raw) return + + it('throws ' + f.exception, () => { + const signature = fromRaw(f.raw) + + assert.throws(() => { + bscriptSig.encode(signature, f.hashType) + }, new RegExp(f.exception)) + }) + }) + }) + + describe('decode', () => { + fixtures.valid.forEach(f => { + it('decodes ' + f.hex, () => { + const decode = bscriptSig.decode(Buffer.from(f.hex, 'hex')) + + assert.deepStrictEqual(toRaw(decode.signature), f.raw) + assert.strictEqual(decode.hashType, f.hashType) + }) + }) + + fixtures.invalid.forEach(f => { + it('throws on ' + f.hex, () => { + const buffer = Buffer.from(f.hex, 'hex') + + assert.throws(() => { + bscriptSig.decode(buffer) + }, new RegExp(f.exception)) + }) + }) + }) +}) diff --git a/test/scripts.js b/test/scripts.js deleted file mode 100644 index ccfee0b13..000000000 --- a/test/scripts.js +++ /dev/null @@ -1,274 +0,0 @@ -/* global describe, it */ - -var assert = require('assert') -var ops = require('../src/opcodes') -var scripts = require('../src/scripts') - -var ECPubKey = require('../src/ecpubkey') -var Script = require('../src/script') - -var fixtures = require('./fixtures/scripts.json') - -describe('Scripts', function () { - // TODO - describe.skip('isCanonicalPubKey', function () {}) - describe.skip('isCanonicalSignature', function () {}) - - describe('classifyInput', function () { - fixtures.valid.forEach(function (f) { - if (!f.scriptSig) return - - it('classifies ' + f.scriptSig + ' as ' + f.type, function () { - var script = Script.fromASM(f.scriptSig) - var type = scripts.classifyInput(script) - - assert.equal(type, f.type) - }) - }) - - fixtures.valid.forEach(function (f) { - if (!f.scriptSig) return - if (!f.typeIncomplete) return - - it('classifies incomplete ' + f.scriptSig + ' as ' + f.typeIncomplete, function () { - var script = Script.fromASM(f.scriptSig) - var type = scripts.classifyInput(script, true) - - assert.equal(type, f.typeIncomplete) - }) - }) - }) - - describe('classifyOutput', function () { - fixtures.valid.forEach(function (f) { - if (!f.scriptPubKey) return - - it('classifies ' + f.scriptPubKey + ' as ' + f.type, function () { - var script = Script.fromASM(f.scriptPubKey) - var type = scripts.classifyOutput(script) - - assert.equal(type, f.type) - }) - }) - }) - - ;['PubKey', 'PubKeyHash', 'ScriptHash', 'Multisig', 'NullData'].forEach(function (type) { - var inputFnName = 'is' + type + 'Input' - var outputFnName = 'is' + type + 'Output' - - var inputFn = scripts[inputFnName] - var outputFn = scripts[outputFnName] - - describe('is' + type + 'Input', function () { - fixtures.valid.forEach(function (f) { - var expected = type.toLowerCase() === f.type - - if (inputFn && f.scriptSig) { - it('returns ' + expected + ' for ' + f.scriptSig, function () { - var script = Script.fromASM(f.scriptSig) - - assert.equal(inputFn(script), expected) - }) - - if (f.typeIncomplete) { - var expectedIncomplete = type.toLowerCase() === f.typeIncomplete - - it('returns ' + expected + ' for ' + f.scriptSig, function () { - var script = Script.fromASM(f.scriptSig) - - assert.equal(inputFn(script, true), expectedIncomplete) - }) - } - } - }) - - if (!(inputFnName in fixtures.invalid)) return - - fixtures.invalid[inputFnName].forEach(function (f) { - if (inputFn && f.scriptSig) { - it('returns false for ' + f.scriptSig, function () { - var script = Script.fromASM(f.scriptSig) - - assert.equal(inputFn(script), false) - }) - } - }) - }) - - describe('is' + type + 'Output', function () { - fixtures.valid.forEach(function (f) { - var expected = type.toLowerCase() === f.type - - if (outputFn && f.scriptPubKey) { - it('returns ' + expected + ' for ' + f.scriptPubKey, function () { - var script = Script.fromASM(f.scriptPubKey) - - assert.equal(outputFn(script), expected) - }) - } - }) - - if (!(outputFnName in fixtures.invalid)) return - - fixtures.invalid[outputFnName].forEach(function (f) { - if (outputFn && f.scriptPubKey) { - it('returns false for ' + f.scriptPubKey, function () { - var script = Script.fromASM(f.scriptPubKey) - - assert.equal(outputFn(script), false) - }) - } - }) - }) - }) - - describe('pubKeyInput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkey') return - - it('returns ' + f.scriptSig, function () { - var signature = new Buffer(f.signature, 'hex') - - var scriptSig = scripts.pubKeyInput(signature) - assert.equal(scriptSig.toASM(), f.scriptSig) - }) - }) - }) - - describe('pubKeyOutput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkey') return - - it('returns ' + f.scriptPubKey, function () { - var pubKey = ECPubKey.fromHex(f.pubKey) - - var scriptPubKey = scripts.pubKeyOutput(pubKey) - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) - }) - }) - - describe('pubKeyHashInput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkeyhash') return - - var pubKey = ECPubKey.fromHex(f.pubKey) - - it('returns ' + f.scriptSig, function () { - var signature = new Buffer(f.signature, 'hex') - - var scriptSig = scripts.pubKeyHashInput(signature, pubKey) - assert.equal(scriptSig.toASM(), f.scriptSig) - }) - }) - }) - - describe('pubKeyHashOutput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'pubkeyhash') return - - var pubKey = ECPubKey.fromHex(f.pubKey) - var address = pubKey.getAddress() - - it('returns ' + f.scriptPubKey, function () { - var scriptPubKey = scripts.pubKeyHashOutput(address.hash) - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) - }) - }) - - describe('multisigInput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'multisig') return - - it('returns ' + f.scriptSig, function () { - var signatures = f.signatures.map(function (signature) { - return signature ? new Buffer(signature, 'hex') : ops.OP_0 - }) - - var scriptSig = scripts.multisigInput(signatures) - assert.equal(scriptSig.toASM(), f.scriptSig) - }) - }) - - fixtures.invalid.multisigInput.forEach(function (f) { - var pubKeys = f.pubKeys.map(ECPubKey.fromHex) - var scriptPubKey = scripts.multisigOutput(pubKeys.length, pubKeys) - - it('throws on ' + f.exception, function () { - var signatures = f.signatures.map(function (signature) { - return signature ? new Buffer(signature, 'hex') : ops.OP_0 - }) - - assert.throws(function () { - scripts.multisigInput(signatures, scriptPubKey) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('multisigOutput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'multisig') return - - var pubKeys = f.pubKeys.map(ECPubKey.fromHex) - var scriptPubKey = scripts.multisigOutput(pubKeys.length, pubKeys) - - it('returns ' + f.scriptPubKey, function () { - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) - }) - - fixtures.invalid.multisigOutput.forEach(function (f) { - var pubKeys = f.pubKeys.map(ECPubKey.fromHex) - - it('throws on ' + f.exception, function () { - assert.throws(function () { - scripts.multisigOutput(f.m, pubKeys) - }, new RegExp(f.exception)) - }) - }) - }) - - describe('scriptHashInput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'scripthash') return - - var redeemScript = Script.fromASM(f.redeemScript) - var redeemScriptSig = Script.fromASM(f.redeemScriptSig) - - it('returns ' + f.scriptSig, function () { - var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) - - assert.equal(scriptSig.toASM(), f.scriptSig) - }) - }) - }) - - describe('scriptHashOutput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'scripthash') return - - var redeemScript = Script.fromASM(f.redeemScript) - - it('returns ' + f.scriptPubKey, function () { - var scriptPubKey = scripts.scriptHashOutput(redeemScript.getHash()) - - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) - }) - }) - - describe('nullDataOutput', function () { - fixtures.valid.forEach(function (f) { - if (f.type !== 'nulldata') return - - var data = new Buffer(f.data, 'hex') - var scriptPubKey = scripts.nullDataOutput(data) - - it('returns ' + f.scriptPubKey, function () { - assert.equal(scriptPubKey.toASM(), f.scriptPubKey) - }) - }) - }) -}) diff --git a/test/transaction.js b/test/transaction.js index 4a64493f1..3fa924320 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -1,168 +1,288 @@ -/* global describe, it, beforeEach */ - -var assert = require('assert') - -var Transaction = require('../src/transaction') -var Script = require('../src/script') - -var fixtures = require('./fixtures/transaction') - -describe('Transaction', function () { - function fromRaw (raw) { - var tx = new Transaction() +const { describe, it, beforeEach } = require('mocha') +const assert = require('assert') +const bscript = require('../src/script') +const fixtures = require('./fixtures/transaction') +const Transaction = require('..').Transaction + +describe('Transaction', () => { + function fromRaw (raw, noWitness) { + const tx = new Transaction() tx.version = raw.version tx.locktime = raw.locktime - raw.ins.forEach(function (txIn) { - var txHash = new Buffer(txIn.hash, 'hex') - var script + raw.ins.forEach((txIn, i) => { + const txHash = Buffer.from(txIn.hash, 'hex') + let scriptSig if (txIn.data) { - script = new Script(new Buffer(txIn.data, 'hex'), []) + scriptSig = Buffer.from(txIn.data, 'hex') } else if (txIn.script) { - script = Script.fromASM(txIn.script) + scriptSig = bscript.fromASM(txIn.script) } - tx.addInput(txHash, txIn.index, txIn.sequence, script) + tx.addInput(txHash, txIn.index, txIn.sequence, scriptSig) + + if (!noWitness && txIn.witness) { + const witness = txIn.witness.map(x => { + return Buffer.from(x, 'hex') + }) + + tx.setWitness(i, witness) + } }) - raw.outs.forEach(function (txOut) { - tx.addOutput(Script.fromASM(txOut.script), txOut.value) + raw.outs.forEach(txOut => { + let script + + if (txOut.data) { + script = Buffer.from(txOut.data, 'hex') + } else if (txOut.script) { + script = bscript.fromASM(txOut.script) + } + + tx.addOutput(script, txOut.value) }) return tx } - describe('fromBuffer/fromHex', function () { - fixtures.valid.forEach(function (f) { - it('imports ' + f.id + ' correctly', function () { - var actual = Transaction.fromHex(f.hex) + describe('fromBuffer/fromHex', () => { + function importExport (f) { + const id = f.id || f.hash + const txHex = f.hex || f.txHex + + it('imports ' + f.description + ' (' + id + ')', () => { + const actual = Transaction.fromHex(txHex) - assert.deepEqual(actual.toHex(), f.hex) + assert.strictEqual(actual.toHex(), txHex) }) - }) - fixtures.invalid.fromBuffer.forEach(function (f) { - it('throws on ' + f.exception, function () { - assert.throws(function () { + if (f.whex) { + it('imports ' + f.description + ' (' + id + ') as witness', () => { + const actual = Transaction.fromHex(f.whex) + + assert.strictEqual(actual.toHex(), f.whex) + }) + } + } + + fixtures.valid.forEach(importExport) + fixtures.hashForSignature.forEach(importExport) + fixtures.hashForWitnessV0.forEach(importExport) + + fixtures.invalid.fromBuffer.forEach(f => { + it('throws on ' + f.exception, () => { + assert.throws(() => { Transaction.fromHex(f.hex) }, new RegExp(f.exception)) }) }) - }) - describe('toBuffer/toHex', function () { - fixtures.valid.forEach(function (f) { - it('exports ' + f.id + ' correctly', function () { - var actual = fromRaw(f.raw) + it('.version should be interpreted as an int32le', () => { + const txHex = 'ffffffff0000ffffffff' + const tx = Transaction.fromHex(txHex) + assert.strictEqual(-1, tx.version) + assert.strictEqual(0xffffffff, tx.locktime) + }) + }) - assert.deepEqual(actual.toHex(), f.hex) + describe('toBuffer/toHex', () => { + fixtures.valid.forEach(f => { + it('exports ' + f.description + ' (' + f.id + ')', () => { + const actual = fromRaw(f.raw, true) + assert.strictEqual(actual.toHex(), f.hex) }) + + if (f.whex) { + it('exports ' + f.description + ' (' + f.id + ') as witness', () => { + const wactual = fromRaw(f.raw) + assert.strictEqual(wactual.toHex(), f.whex) + }) + } + }) + + it('accepts target Buffer and offset parameters', () => { + const f = fixtures.valid[0] + const actual = fromRaw(f.raw) + const byteLength = actual.byteLength() + + const target = Buffer.alloc(byteLength * 2) + const a = actual.toBuffer(target, 0) + const b = actual.toBuffer(target, byteLength) + + assert.strictEqual(a.length, byteLength) + assert.strictEqual(b.length, byteLength) + assert.strictEqual(a.toString('hex'), f.hex) + assert.strictEqual(b.toString('hex'), f.hex) + assert.deepStrictEqual(a, b) + assert.deepStrictEqual(a, target.slice(0, byteLength)) + assert.deepStrictEqual(b, target.slice(byteLength)) }) }) - describe('addInput', function () { - var prevTxHash - beforeEach(function () { - var f = fixtures.valid[0] - prevTxHash = new Buffer(f.hash, 'hex') + describe('hasWitnesses', () => { + fixtures.valid.forEach(f => { + it('detects if the transaction has witnesses: ' + (f.whex ? 'true' : 'false'), () => { + assert.strictEqual(Transaction.fromHex(f.whex ? f.whex : f.hex).hasWitnesses(), !!f.whex) + }) }) + }) - it('accepts a transaction hash', function () { - var tx = new Transaction() - tx.addInput(prevTxHash, 0) + describe('weight/virtualSize', () => { + it('computes virtual size', () => { + fixtures.valid.forEach(f => { + const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex) - assert.deepEqual(tx.ins[0].hash, prevTxHash) + assert.strictEqual(transaction.virtualSize(), f.virtualSize) + }) }) - it('returns an index', function () { - var tx = new Transaction() - assert.equal(tx.addInput(prevTxHash, 0), 0) - assert.equal(tx.addInput(prevTxHash, 0), 1) + it('computes weight', () => { + fixtures.valid.forEach(f => { + const transaction = Transaction.fromHex(f.whex ? f.whex : f.hex) + + assert.strictEqual(transaction.weight(), f.weight) + }) }) + }) - it('defaults to DEFAULT_SEQUENCE', function () { - var tx = new Transaction() - tx.addInput(prevTxHash, 0) + describe('addInput', () => { + let prevTxHash + beforeEach(() => { + prevTxHash = Buffer.from('ffffffff00ffff000000000000000000000000000000000000000000101010ff', 'hex') + }) - assert.equal(tx.ins[0].sequence, Transaction.DEFAULT_SEQUENCE) + it('returns an index', () => { + const tx = new Transaction() + assert.strictEqual(tx.addInput(prevTxHash, 0), 0) + assert.strictEqual(tx.addInput(prevTxHash, 0), 1) }) - it('defaults to empty script', function () { - var tx = new Transaction() + it('defaults to empty script, witness and 0xffffffff SEQUENCE number', () => { + const tx = new Transaction() tx.addInput(prevTxHash, 0) - assert.equal(tx.ins[0].script, Script.EMPTY) + assert.strictEqual(tx.ins[0].script.length, 0) + assert.strictEqual(tx.ins[0].witness.length, 0) + assert.strictEqual(tx.ins[0].sequence, 0xffffffff) }) - fixtures.invalid.addInput.forEach(function (f) { - it('throws on ' + f.exception, function () { - var tx = new Transaction() - var hash = new Buffer(f.hash, 'hex') + fixtures.invalid.addInput.forEach(f => { + it('throws on ' + f.exception, () => { + const tx = new Transaction() + const hash = Buffer.from(f.hash, 'hex') - assert.throws(function () { + assert.throws(() => { tx.addInput(hash, f.index) }, new RegExp(f.exception)) }) }) }) - describe('addOutput', function () { - fixtures.valid.forEach(function (f) { - it('should add the outputs for ' + f.id + ' correctly', function () { - var tx = new Transaction() - - f.raw.outs.forEach(function (txOut, i) { - var scriptPubKey = Script.fromASM(txOut.script) - var j = tx.addOutput(scriptPubKey, txOut.value) - - assert.equal(i, j) - assert.equal(tx.outs[i].script, scriptPubKey) - assert.equal(tx.outs[i].value, txOut.value) - }) - }) + describe('addOutput', () => { + it('returns an index', () => { + const tx = new Transaction() + assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 0) + assert.strictEqual(tx.addOutput(Buffer.alloc(0), 0), 1) }) }) - describe('clone', function () { - fixtures.valid.forEach(function (f) { - var actual, expected + describe('clone', () => { + fixtures.valid.forEach(f => { + let actual + let expected - beforeEach(function () { + beforeEach(() => { expected = Transaction.fromHex(f.hex) actual = expected.clone() }) - it('should have value equality', function () { - assert.deepEqual(actual, expected) + it('should have value equality', () => { + assert.deepStrictEqual(actual, expected) }) - it('should not have reference equality', function () { - assert.notEqual(actual, expected) + it('should not have reference equality', () => { + assert.notStrictEqual(actual, expected) }) }) }) - describe('getId', function () { - fixtures.valid.forEach(function (f) { - it('should return the id for ' + f.id, function () { - var tx = Transaction.fromHex(f.hex) + describe('getHash/getId', () => { + function verify (f) { + it('should return the id for ' + f.id + '(' + f.description + ')', () => { + const tx = Transaction.fromHex(f.whex || f.hex) + + assert.strictEqual(tx.getHash().toString('hex'), f.hash) + assert.strictEqual(tx.getId(), f.id) + }) + } + + fixtures.valid.forEach(verify) + }) + + describe('isCoinbase', () => { + function verify (f) { + it('should return ' + f.coinbase + ' for ' + f.id + '(' + f.description + ')', () => { + const tx = Transaction.fromHex(f.hex) + + assert.strictEqual(tx.isCoinbase(), f.coinbase) + }) + } + + fixtures.valid.forEach(verify) + }) + + describe('hashForSignature', () => { + it('does not use Witness serialization', () => { + const randScript = Buffer.from('6a', 'hex') + + const tx = new Transaction() + tx.addInput(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), 0) + tx.addOutput(randScript, 5000000000) + + const original = tx.__toBuffer + tx.__toBuffer = (a, b, c) => { + if (c !== false) throw new Error('hashForSignature MUST pass false') + + return original.call(this, a, b, c) + } - assert.equal(tx.getId(), f.id) + assert.throws(() => { + tx.__toBuffer(undefined, undefined, true) + }, /hashForSignature MUST pass false/) + + // assert hashForSignature does not pass false + assert.doesNotThrow(() => { + tx.hashForSignature(0, randScript, 1) + }) + }) + + fixtures.hashForSignature.forEach(f => { + it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : f.script), () => { + const tx = Transaction.fromHex(f.txHex) + const script = bscript.fromASM(f.script) + + assert.strictEqual(tx.hashForSignature(f.inIndex, script, f.type).toString('hex'), f.hash) }) }) }) - describe('getHash', function () { - fixtures.valid.forEach(function (f) { - it('should return the hash for ' + f.id, function () { - var tx = Transaction.fromHex(f.hex) + describe('hashForWitnessV0', () => { + fixtures.hashForWitnessV0.forEach(f => { + it('should return ' + f.hash + ' for ' + (f.description ? ('case "' + f.description + '"') : ''), () => { + const tx = Transaction.fromHex(f.txHex) + const script = bscript.fromASM(f.script) - assert.deepEqual(tx.getHash().toString('hex'), f.hash) + assert.strictEqual(tx.hashForWitnessV0(f.inIndex, script, f.value, f.type).toString('hex'), f.hash) }) }) }) - // TODO: - // hashForSignature: [Function], + describe('setWitness', () => { + it('only accepts a a witness stack (Array of Buffers)', () => { + assert.throws(() => { + (new Transaction()).setWitness(0, 'foobar') + }, /Expected property "1" of type \[Buffer], got String "foobar"/) + }) + }) }) diff --git a/test/transaction_builder.js b/test/transaction_builder.js index 350011be2..6374161c7 100644 --- a/test/transaction_builder.js +++ b/test/transaction_builder.js @@ -1,270 +1,789 @@ -/* global describe, it, beforeEach */ - -var assert = require('assert') +const { describe, it, beforeEach } = require('mocha') +const assert = require('assert') +const baddress = require('../src/address') +const bscript = require('../src/script') +const payments = require('../src/payments') + +const ECPair = require('../src/ecpair') +const Transaction = require('..').Transaction +const TransactionBuilder = require('..').TransactionBuilder +const NETWORKS = require('../src/networks') + +console.warn = () => {} // Silence the Deprecation Warning + +const fixtures = require('./fixtures/transaction_builder') + +function constructSign (f, txb, useOldSignArgs) { + const network = NETWORKS[f.network] + const stages = f.stages && f.stages.concat() + + f.inputs.forEach((input, index) => { + if (!input.signs) return + input.signs.forEach(sign => { + const keyPair = ECPair.fromWIF(sign.keyPair, network) + let redeemScript + let witnessScript + let witnessValue + + if (sign.redeemScript) { + redeemScript = bscript.fromASM(sign.redeemScript) + } + + if (sign.value) { + witnessValue = sign.value + } + + if (sign.witnessScript) { + witnessScript = bscript.fromASM(sign.witnessScript) + } + + if (useOldSignArgs) { + // DEPRECATED: v6 will remove this interface + txb.sign(index, keyPair, redeemScript, sign.hashType, witnessValue, witnessScript) + } else { + // prevOutScriptType is required, see /ts_src/transaction_builder.ts + // The PREVOUT_TYPES constant is a Set with all possible values. + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: index, + keyPair, + redeemScript, + hashType: sign.hashType, + witnessValue, + witnessScript, + }) + } -var Address = require('../src/address') -var BigInteger = require('bigi') -var ECKey = require('../src/eckey') -var Script = require('../src/script') -var Transaction = require('../src/transaction') -var TransactionBuilder = require('../src/transaction_builder') + if (sign.stage) { + const tx = txb.buildIncomplete() + assert.strictEqual(tx.toHex(), stages.shift()) + txb = TransactionBuilder.fromTransaction(tx, network) + } + }) + }) -var fixtures = require('./fixtures/transaction_builder') + return txb +} -function construct (txb, f, sign) { - f.inputs.forEach(function (input) { - var prevTxScript +function construct (f, dontSign, useOldSignArgs) { + 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 = Script.fromASM(input.prevTxScript) + prevTxScript = bscript.fromASM(input.prevTxScript) } - txb.addInput(input.txId, input.vout, input.sequence, prevTxScript) + txb.addInput(prevTx, input.vout, input.sequence, prevTxScript) }) - f.outputs.forEach(function (output) { - var script = Script.fromASM(output.script) - - txb.addOutput(script, output.value) + f.outputs.forEach(output => { + if (output.address) { + txb.addOutput(output.address, output.value) + } else { + txb.addOutput(bscript.fromASM(output.script), output.value) + } }) - if (sign === undefined || sign) { - f.inputs.forEach(function (input, index) { - input.signs.forEach(function (sign) { - var privKey = ECKey.fromWIF(sign.privKey) - var redeemScript + if (dontSign) return txb + return constructSign(f, txb, useOldSignArgs) +} + +// TODO: Remove loop in v6 +for (const useOldSignArgs of [ false, true ]) { + // Search for "useOldSignArgs" + // to find the second part of this console.warn replace + let consoleWarn; + if (useOldSignArgs) { + consoleWarn = console.warn; + // Silence console.warn during these tests + console.warn = () => undefined; + } + describe(`TransactionBuilder: useOldSignArgs === ${useOldSignArgs}`, () => { + // constants + const keyPair = ECPair.fromPrivateKey(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')) + const scripts = [ + '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', + '1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP' + ].map(x => { + return baddress.toOutputScript(x) + }) + const txHash = Buffer.from('0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2', 'hex') - if (sign.redeemScript) { - redeemScript = Script.fromASM(sign.redeemScript) - } + describe('fromTransaction', () => { + fixtures.valid.build.forEach(f => { + it('returns TransactionBuilder, with ' + f.description, () => { + const network = NETWORKS[f.network || 'bitcoin'] + + const tx = Transaction.fromHex(f.txHex) + const txb = TransactionBuilder.fromTransaction(tx, network) + const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() - txb.sign(index, privKey, redeemScript, sign.hashType) + assert.strictEqual(txAfter.toHex(), f.txHex) + assert.strictEqual(txb.network, network) + }) }) - }) - } - // FIXME: add support for locktime/version in TransactionBuilder API - if (f.version !== undefined) { - txb.tx.version = f.version - } + fixtures.valid.fromTransaction.forEach(f => { + it('returns TransactionBuilder, with ' + f.description, () => { + const tx = new Transaction() - if (f.locktime !== undefined) { - txb.tx.locktime = f.locktime - } -} + f.inputs.forEach(input => { + const txHash2 = Buffer.from(input.txId, 'hex').reverse() -describe('TransactionBuilder', function () { - var privAddress, privScript - var prevTx, prevTxHash - var privKey - var txb + tx.addInput(txHash2, input.vout, undefined, bscript.fromASM(input.scriptSig)) + }) - beforeEach(function () { - txb = new TransactionBuilder() + f.outputs.forEach(output => { + tx.addOutput(bscript.fromASM(output.script), output.value) + }) - prevTx = new Transaction() - prevTx.addOutput(Address.fromBase58Check('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH').toOutputScript(), 0) - prevTx.addOutput(Address.fromBase58Check('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP').toOutputScript(), 1) - prevTxHash = prevTx.getHash() + const txb = TransactionBuilder.fromTransaction(tx) + const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() - privKey = new ECKey(BigInteger.ONE, false) - privAddress = privKey.pub.getAddress() - privScript = privAddress.toOutputScript() - }) + txAfter.ins.forEach((input, i) => { + assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter) + }) - describe('addInput', function () { - it('accepts a txHash, index [and sequence number]', function () { - var vin = txb.addInput(prevTxHash, 1, 54) - assert.equal(vin, 0) + txAfter.outs.forEach((output, i) => { + assert.strictEqual(bscript.toASM(output.script), f.outputs[i].script) + }) + }) + }) - var txIn = txb.tx.ins[0] - assert.equal(txIn.hash, prevTxHash) - assert.equal(txIn.index, 1) - assert.equal(txIn.sequence, 54) - assert.equal(txb.inputs[0].prevOutScript, undefined) - }) + 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) - it('accepts a txHash, index [, sequence number and scriptPubKey]', function () { - var vin = txb.addInput(prevTxHash, 1, 54, prevTx.outs[1].script) - assert.equal(vin, 0) + tx.ins.forEach((input, i) => { + assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSig) + }) - var txIn = txb.tx.ins[0] - assert.equal(txIn.hash, prevTxHash) - assert.equal(txIn.index, 1) - assert.equal(txIn.sequence, 54) - assert.equal(txb.inputs[0].prevOutScript, prevTx.outs[1].script) - }) + constructSign(f, txb, useOldSignArgs) + const txAfter = f.incomplete ? txb.buildIncomplete() : txb.build() - it('accepts a prevTx, index [and sequence number]', function () { - var vin = txb.addInput(prevTx, 1, 54) - assert.equal(vin, 0) + txAfter.ins.forEach((input, i) => { + assert.strictEqual(bscript.toASM(input.script), f.inputs[i].scriptSigAfter) + }) - var txIn = txb.tx.ins[0] - assert.deepEqual(txIn.hash, prevTxHash) - assert.equal(txIn.index, 1) - assert.equal(txIn.sequence, 54) - assert.equal(txb.inputs[0].prevOutScript, prevTx.outs[1].script) - }) + assert.strictEqual(txAfter.toHex(), f.txHexAfter) + }) + }) - it('returns the input index', function () { - assert.equal(txb.addInput(prevTxHash, 0), 0) - assert.equal(txb.addInput(prevTxHash, 1), 1) - }) + it('classifies transaction inputs', () => { + const tx = Transaction.fromHex(fixtures.valid.classification.hex) + const txb = TransactionBuilder.fromTransaction(tx) - it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function () { - txb.addInput(prevTxHash, 0) - txb.sign(0, privKey) + txb.__INPUTS.forEach(i => { + assert.strictEqual(i.prevOutType, 'scripthash') + assert.strictEqual(i.redeemScriptType, 'multisig') + }) + }) + + fixtures.invalid.fromTransaction.forEach(f => { + it('throws ' + f.exception, () => { + const tx = Transaction.fromHex(f.txHex) - assert.throws(function () { - txb.addInput(prevTxHash, 0) - }, /No, this would invalidate signatures/) + assert.throws(() => { + TransactionBuilder.fromTransaction(tx) + }, new RegExp(f.exception)) + }) + }) }) - }) - describe('addOutput', function () { - it('accepts an address string and value', function () { - var vout = txb.addOutput(privAddress.toBase58Check(), 1000) - assert.equal(vout, 0) + describe('addInput', () => { + let txb + beforeEach(() => { + txb = new TransactionBuilder() + }) + + it('accepts a txHash, index [and sequence number]', () => { + const vin = txb.addInput(txHash, 1, 54) + assert.strictEqual(vin, 0) - var txout = txb.tx.outs[0] - assert.deepEqual(txout.script, privScript) - assert.equal(txout.value, 1000) - }) + const txIn = txb.__TX.ins[0] + assert.strictEqual(txIn.hash, txHash) + assert.strictEqual(txIn.index, 1) + assert.strictEqual(txIn.sequence, 54) + assert.strictEqual(txb.__INPUTS[0].prevOutScript, undefined) + }) + + it('accepts a txHash, index [, sequence number and scriptPubKey]', () => { + const vin = txb.addInput(txHash, 1, 54, scripts[1]) + assert.strictEqual(vin, 0) + + const txIn = txb.__TX.ins[0] + assert.strictEqual(txIn.hash, txHash) + assert.strictEqual(txIn.index, 1) + assert.strictEqual(txIn.sequence, 54) + assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1]) + }) + + it('accepts a prevTx, index [and sequence number]', () => { + const prevTx = new Transaction() + prevTx.addOutput(scripts[0], 0) + prevTx.addOutput(scripts[1], 1) - it('accepts an Address object and value', function () { - var vout = txb.addOutput(privAddress, 1000) - assert.equal(vout, 0) + const vin = txb.addInput(prevTx, 1, 54) + assert.strictEqual(vin, 0) + + const txIn = txb.__TX.ins[0] + assert.deepStrictEqual(txIn.hash, prevTx.getHash()) + assert.strictEqual(txIn.index, 1) + assert.strictEqual(txIn.sequence, 54) + assert.strictEqual(txb.__INPUTS[0].prevOutScript, scripts[1]) + }) + + it('returns the input index', () => { + assert.strictEqual(txb.addInput(txHash, 0), 0) + assert.strictEqual(txb.addInput(txHash, 1), 1) + }) - var txout = txb.tx.outs[0] - assert.deepEqual(txout.script, privScript) - assert.equal(txout.value, 1000) + it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 1000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + + assert.throws(() => { + txb.addInput(txHash, 0) + }, /No, this would invalidate signatures/) + }) }) - it('accepts a ScriptPubKey and value', function () { - var vout = txb.addOutput(privScript, 1000) - assert.equal(vout, 0) + describe('addOutput', () => { + let txb + beforeEach(() => { + txb = new TransactionBuilder() + }) + + it('accepts an address string and value', () => { + const { address } = payments.p2pkh({ pubkey: keyPair.publicKey }) + const vout = txb.addOutput(address, 1000) + assert.strictEqual(vout, 0) + + const txout = txb.__TX.outs[0] + assert.deepStrictEqual(txout.script, scripts[0]) + assert.strictEqual(txout.value, 1000) + }) + + it('accepts a ScriptPubKey and value', () => { + const vout = txb.addOutput(scripts[0], 1000) + assert.strictEqual(vout, 0) + + const txout = txb.__TX.outs[0] + assert.deepStrictEqual(txout.script, scripts[0]) + assert.strictEqual(txout.value, 1000) + }) + + it('throws if address is of the wrong network', () => { + assert.throws(() => { + txb.addOutput('2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9', 1000) + }, /2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9 has no matching Script/) + }) + + it('add second output after signed first input with SIGHASH_NONE', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 2000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_NONE, + }) + assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) + }) + + it('add first output after signed first input with SIGHASH_NONE', () => { + txb.addInput(txHash, 0) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_NONE, + }) + assert.strictEqual(txb.addOutput(scripts[0], 2000), 0) + }) + + it('add second output after signed first input with SIGHASH_SINGLE', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 2000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_SINGLE, + }) + assert.strictEqual(txb.addOutput(scripts[1], 9000), 1) + }) + + it('add first output after signed first input with SIGHASH_SINGLE', () => { + txb.addInput(txHash, 0) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: Transaction.SIGHASH_SINGLE, + }) + assert.throws(() => { + txb.addOutput(scripts[0], 2000) + }, /No, this would invalidate signatures/) + }) + + it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', () => { + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 2000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) - var txout = txb.tx.outs[0] - assert.deepEqual(txout.script, privScript) - assert.equal(txout.value, 1000) + assert.throws(() => { + txb.addOutput(scripts[1], 9000) + }, /No, this would invalidate signatures/) + }) }) - it('throws if SIGHASH_ALL has been used to sign any existing scriptSigs', function () { - txb.addInput(prevTxHash, 0) - txb.addOutput(privScript, 2000) - txb.sign(0, privKey) + describe('setLockTime', () => { + it('throws if if there exist any scriptSigs', () => { + const txb = new TransactionBuilder() + txb.addInput(txHash, 0) + txb.addOutput(scripts[0], 100) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) - assert.throws(function () { - txb.addOutput(privScript, 9000) - }, /No, this would invalidate signatures/) + assert.throws(() => { + txb.setLockTime(65535) + }, /No, this would invalidate signatures/) + }) }) - }) - describe('sign', function () { - fixtures.invalid.sign.forEach(function (f) { - it('throws on ' + f.exception + ' (' + f.description + ')', function () { - construct(txb, f, false) - - f.inputs.forEach(function (input, index) { - input.signs.forEach(function (sign) { - var privKey = ECKey.fromWIF(sign.privKey) - var redeemScript - - if (sign.redeemScript) { - redeemScript = Script.fromASM(sign.redeemScript) - } - - if (!sign.throws) { - txb.sign(index, privKey, redeemScript, sign.hashType) - } else { - assert.throws(function () { - txb.sign(index, privKey, redeemScript, sign.hashType) - }, new RegExp(f.exception)) - } + describe('sign', () => { + it('supports the alternative abstract interface { publicKey, sign }', () => { + const keyPair = { + publicKey: ECPair.makeRandom({ rng: () => { return Buffer.alloc(32, 1) } }).publicKey, + sign: hash => { return Buffer.alloc(64, 0x5f) } + } + + const txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a47304402205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f02205f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078fffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') + }) + + it('supports low R signature signing', () => { + let txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 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({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + // low R + assert.strictEqual(txb.build().toHex(), '0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010000006a473044022012a601efa8756ebe83e9ac7a7db061c3147e3b49d8be67685799fe51a4c8c62f02204d568d301d5ce14af390d566d4fd50e7b8ee48e71ec67786c029e721194dae3601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914000000000000000000000000000000000000000088ac00000000') + }) + + it('fails when missing required arguments', () => { + let txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 1) + txb.addOutput('1111111111111111111114oLvT2', 100000) + assert.throws(() => { + txb.sign() + }, /TransactionBuilder sign first arg must be TxbSignArg or number/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 1, + keyPair, + }) + }, /No input at index: 1/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + keyPair, + }) + }, /sign must include vin parameter as Number \(input index\)/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair: {}, + }) + }, /sign must include keyPair parameter as Signer interface/) + assert.throws(() => { + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + hashType: 'string', + }) + }, /sign hashType parameter must be a number/) + if (useOldSignArgs) { + assert.throws(() => { + txb.sign(0) + }, /sign requires keypair/) + } + }) + + fixtures.invalid.sign.forEach(f => { + it('throws ' + f.exception + (f.description ? ' (' + f.description + ')' : ''), () => { + const txb = construct(f, true) + + let threw = false + f.inputs.forEach((input, index) => { + input.signs.forEach(sign => { + const keyPairNetwork = NETWORKS[sign.network || f.network] + const keyPair2 = ECPair.fromWIF(sign.keyPair, keyPairNetwork) + let redeemScript + let witnessScript + + if (sign.redeemScript) { + redeemScript = bscript.fromASM(sign.redeemScript) + } + + if (sign.witnessScript) { + witnessScript = bscript.fromASM(sign.witnessScript) + } + + if (sign.throws) { + assert.throws(() => { + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: index, + keyPair: keyPair2, + redeemScript, + hashType: sign.hashType, + witnessValue: sign.value, + witnessScript, + }) + }, new RegExp(f.exception)) + threw = true + } else { + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: index, + keyPair: keyPair2, + redeemScript, + hashType: sign.hashType, + witnessValue: sign.value, + witnessScript, + }) + } + }) }) + + assert.strictEqual(threw, true) }) }) }) - }) - describe('build', function () { - fixtures.valid.build.forEach(function (f) { - it('builds "' + f.description + '"', function () { - construct(txb, f) + describe('build', () => { + fixtures.valid.build.forEach(f => { + it('builds "' + f.description + '"', () => { + const txb = construct(f, undefined, useOldSignArgs) + const tx = f.incomplete ? txb.buildIncomplete() : txb.build() - var tx = txb.build() - assert.equal(tx.toHex(), f.txHex) + assert.strictEqual(tx.toHex(), f.txHex) + }) }) - }) - fixtures.invalid.build.forEach(function (f) { - describe('for ' + (f.description || f.exception), function () { - beforeEach(function () { - if (f.txHex) { - var tx = Transaction.fromHex(f.txHex) - txb = TransactionBuilder.fromTransaction(tx) + // TODO: remove duplicate test code + fixtures.invalid.build.forEach(f => { + describe('for ' + (f.description || f.exception), () => { + it('throws ' + f.exception, () => { + assert.throws(() => { + let txb + if (f.txHex) { + txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) + } else { + txb = construct(f, undefined, useOldSignArgs) + } + + txb.build() + }, new RegExp(f.exception)) + }) + + // if throws on incomplete too, enforce that + if (f.incomplete) { + it('throws ' + f.exception, () => { + assert.throws(() => { + let txb + if (f.txHex) { + txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) + } else { + txb = construct(f, undefined, useOldSignArgs) + } + + txb.buildIncomplete() + }, new RegExp(f.exception)) + }) } else { - construct(txb, f) + it('does not throw if buildIncomplete', () => { + let txb + if (f.txHex) { + txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) + } else { + txb = construct(f, undefined, useOldSignArgs) + } + + txb.buildIncomplete() + }) } }) + }) - it('throws', function () { - assert.throws(function () { - txb.build() - }, new RegExp(f.exception)) - }) + it('for incomplete with 0 signatures', () => { + const randomTxData = '0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000' + const randomAddress = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH' - if (f.alwaysThrows) return - it("doesn't throw if building incomplete", function () { - txb.buildIncomplete() - }) + const randomTx = Transaction.fromHex(randomTxData) + let tx = new TransactionBuilder() + tx.addInput(randomTx, 0) + tx.addOutput(randomAddress, 1000) + tx = tx.buildIncomplete() + assert(tx) }) - }) - }) - describe('fromTransaction', function () { - fixtures.valid.build.forEach(function (f) { - it('builds the correct TransactionBuilder for ' + f.description, function () { - var tx = Transaction.fromHex(f.txHex) - var txb = TransactionBuilder.fromTransaction(tx) + it('for incomplete P2SH with 0 signatures', () => { + const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000017a91471a8ec07ff69c6c4fee489184c462a9b1b9237488700000000', 'hex') // arbitrary P2SH input + const inpTx = Transaction.fromBuffer(inp) + + const txb = new TransactionBuilder(NETWORKS.testnet) + txb.addInput(inpTx, 0) + txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output + + txb.buildIncomplete() + }) + + it('for incomplete P2WPKH with 0 signatures', () => { + const inp = Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a8040000001600141a15805e1f4040c9f68ccc887fca2e63547d794b00000000', 'hex') + const inpTx = Transaction.fromBuffer(inp) + + const txb = new TransactionBuilder(NETWORKS.testnet) + txb.addInput(inpTx, 0) + txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output - assert.equal(txb.build().toHex(), f.txHex) + txb.buildIncomplete() + }) + + it('for incomplete P2WSH with 0 signatures', () => { + const inpTx = Transaction.fromBuffer(Buffer.from('010000000173120703f67318aef51f7251272a6816d3f7523bb25e34b136d80be959391c100000000000ffffffff0100c817a80400000022002072df76fcc0b231b94bdf7d8c25d7eef4716597818d211e19ade7813bff7a250200000000', 'hex')) + + const txb = new TransactionBuilder(NETWORKS.testnet) + txb.addInput(inpTx, 0) + txb.addOutput('2NAkqp5xffoomp5RLBcakuGpZ12GU4twdz4', 1e8) // arbitrary output + + txb.buildIncomplete() }) }) - fixtures.invalid.fromTransaction.forEach(function (f) { - it('throws on ' + f.exception, function () { - var tx = Transaction.fromHex(f.txHex) + describe('multisig', () => { + fixtures.valid.multisig.forEach(f => { + it(f.description, () => { + const network = NETWORKS[f.network] + let txb = construct(f, true) + let tx + + f.inputs.forEach((input, i) => { + const redeemScript = bscript.fromASM(input.redeemScript) + + input.signs.forEach(sign => { + // rebuild the transaction each-time after the first + if (tx) { + // manually override the scriptSig? + if (sign.scriptSigBefore) { + tx.ins[i].script = bscript.fromASM(sign.scriptSigBefore) + } + + // rebuild + txb = TransactionBuilder.fromTransaction(tx, network) + } + + const keyPair2 = ECPair.fromWIF(sign.keyPair, network) + txb.sign({ + prevOutScriptType: sign.prevOutScriptType, + vin: i, + keyPair: keyPair2, + redeemScript, + hashType: sign.hashType, + }) + + // update the tx + tx = txb.buildIncomplete() + + // now verify the serialized scriptSig is as expected + assert.strictEqual(bscript.toASM(tx.ins[i].script), sign.scriptSig) + }) + }) - assert.throws(function () { - TransactionBuilder.fromTransaction(tx) - }, new RegExp(f.exception)) + tx = txb.build() + assert.strictEqual(tx.toHex(), f.txHex) + }) }) }) - it('works for the out-of-order P2SH multisig case', function () { - var privKeys = [ - '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT', - '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx' - ].map(ECKey.fromWIF) - var redeemScript = Script.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG') + describe('various edge case', () => { + const network = NETWORKS.testnet + + it('should warn of high fee for segwit transaction based on VSize, not Size', () => { + const rawtx = '01000000000104fdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a' + + '1df90000000000fffffffffdaac89627208b4733484ca56bc291f4cf4fa8d7c5f29893c52b46788a0a1df9' + + '0100000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca40000' + + '000000ffffffffa2ef7aaab316a3e5b5b0a78d1d35c774b95a079f9f0c762277a49caf1f26bca401000000' + + '00ffffffff0100040000000000001976a914cf307285359ab7ef6a2daa0522c7908ddf5fe7a988ac024730' + + '440220113324438816338406841775e079b04c50d04f241da652a4035b1017ea1ecf5502205802191eb49c' + + '54bf2a5667aea72e51c3ca92085efc60f12d1ebda3a64aff343201210283409659355b6d1cc3c32decd5d5' + + '61abaac86c37a353b52895a5e6c196d6f44802483045022100dc2892874e6d8708e3f5a058c5c9263cdf03' + + '969492270f89ee4933caf6daf8bb0220391dfe61a002709b63b9d64422d3db09b727839d1287e10a128a5d' + + 'b52a82309301210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f448024830' + + '450221009e3ed3a6ae93a018f443257b43e47b55cf7f7f3547d8807178072234686b22160220576121cfe6' + + '77c7eddf5575ea0a7c926247df6eca723c4f85df306e8bc08ea2df01210283409659355b6d1cc3c32decd5' + + 'd561abaac86c37a353b52895a5e6c196d6f44802473044022007be81ffd4297441ab10e740fc9bab9545a2' + + '194a565cd6aa4cc38b8eaffa343402201c5b4b61d73fa38e49c1ee68cc0e6dfd2f5dae453dd86eb142e87a' + + '0bafb1bc8401210283409659355b6d1cc3c32decd5d561abaac86c37a353b52895a5e6c196d6f44800000000' + const txb = TransactionBuilder.fromTransaction(Transaction.fromHex(rawtx)) + txb.__INPUTS[0].value = 241530 + txb.__INPUTS[1].value = 241530 + txb.__INPUTS[2].value = 248920 + txb.__INPUTS[3].value = 248920 + + assert.throws(() => { + txb.build() + }, new RegExp('Transaction has absurd fees')) + }) + + it('should classify witness inputs with witness = true during multisigning', () => { + const keyPair = ECPair.fromWIF('cRAwuVuVSBZMPu7hdrYvMCZ8eevzmkExjFbaBLhqnDdrezxN3nTS', network) + const witnessScript = Buffer.from('522102bbbd6eb01efcbe4bd9664b886f26f69de5afcb2e479d72596c8bf21929e352e22102d9c3f7180ef13ec5267723c9c2ffab56a4215241f837502ea8977c8532b9ea1952ae', 'hex') + const redeemScript = Buffer.from('002024376a0a9abab599d0e028248d48ebe817bc899efcffa1cd2984d67289daf5af', 'hex') + const scriptPubKey = Buffer.from('a914b64f1a3eacc1c8515592a6f10457e8ff90e4db6a87', 'hex') + const txb = new TransactionBuilder(network) + txb.setVersion(1) + txb.addInput('a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529', 1, 0xffffffff, scriptPubKey) + txb.addOutput(scriptPubKey, 99000) + txb.sign({ + prevOutScriptType: 'p2sh-p2wsh-p2ms', + vin: 0, + keyPair, + redeemScript, + witnessValue: 100000, + witnessScript, + }) - txb.addInput('4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf', 0) - txb.addOutput('1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', 10000) - txb.sign(0, privKeys[0], redeemScript) + // 2-of-2 signed only once + const tx = txb.buildIncomplete() - var tx = txb.buildIncomplete() + // Only input is segwit, so txid should be accurate with the final tx + assert.strictEqual(tx.getId(), 'f15d0a65b21b4471405b21a099f8b18e1ae4d46d55efbd0f4766cf11ad6cb821') + + const txHex = tx.toHex() + TransactionBuilder.fromTransaction(Transaction.fromHex(txHex)) + }) - // in another galaxy... - // ... far, far away - var txb2 = TransactionBuilder.fromTransaction(tx) + it('should handle badly pre-filled OP_0s', () => { + // OP_0 is used where a signature is missing + const redeemScripSig = bscript.fromASM('OP_0 OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae') + const redeemScript = bscript.fromASM('OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a 04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672 OP_3 OP_CHECKMULTISIG') - // [you should] verify that Transaction is what you want... - // ... then sign it - txb2.sign(0, privKeys[1], redeemScript) - var tx2 = txb2.build() + const tx = new Transaction() + tx.addInput(Buffer.from('cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f07149', 'hex'), 0, undefined, redeemScripSig) + tx.addOutput(Buffer.from('76a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac', 'hex'), 1000) - assert.equal(tx2.toHex(), '0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd1c01004830450221009c92c1ae1767ac04e424da7f6db045d979b08cde86b1ddba48621d59a109d818022004f5bb21ad72255177270abaeb2d7940ac18f1e5ca1f53db4f3fd1045647a8a8014830450221009418caa5bc18da87b188a180125c0cf06dce6092f75b2d3c01a29493466800fd02206ead65e7ca6e0f17eefe6f78457c084eab59af7c9882be1437de2e7116358eb9014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000') + // now import the Transaction + const txb = TransactionBuilder.fromTransaction(tx, NETWORKS.testnet) + + const keyPair2 = ECPair.fromWIF('91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', network) + txb.sign({ + prevOutScriptType: 'p2sh-p2ms', + vin: 0, + keyPair: keyPair2, + redeemScript, + }) + + const tx2 = txb.build() + assert.strictEqual(tx2.getId(), 'eab59618a564e361adef6d918bd792903c3d41bcf1220137364fb847880467f9') + assert.strictEqual(bscript.toASM(tx2.ins[0].script), 'OP_0 3045022100daf0f4f3339d9fbab42b098045c1e4958ee3b308f4ae17be80b63808558d0adb02202f07e3d1f79dc8da285ae0d7f68083d769c11f5621ebd9691d6b48c0d4283d7d01 3045022100a346c61738304eac5e7702188764d19cdf68f4466196729db096d6c87ce18cdd022018c0e8ad03054b0e7e235cda6bedecf35881d7aa7d94ff425a8ace7220f38af001 52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a4104f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e67253ae') + }) + + it('should not classify blank scripts as nonstandard', () => { + let txb = new TransactionBuilder() + txb.setVersion(1) + txb.addInput('aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31', 0) + + const incomplete = txb.buildIncomplete().toHex() + const keyPair = ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy') + + // sign, as expected + txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + const txId = txb.build().getId() + assert.strictEqual(txId, '54f097315acbaedb92a95455da3368eb45981cdae5ffbc387a9afc872c0f29b3') + + // and, repeat + txb = TransactionBuilder.fromTransaction(Transaction.fromHex(incomplete)) + txb.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000) + txb.sign({ + prevOutScriptType: 'p2pkh', + vin: 0, + keyPair, + }) + const txId2 = txb.build().getId() + assert.strictEqual(txId, txId2) + // TODO: Remove me in v6 + if (useOldSignArgs) { + console.warn = consoleWarn; + } + }) }) }) -}) +} diff --git a/test/types.js b/test/types.js new file mode 100644 index 000000000..8911ca15d --- /dev/null +++ b/test/types.js @@ -0,0 +1,51 @@ +const { describe, it } = require('mocha') +const assert = require('assert') +const types = require('../src/types') +const typeforce = require('typeforce') + +describe('types', () => { + describe('Buffer Hash160/Hash256', () => { + const buffer20byte = Buffer.alloc(20) + const buffer32byte = Buffer.alloc(32) + + it('return true for valid size', () => { + assert(types.Hash160bit(buffer20byte)) + assert(types.Hash256bit(buffer32byte)) + }) + + it('return true for oneOf', () => { + assert.doesNotThrow(() => { + typeforce(types.oneOf(types.Hash160bit, types.Hash256bit), buffer32byte) + }) + + assert.doesNotThrow(() => { + typeforce(types.oneOf(types.Hash256bit, types.Hash160bit), buffer32byte) + }) + }) + + it('throws for invalid size', () => { + assert.throws(() => { + types.Hash160bit(buffer32byte) + }, /Expected Buffer\(Length: 20\), got Buffer\(Length: 32\)/) + + assert.throws(() => { + types.Hash256bit(buffer20byte) + }, /Expected Buffer\(Length: 32\), got Buffer\(Length: 20\)/) + }) + }) + + describe('Satoshi', () => { + [ + { value: -1, result: false }, + { value: 0, result: true }, + { value: 1, result: true }, + { value: 20999999 * 1e8, result: true }, + { value: 21000000 * 1e8, result: true }, + { value: 21000001 * 1e8, result: false } + ].forEach(f => { + it('returns ' + f.result + ' for valid for ' + f.value, () => { + assert.strictEqual(types.Satoshi(f.value), f.result) + }) + }) + }) +}) diff --git a/ts_src/address.ts b/ts_src/address.ts new file mode 100644 index 000000000..ad791ad0f --- /dev/null +++ b/ts_src/address.ts @@ -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'); +} diff --git a/ts_src/block.ts b/ts_src/block.ts new file mode 100644 index 000000000..820c075d6 --- /dev/null +++ b/ts_src/block.ts @@ -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, + ), + ) + ); +} diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts new file mode 100644 index 000000000..adb606090 --- /dev/null +++ b/ts_src/bufferutils.ts @@ -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; +} diff --git a/ts_src/classify.ts b/ts_src/classify.ts new file mode 100644 index 000000000..a7ec68ca9 --- /dev/null +++ b/ts_src/classify.ts @@ -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, +}; diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts new file mode 100644 index 000000000..1cb5a6936 --- /dev/null +++ b/ts_src/crypto.ts @@ -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)); +} diff --git a/ts_src/ecpair.ts b/ts_src/ecpair.ts new file mode 100644 index 000000000..90528661b --- /dev/null +++ b/ts_src/ecpair.ts @@ -0,0 +1,161 @@ +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 Signer { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Buffer; + getPublicKey?(): Buffer; +} + +export interface SignerAsync { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Promise; + getPublicKey?(): Buffer; +} + +export interface ECPairInterface extends Signer { + compressed: boolean; + network: Network; + lowR: boolean; + privateKey?: Buffer; + toWIF(): string; + verify(hash: Buffer, signature: Buffer): boolean; +} + +class ECPair implements ECPairInterface { + compressed: boolean; + network: Network; + lowR: boolean; + + constructor( + private __D?: Buffer, + private __Q?: Buffer, + options?: ECPairOptions, + ) { + this.lowR = false; + 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): Buffer { + if (!this.__D) throw new Error('Missing private key'); + if (lowR === undefined) lowR = this.lowR; + 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 }; diff --git a/ts_src/index.ts b/ts_src/index.ts new file mode 100644 index 000000000..58c39c7be --- /dev/null +++ b/ts_src/index.ts @@ -0,0 +1,22 @@ +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 { Psbt } from './psbt'; +export { OPS as opcodes } from './script'; +export { Transaction } from './transaction'; +export { TransactionBuilder } from './transaction_builder'; + +export { BIP32Interface } from 'bip32'; +export { ECPairInterface, Signer, SignerAsync } from './ecpair'; +export { Network } from './networks'; +export { Payment, PaymentOpts, Stack, StackElement } from './payments'; +export { OpCode } from './script'; +export { Input as TxInput, Output as TxOutput } from './transaction'; diff --git a/ts_src/networks.ts b/ts_src/networks.ts new file mode 100644 index 000000000..e66b08c30 --- /dev/null +++ b/ts_src/networks.ts @@ -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, +}; diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts new file mode 100644 index 000000000..c54e27925 --- /dev/null +++ b/ts_src/payments/embed.ts @@ -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 = { name: 'embed', 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); +} diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts new file mode 100644 index 000000000..c48a6f105 --- /dev/null +++ b/ts_src/payments/index.ts @@ -0,0 +1,42 @@ +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 { + name?: string; + 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 diff --git a/ts_src/payments/lazy.ts b/ts_src/payments/lazy.ts new file mode 100644 index 000000000..1df181fd9 --- /dev/null +++ b/ts_src/payments/lazy.ts @@ -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(f: () => T): () => T { + let _value: T; + return (): T => { + if (_value !== undefined) return _value; + _value = f(); + return _value; + }; +} diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts new file mode 100644 index 000000000..7cd6f1059 --- /dev/null +++ b/ts_src/payments/p2ms.ts @@ -0,0 +1,162 @@ +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { Payment, PaymentOpts, Stack } from './index'; +import * as lazy from './lazy'; +const OPS = bscript.OPS; +const typef = require('typeforce'); +const ecc = require('tiny-secp256k1'); + +const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 + +function stacksEqual(a: Buffer[], b: Buffer[]): boolean { + if (a.length !== b.length) return false; + + return a.every((x, i) => { + return x.equals(b[i]); + }); +} + +// input: OP_0 [signatures ...] +// output: m [pubKeys ...] n OP_CHECKMULTISIG +export function p2ms(a: Payment, opts?: PaymentOpts): Payment { + if ( + !a.input && + !a.output && + !(a.pubkeys && a.m !== undefined) && + !a.signatures + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + function isAcceptableSignature(x: Buffer | number): boolean { + return ( + bscript.isCanonicalScriptSignature(x as Buffer) || + (opts!.allowIncomplete && (x as number) === OPS.OP_0) !== undefined + ); + } + + typef( + { + network: typef.maybe(typef.Object), + m: typef.maybe(typef.Number), + n: typef.maybe(typef.Number), + output: typef.maybe(typef.Buffer), + pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), + + signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), + input: typef.maybe(typef.Buffer), + }, + a, + ); + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { network }; + + let chunks: Stack = []; + let decoded = false; + function decode(output: Buffer | Stack): void { + if (decoded) return; + decoded = true; + chunks = bscript.decompile(output) as Stack; + o.m = (chunks[0] as number) - OP_INT_BASE; + o.n = (chunks[chunks.length - 2] as number) - OP_INT_BASE; + o.pubkeys = chunks.slice(1, -2) as Buffer[]; + } + + lazy.prop(o, 'output', () => { + if (!a.m) return; + if (!o.n) return; + if (!a.pubkeys) return; + return bscript.compile( + ([] as Stack).concat( + OP_INT_BASE + a.m, + a.pubkeys, + OP_INT_BASE + o.n, + OPS.OP_CHECKMULTISIG, + ), + ); + }); + lazy.prop(o, 'm', () => { + if (!o.output) return; + decode(o.output); + return o.m; + }); + lazy.prop(o, 'n', () => { + if (!o.pubkeys) return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', () => { + if (!a.output) return; + decode(a.output); + return o.pubkeys; + }); + lazy.prop(o, 'signatures', () => { + if (!a.input) return; + return bscript.decompile(a.input)!.slice(1); + }); + lazy.prop(o, 'input', () => { + if (!a.signatures) return; + return bscript.compile(([OPS.OP_0] as Stack).concat(a.signatures)); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + lazy.prop(o, 'name', () => { + if (!o.m || !o.n) return; + return `p2ms(${o.m} of ${o.n})`; + }); + + // extended validation + if (opts.validate) { + if (a.output) { + decode(a.output); + 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 (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) + throw new TypeError('Output is invalid'); + + if (o.m! <= 0 || o.n! > 16 || o.m! > o.n! || o.n !== chunks.length - 3) + 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.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys!)) + throw new TypeError('Pubkeys mismatch'); + } + + if (a.pubkeys) { + if (a.n !== undefined && a.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 (a.signatures) { + if (a.signatures.length < o.m!) + 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[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 (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); +} diff --git a/ts_src/payments/p2pk.ts b/ts_src/payments/p2pk.ts new file mode 100644 index 000000000..b4fdbc5b7 --- /dev/null +++ b/ts_src/payments/p2pk.ts @@ -0,0 +1,80 @@ +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { Payment, PaymentOpts, StackFunction } from './index'; +import * as lazy from './lazy'; +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); + +// input: {signature} +// output: {pubKey} OP_CHECKSIG +export function p2pk(a: Payment, opts?: PaymentOpts): Payment { + if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + typef( + { + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + pubkey: typef.maybe(ecc.isPoint), + + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer), + }, + a, + ); + + const _chunks = lazy.value(() => { + return bscript.decompile(a.input!); + }) as StackFunction; + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { name: 'p2pk', network }; + + lazy.prop(o, 'output', () => { + if (!a.pubkey) return; + return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.output) return; + return a.output.slice(1, -1); + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0] as Buffer; + }); + lazy.prop(o, 'input', () => { + if (!a.signature) return; + return bscript.compile([a.signature]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + + // extended validation + if (opts.validate) { + if (a.output) { + if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) + 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.input && !a.input.equals(o.input!)) + throw new TypeError('Signature mismatch'); + } + + if (a.input) { + if (_chunks().length !== 1) throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(o.signature!)) + throw new TypeError('Input has invalid signature'); + } + } + + return Object.assign(o, a); +} diff --git a/ts_src/payments/p2pkh.ts b/ts_src/payments/p2pkh.ts new file mode 100644 index 000000000..6503093a3 --- /dev/null +++ b/ts_src/payments/p2pkh.ts @@ -0,0 +1,147 @@ +import * as bcrypto from '../crypto'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { Payment, PaymentOpts, StackFunction } from './index'; +import * as lazy from './lazy'; +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); + +const bs58check = require('bs58check'); + +// input: {signature} {pubkey} +// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG +export function p2pkh(a: Payment, opts?: PaymentOpts): Payment { + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + typef( + { + network: typef.maybe(typef.Object), + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + output: typef.maybe(typef.BufferN(25)), + + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + input: typef.maybe(typef.Buffer), + }, + a, + ); + + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input!); + }) as StackFunction; + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { name: 'p2pkh', network }; + + lazy.prop(o, 'address', () => { + if (!o.hash) return; + + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(network.pubKeyHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + 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', () => { + if (!o.hash) return; + return bscript.compile([ + OPS.OP_DUP, + OPS.OP_HASH160, + o.hash, + OPS.OP_EQUALVERIFY, + OPS.OP_CHECKSIG, + ]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.input) return; + return _chunks()[1] as Buffer; + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0] as Buffer; + }); + lazy.prop(o, 'input', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return bscript.compile([a.signature, a.pubkey]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + + // extended validation + if (opts.validate) { + let hash: Buffer = Buffer.from([]); + if (a.address) { + if (_address().version !== network.pubKeyHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; + } + + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + + if (a.output) { + if ( + a.output.length !== 25 || + a.output[0] !== OPS.OP_DUP || + a.output[1] !== OPS.OP_HASH160 || + a.output[2] !== 0x14 || + a.output[23] !== OPS.OP_EQUALVERIFY || + a.output[24] !== OPS.OP_CHECKSIG + ) + throw new TypeError('Output is invalid'); + + 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) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + + if (a.input) { + const chunks = _chunks(); + if (chunks.length !== 2) throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(chunks[0] as Buffer)) + throw new TypeError('Input has invalid signature'); + if (!ecc.isPoint(chunks[1])) + throw new TypeError('Input has invalid pubkey'); + + if (a.signature && !a.signature.equals(chunks[0] as Buffer)) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(chunks[1] as Buffer)) + throw new TypeError('Pubkey mismatch'); + + const pkh = bcrypto.hash160(chunks[1] as Buffer); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + } + + return Object.assign(o, a); +} diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts new file mode 100644 index 000000000..8a097bd77 --- /dev/null +++ b/ts_src/payments/p2sh.ts @@ -0,0 +1,218 @@ +import * as bcrypto from '../crypto'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { + Payment, + PaymentFunction, + PaymentOpts, + Stack, + StackFunction, +} from './index'; +import * as lazy from './lazy'; +const typef = require('typeforce'); +const OPS = bscript.OPS; + +const bs58check = require('bs58check'); + +function stacksEqual(a: Buffer[], b: Buffer[]): boolean { + if (a.length !== b.length) return false; + + return a.every((x, i) => { + return x.equals(b[i]); + }); +} + +// input: [redeemScriptSig ...] {redeemScript} +// witness: +// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL +export function p2sh(a: Payment, opts?: PaymentOpts): Payment { + if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + 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), + output: typef.maybe(typef.Buffer), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK; + } + + const o: Payment = { network }; + + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input!); + }) as StackFunction; + const _redeem = lazy.value( + (): Payment => { + const chunks = _chunks(); + return { + network, + output: chunks[chunks.length - 1] as Buffer, + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [], + }; + }, + ) as PaymentFunction; + + // output dependents + lazy.prop(o, 'address', () => { + if (!o.hash) return; + + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(o.network!.scriptHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + // in order of least effort + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().hash; + if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); + }); + + // input dependents + lazy.prop(o, 'redeem', () => { + if (!a.input) return; + return _redeem(); + }); + lazy.prop(o, 'input', () => { + if (!a.redeem || !a.redeem.input || !a.redeem.output) return; + return bscript.compile( + ([] as Stack).concat( + bscript.decompile(a.redeem.input) as Stack, + a.redeem.output, + ), + ); + }); + lazy.prop(o, 'witness', () => { + if (o.redeem && o.redeem.witness) return o.redeem.witness; + if (o.input) return []; + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2sh']; + if (o.redeem !== undefined) nameParts.push(o.redeem.name!); + return nameParts.join('-'); + }); + + if (opts.validate) { + let hash: Buffer = Buffer.from([]); + if (a.address) { + if (_address().version !== network.scriptHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; + } + + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + + if (a.output) { + if ( + a.output.length !== 23 || + a.output[0] !== OPS.OP_HASH160 || + a.output[1] !== 0x14 || + a.output[22] !== OPS.OP_EQUAL + ) + throw new TypeError('Output is invalid'); + + 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 + const checkRedeem = (redeem: Payment): void => { + // is the redeem output empty/invalid? + if (redeem.output) { + const decompile = bscript.decompile(redeem.output); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output too short'); + + // match hash against other sources + const hash2 = bcrypto.hash160(redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + + if (redeem.input) { + const hasInput = redeem.input.length > 0; + const hasWitness = redeem.witness && redeem.witness.length > 0; + if (!hasInput && !hasWitness) throw new TypeError('Empty input'); + if (hasInput && hasWitness) + throw new TypeError('Input and witness provided'); + if (hasInput) { + const richunks = bscript.decompile(redeem.input) as Stack; + if (!bscript.isPushOnly(richunks)) + throw new TypeError('Non push-only scriptSig'); + } + } + }; + + if (a.input) { + const chunks = _chunks(); + if (!chunks || chunks.length < 1) throw new TypeError('Input too short'); + if (!Buffer.isBuffer(_redeem().output)) + throw new TypeError('Input is invalid'); + + checkRedeem(_redeem()); + } + + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + if (a.input) { + 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); + } + + if (a.witness) { + if ( + a.redeem && + a.redeem.witness && + !stacksEqual(a.redeem.witness, a.witness) + ) + throw new TypeError('Witness and redeem.witness mismatch'); + } + } + + return Object.assign(o, a); +} diff --git a/ts_src/payments/p2wpkh.ts b/ts_src/payments/p2wpkh.ts new file mode 100644 index 000000000..fc2a458d9 --- /dev/null +++ b/ts_src/payments/p2wpkh.ts @@ -0,0 +1,142 @@ +import * as bcrypto from '../crypto'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { Payment, PaymentOpts } from './index'; +import * as lazy from './lazy'; +const typef = require('typeforce'); +const OPS = bscript.OPS; +const ecc = require('tiny-secp256k1'); + +const bech32 = require('bech32'); + +const EMPTY_BUFFER = Buffer.alloc(0); + +// witness: {signature} {pubKey} +// input: <> +// output: OP_0 {pubKeyHash} +export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { + if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + typef( + { + address: typef.maybe(typef.String), + hash: typef.maybe(typef.BufferN(20)), + input: typef.maybe(typef.BufferN(0)), + network: typef.maybe(typef.Object), + output: typef.maybe(typef.BufferN(22)), + pubkey: typef.maybe(ecc.isPoint), + signature: typef.maybe(bscript.isCanonicalScriptSignature), + 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 { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + + const network = a.network || BITCOIN_NETWORK; + const o: Payment = { name: 'p2wpkh', network }; + + lazy.prop(o, 'address', () => { + if (!o.hash) return; + + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().data; + if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (!a.witness) return; + return a.witness[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.witness) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return [a.signature, a.pubkey]; + }); + + // extended validation + if (opts.validate) { + let hash: Buffer = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 20) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + + if (a.output) { + if ( + a.output.length !== 22 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x14 + ) + 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) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + + if (a.witness) { + 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 (!ecc.isPoint(a.witness[1])) + throw new TypeError('Witness has invalid pubkey'); + + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(a.witness[1])) + 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); +} diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts new file mode 100644 index 000000000..132dde496 --- /dev/null +++ b/ts_src/payments/p2wsh.ts @@ -0,0 +1,203 @@ +import * as bcrypto from '../crypto'; +import { bitcoin as BITCOIN_NETWORK } from '../networks'; +import * as bscript from '../script'; +import { Payment, PaymentOpts, StackFunction } from './index'; +import * as lazy from './lazy'; +const typef = require('typeforce'); +const OPS = bscript.OPS; + +const bech32 = require('bech32'); + +const EMPTY_BUFFER = Buffer.alloc(0); + +function stacksEqual(a: Buffer[], b: Buffer[]): boolean { + if (a.length !== b.length) return false; + + return a.every((x, i) => { + return x.equals(b[i]); + }); +} + +// input: <> +// witness: [redeemScriptSig ...] {redeemScript} +// output: OP_0 {sha256(redeemScript)} +export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { + if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + + 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), + output: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + }), + input: typef.maybe(typef.BufferN(0)), + 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 { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const _rchunks = lazy.value(() => { + return bscript.decompile(a.redeem!.input!); + }) as StackFunction; + + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK; + } + + const o: Payment = { network }; + + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network!.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'redeem', () => { + if (!a.witness) return; + return { + output: a.witness[a.witness.length - 1], + input: EMPTY_BUFFER, + witness: a.witness.slice(0, -1), + }; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + // transform redeem input to witness stack? + if ( + a.redeem && + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.output && + a.redeem.output.length > 0 + ) { + const stack = bscript.toStack(_rchunks()); + + // assign, and blank the existing input + o.redeem = Object.assign({ witness: stack }, a.redeem); + o.redeem.input = EMPTY_BUFFER; + return ([] as Buffer[]).concat(stack, a.redeem.output); + } + + if (!a.redeem) return; + if (!a.redeem.output) return; + if (!a.redeem.witness) return; + return ([] as Buffer[]).concat(a.redeem.witness, a.redeem.output); + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2wsh']; + if (o.redeem !== undefined) nameParts.push(o.redeem.name!); + return nameParts.join('-'); + }); + + // extended validation + if (opts.validate) { + let hash: Buffer = Buffer.from([]); + if (a.address) { + if (_address().prefix !== network.bech32) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + 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.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + + // is there two redeem sources? + if ( + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.witness && + a.redeem.witness.length > 0 + ) + throw new TypeError('Ambiguous witness source'); + + // is the redeem output non-empty? + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output)!.length === 0) + throw new TypeError('Redeem.output is invalid'); + + // match hash against other sources + const hash2 = bcrypto.sha256(a.redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + + if (a.redeem.input && !bscript.isPushOnly(_rchunks())) + 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) { + 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); +} diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts new file mode 100644 index 000000000..eb09155a7 --- /dev/null +++ b/ts_src/psbt.ts @@ -0,0 +1,1414 @@ +import { Psbt as PsbtBase } from 'bip174'; +import * as varuint from 'bip174/src/lib/converter/varint'; +import { + KeyValue, + PartialSig, + PsbtGlobalUpdate, + PsbtInput, + PsbtInputUpdate, + PsbtOutputUpdate, + Transaction as ITransaction, + TransactionFromBuffer, + TransactionInput, + TransactionOutput, +} from 'bip174/src/lib/interfaces'; +import { checkForInput } from 'bip174/src/lib/utils'; +import { toOutputScript } from './address'; +import { reverseBuffer } from './bufferutils'; +import { hash160 } from './crypto'; +import { + fromPublicKey as ecPairFromPublicKey, + Signer, + SignerAsync, +} from './ecpair'; +import { bitcoin as btcNetwork, Network } from './networks'; +import * as payments from './payments'; +import * as bscript from './script'; +import { Output, Transaction } from './transaction'; + +/** + * These are the default arguments for a Psbt instance. + */ +const DEFAULT_OPTS: PsbtOpts = { + /** + * A bitcoinjs Network object. This is only used if you pass an `address` + * parameter to addOutput. Otherwise it is not needed and can be left default. + */ + network: btcNetwork, + /** + * When extractTransaction is called, the fee rate is checked. + * THIS IS NOT TO BE RELIED ON. + * It is only here as a last ditch effort to prevent sending a 500 BTC fee etc. + */ + maximumFeeRate: 5000, // satoshi per byte +}; + +/** + * Psbt class can parse and generate a PSBT binary based off of the BIP174. + * There are 6 roles that this class fulfills. (Explained in BIP174) + * + * Creator: This can be done with `new Psbt()` + * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, + * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to + * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, + * `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)` + * addInput requires hash: Buffer | string; and index: number; as attributes + * and can also include any attributes that are used in updateInput method. + * addOutput requires script: Buffer; and value: number; and likewise can include + * data for updateOutput. + * For a list of what attributes should be what types. Check the bip174 library. + * Also, check the integration tests for some examples of usage. + * Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input + * information for your pubkey or pubkeyhash, and only sign inputs where it finds + * your info. Or you can explicitly sign a specific input with signInput and + * signInputAsync. For the async methods you can create a SignerAsync object + * and use something like a hardware wallet to sign with. (You must implement this) + * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` + * the psbt calling combine will always have precedence when a conflict occurs. + * Combine checks if the internal bitcoin transaction is the same, so be sure that + * all sequences, version, locktime, etc. are the same before combining. + * Input Finalizer: This role is fairly important. Not only does it need to construct + * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. + * Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()` + * Running any finalize method will delete any data in the input(s) that are no longer + * needed due to the finalized scripts containing the information. + * Transaction Extractor: This role will perform some checks before returning a + * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. + */ +export class Psbt { + static fromBase64(data: string, opts: PsbtOptsOptional = {}): Psbt { + const buffer = Buffer.from(data, 'base64'); + return this.fromBuffer(buffer, opts); + } + + static fromHex(data: string, opts: PsbtOptsOptional = {}): Psbt { + const buffer = Buffer.from(data, 'hex'); + return this.fromBuffer(buffer, opts); + } + + static fromBuffer(buffer: Buffer, opts: PsbtOptsOptional = {}): Psbt { + const psbtBase = PsbtBase.fromBuffer(buffer, transactionFromBuffer); + const psbt = new Psbt(opts, psbtBase); + checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); + return psbt; + } + + private __CACHE: PsbtCache; + private opts: PsbtOpts; + + constructor( + opts: PsbtOptsOptional = {}, + readonly data: PsbtBase = new PsbtBase(new PsbtTransaction()), + ) { + // set defaults + this.opts = Object.assign({}, DEFAULT_OPTS, opts); + this.__CACHE = { + __NON_WITNESS_UTXO_TX_CACHE: [], + __NON_WITNESS_UTXO_BUF_CACHE: [], + __TX_IN_CACHE: {}, + __TX: (this.data.globalMap.unsignedTx as PsbtTransaction).tx, + }; + if (this.data.inputs.length === 0) this.setVersion(2); + + // Make data hidden when enumerating + const dpew = ( + obj: any, + attr: string, + enumerable: boolean, + writable: boolean, + ): any => + Object.defineProperty(obj, attr, { + enumerable, + writable, + }); + dpew(this, '__CACHE', false, true); + dpew(this, 'opts', false, true); + } + + get inputCount(): number { + return this.data.inputs.length; + } + + combine(...those: Psbt[]): this { + this.data.combine(...those.map(o => o.data)); + return this; + } + + clone(): Psbt { + // TODO: more efficient cloning + const res = Psbt.fromBuffer(this.data.toBuffer()); + res.opts = JSON.parse(JSON.stringify(this.opts)); + return res; + } + + setMaximumFeeRate(satoshiPerByte: number): void { + check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw + this.opts.maximumFeeRate = satoshiPerByte; + } + + setVersion(version: number): this { + check32Bit(version); + checkInputsForPartialSig(this.data.inputs, 'setVersion'); + const c = this.__CACHE; + c.__TX.version = version; + c.__EXTRACTED_TX = undefined; + return this; + } + + setLocktime(locktime: number): this { + check32Bit(locktime); + checkInputsForPartialSig(this.data.inputs, 'setLocktime'); + const c = this.__CACHE; + c.__TX.locktime = locktime; + c.__EXTRACTED_TX = undefined; + return this; + } + + setInputSequence(inputIndex: number, sequence: number): this { + check32Bit(sequence); + checkInputsForPartialSig(this.data.inputs, 'setInputSequence'); + const c = this.__CACHE; + if (c.__TX.ins.length <= inputIndex) { + throw new Error('Input index too high'); + } + c.__TX.ins[inputIndex].sequence = sequence; + c.__EXTRACTED_TX = undefined; + return this; + } + + addInputs(inputDatas: TransactionInput[]): this { + inputDatas.forEach(inputData => this.addInput(inputData)); + return this; + } + + addInput(inputData: TransactionInput): this { + checkInputsForPartialSig(this.data.inputs, 'addInput'); + const c = this.__CACHE; + this.data.addInput(inputData); + const txIn = c.__TX.ins[c.__TX.ins.length - 1]; + checkTxInputCache(c, txIn); + + const inputIndex = this.data.inputs.length - 1; + const input = this.data.inputs[inputIndex]; + if (input.nonWitnessUtxo) { + addNonWitnessTxCache(this.__CACHE, input, inputIndex); + } + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + + addOutputs(outputDatas: TransactionOutput[]): this { + outputDatas.forEach(outputData => this.addOutput(outputData)); + return this; + } + + addOutput(outputData: TransactionOutput): this { + checkInputsForPartialSig(this.data.inputs, 'addOutput'); + const { address } = outputData as any; + if (typeof address === 'string') { + const { network } = this.opts; + const script = toOutputScript(address, network); + outputData = Object.assign(outputData, { script }); + } + const c = this.__CACHE; + this.data.addOutput(outputData); + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + + extractTransaction(disableFeeCheck?: boolean): Transaction { + if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized'); + const c = this.__CACHE; + if (!disableFeeCheck) { + checkFees(this, c, this.opts); + } + if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX; + const tx = c.__TX.clone(); + inputFinalizeGetAmts(this.data.inputs, tx, c, true); + return tx; + } + + getFeeRate(): number { + if (!this.data.inputs.every(isFinalized)) + throw new Error('PSBT must be finalized to calculate fee rate'); + const c = this.__CACHE; + if (c.__FEE_RATE) return c.__FEE_RATE; + let tx: Transaction; + let mustFinalize = true; + if (c.__EXTRACTED_TX) { + tx = c.__EXTRACTED_TX; + mustFinalize = false; + } else { + tx = c.__TX.clone(); + } + inputFinalizeGetAmts(this.data.inputs, tx, c, mustFinalize); + return c.__FEE_RATE!; + } + + finalizeAllInputs(): this { + checkForInput(this.data.inputs, 0); // making sure we have at least one + range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); + return this; + } + + finalizeInput(inputIndex: number): this { + const input = checkForInput(this.data.inputs, inputIndex); + const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( + inputIndex, + input, + this.__CACHE, + ); + if (!script) throw new Error(`No script found for input #${inputIndex}`); + + const scriptType = classifyScript(script); + if (!canFinalize(input, script, scriptType)) + throw new Error(`Can not finalize input #${inputIndex}`); + + checkPartialSigSighashes(input); + + const { finalScriptSig, finalScriptWitness } = getFinalScripts( + script, + scriptType, + input.partialSig!, + isSegwit, + isP2SH, + isP2WSH, + ); + + if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); + if (finalScriptWitness) + this.data.updateInput(inputIndex, { finalScriptWitness }); + if (!finalScriptSig && !finalScriptWitness) + throw new Error(`Unknown error finalizing input #${inputIndex}`); + + this.data.clearFinalizedInput(inputIndex); + return this; + } + + validateSignaturesOfAllInputs(): boolean { + checkForInput(this.data.inputs, 0); // making sure we have at least one + const results = range(this.data.inputs.length).map(idx => + this.validateSignaturesOfInput(idx), + ); + return results.reduce((final, res) => res === true && final, true); + } + + validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean { + const input = this.data.inputs[inputIndex]; + const partialSig = (input || {}).partialSig; + if (!input || !partialSig || partialSig.length < 1) + throw new Error('No signatures to validate'); + const mySigs = pubkey + ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) + : partialSig; + if (mySigs.length < 1) throw new Error('No signatures for this pubkey'); + const results: boolean[] = []; + let hashCache: Buffer; + let scriptCache: Buffer; + let sighashCache: number; + for (const pSig of mySigs) { + const sig = bscript.signature.decode(pSig.signature); + const { hash, script } = + sighashCache! !== sig.hashType + ? getHashForSig( + inputIndex, + Object.assign({}, input, { sighashType: sig.hashType }), + this.__CACHE, + ) + : { hash: hashCache!, script: scriptCache! }; + sighashCache = sig.hashType; + hashCache = hash; + scriptCache = script; + checkScriptForPubkey(pSig.pubkey, script, 'verify'); + const keypair = ecPairFromPublicKey(pSig.pubkey); + results.push(keypair.verify(hash, sig.signature)); + } + return results.every(res => res === true); + } + + signAllInputsHD( + hdKeyPair: HDSigner, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): this { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + + const results: boolean[] = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInputHD(i, hdKeyPair, sighashTypes); + results.push(true); + } catch (err) { + results.push(false); + } + } + if (results.every(v => v === false)) { + throw new Error('No inputs were signed'); + } + return this; + } + + signAllInputsHDAsync( + hdKeyPair: HDSigner | HDSignerAsync, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): Promise { + return new Promise( + (resolve, reject): any => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + + const results: boolean[] = []; + const promises: Array> = []; + for (const i of range(this.data.inputs.length)) { + promises.push( + this.signInputHDAsync(i, hdKeyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); + } + resolve(); + }); + }, + ); + } + + signInputHD( + inputIndex: number, + hdKeyPair: HDSigner, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): this { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + const signers = getSignersFromHD( + inputIndex, + this.data.inputs, + hdKeyPair, + ) as Signer[]; + signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes)); + return this; + } + + signInputHDAsync( + inputIndex: number, + hdKeyPair: HDSigner | HDSignerAsync, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): Promise { + return new Promise( + (resolve, reject): any => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const signers = getSignersFromHD( + inputIndex, + this.data.inputs, + hdKeyPair, + ); + const promises = signers.map(signer => + this.signInputAsync(inputIndex, signer, sighashTypes), + ); + return Promise.all(promises) + .then(() => { + resolve(); + }) + .catch(reject); + }, + ); + } + + signAllInputs( + keyPair: Signer, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): this { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results: boolean[] = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInput(i, keyPair, sighashTypes); + results.push(true); + } catch (err) { + results.push(false); + } + } + if (results.every(v => v === false)) { + throw new Error('No inputs were signed'); + } + return this; + } + + signAllInputsAsync( + keyPair: Signer | SignerAsync, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): Promise { + return new Promise( + (resolve, reject): any => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results: boolean[] = []; + const promises: Array> = []; + for (const [i] of this.data.inputs.entries()) { + promises.push( + this.signInputAsync(i, keyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); + } + resolve(); + }); + }, + ); + } + + signInput( + inputIndex: number, + keyPair: Signer, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): this { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + + this.data.updateInput(inputIndex, { partialSig }); + return this; + } + + signInputAsync( + inputIndex: number, + keyPair: Signer | SignerAsync, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): Promise { + return new Promise( + (resolve, reject): void => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + + Promise.resolve(keyPair.sign(hash)).then(signature => { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(signature, sighashType), + }, + ]; + + this.data.updateInput(inputIndex, { partialSig }); + resolve(); + }); + }, + ); + } + + toBuffer(): Buffer { + return this.data.toBuffer(); + } + + toHex(): string { + return this.data.toHex(); + } + + toBase64(): string { + return this.data.toBase64(); + } + + updateGlobal(updateData: PsbtGlobalUpdate): this { + this.data.updateGlobal(updateData); + return this; + } + + updateInput(inputIndex: number, updateData: PsbtInputUpdate): this { + this.data.updateInput(inputIndex, updateData); + if (updateData.nonWitnessUtxo) { + addNonWitnessTxCache( + this.__CACHE, + this.data.inputs[inputIndex], + inputIndex, + ); + } + return this; + } + + updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this { + this.data.updateOutput(outputIndex, updateData); + return this; + } + + addUnknownKeyValToGlobal(keyVal: KeyValue): this { + this.data.addUnknownKeyValToGlobal(keyVal); + return this; + } + + addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this { + this.data.addUnknownKeyValToInput(inputIndex, keyVal); + return this; + } + + addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this { + this.data.addUnknownKeyValToOutput(outputIndex, keyVal); + return this; + } + + clearFinalizedInput(inputIndex: number): this { + this.data.clearFinalizedInput(inputIndex); + return this; + } +} + +interface PsbtCache { + __NON_WITNESS_UTXO_TX_CACHE: Transaction[]; + __NON_WITNESS_UTXO_BUF_CACHE: Buffer[]; + __TX_IN_CACHE: { [index: string]: number }; + __TX: Transaction; + __FEE_RATE?: number; + __EXTRACTED_TX?: Transaction; +} + +interface PsbtOptsOptional { + network?: Network; + maximumFeeRate?: number; +} + +interface PsbtOpts { + network: Network; + maximumFeeRate: number; +} + +interface HDSignerBase { + /** + * DER format compressed publicKey buffer + */ + publicKey: Buffer; + /** + * The first 4 bytes of the sha256-ripemd160 of the publicKey + */ + fingerprint: Buffer; +} + +interface HDSigner extends HDSignerBase { + /** + * The path string must match /^m(\/\d+'?)+$/ + * ex. m/44'/0'/0'/1/23 levels with ' must be hard derivations + */ + derivePath(path: string): HDSigner; + /** + * Input hash (the "message digest") for the signature algorithm + * Return a 64 byte signature (32 byte r and 32 byte s in that order) + */ + sign(hash: Buffer): Buffer; +} + +/** + * Same as above but with async sign method + */ +interface HDSignerAsync extends HDSignerBase { + derivePath(path: string): HDSignerAsync; + sign(hash: Buffer): Promise; +} + +/** + * This function is needed to pass to the bip174 base class's fromBuffer. + * It takes the "transaction buffer" portion of the psbt buffer and returns a + * Transaction (From the bip174 library) interface. + */ +const transactionFromBuffer: TransactionFromBuffer = ( + buffer: Buffer, +): ITransaction => new PsbtTransaction(buffer); + +/** + * This class implements the Transaction interface from bip174 library. + * It contains a bitcoinjs-lib Transaction object. + */ +class PsbtTransaction implements ITransaction { + tx: Transaction; + constructor(buffer: Buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { + this.tx = Transaction.fromBuffer(buffer); + checkTxEmpty(this.tx); + Object.defineProperty(this, 'tx', { + enumerable: false, + writable: true, + }); + } + + getInputOutputCounts(): { + inputCount: number; + outputCount: number; + } { + return { + inputCount: this.tx.ins.length, + outputCount: this.tx.outs.length, + }; + } + + addInput(input: any): void { + if ( + (input as any).hash === undefined || + (input as any).index === undefined || + (!Buffer.isBuffer((input as any).hash) && + typeof (input as any).hash !== 'string') || + typeof (input as any).index !== 'number' + ) { + throw new Error('Error adding input.'); + } + const hash = + typeof input.hash === 'string' + ? reverseBuffer(Buffer.from(input.hash, 'hex')) + : input.hash; + this.tx.addInput(hash, input.index, input.sequence); + } + + addOutput(output: any): void { + if ( + (output as any).script === undefined || + (output as any).value === undefined || + !Buffer.isBuffer((output as any).script) || + typeof (output as any).value !== 'number' + ) { + throw new Error('Error adding output.'); + } + this.tx.addOutput(output.script, output.value); + } + + toBuffer(): Buffer { + return this.tx.toBuffer(); + } +} + +function canFinalize( + input: PsbtInput, + script: Buffer, + scriptType: string, +): boolean { + switch (scriptType) { + case 'pubkey': + case 'pubkeyhash': + case 'witnesspubkeyhash': + return hasSigs(1, input.partialSig); + case 'multisig': + const p2ms = payments.p2ms({ output: script }); + return hasSigs(p2ms.m!, input.partialSig); + default: + return false; + } +} + +function hasSigs(neededSigs: number, partialSig?: any[]): boolean { + if (!partialSig) return false; + if (partialSig.length > neededSigs) throw new Error('Too many signatures'); + return partialSig.length === neededSigs; +} + +function isFinalized(input: PsbtInput): boolean { + return !!input.finalScriptSig || !!input.finalScriptWitness; +} + +function isPaymentFactory(payment: any): (script: Buffer) => boolean { + return (script: Buffer): boolean => { + try { + payment({ output: script }); + return true; + } catch (err) { + return false; + } + }; +} +const isP2MS = isPaymentFactory(payments.p2ms); +const isP2PK = isPaymentFactory(payments.p2pk); +const isP2PKH = isPaymentFactory(payments.p2pkh); +const isP2WPKH = isPaymentFactory(payments.p2wpkh); +const isP2WSHScript = isPaymentFactory(payments.p2wsh); + +function check32Bit(num: number): void { + if ( + typeof num !== 'number' || + num !== Math.floor(num) || + num > 0xffffffff || + num < 0 + ) { + throw new Error('Invalid 32 bit integer'); + } +} + +function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void { + const feeRate = cache.__FEE_RATE || psbt.getFeeRate(); + const vsize = cache.__EXTRACTED_TX!.virtualSize(); + const satoshis = feeRate * vsize; + if (feeRate >= opts.maximumFeeRate) { + throw new Error( + `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` + + `fees, which is ${feeRate} satoshi per byte for a transaction ` + + `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` + + `byte). Use setMaximumFeeRate method to raise your threshold, or ` + + `pass true to the first arg of extractTransaction.`, + ); + } +} + +function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void { + inputs.forEach(input => { + let throws = false; + let pSigs: PartialSig[] = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig!; + } + pSigs.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + const whitelist: string[] = []; + const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case Transaction.SIGHASH_ALL: + break; + case Transaction.SIGHASH_SINGLE: + case Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + throws = true; + } + }); + if (throws) { + throw new Error('Can not modify transaction, signatures exist.'); + } + }); +} + +function checkPartialSigSighashes(input: PsbtInput): void { + if (!input.sighashType || !input.partialSig) return; + const { partialSig, sighashType } = input; + partialSig.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + if (sighashType !== hashType) { + throw new Error('Signature sighash does not match input sighash type'); + } + }); +} + +function checkScriptForPubkey( + pubkey: Buffer, + script: Buffer, + action: string, +): void { + const pubkeyHash = hash160(pubkey); + + const decompiled = bscript.decompile(script); + if (decompiled === null) throw new Error('Unknown script error'); + + const hasKey = decompiled.some(element => { + if (typeof element === 'number') return false; + return element.equals(pubkey) || element.equals(pubkeyHash); + }); + + if (!hasKey) { + throw new Error( + `Can not ${action} for this input with the key ${pubkey.toString('hex')}`, + ); + } +} + +function checkTxEmpty(tx: Transaction): void { + const isEmpty = tx.ins.every( + input => + input.script && + input.script.length === 0 && + input.witness && + input.witness.length === 0, + ); + if (!isEmpty) { + throw new Error('Format Error: Transaction ScriptSigs are not empty'); + } +} + +function checkTxForDupeIns(tx: Transaction, cache: PsbtCache): void { + tx.ins.forEach(input => { + checkTxInputCache(cache, input); + }); +} + +function checkTxInputCache( + cache: PsbtCache, + input: { hash: Buffer; index: number }, +): void { + const key = + reverseBuffer(Buffer.from(input.hash)).toString('hex') + ':' + input.index; + if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.'); + cache.__TX_IN_CACHE[key] = 1; +} + +function scriptCheckerFactory( + payment: any, + paymentScriptName: string, +): (idx: number, spk: Buffer, rs: Buffer) => void { + return ( + inputIndex: number, + scriptPubKey: Buffer, + redeemScript: Buffer, + ): void => { + const redeemScriptOutput = payment({ + redeem: { output: redeemScript }, + }).output as Buffer; + + if (!scriptPubKey.equals(redeemScriptOutput)) { + throw new Error( + `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, + ); + } + }; +} +const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); +const checkWitnessScript = scriptCheckerFactory( + payments.p2wsh, + 'Witness script', +); + +function getFinalScripts( + script: Buffer, + scriptType: string, + partialSig: PartialSig[], + isSegwit: boolean, + isP2SH: boolean, + isP2WSH: boolean, +): { + finalScriptSig: Buffer | undefined; + finalScriptWitness: Buffer | undefined; +} { + let finalScriptSig: Buffer | undefined; + let finalScriptWitness: Buffer | undefined; + + // Wow, the payments API is very handy + const payment: payments.Payment = getPayment(script, scriptType, partialSig); + const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment }); + const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); + + if (isSegwit) { + if (p2wsh) { + finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness!); + } else { + finalScriptWitness = witnessStackToScriptWitness(payment.witness!); + } + if (p2sh) { + finalScriptSig = p2sh.input; + } + } else { + if (p2sh) { + finalScriptSig = p2sh.input; + } else { + finalScriptSig = payment.input; + } + } + return { + finalScriptSig, + finalScriptWitness, + }; +} + +function getHashAndSighashType( + inputs: PsbtInput[], + inputIndex: number, + pubkey: Buffer, + cache: PsbtCache, + sighashTypes: number[], +): { + hash: Buffer; + sighashType: number; +} { + const input = checkForInput(inputs, inputIndex); + const { hash, sighashType, script } = getHashForSig( + inputIndex, + input, + cache, + sighashTypes, + ); + checkScriptForPubkey(pubkey, script, 'sign'); + return { + hash, + sighashType, + }; +} + +function getHashForSig( + inputIndex: number, + input: PsbtInput, + cache: PsbtCache, + sighashTypes?: number[], +): { + script: Buffer; + hash: Buffer; + sighashType: number; +} { + const unsignedTx = cache.__TX; + const sighashType = input.sighashType || Transaction.SIGHASH_ALL; + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error( + `Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`, + ); + } + let hash: Buffer; + let script: Buffer; + + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + + const prevoutHash = unsignedTx.ins[inputIndex].hash; + const utxoHash = nonWitnessUtxoTx.getHash(); + + // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout + if (!prevoutHash.equals(utxoHash)) { + throw new Error( + `Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`, + ); + } + + const prevoutIndex = unsignedTx.ins[inputIndex].index; + const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; + + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript(inputIndex, prevout.script, input.redeemScript); + script = input.redeemScript; + } else { + script = prevout.script; + } + + if (isP2WPKH(script) || isP2WSHScript(script)) { + throw new Error( + `Input #${inputIndex} has nonWitnessUtxo but segwit script: ` + + `${script.toString('hex')}`, + ); + } + + hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); + } else if (input.witnessUtxo) { + let _script: Buffer; // so we don't shadow the `let script` above + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript( + inputIndex, + input.witnessUtxo.script, + input.redeemScript, + ); + _script = input.redeemScript; + } else { + _script = input.witnessUtxo.script; + } + if (isP2WPKH(_script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output!; + hash = unsignedTx.hashForWitnessV0( + inputIndex, + signingScript, + input.witnessUtxo.value, + sighashType, + ); + script = _script; + } else if (isP2WSHScript(_script)) { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, _script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0( + inputIndex, + input.witnessScript, + input.witnessUtxo.value, + sighashType, + ); + // want to make sure the script we return is the actual meaningful script + script = input.witnessScript; + } else { + throw new Error( + `Input #${inputIndex} has witnessUtxo but non-segwit script: ` + + `${_script.toString('hex')}`, + ); + } + } else { + throw new Error('Need a Utxo input item for signing'); + } + return { + script, + sighashType, + hash, + }; +} + +function getPayment( + script: Buffer, + scriptType: string, + partialSig: PartialSig[], +): payments.Payment { + let payment: payments.Payment; + switch (scriptType) { + case 'multisig': + const sigs = getSortedSigs(script, partialSig); + payment = payments.p2ms({ + output: script, + signatures: sigs, + }); + break; + case 'pubkey': + payment = payments.p2pk({ + output: script, + signature: partialSig[0].signature, + }); + break; + case 'pubkeyhash': + payment = payments.p2pkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + case 'witnesspubkeyhash': + payment = payments.p2wpkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + } + return payment!; +} + +function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { + const scriptItems = !input.finalScriptSig + ? [] + : bscript.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : bscript.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); + }) + .map(sig => ({ signature: sig })) as PartialSig[]; +} + +interface GetScriptReturn { + script: Buffer | null; + isSegwit: boolean; + isP2SH: boolean; + isP2WSH: boolean; +} +function getScriptFromInput( + inputIndex: number, + input: PsbtInput, + cache: PsbtCache, +): GetScriptReturn { + const unsignedTx = cache.__TX; + const res: GetScriptReturn = { + script: null, + isSegwit: false, + isP2SH: false, + isP2WSH: false, + }; + if (input.nonWitnessUtxo) { + if (input.redeemScript) { + res.isP2SH = true; + res.script = input.redeemScript; + } else { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + const prevoutIndex = unsignedTx.ins[inputIndex].index; + res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; + } + } else if (input.witnessUtxo) { + res.isSegwit = true; + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript; + if (input.witnessScript) { + res.script = input.witnessScript; + } else if (input.redeemScript) { + res.script = payments.p2wpkh({ + hash: input.redeemScript.slice(2), + }).output!; + } else { + res.script = payments.p2wpkh({ + hash: input.witnessUtxo.script.slice(2), + }).output!; + } + } + return res; +} + +function getSignersFromHD( + inputIndex: number, + inputs: PsbtInput[], + hdKeyPair: HDSigner | HDSignerAsync, +): Array { + const input = checkForInput(inputs, inputIndex); + if (!input.bip32Derivation || input.bip32Derivation.length === 0) { + throw new Error('Need bip32Derivation to sign with HD'); + } + const myDerivations = input.bip32Derivation + .map(bipDv => { + if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) { + return bipDv; + } else { + return; + } + }) + .filter(v => !!v); + if (myDerivations.length === 0) { + throw new Error( + 'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint', + ); + } + const signers: Array = myDerivations.map(bipDv => { + const node = hdKeyPair.derivePath(bipDv!.path); + if (!bipDv!.pubkey.equals(node.publicKey)) { + throw new Error('pubkey did not match bip32Derivation'); + } + return node; + }); + return signers; +} + +function getSortedSigs(script: Buffer, partialSig: PartialSig[]): Buffer[] { + const p2ms = payments.p2ms({ output: script }); + // for each pubkey in order of p2ms script + return p2ms + .pubkeys!.map(pk => { + // filter partialSig array by pubkey being equal + return ( + partialSig.filter(ps => { + return ps.pubkey.equals(pk); + })[0] || {} + ).signature; + // Any pubkey without a match will return undefined + // this last filter removes all the undefined items in the array. + }) + .filter(v => !!v); +} + +function scriptWitnessToWitnessStack(buffer: Buffer): Buffer[] { + let offset = 0; + + function readSlice(n: number): Buffer { + offset += n; + return buffer.slice(offset - n, offset); + } + + function readVarInt(): number { + const vi = varuint.decode(buffer, offset); + offset += (varuint.decode as any).bytes; + return vi; + } + + function readVarSlice(): Buffer { + return readSlice(readVarInt()); + } + + function readVector(): Buffer[] { + const count = readVarInt(); + const vector: Buffer[] = []; + for (let i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; + } + + return readVector(); +} + +function sighashTypeToString(sighashType: number): string { + let text = + sighashType & Transaction.SIGHASH_ANYONECANPAY + ? 'SIGHASH_ANYONECANPAY | ' + : ''; + const sigMod = sighashType & 0x1f; + switch (sigMod) { + case Transaction.SIGHASH_ALL: + text += 'SIGHASH_ALL'; + break; + case Transaction.SIGHASH_SINGLE: + text += 'SIGHASH_SINGLE'; + break; + case Transaction.SIGHASH_NONE: + text += 'SIGHASH_NONE'; + break; + } + return text; +} + +function witnessStackToScriptWitness(witness: Buffer[]): Buffer { + let buffer = Buffer.allocUnsafe(0); + + function writeSlice(slice: Buffer): void { + buffer = Buffer.concat([buffer, Buffer.from(slice)]); + } + + function writeVarInt(i: number): void { + const currentLen = buffer.length; + const varintLen = varuint.encodingLength(i); + + buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); + varuint.encode(i, buffer, currentLen); + } + + function writeVarSlice(slice: Buffer): void { + writeVarInt(slice.length); + writeSlice(slice); + } + + function writeVector(vector: Buffer[]): void { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + + writeVector(witness); + + return buffer; +} + +function addNonWitnessTxCache( + cache: PsbtCache, + input: PsbtInput, + inputIndex: number, +): void { + cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo!; + + const tx = Transaction.fromBuffer(input.nonWitnessUtxo!); + cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx; + + const self = cache; + const selfIndex = inputIndex; + delete input.nonWitnessUtxo; + Object.defineProperty(input, 'nonWitnessUtxo', { + enumerable: true, + get(): Buffer { + const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]; + const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]; + if (buf !== undefined) { + return buf; + } else { + const newBuf = txCache.toBuffer(); + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf; + return newBuf; + } + }, + set(data: Buffer): void { + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; + }, + }); +} + +function inputFinalizeGetAmts( + inputs: PsbtInput[], + tx: Transaction, + cache: PsbtCache, + mustFinalize: boolean, +): void { + let inputAmount = 0; + inputs.forEach((input, idx) => { + if (mustFinalize && input.finalScriptSig) + tx.ins[idx].script = input.finalScriptSig; + if (mustFinalize && input.finalScriptWitness) { + tx.ins[idx].witness = scriptWitnessToWitnessStack( + input.finalScriptWitness, + ); + } + if (input.witnessUtxo) { + inputAmount += input.witnessUtxo.value; + } else if (input.nonWitnessUtxo) { + const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx); + const vout = tx.ins[idx].index; + const out = nwTx.outs[vout] as Output; + inputAmount += out.value; + } + }); + const outputAmount = (tx.outs as Output[]).reduce( + (total, o) => total + o.value, + 0, + ); + const fee = inputAmount - outputAmount; + if (fee < 0) { + throw new Error('Outputs are spending more than Inputs'); + } + const bytes = tx.virtualSize(); + cache.__EXTRACTED_TX = tx; + cache.__FEE_RATE = Math.floor(fee / bytes); +} + +function nonWitnessUtxoTxFromCache( + cache: PsbtCache, + input: PsbtInput, + inputIndex: number, +): Transaction { + const c = cache.__NON_WITNESS_UTXO_TX_CACHE; + if (!c[inputIndex]) { + addNonWitnessTxCache(cache, input, inputIndex); + } + return c[inputIndex]; +} + +function classifyScript(script: Buffer): string { + if (isP2WPKH(script)) return 'witnesspubkeyhash'; + if (isP2PKH(script)) return 'pubkeyhash'; + if (isP2MS(script)) return 'multisig'; + if (isP2PK(script)) return 'pubkey'; + return 'nonstandard'; +} + +function range(n: number): number[] { + return [...Array(n).keys()]; +} diff --git a/ts_src/script.ts b/ts_src/script.ts new file mode 100644 index 000000000..951f48b49 --- /dev/null +++ b/ts_src/script.ts @@ -0,0 +1,216 @@ +import { Stack } from './payments'; +import * as scriptNumber from './script_number'; +import * as scriptSignature from './script_signature'; +import * as types from './types'; +const bip66 = require('bip66'); +const ecc = require('tiny-secp256k1'); +const pushdata = require('pushdata-bitcoin'); +const typeforce = require('typeforce'); + +export type OpCode = number; +export const OPS = require('bitcoin-ops') as { [index: string]: OpCode }; + +const REVERSE_OPS = require('bitcoin-ops/map') as { [index: number]: string }; +const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 + +function isOPInt(value: number): boolean { + return ( + types.Number(value) && + (value === OPS.OP_0 || + (value >= OPS.OP_1 && value <= OPS.OP_16) || + value === OPS.OP_1NEGATE) + ); +} + +function isPushOnlyChunk(value: number | Buffer): boolean { + return types.Buffer(value) || isOPInt(value as number); +} + +export function isPushOnly(value: Stack): boolean { + return types.Array(value) && value.every(isPushOnlyChunk); +} + +function asMinimalOP(buffer: Buffer): number | void { + if (buffer.length === 0) return OPS.OP_0; + if (buffer.length !== 1) return; + if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; + if (buffer[0] === 0x81) return OPS.OP_1NEGATE; +} + +function chunksIsBuffer(buf: Buffer | Stack): buf is Buffer { + return Buffer.isBuffer(buf); +} + +function chunksIsArray(buf: Buffer | Stack): buf is Stack { + return types.Array(buf); +} + +function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { + return Buffer.isBuffer(buf); +} + +export function compile(chunks: Buffer | Stack): Buffer { + // TODO: remove me + if (chunksIsBuffer(chunks)) return chunks; + + typeforce(types.Array, chunks); + + const bufferSize = chunks.reduce((accum: number, chunk) => { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { + return accum + 1; + } + + return accum + pushdata.encodingLength(chunk.length) + chunk.length; + } + + // opcode + return accum + 1; + }, 0.0); + + const buffer = Buffer.allocUnsafe(bufferSize); + let offset = 0; + + chunks.forEach(chunk => { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + const opcode = asMinimalOP(chunk); + if (opcode !== undefined) { + buffer.writeUInt8(opcode, offset); + offset += 1; + return; + } + + offset += pushdata.encode(buffer, chunk.length, offset); + chunk.copy(buffer, offset); + offset += chunk.length; + + // opcode + } else { + buffer.writeUInt8(chunk, offset); + offset += 1; + } + }); + + if (offset !== buffer.length) throw new Error('Could not decode chunks'); + return buffer; +} + +export function decompile( + buffer: Buffer | Array, +): Array | null { + // TODO: remove me + if (chunksIsArray(buffer)) return buffer; + + typeforce(types.Buffer, buffer); + + const chunks: Array = []; + let i = 0; + + while (i < buffer.length) { + const opcode = buffer[i]; + + // data chunk + if (opcode > OPS.OP_0 && opcode <= OPS.OP_PUSHDATA4) { + const d = pushdata.decode(buffer, i); + + // did reading a pushDataInt fail? + if (d === null) return null; + i += d.size; + + // attempt to read too much data? + if (i + d.number > buffer.length) return null; + + const data = buffer.slice(i, i + d.number); + i += d.number; + + // decompile minimally + const op = asMinimalOP(data); + if (op !== undefined) { + chunks.push(op); + } else { + chunks.push(data); + } + + // opcode + } else { + chunks.push(opcode); + + i += 1; + } + } + + return chunks; +} + +export function toASM(chunks: Buffer | Array): string { + if (chunksIsBuffer(chunks)) { + chunks = decompile(chunks) as Stack; + } + + return chunks + .map(chunk => { + // data? + if (singleChunkIsBuffer(chunk)) { + const op = asMinimalOP(chunk); + if (op === undefined) return chunk.toString('hex'); + chunk = op as number; + } + + // opcode! + return REVERSE_OPS[chunk]; + }) + .join(' '); +} + +export function fromASM(asm: string): Buffer { + typeforce(types.String, asm); + + return compile( + asm.split(' ').map(chunkStr => { + // opcode? + if (OPS[chunkStr] !== undefined) return OPS[chunkStr]; + typeforce(types.Hex, chunkStr); + + // data! + return Buffer.from(chunkStr, 'hex'); + }), + ); +} + +export function toStack(chunks: Buffer | Array): Buffer[] { + chunks = decompile(chunks) as Stack; + typeforce(isPushOnly, chunks); + + return chunks.map(op => { + if (singleChunkIsBuffer(op)) return op; + if (op === OPS.OP_0) return Buffer.allocUnsafe(0); + + return scriptNumber.encode(op - OP_INT_BASE); + }); +} + +export function isCanonicalPubKey(buffer: Buffer): boolean { + return ecc.isPoint(buffer); +} + +export function isDefinedHashType(hashType: number): boolean { + const hashTypeMod = hashType & ~0x80; + + // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE + return hashTypeMod > 0x00 && hashTypeMod < 0x04; +} + +export function isCanonicalScriptSignature(buffer: Buffer): boolean { + if (!Buffer.isBuffer(buffer)) return false; + if (!isDefinedHashType(buffer[buffer.length - 1])) return false; + + return bip66.check(buffer.slice(0, -1)); +} + +// tslint:disable-next-line variable-name +export const number = scriptNumber; +export const signature = scriptSignature; diff --git a/ts_src/script_number.ts b/ts_src/script_number.ts new file mode 100644 index 000000000..a4c502fc9 --- /dev/null +++ b/ts_src/script_number.ts @@ -0,0 +1,71 @@ +export function decode( + buffer: Buffer, + maxLength?: number, + minimal?: boolean, +): number { + maxLength = maxLength || 4; + minimal = minimal === undefined ? true : minimal; + + const length = buffer.length; + if (length === 0) return 0; + if (length > maxLength) throw new TypeError('Script number overflow'); + if (minimal) { + if ((buffer[length - 1] & 0x7f) === 0) { + if (length <= 1 || (buffer[length - 2] & 0x80) === 0) + throw new Error('Non-minimally encoded script number'); + } + } + + // 40-bit + if (length === 5) { + const a = buffer.readUInt32LE(0); + const b = buffer.readUInt8(4); + + if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a); + return b * 0x100000000 + a; + } + + // 32-bit / 24-bit / 16-bit / 8-bit + let result = 0; + for (let i = 0; i < length; ++i) { + result |= buffer[i] << (8 * i); + } + + if (buffer[length - 1] & 0x80) + return -(result & ~(0x80 << (8 * (length - 1)))); + return result; +} + +function scriptNumSize(i: number): number { + return i > 0x7fffffff + ? 5 + : i > 0x7fffff + ? 4 + : i > 0x7fff + ? 3 + : i > 0x7f + ? 2 + : i > 0x00 + ? 1 + : 0; +} + +export function encode(_number: number): Buffer { + let value = Math.abs(_number); + const size = scriptNumSize(value); + const buffer = Buffer.allocUnsafe(size); + const negative = _number < 0; + + for (let i = 0; i < size; ++i) { + buffer.writeUInt8(value & 0xff, i); + value >>= 8; + } + + if (buffer[size - 1] & 0x80) { + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); + } else if (negative) { + buffer[size - 1] |= 0x80; + } + + return buffer; +} diff --git a/ts_src/script_signature.ts b/ts_src/script_signature.ts new file mode 100644 index 000000000..af9930edc --- /dev/null +++ b/ts_src/script_signature.ts @@ -0,0 +1,64 @@ +import * as types from './types'; +const bip66 = require('bip66'); + +const typeforce = require('typeforce'); + +const ZERO = Buffer.alloc(1, 0); +function toDER(x: Buffer): Buffer { + let i = 0; + while (x[i] === 0) ++i; + if (i === x.length) return ZERO; + x = x.slice(i); + if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length); + return x; +} + +function fromDER(x: Buffer): Buffer { + if (x[0] === 0x00) x = x.slice(1); + const buffer = Buffer.alloc(32, 0); + const bstart = Math.max(0, 32 - x.length); + x.copy(buffer, bstart); + return buffer; +} + +interface ScriptSignature { + signature: Buffer; + hashType: number; +} + +// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) +export function decode(buffer: Buffer): ScriptSignature { + const hashType = buffer.readUInt8(buffer.length - 1); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + + const decoded = bip66.decode(buffer.slice(0, -1)); + const r = fromDER(decoded.r); + const s = fromDER(decoded.s); + const signature = Buffer.concat([r, s], 64); + + return { signature, hashType }; +} + +export function encode(signature: Buffer, hashType: number): Buffer { + typeforce( + { + signature: types.BufferN(64), + hashType: types.UInt8, + }, + { signature, hashType }, + ); + + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + + const hashTypeBuffer = Buffer.allocUnsafe(1); + hashTypeBuffer.writeUInt8(hashType, 0); + + const r = toDER(signature.slice(0, 32)); + const s = toDER(signature.slice(32, 64)); + + return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); +} diff --git a/ts_src/templates/multisig/index.ts b/ts_src/templates/multisig/index.ts new file mode 100644 index 000000000..aa2bdcb16 --- /dev/null +++ b/ts_src/templates/multisig/index.ts @@ -0,0 +1,4 @@ +import * as input from './input'; +import * as output from './output'; + +export { input, output }; diff --git a/ts_src/templates/multisig/input.ts b/ts_src/templates/multisig/input.ts new file mode 100644 index 000000000..31fe416b8 --- /dev/null +++ b/ts_src/templates/multisig/input.ts @@ -0,0 +1,31 @@ +// OP_0 [signatures ...] + +import { Stack } from '../../payments'; +import * as bscript from '../../script'; +import { OPS } from '../../script'; + +function partialSignature(value: number | Buffer): boolean { + return ( + value === OPS.OP_0 || bscript.isCanonicalScriptSignature(value as Buffer) + ); +} + +export function check( + script: Buffer | Stack, + allowIncomplete?: boolean, +): boolean { + const chunks = bscript.decompile(script) as Stack; + if (chunks.length < 2) return false; + if (chunks[0] !== OPS.OP_0) return false; + + if (allowIncomplete) { + return chunks.slice(1).every(partialSignature); + } + + return (chunks.slice(1) as Buffer[]).every( + bscript.isCanonicalScriptSignature, + ); +} +check.toJSON = (): string => { + return 'multisig input'; +}; diff --git a/ts_src/templates/multisig/output.ts b/ts_src/templates/multisig/output.ts new file mode 100644 index 000000000..20c21625b --- /dev/null +++ b/ts_src/templates/multisig/output.ts @@ -0,0 +1,33 @@ +// m [pubKeys ...] n OP_CHECKMULTISIG + +import { Stack } from '../../payments'; +import * as bscript from '../../script'; +import { OPS } from '../../script'; +import * as types from '../../types'; +const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 + +export function check( + script: Buffer | Stack, + allowIncomplete?: boolean, +): boolean { + const chunks = bscript.decompile(script) as Stack; + + if (chunks.length < 4) return false; + if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false; + if (!types.Number(chunks[0])) return false; + if (!types.Number(chunks[chunks.length - 2])) return false; + const m = (chunks[0] as number) - OP_INT_BASE; + const n = (chunks[chunks.length - 2] as number) - OP_INT_BASE; + + if (m <= 0) return false; + if (n > 16) return false; + if (m > n) return false; + if (n !== chunks.length - 3) return false; + if (allowIncomplete) return true; + + const keys = chunks.slice(1, -2) as Buffer[]; + return keys.every(bscript.isCanonicalPubKey); +} +check.toJSON = (): string => { + return 'multi-sig output'; +}; diff --git a/ts_src/templates/nulldata.ts b/ts_src/templates/nulldata.ts new file mode 100644 index 000000000..99b5c446a --- /dev/null +++ b/ts_src/templates/nulldata.ts @@ -0,0 +1,16 @@ +// OP_RETURN {data} +import * as bscript from '../script'; +const OPS = bscript.OPS; + +export function check(script: Buffer | Array): boolean { + const buffer = bscript.compile(script); + + return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; +} +check.toJSON = (): string => { + return 'null data output'; +}; + +const output = { check }; + +export { output }; diff --git a/ts_src/templates/pubkey/index.ts b/ts_src/templates/pubkey/index.ts new file mode 100644 index 000000000..aa2bdcb16 --- /dev/null +++ b/ts_src/templates/pubkey/index.ts @@ -0,0 +1,4 @@ +import * as input from './input'; +import * as output from './output'; + +export { input, output }; diff --git a/ts_src/templates/pubkey/input.ts b/ts_src/templates/pubkey/input.ts new file mode 100644 index 000000000..745e6d96c --- /dev/null +++ b/ts_src/templates/pubkey/input.ts @@ -0,0 +1,16 @@ +// {signature} + +import { Stack } from '../../payments'; +import * as bscript from '../../script'; + +export function check(script: Buffer | Stack): boolean { + const chunks = bscript.decompile(script) as Stack; + + return ( + chunks.length === 1 && + bscript.isCanonicalScriptSignature(chunks[0] as Buffer) + ); +} +check.toJSON = (): string => { + return 'pubKey input'; +}; diff --git a/ts_src/templates/pubkey/output.ts b/ts_src/templates/pubkey/output.ts new file mode 100644 index 000000000..1b2c39196 --- /dev/null +++ b/ts_src/templates/pubkey/output.ts @@ -0,0 +1,18 @@ +// {pubKey} OP_CHECKSIG + +import { Stack } from '../../payments'; +import * as bscript from '../../script'; +import { OPS } from '../../script'; + +export function check(script: Buffer | Stack): boolean { + const chunks = bscript.decompile(script) as Stack; + + return ( + chunks.length === 2 && + bscript.isCanonicalPubKey(chunks[0] as Buffer) && + chunks[1] === OPS.OP_CHECKSIG + ); +} +check.toJSON = (): string => { + return 'pubKey output'; +}; diff --git a/ts_src/templates/pubkeyhash/index.ts b/ts_src/templates/pubkeyhash/index.ts new file mode 100644 index 000000000..aa2bdcb16 --- /dev/null +++ b/ts_src/templates/pubkeyhash/index.ts @@ -0,0 +1,4 @@ +import * as input from './input'; +import * as output from './output'; + +export { input, output }; diff --git a/ts_src/templates/pubkeyhash/input.ts b/ts_src/templates/pubkeyhash/input.ts new file mode 100644 index 000000000..de939687e --- /dev/null +++ b/ts_src/templates/pubkeyhash/input.ts @@ -0,0 +1,17 @@ +// {signature} {pubKey} + +import { Stack } from '../../payments'; +import * as bscript from '../../script'; + +export function check(script: Buffer | Stack): boolean { + const chunks = bscript.decompile(script) as Stack; + + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0] as Buffer) && + bscript.isCanonicalPubKey(chunks[1] as Buffer) + ); +} +check.toJSON = (): string => { + return 'pubKeyHash input'; +}; diff --git a/ts_src/templates/pubkeyhash/output.ts b/ts_src/templates/pubkeyhash/output.ts new file mode 100644 index 000000000..248c21054 --- /dev/null +++ b/ts_src/templates/pubkeyhash/output.ts @@ -0,0 +1,20 @@ +// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG + +import * as bscript from '../../script'; +import { OPS } from '../../script'; + +export function check(script: Buffer | Array): boolean { + const buffer = bscript.compile(script); + + return ( + buffer.length === 25 && + buffer[0] === OPS.OP_DUP && + buffer[1] === OPS.OP_HASH160 && + buffer[2] === 0x14 && + buffer[23] === OPS.OP_EQUALVERIFY && + buffer[24] === OPS.OP_CHECKSIG + ); +} +check.toJSON = (): string => { + return 'pubKeyHash output'; +}; diff --git a/ts_src/templates/scripthash/index.ts b/ts_src/templates/scripthash/index.ts new file mode 100644 index 000000000..aa2bdcb16 --- /dev/null +++ b/ts_src/templates/scripthash/index.ts @@ -0,0 +1,4 @@ +import * as input from './input'; +import * as output from './output'; + +export { input, output }; diff --git a/ts_src/templates/scripthash/input.ts b/ts_src/templates/scripthash/input.ts new file mode 100644 index 000000000..3ef8aab3e --- /dev/null +++ b/ts_src/templates/scripthash/input.ts @@ -0,0 +1,61 @@ +// {serialized scriptPubKey script} + +import * as bscript from '../../script'; +import * as p2ms from '../multisig'; +import * as p2pk from '../pubkey'; +import * as p2pkh from '../pubkeyhash'; +import * as p2wpkho from '../witnesspubkeyhash/output'; +import * as p2wsho from '../witnessscripthash/output'; + +export function check( + script: Buffer | Array, + allowIncomplete?: boolean, +): boolean { + const chunks = bscript.decompile(script)!; + if (chunks.length < 1) return false; + + const lastChunk = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(lastChunk)) return false; + + const scriptSigChunks = bscript.decompile( + bscript.compile(chunks.slice(0, -1)), + )!; + const redeemScriptChunks = bscript.decompile(lastChunk); + + // is redeemScript a valid script? + if (!redeemScriptChunks) return false; + + // is redeemScriptSig push only? + if (!bscript.isPushOnly(scriptSigChunks)) return false; + + // is witness? + if (chunks.length === 1) { + return ( + p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks) + ); + } + + // match types + if ( + p2pkh.input.check(scriptSigChunks) && + p2pkh.output.check(redeemScriptChunks) + ) + return true; + + if ( + p2ms.input.check(scriptSigChunks, allowIncomplete) && + p2ms.output.check(redeemScriptChunks) + ) + return true; + + if ( + p2pk.input.check(scriptSigChunks) && + p2pk.output.check(redeemScriptChunks) + ) + return true; + + return false; +} +check.toJSON = (): string => { + return 'scriptHash input'; +}; diff --git a/ts_src/templates/scripthash/output.ts b/ts_src/templates/scripthash/output.ts new file mode 100644 index 000000000..aea8e240c --- /dev/null +++ b/ts_src/templates/scripthash/output.ts @@ -0,0 +1,18 @@ +// OP_HASH160 {scriptHash} OP_EQUAL + +import * as bscript from '../../script'; +import { OPS } from '../../script'; + +export function check(script: Buffer | Array): boolean { + const buffer = bscript.compile(script); + + return ( + buffer.length === 23 && + buffer[0] === OPS.OP_HASH160 && + buffer[1] === 0x14 && + buffer[22] === OPS.OP_EQUAL + ); +} +check.toJSON = (): string => { + return 'scriptHash output'; +}; diff --git a/ts_src/templates/witnesscommitment/index.ts b/ts_src/templates/witnesscommitment/index.ts new file mode 100644 index 000000000..d5c166d19 --- /dev/null +++ b/ts_src/templates/witnesscommitment/index.ts @@ -0,0 +1,3 @@ +import * as output from './output'; + +export { output }; diff --git a/ts_src/templates/witnesscommitment/output.ts b/ts_src/templates/witnesscommitment/output.ts new file mode 100644 index 000000000..482798fb1 --- /dev/null +++ b/ts_src/templates/witnesscommitment/output.ts @@ -0,0 +1,40 @@ +// OP_RETURN {aa21a9ed} {commitment} + +import * as bscript from '../../script'; +import { OPS } from '../../script'; +import * as types from '../../types'; + +const typeforce = require('typeforce'); + +const HEADER: Buffer = Buffer.from('aa21a9ed', 'hex'); + +export function check(script: Buffer | Array): boolean { + const buffer = bscript.compile(script); + + return ( + buffer.length > 37 && + buffer[0] === OPS.OP_RETURN && + buffer[1] === 0x24 && + buffer.slice(2, 6).equals(HEADER) + ); +} + +check.toJSON = (): string => { + return 'Witness commitment output'; +}; + +export function encode(commitment: Buffer): Buffer { + typeforce(types.Hash256bit, commitment); + + const buffer = Buffer.allocUnsafe(36); + HEADER.copy(buffer, 0); + commitment.copy(buffer, 4); + + return bscript.compile([OPS.OP_RETURN, buffer]); +} + +export function decode(buffer: Buffer): Buffer { + typeforce(check, buffer); + + return (bscript.decompile(buffer)![1] as Buffer).slice(4, 36); +} diff --git a/ts_src/templates/witnesspubkeyhash/index.ts b/ts_src/templates/witnesspubkeyhash/index.ts new file mode 100644 index 000000000..aa2bdcb16 --- /dev/null +++ b/ts_src/templates/witnesspubkeyhash/index.ts @@ -0,0 +1,4 @@ +import * as input from './input'; +import * as output from './output'; + +export { input, output }; diff --git a/ts_src/templates/witnesspubkeyhash/input.ts b/ts_src/templates/witnesspubkeyhash/input.ts new file mode 100644 index 000000000..26fe63dcc --- /dev/null +++ b/ts_src/templates/witnesspubkeyhash/input.ts @@ -0,0 +1,21 @@ +// {signature} {pubKey} + +import { Stack } from '../../payments'; +import * as bscript from '../../script'; + +function isCompressedCanonicalPubKey(pubKey: Buffer): boolean { + return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; +} + +export function check(script: Buffer | Stack): boolean { + const chunks = bscript.decompile(script) as Stack; + + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0] as Buffer) && + isCompressedCanonicalPubKey(chunks[1] as Buffer) + ); +} +check.toJSON = (): string => { + return 'witnessPubKeyHash input'; +}; diff --git a/ts_src/templates/witnesspubkeyhash/output.ts b/ts_src/templates/witnesspubkeyhash/output.ts new file mode 100644 index 000000000..bfd6690c1 --- /dev/null +++ b/ts_src/templates/witnesspubkeyhash/output.ts @@ -0,0 +1,13 @@ +// OP_0 {pubKeyHash} + +import * as bscript from '../../script'; +import { OPS } from '../../script'; + +export function check(script: Buffer | Array): boolean { + const buffer = bscript.compile(script); + + return buffer.length === 22 && buffer[0] === OPS.OP_0 && buffer[1] === 0x14; +} +check.toJSON = (): string => { + return 'Witness pubKeyHash output'; +}; diff --git a/ts_src/templates/witnessscripthash/index.ts b/ts_src/templates/witnessscripthash/index.ts new file mode 100644 index 000000000..aa2bdcb16 --- /dev/null +++ b/ts_src/templates/witnessscripthash/index.ts @@ -0,0 +1,4 @@ +import * as input from './input'; +import * as output from './output'; + +export { input, output }; diff --git a/ts_src/templates/witnessscripthash/input.ts b/ts_src/templates/witnessscripthash/input.ts new file mode 100644 index 000000000..0f633305c --- /dev/null +++ b/ts_src/templates/witnessscripthash/input.ts @@ -0,0 +1,47 @@ +// {serialized scriptPubKey script} + +import * as bscript from '../../script'; +const typeforce = require('typeforce'); + +import * as p2ms from '../multisig'; +import * as p2pk from '../pubkey'; +import * as p2pkh from '../pubkeyhash'; + +export function check(chunks: Buffer[], allowIncomplete?: boolean): boolean { + typeforce(typeforce.Array, chunks); + if (chunks.length < 1) return false; + + const witnessScript = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(witnessScript)) return false; + + const witnessScriptChunks = bscript.decompile(witnessScript); + + // is witnessScript a valid script? + if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false; + + const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); + + // match types + if ( + p2pkh.input.check(witnessRawScriptSig) && + p2pkh.output.check(witnessScriptChunks) + ) + return true; + + if ( + p2ms.input.check(witnessRawScriptSig, allowIncomplete) && + p2ms.output.check(witnessScriptChunks) + ) + return true; + + if ( + p2pk.input.check(witnessRawScriptSig) && + p2pk.output.check(witnessScriptChunks) + ) + return true; + + return false; +} +check.toJSON = (): string => { + return 'witnessScriptHash input'; +}; diff --git a/ts_src/templates/witnessscripthash/output.ts b/ts_src/templates/witnessscripthash/output.ts new file mode 100644 index 000000000..7d4f33a68 --- /dev/null +++ b/ts_src/templates/witnessscripthash/output.ts @@ -0,0 +1,13 @@ +// OP_0 {scriptHash} + +import * as bscript from '../../script'; +import { OPS } from '../../script'; + +export function check(script: Buffer | Array): boolean { + const buffer = bscript.compile(script); + + return buffer.length === 34 && buffer[0] === OPS.OP_0 && buffer[1] === 0x20; +} +check.toJSON = (): string => { + return 'Witness scriptHash output'; +}; diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts new file mode 100644 index 000000000..218d00464 --- /dev/null +++ b/ts_src/transaction.ts @@ -0,0 +1,623 @@ +import * as bufferutils from './bufferutils'; +import { reverseBuffer } from './bufferutils'; +import * as bcrypto from './crypto'; +import * as bscript from './script'; +import { OPS as opcodes } from './script'; +import * as types from './types'; + +const typeforce = require('typeforce'); +const varuint = require('varuint-bitcoin'); + +function varSliceSize(someScript: Buffer): number { + const length = someScript.length; + + return varuint.encodingLength(length) + length; +} + +function vectorSize(someVector: Buffer[]): number { + const length = someVector.length; + + return ( + varuint.encodingLength(length) + + someVector.reduce((sum, witness) => { + return sum + varSliceSize(witness); + }, 0) + ); +} + +const EMPTY_SCRIPT: Buffer = Buffer.allocUnsafe(0); +const EMPTY_WITNESS: Buffer[] = []; +const ZERO: Buffer = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000000', + 'hex', +); +const ONE: Buffer = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex', +); +const VALUE_UINT64_MAX: Buffer = Buffer.from('ffffffffffffffff', 'hex'); +const BLANK_OUTPUT: BlankOutput = { + script: EMPTY_SCRIPT, + valueBuffer: VALUE_UINT64_MAX, +}; + +function isOutput(out: Output | BlankOutput): out is Output { + return (out as Output).value !== undefined; +} + +export interface BlankOutput { + script: Buffer; + valueBuffer: Buffer; +} + +export interface Output { + script: Buffer; + value: number; +} + +type OpenOutput = Output | BlankOutput; + +export interface Input { + hash: Buffer; + index: number; + script: Buffer; + sequence: number; + witness: Buffer[]; +} + +export class Transaction { + static readonly DEFAULT_SEQUENCE = 0xffffffff; + static readonly SIGHASH_ALL = 0x01; + static readonly SIGHASH_NONE = 0x02; + static readonly SIGHASH_SINGLE = 0x03; + static readonly SIGHASH_ANYONECANPAY = 0x80; + static readonly ADVANCED_TRANSACTION_MARKER = 0x00; + static readonly ADVANCED_TRANSACTION_FLAG = 0x01; + + static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction { + let offset: number = 0; + + function readSlice(n: number): Buffer { + offset += n; + return buffer.slice(offset - n, offset); + } + + function readUInt32(): number { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + } + + function readInt32(): number { + const i = buffer.readInt32LE(offset); + offset += 4; + return i; + } + + function readUInt64(): number { + const i = bufferutils.readUInt64LE(buffer, offset); + offset += 8; + return i; + } + + function readVarInt(): number { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + } + + function readVarSlice(): Buffer { + return readSlice(readVarInt()); + } + + function readVector(): Buffer[] { + const count = readVarInt(); + const vector: Buffer[] = []; + 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: string): Transaction { + return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); + } + + static isCoinbaseHash(buffer: Buffer): boolean { + typeforce(types.Hash256bit, buffer); + for (let i = 0; i < 32; ++i) { + if (buffer[i] !== 0) return false; + } + return true; + } + + version: number = 1; + locktime: number = 0; + ins: Input[] = []; + outs: OpenOutput[] = []; + + isCoinbase(): boolean { + return ( + this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) + ); + } + + addInput( + hash: Buffer, + index: number, + sequence?: number, + scriptSig?: Buffer, + ): number { + 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, + index, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence as number, + witness: EMPTY_WITNESS, + }) - 1 + ); + } + + addOutput(scriptPubKey: Buffer, value: number): number { + typeforce(types.tuple(types.Buffer, types.Satoshi), arguments); + + // Add the output and return the output's index + return ( + this.outs.push({ + script: scriptPubKey, + value, + }) - 1 + ); + } + + hasWitnesses(): boolean { + return this.ins.some(x => { + return x.witness.length !== 0; + }); + } + + weight(): number { + const base = this.__byteLength(false); + const total = this.__byteLength(true); + return base * 3 + total; + } + + virtualSize(): number { + return Math.ceil(this.weight() / 4); + } + + byteLength(): number { + return this.__byteLength(true); + } + + clone(): Transaction { + const newTx = new Transaction(); + newTx.version = this.version; + newTx.locktime = this.locktime; + + newTx.ins = this.ins.map(txIn => { + return { + hash: txIn.hash, + index: txIn.index, + script: txIn.script, + sequence: txIn.sequence, + witness: txIn.witness, + }; + }); + + newTx.outs = this.outs.map(txOut => { + return { + script: txOut.script, + value: (txOut as Output).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: number, + prevOutScript: Buffer, + hashType: number, + ): Buffer { + 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 !== 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((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 { + // "blank" others input scripts + txTmp.ins.forEach(input => { + input.script = EMPTY_SCRIPT; + }); + txTmp.ins[inIndex].script = ourScript; + } + + // serialize and hash + const buffer: Buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4); + buffer.writeInt32LE(hashType, buffer.length - 4); + txTmp.__toBuffer(buffer, 0, false); + + return bcrypto.hash256(buffer); + } + + hashForWitnessV0( + inIndex: number, + prevOutScript: Buffer, + value: number, + hashType: number, + ): Buffer { + typeforce( + types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), + arguments, + ); + + let tbuffer: Buffer = Buffer.from([]); + let toffset: number = 0; + + function writeSlice(slice: Buffer): void { + toffset += slice.copy(tbuffer, toffset); + } + + function writeUInt32(i: number): void { + toffset = tbuffer.writeUInt32LE(i, toffset); + } + + function writeUInt64(i: number): void { + toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset); + } + + function writeVarInt(i: number): void { + varuint.encode(i, tbuffer, toffset); + toffset += varuint.encode.bytes; + } + + function writeVarSlice(slice: Buffer): void { + 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 as Output).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 as 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?: boolean): Buffer { + // 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(): string { + // transaction hash's are displayed in reverse order + return reverseBuffer(this.getHash(false)).toString('hex'); + } + + toBuffer(buffer?: Buffer, initialOffset?: number): Buffer { + return this.__toBuffer(buffer, initialOffset, true); + } + + toHex(): string { + return this.toBuffer(undefined, undefined).toString('hex'); + } + + setInputScript(index: number, scriptSig: Buffer): void { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + + this.ins[index].script = scriptSig; + } + + setWitness(index: number, witness: Buffer[]): void { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + + this.ins[index].witness = witness; + } + + private __byteLength(_ALLOW_WITNESS: boolean): number { + 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) + ); + } + + private __toBuffer( + buffer?: Buffer, + initialOffset?: number, + _ALLOW_WITNESS?: boolean, + ): Buffer { + if (!buffer) + buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS!)) as Buffer; + + let offset = initialOffset || 0; + + function writeSlice(slice: Buffer): void { + offset += slice.copy(buffer!, offset); + } + + function writeUInt8(i: number): void { + offset = buffer!.writeUInt8(i, offset); + } + + function writeUInt32(i: number): void { + offset = buffer!.writeUInt32LE(i, offset); + } + + function writeInt32(i: number): void { + offset = buffer!.writeInt32LE(i, offset); + } + + function writeUInt64(i: number): void { + offset = bufferutils.writeUInt64LE(buffer!, i, offset); + } + + function writeVarInt(i: number): void { + varuint.encode(i, buffer, offset); + offset += varuint.encode.bytes; + } + + function writeVarSlice(slice: Buffer): void { + writeVarInt(slice.length); + writeSlice(slice); + } + + function writeVector(vector: Buffer[]): void { + 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; + } +} diff --git a/ts_src/transaction_builder.ts b/ts_src/transaction_builder.ts new file mode 100644 index 000000000..1edf0f29f --- /dev/null +++ b/ts_src/transaction_builder.ts @@ -0,0 +1,1318 @@ +import * as baddress from './address'; +import { reverseBuffer } from './bufferutils'; +import * as classify from './classify'; +import * as bcrypto from './crypto'; +import { Signer } from './ecpair'; +import * as ECPair from './ecpair'; +import { Network } from './networks'; +import * as networks from './networks'; +import { Payment } from './payments'; +import * as payments from './payments'; +import * as bscript from './script'; +import { OPS as ops } from './script'; +import { Output, Transaction } from './transaction'; +import * as types from './types'; +const typeforce = require('typeforce'); + +const SCRIPT_TYPES = classify.types; + +const PREVOUT_TYPES: Set = new Set([ + // Raw + 'p2pkh', + 'p2pk', + 'p2wpkh', + 'p2ms', + // P2SH wrapped + 'p2sh-p2pkh', + 'p2sh-p2pk', + 'p2sh-p2wpkh', + 'p2sh-p2ms', + // P2WSH wrapped + 'p2wsh-p2pkh', + 'p2wsh-p2pk', + 'p2wsh-p2ms', + // P2SH-P2WSH wrapper + 'p2sh-p2wsh-p2pkh', + 'p2sh-p2wsh-p2pk', + 'p2sh-p2wsh-p2ms', +]); + +type MaybeBuffer = Buffer | undefined; +type TxbSignatures = Buffer[] | MaybeBuffer[]; +type TxbPubkeys = MaybeBuffer[]; +type TxbWitness = Buffer[]; +type TxbScriptType = string; +type TxbScript = Buffer; + +interface TxbInput { + value?: number; + hasWitness?: boolean; + signScript?: TxbScript; + signType?: TxbScriptType; + prevOutScript?: TxbScript; + redeemScript?: TxbScript; + redeemScriptType?: TxbScriptType; + prevOutType?: TxbScriptType; + pubkeys?: TxbPubkeys; + signatures?: TxbSignatures; + witness?: TxbWitness; + witnessScript?: TxbScript; + witnessScriptType?: TxbScriptType; + script?: TxbScript; + sequence?: number; + scriptSig?: TxbScript; + maxSignatures?: number; +} + +interface TxbOutput { + type: string; + pubkeys?: TxbPubkeys; + signatures?: TxbSignatures; + maxSignatures?: number; +} + +interface TxbSignArg { + prevOutScriptType: string; + vin: number; + keyPair: Signer; + redeemScript?: Buffer; + hashType?: number; + witnessValue?: number; + witnessScript?: Buffer; +} + +function tfMessage(type: any, value: any, message: string): void { + try { + typeforce(type, value); + } catch (err) { + throw new Error(message); + } +} + +function txIsString(tx: Buffer | string | Transaction): tx is string { + return typeof tx === 'string' || tx instanceof String; +} + +function txIsTransaction(tx: Buffer | string | Transaction): tx is Transaction { + return tx instanceof Transaction; +} + +export class TransactionBuilder { + static fromTransaction( + transaction: Transaction, + network?: Network, + ): TransactionBuilder { + const txb = new TransactionBuilder(network); + + // Copy transaction fields + txb.setVersion(transaction.version); + txb.setLockTime(transaction.locktime); + + // Copy outputs (done first to avoid signature invalidation) + transaction.outs.forEach(txOut => { + txb.addOutput(txOut.script, (txOut as Output).value); + }); + + // Copy inputs + transaction.ins.forEach(txIn => { + txb.__addInputUnsafe(txIn.hash, txIn.index, { + sequence: txIn.sequence, + script: txIn.script, + witness: txIn.witness, + }); + }); + + // fix some things not possible through the public API + txb.__INPUTS.forEach((input, i) => { + fixMultisigOrder(input, transaction, i); + }); + + return txb; + } + + private __PREV_TX_SET: { [index: string]: boolean }; + private __INPUTS: TxbInput[]; + private __TX: Transaction; + private __USE_LOW_R: boolean; + + // WARNING: maximumFeeRate is __NOT__ to be relied on, + // it's just another potential safety mechanism (safety in-depth) + constructor( + public network: Network = networks.bitcoin, + public maximumFeeRate: number = 2500, + ) { + this.__PREV_TX_SET = {}; + this.__INPUTS = []; + this.__TX = new Transaction(); + this.__TX.version = 2; + this.__USE_LOW_R = false; + console.warn( + 'Deprecation Warning: TransactionBuilder will be removed in the future. ' + + '(v6.x.x or later) Please use the Psbt class instead. Examples of usage ' + + 'are available in the transactions-psbt.js integration test file on our ' + + 'Github. A high level explanation is available in the psbt.ts and psbt.js ' + + 'files as well.', + ); + } + + setLowR(setting?: boolean): boolean { + typeforce(typeforce.maybe(typeforce.Boolean), setting); + if (setting === undefined) { + setting = true; + } + this.__USE_LOW_R = setting; + return setting; + } + + setLockTime(locktime: number): void { + typeforce(types.UInt32, locktime); + + // if any signatures exist, throw + if ( + this.__INPUTS.some(input => { + if (!input.signatures) return false; + + return input.signatures.some(s => s !== undefined); + }) + ) { + throw new Error('No, this would invalidate signatures'); + } + + this.__TX.locktime = locktime; + } + + setVersion(version: number): void { + typeforce(types.UInt32, version); + + // XXX: this might eventually become more complex depending on what the versions represent + this.__TX.version = version; + } + + addInput( + txHash: Buffer | string | Transaction, + vout: number, + sequence?: number, + prevOutScript?: Buffer, + ): number { + if (!this.__canModifyInputs()) { + throw new Error('No, this would invalidate signatures'); + } + + let value: number | undefined; + + // is it a hex string? + if (txIsString(txHash)) { + // transaction hashs's are displayed in reverse order, un-reverse it + txHash = reverseBuffer(Buffer.from(txHash, 'hex')); + + // is it a Transaction object? + } else if (txIsTransaction(txHash)) { + const txOut = txHash.outs[vout]; + prevOutScript = txOut.script; + value = (txOut as Output).value; + + txHash = txHash.getHash(false) as Buffer; + } + + return this.__addInputUnsafe(txHash, vout, { + sequence, + prevOutScript, + value, + }); + } + + addOutput(scriptPubKey: string | Buffer, value: number): number { + if (!this.__canModifyOutputs()) { + throw new Error('No, this would invalidate signatures'); + } + + // Attempt to get a script if it's a base58 or bech32 address string + if (typeof scriptPubKey === 'string') { + scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network); + } + + return this.__TX.addOutput(scriptPubKey, value); + } + + build(): Transaction { + return this.__build(false); + } + + buildIncomplete(): Transaction { + return this.__build(true); + } + + sign( + signParams: number | TxbSignArg, + keyPair?: Signer, + redeemScript?: Buffer, + hashType?: number, + witnessValue?: number, + witnessScript?: Buffer, + ): void { + trySign( + getSigningData( + this.network, + this.__INPUTS, + this.__needsOutputs.bind(this), + this.__TX, + signParams, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + this.__USE_LOW_R, + ), + ); + } + + private __addInputUnsafe( + txHash: Buffer, + vout: number, + options: TxbInput, + ): number { + if (Transaction.isCoinbaseHash(txHash)) { + throw new Error('coinbase inputs not supported'); + } + + const prevTxOut = txHash.toString('hex') + ':' + vout; + if (this.__PREV_TX_SET[prevTxOut] !== undefined) + throw new Error('Duplicate TxOut: ' + prevTxOut); + + let input: TxbInput = {}; + + // derive what we can from the scriptSig + if (options.script !== undefined) { + input = expandInput(options.script, options.witness || []); + } + + // if an input value was given, retain it + if (options.value !== undefined) { + input.value = options.value; + } + + // derive what we can from the previous transactions output script + if (!input.prevOutScript && options.prevOutScript) { + let prevOutType; + + if (!input.pubkeys && !input.signatures) { + const expanded = expandOutput(options.prevOutScript); + if (expanded.pubkeys) { + input.pubkeys = expanded.pubkeys; + input.signatures = expanded.signatures; + } + + prevOutType = expanded.type; + } + + input.prevOutScript = options.prevOutScript; + input.prevOutType = prevOutType || classify.output(options.prevOutScript); + } + + const vin = this.__TX.addInput( + txHash, + vout, + options.sequence, + options.scriptSig, + ); + this.__INPUTS[vin] = input; + this.__PREV_TX_SET[prevTxOut] = true; + return vin; + } + + private __build(allowIncomplete?: boolean): Transaction { + if (!allowIncomplete) { + if (!this.__TX.ins.length) throw new Error('Transaction has no inputs'); + if (!this.__TX.outs.length) throw new Error('Transaction has no outputs'); + } + + const tx = this.__TX.clone(); + + // create script signatures from inputs + this.__INPUTS.forEach((input, i) => { + if (!input.prevOutType && !allowIncomplete) + throw new Error('Transaction is not complete'); + + const result = build(input.prevOutType!, input, allowIncomplete); + if (!result) { + if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) + throw new Error('Unknown input type'); + if (!allowIncomplete) throw new Error('Not enough information'); + return; + } + + tx.setInputScript(i, result.input!); + tx.setWitness(i, result.witness!); + }); + + if (!allowIncomplete) { + // do not rely on this, its merely a last resort + if (this.__overMaximumFees(tx.virtualSize())) { + throw new Error('Transaction has absurd fees'); + } + } + + return tx; + } + + private __canModifyInputs(): boolean { + return this.__INPUTS.every(input => { + if (!input.signatures) return true; + + return input.signatures.every(signature => { + if (!signature) return true; + const hashType = signatureHashType(signature); + + // if SIGHASH_ANYONECANPAY is set, signatures would not + // be invalidated by more inputs + return (hashType & Transaction.SIGHASH_ANYONECANPAY) !== 0; + }); + }); + } + + private __needsOutputs(signingHashType: number): boolean { + if (signingHashType === Transaction.SIGHASH_ALL) { + return this.__TX.outs.length === 0; + } + + // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs + // .build() will fail, but .buildIncomplete() is OK + return ( + this.__TX.outs.length === 0 && + this.__INPUTS.some(input => { + if (!input.signatures) return false; + + return input.signatures.some(signature => { + if (!signature) return false; // no signature, no issue + const hashType = signatureHashType(signature); + if (hashType & Transaction.SIGHASH_NONE) return false; // SIGHASH_NONE doesn't care about outputs + return true; // SIGHASH_* does care + }); + }) + ); + } + + private __canModifyOutputs(): boolean { + const nInputs = this.__TX.ins.length; + const nOutputs = this.__TX.outs.length; + + return this.__INPUTS.every(input => { + if (input.signatures === undefined) return true; + + return input.signatures.every(signature => { + if (!signature) return true; + const hashType = signatureHashType(signature); + + const hashTypeMod = hashType & 0x1f; + if (hashTypeMod === Transaction.SIGHASH_NONE) return true; + if (hashTypeMod === Transaction.SIGHASH_SINGLE) { + // if SIGHASH_SINGLE is set, and nInputs > nOutputs + // some signatures would be invalidated by the addition + // of more outputs + return nInputs <= nOutputs; + } + return false; + }); + }); + } + + private __overMaximumFees(bytes: number): boolean { + // not all inputs will have .value defined + const incoming = this.__INPUTS.reduce((a, x) => a + (x.value! >>> 0), 0); + + // but all outputs do, and if we have any input value + // we can immediately determine if the outputs are too small + const outgoing = this.__TX.outs.reduce( + (a, x) => a + (x as Output).value, + 0, + ); + const fee = incoming - outgoing; + const feeRate = fee / bytes; + + return feeRate > this.maximumFeeRate; + } +} + +function expandInput( + scriptSig: Buffer, + witnessStack: Buffer[], + type?: string, + scriptPubKey?: Buffer, +): TxbInput { + if (scriptSig.length === 0 && witnessStack.length === 0) return {}; + if (!type) { + let ssType: string | undefined = classify.input(scriptSig, true); + let wsType: string | undefined = classify.witness(witnessStack, true); + if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined; + if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined; + type = ssType || wsType; + } + + switch (type) { + case SCRIPT_TYPES.P2WPKH: { + const { output, pubkey, signature } = payments.p2wpkh({ + witness: witnessStack, + }); + + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WPKH, + pubkeys: [pubkey], + signatures: [signature], + }; + } + + case SCRIPT_TYPES.P2PKH: { + const { output, pubkey, signature } = payments.p2pkh({ + input: scriptSig, + }); + + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2PKH, + pubkeys: [pubkey], + signatures: [signature], + }; + } + + case SCRIPT_TYPES.P2PK: { + const { signature } = payments.p2pk({ input: scriptSig }); + + return { + prevOutType: SCRIPT_TYPES.P2PK, + pubkeys: [undefined], + signatures: [signature], + }; + } + + case SCRIPT_TYPES.P2MS: { + const { m, pubkeys, signatures } = payments.p2ms( + { + input: scriptSig, + output: scriptPubKey, + }, + { allowIncomplete: true }, + ); + + return { + prevOutType: SCRIPT_TYPES.P2MS, + pubkeys, + signatures, + maxSignatures: m, + }; + } + } + + if (type === SCRIPT_TYPES.P2SH) { + const { output, redeem } = payments.p2sh({ + input: scriptSig, + witness: witnessStack, + }); + + const outputType = classify.output(redeem!.output!); + const expanded = expandInput( + redeem!.input!, + redeem!.witness!, + outputType, + redeem!.output, + ); + if (!expanded.prevOutType) return {}; + + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2SH, + redeemScript: redeem!.output, + redeemScriptType: expanded.prevOutType, + witnessScript: expanded.witnessScript, + witnessScriptType: expanded.witnessScriptType, + + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + }; + } + + if (type === SCRIPT_TYPES.P2WSH) { + const { output, redeem } = payments.p2wsh({ + input: scriptSig, + witness: witnessStack, + }); + const outputType = classify.output(redeem!.output!); + let expanded; + if (outputType === SCRIPT_TYPES.P2WPKH) { + expanded = expandInput(redeem!.input!, redeem!.witness!, outputType); + } else { + expanded = expandInput( + bscript.compile(redeem!.witness!), + [], + outputType, + redeem!.output, + ); + } + if (!expanded.prevOutType) return {}; + + return { + prevOutScript: output, + prevOutType: SCRIPT_TYPES.P2WSH, + witnessScript: redeem!.output, + witnessScriptType: expanded.prevOutType, + + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + }; + } + + return { + prevOutType: SCRIPT_TYPES.NONSTANDARD, + prevOutScript: scriptSig, + }; +} + +// could be done in expandInput, but requires the original Transaction for hashForSignature +function fixMultisigOrder( + input: TxbInput, + transaction: Transaction, + vin: number, +): void { + if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) + return; + if (input.pubkeys!.length === input.signatures!.length) return; + + const unmatched = input.signatures!.concat(); + + input.signatures = input.pubkeys!.map(pubKey => { + const keyPair = ECPair.fromPublicKey(pubKey!); + let match: Buffer | undefined; + + // check for a signature + unmatched.some((signature, i) => { + // skip if undefined || OP_0 + if (!signature) return false; + + // TODO: avoid O(n) hashForSignature + const parsed = bscript.signature.decode(signature); + const hash = transaction.hashForSignature( + vin, + input.redeemScript!, + parsed.hashType, + ); + + // skip if signature does not match pubKey + if (!keyPair.verify(hash, parsed.signature)) return false; + + // remove matched signature from unmatched + unmatched[i] = undefined; + match = signature; + + return true; + }); + + return match; + }); +} + +function expandOutput(script: Buffer, ourPubKey?: Buffer): TxbOutput { + typeforce(types.Buffer, script); + const type = classify.output(script); + + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (!ourPubKey) return { type }; + + // does our hash160(pubKey) match the output scripts? + const pkh1 = payments.p2pkh({ output: script }).hash; + const pkh2 = bcrypto.hash160(ourPubKey); + if (!pkh1!.equals(pkh2)) return { type }; + + return { + type, + pubkeys: [ourPubKey], + signatures: [undefined], + }; + } + + case SCRIPT_TYPES.P2WPKH: { + if (!ourPubKey) return { type }; + + // does our hash160(pubKey) match the output scripts? + const wpkh1 = payments.p2wpkh({ output: script }).hash; + const wpkh2 = bcrypto.hash160(ourPubKey); + if (!wpkh1!.equals(wpkh2)) return { type }; + + return { + type, + pubkeys: [ourPubKey], + signatures: [undefined], + }; + } + + case SCRIPT_TYPES.P2PK: { + const p2pk = payments.p2pk({ output: script }); + return { + type, + pubkeys: [p2pk.pubkey], + signatures: [undefined], + }; + } + + case SCRIPT_TYPES.P2MS: { + const p2ms = payments.p2ms({ output: script }); + return { + type, + pubkeys: p2ms.pubkeys, + signatures: p2ms.pubkeys!.map((): undefined => undefined), + maxSignatures: p2ms.m, + }; + } + } + + return { type }; +} + +function prepareInput( + input: TxbInput, + ourPubKey: Buffer, + redeemScript?: Buffer, + witnessScript?: Buffer, +): TxbInput { + if (redeemScript && witnessScript) { + const p2wsh = payments.p2wsh({ + redeem: { output: witnessScript }, + }) as Payment; + const p2wshAlt = payments.p2wsh({ output: redeemScript }) as Payment; + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }) as Payment; + const p2shAlt = payments.p2sh({ redeem: p2wsh }) as Payment; + + // enforces P2SH(P2WSH(...)) + if (!p2wsh.hash!.equals(p2wshAlt.hash!)) + throw new Error('Witness script inconsistent with prevOutScript'); + if (!p2sh.hash!.equals(p2shAlt.hash!)) + throw new Error('Redeem script inconsistent with prevOutScript'); + + const expanded = expandOutput(p2wsh.redeem!.output!, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as witnessScript (' + + bscript.toASM(witnessScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + + const signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure'); + + return { + redeemScript, + redeemScriptType: SCRIPT_TYPES.P2WSH, + + witnessScript, + witnessScriptType: expanded.type, + + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + + hasWitness: true, + signScript, + signType: expanded.type, + + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + + if (redeemScript) { + const p2sh = payments.p2sh({ redeem: { output: redeemScript } }) as Payment; + + if (input.prevOutScript) { + let p2shAlt; + try { + p2shAlt = payments.p2sh({ output: input.prevOutScript }) as Payment; + } catch (e) { + throw new Error('PrevOutScript must be P2SH'); + } + if (!p2sh.hash!.equals(p2shAlt.hash!)) + throw new Error('Redeem script inconsistent with prevOutScript'); + } + + const expanded = expandOutput(p2sh.redeem!.output!, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as redeemScript (' + + bscript.toASM(redeemScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + + let signScript = redeemScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output!; + } + + return { + redeemScript, + redeemScriptType: expanded.type, + + prevOutType: SCRIPT_TYPES.P2SH, + prevOutScript: p2sh.output, + + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + + if (witnessScript) { + const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }); + + if (input.prevOutScript) { + const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }); + if (!p2wsh.hash!.equals(p2wshAlt.hash!)) + throw new Error('Witness script inconsistent with prevOutScript'); + } + + const expanded = expandOutput(p2wsh.redeem!.output!, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported as witnessScript (' + + bscript.toASM(witnessScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + + const signScript = witnessScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) + throw new Error('P2WSH(P2WPKH) is a consensus failure'); + + return { + witnessScript, + witnessScriptType: expanded.type, + + prevOutType: SCRIPT_TYPES.P2WSH, + prevOutScript: p2wsh.output, + + hasWitness: true, + signScript, + signType: expanded.type, + + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + + if (input.prevOutType && input.prevOutScript) { + // embedded scripts are not possible without extra information + if (input.prevOutType === SCRIPT_TYPES.P2SH) + throw new Error( + 'PrevOutScript is ' + input.prevOutType + ', requires redeemScript', + ); + if (input.prevOutType === SCRIPT_TYPES.P2WSH) + throw new Error( + 'PrevOutScript is ' + input.prevOutType + ', requires witnessScript', + ); + if (!input.prevOutScript) throw new Error('PrevOutScript is missing'); + + const expanded = expandOutput(input.prevOutScript, ourPubKey); + if (!expanded.pubkeys) + throw new Error( + expanded.type + + ' not supported (' + + bscript.toASM(input.prevOutScript) + + ')', + ); + if (input.signatures && input.signatures.some(x => x !== undefined)) { + expanded.signatures = input.signatures; + } + + let signScript = input.prevOutScript; + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }) + .output as Buffer; + } + + return { + prevOutType: expanded.type, + prevOutScript: input.prevOutScript, + + hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, + signScript, + signType: expanded.type, + + pubkeys: expanded.pubkeys, + signatures: expanded.signatures, + maxSignatures: expanded.maxSignatures, + }; + } + + const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output; + return { + prevOutType: SCRIPT_TYPES.P2PKH, + prevOutScript, + + hasWitness: false, + signScript: prevOutScript, + signType: SCRIPT_TYPES.P2PKH, + + pubkeys: [ourPubKey], + signatures: [undefined], + }; +} + +function build( + type: string, + input: TxbInput, + allowIncomplete?: boolean, +): Payment | undefined { + const pubkeys = (input.pubkeys || []) as Buffer[]; + let signatures = (input.signatures || []) as Buffer[]; + + switch (type) { + case SCRIPT_TYPES.P2PKH: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + + return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); + } + case SCRIPT_TYPES.P2WPKH: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + + return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); + } + case SCRIPT_TYPES.P2PK: { + if (pubkeys.length === 0) break; + if (signatures.length === 0) break; + + return payments.p2pk({ signature: signatures[0] }); + } + case SCRIPT_TYPES.P2MS: { + const m = input.maxSignatures; + if (allowIncomplete) { + signatures = signatures.map(x => x || ops.OP_0); + } else { + signatures = signatures.filter(x => x); + } + + // if the transaction is not not complete (complete), or if signatures.length === m, validate + // otherwise, the number of OP_0's may be >= m, so don't validate (boo) + const validate = !allowIncomplete || m === signatures.length; + return payments.p2ms( + { m, pubkeys, signatures }, + { allowIncomplete, validate }, + ); + } + case SCRIPT_TYPES.P2SH: { + const redeem = build(input.redeemScriptType!, input, allowIncomplete); + if (!redeem) return; + + return payments.p2sh({ + redeem: { + output: redeem.output || input.redeemScript, + input: redeem.input, + witness: redeem.witness, + }, + }); + } + case SCRIPT_TYPES.P2WSH: { + const redeem = build(input.witnessScriptType!, input, allowIncomplete); + if (!redeem) return; + + return payments.p2wsh({ + redeem: { + output: input.witnessScript, + input: redeem.input, + witness: redeem.witness, + }, + }); + } + } +} + +function canSign(input: TxbInput): boolean { + return ( + input.signScript !== undefined && + input.signType !== undefined && + input.pubkeys !== undefined && + input.signatures !== undefined && + input.signatures.length === input.pubkeys.length && + input.pubkeys.length > 0 && + (input.hasWitness === false || input.value !== undefined) + ); +} + +function signatureHashType(buffer: Buffer): number { + return buffer.readUInt8(buffer.length - 1); +} + +function checkSignArgs(inputs: TxbInput[], signParams: TxbSignArg): void { + if (!PREVOUT_TYPES.has(signParams.prevOutScriptType)) { + throw new TypeError( + `Unknown prevOutScriptType "${signParams.prevOutScriptType}"`, + ); + } + tfMessage( + typeforce.Number, + signParams.vin, + `sign must include vin parameter as Number (input index)`, + ); + tfMessage( + types.Signer, + signParams.keyPair, + `sign must include keyPair parameter as Signer interface`, + ); + tfMessage( + typeforce.maybe(typeforce.Number), + signParams.hashType, + `sign hashType parameter must be a number`, + ); + const prevOutType = (inputs[signParams.vin] || []).prevOutType; + const posType = signParams.prevOutScriptType; + switch (posType) { + case 'p2pkh': + if (prevOutType && prevOutType !== 'pubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2pk': + if (prevOutType && prevOutType !== 'pubkey') { + throw new TypeError( + `input #${signParams.vin} is not of type p2pk: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wpkh': + if (prevOutType && prevOutType !== 'witnesspubkeyhash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2wpkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2ms': + if (prevOutType && prevOutType !== 'multisig') { + throw new TypeError( + `input #${signParams.vin} is not of type p2ms: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2sh-p2wpkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type p2sh-p2wpkh: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2ms': + case 'p2sh-p2pk': + case 'p2sh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.value(undefined), + signParams.witnessScript, + `${posType} requires NO witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires redeemScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.witnessValue, + `${posType} requires NO witnessValue`, + ); + break; + case 'p2wsh-p2ms': + case 'p2wsh-p2pk': + case 'p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'witnessscripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.value(undefined), + signParams.redeemScript, + `${posType} requires NO redeemScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessValue`, + ); + break; + case 'p2sh-p2wsh-p2ms': + case 'p2sh-p2wsh-p2pk': + case 'p2sh-p2wsh-p2pkh': + if (prevOutType && prevOutType !== 'scripthash') { + throw new TypeError( + `input #${signParams.vin} is not of type ${posType}: ${prevOutType}`, + ); + } + tfMessage( + typeforce.Buffer, + signParams.witnessScript, + `${posType} requires witnessScript`, + ); + tfMessage( + typeforce.Buffer, + signParams.redeemScript, + `${posType} requires witnessScript`, + ); + tfMessage( + types.Satoshi, + signParams.witnessValue, + `${posType} requires witnessScript`, + ); + break; + } +} + +function trySign({ + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR, +}: SigningData): void { + // enforce in order signing of public keys + let signed = false; + for (const [i, pubKey] of input.pubkeys!.entries()) { + if (!ourPubKey.equals(pubKey!)) continue; + if (input.signatures![i]) throw new Error('Signature already exists'); + + // TODO: add tests + if (ourPubKey.length !== 33 && input.hasWitness) { + throw new Error( + 'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', + ); + } + + const signature = keyPair.sign(signatureHash, useLowR); + input.signatures![i] = bscript.signature.encode(signature, hashType); + signed = true; + } + + if (!signed) throw new Error('Key pair cannot sign for this input'); +} + +interface SigningData { + input: TxbInput; + ourPubKey: Buffer; + keyPair: Signer; + signatureHash: Buffer; + hashType: number; + useLowR: boolean; +} + +type HashTypeCheck = (hashType: number) => boolean; + +function getSigningData( + network: Network, + inputs: TxbInput[], + needsOutputs: HashTypeCheck, + tx: Transaction, + signParams: number | TxbSignArg, + keyPair?: Signer, + redeemScript?: Buffer, + hashType?: number, + witnessValue?: number, + witnessScript?: Buffer, + useLowR?: boolean, +): SigningData { + let vin: number; + if (typeof signParams === 'number') { + console.warn( + 'DEPRECATED: TransactionBuilder sign method arguments ' + + 'will change in v6, please use the TxbSignArg interface', + ); + vin = signParams; + } else if (typeof signParams === 'object') { + checkSignArgs(inputs, signParams); + ({ + vin, + keyPair, + redeemScript, + hashType, + witnessValue, + witnessScript, + } = signParams); + } else { + throw new TypeError( + 'TransactionBuilder sign first arg must be TxbSignArg or number', + ); + } + if (keyPair === undefined) { + throw new Error('sign requires keypair'); + } + // TODO: remove keyPair.network matching in 4.0.0 + if (keyPair.network && keyPair.network !== network) + throw new TypeError('Inconsistent network'); + if (!inputs[vin]) throw new Error('No input at index: ' + vin); + + hashType = hashType || Transaction.SIGHASH_ALL; + if (needsOutputs(hashType)) throw new Error('Transaction needs outputs'); + + const input = inputs[vin]; + + // if redeemScript was previously provided, enforce consistency + if ( + input.redeemScript !== undefined && + redeemScript && + !input.redeemScript.equals(redeemScript) + ) { + throw new Error('Inconsistent redeemScript'); + } + + const ourPubKey = + keyPair.publicKey || (keyPair.getPublicKey && keyPair.getPublicKey()); + if (!canSign(input)) { + if (witnessValue !== undefined) { + if (input.value !== undefined && input.value !== witnessValue) + throw new Error('Input did not match witnessValue'); + typeforce(types.Satoshi, witnessValue); + input.value = witnessValue; + } + + if (!canSign(input)) { + const prepared = prepareInput( + input, + ourPubKey, + redeemScript, + witnessScript, + ); + + // updates inline + Object.assign(input, prepared); + } + + if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); + } + + // ready to sign + let signatureHash: Buffer; + if (input.hasWitness) { + signatureHash = tx.hashForWitnessV0( + vin, + input.signScript as Buffer, + input.value as number, + hashType, + ); + } else { + signatureHash = tx.hashForSignature( + vin, + input.signScript as Buffer, + hashType, + ); + } + + return { + input, + ourPubKey, + keyPair, + signatureHash, + hashType, + useLowR: !!useLowR, + }; +} diff --git a/ts_src/types.ts b/ts_src/types.ts new file mode 100644 index 000000000..2e4126767 --- /dev/null +++ b/ts_src/types.ts @@ -0,0 +1,59 @@ +const typeforce = require('typeforce'); + +const UINT31_MAX: number = Math.pow(2, 31) - 1; +export function UInt31(value: number): boolean { + return typeforce.UInt32(value) && value <= UINT31_MAX; +} + +export function BIP32Path(value: string): boolean { + return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); +} +BIP32Path.toJSON = (): string => { + return 'BIP32 derivation path'; +}; + +export function Signer(obj: any): boolean { + return ( + (typeforce.Buffer(obj.publicKey) || + typeof obj.getPublicKey === 'function') && + typeof obj.sign === 'function' + ); +} + +const SATOSHI_MAX: number = 21 * 1e14; +export function Satoshi(value: number): boolean { + return typeforce.UInt53(value) && value <= SATOSHI_MAX; +} + +// external dependent types +export const ECPoint = typeforce.quacksLike('Point'); + +// exposed, external API +export const Network = typeforce.compile({ + messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), + bip32: { + public: typeforce.UInt32, + private: typeforce.UInt32, + }, + pubKeyHash: typeforce.UInt8, + scriptHash: typeforce.UInt8, + wif: typeforce.UInt8, +}); + +export const Buffer256bit = typeforce.BufferN(32); +export const Hash160bit = typeforce.BufferN(20); +export const Hash256bit = typeforce.BufferN(32); +export const Number = typeforce.Number; // tslint:disable-line variable-name +export const Array = typeforce.Array; +export const Boolean = typeforce.Boolean; // tslint:disable-line variable-name +export const String = typeforce.String; // tslint:disable-line variable-name +export const Buffer = typeforce.Buffer; +export const Hex = typeforce.Hex; +export const maybe = typeforce.maybe; +export const tuple = typeforce.tuple; +export const UInt8 = typeforce.UInt8; +export const UInt32 = typeforce.UInt32; +export const Function = typeforce.Function; +export const BufferN = typeforce.BufferN; +export const Null = typeforce.Null; +export const oneOf = typeforce.oneOf; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..f770a4531 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2015", + "module": "commonjs", + "outDir": "./src", + "declaration": true, + "declarationDir": "./types", + "rootDir": "./ts_src", + "types": [ + "node" + ], + "allowJs": false, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "esModuleInterop": false, + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": [ + "ts_src/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts", + "node_modules/**/*" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 000000000..1ba998dfb --- /dev/null +++ b/tslint.json @@ -0,0 +1,40 @@ +{ + "defaultSeverity": "error", + "extends": ["tslint:recommended"], + "rules": { + "arrow-parens": [true, "ban-single-arg-parens"], + "curly": false, + "indent": [ + true, + "spaces", + 2 + ], + "interface-name": [false], + "match-default-export-name": true, + "max-classes-per-file": [false], + "member-access": [true, "no-public"], + "no-bitwise": false, + "no-console": false, + "no-empty": [true, "allow-empty-catch"], + "no-implicit-dependencies": true, + "no-return-await": true, + "no-var-requires": false, + "no-unused-expression": false, + "object-literal-sort-keys": false, + "quotemark": [true, "single"], + "typedef": [ + true, + "call-signature", + "arrow-call-signature", + "property-declaration" + ], + "variable-name": [ + true, + "ban-keywords", + "check-format", + "allow-leading-underscore", + "allow-pascal-case" + ] + }, + "rulesDirectory": [] +} diff --git a/types/address.d.ts b/types/address.d.ts new file mode 100644 index 000000000..be0e00a61 --- /dev/null +++ b/types/address.d.ts @@ -0,0 +1,17 @@ +/// +import { Network } from './networks'; +export interface Base58CheckResult { + hash: Buffer; + version: number; +} +export interface Bech32Result { + version: number; + prefix: string; + data: Buffer; +} +export declare function fromBase58Check(address: string): Base58CheckResult; +export declare function fromBech32(address: string): Bech32Result; +export declare function toBase58Check(hash: Buffer, version: number): string; +export declare function toBech32(data: Buffer, version: number, prefix: string): string; +export declare function fromOutputScript(output: Buffer, network?: Network): string; +export declare function toOutputScript(address: string, network?: Network): Buffer; diff --git a/types/block.d.ts b/types/block.d.ts new file mode 100644 index 000000000..dd4fc5569 --- /dev/null +++ b/types/block.d.ts @@ -0,0 +1,29 @@ +/// +import { Transaction } from './transaction'; +export declare class Block { + static fromBuffer(buffer: Buffer): Block; + static fromHex(hex: string): Block; + static calculateTarget(bits: number): Buffer; + static calculateMerkleRoot(transactions: Transaction[], forWitness?: boolean): Buffer; + version: number; + prevHash?: Buffer; + merkleRoot?: Buffer; + timestamp: number; + witnessCommit?: Buffer; + bits: number; + nonce: number; + transactions?: Transaction[]; + getWitnessCommit(): Buffer | null; + hasWitnessCommit(): boolean; + hasWitness(): boolean; + byteLength(headersOnly: boolean): number; + getHash(): Buffer; + getId(): string; + getUTCDate(): Date; + toBuffer(headersOnly: boolean): Buffer; + toHex(headersOnly: boolean): string; + checkTxRoots(): boolean; + checkProofOfWork(): boolean; + private __checkMerkleRoot; + private __checkWitnessCommit; +} diff --git a/types/bufferutils.d.ts b/types/bufferutils.d.ts new file mode 100644 index 000000000..2686e4ee0 --- /dev/null +++ b/types/bufferutils.d.ts @@ -0,0 +1,4 @@ +/// +export declare function readUInt64LE(buffer: Buffer, offset: number): number; +export declare function writeUInt64LE(buffer: Buffer, value: number, offset: number): number; +export declare function reverseBuffer(buffer: Buffer): Buffer; diff --git a/types/classify.d.ts b/types/classify.d.ts new file mode 100644 index 000000000..67f913b53 --- /dev/null +++ b/types/classify.d.ts @@ -0,0 +1,16 @@ +/// +declare const types: { + P2MS: string; + NONSTANDARD: string; + NULLDATA: string; + P2PK: string; + P2PKH: string; + P2SH: string; + P2WPKH: string; + P2WSH: string; + WITNESS_COMMITMENT: string; +}; +declare function classifyOutput(script: Buffer): string; +declare function classifyInput(script: Buffer, allowIncomplete: boolean): string; +declare function classifyWitness(script: Buffer[], allowIncomplete: boolean): string; +export { classifyInput as input, classifyOutput as output, classifyWitness as witness, types, }; diff --git a/types/crypto.d.ts b/types/crypto.d.ts new file mode 100644 index 000000000..174368172 --- /dev/null +++ b/types/crypto.d.ts @@ -0,0 +1,6 @@ +/// +export declare function ripemd160(buffer: Buffer): Buffer; +export declare function sha1(buffer: Buffer): Buffer; +export declare function sha256(buffer: Buffer): Buffer; +export declare function hash160(buffer: Buffer): Buffer; +export declare function hash256(buffer: Buffer): Buffer; diff --git a/types/ecpair.d.ts b/types/ecpair.d.ts new file mode 100644 index 000000000..07d71d413 --- /dev/null +++ b/types/ecpair.d.ts @@ -0,0 +1,45 @@ +/// +import { Network } from './networks'; +interface ECPairOptions { + compressed?: boolean; + network?: Network; + rng?(arg0: number): Buffer; +} +export interface Signer { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Buffer; + getPublicKey?(): Buffer; +} +export interface SignerAsync { + publicKey: Buffer; + network?: Network; + sign(hash: Buffer, lowR?: boolean): Promise; + getPublicKey?(): Buffer; +} +export interface ECPairInterface extends Signer { + compressed: boolean; + network: Network; + lowR: boolean; + privateKey?: Buffer; + toWIF(): string; + verify(hash: Buffer, signature: Buffer): boolean; +} +declare class ECPair implements ECPairInterface { + private __D?; + private __Q?; + compressed: boolean; + network: Network; + lowR: boolean; + constructor(__D?: Buffer | undefined, __Q?: Buffer | undefined, options?: ECPairOptions); + readonly privateKey: Buffer | undefined; + readonly publicKey: Buffer; + toWIF(): string; + sign(hash: Buffer, lowR?: boolean): Buffer; + verify(hash: Buffer, signature: Buffer): boolean; +} +declare function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair; +declare function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair; +declare function fromWIF(wifString: string, network?: Network | Network[]): ECPair; +declare function makeRandom(options?: ECPairOptions): ECPair; +export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF }; diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 000000000..fc5a932e5 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,19 @@ +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 { Psbt } from './psbt'; +export { OPS as opcodes } from './script'; +export { Transaction } from './transaction'; +export { TransactionBuilder } from './transaction_builder'; +export { BIP32Interface } from 'bip32'; +export { ECPairInterface, Signer, SignerAsync } from './ecpair'; +export { Network } from './networks'; +export { Payment, PaymentOpts, Stack, StackElement } from './payments'; +export { OpCode } from './script'; +export { Input as TxInput, Output as TxOutput } from './transaction'; diff --git a/types/networks.d.ts b/types/networks.d.ts new file mode 100644 index 000000000..d5590fd1b --- /dev/null +++ b/types/networks.d.ts @@ -0,0 +1,16 @@ +export interface Network { + messagePrefix: string; + bech32: string; + bip32: Bip32; + pubKeyHash: number; + scriptHash: number; + wif: number; +} +interface Bip32 { + public: number; + private: number; +} +export declare const bitcoin: Network; +export declare const regtest: Network; +export declare const testnet: Network; +export {}; diff --git a/types/payments/embed.d.ts b/types/payments/embed.d.ts new file mode 100644 index 000000000..76a9ed28f --- /dev/null +++ b/types/payments/embed.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2data(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/index.d.ts b/types/payments/index.d.ts new file mode 100644 index 000000000..7f17b796e --- /dev/null +++ b/types/payments/index.d.ts @@ -0,0 +1,35 @@ +/// +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 { + name?: string; + 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 declare type PaymentFunction = () => Payment; +export interface PaymentOpts { + validate?: boolean; + allowIncomplete?: boolean; +} +export declare type StackElement = Buffer | number; +export declare type Stack = StackElement[]; +export declare type StackFunction = () => Stack; +export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }; diff --git a/types/payments/lazy.d.ts b/types/payments/lazy.d.ts new file mode 100644 index 000000000..3463906d8 --- /dev/null +++ b/types/payments/lazy.d.ts @@ -0,0 +1,2 @@ +export declare function prop(object: {}, name: string, f: () => any): void; +export declare function value(f: () => T): () => T; diff --git a/types/payments/p2ms.d.ts b/types/payments/p2ms.d.ts new file mode 100644 index 000000000..199e02915 --- /dev/null +++ b/types/payments/p2ms.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2ms(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2pk.d.ts b/types/payments/p2pk.d.ts new file mode 100644 index 000000000..d7e824d6c --- /dev/null +++ b/types/payments/p2pk.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2pk(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2pkh.d.ts b/types/payments/p2pkh.d.ts new file mode 100644 index 000000000..a33eeb030 --- /dev/null +++ b/types/payments/p2pkh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2pkh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2sh.d.ts b/types/payments/p2sh.d.ts new file mode 100644 index 000000000..bb76772e7 --- /dev/null +++ b/types/payments/p2sh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2sh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2wpkh.d.ts b/types/payments/p2wpkh.d.ts new file mode 100644 index 000000000..360939119 --- /dev/null +++ b/types/payments/p2wpkh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2wpkh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/payments/p2wsh.d.ts b/types/payments/p2wsh.d.ts new file mode 100644 index 000000000..d9ae925e8 --- /dev/null +++ b/types/payments/p2wsh.d.ts @@ -0,0 +1,2 @@ +import { Payment, PaymentOpts } from './index'; +export declare function p2wsh(a: Payment, opts?: PaymentOpts): Payment; diff --git a/types/psbt.d.ts b/types/psbt.d.ts new file mode 100644 index 000000000..04a3cfd45 --- /dev/null +++ b/types/psbt.d.ts @@ -0,0 +1,116 @@ +/// +import { Psbt as PsbtBase } from 'bip174'; +import { KeyValue, PsbtGlobalUpdate, PsbtInputUpdate, PsbtOutputUpdate, TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces'; +import { Signer, SignerAsync } from './ecpair'; +import { Network } from './networks'; +import { Transaction } from './transaction'; +/** + * Psbt class can parse and generate a PSBT binary based off of the BIP174. + * There are 6 roles that this class fulfills. (Explained in BIP174) + * + * Creator: This can be done with `new Psbt()` + * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, + * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to + * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, + * `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)` + * addInput requires hash: Buffer | string; and index: number; as attributes + * and can also include any attributes that are used in updateInput method. + * addOutput requires script: Buffer; and value: number; and likewise can include + * data for updateOutput. + * For a list of what attributes should be what types. Check the bip174 library. + * Also, check the integration tests for some examples of usage. + * Signer: There are a few methods. signAllInputs and signAllInputsAsync, which will search all input + * information for your pubkey or pubkeyhash, and only sign inputs where it finds + * your info. Or you can explicitly sign a specific input with signInput and + * signInputAsync. For the async methods you can create a SignerAsync object + * and use something like a hardware wallet to sign with. (You must implement this) + * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` + * the psbt calling combine will always have precedence when a conflict occurs. + * Combine checks if the internal bitcoin transaction is the same, so be sure that + * all sequences, version, locktime, etc. are the same before combining. + * Input Finalizer: This role is fairly important. Not only does it need to construct + * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. + * Before running `psbt.finalizeAllInputs()` please run `psbt.validateSignaturesOfAllInputs()` + * Running any finalize method will delete any data in the input(s) that are no longer + * needed due to the finalized scripts containing the information. + * Transaction Extractor: This role will perform some checks before returning a + * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. + */ +export declare class Psbt { + readonly data: PsbtBase; + static fromBase64(data: string, opts?: PsbtOptsOptional): Psbt; + static fromHex(data: string, opts?: PsbtOptsOptional): Psbt; + static fromBuffer(buffer: Buffer, opts?: PsbtOptsOptional): Psbt; + private __CACHE; + private opts; + constructor(opts?: PsbtOptsOptional, data?: PsbtBase); + readonly inputCount: number; + combine(...those: Psbt[]): this; + clone(): Psbt; + setMaximumFeeRate(satoshiPerByte: number): void; + setVersion(version: number): this; + setLocktime(locktime: number): this; + setInputSequence(inputIndex: number, sequence: number): this; + addInputs(inputDatas: TransactionInput[]): this; + addInput(inputData: TransactionInput): this; + addOutputs(outputDatas: TransactionOutput[]): this; + addOutput(outputData: TransactionOutput): this; + extractTransaction(disableFeeCheck?: boolean): Transaction; + getFeeRate(): number; + finalizeAllInputs(): this; + finalizeInput(inputIndex: number): this; + validateSignaturesOfAllInputs(): boolean; + validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean; + signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this; + signAllInputsHDAsync(hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise; + signInputHD(inputIndex: number, hdKeyPair: HDSigner, sighashTypes?: number[]): this; + signInputHDAsync(inputIndex: number, hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise; + signAllInputs(keyPair: Signer, sighashTypes?: number[]): this; + signAllInputsAsync(keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise; + signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this; + signInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise; + toBuffer(): Buffer; + toHex(): string; + toBase64(): string; + updateGlobal(updateData: PsbtGlobalUpdate): this; + updateInput(inputIndex: number, updateData: PsbtInputUpdate): this; + updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this; + addUnknownKeyValToGlobal(keyVal: KeyValue): this; + addUnknownKeyValToInput(inputIndex: number, keyVal: KeyValue): this; + addUnknownKeyValToOutput(outputIndex: number, keyVal: KeyValue): this; + clearFinalizedInput(inputIndex: number): this; +} +interface PsbtOptsOptional { + network?: Network; + maximumFeeRate?: number; +} +interface HDSignerBase { + /** + * DER format compressed publicKey buffer + */ + publicKey: Buffer; + /** + * The first 4 bytes of the sha256-ripemd160 of the publicKey + */ + fingerprint: Buffer; +} +interface HDSigner extends HDSignerBase { + /** + * The path string must match /^m(\/\d+'?)+$/ + * ex. m/44'/0'/0'/1/23 levels with ' must be hard derivations + */ + derivePath(path: string): HDSigner; + /** + * Input hash (the "message digest") for the signature algorithm + * Return a 64 byte signature (32 byte r and 32 byte s in that order) + */ + sign(hash: Buffer): Buffer; +} +/** + * Same as above but with async sign method + */ +interface HDSignerAsync extends HDSignerBase { + derivePath(path: string): HDSignerAsync; + sign(hash: Buffer): Promise; +} +export {}; diff --git a/types/script.d.ts b/types/script.d.ts new file mode 100644 index 000000000..52ad4ddf5 --- /dev/null +++ b/types/script.d.ts @@ -0,0 +1,19 @@ +/// +import { Stack } from './payments'; +import * as scriptNumber from './script_number'; +import * as scriptSignature from './script_signature'; +export declare type OpCode = number; +export declare const OPS: { + [index: string]: number; +}; +export declare function isPushOnly(value: Stack): boolean; +export declare function compile(chunks: Buffer | Stack): Buffer; +export declare function decompile(buffer: Buffer | Array): Array | null; +export declare function toASM(chunks: Buffer | Array): string; +export declare function fromASM(asm: string): Buffer; +export declare function toStack(chunks: Buffer | Array): Buffer[]; +export declare function isCanonicalPubKey(buffer: Buffer): boolean; +export declare function isDefinedHashType(hashType: number): boolean; +export declare function isCanonicalScriptSignature(buffer: Buffer): boolean; +export declare const number: typeof scriptNumber; +export declare const signature: typeof scriptSignature; diff --git a/types/script_number.d.ts b/types/script_number.d.ts new file mode 100644 index 000000000..015bb8943 --- /dev/null +++ b/types/script_number.d.ts @@ -0,0 +1,3 @@ +/// +export declare function decode(buffer: Buffer, maxLength?: number, minimal?: boolean): number; +export declare function encode(_number: number): Buffer; diff --git a/types/script_signature.d.ts b/types/script_signature.d.ts new file mode 100644 index 000000000..2057dd99f --- /dev/null +++ b/types/script_signature.d.ts @@ -0,0 +1,8 @@ +/// +interface ScriptSignature { + signature: Buffer; + hashType: number; +} +export declare function decode(buffer: Buffer): ScriptSignature; +export declare function encode(signature: Buffer, hashType: number): Buffer; +export {}; diff --git a/types/templates/multisig/index.d.ts b/types/templates/multisig/index.d.ts new file mode 100644 index 000000000..f6288e2a4 --- /dev/null +++ b/types/templates/multisig/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output }; diff --git a/types/templates/multisig/input.d.ts b/types/templates/multisig/input.d.ts new file mode 100644 index 000000000..a207dd6f6 --- /dev/null +++ b/types/templates/multisig/input.d.ts @@ -0,0 +1,6 @@ +/// +import { Stack } from '../../payments'; +export declare function check(script: Buffer | Stack, allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/multisig/output.d.ts b/types/templates/multisig/output.d.ts new file mode 100644 index 000000000..a207dd6f6 --- /dev/null +++ b/types/templates/multisig/output.d.ts @@ -0,0 +1,6 @@ +/// +import { Stack } from '../../payments'; +export declare function check(script: Buffer | Stack, allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/nulldata.d.ts b/types/templates/nulldata.d.ts new file mode 100644 index 000000000..aff3cd0a5 --- /dev/null +++ b/types/templates/nulldata.d.ts @@ -0,0 +1,9 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} +declare const output: { + check: typeof check; +}; +export { output }; diff --git a/types/templates/pubkey/index.d.ts b/types/templates/pubkey/index.d.ts new file mode 100644 index 000000000..f6288e2a4 --- /dev/null +++ b/types/templates/pubkey/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output }; diff --git a/types/templates/pubkey/input.d.ts b/types/templates/pubkey/input.d.ts new file mode 100644 index 000000000..c4ffeab4e --- /dev/null +++ b/types/templates/pubkey/input.d.ts @@ -0,0 +1,6 @@ +/// +import { Stack } from '../../payments'; +export declare function check(script: Buffer | Stack): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/pubkey/output.d.ts b/types/templates/pubkey/output.d.ts new file mode 100644 index 000000000..c4ffeab4e --- /dev/null +++ b/types/templates/pubkey/output.d.ts @@ -0,0 +1,6 @@ +/// +import { Stack } from '../../payments'; +export declare function check(script: Buffer | Stack): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/pubkeyhash/index.d.ts b/types/templates/pubkeyhash/index.d.ts new file mode 100644 index 000000000..f6288e2a4 --- /dev/null +++ b/types/templates/pubkeyhash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output }; diff --git a/types/templates/pubkeyhash/input.d.ts b/types/templates/pubkeyhash/input.d.ts new file mode 100644 index 000000000..c4ffeab4e --- /dev/null +++ b/types/templates/pubkeyhash/input.d.ts @@ -0,0 +1,6 @@ +/// +import { Stack } from '../../payments'; +export declare function check(script: Buffer | Stack): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/pubkeyhash/output.d.ts b/types/templates/pubkeyhash/output.d.ts new file mode 100644 index 000000000..091758ffe --- /dev/null +++ b/types/templates/pubkeyhash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/scripthash/index.d.ts b/types/templates/scripthash/index.d.ts new file mode 100644 index 000000000..f6288e2a4 --- /dev/null +++ b/types/templates/scripthash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output }; diff --git a/types/templates/scripthash/input.d.ts b/types/templates/scripthash/input.d.ts new file mode 100644 index 000000000..a04d03ca6 --- /dev/null +++ b/types/templates/scripthash/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array, allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/scripthash/output.d.ts b/types/templates/scripthash/output.d.ts new file mode 100644 index 000000000..091758ffe --- /dev/null +++ b/types/templates/scripthash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnesscommitment/index.d.ts b/types/templates/witnesscommitment/index.d.ts new file mode 100644 index 000000000..c37ee7c82 --- /dev/null +++ b/types/templates/witnesscommitment/index.d.ts @@ -0,0 +1,2 @@ +import * as output from './output'; +export { output }; diff --git a/types/templates/witnesscommitment/output.d.ts b/types/templates/witnesscommitment/output.d.ts new file mode 100644 index 000000000..778c9a157 --- /dev/null +++ b/types/templates/witnesscommitment/output.d.ts @@ -0,0 +1,7 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} +export declare function encode(commitment: Buffer): Buffer; +export declare function decode(buffer: Buffer): Buffer; diff --git a/types/templates/witnesspubkeyhash/index.d.ts b/types/templates/witnesspubkeyhash/index.d.ts new file mode 100644 index 000000000..f6288e2a4 --- /dev/null +++ b/types/templates/witnesspubkeyhash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output }; diff --git a/types/templates/witnesspubkeyhash/input.d.ts b/types/templates/witnesspubkeyhash/input.d.ts new file mode 100644 index 000000000..c4ffeab4e --- /dev/null +++ b/types/templates/witnesspubkeyhash/input.d.ts @@ -0,0 +1,6 @@ +/// +import { Stack } from '../../payments'; +export declare function check(script: Buffer | Stack): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnesspubkeyhash/output.d.ts b/types/templates/witnesspubkeyhash/output.d.ts new file mode 100644 index 000000000..091758ffe --- /dev/null +++ b/types/templates/witnesspubkeyhash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnessscripthash/index.d.ts b/types/templates/witnessscripthash/index.d.ts new file mode 100644 index 000000000..f6288e2a4 --- /dev/null +++ b/types/templates/witnessscripthash/index.d.ts @@ -0,0 +1,3 @@ +import * as input from './input'; +import * as output from './output'; +export { input, output }; diff --git a/types/templates/witnessscripthash/input.d.ts b/types/templates/witnessscripthash/input.d.ts new file mode 100644 index 000000000..b2a6e8a20 --- /dev/null +++ b/types/templates/witnessscripthash/input.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(chunks: Buffer[], allowIncomplete?: boolean): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/templates/witnessscripthash/output.d.ts b/types/templates/witnessscripthash/output.d.ts new file mode 100644 index 000000000..091758ffe --- /dev/null +++ b/types/templates/witnessscripthash/output.d.ts @@ -0,0 +1,5 @@ +/// +export declare function check(script: Buffer | Array): boolean; +export declare namespace check { + var toJSON: () => string; +} diff --git a/types/transaction.d.ts b/types/transaction.d.ts new file mode 100644 index 000000000..9bdba196c --- /dev/null +++ b/types/transaction.d.ts @@ -0,0 +1,60 @@ +/// +export interface BlankOutput { + script: Buffer; + valueBuffer: Buffer; +} +export interface Output { + script: Buffer; + value: number; +} +declare type OpenOutput = Output | BlankOutput; +export interface Input { + hash: Buffer; + index: number; + script: Buffer; + sequence: number; + witness: Buffer[]; +} +export declare class Transaction { + static readonly DEFAULT_SEQUENCE = 4294967295; + static readonly SIGHASH_ALL = 1; + static readonly SIGHASH_NONE = 2; + static readonly SIGHASH_SINGLE = 3; + static readonly SIGHASH_ANYONECANPAY = 128; + static readonly ADVANCED_TRANSACTION_MARKER = 0; + static readonly ADVANCED_TRANSACTION_FLAG = 1; + static fromBuffer(buffer: Buffer, _NO_STRICT?: boolean): Transaction; + static fromHex(hex: string): Transaction; + static isCoinbaseHash(buffer: Buffer): boolean; + version: number; + locktime: number; + ins: Input[]; + outs: OpenOutput[]; + isCoinbase(): boolean; + addInput(hash: Buffer, index: number, sequence?: number, scriptSig?: Buffer): number; + addOutput(scriptPubKey: Buffer, value: number): number; + hasWitnesses(): boolean; + weight(): number; + virtualSize(): number; + byteLength(): number; + clone(): Transaction; + /** + * 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: number, prevOutScript: Buffer, hashType: number): Buffer; + hashForWitnessV0(inIndex: number, prevOutScript: Buffer, value: number, hashType: number): Buffer; + getHash(forWitness?: boolean): Buffer; + getId(): string; + toBuffer(buffer?: Buffer, initialOffset?: number): Buffer; + toHex(): string; + setInputScript(index: number, scriptSig: Buffer): void; + setWitness(index: number, witness: Buffer[]): void; + private __byteLength; + private __toBuffer; +} +export {}; diff --git a/types/transaction_builder.d.ts b/types/transaction_builder.d.ts new file mode 100644 index 000000000..2799464bf --- /dev/null +++ b/types/transaction_builder.d.ts @@ -0,0 +1,38 @@ +/// +import { Signer } from './ecpair'; +import { Network } from './networks'; +import { Transaction } from './transaction'; +interface TxbSignArg { + prevOutScriptType: string; + vin: number; + keyPair: Signer; + redeemScript?: Buffer; + hashType?: number; + witnessValue?: number; + witnessScript?: Buffer; +} +export declare class TransactionBuilder { + network: Network; + maximumFeeRate: number; + static fromTransaction(transaction: Transaction, network?: Network): TransactionBuilder; + private __PREV_TX_SET; + private __INPUTS; + private __TX; + private __USE_LOW_R; + constructor(network?: Network, maximumFeeRate?: number); + setLowR(setting?: boolean): boolean; + setLockTime(locktime: number): void; + setVersion(version: number): void; + addInput(txHash: Buffer | string | Transaction, vout: number, sequence?: number, prevOutScript?: Buffer): number; + addOutput(scriptPubKey: string | Buffer, value: number): number; + build(): Transaction; + buildIncomplete(): Transaction; + sign(signParams: number | TxbSignArg, keyPair?: Signer, redeemScript?: Buffer, hashType?: number, witnessValue?: number, witnessScript?: Buffer): void; + private __addInputUnsafe; + private __build; + private __canModifyInputs; + private __needsOutputs; + private __canModifyOutputs; + private __overMaximumFees; +} +export {}; diff --git a/types/types.d.ts b/types/types.d.ts new file mode 100644 index 000000000..e7c588dcf --- /dev/null +++ b/types/types.d.ts @@ -0,0 +1,26 @@ +export declare function UInt31(value: number): boolean; +export declare function BIP32Path(value: string): boolean; +export declare namespace BIP32Path { + var toJSON: () => string; +} +export declare function Signer(obj: any): boolean; +export declare function Satoshi(value: number): boolean; +export declare const ECPoint: any; +export declare const Network: any; +export declare const Buffer256bit: any; +export declare const Hash160bit: any; +export declare const Hash256bit: any; +export declare const Number: any; +export declare const Array: any; +export declare const Boolean: any; +export declare const String: any; +export declare const Buffer: any; +export declare const Hex: any; +export declare const maybe: any; +export declare const tuple: any; +export declare const UInt8: any; +export declare const UInt32: any; +export declare const Function: any; +export declare const BufferN: any; +export declare const Null: any; +export declare const oneOf: any;