bitcoinjs-lib
Advanced tools
Comparing version 0.2.0 to 1.0.0
{ | ||
"name": "bitcoinjs-lib", | ||
"version": "0.2.0", | ||
"version": "1.0.0", | ||
"description": "Client-side Bitcoin JavaScript library", | ||
@@ -12,3 +12,24 @@ "main": "./src/index.js", | ||
], | ||
"author": "Stefan Thomas <justmoon@members.fsf.org> (http://www.justmoon.net)", | ||
"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" | ||
} | ||
], | ||
"repository": { | ||
@@ -19,17 +40,21 @@ "type": "git", | ||
"devDependencies": { | ||
"browserify": "4.1.11", | ||
"coveralls": "~2.10.0", | ||
"helloblock-js": "^0.2.1", | ||
"istanbul": "0.1.30", | ||
"jshint": "2.5.1", | ||
"mocha": "1.18.2", | ||
"istanbul": "0.1.30", | ||
"uglify-js": "2.4.13", | ||
"node-browserify": "https://github.com/substack/node-browserify/tarball/master", | ||
"sinon": "1.9.0" | ||
"mocha-lcov-reporter": "0.0.1", | ||
"sinon": "1.9.0", | ||
"uglify-js": "2.4.13" | ||
}, | ||
"testling": { | ||
"browsers": [ | ||
"android-browser/4.2..latest", | ||
"chrome/20..latest", | ||
"firefox/21..latest", | ||
"safari/latest", | ||
"ipad/6..latest", | ||
"iphone/6..latest", | ||
"opera/15..latest", | ||
"iphone/6..latest", | ||
"ipad/6..latest", | ||
"android-browser/4.2..latest" | ||
"safari/latest" | ||
], | ||
@@ -40,10 +65,21 @@ "harness": "mocha-bdd", | ||
"scripts": { | ||
"test": "./node_modules/.bin/istanbul test ./node_modules/.bin/_mocha -- --reporter list test/*.js", | ||
"compile": "./node_modules/.bin/browserify ./src/index.js -s Bitcoin | ./node_modules/.bin/uglifyjs > bitcoinjs-min.js", | ||
"coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --reporter list test/*.js", | ||
"compile": "./node_modules/.bin/browserify ./src/index.js -s Bitcoin | ./node_modules/.bin/uglifyjs > bitcoinjs-min.js" | ||
"coveralls": "npm run-script coverage && node ./node_modules/.bin/coveralls < coverage/lcov.info", | ||
"integration": "./node_modules/.bin/_mocha --reporter list test/integration/*.js", | ||
"jshint": "./node_modules/.bin/jshint --config jshint.json src/*.js ; true", | ||
"test": "npm run-script unit", | ||
"unit": "./node_modules/.bin/istanbul test ./node_modules/.bin/_mocha -- --reporter list `find test -maxdepth 1 -not -type d`" | ||
}, | ||
"browser": { | ||
"crypto": "crypto-browserify" | ||
}, | ||
"dependencies": { | ||
"bigi": "1.1.0", | ||
"bs58": "1.1.0", | ||
"crypto-js": "3.1.2-3", | ||
"secure-random": "0.2.1" | ||
"crypto-browserify": "2.1.8", | ||
"ecurve": "0.10.0", | ||
"secure-random": "1.1.1" | ||
} | ||
} |
109
README.md
@@ -1,20 +0,35 @@ | ||
# bitcoinjs-lib | ||
# BitcoinJS (bitcoinjs-lib) | ||
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) | ||
[![Build Status](https://travis-ci.org/bitcoinjs/bitcoinjs-lib.png?branch=master)](https://travis-ci.org/bitcoinjs/bitcoinjs-lib) [![Coverage Status](https://coveralls.io/repos/bitcoinjs/bitcoinjs-lib/badge.png)](https://coveralls.io/r/bitcoinjs/bitcoinjs-lib) | ||
[![NPM](https://nodei.co/npm/bitcoinjs-lib.png)](https://nodei.co/npm/bitcoinjs-lib/) | ||
[![Browser Support](https://ci.testling.com/bitcoinjs/bitcoinjs-lib.png)](https://ci.testling.com/bitcoinjs/bitcoinjs-lib) | ||
A pure JavaScript Bitcoin library for node.js and browsers. Backed by (slowly improving) testing, proven by over a million wallet users. The backbone for almost all Bitcoin web wallets in production today. | ||
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. | ||
**Warning**: Master is not stable. Expect the interface to change rapidly, including some of the examples below. This is not the original bitcoinjs-lib that was not updated for a while. The current bitcoinjs-lib has been refactored to clean things up, add new functionality and merge improvements from the community. If you are looking for the original, it will be tagged as `0.1.3`. We will use `0.2.x` for releases based on these changes, so be sure to use the `0.1.3` tag if you need the original version. | ||
## Features | ||
- Bitcoin Testnet and Mainnet (production) support | ||
- [HD Wallets](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) | ||
- Highly secure random private key / address generation using [window.crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Window.crypto) | ||
- ECDSA signing and verification | ||
- Transaction creation (pay-to-pubkey-hash), support for multisignature transactions | ||
- A (somewhat incomplete) wallet implementation, improvements ongoing | ||
- 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). | ||
## 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. | ||
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. | ||
## Installation | ||
@@ -24,3 +39,2 @@ | ||
Note: The npm version is currently out of date, are working to resolve this. The best way to use the latest code is to clone the repository. | ||
@@ -37,10 +51,17 @@ ## Setup | ||
### Browser | ||
Compile `bitcoinjs-min.js` with the following command: | ||
From the repository: Compile `bitcoinjs-min.js` with the following command: | ||
$ npm run-script compile | ||
After loading this file in your browser, you will be able to use the global `Bitcoin` object. | ||
From NPM: | ||
$ npm -g install bitcoinjs-lib browserify uglify-js | ||
$ browserify -r bitcoinjs-lib -s Bitcoin | uglifyjs > bitcoinjs.min.js | ||
After loading this file in your browser, you will be able to use the global `bitcoin` object. | ||
## Usage | ||
@@ -50,2 +71,3 @@ | ||
### Generating a Bitcoin address | ||
@@ -55,10 +77,10 @@ | ||
key = new Bitcoin.ECKey() | ||
key = bitcoin.ECKey.makeRandom() | ||
// Print your private key (a hex string) | ||
console.log(key.toString()) | ||
// Print your private key (in WIF format) | ||
console.log(key.toWIF()) | ||
// => 8c112cf628362ecf4d482f68af2dbb50c8a2cb90d226215de925417aa9336a48 | ||
// Print your public key (defaults to a Bitcoin address) | ||
console.log(key.getPub().getAddress()) | ||
// Print your public key (toString defaults to a Bitcoin address) | ||
console.log(key.pub.getAddress().toString()) | ||
// => 14bZ7YWde4KdRb5YN7GYkToz3EHVCvRxkF | ||
@@ -70,3 +92,3 @@ ``` | ||
```javascript | ||
tx = new Bitcoin.Transaction() | ||
tx = new bitcoin.Transaction() | ||
@@ -79,4 +101,4 @@ // Add the input (who is paying) of the form [previous transaction hash, index of the output to use] | ||
// Initialize a private key using hex | ||
key = new Bitcoin.ECKey("8c112cf628362ecf4d482f68af2dbb50c8a2cb90d226215de925417aa9336a48") | ||
// Initialize a private key using WIF | ||
key = bitcoin.ECKey.fromWIF("L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy") | ||
@@ -87,3 +109,3 @@ // Sign the first input with the new key | ||
// Print transaction serialized as hex | ||
console.log(tx.serializeHex()) | ||
console.log(tx.toHex()) | ||
// => 0100000001313eb630b128102b60241ca895f1d0ffca2170d5a0990e094f2182c102ab94aa000000008a47304402200169f1f844936dc60df54e812345f5dd3e6681fea52e33c25154ad9cc23a330402204381ed8e73d74a95b15f312f33d5a0072c7a12dd6c3294df6e8efbe4aff27426014104e75628573696aed32d7656fb35e9c71ea08eb6492837e13d2662b9a36821d0fff992692fd14d74fdec20fae29128ba12653249cbeef521fc5eba84dde0689f27ffffffff01983a0000000000001976a914ad618cf4333b3b248f9744e8e81db2964d0ae39788ac00000000 | ||
@@ -95,12 +117,23 @@ | ||
## Projects utilizing bitcoinjs-lib | ||
## Projects utilizing BitcoinJS | ||
- [Blockchain.info Wallet](http://blockchain.info/wallet) | ||
- [Bitaddress.org](https://www.bitaddress.org) | ||
- [Coinpunk](https://coinpunk.com) | ||
- [DarkWallet](https://darkwallet.unsystem.net) | ||
- [Hive Wallet](https://www.hivewallet.com) | ||
- [Justchain Exchange](https://justcoin.com) | ||
- [Skyhook ATM](http://projectskyhook.com) | ||
- [BitAddress](https://www.bitaddress.org) | ||
- [Blockchain.info](https://blockchain.info/wallet) | ||
- [Brainwallet](https://brainwallet.github.io) | ||
- [Dark Wallet](https://darkwallet.unsystem.net) | ||
- [Dogechain Wallet](https://dogechain.info) | ||
- [GreenAddress](https://greenaddress.it) | ||
Feel free to send pull requests to have your project/startup listed here. | ||
## Contributors | ||
Stefan Thomas is the inventor and creator of this project. His pioneering work made Bitcoin web wallets possible. | ||
Since then, many people have contributed. [Click here](https://github.com/bitcoinjs/bitcoinjs-lib/graphs/contributors) to see the comprehensive list. | ||
Daniel Cousens, Wei Lu, JP Richardson and Kyle Drake lead the major refactor of the library from 0.1.3 to 1.0.0. | ||
## Contributing | ||
@@ -114,6 +147,17 @@ | ||
### Running the test suite | ||
$ npm test | ||
$ npm run-script coverage | ||
## Complementing Libraries | ||
- [bip39](https://github.com/weilu/bip39) - Wei Lu's Mnemonic code generator | ||
- [BCoin](https://github.com/indutny/bcoin) - BIP37 / Bloom Filters / SPV client | ||
- [scryptsy](https://github.com/cryptocoinjs/scryptsy) - Private key encryption (BIP38) | ||
- [insight](https://github.com/bitpay/insight) - A bitcoin blockchain API for web wallets. | ||
## Alternatives | ||
@@ -128,2 +172,3 @@ | ||
## Copyright | ||
@@ -133,11 +178,1 @@ | ||
Released under MIT license | ||
http://bitcoinjs.org/ | ||
JSBN (c) 2003-2005 Tom Wu | ||
Released under BSD license | ||
http://www-cs-students.stanford.edu/~tjw/jsbn/ | ||
CryptoJS (c) 2009–2012 by Jeff Mott | ||
Released under New BSD license | ||
http://code.google.com/p/crypto-js/ | ||
@@ -1,65 +0,64 @@ | ||
var base58 = require('./base58') | ||
var assert = require('assert') | ||
var base58check = require('./base58check') | ||
var convert = require('./convert') | ||
var error = require('./util').error | ||
var mainnet = require('./network').mainnet.addressVersion | ||
var networks = require('./networks') | ||
var scripts = require('./scripts') | ||
function Address(bytes, version) { | ||
if (!(this instanceof Address)) { | ||
return new Address(bytes, version) | ||
} | ||
function findScriptTypeByVersion(version) { | ||
for (var networkName in networks) { | ||
var network = networks[networkName] | ||
if (bytes instanceof Address) { | ||
this.hash = bytes.hash | ||
this.version = bytes.version | ||
if (version === network.pubKeyHash) return 'pubkeyhash' | ||
if (version === network.scriptHash) return 'scripthash' | ||
} | ||
else if (typeof bytes === 'string') { | ||
if (bytes.length <= 35) { | ||
var decode = base58check.decode(bytes) | ||
} | ||
this.hash = decode.payload | ||
this.version = decode.version | ||
} | ||
else if (bytes.length <= 40) { | ||
this.hash = convert.hexToBytes(bytes) | ||
this.version = version || mainnet | ||
} | ||
else { | ||
error('invalid or unrecognized input') | ||
} | ||
} | ||
else { | ||
this.hash = bytes | ||
this.version = version || mainnet | ||
} | ||
function Address(hash, version) { | ||
assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) | ||
assert.strictEqual(hash.length, 20, 'Invalid hash length') | ||
assert.strictEqual(version & 0xff, version, 'Invalid version byte') | ||
this.hash = hash | ||
this.version = version | ||
} | ||
/** | ||
* Serialize this object as a standard Bitcoin address. | ||
* Returns the address as a base58-encoded string in the standardized format. | ||
*/ | ||
Address.prototype.toString = function () { | ||
return base58check.encode(this.hash.slice(0), this.version) | ||
// Import functions | ||
Address.fromBase58Check = function(string) { | ||
var payload = base58check.decode(string) | ||
var version = payload.readUInt8(0) | ||
var hash = payload.slice(1) | ||
return new Address(hash, version) | ||
} | ||
/** | ||
* Returns the version of an address, e.g. if the address belongs to the main | ||
* net or the test net. | ||
*/ | ||
Address.getVersion = function (address) { | ||
return base58.decode(address)[0] | ||
Address.fromOutputScript = function(script, network) { | ||
network = network || networks.bitcoin | ||
var type = scripts.classifyOutput(script) | ||
if (type === 'pubkeyhash') return new Address(script.chunks[2], network.pubKeyHash) | ||
if (type === 'scripthash') return new Address(script.chunks[1], network.scriptHash) | ||
assert(false, type + ' has no matching Address') | ||
} | ||
/** | ||
* Returns true if a bitcoin address is a valid address, otherwise false. | ||
*/ | ||
Address.validate = function (address) { | ||
try { | ||
base58check.decode(address) | ||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
// Export functions | ||
Address.prototype.toBase58Check = function () { | ||
var payload = new Buffer(21) | ||
payload.writeUInt8(this.version, 0) | ||
this.hash.copy(payload, 1) | ||
return base58check.encode(payload) | ||
} | ||
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') | ||
} | ||
Address.prototype.toString = Address.prototype.toBase58Check | ||
module.exports = Address |
// https://en.bitcoin.it/wiki/Base58Check_encoding | ||
var assert = require('assert') | ||
var base58 = require('./base58') | ||
var crypto = require('crypto') | ||
var base58 = require('bs58') | ||
var crypto = require('./crypto') | ||
function sha256(buf) { | ||
var hash = crypto.createHash('sha256') | ||
hash.update(buf) | ||
return hash.digest() | ||
} | ||
// Encode a buffer as a base58-check-encoded string | ||
function encode(buffer, version) { | ||
version = version || 0 | ||
function encode(payload) { | ||
var checksum = crypto.hash256(payload).slice(0, 4) | ||
// FIXME: `new Buffer(buffer)` is unnecessary if input is a Buffer | ||
var version = new Buffer([version]) | ||
var payload = new Buffer(buffer) | ||
var message = Buffer.concat([version, payload]) | ||
var checksum = sha256(sha256(message)).slice(0, 4) | ||
return base58.encode(Buffer.concat([ | ||
message, | ||
payload, | ||
checksum | ||
@@ -34,16 +20,9 @@ ])) | ||
var message = buffer.slice(0, -4) | ||
var payload = buffer.slice(0, -4) | ||
var checksum = buffer.slice(-4) | ||
var newChecksum = sha256(sha256(message)).slice(0, 4) | ||
var newChecksum = crypto.hash256(payload).slice(0, 4) | ||
assert.deepEqual(newChecksum, checksum) | ||
assert.deepEqual(newChecksum, checksum, 'Invalid checksum') | ||
var version = message.readUInt8(0) | ||
var payload = message.slice(1) | ||
return { | ||
version: version, | ||
payload: payload, | ||
checksum: checksum | ||
} | ||
return payload | ||
} | ||
@@ -50,0 +29,0 @@ |
@@ -0,200 +1,32 @@ | ||
var assert = require('assert') | ||
var Crypto = require('crypto-js') | ||
var WordArray = Crypto.lib.WordArray | ||
var base64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' | ||
function lpad(str, padString, length) { | ||
while (str.length < length) str = padString + str | ||
return str | ||
} | ||
function bufferToWordArray(buffer) { | ||
assert(Buffer.isBuffer(buffer), 'Expected Buffer, got', buffer) | ||
function bytesToHex(bytes) { | ||
// FIXME: transitionary fix | ||
if (Buffer.isBuffer(bytes)) { | ||
return bytes.toString('hex') | ||
var words = [] | ||
for (var i = 0, b = 0; i < buffer.length; i++, b += 8) { | ||
words[b >>> 5] |= buffer[i] << (24 - b % 32) | ||
} | ||
return bytes.map(function(x) { | ||
return lpad(x.toString(16), '0', 2) | ||
}).join('') | ||
return new WordArray.init(words, buffer.length) | ||
} | ||
function hexToBytes(hex) { | ||
return hex.match(/../g).map(function(x) { | ||
return parseInt(x,16) | ||
}) | ||
} | ||
function wordArrayToBuffer(wordArray) { | ||
assert(Array.isArray(wordArray.words), 'Expected WordArray, got' + wordArray) | ||
function bytesToBase64(bytes) { | ||
var base64 = [] | ||
var words = wordArray.words | ||
var buffer = new Buffer(words.length * 4) | ||
for (var i = 0; i < bytes.length; i += 3) { | ||
var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2] | ||
for (var j = 0; j < 4; j++) { | ||
if (i * 8 + j * 6 <= bytes.length * 8) { | ||
base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F)) | ||
} else { | ||
base64.push('=') | ||
} | ||
} | ||
} | ||
return base64.join('') | ||
} | ||
function base64ToBytes(base64) { | ||
// Remove non-base-64 characters | ||
base64 = base64.replace(/[^A-Z0-9+\/]/ig, '') | ||
var bytes = [] | ||
var imod4 = 0 | ||
for (var i = 0; i < base64.length; imod4 = ++i % 4) { | ||
if (!imod4) continue | ||
bytes.push( | ||
( | ||
(base64map.indexOf(base64.charAt(i - 1)) & (Math.pow(2, -2 * imod4 + 8) - 1)) << | ||
(imod4 * 2) | ||
) | | ||
(base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2)) | ||
) | ||
} | ||
return bytes | ||
} | ||
/** | ||
* Hex only (allowing bin would be potentially risky, as 01010101 = \x01 * 4 or 85) | ||
*/ | ||
function coerceToBytes(input) { | ||
if (typeof input != 'string') return input | ||
return hexToBytes(input) | ||
} | ||
function binToBytes(bin) { | ||
return bin.match(/......../g).map(function(x) { | ||
return parseInt(x,2) | ||
words.forEach(function(value, i) { | ||
buffer.writeInt32BE(value & -1, i * 4) | ||
}) | ||
} | ||
function bytesToBin(bytes) { | ||
return bytes.map(function(x) { | ||
return lpad(x.toString(2), '0', 8) | ||
}).join('') | ||
return buffer | ||
} | ||
function bytesToString(bytes) { | ||
return bytes.map(function(x){ | ||
return String.fromCharCode(x) | ||
}).join('') | ||
} | ||
function stringToBytes(string) { | ||
return string.split('').map(function(x) { | ||
return x.charCodeAt(0) | ||
}) | ||
} | ||
/** | ||
* Create a byte array representing a number with the given length | ||
*/ | ||
function numToBytes(num, bytes) { | ||
if (bytes === undefined) bytes = 8 | ||
if (bytes === 0) return [] | ||
return [num % 256].concat(numToBytes(Math.floor(num / 256), bytes - 1)) | ||
} | ||
/** | ||
* Convert a byte array to the number that it represents | ||
*/ | ||
function bytesToNum(bytes) { | ||
if (bytes.length === 0) return 0 | ||
return bytes[0] + 256 * bytesToNum(bytes.slice(1)) | ||
} | ||
/** | ||
* Turn an integer into a "var_int". | ||
* | ||
* "var_int" is a variable length integer used by Bitcoin's binary format. | ||
* | ||
* Returns a byte array. | ||
*/ | ||
function numToVarInt(num) { | ||
if (num < 253) return [num] | ||
if (num < 65536) return [253].concat(numToBytes(num, 2)) | ||
if (num < 4294967296) return [254].concat(numToBytes(num, 4)) | ||
return [255].concat(numToBytes(num, 8)) | ||
} | ||
/** | ||
* Turn an VarInt into an integer | ||
* | ||
* "var_int" is a variable length integer used by Bitcoin's binary format. | ||
* | ||
* Returns { bytes: bytesUsed, number: theNumber } | ||
*/ | ||
function varIntToNum(bytes) { | ||
var prefix = bytes[0] | ||
var viBytes = | ||
prefix < 253 ? bytes.slice(0, 1) | ||
: prefix === 253 ? bytes.slice(1, 3) | ||
: prefix === 254 ? bytes.slice(1, 5) | ||
: bytes.slice(1, 9) | ||
return { | ||
bytes: prefix < 253 ? viBytes : bytes.slice(0, viBytes.length + 1), | ||
number: bytesToNum(viBytes) | ||
} | ||
} | ||
function bytesToWords(bytes) { | ||
var words = [] | ||
for (var i = 0, b = 0; i < bytes.length; i++, b += 8) { | ||
words[b >>> 5] |= bytes[i] << (24 - b % 32) | ||
} | ||
return words | ||
} | ||
function wordsToBytes(words) { | ||
var bytes = [] | ||
for (var b = 0; b < words.length * 32; b += 8) { | ||
bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF) | ||
} | ||
return bytes | ||
} | ||
function bytesToWordArray(bytes) { | ||
return new WordArray.init(bytesToWords(bytes), bytes.length) | ||
} | ||
function wordArrayToBytes(wordArray) { | ||
return wordsToBytes(wordArray.words) | ||
} | ||
function reverseEndian (hex) { | ||
return bytesToHex(hexToBytes(hex).reverse()) | ||
} | ||
module.exports = { | ||
lpad: lpad, | ||
bytesToHex: bytesToHex, | ||
hexToBytes: hexToBytes, | ||
bytesToBase64: bytesToBase64, | ||
base64ToBytes: base64ToBytes, | ||
coerceToBytes: coerceToBytes, | ||
binToBytes: binToBytes, | ||
bytesToBin: bytesToBin, | ||
bytesToString: bytesToString, | ||
stringToBytes: stringToBytes, | ||
numToBytes: numToBytes, | ||
bytesToNum: bytesToNum, | ||
numToVarInt: numToVarInt, | ||
varIntToNum: varIntToNum, | ||
bytesToWords: bytesToWords, | ||
wordsToBytes: wordsToBytes, | ||
bytesToWordArray: bytesToWordArray, | ||
wordArrayToBytes: wordArrayToBytes, | ||
reverseEndian: reverseEndian | ||
bufferToWordArray: bufferToWordArray, | ||
wordArrayToBuffer: wordArrayToBuffer | ||
} |
385
src/ecdsa.js
@@ -1,299 +0,180 @@ | ||
var sec = require('./jsbn/sec') | ||
var rng = require('secure-random') | ||
var BigInteger = require('./jsbn/jsbn') | ||
var convert = require('./convert') | ||
var HmacSHA256 = require('crypto-js/hmac-sha256') | ||
var ECPointFp = require('./jsbn/ec').ECPointFp | ||
var ecparams = sec("secp256k1") | ||
var P_OVER_FOUR = null | ||
var assert = require('assert') | ||
var crypto = require('./crypto') | ||
function implShamirsTrick(P, k, Q, l) { | ||
var m = Math.max(k.bitLength(), l.bitLength()) | ||
var Z = P.add2D(Q) | ||
var R = P.curve.getInfinity() | ||
var BigInteger = require('bigi') | ||
var ECSignature = require('./ecsignature') | ||
var Point = require('ecurve').Point | ||
for (var i = m - 1; i >= 0; --i) { | ||
R = R.twice2D() | ||
// https://tools.ietf.org/html/rfc6979#section-3.2 | ||
function deterministicGenerateK(curve, hash, d) { | ||
assert(Buffer.isBuffer(hash), 'Hash must be a Buffer, not ' + hash) | ||
assert.equal(hash.length, 32, 'Hash must be 256 bit') | ||
assert(d instanceof BigInteger, 'Private key must be a BigInteger') | ||
R.z = BigInteger.ONE | ||
var x = d.toBuffer(32) | ||
var k = new Buffer(32) | ||
var v = new Buffer(32) | ||
if (k.testBit(i)) { | ||
if (l.testBit(i)) { | ||
R = R.add2D(Z) | ||
} else { | ||
R = R.add2D(P) | ||
} | ||
} else { | ||
if (l.testBit(i)) { | ||
R = R.add2D(Q) | ||
} | ||
} | ||
} | ||
// Step B | ||
v.fill(1) | ||
return R | ||
} | ||
// Step C | ||
k.fill(0) | ||
function deterministicGenerateK(hash,key) { | ||
var vArr = [] | ||
var kArr = [] | ||
for (var i = 0;i < 32;i++) vArr.push(1) | ||
for (var i = 0;i < 32;i++) kArr.push(0) | ||
var v = convert.bytesToWordArray(vArr) | ||
var k = convert.bytesToWordArray(kArr) | ||
// Step D | ||
k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0]), x, hash]), k) | ||
k = HmacSHA256(convert.bytesToWordArray(vArr.concat([0]).concat(key).concat(hash)), k) | ||
v = HmacSHA256(v, k) | ||
vArr = convert.wordArrayToBytes(v) | ||
k = HmacSHA256(convert.bytesToWordArray(vArr.concat([1]).concat(key).concat(hash)), k) | ||
v = HmacSHA256(v,k) | ||
v = HmacSHA256(v,k) | ||
vArr = convert.wordArrayToBytes(v) | ||
return BigInteger.fromByteArrayUnsigned(vArr) | ||
} | ||
// Step E | ||
v = crypto.HmacSHA256(v, k) | ||
var ECDSA = { | ||
getBigRandom: function (limit) { | ||
return new BigInteger(limit.bitLength(), rng). | ||
mod(limit.subtract(BigInteger.ONE)). | ||
add(BigInteger.ONE) | ||
}, | ||
sign: function (hash, priv) { | ||
var d = priv | ||
var n = ecparams.getN() | ||
var e = BigInteger.fromByteArrayUnsigned(hash) | ||
// Step F | ||
k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([1]), x, hash]), k) | ||
var k = deterministicGenerateK(hash,priv.toByteArrayUnsigned()) | ||
var G = ecparams.getG() | ||
var Q = G.multiply(k) | ||
var r = Q.getX().toBigInteger().mod(n) | ||
// Step G | ||
v = crypto.HmacSHA256(v, k) | ||
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n) | ||
// Step H1/H2a, ignored as tlen === qlen (256 bit) | ||
// Step H2b | ||
v = crypto.HmacSHA256(v, k) | ||
return ECDSA.serializeSig(r, s) | ||
}, | ||
var T = BigInteger.fromBuffer(v) | ||
verify: function (hash, sig, pubkey) { | ||
var r,s | ||
if (Array.isArray(sig)) { | ||
var obj = ECDSA.parseSig(sig) | ||
r = obj.r | ||
s = obj.s | ||
} else if ("object" === typeof sig && sig.r && sig.s) { | ||
r = sig.r | ||
s = sig.s | ||
} else { | ||
throw new Error("Invalid value for signature") | ||
} | ||
// Step H3, repeat until T is within the interval [1, n - 1] | ||
while ((T.signum() <= 0) || (T.compareTo(curve.n) >= 0)) { | ||
k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k) | ||
v = crypto.HmacSHA256(v, k) | ||
var Q | ||
if (pubkey instanceof ECPointFp) { | ||
Q = pubkey | ||
} else if (Array.isArray(pubkey)) { | ||
Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey) | ||
} else { | ||
throw new Error("Invalid format for pubkey value, must be byte array or ECPointFp") | ||
} | ||
var e = BigInteger.fromByteArrayUnsigned(hash) | ||
T = BigInteger.fromBuffer(v) | ||
} | ||
return ECDSA.verifyRaw(e, r, s, Q) | ||
}, | ||
return T | ||
} | ||
verifyRaw: function (e, r, s, Q) { | ||
var n = ecparams.getN() | ||
var G = ecparams.getG() | ||
function sign(curve, hash, d) { | ||
var k = deterministicGenerateK(curve, hash, d) | ||
if (r.compareTo(BigInteger.ONE) < 0 || r.compareTo(n) >= 0) { | ||
return false | ||
} | ||
var n = curve.n | ||
var G = curve.G | ||
var Q = G.multiply(k) | ||
var e = BigInteger.fromBuffer(hash) | ||
if (s.compareTo(BigInteger.ONE) < 0 || s.compareTo(n) >= 0) { | ||
return false | ||
} | ||
var r = Q.affineX.mod(n) | ||
assert.notEqual(r.signum(), 0, 'Invalid R value') | ||
var c = s.modInverse(n) | ||
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n) | ||
assert.notEqual(s.signum(), 0, 'Invalid S value') | ||
var u1 = e.multiply(c).mod(n) | ||
var u2 = r.multiply(c).mod(n) | ||
var N_OVER_TWO = n.shiftRight(1) | ||
// TODO(!!!): For some reason Shamir's trick isn't working with | ||
// signed message verification!? Probably an implementation | ||
// error! | ||
//var point = implShamirsTrick(G, u1, Q, u2) | ||
var point = G.multiply(u1).add(Q.multiply(u2)) | ||
// enforce low S values, see bip62: 'low s values in signatures' | ||
if (s.compareTo(N_OVER_TWO) > 0) { | ||
s = n.subtract(s) | ||
} | ||
var v = point.getX().toBigInteger().mod(n) | ||
return new ECSignature(r, s) | ||
} | ||
return v.equals(r) | ||
}, | ||
function verify(curve, hash, signature, Q) { | ||
var e = BigInteger.fromBuffer(hash) | ||
/** | ||
* Serialize a signature into DER format. | ||
* | ||
* Takes two BigIntegers representing r and s and returns a byte array. | ||
*/ | ||
serializeSig: function (r, s) { | ||
var rBa = r.toByteArraySigned() | ||
var sBa = s.toByteArraySigned() | ||
return verifyRaw(curve, e, signature, Q) | ||
} | ||
var sequence = [] | ||
sequence.push(0x02); // INTEGER | ||
sequence.push(rBa.length) | ||
sequence = sequence.concat(rBa) | ||
function verifyRaw(curve, e, signature, Q) { | ||
var n = curve.n | ||
var G = curve.G | ||
sequence.push(0x02); // INTEGER | ||
sequence.push(sBa.length) | ||
sequence = sequence.concat(sBa) | ||
var r = signature.r | ||
var s = signature.s | ||
sequence.unshift(sequence.length) | ||
sequence.unshift(0x30); // SEQUENCE | ||
if (r.signum() === 0 || r.compareTo(n) >= 0) return false | ||
if (s.signum() === 0 || s.compareTo(n) >= 0) return false | ||
return sequence | ||
}, | ||
var c = s.modInverse(n) | ||
/** | ||
* Parses a byte array containing a DER-encoded signature. | ||
* | ||
* This function will return an object of the form: | ||
* | ||
* { | ||
* r: BigInteger, | ||
* s: BigInteger | ||
* } | ||
*/ | ||
parseSig: function (sig) { | ||
var cursor | ||
if (sig[0] != 0x30) { | ||
throw new Error("Signature not a valid DERSequence") | ||
} | ||
var u1 = e.multiply(c).mod(n) | ||
var u2 = r.multiply(c).mod(n) | ||
cursor = 2 | ||
if (sig[cursor] != 0x02) { | ||
throw new Error("First element in signature must be a DERInteger") | ||
} | ||
var rBa = sig.slice(cursor+2, cursor+2+sig[cursor+1]) | ||
var point = G.multiplyTwo(u1, Q, u2) | ||
var v = point.affineX.mod(n) | ||
cursor += 2+sig[cursor+1] | ||
if (sig[cursor] != 0x02) { | ||
throw new Error("Second element in signature must be a DERInteger") | ||
} | ||
var sBa = sig.slice(cursor+2, cursor+2+sig[cursor+1]) | ||
return v.equals(r) | ||
} | ||
cursor += 2+sig[cursor+1] | ||
/** | ||
* 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') | ||
//if (cursor != sig.length) | ||
// throw new Error("Extra bytes in signature") | ||
var r = signature.r | ||
var s = signature.s | ||
var r = BigInteger.fromByteArrayUnsigned(rBa) | ||
var s = BigInteger.fromByteArrayUnsigned(sBa) | ||
// A set LSB signifies that the y-coordinate is odd | ||
var isYOdd = i & 1 | ||
return {r: r, s: s} | ||
}, | ||
// The more significant bit specifies whether we should use the | ||
// first or second candidate key. | ||
var isSecondKey = i >> 1 | ||
parseSigCompact: function (sig) { | ||
if (sig.length !== 65) { | ||
throw new Error("Signature has the wrong length") | ||
} | ||
var n = curve.n | ||
var G = curve.G | ||
// Signature is prefixed with a type byte storing three bits of | ||
// information. | ||
var i = sig[0] - 27 | ||
if (i < 0 || i > 7) { | ||
throw new Error("Invalid signature type") | ||
} | ||
// 1.1 Let x = r + jn | ||
var x = isSecondKey ? r.add(n) : r | ||
var R = curve.pointFromX(isYOdd, x) | ||
var n = ecparams.getN() | ||
var r = BigInteger.fromByteArrayUnsigned(sig.slice(1, 33)).mod(n) | ||
var s = BigInteger.fromByteArrayUnsigned(sig.slice(33, 65)).mod(n) | ||
// 1.4 Check that nR is at infinity | ||
var nR = R.multiply(n) | ||
assert(curve.isInfinity(nR), 'nR is not a valid curve point') | ||
return {r: r, s: s, i: i} | ||
}, | ||
// Compute -e from e | ||
var eNeg = e.negate().mod(n) | ||
/** | ||
* 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 | ||
*/ | ||
recoverPubKey: function (r, s, hash, i) { | ||
// The recovery parameter i has two bits. | ||
i = i & 3 | ||
// 1.6.1 Compute Q = r^-1 (sR - eG) | ||
// Q = r^-1 (sR + -eG) | ||
var rInv = r.modInverse(n) | ||
// The less significant bit specifies whether the y coordinate | ||
// of the compressed point is even or not. | ||
var isYEven = i & 1 | ||
var Q = R.multiplyTwo(s, G, eNeg).multiply(rInv) | ||
curve.validate(Q) | ||
// The more significant bit specifies whether we should use the | ||
// first or second candidate key. | ||
var isSecondKey = i >> 1 | ||
return Q | ||
} | ||
var n = ecparams.getN() | ||
var G = ecparams.getG() | ||
var curve = ecparams.getCurve() | ||
var p = curve.getQ() | ||
var a = curve.getA().toBigInteger() | ||
var b = curve.getB().toBigInteger() | ||
/** | ||
* 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) | ||
// We precalculate (p + 1) / 4 where p is if the field order | ||
if (!P_OVER_FOUR) { | ||
P_OVER_FOUR = p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)) | ||
// 1.6.2 Verify Q | ||
if (Qprime.equals(Q)) { | ||
return i | ||
} | ||
} | ||
// 1.1 Compute x | ||
var x = isSecondKey ? r.add(n) : r | ||
throw new Error('Unable to find valid recovery factor') | ||
} | ||
// 1.3 Convert x to point | ||
var alpha = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p) | ||
var beta = alpha.modPow(P_OVER_FOUR, p) | ||
// var xorOdd = beta.isEven() ? (i % 2) : ((i+1) % 2) | ||
// If beta is even, but y isn't or vice versa, then convert it, | ||
// otherwise we're done and y == beta. | ||
var y = (beta.isEven() ? !isYEven : isYEven) ? beta : p.subtract(beta) | ||
// 1.4 Check that nR is at infinity | ||
var R = new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y)) | ||
R.validate() | ||
// 1.5 Compute e from M | ||
var e = BigInteger.fromByteArrayUnsigned(hash) | ||
var eNeg = BigInteger.ZERO.subtract(e).mod(n) | ||
// 1.6 Compute Q = r^-1 (sR - eG) | ||
var rInv = r.modInverse(n) | ||
var Q = implShamirsTrick(R, s, G, eNeg).multiply(rInv) | ||
Q.validate() | ||
if (!ECDSA.verifyRaw(e, r, s, Q)) { | ||
throw new Error("Pubkey recovery unsuccessful") | ||
} | ||
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. | ||
*/ | ||
calcPubKeyRecoveryParam: function (origPubKey, r, s, hash) { | ||
for (var i = 0; i < 4; i++) { | ||
var pubKey = ECDSA.recoverPubKey(r, s, hash, i) | ||
if (pubKey.equals(origPubKey)) { | ||
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 | ||
} | ||
module.exports = ECDSA |
205
src/eckey.js
@@ -1,188 +0,79 @@ | ||
var Address = require('./address') | ||
var assert = require('assert') | ||
var convert = require('./convert') | ||
var base58check = require('./base58check') | ||
var BigInteger = require('./jsbn/jsbn') | ||
var ecdsa = require('./ecdsa') | ||
var ECPointFp = require('./jsbn/ec').ECPointFp | ||
var sec = require('./jsbn/sec') | ||
var Network = require('./network') | ||
var util = require('./util') | ||
var ecparams = sec("secp256k1") | ||
var networks = require('./networks') | ||
var secureRandom = require('secure-random') | ||
// input can be nothing, array of bytes, hex string, or base58 string | ||
var ECKey = function (input, compressed) { | ||
if (!(this instanceof ECKey)) { return new ECKey(input, compressed) } | ||
if (!input) { | ||
// Generate new key | ||
var n = ecparams.getN() | ||
this.priv = ecdsa.getBigRandom(n) | ||
this.compressed = compressed || false | ||
} | ||
else this.import(input,compressed) | ||
} | ||
var BigInteger = require('bigi') | ||
var ECPubKey = require('./ecpubkey') | ||
ECKey.prototype.import = function (input, compressed) { | ||
function has(li, v) { return li.indexOf(v) >= 0 } | ||
function fromBin(x) { return BigInteger.fromByteArrayUnsigned(x) } | ||
var ecurve = require('ecurve') | ||
var curve = ecurve.getCurveByName('secp256k1') | ||
this.priv = | ||
input instanceof ECKey ? input.priv | ||
: input instanceof BigInteger ? input.mod(ecparams.getN()) | ||
: Array.isArray(input) ? fromBin(input.slice(0, 32)) | ||
: typeof input != "string" ? null | ||
: input.length == 44 ? fromBin(convert.base64ToBytes(input)) | ||
: input.length == 51 && input[0] == '5' ? fromBin(base58check.decode(input).payload) | ||
: input.length == 51 && input[0] == '9' ? fromBin(base58check.decode(input).payload) | ||
: input.length == 52 && has('LK', input[0]) ? fromBin(base58check.decode(input).payload.slice(0, 32)) | ||
: input.length == 52 && input[0] == 'c' ? fromBin(base58check.decode(input).payload.slice(0, 32)) | ||
: has([64,65],input.length) ? fromBin(convert.hexToBytes(input.slice(0, 64))) | ||
: null | ||
function ECKey(d, compressed) { | ||
assert(d.signum() > 0, 'Private key must be greater than 0') | ||
assert(d.compareTo(curve.n) < 0, 'Private key must be less than the curve order') | ||
assert(this.priv !== null) | ||
var Q = curve.G.multiply(d) | ||
this.compressed = | ||
compressed !== undefined ? compressed | ||
: input instanceof ECKey ? input.compressed | ||
: input instanceof BigInteger ? false | ||
: Array.isArray(input) ? false | ||
: typeof input != "string" ? null | ||
: input.length == 44 ? false | ||
: input.length == 51 && input[0] == '5' ? false | ||
: input.length == 51 && input[0] == '9' ? false | ||
: input.length == 52 && has('LK', input[0]) ? true | ||
: input.length == 52 && input[0] == 'c' ? true | ||
: input.length == 64 ? false | ||
: input.length == 65 ? true | ||
: null | ||
assert(this.compressed !== null) | ||
this.d = d | ||
this.pub = new ECPubKey(Q, compressed) | ||
} | ||
ECKey.prototype.getPub = function(compressed) { | ||
if (compressed === undefined) compressed = this.compressed | ||
return ECPubKey(ecparams.getG().multiply(this.priv), compressed) | ||
} | ||
// Static constructors | ||
ECKey.fromWIF = function(string) { | ||
var payload = base58check.decode(string) | ||
var compressed = false | ||
ECKey.prototype.toBin = function() { | ||
return convert.bytesToString(this.toBytes()) | ||
} | ||
// Ignore the version byte | ||
payload = payload.slice(1) | ||
ECKey.version_bytes = { | ||
0: 128, | ||
111: 239 | ||
} | ||
if (payload.length === 33) { | ||
assert.strictEqual(payload[32], 0x01, 'Invalid compression flag') | ||
ECKey.prototype.toWif = function(version) { | ||
version = version || Network.mainnet.addressVersion | ||
// Truncate the compression flag | ||
payload = payload.slice(0, -1) | ||
compressed = true | ||
} | ||
return base58check.encode(this.toBytes(), ECKey.version_bytes[version]) | ||
} | ||
assert.equal(payload.length, 32, 'Invalid WIF payload length') | ||
ECKey.prototype.toHex = function() { | ||
return convert.bytesToHex(this.toBytes()) | ||
var d = BigInteger.fromBuffer(payload) | ||
return new ECKey(d, compressed) | ||
} | ||
ECKey.prototype.toBytes = function() { | ||
var bytes = this.priv.toByteArrayUnsigned() | ||
if (this.compressed) bytes.push(1) | ||
return bytes | ||
} | ||
ECKey.makeRandom = function(compressed, rng) { | ||
rng = rng || secureRandom.randomBuffer | ||
ECKey.prototype.toBase64 = function() { | ||
return convert.bytesToBase64(this.toBytes()) | ||
} | ||
var buffer = rng(32) | ||
assert(Buffer.isBuffer(buffer), 'Expected Buffer, got ' + buffer) | ||
ECKey.prototype.toString = ECKey.prototype.toHex | ||
var d = BigInteger.fromBuffer(buffer) | ||
d = d.mod(curve.n) | ||
ECKey.prototype.getAddress = function(version) { | ||
return this.getPub().getAddress(version) | ||
return new ECKey(d, compressed) | ||
} | ||
ECKey.prototype.add = function(key) { | ||
return ECKey(this.priv.add(ECKey(key).priv), this.compressed) | ||
} | ||
// Export functions | ||
ECKey.prototype.toWIF = function(network) { | ||
network = network || networks.bitcoin | ||
ECKey.prototype.multiply = function(key) { | ||
return ECKey(this.priv.multiply(ECKey(key).priv), this.compressed) | ||
} | ||
var bufferLen = this.pub.compressed ? 34 : 33 | ||
var buffer = new Buffer(bufferLen) | ||
ECKey.prototype.sign = function(hash) { | ||
return ecdsa.sign(hash, this.priv) | ||
} | ||
buffer.writeUInt8(network.wif, 0) | ||
this.d.toBuffer(32).copy(buffer, 1) | ||
ECKey.prototype.verify = function(hash, sig) { | ||
return this.getPub().verify(hash, sig) | ||
} | ||
var ECPubKey = function(input, compressed) { | ||
if (!(this instanceof ECPubKey)) { | ||
return new ECPubKey(input, compressed) | ||
if (this.pub.compressed) { | ||
buffer.writeUInt8(0x01, 33) | ||
} | ||
this.import(input, compressed) | ||
return base58check.encode(buffer) | ||
} | ||
ECPubKey.prototype.import = function(input, compressed) { | ||
var decode = function(x) { return ECPointFp.decodeFrom(ecparams.getCurve(), x) } | ||
this.pub = | ||
input instanceof ECPointFp ? input | ||
: input instanceof ECKey ? ecparams.getG().multiply(input.priv) | ||
: input instanceof ECPubKey ? input.pub | ||
: typeof input == "string" ? decode(convert.hexToBytes(input)) | ||
: Array.isArray(input) ? decode(input) | ||
: null | ||
assert(this.pub !== null) | ||
this.compressed = | ||
compressed ? compressed | ||
: input instanceof ECPointFp ? input.compressed | ||
: input instanceof ECPubKey ? input.compressed | ||
: (this.pub[0] < 4) | ||
// Operations | ||
ECKey.prototype.sign = function(hash) { | ||
return ecdsa.sign(curve, hash, this.d) | ||
} | ||
ECPubKey.prototype.add = function(key) { | ||
return ECPubKey(this.pub.add(ECPubKey(key).pub), this.compressed) | ||
} | ||
ECPubKey.prototype.multiply = function(key) { | ||
return ECPubKey(this.pub.multiply(ECKey(key).priv), this.compressed) | ||
} | ||
ECPubKey.prototype.toBytes = function(compressed) { | ||
if (compressed === undefined) compressed = this.compressed | ||
return this.pub.getEncoded(compressed) | ||
} | ||
ECPubKey.prototype.toHex = function(compressed) { | ||
return convert.bytesToHex(this.toBytes(compressed)) | ||
} | ||
ECPubKey.prototype.toBin = function(compressed) { | ||
return convert.bytesToString(this.toBytes(compressed)) | ||
} | ||
ECPubKey.prototype.toWif = function(version) { | ||
version = version || Network.mainnet.addressVersion | ||
return base58check.encode(this.toBytes(), version) | ||
} | ||
ECPubKey.prototype.toString = ECPubKey.prototype.toHex | ||
ECPubKey.prototype.getAddress = function(version) { | ||
version = version || Network.mainnet.addressVersion | ||
return new Address(util.sha256ripe160(this.toBytes()), version) | ||
} | ||
ECPubKey.prototype.verify = function(hash, sig) { | ||
return ecdsa.verify(hash, sig, this.toBytes()) | ||
} | ||
module.exports = { | ||
ECKey: ECKey, | ||
ECPubKey: ECPubKey | ||
} | ||
module.exports = ECKey |
@@ -1,26 +0,19 @@ | ||
var Key = require('./eckey') | ||
var T = require('./transaction') | ||
module.exports = { | ||
Address: require('./address'), | ||
Key: Key.ECKey, | ||
ECKey: Key.ECKey, | ||
ECPubKey: Key.ECPubKey, | ||
base58check: require('./base58check'), | ||
bufferutils: require('./bufferutils'), | ||
convert: require('./convert'), | ||
crypto: require('./crypto'), | ||
ecdsa: require('./ecdsa'), | ||
ECKey: require('./eckey'), | ||
ECPubKey: require('./ecpubkey'), | ||
ECSignature: require('./ecsignature'), | ||
Message: require('./message'), | ||
BigInteger: require('./jsbn/jsbn'), | ||
Crypto: require('crypto-js'), //should we expose this at all? | ||
opcodes: require('./opcodes'), | ||
HDNode: require('./hdnode'), | ||
Script: require('./script'), | ||
Opcode: require('./opcode'), | ||
Transaction: T.Transaction, | ||
Util: require('./util'), | ||
TransactionIn: T.TransactionIn, | ||
TransactionOut: T.TransactionOut, | ||
ECPointFp: require('./jsbn/ec').ECPointFp, | ||
Wallet: require('./wallet'), | ||
network: require('./network'), | ||
ecdsa: require('./ecdsa'), | ||
HDWallet: require('./hdwallet.js'), | ||
base58: require('./base58'), | ||
base58check: require('./base58check'), | ||
convert: require('./convert') | ||
scripts: require('./scripts'), | ||
Transaction: require('./transaction'), | ||
networks: require('./networks'), | ||
Wallet: require('./wallet') | ||
} |
/// Implements Bitcoin's feature for signing arbitrary messages. | ||
var Address = require('./address') | ||
var convert = require('./convert') | ||
var BigInteger = require('bigi') | ||
var bufferutils = require('./bufferutils') | ||
var crypto = require('./crypto') | ||
var ecdsa = require('./ecdsa') | ||
var ECPubKey = require('./eckey').ECPubKey | ||
var SHA256 = require('crypto-js/sha256') | ||
var networks = require('./networks') | ||
// FIXME: magicHash is incompatible with other magic messages | ||
var magicBytes = convert.stringToBytes('Bitcoin Signed Message:\n') | ||
var Address = require('./address') | ||
var ECPubKey = require('./ecpubkey') | ||
var ECSignature = require('./ecsignature') | ||
function magicHash(message) { | ||
var messageBytes = convert.stringToBytes(message) | ||
var ecurve = require('ecurve') | ||
var ecparams = ecurve.getCurveByName('secp256k1') | ||
var buffer = [].concat( | ||
convert.numToVarInt(magicBytes.length), | ||
magicBytes, | ||
convert.numToVarInt(messageBytes.length), | ||
messageBytes | ||
) | ||
function magicHash(message, network) { | ||
var magicPrefix = new Buffer(network.magicPrefix) | ||
var messageBuffer = new Buffer(message) | ||
var lengthBuffer = new Buffer(bufferutils.varIntSize(messageBuffer.length)) | ||
bufferutils.writeVarInt(lengthBuffer, messageBuffer.length, 0) | ||
return convert.wordArrayToBytes(SHA256(SHA256(convert.bytesToWordArray(buffer)))) | ||
var buffer = Buffer.concat([magicPrefix, lengthBuffer, messageBuffer]) | ||
return crypto.hash256(buffer) | ||
} | ||
// TODO: parameterize compression instead of using ECKey.compressed | ||
function sign(key, message) { | ||
var hash = magicHash(message) | ||
var sig = key.sign(hash) | ||
var obj = ecdsa.parseSig(sig) | ||
var i = ecdsa.calcPubKeyRecoveryParam(key.getPub().pub, obj.r, obj.s, hash) | ||
function sign(privKey, message, network) { | ||
network = network || networks.bitcoin | ||
i += 27 | ||
if (key.compressed) { | ||
i += 4 | ||
} | ||
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) | ||
var rBa = obj.r.toByteArrayUnsigned() | ||
var sBa = obj.s.toByteArrayUnsigned() | ||
// Pad to 32 bytes per value | ||
while (rBa.length < 32) rBa.unshift(0); | ||
while (sBa.length < 32) sBa.unshift(0); | ||
sig = [i].concat(rBa, sBa) | ||
return sig | ||
return signature.toCompact(i, privKey.pub.compressed) | ||
} | ||
function verify(address, sig, message) { | ||
sig = ecdsa.parseSigCompact(sig) | ||
// TODO: network could be implied from address | ||
function verify(address, signatureBuffer, message, network) { | ||
if (address instanceof Address) { | ||
address = address.toString() | ||
} | ||
network = network || networks.bitcoin | ||
var pubKey = new ECPubKey(ecdsa.recoverPubKey(sig.r, sig.s, magicHash(message), sig.i)) | ||
var isCompressed = !!(sig.i & 4) | ||
pubKey.compressed = isCompressed | ||
var hash = magicHash(message, network) | ||
var parsed = ECSignature.parseCompact(signatureBuffer) | ||
var e = BigInteger.fromBuffer(hash) | ||
var Q = ecdsa.recoverPubKey(ecparams, e, parsed.signature, parsed.i) | ||
address = new Address(address) | ||
return pubKey.getAddress(address.version).toString() === address.toString() | ||
var pubKey = new ECPubKey(Q, parsed.compressed) | ||
return pubKey.getAddress(network).toString() === address | ||
} | ||
@@ -59,0 +52,0 @@ |
@@ -1,391 +0,133 @@ | ||
var Opcode = require('./opcode') | ||
var util = require('./util') | ||
var convert = require('./convert') | ||
var Address = require('./address') | ||
var network = require('./network') | ||
var assert = require('assert') | ||
var bufferutils = require('./bufferutils') | ||
var crypto = require('./crypto') | ||
var opcodes = require('./opcodes') | ||
var Script = function(data) { | ||
this.buffer = data || [] | ||
if(!Array.isArray(this.buffer)) { | ||
throw new Error('expect Script to be initialized with Array, but got ' + data) | ||
} | ||
this.parse() | ||
} | ||
function Script(buffer, chunks) { | ||
assert(Buffer.isBuffer(buffer), 'Expected Buffer, got ' + buffer) | ||
assert(Array.isArray(chunks), 'Expected Array, got ' + chunks) | ||
Script.fromHex = function(data) { | ||
return new Script(convert.hexToBytes(data)) | ||
this.buffer = buffer | ||
this.chunks = chunks | ||
} | ||
Script.fromPubKey = function(str) { | ||
var script = new Script() | ||
var s = str.split(' ') | ||
for (var i in s) { | ||
if (Opcode.map.hasOwnProperty(s[i])) { | ||
script.writeOp(Opcode.map[s[i]]) | ||
// Import operations | ||
Script.fromASM = function(asm) { | ||
var strChunks = asm.split(' ') | ||
var chunks = strChunks.map(function(strChunk) { | ||
if (strChunk in opcodes) { | ||
return opcodes[strChunk] | ||
} else { | ||
script.writeBytes(convert.hexToBytes(s[i])) | ||
return new Buffer(strChunk, 'hex') | ||
} | ||
} | ||
return script | ||
} | ||
}) | ||
Script.fromScriptSig = function(str) { | ||
var script = new Script() | ||
var s = str.split(' ') | ||
for (var i in s) { | ||
if (Opcode.map.hasOwnProperty(s[i])) { | ||
script.writeOp(Opcode.map[s[i]]) | ||
} else { | ||
script.writeBytes(convert.hexToBytes(s[i])) | ||
} | ||
} | ||
return script | ||
return Script.fromChunks(chunks) | ||
} | ||
/** | ||
* Update the parsed script representation. | ||
* | ||
* Each Script object stores the script in two formats. First as a raw byte | ||
* array and second as an array of 'chunks', such as opcodes and pieces of | ||
* data. | ||
* | ||
* This method updates the chunks cache. Normally this is called by the | ||
* constructor and you don't need to worry about it. However, if you change | ||
* the script buffer manually, you should update the chunks using this method. | ||
*/ | ||
Script.prototype.parse = function() { | ||
var self = this | ||
Script.fromBuffer = function(buffer) { | ||
var chunks = [] | ||
this.chunks = [] | ||
// Cursor | ||
var i = 0 | ||
// Read n bytes and store result as a chunk | ||
function readChunk(n) { | ||
self.chunks.push(self.buffer.slice(i, i + n)) | ||
i += n | ||
} | ||
while (i < buffer.length) { | ||
var opcode = buffer.readUInt8(i) | ||
while (i < this.buffer.length) { | ||
var opcode = this.buffer[i++] | ||
if (opcode >= 0xF0) { | ||
// Two byte opcode | ||
opcode = (opcode << 8) | this.buffer[i++] | ||
} | ||
if ((opcode > opcodes.OP_0) && (opcode <= opcodes.OP_PUSHDATA4)) { | ||
var d = bufferutils.readPushDataInt(buffer, i) | ||
i += d.size | ||
var len | ||
if (opcode > 0 && opcode < Opcode.map.OP_PUSHDATA1) { | ||
// Read some bytes of data, opcode value is the length of data | ||
readChunk(opcode) | ||
} else if (opcode == Opcode.map.OP_PUSHDATA1) { | ||
len = this.buffer[i++] | ||
readChunk(len) | ||
} else if (opcode == Opcode.map.OP_PUSHDATA2) { | ||
len = (this.buffer[i++] << 8) | this.buffer[i++] | ||
readChunk(len) | ||
} else if (opcode == Opcode.map.OP_PUSHDATA4) { | ||
len = (this.buffer[i++] << 24) | | ||
(this.buffer[i++] << 16) | | ||
(this.buffer[i++] << 8) | | ||
this.buffer[i++] | ||
readChunk(len) | ||
} else { | ||
this.chunks.push(opcode) | ||
} | ||
} | ||
} | ||
var data = buffer.slice(i, i + d.number) | ||
i += d.number | ||
/** | ||
* Compare the script to known templates of scriptPubKey. | ||
* | ||
* This method will compare the script to a small number of standard script | ||
* templates and return a string naming the detected type. | ||
* | ||
* Currently supported are: | ||
* Address: | ||
* Paying to a Bitcoin address which is the hash of a pubkey. | ||
* OP_DUP OP_HASH160 [pubKeyHash] OP_EQUALVERIFY OP_CHECKSIG | ||
* | ||
* Pubkey: | ||
* Paying to a public key directly. | ||
* [pubKey] OP_CHECKSIG | ||
* | ||
* Strange: | ||
* Any other script (no template matched). | ||
*/ | ||
Script.prototype.getOutType = function() { | ||
if (this.chunks[this.chunks.length - 1] == Opcode.map.OP_EQUAL && | ||
this.chunks[0] == Opcode.map.OP_HASH160 && | ||
this.chunks.length == 3) { | ||
// Transfer to M-OF-N | ||
return 'P2SH' | ||
} else if (this.chunks.length == 5 && | ||
this.chunks[0] == Opcode.map.OP_DUP && | ||
this.chunks[1] == Opcode.map.OP_HASH160 && | ||
this.chunks[3] == Opcode.map.OP_EQUALVERIFY && | ||
this.chunks[4] == Opcode.map.OP_CHECKSIG) { | ||
// Transfer to Bitcoin address | ||
return 'Pubkey' | ||
} else { | ||
return 'Strange' | ||
} | ||
} | ||
chunks.push(data) | ||
/** | ||
* Returns the address corresponding to this output in hash160 form. | ||
* Assumes strange scripts are P2SH | ||
*/ | ||
Script.prototype.toScriptHash = function() { | ||
var outType = this.getOutType() | ||
} else { | ||
chunks.push(opcode) | ||
if (outType == 'Pubkey') { | ||
return this.chunks[2] | ||
i += 1 | ||
} | ||
} | ||
if (outType == 'P2SH') { | ||
return util.sha256ripe160(this.buffer) | ||
} | ||
return util.sha256ripe160(this.buffer) | ||
return new Script(buffer, chunks) | ||
} | ||
//TODO: support testnet | ||
Script.prototype.getToAddress = function() { | ||
var outType = this.getOutType() | ||
Script.fromChunks = function(chunks) { | ||
assert(Array.isArray(chunks), 'Expected Array, got ' + chunks) | ||
if (outType == 'Pubkey') { | ||
return new Address(this.chunks[2]) | ||
} | ||
var bufferSize = chunks.reduce(function(accum, chunk) { | ||
if (Buffer.isBuffer(chunk)) { | ||
return accum + bufferutils.pushDataSize(chunk.length) + chunk.length | ||
} | ||
if (outType == 'P2SH') { | ||
return new Address(this.chunks[1], 5) | ||
} | ||
return accum + 1 | ||
}, 0.0) | ||
return new Address(this.chunks[1], 5) | ||
} | ||
var buffer = new Buffer(bufferSize) | ||
var offset = 0 | ||
//TODO: support testnet | ||
Script.prototype.getFromAddress = function(){ | ||
return new Address(this.simpleInHash()) | ||
} | ||
chunks.forEach(function(chunk) { | ||
if (Buffer.isBuffer(chunk)) { | ||
offset += bufferutils.writePushDataInt(buffer, chunk.length, offset) | ||
/** | ||
* Compare the script to known templates of scriptSig. | ||
* | ||
* This method will compare the script to a small number of standard script | ||
* templates and return a string naming the detected type. | ||
* | ||
* WARNING: Use this method with caution. It merely represents a heuristic | ||
* based on common transaction formats. A non-standard transaction could | ||
* very easily match one of these templates by accident. | ||
* | ||
* Currently supported are: | ||
* Address: | ||
* Paying to a Bitcoin address which is the hash of a pubkey. | ||
* [sig] [pubKey] | ||
* | ||
* Pubkey: | ||
* Paying to a public key directly. | ||
* [sig] | ||
* | ||
* Multisig: | ||
* Paying to M-of-N public keys. | ||
* | ||
* Strange: | ||
* Any other script (no template matched). | ||
*/ | ||
Script.prototype.getInType = function() { | ||
if (this.chunks.length == 1 && | ||
Array.isArray(this.chunks[0])) { | ||
// Direct IP to IP transactions only have the signature in their scriptSig. | ||
// TODO: We could also check that the length of the data is correct. | ||
return 'Pubkey' | ||
} else if (this.chunks.length == 2 && | ||
Array.isArray(this.chunks[0]) && | ||
Array.isArray(this.chunks[1])) { | ||
return 'Address' | ||
} else if (this.chunks[0] == Opcode.map.OP_0 && | ||
this.chunks.slice(1).reduce(function(t, chunk, i) { | ||
return t && Array.isArray(chunk) && (chunk[0] == 48 || i == this.chunks.length - 1) | ||
}, true)) { | ||
return 'Multisig' | ||
} else { | ||
return 'Strange' | ||
} | ||
} | ||
chunk.copy(buffer, offset) | ||
offset += chunk.length | ||
/** | ||
* Returns the affected public key for this input. | ||
* | ||
* This currently only works with payToPubKeyHash transactions. It will also | ||
* work in the future for standard payToScriptHash transactions that use a | ||
* single public key. | ||
* | ||
* However for multi-key and other complex transactions, this will only return | ||
* one of the keys or raise an error. Therefore, it is recommended for indexing | ||
* purposes to use Script#simpleInHash or Script#simpleOutHash instead. | ||
* | ||
* @deprecated | ||
*/ | ||
Script.prototype.simpleInPubKey = function() { | ||
switch (this.getInType()) { | ||
case 'Address': | ||
return this.chunks[1] | ||
case 'Pubkey': | ||
// TODO: Theoretically, we could recover the pubkey from the sig here. | ||
// See https://bitcointalk.org/?topic=6430.0 | ||
throw new Error('Script does not contain pubkey') | ||
default: | ||
throw new Error('Encountered non-standard scriptSig') | ||
} | ||
} | ||
} else { | ||
buffer.writeUInt8(chunk, offset) | ||
offset += 1 | ||
} | ||
}) | ||
/** | ||
* Returns the affected address hash for this input. | ||
* | ||
* For standard transactions, this will return the hash of the pubKey that | ||
* can spend this output. | ||
* | ||
* In the future, for standard payToScriptHash inputs, this will return the | ||
* scriptHash. | ||
* | ||
* Note: This function provided for convenience. If you have the corresponding | ||
* scriptPubKey available, you are urged to use Script#simpleOutHash instead | ||
* as it is more reliable for non-standard payToScriptHash transactions. | ||
* | ||
* This method is useful for indexing transactions. | ||
*/ | ||
Script.prototype.simpleInHash = function() { | ||
return util.sha256ripe160(this.simpleInPubKey()) | ||
assert.equal(offset, buffer.length, 'Could not decode chunks') | ||
return new Script(buffer, chunks) | ||
} | ||
/** | ||
* Old name for Script#simpleInHash. | ||
* | ||
* @deprecated | ||
*/ | ||
Script.prototype.simpleInPubKeyHash = Script.prototype.simpleInHash | ||
/** | ||
* Add an op code to the script. | ||
*/ | ||
Script.prototype.writeOp = function(opcode) { | ||
this.buffer.push(opcode) | ||
this.chunks.push(opcode) | ||
Script.fromHex = function(hex) { | ||
return Script.fromBuffer(new Buffer(hex, 'hex')) | ||
} | ||
/** | ||
* Add a data chunk to the script. | ||
*/ | ||
Script.prototype.writeBytes = function(data) { | ||
// FIXME: Script module doesn't support buffers yet | ||
if (Buffer.isBuffer(data)) { | ||
data = Array.prototype.map.bind(data, function(x) { return x })() | ||
} | ||
// Constants | ||
Script.EMPTY = Script.fromChunks([]) | ||
if (data.length < Opcode.map.OP_PUSHDATA1) { | ||
this.buffer.push(data.length) | ||
} else if (data.length <= 0xff) { | ||
this.buffer.push(Opcode.map.OP_PUSHDATA1) | ||
this.buffer.push(data.length) | ||
} else if (data.length <= 0xffff) { | ||
this.buffer.push(Opcode.map.OP_PUSHDATA2) | ||
this.buffer.push(data.length & 0xff) | ||
this.buffer.push((data.length >>> 8) & 0xff) | ||
} else { | ||
this.buffer.push(Opcode.map.OP_PUSHDATA4) | ||
this.buffer.push(data.length & 0xff) | ||
this.buffer.push((data.length >>> 8) & 0xff) | ||
this.buffer.push((data.length >>> 16) & 0xff) | ||
this.buffer.push((data.length >>> 24) & 0xff) | ||
} | ||
this.buffer = this.buffer.concat(data) | ||
this.chunks.push(data) | ||
// Operations | ||
Script.prototype.getHash = function() { | ||
return crypto.hash160(this.buffer) | ||
} | ||
/** | ||
* Create an output for an address | ||
*/ | ||
Script.createOutputScript = function(address) { | ||
var script = new Script() | ||
address = new Address(address) | ||
if (address.version == network.mainnet.p2shVersion || | ||
address.version == network.testnet.p2shVersion) { | ||
// Standard pay-to-script-hash | ||
script.writeOp(Opcode.map.OP_HASH160) | ||
script.writeBytes(address.hash) | ||
script.writeOp(Opcode.map.OP_EQUAL) | ||
} | ||
else { | ||
// Standard pay-to-pubkey-hash | ||
script.writeOp(Opcode.map.OP_DUP) | ||
script.writeOp(Opcode.map.OP_HASH160) | ||
script.writeBytes(address.hash) | ||
script.writeOp(Opcode.map.OP_EQUALVERIFY) | ||
script.writeOp(Opcode.map.OP_CHECKSIG) | ||
} | ||
return script | ||
// 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 | ||
})) | ||
} | ||
/** | ||
* Extract pubkeys from a multisig script | ||
*/ | ||
Script.prototype.extractPubkeys = function() { | ||
return this.chunks.filter(function(chunk) { | ||
return(chunk[0] == 4 && chunk.length == 65 || chunk[0] < 4 && chunk.length == 33) | ||
}) | ||
// Export operations | ||
var reverseOps = [] | ||
for (var op in opcodes) { | ||
var code = opcodes[op] | ||
reverseOps[code] = op | ||
} | ||
/** | ||
* Create an m-of-n output script | ||
*/ | ||
Script.createMultiSigOutputScript = function(m, pubkeys) { | ||
var script = new Script() | ||
pubkeys = pubkeys.sort() | ||
Script.prototype.toASM = function() { | ||
return this.chunks.map(function(chunk) { | ||
if (Buffer.isBuffer(chunk)) { | ||
return chunk.toString('hex') | ||
script.writeOp(Opcode.map.OP_1 + m - 1) | ||
for (var i = 0; i < pubkeys.length; ++i) { | ||
script.writeBytes(pubkeys[i]) | ||
} | ||
script.writeOp(Opcode.map.OP_1 + pubkeys.length - 1) | ||
script.writeOp(Opcode.map.OP_CHECKMULTISIG) | ||
return script | ||
} else { | ||
return reverseOps[chunk] | ||
} | ||
}).join(' ') | ||
} | ||
/** | ||
* Create a standard payToPubKeyHash input. | ||
*/ | ||
Script.createInputScript = function(signature, pubKey) { | ||
var script = new Script() | ||
script.writeBytes(signature) | ||
script.writeBytes(pubKey) | ||
return script | ||
Script.prototype.toBuffer = function() { | ||
return this.buffer | ||
} | ||
/** | ||
* Create a multisig input | ||
*/ | ||
Script.createMultiSigInputScript = function(signatures, script) { | ||
script = new Script(script) | ||
var k = script.chunks[0][0] | ||
//Not enough sigs | ||
if (signatures.length < k) return false; | ||
var inScript = new Script() | ||
inScript.writeOp(Opcode.map.OP_0) | ||
signatures.map(function(sig) { | ||
inScript.writeBytes(sig) | ||
}) | ||
inScript.writeBytes(script.buffer) | ||
return inScript | ||
Script.prototype.toHex = function() { | ||
return this.toBuffer().toString('hex') | ||
} | ||
Script.prototype.clone = function() { | ||
return new Script(this.buffer) | ||
} | ||
module.exports = Script |
@@ -1,12 +0,19 @@ | ||
var BigInteger = require('./jsbn/jsbn') | ||
var assert = require('assert') | ||
var bufferutils = require('./bufferutils') | ||
var crypto = require('./crypto') | ||
var opcodes = require('./opcodes') | ||
var scripts = require('./scripts') | ||
var Address = require('./address') | ||
var ECKey = require('./eckey') | ||
var ECSignature = require('./ecsignature') | ||
var Script = require('./script') | ||
var util = require('./util') | ||
var convert = require('./convert') | ||
var ECKey = require('./eckey').ECKey | ||
var ECDSA = require('./ecdsa') | ||
var Address = require('./address') | ||
var SHA256 = require('crypto-js/sha256') | ||
var Transaction = function (doc) { | ||
if (!(this instanceof Transaction)) { return new Transaction(doc) } | ||
Transaction.DEFAULT_SEQUENCE = 0xffffffff | ||
Transaction.SIGHASH_ALL = 0x01 | ||
Transaction.SIGHASH_NONE = 0x02 | ||
Transaction.SIGHASH_SINGLE = 0x03 | ||
Transaction.SIGHASH_ANYONECANPAY = 0x80 | ||
function Transaction() { | ||
this.version = 1 | ||
@@ -16,26 +23,2 @@ this.locktime = 0 | ||
this.outs = [] | ||
this.defaultSequence = [255, 255, 255, 255] // 0xFFFFFFFF | ||
if (doc) { | ||
if (typeof doc == "string" || Array.isArray(doc)) { | ||
doc = Transaction.deserialize(doc) | ||
} | ||
if (doc.hash) this.hash = doc.hash; | ||
if (doc.version) this.version = doc.version; | ||
if (doc.locktime) this.locktime = doc.locktime; | ||
if (doc.ins && doc.ins.length) { | ||
doc.ins.forEach(function(input) { | ||
this.addInput(new TransactionIn(input)) | ||
}, this) | ||
} | ||
if (doc.outs && doc.outs.length) { | ||
doc.outs.forEach(function(output) { | ||
this.addOutput(new TransactionOut(output)) | ||
}, this) | ||
} | ||
this.hash = this.hash || this.getHash() | ||
} | ||
} | ||
@@ -48,30 +31,35 @@ | ||
* | ||
* - An existing TransactionIn object | ||
* - A transaction and an index | ||
* - A transaction hash and an index | ||
* - A single string argument of the form txhash:index | ||
* | ||
* Note that this method does not sign the created input. | ||
*/ | ||
Transaction.prototype.addInput = function (tx, outIndex) { | ||
if (arguments[0] instanceof TransactionIn) { | ||
this.ins.push(arguments[0]) | ||
Transaction.prototype.addInput = function(tx, index, sequence) { | ||
if (sequence == undefined) sequence = Transaction.DEFAULT_SEQUENCE | ||
var hash | ||
if (typeof tx === 'string') { | ||
hash = new Buffer(tx, 'hex') | ||
// TxId hex is big-endian, we need little-endian | ||
Array.prototype.reverse.call(hash) | ||
} else if (tx instanceof Transaction) { | ||
hash = tx.getHash() | ||
} else { | ||
hash = tx | ||
} | ||
else if (arguments[0].length > 65) { | ||
var args = arguments[0].split(':') | ||
return this.addInput(args[0], args[1]) | ||
} | ||
else { | ||
var hash = typeof tx === "string" ? tx : tx.hash | ||
hash = Array.isArray(hash) ? convert.bytesToHex(hash) : hash | ||
this.ins.push(new TransactionIn({ | ||
outpoint: { | ||
hash: hash, | ||
index: outIndex | ||
}, | ||
script: new Script(), | ||
sequence: this.defaultSequence | ||
})) | ||
} | ||
assert(Buffer.isBuffer(hash), 'Expected Transaction, txId or txHash, got ' + tx) | ||
assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) | ||
assert.equal(typeof index, 'number', 'Expected number index, got ' + index) | ||
return (this.ins.push({ | ||
hash: hash, | ||
index: index, | ||
script: Script.EMPTY, | ||
sequence: sequence | ||
}) - 1) | ||
} | ||
@@ -84,61 +72,79 @@ | ||
* | ||
* i) An existing TransactionOut object | ||
* ii) An address object or an address and a value | ||
* iii) An address:value string | ||
* | ||
* - A base58 address string and a value | ||
* - An Address object and a value | ||
* - A scriptPubKey Script and a value | ||
*/ | ||
Transaction.prototype.addOutput = function (address, value) { | ||
if (arguments[0] instanceof TransactionOut) { | ||
this.outs.push(arguments[0]) | ||
return | ||
Transaction.prototype.addOutput = function(scriptPubKey, value) { | ||
// Attempt to get a valid address if it's a base58 address string | ||
if (typeof scriptPubKey === 'string') { | ||
scriptPubKey = Address.fromBase58Check(scriptPubKey) | ||
} | ||
if (arguments[0].indexOf(':') >= 0) { | ||
var args = arguments[0].split(':') | ||
address = args[0] | ||
value = parseInt(args[1]) | ||
// Attempt to get a valid script if it's an Address object | ||
if (scriptPubKey instanceof Address) { | ||
var address = scriptPubKey | ||
scriptPubKey = address.toOutputScript() | ||
} | ||
this.outs.push(new TransactionOut({ | ||
return (this.outs.push({ | ||
script: scriptPubKey, | ||
value: value, | ||
script: Script.createOutputScript(address) | ||
})) | ||
}) - 1) | ||
} | ||
/** | ||
* Serialize this transaction. | ||
* | ||
* Returns the transaction as a byte array in the standard Bitcoin binary | ||
* format. This method is byte-perfect, i.e. the resulting byte array can | ||
* be hashed to get the transaction's standard Bitcoin hash. | ||
*/ | ||
Transaction.prototype.serialize = function () { | ||
var buffer = [] | ||
buffer = buffer.concat(convert.numToBytes(parseInt(this.version), 4)) | ||
buffer = buffer.concat(convert.numToVarInt(this.ins.length)) | ||
Transaction.prototype.toBuffer = function () { | ||
var txInSize = this.ins.reduce(function(a, x) { | ||
return a + (40 + bufferutils.varIntSize(x.script.buffer.length) + x.script.buffer.length) | ||
}, 0) | ||
this.ins.forEach(function(txin) { | ||
// Why do blockchain.info, blockexplorer.com, sx and just about everybody | ||
// else use little-endian hashes? No idea... | ||
buffer = buffer.concat(convert.hexToBytes(txin.outpoint.hash).reverse()) | ||
var txOutSize = this.outs.reduce(function(a, x) { | ||
return a + (8 + bufferutils.varIntSize(x.script.buffer.length) + x.script.buffer.length) | ||
}, 0) | ||
buffer = buffer.concat(convert.numToBytes(parseInt(txin.outpoint.index), 4)) | ||
var buffer = new Buffer( | ||
8 + | ||
bufferutils.varIntSize(this.ins.length) + | ||
bufferutils.varIntSize(this.outs.length) + | ||
txInSize + | ||
txOutSize | ||
) | ||
var scriptBytes = txin.script.buffer | ||
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length)) | ||
buffer = buffer.concat(scriptBytes) | ||
buffer = buffer.concat(txin.sequence) | ||
var offset = 0 | ||
function writeSlice(slice) { | ||
slice.copy(buffer, offset) | ||
offset += slice.length | ||
} | ||
function writeUInt32(i) { | ||
buffer.writeUInt32LE(i, offset) | ||
offset += 4 | ||
} | ||
function writeUInt64(i) { | ||
bufferutils.writeUInt64LE(buffer, i, offset) | ||
offset += 8 | ||
} | ||
function writeVarInt(i) { | ||
var n = bufferutils.writeVarInt(buffer, i, offset) | ||
offset += n | ||
} | ||
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) | ||
}) | ||
buffer = buffer.concat(convert.numToVarInt(this.outs.length)) | ||
writeVarInt(this.outs.length) | ||
this.outs.forEach(function(txout) { | ||
buffer = buffer.concat(convert.numToBytes(txout.value,8)) | ||
var scriptBytes = txout.script.buffer | ||
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length)) | ||
buffer = buffer.concat(scriptBytes) | ||
writeUInt64(txout.value) | ||
writeVarInt(txout.script.buffer.length) | ||
writeSlice(txout.script.buffer) | ||
}) | ||
buffer = buffer.concat(convert.numToBytes(parseInt(this.locktime), 4)) | ||
writeUInt32(this.locktime) | ||
@@ -148,13 +154,6 @@ return buffer | ||
Transaction.prototype.serializeHex = function() { | ||
return convert.bytesToHex(this.serialize()) | ||
Transaction.prototype.toHex = function() { | ||
return this.toBuffer().toString('hex') | ||
} | ||
//var OP_CODESEPARATOR = 171 | ||
var SIGHASH_ALL = 1 | ||
var SIGHASH_NONE = 2 | ||
var SIGHASH_SINGLE = 3 | ||
var SIGHASH_ANYONECANPAY = 80 | ||
/** | ||
@@ -168,62 +167,50 @@ * Hash transaction for signing a specific input. | ||
*/ | ||
Transaction.prototype.hashTransactionForSignature = | ||
function (connectedScript, inIndex, hashType) | ||
{ | ||
Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashType) { | ||
assert(inIndex >= 0, 'Invalid vin index') | ||
assert(inIndex < this.ins.length, 'Invalid vin index') | ||
assert(prevOutScript instanceof Script, 'Invalid Script object') | ||
var txTmp = this.clone() | ||
var hashScript = prevOutScript.without(opcodes.OP_CODESEPARATOR) | ||
// In case concatenating two scripts ends up with two codeseparators, | ||
// or an extra one at the end, this prevents all those possible | ||
// incompatibilities. | ||
/*scriptCode = scriptCode.filter(function (val) { | ||
return val !== OP_CODESEPARATOR | ||
});*/ | ||
// Blank out other inputs' signatures | ||
txTmp.ins.forEach(function(txin) { | ||
txin.script = new Script() | ||
txin.script = Script.EMPTY | ||
}) | ||
txTmp.ins[inIndex].script = hashScript | ||
txTmp.ins[inIndex].script = connectedScript | ||
var hashTypeModifier = hashType & 0x1f | ||
if (hashTypeModifier === Transaction.SIGHASH_NONE) { | ||
assert(false, 'SIGHASH_NONE not yet supported') | ||
// Blank out some of the outputs | ||
if ((hashType & 0x1f) == SIGHASH_NONE) { | ||
txTmp.outs = [] | ||
} else if (hashTypeModifier === Transaction.SIGHASH_SINGLE) { | ||
assert(false, 'SIGHASH_SINGLE not yet supported') | ||
// Let the others update at will | ||
txTmp.ins.forEach(function(txin, i) { | ||
if (i != inIndex) { | ||
txTmp.ins[i].sequence = 0 | ||
} | ||
}) | ||
} else if ((hashType & 0x1f) == SIGHASH_SINGLE) { | ||
// TODO: Implement | ||
} | ||
// Blank out other inputs completely, not recommended for open transactions | ||
if (hashType & SIGHASH_ANYONECANPAY) { | ||
txTmp.ins = [txTmp.ins[inIndex]] | ||
if (hashType & Transaction.SIGHASH_ANYONECANPAY) { | ||
assert(false, 'SIGHASH_ANYONECANPAY not yet supported') | ||
} | ||
var buffer = txTmp.serialize() | ||
var hashTypeBuffer = new Buffer(4) | ||
hashTypeBuffer.writeInt32LE(hashType, 0) | ||
buffer = buffer.concat(convert.numToBytes(parseInt(hashType), 4)) | ||
buffer = convert.bytesToWordArray(buffer) | ||
var buffer = Buffer.concat([txTmp.toBuffer(), hashTypeBuffer]) | ||
return crypto.hash256(buffer) | ||
} | ||
return convert.wordArrayToBytes(SHA256(SHA256(buffer))) | ||
Transaction.prototype.getHash = function () { | ||
return crypto.hash256(this.toBuffer()) | ||
} | ||
/** | ||
* Calculate and return the transaction's hash. | ||
* Reverses hash since blockchain.info, blockexplorer.com and others | ||
* use little-endian hashes for some stupid reason | ||
*/ | ||
Transaction.prototype.getHash = function () | ||
{ | ||
var buffer = convert.bytesToWordArray(this.serialize()) | ||
return convert.wordArrayToBytes(SHA256(SHA256(buffer))).reverse() | ||
Transaction.prototype.getId = function () { | ||
var buffer = this.getHash() | ||
// Big-endian is used for TxHash | ||
Array.prototype.reverse.call(buffer) | ||
return buffer.toString('hex') | ||
} | ||
Transaction.prototype.clone = function () | ||
{ | ||
Transaction.prototype.clone = function () { | ||
var newTx = new Transaction() | ||
@@ -233,8 +220,16 @@ newTx.version = this.version | ||
this.ins.forEach(function(txin) { | ||
newTx.addInput(txin.clone()) | ||
newTx.ins = this.ins.map(function(txin) { | ||
return { | ||
hash: txin.hash, | ||
index: txin.index, | ||
script: txin.script, | ||
sequence: txin.sequence | ||
} | ||
}) | ||
this.outs.forEach(function(txout) { | ||
newTx.addOutput(txout.clone()) | ||
newTx.outs = this.outs.map(function(txout) { | ||
return { | ||
script: txout.script, | ||
value: txout.value | ||
} | ||
}) | ||
@@ -245,211 +240,98 @@ | ||
Transaction.deserialize = function(buffer) { | ||
if (typeof buffer == "string") { | ||
buffer = convert.hexToBytes(buffer) | ||
Transaction.fromBuffer = function(buffer) { | ||
var offset = 0 | ||
function readSlice(n) { | ||
offset += n | ||
return buffer.slice(offset - n, offset) | ||
} | ||
var pos = 0 | ||
var readAsInt = function(bytes) { | ||
if (bytes === 0) return 0; | ||
pos++; | ||
return buffer[pos-1] + readAsInt(bytes-1) * 256 | ||
function readUInt32() { | ||
var i = buffer.readUInt32LE(offset) | ||
offset += 4 | ||
return i | ||
} | ||
var readVarInt = function() { | ||
var bytes = buffer.slice(pos, pos + 9) // maximum possible number of bytes to read | ||
var result = convert.varIntToNum(bytes) | ||
pos += result.bytes.length | ||
return result.number | ||
function readUInt64() { | ||
var i = bufferutils.readUInt64LE(buffer, offset) | ||
offset += 8 | ||
return i | ||
} | ||
var readBytes = function(bytes) { | ||
pos += bytes | ||
return buffer.slice(pos - bytes, pos) | ||
function readVarInt() { | ||
var vi = bufferutils.readVarInt(buffer, offset) | ||
offset += vi.size | ||
return vi.number | ||
} | ||
var readVarString = function() { | ||
var size = readVarInt() | ||
return readBytes(size) | ||
} | ||
var obj = { | ||
ins: [], | ||
outs: [] | ||
} | ||
obj.version = readAsInt(4) | ||
var ins = readVarInt() | ||
var i | ||
for (i = 0; i < ins; i++) { | ||
obj.ins.push({ | ||
outpoint: { | ||
hash: convert.bytesToHex(readBytes(32).reverse()), | ||
index: readAsInt(4) | ||
}, | ||
script: new Script(readVarString()), | ||
sequence: readBytes(4) | ||
var tx = new Transaction() | ||
tx.version = readUInt32() | ||
var vinLen = readVarInt() | ||
for (var i = 0; i < vinLen; ++i) { | ||
var hash = readSlice(32) | ||
var vout = readUInt32() | ||
var scriptLen = readVarInt() | ||
var script = readSlice(scriptLen) | ||
var sequence = readUInt32() | ||
tx.ins.push({ | ||
hash: hash, | ||
index: vout, | ||
script: Script.fromBuffer(script), | ||
sequence: sequence | ||
}) | ||
} | ||
var outs = readVarInt() | ||
for (i = 0; i < outs; i++) { | ||
obj.outs.push({ | ||
value: convert.bytesToNum(readBytes(8)), | ||
script: new Script(readVarString()) | ||
var voutLen = readVarInt() | ||
for (i = 0; i < voutLen; ++i) { | ||
var value = readUInt64() | ||
var scriptLen = readVarInt() | ||
var script = readSlice(scriptLen) | ||
tx.outs.push({ | ||
value: value, | ||
script: Script.fromBuffer(script) | ||
}) | ||
} | ||
obj.locktime = readAsInt(4) | ||
tx.locktime = readUInt32() | ||
assert.equal(offset, buffer.length, 'Transaction has unexpected data') | ||
return new Transaction(obj) | ||
return tx | ||
} | ||
/** | ||
* Signs a standard output at some index with the given key | ||
*/ | ||
Transaction.prototype.sign = function(index, key, type) { | ||
type = type || SIGHASH_ALL | ||
key = new ECKey(key) | ||
// TODO: getPub is slow, sha256ripe160 probably is too. | ||
// This could be sped up a lot by providing these as inputs. | ||
var pub = key.getPub().toBytes(), | ||
hash160 = util.sha256ripe160(pub), | ||
script = Script.createOutputScript(new Address(hash160)), | ||
hash = this.hashTransactionForSignature(script, index, type), | ||
sig = key.sign(hash).concat([type]) | ||
this.ins[index].script = Script.createInputScript(sig, pub) | ||
Transaction.fromHex = function(hex) { | ||
return Transaction.fromBuffer(new Buffer(hex, 'hex')) | ||
} | ||
// Takes outputs of the form [{ output: 'txhash:index', address: 'address' },...] | ||
Transaction.prototype.signWithKeys = function(keys, outputs, type) { | ||
type = type || SIGHASH_ALL | ||
var addrdata = keys.map(function(key) { | ||
key = new ECKey(key) | ||
return { | ||
key: key, | ||
address: key.getAddress().toString() | ||
} | ||
}) | ||
var hmap = {} | ||
outputs.forEach(function(o) { | ||
hmap[o.output] = o | ||
}) | ||
for (var i = 0; i < this.ins.length; i++) { | ||
var outpoint = this.ins[i].outpoint.hash + ':' + this.ins[i].outpoint.index | ||
var histItem = hmap[outpoint] | ||
if (!histItem) continue; | ||
var thisInputAddrdata = addrdata.filter(function(a) { | ||
return a.address == histItem.address | ||
}) | ||
if (thisInputAddrdata.length === 0) continue; | ||
this.sign(i,thisInputAddrdata[0].key) | ||
} | ||
} | ||
/** | ||
* Signs a P2SH output at some index with the given key | ||
* Signs a pubKeyHash output at some index with the given key | ||
*/ | ||
Transaction.prototype.p2shsign = function(index, script, key, type) { | ||
script = new Script(script) | ||
key = new ECKey(key) | ||
type = type || SIGHASH_ALL | ||
var hash = this.hashTransactionForSignature(script, index, type), | ||
sig = key.sign(hash).concat([type]) | ||
return sig | ||
} | ||
Transaction.prototype.sign = function(index, privKey, hashType) { | ||
var prevOutScript = privKey.pub.getAddress().toOutputScript() | ||
var signature = this.signInput(index, prevOutScript, privKey, hashType) | ||
Transaction.prototype.multisign = Transaction.prototype.p2shsign | ||
Transaction.prototype.applyMultisigs = function(index, script, sigs/*, type*/) { | ||
this.ins[index].script = Script.createMultiSigInputScript(sigs, script) | ||
// FIXME: Assumed prior TX was pay-to-pubkey-hash | ||
var scriptSig = scripts.pubKeyHashInput(signature, privKey.pub) | ||
this.setInputScript(index, scriptSig) | ||
} | ||
Transaction.prototype.validateSig = function(index, script, sig, pub) { | ||
script = new Script(script) | ||
var hash = this.hashTransactionForSignature(script,index,1) | ||
return ECDSA.verify(hash, convert.coerceToBytes(sig), | ||
convert.coerceToBytes(pub)) | ||
} | ||
Transaction.prototype.signInput = function(index, prevOutScript, privKey, hashType) { | ||
hashType = hashType || Transaction.SIGHASH_ALL | ||
Transaction.feePerKb = 20000 | ||
Transaction.prototype.estimateFee = function(feePerKb){ | ||
var uncompressedInSize = 180 | ||
var outSize = 34 | ||
var fixedPadding = 34 | ||
var hash = this.hashForSignature(prevOutScript, index, hashType) | ||
var signature = privKey.sign(hash) | ||
if(feePerKb == undefined) feePerKb = Transaction.feePerKb; | ||
var size = this.ins.length * uncompressedInSize + this.outs.length * outSize + fixedPadding | ||
return feePerKb * Math.ceil(size / 1000) | ||
return signature.toScriptSignature(hashType) | ||
} | ||
var TransactionIn = function (data) { | ||
if (typeof data == "string") { | ||
this.outpoint = { hash: data.split(':')[0], index: data.split(':')[1] } | ||
} else if (data.outpoint) { | ||
this.outpoint = data.outpoint | ||
} else { | ||
this.outpoint = { hash: data.hash, index: data.index } | ||
} | ||
if (data.scriptSig) { | ||
this.script = Script.fromScriptSig(data.scriptSig) | ||
} else if (data.script) { | ||
this.script = data.script | ||
} else { | ||
this.script = new Script(data.script) | ||
} | ||
this.sequence = data.sequence || this.defaultSequence | ||
Transaction.prototype.setInputScript = function(index, script) { | ||
this.ins[index].script = script | ||
} | ||
TransactionIn.prototype.clone = function () { | ||
return new TransactionIn({ | ||
outpoint: { | ||
hash: this.outpoint.hash, | ||
index: this.outpoint.index | ||
}, | ||
script: this.script.clone(), | ||
sequence: this.sequence | ||
}) | ||
} | ||
// FIXME: could be validateInput(index, prevTxOut, pub) | ||
Transaction.prototype.validateInput = function(index, prevOutScript, pubKey, buffer) { | ||
var parsed = ECSignature.parseScriptSignature(buffer) | ||
var hash = this.hashForSignature(prevOutScript, index, parsed.hashType) | ||
var TransactionOut = function (data) { | ||
this.script = | ||
data.script instanceof Script ? data.script.clone() | ||
: Array.isArray(data.script) ? new Script(data.script) | ||
: typeof data.script == "string" ? new Script(convert.hexToBytes(data.script)) | ||
: data.scriptPubKey ? Script.fromScriptSig(data.scriptPubKey) | ||
: data.address ? Script.createOutputScript(data.address) | ||
: new Script() | ||
if (this.script.buffer.length > 0) this.address = this.script.getToAddress(); | ||
this.value = | ||
Array.isArray(data.value) ? convert.bytesToNum(data.value) | ||
: "string" == typeof data.value ? parseInt(data.value) | ||
: data.value instanceof BigInteger ? parseInt(data.value.toString()) | ||
: data.value | ||
return pubKey.verify(hash, parsed.signature) | ||
} | ||
TransactionOut.prototype.clone = function() { | ||
var newTxout = new TransactionOut({ | ||
script: this.script.clone(), | ||
value: this.value | ||
}) | ||
return newTxout | ||
} | ||
TransactionOut.prototype.scriptPubKey = function() { | ||
return convert.bytesToHex(this.script.buffer) | ||
} | ||
module.exports = { | ||
Transaction: Transaction, | ||
TransactionIn: TransactionIn, | ||
TransactionOut: TransactionOut | ||
} | ||
module.exports = Transaction |
@@ -1,11 +0,11 @@ | ||
var convert = require('./convert') | ||
var Transaction = require('./transaction').Transaction | ||
var HDNode = require('./hdwallet.js') | ||
var assert = require('assert') | ||
var networks = require('./networks') | ||
var rng = require('secure-random') | ||
function Wallet(seed, options) { | ||
if (!(this instanceof Wallet)) { return new Wallet(seed, options); } | ||
var Address = require('./address') | ||
var HDNode = require('./hdnode') | ||
var Transaction = require('./transaction') | ||
var options = options || {} | ||
var network = options.network || 'mainnet' | ||
function Wallet(seed, network) { | ||
network = network || networks.bitcoin | ||
@@ -27,9 +27,9 @@ // Stored in a closure to make accidental serialization less likely | ||
// Make a new master key | ||
this.newMasterKey = function(seed, network) { | ||
if (!seed) seed = rng(32, { array: true }); | ||
masterkey = new HDNode(seed, network) | ||
this.newMasterKey = function(seed) { | ||
seed = seed || new Buffer(rng(32)) | ||
masterkey = HDNode.fromSeedBuffer(seed, network) | ||
// HD first-level child derivation method should be private | ||
// HD first-level child derivation method should be hardened | ||
// See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 | ||
accountZero = masterkey.derivePrivate(0) | ||
accountZero = masterkey.deriveHardened(0) | ||
externalAccount = accountZero.derive(0) | ||
@@ -43,4 +43,4 @@ internalAccount = accountZero.derive(1) | ||
} | ||
this.newMasterKey(seed, network) | ||
this.newMasterKey(seed) | ||
@@ -70,3 +70,3 @@ this.generateAddress = function() { | ||
var output = this.outputs[key] | ||
if(!output.spend) utxo.push(outputToUnspentOutput(output)) | ||
if(!output.to) utxo.push(outputToUnspentOutput(output)) | ||
} | ||
@@ -83,3 +83,3 @@ | ||
var o = unspentOutputToOutput(uo) | ||
outputs[o.receive] = o | ||
outputs[o.from] = o | ||
}) | ||
@@ -90,22 +90,11 @@ | ||
this.setUnspentOutputsAsync = function(utxo, callback) { | ||
var error = null | ||
try { | ||
this.setUnspentOutputs(utxo) | ||
} catch(err) { | ||
error = err | ||
} finally { | ||
process.nextTick(function(){ callback(error) }) | ||
} | ||
} | ||
function outputToUnspentOutput(output){ | ||
var hashAndIndex = output.receive.split(":") | ||
var hashAndIndex = output.from.split(":") | ||
return { | ||
hash: hashAndIndex[0], | ||
hashLittleEndian: convert.reverseEndian(hashAndIndex[0]), | ||
outputIndex: parseInt(hashAndIndex[1]), | ||
address: output.address, | ||
value: output.value | ||
value: output.value, | ||
pending: output.pending | ||
} | ||
@@ -115,8 +104,9 @@ } | ||
function unspentOutputToOutput(o) { | ||
var hash = o.hash || convert.reverseEndian(o.hashLittleEndian) | ||
var hash = o.hash | ||
var key = hash + ":" + o.outputIndex | ||
return { | ||
receive: key, | ||
from: key, | ||
address: o.address, | ||
value: o.value | ||
value: o.value, | ||
pending: o.pending | ||
} | ||
@@ -128,4 +118,4 @@ } | ||
if (isNullOrUndefined(uo.hash) && isNullOrUndefined(uo.hashLittleEndian)) { | ||
missingField = "hash(or hashLittleEndian)" | ||
if (isNullOrUndefined(uo.hash)) { | ||
missingField = "hash" | ||
} | ||
@@ -146,3 +136,3 @@ | ||
message.push(requiredKeys.join(', ')) | ||
message.push("and hash(or hashLittleEndian)") | ||
message.push("and hash") | ||
throw new Error(message.join(' ')) | ||
@@ -156,13 +146,30 @@ } | ||
this.processTx = function(tx) { | ||
var txhash = convert.bytesToHex(tx.getHash()) | ||
this.processPendingTx = function(tx){ | ||
processTx(tx, true) | ||
} | ||
tx.outs.forEach(function(txOut, i){ | ||
var address = txOut.address.toString() | ||
this.processConfirmedTx = function(tx){ | ||
processTx(tx, false) | ||
} | ||
function processTx(tx, isPending) { | ||
var txid = tx.getId() | ||
tx.outs.forEach(function(txOut, i) { | ||
var address | ||
try { | ||
address = Address.fromOutputScript(txOut.script, network).toString() | ||
} catch(e) { | ||
if (!(e.message.match(/has no matching Address/))) throw e | ||
} | ||
if (isMyAddress(address)) { | ||
var output = txhash+':'+i | ||
var output = txid + ':' + i | ||
me.outputs[output] = { | ||
receive: output, | ||
from: output, | ||
value: txOut.value, | ||
address: address, | ||
pending: isPending | ||
} | ||
@@ -172,7 +179,17 @@ } | ||
tx.ins.forEach(function(txIn, i){ | ||
var op = txIn.outpoint | ||
var o = me.outputs[op.hash+':'+op.index] | ||
if (o) { | ||
o.spend = txhash+':'+i | ||
tx.ins.forEach(function(txIn, i) { | ||
// copy and convert to big-endian hex | ||
var txinId = new Buffer(txIn.hash) | ||
Array.prototype.reverse.call(txinId) | ||
txinId = txinId.toString('hex') | ||
var output = txinId + ':' + txIn.index | ||
if (!(output in me.outputs)) return | ||
if (isPending) { | ||
me.outputs[output].to = txid + ':' + i | ||
me.outputs[output].pending = true | ||
} else { | ||
delete me.outputs[output] | ||
} | ||
@@ -182,67 +199,47 @@ }) | ||
this.createTx = function(to, value, fixedFee) { | ||
checkDust(value) | ||
this.createTx = function(to, value, fixedFee, changeAddress) { | ||
assert(value > network.dustThreshold, value + ' must be above dust threshold (' + network.dustThreshold + ' Satoshis)') | ||
var utxos = getCandidateOutputs(value) | ||
var accum = 0 | ||
var subTotal = value | ||
var addresses = [] | ||
var tx = new Transaction() | ||
tx.addOutput(to, value) | ||
var utxo = getCandidateOutputs(value) | ||
var totalInValue = 0 | ||
for(var i=0; i<utxo.length; i++){ | ||
var output = utxo[i] | ||
tx.addInput(output.receive) | ||
for (var i = 0; i < utxos.length; ++i) { | ||
var utxo = utxos[i] | ||
addresses.push(utxo.address) | ||
totalInValue += output.value | ||
if(totalInValue < value) continue | ||
var outpoint = utxo.from.split(':') | ||
tx.addInput(outpoint[0], parseInt(outpoint[1])) | ||
var fee = fixedFee == undefined ? estimateFeePadChangeOutput(tx) : fixedFee | ||
if(totalInValue < value + fee) continue | ||
var change = totalInValue - value - fee | ||
if(change > 0 && !isDust(change)) { | ||
tx.addOutput(getChangeAddress(), change) | ||
accum += utxo.value | ||
subTotal = value + fee | ||
if (accum >= subTotal) { | ||
var change = accum - subTotal | ||
if (change > network.dustThreshold) { | ||
tx.addOutput(changeAddress || getChangeAddress(), change) | ||
} | ||
break | ||
} | ||
break | ||
} | ||
checkInsufficientFund(totalInValue, value, fee) | ||
assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) | ||
this.sign(tx) | ||
this.signWith(tx, addresses) | ||
return tx | ||
} | ||
this.createTxAsync = function(to, value, fixedFee, callback){ | ||
if(fixedFee instanceof Function) { | ||
callback = fixedFee | ||
fixedFee = undefined | ||
} | ||
var tx = null | ||
var error = null | ||
function getCandidateOutputs() { | ||
var unspent = [] | ||
try { | ||
tx = this.createTx(to, value, fixedFee) | ||
} catch(err) { | ||
error = err | ||
} finally { | ||
process.nextTick(function(){ callback(error, tx) }) | ||
} | ||
} | ||
this.dustThreshold = 5430 | ||
function isDust(amount) { | ||
return amount <= me.dustThreshold | ||
} | ||
function checkDust(value){ | ||
if (isNullOrUndefined(value) || isDust(value)) { | ||
throw new Error("Value must be above dust threshold") | ||
} | ||
} | ||
function getCandidateOutputs(value){ | ||
var unspent = [] | ||
for (var key in me.outputs){ | ||
for (var key in me.outputs) { | ||
var output = me.outputs[key] | ||
if(!output.spend) unspent.push(output) | ||
if (!output.pending) unspent.push(output) | ||
} | ||
@@ -257,6 +254,7 @@ | ||
function estimateFeePadChangeOutput(tx){ | ||
function estimateFeePadChangeOutput(tx) { | ||
var tmpTx = tx.clone() | ||
tmpTx.addOutput(getChangeAddress(), 0) | ||
return tmpTx.estimateFee() | ||
tmpTx.addOutput(getChangeAddress(), network.dustSoftThreshold || 0) | ||
return network.estimateFee(tmpTx) | ||
} | ||
@@ -269,16 +267,11 @@ | ||
function checkInsufficientFund(totalInValue, value, fee) { | ||
if(totalInValue < value + fee) { | ||
throw new Error('Not enough money to send funds including transaction fee. Have: ' + | ||
totalInValue + ', needed: ' + (value + fee)) | ||
} | ||
} | ||
this.signWith = function(tx, addresses) { | ||
assert.equal(tx.ins.length, addresses.length, 'Number of addresses must match number of transaction inputs') | ||
this.sign = function(tx) { | ||
tx.ins.forEach(function(inp,i) { | ||
var output = me.outputs[inp.outpoint.hash + ':' + inp.outpoint.index] | ||
if (output) { | ||
tx.sign(i, me.getPrivateKeyForAddress(output.address)) | ||
} | ||
addresses.forEach(function(address, i) { | ||
var key = me.getPrivateKeyForAddress(address) | ||
tx.sign(i, key) | ||
}) | ||
return tx | ||
@@ -293,7 +286,7 @@ } | ||
this.getPrivateKey = function(index) { | ||
return externalAccount.derive(index).priv | ||
return externalAccount.derive(index).privKey | ||
} | ||
this.getInternalPrivateKey = function(index) { | ||
return internalAccount.derive(index).priv | ||
return internalAccount.derive(index).privKey | ||
} | ||
@@ -300,0 +293,0 @@ |
var assert = require('assert') | ||
var networks = require('../src/networks') | ||
var Address = require('../src/address') | ||
var network = require('../src/network') | ||
var base58 = require('../src/base58') | ||
var base58check = require('../src/base58check') | ||
var mainnet = network.mainnet.addressVersion | ||
var testnet = network.testnet.addressVersion | ||
var Script = require('../src/script') | ||
var fixtures = require('./fixtures/address.json') | ||
describe('Address', function() { | ||
var testnetAddress, mainnetAddress | ||
var testnetP2shAddress, mainnetP2shAddress | ||
describe('Constructor', function() { | ||
it('does not mutate the input', function() { | ||
fixtures.valid.forEach(function(f) { | ||
var hash = new Buffer(f.hex, 'hex') | ||
var addr = new Address(hash, f.version) | ||
beforeEach(function(){ | ||
mainnetAddress = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' | ||
testnetAddress = 'mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef' | ||
mainnetP2shAddress = '3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt' | ||
testnetP2shAddress = '2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7' | ||
assert.equal(addr.version, f.version) | ||
assert.equal(addr.hash.toString('hex'), f.hex) | ||
}) | ||
}) | ||
}) | ||
describe('parsing', function() { | ||
it('works with Address object', function() { | ||
var addr = new Address(new Address('mwrB4fgT1KSBCqELaWv7o7tsExuQzW3NY3', network.testnet.addressVersion)) | ||
describe('fromBase58Check', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('imports ' + f.description + '(' + f.network + ') correctly', function() { | ||
var addr = Address.fromBase58Check(f.base58check) | ||
assert.equal(addr.toString(), 'mwrB4fgT1KSBCqELaWv7o7tsExuQzW3NY3') | ||
assert.equal(addr.version, network.testnet.addressVersion) | ||
assert.equal(addr.version, f.version) | ||
assert.equal(addr.hash.toString('hex'), f.hex) | ||
}) | ||
}) | ||
it('works with hex', function() { | ||
var addr = new Address('13483382d3c3d43fc9d7b52e652b6bbb70e8b667') | ||
assert.equal(addr.toString(), '12kxLGqrnnchwN9bHHNV2fWDtJGwxKTcJS') | ||
fixtures.invalid.fromBase58Check.forEach(function(f) { | ||
it('throws on ' + f.description, function() { | ||
assert.throws(function() { | ||
Address.fromBase58Check(f.base58check) | ||
}, new RegExp(f.exception)) | ||
}) | ||
}) | ||
}) | ||
it('throws error for invalid or unrecognized input', function() { | ||
assert.throws(function() { | ||
new Address('beepboopbeepboopbeepboopbeepboopbeepboopbeep') | ||
}, Error) | ||
}) | ||
describe('fromOutputScript', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('imports ' + f.description + '(' + f.network + ') correctly', function() { | ||
var script = Script.fromHex(f.script) | ||
var addr = Address.fromOutputScript(script, networks[f.network]) | ||
it('works for byte input', function() { | ||
var hash = base58check.decode(mainnetAddress) | ||
var addr = new Address(hash.payload) | ||
assert.equal(addr.hash, hash.payload) | ||
assert.equal(network.mainnet.addressVersion, hash.version) | ||
var hash = base58check.decode(testnetAddress) | ||
var addr = new Address(hash.payload) | ||
assert.equal(addr.hash, hash.payload) | ||
assert.equal(network.testnet.addressVersion, hash.version) | ||
assert.equal(addr.version, f.version) | ||
assert.equal(addr.hash.toString('hex'), f.hex) | ||
}) | ||
}) | ||
it('fails for bad input', function() { | ||
assert.throws(function() { | ||
new Address('foo') | ||
}, Error) | ||
}) | ||
}) | ||
fixtures.invalid.fromOutputScript.forEach(function(f) { | ||
it('throws when ' + f.description, function() { | ||
var script = Script.fromHex(f.hex) | ||
describe('getVersion', function() { | ||
it('returns the proper address version', function() { | ||
assert.equal(Address.getVersion(mainnetAddress), network.mainnet.addressVersion) | ||
assert.equal(Address.getVersion(testnetAddress), network.testnet.addressVersion) | ||
assert.throws(function() { | ||
Address.fromOutputScript(script) | ||
}, new RegExp(f.description)) | ||
}) | ||
}) | ||
}) | ||
describe('toString', function() { | ||
it('defaults to base58', function() { | ||
var addr = '18fN1QTGWmHWCA9r2dyDH6FbMEyc7XHmQQ' | ||
assert.equal((new Address(addr)).toString(), addr) | ||
}) | ||
}) | ||
describe('toBase58Check', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('exports ' + f.description + '(' + f.network + ') correctly', function() { | ||
var addr = Address.fromBase58Check(f.base58check) | ||
var result = addr.toBase58Check() | ||
describe('Constructor', function(){ | ||
it('resolves version correctly', function(){ | ||
assert.equal((new Address(testnetAddress)).version, testnet) | ||
assert.equal((new Address(mainnetAddress)).version, mainnet) | ||
assert.equal((new Address(testnetP2shAddress)).version, network.testnet.p2shVersion) | ||
assert.equal((new Address(mainnetP2shAddress)).version, network.mainnet.p2shVersion) | ||
assert.equal(result, f.base58check) | ||
}) | ||
}) | ||
}) | ||
describe('validate', function() { | ||
it('validates known good addresses', function() { | ||
function validate(addr, expectedVersion) { | ||
assert.ok(Address.validate(addr)) | ||
} | ||
describe('toOutputScript', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('imports ' + f.description + '(' + f.network + ') correctly', function() { | ||
var addr = Address.fromBase58Check(f.base58check) | ||
var script = addr.toOutputScript() | ||
validate(testnetAddress) | ||
validate(mainnetAddress) | ||
validate('12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP') | ||
validate('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y') | ||
validate('1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs') | ||
validate('1SQHtwR5oJRKLfiWQ2APsAd9miUc4k2ez') | ||
validate('116CGDLddrZhMrTwhCVJXtXQpxygTT1kHd') | ||
// p2sh addresses | ||
validate(testnetP2shAddress) | ||
validate(mainnetP2shAddress) | ||
assert.equal(script.toHex(), f.script) | ||
}) | ||
}) | ||
it('does not validate illegal examples', function() { | ||
function invalid(addr) { | ||
assert.ok(!Address.validate(addr)) | ||
} | ||
fixtures.invalid.toOutputScript.forEach(function(f) { | ||
it('throws when ' + f.description, function() { | ||
var addr = new Address(new Buffer(f.hex, 'hex'), f.version) | ||
invalid(''); //empty should be invalid | ||
invalid('%%@'); // invalid base58 string | ||
invalid('1A1zP1eP5QGefi2DzPTf2L5SLmv7DivfNz'); // bad address (doesn't checksum) | ||
invalid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhe'); // bad address (doesn't checksum) | ||
assert.throws(function() { | ||
addr.toOutputScript() | ||
}, new RegExp(f.description)) | ||
}) | ||
}) | ||
}) | ||
}) |
var assert = require('assert') | ||
var base58check = require('../').base58check | ||
var base58check = require('../src/base58check') | ||
var fixtures = require('./fixtures/base58check.json') | ||
describe('base58check', function() { | ||
var evec, dvec | ||
describe('decode', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('can decode ' + f.string, function() { | ||
var actual = base58check.decode(f.string).toString('hex') | ||
beforeEach(function() { | ||
function fromHex(h) { return new Buffer(h, 'hex') } | ||
// base58check encoded strings | ||
evec = [ | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAbuatmU', // 0x00 WIF | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf', // 0x01 WIF | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreQyNNN1W', // 0x7f WIF | ||
'1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm', // uncompressed 0x01 address | ||
'1FB8cZijTpRQp3HX8AEkNuQJBqApqfTcX7' // uncompressed 0x7f address | ||
] | ||
// decoded equivalent of above | ||
dvec = [ | ||
{ | ||
version: 0x80, | ||
payload: '0000000000000000000000000000000000000000000000000000000000000000', | ||
checksum: '0565fba7' | ||
}, | ||
{ | ||
version: 0x80, | ||
payload: '0000000000000000000000000000000000000000000000000000000000000001', | ||
checksum: 'a85aa87e', | ||
}, | ||
{ | ||
version: 0x80, | ||
payload: '000000000000000000000000000000000000000000000000000000000000007f', | ||
checksum: '64046be9', | ||
}, | ||
{ | ||
version: 0x00, | ||
payload: '91b24bf9f5288532960ac687abb035127b1d28a5', | ||
checksum: '0074ffe0', | ||
}, | ||
{ | ||
version: 0x00, | ||
payload: '9b7c46977b68474e12066a370b169ec6b9b02644', | ||
checksum: '4d210d6e' | ||
} | ||
].map(function(x) { | ||
return { | ||
version: x.version, | ||
payload: fromHex(x.payload), | ||
checksum: fromHex(x.checksum) | ||
} | ||
assert.equal(actual, f.payload) | ||
}) | ||
}) | ||
}) | ||
describe('decode', function() { | ||
it('decodes the test vectors', function() { | ||
evec.forEach(function(x, i) { | ||
var actual = base58check.decode(x) | ||
var expected = dvec[i] | ||
assert.deepEqual(expected, actual) | ||
fixtures.invalid.forEach(function(f) { | ||
it('throws on ' + f, function() { | ||
assert.throws(function() { | ||
base58check.decode(f) | ||
}, /Invalid checksum/) | ||
}) | ||
@@ -67,8 +26,7 @@ }) | ||
describe('encode', function() { | ||
it('encodes the test vectors', function() { | ||
dvec.forEach(function(x, i) { | ||
var actual = base58check.encode(x.payload, x.version) | ||
var expected = evec[i] | ||
fixtures.valid.forEach(function(f) { | ||
it('can encode ' + f.string, function() { | ||
var actual = base58check.encode(new Buffer(f.payload, 'hex')) | ||
assert.deepEqual(expected, actual) | ||
assert.strictEqual(actual, f.string) | ||
}) | ||
@@ -75,0 +33,0 @@ }) |
var assert = require('assert') | ||
var convert = require('../src/convert.js') | ||
var convert = require('../src/convert') | ||
var fixtures = require('./fixtures/convert') | ||
describe('convert', function() { | ||
describe('bytesToHex', function() { | ||
it('handles example 1', function() { | ||
assert.equal(convert.bytesToHex([0, 1, 2, 255]), '000102ff') | ||
}) | ||
}) | ||
describe('bufferToWordArray', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('converts ' + f.hex + ' correctly', function() { | ||
var buffer = new Buffer(f.hex, 'hex') | ||
var result = convert.bufferToWordArray(buffer) | ||
describe('hexToBytes', function() { | ||
it('handles example 1', function() { | ||
assert.deepEqual(convert.hexToBytes('000102ff'), [0, 1, 2, 255]) | ||
assert.deepEqual(result, f.wordArray) | ||
}) | ||
}) | ||
}) | ||
it('converts from bytes to hex and back', function() { | ||
var bytes = [] | ||
for (var i=0 ; i<256 ; ++i) { | ||
bytes.push(i) | ||
} | ||
describe('wordArrayToBuffer', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('converts to ' + f.hex + ' correctly', function() { | ||
var resultHex = convert.wordArrayToBuffer(f.wordArray).toString('hex') | ||
var hex = convert.bytesToHex(bytes) | ||
assert.equal(hex.length, 512) | ||
assert.deepEqual(convert.hexToBytes(hex), bytes) | ||
}) | ||
describe('bytesToBase64', function() { | ||
it('passes RFC4648 test vectors', function() { | ||
// Test vectors from: | ||
// http://tools.ietf.org/html/rfc4648#page-12 | ||
var b64 = function(s) { | ||
return convert.bytesToBase64(convert.stringToBytes(s)) | ||
} | ||
assert.equal(b64(''), '') | ||
assert.equal(b64('f'), 'Zg==') | ||
assert.equal(b64('fo'), 'Zm8=') | ||
assert.equal(b64('foo'), 'Zm9v') | ||
assert.equal(b64('foob'), 'Zm9vYg==') | ||
assert.equal(b64('fooba'), 'Zm9vYmE=') | ||
assert.equal(b64('foobar'), 'Zm9vYmFy') | ||
}) | ||
}) | ||
describe('byte array and word array conversions', function(){ | ||
var bytes, wordArray | ||
beforeEach(function(){ | ||
bytes = [ | ||
98, 233, 7, 177, 92, 191, 39, 213, 66, 83, | ||
153, 235, 246, 240, 251, 80, 235, 184, 143, 24 | ||
] | ||
wordArray = { | ||
words: [1659439025, 1556031445, 1112775147, -151979184, -340226280], | ||
sigBytes: 20 | ||
} | ||
}) | ||
describe('bytesToWords', function() { | ||
it('works', function() { | ||
assert.deepEqual(convert.bytesToWordArray(bytes), wordArray) | ||
assert.deepEqual(resultHex, f.hex) | ||
}) | ||
}) | ||
describe('bytesToWords', function() { | ||
it('works', function() { | ||
assert.deepEqual(convert.wordArrayToBytes(wordArray), bytes) | ||
}) | ||
}) | ||
}) | ||
describe('numToVarInt', function() { | ||
describe('works', function() { | ||
var data = [ | ||
0, 128, 252, // 8-bit | ||
256, 512, 1024, // 16-bit | ||
65541, // 32-bit | ||
4294967299, // 64-bit | ||
] | ||
var expected = [ | ||
[0], [128], [252], // 8-bit | ||
[253, 0, 1], [253, 0, 2], [253, 0, 4], // 16-bit | ||
[254, 5, 0, 1, 0], // 32-bit | ||
[255, 3, 0, 0, 0, 1, 0, 0, 0] // 64-bit | ||
] | ||
for (var i = 0; i < data.length; ++i) { | ||
var actual = convert.numToVarInt(data[i]) | ||
assert.deepEqual(actual, expected[i]) | ||
} | ||
}) | ||
}) | ||
describe('varIntToNum', function() { | ||
it('works on valid input', function() { | ||
var data = [ | ||
[0], [128], [252], // 8-bit | ||
[253, 0, 1], [253, 0, 2], [253, 0, 4], // 16-bit | ||
[254, 5, 0, 1, 0], // 32-bit | ||
[255, 3, 0, 0, 0, 1, 0, 0, 0] // 64-bit | ||
] | ||
var expected = [ | ||
0, 128, 252, // 8-bit | ||
256, 512, 1024, // 16-bit | ||
65541, // 32-bit | ||
4294967299, // 64-bit | ||
] | ||
for (var i = 0; i < data.length; ++i) { | ||
var actual = convert.varIntToNum(data[i]) | ||
assert.equal(actual.number, expected[i]) | ||
assert.deepEqual(actual.bytes, data[i]) | ||
} | ||
}) | ||
it('uses only what is necessary', function() { | ||
var data = [ | ||
[0, 99], | ||
[253, 0, 1, 99], | ||
[254, 5, 0, 1, 0, 99], | ||
[255, 3, 0, 0, 0, 1, 0, 0, 0, 99] | ||
] | ||
var expected = [0, 256, 65541, 4294967299] | ||
for (var i = 0; i < data.length; ++i) { | ||
var actual = convert.varIntToNum(data[i]) | ||
assert.equal(actual.number, expected[i]) | ||
assert.deepEqual(actual.bytes, data[i].slice(0, -1)) | ||
} | ||
}) | ||
}) | ||
describe('reverseEndian', function() { | ||
it('works', function() { | ||
var bigEndian = "6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7" | ||
var littleEdian = "c7b97b432e7b3a8e31d1a4a0fa326c8fb002d19f2da5fc4feaf9c43a2762406a" | ||
assert.deepEqual(convert.reverseEndian(bigEndian), littleEdian) | ||
assert.deepEqual(convert.reverseEndian(littleEdian), bigEndian) | ||
}) | ||
}) | ||
}) |
var assert = require('assert') | ||
var convert = require('../').convert | ||
var ecdsa = require('../').ecdsa | ||
var ECPubKey = require('../').ECPubKey | ||
var Message = require('../').Message | ||
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() { | ||
fixtures.valid.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) | ||
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')) | ||
.onCall(1).returns(curve.n) | ||
.onCall(2).returns(new BigInteger('42')) | ||
var d = new BigInteger('1') | ||
var h1 = new Buffer(32) | ||
var k = ecdsa.deterministicGenerateK(curve, h1, d) | ||
assert.equal(k.toString(), '42') | ||
})) | ||
}) | ||
describe('recoverPubKey', function() { | ||
it('succesfully recovers a public key', function() { | ||
var addr = 'mgQK8S6CfSXKjPmnujArSmVxafeJfrZsa3' | ||
var signature = convert.base64ToBytes('H0PG6+PUo96UPTJ/DVj8aBU5it+Nuli4YdsLuTMvfJxoHH9Jb7jYTQXCCOX2jrTChD5S1ic3vCrUQHdmB5/sEQY=') | ||
var obj = ecdsa.parseSigCompact(signature) | ||
var pubKey = new ECPubKey(ecdsa.recoverPubKey(obj.r, obj.s, Message.magicHash('1111'), obj.i)) | ||
fixtures.valid.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.equal(pubKey.toHex(true), '02e8fcf4d749b35879bc1f3b14b49e67ab7301da3558c5a9b74a54f1e6339c334c') | ||
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.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('verifyRaw', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('verifies a valid signature for \"' + f.message + '\"', function() { | ||
var d = BigInteger.fromHex(f.d) | ||
var e = BigInteger.fromBuffer(crypto.sha256(f.message)) | ||
var signature = new ECSignature( | ||
new BigInteger(f.signature.r), | ||
new BigInteger(f.signature.s) | ||
) | ||
var Q = curve.G.multiply(d) | ||
assert(ecdsa.verifyRaw(curve, e, signature, Q)) | ||
}) | ||
}) | ||
fixtures.invalid.verifyRaw.forEach(function(f) { | ||
it('fails to verify with ' + f.description, function() { | ||
var d = BigInteger.fromHex(f.d) | ||
var e = BigInteger.fromHex(f.e) | ||
var signature = new ECSignature( | ||
new BigInteger(f.signature.r), | ||
new BigInteger(f.signature.s) | ||
) | ||
var Q = curve.G.multiply(d) | ||
assert.equal(ecdsa.verifyRaw(curve, e, signature, Q), false) | ||
}) | ||
}) | ||
}) | ||
}) |
var assert = require('assert') | ||
var ECKey = require('../src/eckey.js').ECKey | ||
var ECPubKey = require('../src/eckey.js').ECPubKey | ||
var convert = require('../src/convert.js') | ||
var bytesToHex = convert.bytesToHex | ||
var hexToBytes = convert.hexToBytes | ||
var Address = require('../src/address') | ||
var Network = require('../src/network') | ||
var testnet = Network.testnet.addressVersion | ||
var crypto = require('../src/crypto') | ||
var networks = require('../src/networks') | ||
var secureRandom = require('secure-random') | ||
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('parses hex', function() { | ||
var priv = '18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725' | ||
var pub = '0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b235' + | ||
'22cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6' | ||
var key = new ECKey(priv) | ||
it('defaults to compressed', function() { | ||
var privKey = new ECKey(BigInteger.ONE) | ||
assert.equal(key.getPub().toHex(), pub) | ||
assert.equal(key.compressed, false) | ||
assert.equal(privKey.pub.compressed, true) | ||
}) | ||
it('parses base64', function() { | ||
var priv = 'VYdB+iv47y5FaUVIPdQInkgATrABeuD1lACUoM4x7tU=' | ||
var pub = '042f43c16c08849fed20a35bb7b1947bbf0923c52d613ee13b5c665a1e10d24b2' + | ||
'8be909a70f5f87c1adb79fbcd1b3f17d20aa91c04fc355112dba2ce9b1cbf013b' | ||
var key = new ECKey(priv) | ||
it('supports the uncompressed flag', function() { | ||
var privKey = new ECKey(BigInteger.ONE, false) | ||
assert.equal(key.getPub().toHex(), pub) | ||
assert.equal(key.compressed, false) | ||
assert.equal(privKey.pub.compressed, false) | ||
}) | ||
it('parses WIF', function() { | ||
var priv = '5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh' | ||
var pub = '044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0' + | ||
'f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1' | ||
var addr = '1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a' | ||
var key = new ECKey(priv) | ||
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(key.compressed, false) | ||
assert.equal(key.getPub().toHex(), pub) | ||
assert.equal(key.getAddress().toString(), addr) | ||
assert.equal(privKey.pub.Q.toString(), f.Q) | ||
}) | ||
}) | ||
it('parses compressed WIF', function() { | ||
var priv = 'KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp' | ||
var pub = '034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa' | ||
var addr = '1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9' | ||
var key = new ECKey(priv) | ||
fixtures.invalid.constructor.forEach(function(f) { | ||
it('throws on ' + f.d, function() { | ||
var d = new BigInteger(f.d) | ||
assert.equal(key.compressed, true) | ||
assert.equal(key.getPub().toHex(), pub) | ||
assert.equal(key.getAddress().toString(), addr) | ||
assert.throws(function() { | ||
new ECKey(d) | ||
}, new RegExp(f.exception)) | ||
}) | ||
}) | ||
it('alternative constructor syntax', function() { | ||
var priv = 'ca48ec9783cf3ad0dfeff1fc254395a2e403cbbc666477b61b45e31d3b8ab458' | ||
var pub = '044b12d9d7c77db68388b6ff7c89046174c871546436806bcd80d07c28ea81199' + | ||
'283fbec990dad6fb98f93f712d50cb874dd717de6a184158d63886dda3090f566' | ||
var key = ECKey(priv, false) | ||
assert.equal(key.getPub().toHex(), pub) | ||
assert.equal(key.compressed, false) | ||
assert.equal(key.toHex(), priv) | ||
}) | ||
}) | ||
describe('toAddress', function() { | ||
var privkeys = [ | ||
'ca48ec9783cf3ad0dfeff1fc254395a2e403cbbc666477b61b45e31d3b8ab458', | ||
'1111111111111111111111111111111111111111111111111111111111111111', | ||
'18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725' | ||
] | ||
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) | ||
// compressed pubkeys | ||
var cpubkeys = [ | ||
'024b12d9d7c77db68388b6ff7c89046174c871546436806bcd80d07c28ea811992', | ||
'034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa', | ||
'0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352' | ||
] | ||
var pubkeys = cpubkeys.map(function(x) { | ||
return ECPubKey(x).toHex(false) | ||
assert.equal(privKey.d.toString(), f.d) | ||
assert.equal(privKey.pub.compressed, wif.compressed) | ||
}) | ||
}) | ||
}) | ||
it('mainnet', function() { | ||
var addresses = [ | ||
'19SgmoUj4xowEjwtXvNAtYTAgbvR9iBCui', | ||
'1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a', | ||
'16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM' | ||
] | ||
var compressedAddresses = [ | ||
'1AA4sjKW2aUmbtN3MtegdvhYtDBbDEke1q', | ||
'1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9', | ||
'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs', | ||
] | ||
for (var i = 0; i < addresses.length; ++i) { | ||
var priv = new ECKey(privkeys[i], false) | ||
var pub = new ECPubKey(pubkeys[i], false) | ||
var cpub = new ECPubKey(cpubkeys[i], true) | ||
var addr = addresses[i] | ||
var caddr = compressedAddresses[i] | ||
assert.equal(priv.getAddress().toString(), addr) | ||
assert.equal(pub.getAddress().toString(), addr) | ||
assert.equal(cpub.getAddress().toString(), caddr) | ||
} | ||
fixtures.invalid.WIF.forEach(function(f) { | ||
it('throws on ' + f.string, function() { | ||
assert.throws(function() { | ||
ECKey.fromWIF(f.string) | ||
}, new RegExp(f.exception)) | ||
}) | ||
}) | ||
}) | ||
it('testnet', function() { | ||
var addresses = [ | ||
'19SgmoUj4xowEjwtXvNAtYTAgbvR9iBCui', | ||
'1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a', | ||
'16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM' | ||
] | ||
var compressedAddresses = [ | ||
'1AA4sjKW2aUmbtN3MtegdvhYtDBbDEke1q', | ||
'1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9', | ||
'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs', | ||
] | ||
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) | ||
for (var i = 0; i < addresses.length; ++i) { | ||
var priv = new ECKey(privkeys[i], false) | ||
var pub = new ECPubKey(pubkeys[i], false) | ||
var cpub = new ECPubKey(cpubkeys[i], true) | ||
var addr = addresses[i] | ||
var caddr = compressedAddresses[i] | ||
assert.equal(priv.getAddress().toString(), addr) | ||
assert.equal(pub.getAddress().toString(), addr) | ||
assert.equal(cpub.getAddress().toString(), caddr) | ||
} | ||
assert.equal(result, wif.string) | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe('signing', function() { | ||
var hpriv = 'ca48ec9783cf3ad0dfeff1fc254395a2e403cbbc666477b61b45e31d3b8ab458' | ||
var hcpub = '024b12d9d7c77db68388b6ff7c89046174c871546436806bcd80d07c28ea811992' | ||
var message = 'Vires in numeris' | ||
describe('makeRandom', function() { | ||
var exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv' | ||
var exPrivKey = ECKey.fromWIF(exWIF) | ||
var exBuffer = exPrivKey.d.toBuffer(32) | ||
it('should verify against the private key', function() { | ||
var priv = new ECKey(hpriv) | ||
var signature = priv.sign(message) | ||
describe('using default RNG', function() { | ||
beforeEach(function() { | ||
sinon.stub(secureRandom, 'randomBuffer').returns(exBuffer) | ||
}) | ||
assert(priv.verify(message, signature)) | ||
}) | ||
afterEach(function() { | ||
secureRandom.randomBuffer.restore() | ||
}) | ||
it('should verify against the public key', function() { | ||
var priv = new ECKey(hpriv) | ||
var pub = new ECPubKey(hcpub, true) | ||
var signature = priv.sign(message) | ||
it('generates a ECKey', function() { | ||
var privKey = ECKey.makeRandom() | ||
assert(pub.verify(message, signature)) | ||
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('should not verify against the wrong private key', function() { | ||
var priv1 = new ECKey(hpriv) | ||
var priv2 = new ECKey('1111111111111111111111111111111111111111111111111111111111111111') | ||
it('allows a custom RNG to be used', function() { | ||
function rng(size) { | ||
return exBuffer.slice(0, size) | ||
} | ||
var signature = priv1.sign(message) | ||
assert(!priv2.verify(message, signature)) | ||
var privKey = ECKey.makeRandom(undefined, rng) | ||
assert.equal(privKey.toWIF(), exWIF) | ||
}) | ||
}) | ||
describe('output of ECPubKey', function() { | ||
var hcpub = '024b12d9d7c77db68388b6ff7c89046174c871546436806bcd80d07c28ea811992' | ||
var hpub = '044b12d9d7c77db68388b6ff7c89046174c871546436806bcd80d07c28ea81199283fbec990dad6fb98f93f712d50cb874dd717de6a184158d63886dda3090f566' | ||
describe('signing', function() { | ||
var hash = crypto.sha256('Vires in numeris') | ||
var priv = ECKey.makeRandom() | ||
var signature = priv.sign(hash) | ||
it('using toHex should support compression', function() { | ||
var pub = new ECPubKey(hpub) | ||
assert.equal(pub.toHex(true), hcpub) | ||
assert.equal(pub.toHex(false), hpub) | ||
it('should verify against the public key', function() { | ||
assert(priv.pub.verify(hash, signature)) | ||
}) | ||
it('using toBytes should support compression', function() { | ||
var pub = new ECPubKey(hpub) | ||
it('should not verify against the wrong public key', function() { | ||
var priv2 = ECKey.makeRandom() | ||
assert.equal(bytesToHex(pub.toBytes(true)), hcpub) | ||
assert.equal(bytesToHex(pub.toBytes(false)), hpub) | ||
assert(!priv2.pub.verify(hash, signature)) | ||
}) | ||
}) | ||
}) |
var assert = require('assert') | ||
var convert = require('../').convert | ||
var ECKey = require('../src/eckey').ECKey | ||
var Message = require('../').Message | ||
var testnet = require('../').network.testnet.addressVersion | ||
var networks = require('../src/networks') | ||
var Address = require('../src/address') | ||
var BigInteger = require('bigi') | ||
var ECKey = require('../src/eckey') | ||
var Message = require('../src/message') | ||
var fixtures = require('./fixtures/message.json') | ||
describe('Message', function() { | ||
var msg | ||
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) | ||
beforeEach(function() { | ||
msg = 'vires is numeris' | ||
assert.equal(actual.toString('hex'), f.magicHash) | ||
}) | ||
}) | ||
}) | ||
describe('verify', function() { | ||
var addr, sig, caddr, csig | ||
it('accepts an Address object', function() { | ||
var f = fixtures.valid.verify[0] | ||
var network = networks[f.network] | ||
beforeEach(function() { | ||
addr = '16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM' // uncompressed | ||
caddr = '1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs' // compressed | ||
sig = convert.hexToBytes('1bc25ac0fb503abc9bad23f558742740fafaec1f52deaaf106b9759a5ce84c93921c4a669c5ec3dfeb7e2d7d177a2f49db407900874f6de2f701a4c16783776d8d') | ||
csig = convert.hexToBytes('1fc25ac0fb503abc9bad23f558742740fafaec1f52deaaf106b9759a5ce84c93921c4a669c5ec3dfeb7e2d7d177a2f49db407900874f6de2f701a4c16783776d8d') | ||
var address = Address.fromBase58Check(f.address) | ||
var signature = new Buffer(f.signature, 'base64') | ||
assert.ok(Message.verify(address, signature, f.message, network)) | ||
}) | ||
it('can verify a signed message', function() { | ||
assert.ok(Message.verify(addr, sig, msg)) | ||
assert.ok(Message.verify(caddr, csig, msg)) | ||
}) | ||
fixtures.valid.verify.forEach(function(f) { | ||
it('verifies a valid signature for \"' + f.message + '\" (' + f.network + ')', function() { | ||
var network = networks[f.network] | ||
it('will fail for the wrong message', function() { | ||
assert.ok(!Message.verify(addr, sig, 'foobar')) | ||
assert.ok(!Message.verify(caddr, csig, 'foobar')) | ||
}) | ||
var signature = new Buffer(f.signature, 'base64') | ||
assert.ok(Message.verify(f.address, signature, f.message, network)) | ||
it('will fail for the wrong public key', function() { | ||
assert.ok(!Message.verify('1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a', sig, msg)) | ||
assert.ok(!Message.verify('1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9', csig, msg)) | ||
}) | ||
if (f.compressed) { | ||
var compressedSignature = new Buffer(f.compressed.signature, 'base64') | ||
it('supports alternate network addresses', function() { | ||
var taddr = 'mxnQZKxSKjzaMgrdXzk35rif3u62TLDrg9' | ||
var tsig = convert.base64ToBytes('IGucnrTku3KLCCHUMwq9anawfrlN8RK1HWMN+10LhsHJeysBdWfj5ohJcS/+oqrlVFNvEgbgEeAQUL6r3sZwnj8=') | ||
assert.ok(Message.verify(taddr, tsig, msg)) | ||
assert.ok(!Message.verify(taddr, tsig, 'foobar')) | ||
assert.ok(Message.verify(f.compressed.address, compressedSignature, f.message, network)) | ||
} | ||
}) | ||
}) | ||
it('does not cross verify (compressed/uncompressed)', function() { | ||
assert.ok(!Message.verify(addr, csig, msg)) | ||
assert.ok(!Message.verify(caddr, sig, msg)) | ||
fixtures.invalid.verify.forEach(function(f) { | ||
it(f.description, function() { | ||
var signature = new Buffer(f.signature, 'base64') | ||
assert.ok(!Message.verify(f.address, signature, f.message)) | ||
}) | ||
}) | ||
@@ -55,26 +57,16 @@ }) | ||
describe('signing', function() { | ||
describe('using the uncompressed public key', function(){ | ||
it('gives same signature as a compressed public key', function() { | ||
var key = new ECKey(null) // uncompressed | ||
var sig = Message.sign(key, msg) | ||
fixtures.valid.signing.forEach(function(f) { | ||
it(f.description, function() { | ||
var network = networks[f.network] | ||
var compressedKey = new ECKey(key, true) // compressed clone | ||
var csig = Message.sign(compressedKey, msg) // FIXME: bad compression support | ||
var privKey = new ECKey(new BigInteger(f.d), false) | ||
var signature = Message.sign(privKey, f.message, network) | ||
assert.equal(signature.toString('base64'), f.signature) | ||
var addr = key.getPub().getAddress() | ||
var caddr = compressedKey.getPub().getAddress() | ||
assert.ok(Message.verify(addr, sig, msg)) | ||
assert.ok(Message.verify(caddr, csig, msg)) | ||
assert.notDeepEqual(sig.slice(0, 2), csig.slice(0, 2)) // unequal compression flags | ||
assert.deepEqual(sig.slice(2), csig.slice(2)) // equal signatures | ||
}) | ||
}) | ||
if (f.compressed) { | ||
var compressedPrivKey = new ECKey(new BigInteger(f.d)) | ||
var compressedSignature = Message.sign(compressedPrivKey, f.message) | ||
describe('testnet address', function(){ | ||
it('works', function(){ | ||
var key = new ECKey(null) | ||
var sig = Message.sign(key, msg) | ||
var addr = key.getAddress(testnet) | ||
assert(Message.verify(addr, sig, msg)) | ||
assert.equal(compressedSignature.toString('base64'), f.compressed.signature) | ||
} | ||
}) | ||
@@ -81,0 +73,0 @@ }) |
@@ -1,113 +0,82 @@ | ||
var Script = require('../src/script.js') | ||
var assert = require('assert') | ||
var Address = require('../src/address.js') | ||
var Network = require('../src/network.js') | ||
var Util = require('../src/util.js') | ||
var sha256ripe160 = Util.sha256ripe160 | ||
var Convert = require('../src/convert.js') | ||
var bytesToHex = Convert.bytesToHex | ||
var hexToBytes = Convert.hexToBytes | ||
var opcodes = require('../src/opcodes') | ||
describe('Script', function() { | ||
var p2shScriptPubKey, pubkeyScriptPubkey, addressScriptSig | ||
var Script = require('../src/script') | ||
beforeEach(function(){ | ||
p2shScriptPubKey = "a914e8c300c87986efa84c37c0519929019ef86eb5b487" | ||
pubkeyScriptPubKey = "76a9145a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a888ac" | ||
addressScriptSig = "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f30141040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8" | ||
}) | ||
var fixtures = require('./fixtures/script.json') | ||
describe('Script', function() { | ||
describe('constructor', function() { | ||
it('works for a byte array', function() { | ||
assert.ok(new Script([])) | ||
}) | ||
it('accepts valid parameters', function() { | ||
var buffer = new Buffer([1]) | ||
var chunks = [1] | ||
var script = new Script(buffer, chunks) | ||
it('works when nothing is passed in', function() { | ||
assert.ok(new Script()) | ||
assert.equal(script.buffer, buffer) | ||
assert.equal(script.chunks, chunks) | ||
}) | ||
it('throws an error when input is not an array', function() { | ||
assert.throws(function(){ new Script({}) }) | ||
assert.throws(function(){ new Script({}) }, /Expected Buffer, got/) | ||
}) | ||
}) | ||
describe('getOutType', function() { | ||
it('works for p2sh', function() { | ||
var script = Script.fromHex(p2shScriptPubKey) | ||
assert.equal(script.getOutType(), 'P2SH') | ||
describe('fromASM/toASM', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('decodes/encodes ' + f.description, function() { | ||
assert.equal(Script.fromASM(f.asm).toASM(), f.asm) | ||
}) | ||
}) | ||
it('works for pubkey', function() { | ||
var script = Script.fromHex(pubkeyScriptPubKey) | ||
assert.equal(script.getOutType(), 'Pubkey') | ||
}) | ||
}) | ||
describe('getInType', function() { | ||
it('works for address', function() { | ||
var script = Script.fromHex(addressScriptSig) | ||
assert.equal(script.getInType(), 'Address') | ||
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('getToAddress', function() { | ||
it('works for p2sh type output', function() { | ||
var script = Script.fromHex(p2shScriptPubKey) | ||
assert.equal(script.getToAddress().toString(), '3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8') | ||
}) | ||
describe('getHash', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('produces a HASH160 of \"' + f.asm + '\"', function() { | ||
var script = Script.fromHex(f.hex) | ||
it('works for pubkey type output', function() { | ||
var script = Script.fromHex(pubkeyScriptPubKey) | ||
assert.equal(script.getToAddress().toString(), '19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu') | ||
assert.equal(script.getHash().toString('hex'), f.hash) | ||
}) | ||
}) | ||
}) | ||
describe('getFromAddress', function() { | ||
it('works for address type input', function() { | ||
var script = Script.fromHex(addressScriptSig) | ||
assert.equal(script.getFromAddress().toString(), '1BBjuhF2jHxu7tPinyQGCuaNhEs6f5u59u') | ||
describe('fromChunks', function() { | ||
it('should match expected behaviour', function() { | ||
var hash = new Buffer(32) | ||
hash.fill(0) | ||
var script = Script.fromChunks([ | ||
opcodes.OP_HASH160, | ||
hash, | ||
opcodes.OP_EQUAL | ||
]) | ||
assert.equal(script.toHex(), 'a920000000000000000000000000000000000000000000000000000000000000000087') | ||
}) | ||
}) | ||
describe('2-of-3 Multi-Signature', function() { | ||
var compressedPubKeys = [] | ||
var numSigs, script, multisig, network | ||
describe('without', function() { | ||
var hex = 'a914e8c300c87986efa94c37c0519929019ef86eb5b487' | ||
var script = Script.fromHex(hex) | ||
beforeEach(function() { | ||
compressedPubKeys = ['02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f', | ||
'02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f', | ||
'036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19'] | ||
numSigs = 2 | ||
network = Network.mainnet.p2shVersion | ||
}) | ||
it('should return a script without the given value', function() { | ||
var subScript = script.without(opcodes.OP_HASH160) | ||
it('should create valid multi-sig address', function() { | ||
script = Script.createMultiSigOutputScript(numSigs, compressedPubKeys.map(hexToBytes)) | ||
multisig = sha256ripe160(script.buffer) | ||
var multiSigAddress = Address(multisig, network).toString() | ||
assert.ok(Address.validate(multiSigAddress)) | ||
assert.equal(Address.getVersion(multiSigAddress), Network.mainnet.p2shVersion) | ||
assert.equal(multiSigAddress,'32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v') | ||
assert.equal(subScript.toHex(), '14e8c300c87986efa94c37c0519929019ef86eb5b487') | ||
}) | ||
it('should create valid redeemScript', function() { | ||
var redeemScript = script.buffer | ||
var deserialized = new Script(redeemScript) | ||
var numOfSignatures = deserialized.chunks[deserialized.chunks.length - 2] - 80 | ||
var signaturesRequired = deserialized.chunks[0] - 80 | ||
var sigs = [ | ||
bytesToHex(deserialized.chunks[1]), | ||
bytesToHex(deserialized.chunks[2]), | ||
bytesToHex(deserialized.chunks[3]) | ||
] | ||
it('shouldnt mutate the original script', function() { | ||
var subScript = script.without(opcodes.OP_EQUAL) | ||
assert.equal(numOfSignatures, 3) | ||
assert.equal(signaturesRequired, 2) | ||
assert.equal(sigs[0], '02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f') | ||
assert.equal(sigs[1], '02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f') | ||
assert.equal(sigs[2], '036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19') | ||
assert.equal(Address(sha256ripe160(redeemScript), network).toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v') | ||
assert.notEqual(subScript.toHex(), hex) | ||
assert.equal(script.toHex(), hex) | ||
}) | ||
}) | ||
}) |
@@ -1,233 +0,256 @@ | ||
var T = require('../src/transaction') | ||
var Transaction = T.Transaction | ||
var TransactionOut = T.TransactionOut | ||
var convert = require('../src/convert') | ||
var ECKey = require('../src/eckey').ECKey | ||
var Script = require('../src/script') | ||
var assert = require('assert') | ||
var networks = require('../src/networks') | ||
var scripts = require('../src/scripts') | ||
var fixtureTxes = require('./fixtures/mainnet_tx') | ||
var fixtureTx1Hex = fixtureTxes.prevTx | ||
var fixtureTx2Hex = fixtureTxes.tx | ||
var fixtureTxBigHex = fixtureTxes.bigTx | ||
var Address = require('../src/address') | ||
var ECKey = require('../src/eckey') | ||
var Transaction = require('../src/transaction') | ||
var fixtures = require('./fixtures/transaction') | ||
// FIXME: what is a better way to do this, seems a bit odd | ||
fixtures.valid.forEach(function(f) { | ||
var Script = require('../src/script') | ||
f.raw.ins.forEach(function(fin) { | ||
fin.hash = new Buffer(fin.hash, 'hex') | ||
fin.script = Script.fromHex(fin.script) | ||
}) | ||
f.raw.outs.forEach(function(fout) { | ||
fout.script = Script.fromHex(fout.script) | ||
}) | ||
}) | ||
describe('Transaction', function() { | ||
describe('deserialize', function() { | ||
var tx, serializedTx | ||
beforeEach(function() { | ||
serializedTx = [ | ||
'0100000001344630cbff61fbc362f7e1ff2f11a344c29326e4ee96e78', | ||
'7dc0d4e5cc02fd069000000004a493046022100ef89701f460e8660c8', | ||
'0808a162bbf2d676f40a331a243592c36d6bd1f81d6bdf022100d29c0', | ||
'72f1b18e59caba6e1f0b8cadeb373fd33a25feded746832ec179880c2', | ||
'3901ffffffff0100f2052a010000001976a914dd40dedd8f7e3746662', | ||
'4c4dacc6362d8e7be23dd88ac00000000' | ||
].join('') | ||
tx = Transaction.deserialize(serializedTx) | ||
describe('fromBuffer/fromHex', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('imports ' + f.txid + ' correctly', function() { | ||
var actual = Transaction.fromHex(f.hex) | ||
assert.deepEqual(actual, f.raw) | ||
}) | ||
}) | ||
it('returns the original after serialized again', function() { | ||
var actual = tx.serialize() | ||
var expected = convert.hexToBytes(serializedTx) | ||
assert.deepEqual(actual, expected) | ||
fixtures.invalid.fromBuffer.forEach(function(f) { | ||
it('throws on ' + f.exception, function() { | ||
assert.throws(function() { | ||
Transaction.fromHex(f.hex) | ||
}, new RegExp(f.exception)) | ||
}) | ||
}) | ||
}) | ||
it('decodes version correctly', function(){ | ||
assert.equal(tx.version, 1) | ||
describe('toBuffer/toHex', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('exports ' + f.txid + ' correctly', function() { | ||
var actual = Transaction.prototype.toBuffer.call(f.raw) | ||
assert.equal(actual.toString('hex'), f.hex) | ||
}) | ||
}) | ||
}) | ||
it('decodes locktime correctly', function(){ | ||
assert.equal(tx.locktime, 0) | ||
describe('addInput', function() { | ||
// FIXME: not as pretty as could be | ||
// Probably a bit representative of the API | ||
var prevTxHash, prevTxId, prevTx | ||
beforeEach(function() { | ||
var f = fixtures.valid[0] | ||
prevTx = Transaction.fromHex(f.hex) | ||
prevTxHash = prevTx.getHash() | ||
prevTxId = prevTx.getId() | ||
}) | ||
it('decodes inputs correctly', function(){ | ||
assert.equal(tx.ins.length, 1) | ||
it('accepts a transaction id', function() { | ||
var tx = new Transaction() | ||
tx.addInput(prevTxId, 0) | ||
var input = tx.ins[0] | ||
assert.deepEqual(input.sequence, [255, 255, 255, 255]) | ||
assert.deepEqual(tx.ins[0].hash, prevTxHash) | ||
}) | ||
assert.equal(input.outpoint.index, 0) | ||
assert.equal(input.outpoint.hash, "69d02fc05c4e0ddc87e796eee42693c244a3112fffe1f762c3fb61ffcb304634") | ||
it('accepts a transaction hash', function() { | ||
var tx = new Transaction() | ||
tx.addInput(prevTxHash, 0) | ||
assert.equal(convert.bytesToHex(input.script.buffer), | ||
"493046022100ef89701f460e8660c80808a162bbf2d676f40a331a243592c36d6bd1f81d6bdf022100d29c072f1b18e59caba6e1f0b8cadeb373fd33a25feded746832ec179880c23901") | ||
assert.deepEqual(tx.ins[0].hash, prevTxHash) | ||
}) | ||
it('decodes outputs correctly', function(){ | ||
assert.equal(tx.outs.length, 1) | ||
it('accepts a Transaction object', function() { | ||
var tx = new Transaction() | ||
tx.addInput(prevTx, 0) | ||
var output = tx.outs[0] | ||
assert.equal(output.value, 5000000000) | ||
assert.equal(convert.bytesToHex(output.script.toScriptHash()), "dd40dedd8f7e37466624c4dacc6362d8e7be23dd") | ||
// assert.equal(output.address.toString(), "n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9") | ||
// TODO: address is wrong because it's a testnet transaction. Transaction needs to support testnet | ||
assert.deepEqual(tx.ins[0].hash, prevTxHash) | ||
}) | ||
it('assigns hash to deserialized object', function(){ | ||
var hashHex = "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c" | ||
assert.deepEqual(tx.hash, convert.hexToBytes(hashHex)) | ||
it('returns an index', function() { | ||
var tx = new Transaction() | ||
assert.equal(tx.addInput(prevTxHash, 0), 0) | ||
assert.equal(tx.addInput(prevTxHash, 0), 1) | ||
}) | ||
it('decodes large inputs correctly', function() { | ||
// transaction has only 1 input | ||
it('defaults to DEFAULT_SEQUENCE', function() { | ||
var tx = new Transaction() | ||
tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57", 0) | ||
tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3", 100) | ||
tx.addInput(prevTxHash, 0) | ||
// but we're going to replace the tx.ins.length VarInt with a 32-bit equivalent | ||
// however the same resultant number of inputs (1) | ||
var bytes = tx.serialize() | ||
var mutated = bytes.slice(0, 4).concat([254, 1, 0, 0, 0], bytes.slice(5)) | ||
// the deserialized-serialized transaction should return to its original state (== tx) | ||
var bytes2 = Transaction.deserialize(mutated).serialize() | ||
assert.deepEqual(bytes, bytes2) | ||
assert.equal(tx.ins[0].sequence, Transaction.DEFAULT_SEQUENCE) | ||
}) | ||
}) | ||
describe('creating a transaction', function() { | ||
var tx, prevTx | ||
beforeEach(function() { | ||
prevTx = Transaction.deserialize(fixtureTx1Hex) | ||
tx = new Transaction() | ||
}) | ||
fixtures.valid.forEach(function(f) { | ||
it('should add the inputs for ' + f.txid + ' correctly', function() { | ||
var tx = new Transaction() | ||
describe('addInput', function(){ | ||
it('allows a Transaction object to be passed in', function(){ | ||
tx.addInput(prevTx, 0) | ||
verifyTransactionIn() | ||
}) | ||
f.raw.ins.forEach(function(txIn, i) { | ||
var j = tx.addInput(txIn.hash, txIn.index, txIn.sequence) | ||
it('allows a Transaction hash to be passed in', function(){ | ||
tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57", 0) | ||
verifyTransactionIn() | ||
assert.equal(i, j) | ||
assert.deepEqual(tx.ins[i].hash, txIn.hash) | ||
assert.equal(tx.ins[i].index, txIn.index) | ||
var sequence = txIn.sequence | ||
if (sequence == undefined) sequence = Transaction.DEFAULT_SEQUENCE | ||
assert.equal(tx.ins[i].sequence, sequence) | ||
}) | ||
}) | ||
}) | ||
it('allows a TransactionIn object to be passed in', function(){ | ||
var txCopy = tx.clone() | ||
txCopy.addInput(prevTx, 0) | ||
var transactionIn = txCopy.ins[0] | ||
fixtures.invalid.addInput.forEach(function(f) { | ||
it('throws on ' + f.exception, function() { | ||
var tx = new Transaction() | ||
var hash = new Buffer(f.hash, 'hex') | ||
tx.addInput(transactionIn) | ||
verifyTransactionIn() | ||
assert.throws(function() { | ||
tx.addInput(hash, f.index) | ||
}, new RegExp(f.exception)) | ||
}) | ||
}) | ||
}) | ||
it('allows a string in the form of txhash:index to be passed in', function(){ | ||
tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57:0") | ||
verifyTransactionIn() | ||
}) | ||
describe('addOutput', function() { | ||
// FIXME: not as pretty as could be | ||
// Probably a bit representative of the API | ||
var destAddressB58, destAddress, destScript | ||
beforeEach(function() { | ||
destAddressB58 = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' | ||
destAddress = Address.fromBase58Check(destAddressB58) | ||
destScript = destAddress.toOutputScript() | ||
}) | ||
function verifyTransactionIn(){ | ||
assert.equal(tx.ins.length, 1) | ||
it('accepts an address string', function() { | ||
var tx = new Transaction() | ||
tx.addOutput(destAddressB58, 40000) | ||
var input = tx.ins[0] | ||
assert.deepEqual(input.sequence, [255, 255, 255, 255]) | ||
assert.deepEqual(tx.outs[0].script, destScript) | ||
assert.equal(tx.outs[0].value, 40000) | ||
}) | ||
assert.equal(input.outpoint.index, 0) | ||
assert.equal(input.outpoint.hash, "0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57") | ||
it('accepts an Address', function() { | ||
var tx = new Transaction() | ||
tx.addOutput(destAddress, 40000) | ||
assert.deepEqual(input.script.buffer, []) | ||
} | ||
assert.deepEqual(tx.outs[0].script, destScript) | ||
assert.equal(tx.outs[0].value, 40000) | ||
}) | ||
describe('addOutput', function(){ | ||
it('allows an address and a value to be passed in', function(){ | ||
tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3", 40000) | ||
verifyTransactionOut() | ||
}) | ||
it('accepts a scriptPubKey', function() { | ||
var tx = new Transaction() | ||
tx.addOutput(destScript, 40000) | ||
it('allows a string in the form of address:index to be passed in', function(){ | ||
tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3:40000") | ||
verifyTransactionOut() | ||
}) | ||
assert.deepEqual(tx.outs[0].script, destScript) | ||
assert.equal(tx.outs[0].value, 40000) | ||
}) | ||
it('allows a TransactionOut object to be passed in', function(){ | ||
var txCopy = tx.clone() | ||
txCopy.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3:40000") | ||
var transactionOut = txCopy.outs[0] | ||
it('returns an index', function() { | ||
var tx = new Transaction() | ||
assert.equal(tx.addOutput(destScript, 40000), 0) | ||
assert.equal(tx.addOutput(destScript, 40000), 1) | ||
}) | ||
tx.addOutput(transactionOut) | ||
verifyTransactionOut() | ||
}) | ||
fixtures.valid.forEach(function(f) { | ||
it('should add the outputs for ' + f.txid + ' correctly', function() { | ||
var tx = new Transaction() | ||
function verifyTransactionOut(){ | ||
assert.equal(tx.outs.length, 1) | ||
f.raw.outs.forEach(function(txOut, i) { | ||
var j = tx.addOutput(txOut.script, txOut.value) | ||
var output = tx.outs[0] | ||
assert.equal(output.value, 40000) | ||
assert.deepEqual(convert.bytesToHex(output.script.buffer), "76a9143443bc45c560866cfeabf1d52f50a6ed358c69f288ac") | ||
} | ||
assert.equal(i, j) | ||
}) | ||
assert.deepEqual(tx.outs, f.raw.outs) | ||
}) | ||
}) | ||
}) | ||
describe('sign', function(){ | ||
it('works', function(){ | ||
tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57:0") | ||
tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3:40000") | ||
tx.addOutput("1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd:50000") | ||
describe('clone', function() { | ||
fixtures.valid.forEach(function(f) { | ||
var expected = Transaction.fromHex(f.hex) | ||
var actual = expected.clone() | ||
var key = new ECKey('L44f7zxJ5Zw4EK9HZtyAnzCYz2vcZ5wiJf9AuwhJakiV4xVkxBeb') | ||
tx.sign(0, key) | ||
it('should have value equality', function() { | ||
assert.deepEqual(actual, expected) | ||
}) | ||
var pub = key.getPub().toBytes() | ||
var script = prevTx.outs[0].script.buffer | ||
var sig = tx.ins[0].script.chunks[0] | ||
assert.equal(tx.validateSig(0, script, sig, pub), true) | ||
it('should not have reference equality', function() { | ||
assert.notEqual(actual, expected) | ||
}) | ||
}) | ||
}) | ||
describe('validateSig', function(){ | ||
var validTx | ||
describe('getId', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('should return the txid for ' + f.txid, function() { | ||
var tx = Transaction.fromHex(f.hex) | ||
var actual = tx.getId() | ||
beforeEach(function() { | ||
validTx = Transaction.deserialize(fixtureTx2Hex) | ||
assert.equal(actual, f.txid) | ||
}) | ||
}) | ||
}) | ||
it('returns true for valid signature', function(){ | ||
var key = new ECKey('L44f7zxJ5Zw4EK9HZtyAnzCYz2vcZ5wiJf9AuwhJakiV4xVkxBeb') | ||
var pub = key.getPub().toBytes() | ||
var script = prevTx.outs[0].script.buffer | ||
var sig = validTx.ins[0].script.chunks[0] | ||
describe('getHash', function() { | ||
fixtures.valid.forEach(function(f) { | ||
it('should return the hash for ' + f.txid, function() { | ||
var tx = Transaction.fromHex(f.hex) | ||
var actual = tx.getHash().toString('hex') | ||
assert.equal(validTx.validateSig(0, script, sig, pub), true) | ||
assert.equal(actual, f.hash) | ||
}) | ||
}) | ||
}) | ||
describe('estimateFee', function(){ | ||
it('works for fixture tx 1', function(){ | ||
var tx = Transaction.deserialize(fixtureTx1Hex) | ||
assert.equal(tx.estimateFee(), 20000) | ||
}) | ||
// TODO: | ||
// hashForSignature: [Function], | ||
it('works for fixture big tx', function(){ | ||
var tx = Transaction.deserialize(fixtureTxBigHex) | ||
assert.equal(tx.estimateFee(), 60000) | ||
}) | ||
// FIXME: could be better | ||
describe('signInput/validateInput', function() { | ||
it('works for multi-sig redeem script', function() { | ||
var tx = new Transaction() | ||
tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0) | ||
tx.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1) | ||
it('allow feePerKb to be passed in as an argument', function(){ | ||
var tx = Transaction.deserialize(fixtureTx2Hex) | ||
assert.equal(tx.estimateFee(10000), 10000) | ||
var privKeys = [ | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf', | ||
'5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAvUcVfH' | ||
].map(function(wif) { | ||
return ECKey.fromWIF(wif) | ||
}) | ||
var pubKeys = privKeys.map(function(eck) { return eck.pub }) | ||
var redeemScript = scripts.multisigOutput(2, pubKeys) | ||
it('allow feePerKb to be set to 0', function(){ | ||
var tx = Transaction.deserialize(fixtureTx2Hex) | ||
assert.equal(tx.estimateFee(0), 0) | ||
var signatures = privKeys.map(function(privKey) { | ||
return tx.signInput(0, redeemScript, privKey) | ||
}) | ||
}) | ||
}) | ||
describe('TransactionOut', function() { | ||
describe('scriptPubKey', function() { | ||
it('returns hex string', function() { | ||
var txOut = new TransactionOut({ | ||
value: 50000, | ||
script: Script.createOutputScript("1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv") | ||
}) | ||
var redeemScriptSig = scripts.multisigInput(signatures) | ||
var scriptSig = scripts.scriptHashInput(redeemScriptSig, redeemScript) | ||
tx.setInputScript(0, scriptSig) | ||
assert.equal(txOut.scriptPubKey(), "76a91468edf28474ee22f68dfe7e56e76c017c1701b84f88ac") | ||
signatures.forEach(function(sig, i){ | ||
assert(tx.validateInput(0, redeemScript, privKeys[i].pub, sig)) | ||
}) | ||
var expected = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d600000000fd1b0100483045022100e5be20d440b2bbbc886161f9095fa6d0bca749a4e41d30064f30eb97adc7a1f5022061af132890d8e4e90fedff5e9365aeeb77021afd8ef1d5c114d575512e9a130a0147304402205054e38e9d7b5c10481b6b4991fde5704cd94d49e344406e3c2ce4d18a43bf8e022051d7ba8479865b53a48bee0cce86e89a25633af5b2918aa276859489e232f51c014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0101000000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000' | ||
assert.equal(tx.toHex(), expected) | ||
}) | ||
}) | ||
}) | ||
@@ -1,13 +0,12 @@ | ||
var Wallet = require('../src/wallet.js') | ||
var HDNode = require('../src/hdwallet.js') | ||
var T = require('../src/transaction.js') | ||
var Transaction = T.Transaction | ||
var TransactionOut = T.TransactionOut | ||
var Script = require('../src/script.js') | ||
var convert = require('../src/convert.js') | ||
var assert = require('assert') | ||
var crypto = require('../src/crypto') | ||
var networks = require('../src/networks') | ||
var sinon = require('sinon') | ||
var SHA256 = require('crypto-js/sha256') | ||
var Crypto = require('crypto-js') | ||
var scripts = require('../src/scripts') | ||
var Address = require('../src/address') | ||
var HDNode = require('../src/hdnode') | ||
var Transaction = require('../src/transaction') | ||
var Wallet = require('../src/wallet') | ||
var fixtureTxes = require('./fixtures/mainnet_tx') | ||
@@ -17,6 +16,18 @@ var fixtureTx1Hex = fixtureTxes.prevTx | ||
function fakeTxHash(i) { | ||
var hash = new Buffer(32) | ||
hash.fill(i) | ||
return hash | ||
} | ||
function fakeTxId(i) { | ||
var hash = fakeTxHash(i) | ||
Array.prototype.reverse.call(hash) | ||
return hash.toString('hex') | ||
} | ||
describe('Wallet', function() { | ||
var seed, wallet | ||
beforeEach(function(){ | ||
seed = convert.wordArrayToBytes(SHA256("don't use a string seed like this in real life")) | ||
seed = crypto.sha256("don't use a string seed like this in real life") | ||
wallet = new Wallet(seed) | ||
@@ -26,10 +37,6 @@ }) | ||
describe('constructor', function() { | ||
it('should be ok to call without new', function() { | ||
assert.ok(Wallet(seed) instanceof Wallet) | ||
it('defaults to Bitcoin network', function() { | ||
assert.equal(wallet.getMasterKey().network, networks.bitcoin) | ||
}) | ||
it('defaults to Bitcoin mainnet', function() { | ||
assert.equal(wallet.getMasterKey().network, 'mainnet') | ||
}) | ||
it("generates m/0' as the main account", function() { | ||
@@ -62,7 +69,7 @@ var mainAccount = wallet.getAccountZero() | ||
beforeEach(function() { | ||
wallet = new Wallet(seed, {network: 'testnet'}) | ||
wallet = new Wallet(seed, networks.testnet) | ||
}) | ||
it('uses the network if specified', function() { | ||
assert.equal(wallet.getMasterKey().network, 'testnet') | ||
assert.equal(wallet.getMasterKey().network, networks.testnet) | ||
}) | ||
@@ -102,3 +109,3 @@ }) | ||
it('generate receiving addresses', function(){ | ||
var wallet = new Wallet(seed, {network: 'testnet'}) | ||
var wallet = new Wallet(seed, networks.testnet) | ||
var expectedAddresses = [ | ||
@@ -117,3 +124,3 @@ "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa", | ||
it('generates change addresses', function(){ | ||
var wallet = new Wallet(seed, {network: 'testnet'}) | ||
var wallet = new Wallet(seed, networks.testnet) | ||
var expectedAddresses = ["mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"] | ||
@@ -128,6 +135,6 @@ | ||
it('returns the private key at the given index of external account', function(){ | ||
var wallet = new Wallet(seed, {network: 'testnet'}) | ||
var wallet = new Wallet(seed, networks.testnet) | ||
assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).priv) | ||
assertEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).priv) | ||
assertEqual(wallet.getPrivateKey(0), wallet.getExternalAccount().derive(0).privKey) | ||
assertEqual(wallet.getPrivateKey(1), wallet.getExternalAccount().derive(1).privKey) | ||
}) | ||
@@ -138,6 +145,6 @@ }) | ||
it('returns the private key at the given index of internal account', function(){ | ||
var wallet = new Wallet(seed, {network: 'testnet'}) | ||
var wallet = new Wallet(seed, networks.testnet) | ||
assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).priv) | ||
assertEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).priv) | ||
assertEqual(wallet.getInternalPrivateKey(0), wallet.getInternalAccount().derive(0).privKey) | ||
assertEqual(wallet.getInternalPrivateKey(1), wallet.getInternalAccount().derive(1).privKey) | ||
}) | ||
@@ -148,3 +155,3 @@ }) | ||
it('returns the private key for the given address', function(){ | ||
var wallet = new Wallet(seed, {network: 'testnet'}) | ||
var wallet = new Wallet(seed, networks.testnet) | ||
wallet.generateChangeAddress() | ||
@@ -154,10 +161,14 @@ wallet.generateAddress() | ||
assertEqual(wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), | ||
wallet.getExternalAccount().derive(1).priv) | ||
assertEqual(wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), | ||
wallet.getInternalAccount().derive(0).priv) | ||
assertEqual( | ||
wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X"), | ||
wallet.getExternalAccount().derive(1).privKey | ||
) | ||
assertEqual( | ||
wallet.getPrivateKeyForAddress("mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"), | ||
wallet.getInternalAccount().derive(0).privKey | ||
) | ||
}) | ||
it('raises an error when address is not found', function(){ | ||
var wallet = new Wallet(seed, {network: 'testnet'}) | ||
var wallet = new Wallet(seed, networks.testnet) | ||
assert.throws(function() { | ||
@@ -174,6 +185,6 @@ wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X") | ||
"hash":"6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7", | ||
"hashLittleEndian":"c7b97b432e7b3a8e31d1a4a0fa326c8fb002d19f2da5fc4feaf9c43a2762406a", | ||
"outputIndex": 0, | ||
"address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv", | ||
"value": 20000 | ||
"value": 20000, | ||
"pending": true | ||
} | ||
@@ -186,5 +197,6 @@ expectedOutputKey = expectedUtxo.hash + ":" + expectedUtxo.outputIndex | ||
wallet.outputs[key] = { | ||
receive: key, | ||
from: key, | ||
address: utxo.address, | ||
value: utxo.value | ||
value: utxo.value, | ||
pending: utxo.pending | ||
} | ||
@@ -207,10 +219,2 @@ } | ||
}) | ||
it('excludes spent outputs', function(){ | ||
addUtxoToOutput(expectedUtxo) | ||
addUtxoToOutput(utxo1) | ||
wallet.outputs[utxo1.hash + ':' + utxo1.outputIndex].spend = "sometxn:m" | ||
assert.equal(wallet.getBalance(), 20000) | ||
}) | ||
}) | ||
@@ -227,4 +231,6 @@ | ||
it('excludes spent outputs', function(){ | ||
wallet.outputs[expectedOutputKey].spend = "sometxn:m" | ||
it("ignores pending spending outputs (outputs with 'to' property)", function(){ | ||
var output = wallet.outputs[expectedOutputKey] | ||
output.to = fakeTxId(0) + ':' + 0 | ||
output.pending = true | ||
assert.deepEqual(wallet.getUnspentOutputs(), []) | ||
@@ -240,5 +246,3 @@ }) | ||
it('uses hashLittleEndian when hash is not present', function(){ | ||
delete utxo[0]['hash'] | ||
it('matches the expected behaviour', function(){ | ||
wallet.setUnspentOutputs(utxo) | ||
@@ -248,25 +252,4 @@ verifyOutputs() | ||
it('uses hash when hashLittleEndian is not present', function(){ | ||
delete utxo[0]['hashLittleEndian'] | ||
wallet.setUnspentOutputs(utxo) | ||
verifyOutputs() | ||
}) | ||
it('uses hash when both hash and hashLittleEndian are present', function(){ | ||
wallet.setUnspentOutputs(utxo) | ||
verifyOutputs() | ||
}) | ||
describe('required fields', function(){ | ||
it("throws an error when hash and hashLittleEndian are both missing", function(){ | ||
delete utxo[0]['hash'] | ||
delete utxo[0]['hashLittleEndian'] | ||
assert.throws(function() { | ||
wallet.setUnspentOutputs(utxo) | ||
}, /Invalid unspent output: key hash\(or hashLittleEndian\) is missing/) | ||
}); | ||
['outputIndex', 'address', 'value'].forEach(function(field){ | ||
['outputIndex', 'address', 'hash', 'value'].forEach(function(field){ | ||
it("throws an error when " + field + " is missing", function(){ | ||
@@ -289,108 +272,138 @@ delete utxo[0][field] | ||
}) | ||
}) | ||
describe('setUnspentOutputsAsync', function(){ | ||
var utxo | ||
beforeEach(function(){ | ||
utxo = cloneObject([expectedUtxo]) | ||
}) | ||
describe('Process transaction', function(){ | ||
var addresses | ||
var tx | ||
afterEach(function(){ | ||
wallet.setUnspentOutputs.restore() | ||
}) | ||
beforeEach(function(){ | ||
addresses = [ | ||
'115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi', | ||
'1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd', | ||
'1BBjuhF2jHxu7tPinyQGCuaNhEs6f5u59u' | ||
] | ||
it('calls setUnspentOutputs', function(done){ | ||
sinon.stub(wallet, "setUnspentOutputs") | ||
tx = Transaction.fromHex(fixtureTx1Hex) | ||
}) | ||
var callback = function(){ | ||
assert(wallet.setUnspentOutputs.calledWith(utxo)) | ||
done() | ||
} | ||
describe("processPendingTx", function(){ | ||
it("incoming: sets the pending flag on output", function(){ | ||
wallet.addresses = [addresses[0]] | ||
wallet.processPendingTx(tx) | ||
wallet.setUnspentOutputsAsync(utxo, callback) | ||
verifyOutputAdded(0, true) | ||
}) | ||
it('when setUnspentOutputs throws an error, it invokes callback with error', function(done){ | ||
sinon.stub(wallet, "setUnspentOutputs").throws() | ||
describe("when tx ins outpoint contains a known txhash:i", function(){ | ||
var spendTx | ||
beforeEach(function(){ | ||
wallet.addresses = [addresses[0]] | ||
wallet.processConfirmedTx(tx) | ||
var callback = function(err){ | ||
assert(err instanceof Error) | ||
done() | ||
} | ||
wallet.setUnspentOutputsAsync(utxo, callback) | ||
}) | ||
}) | ||
}) | ||
spendTx = Transaction.fromHex(fixtureTx2Hex) | ||
}) | ||
describe('processTx', function(){ | ||
var tx | ||
it("outgoing: sets the pending flag and 'to' on output", function(){ | ||
var txIn = spendTx.ins[0] | ||
var txInId = new Buffer(txIn.hash) | ||
Array.prototype.reverse.call(txInId) | ||
txInId = txInId.toString('hex') | ||
beforeEach(function(){ | ||
tx = Transaction.deserialize(fixtureTx1Hex) | ||
var key = txInId + ':' + txIn.index | ||
assert(!wallet.outputs[key].pending) | ||
wallet.processPendingTx(spendTx) | ||
assert(wallet.outputs[key].pending) | ||
assert.equal(wallet.outputs[key].to, spendTx.getId() + ':' + 0) | ||
}) | ||
}) | ||
}) | ||
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){ | ||
it("works for receive address", function(){ | ||
var totalOuts = outputCount() | ||
wallet.addresses = [tx.outs[0].address.toString()] | ||
describe('processConfirmedTx', function(){ | ||
it('does not fail on scripts with no corresponding Address', function() { | ||
var pubKey = wallet.getPrivateKey(0).pub | ||
var script = scripts.pubKeyOutput(pubKey) | ||
var tx2 = new Transaction() | ||
tx2.addInput(fakeTxId(1), 0) | ||
wallet.processTx(tx) | ||
// FIXME: Transaction doesn't support custom ScriptPubKeys... yet | ||
// So for now, we hijack the script with our own, and undefine the cached address | ||
tx2.addOutput(addresses[0], 10000) | ||
tx2.outs[0].script = script | ||
tx2.outs[0].address = undefined | ||
assert.equal(outputCount(), totalOuts + 1) | ||
verifyOutputAdded(0) | ||
wallet.processConfirmedTx(tx2) | ||
}) | ||
it("works for change address", function(){ | ||
var totalOuts = outputCount() | ||
wallet.changeAddresses = [tx.outs[1].address.toString()] | ||
describe("when tx outs contains an address owned by the wallet, an 'output' gets added to wallet.outputs", function(){ | ||
it("works for receive address", function(){ | ||
var totalOuts = outputCount() | ||
wallet.processTx(tx) | ||
wallet.addresses = [addresses[0]] | ||
wallet.processConfirmedTx(tx) | ||
assert.equal(outputCount(), totalOuts + 1) | ||
verifyOutputAdded(1) | ||
}) | ||
assert.equal(outputCount(), totalOuts + 1) | ||
verifyOutputAdded(0, false) | ||
}) | ||
function outputCount(){ | ||
return Object.keys(wallet.outputs).length | ||
} | ||
it("works for change address", function(){ | ||
var totalOuts = outputCount() | ||
wallet.changeAddresses = [addresses[1]] | ||
function verifyOutputAdded(index) { | ||
var txOut = tx.outs[index] | ||
var key = convert.bytesToHex(tx.getHash()) + ":" + index | ||
var output = wallet.outputs[key] | ||
assert.equal(output.receive, key) | ||
assert.equal(output.value, txOut.value) | ||
assert.equal(output.address, txOut.address) | ||
} | ||
}) | ||
wallet.processConfirmedTx(tx) | ||
describe("when tx ins outpoint contains a known txhash:i, the corresponding 'output' gets updated", function(){ | ||
beforeEach(function(){ | ||
wallet.addresses = [tx.outs[0].address.toString()] // the address fixtureTx2 used as input | ||
wallet.processTx(tx) | ||
assert.equal(outputCount(), totalOuts + 1) | ||
verifyOutputAdded(1, false) | ||
}) | ||
tx = Transaction.deserialize(fixtureTx2Hex) | ||
function outputCount(){ | ||
return Object.keys(wallet.outputs).length | ||
} | ||
}) | ||
it("does not add to wallet.outputs", function(){ | ||
var outputs = wallet.outputs | ||
wallet.processTx(tx) | ||
assert.deepEqual(wallet.outputs, outputs) | ||
}) | ||
describe("when tx ins outpoint contains a known txhash:i", function(){ | ||
var spendTx | ||
beforeEach(function(){ | ||
wallet.addresses = [addresses[0]] // the address fixtureTx2 used as input | ||
wallet.processConfirmedTx(tx) | ||
it("sets spend with the transaction hash and input index", function(){ | ||
wallet.processTx(tx) | ||
spendTx = Transaction.fromHex(fixtureTx2Hex) | ||
}) | ||
var txIn = tx.ins[0] | ||
var key = txIn.outpoint.hash + ":" + txIn.outpoint.index | ||
var output = wallet.outputs[key] | ||
it("does not add to wallet.outputs", function(){ | ||
wallet.processConfirmedTx(spendTx) | ||
assert.deepEqual(wallet.outputs, {}) | ||
}) | ||
assert.equal(output.spend, convert.bytesToHex(tx.getHash()) + ':' + 0) | ||
it("deletes corresponding 'output'", function(){ | ||
var txIn = spendTx.ins[0] | ||
var txInId = new Buffer(txIn.hash) | ||
Array.prototype.reverse.call(txInId) | ||
txInId = txInId.toString('hex') | ||
var expected = txInId + ':' + txIn.index | ||
assert(expected in wallet.outputs) | ||
wallet.processConfirmedTx(spendTx) | ||
assert(!(expected in wallet.outputs)) | ||
}) | ||
}) | ||
it("does nothing when none of the involved addresses belong to the wallet", function(){ | ||
wallet.processConfirmedTx(tx) | ||
assert.deepEqual(wallet.outputs, {}) | ||
}) | ||
}) | ||
it("does nothing when none of the involved addresses belong to the wallet", function(){ | ||
var outputs = wallet.outputs | ||
wallet.processTx(tx) | ||
assert.deepEqual(wallet.outputs, outputs) | ||
}) | ||
function verifyOutputAdded(index, pending) { | ||
var txOut = tx.outs[index] | ||
var key = tx.getId() + ":" + index | ||
var output = wallet.outputs[key] | ||
assert.equal(output.from, key) | ||
assert.equal(output.value, txOut.value) | ||
assert.equal(output.pending, pending) | ||
var txOutAddress = Address.fromOutputScript(txOut.script).toString() | ||
assert.equal(output.address, txOutAddress) | ||
} | ||
}) | ||
@@ -413,3 +426,3 @@ | ||
{ | ||
"hash": fakeTxHash(1), | ||
"hash": fakeTxId(1), | ||
"outputIndex": 0, | ||
@@ -420,3 +433,3 @@ "address" : address1, | ||
{ | ||
"hash": fakeTxHash(2), | ||
"hash": fakeTxId(2), | ||
"outputIndex": 1, | ||
@@ -427,6 +440,6 @@ "address" : address1, | ||
{ | ||
"hash": fakeTxHash(3), | ||
"hash": fakeTxId(3), | ||
"outputIndex": 0, | ||
"address" : address2, | ||
"value": 520000 // enough for value and fee | ||
"value": 510000 // enough for value and fee | ||
} | ||
@@ -437,10 +450,3 @@ ] | ||
describe('choosing utxo', function(){ | ||
it('calculates fees', function(){ | ||
var tx = wallet.createTx(to, value) | ||
assert.equal(tx.ins.length, 1) | ||
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 }) | ||
}) | ||
describe('transaction fee', function(){ | ||
it('allows fee to be specified', function(){ | ||
@@ -450,27 +456,61 @@ var fee = 30000 | ||
assert.equal(tx.ins.length, 2) | ||
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 }) | ||
assert.deepEqual(tx.ins[1].outpoint, { hash: fakeTxHash(2), index: 1 }) | ||
assert.equal(getFee(wallet, tx), fee) | ||
}) | ||
it('allows fee to be set to zero', function(){ | ||
value = 520000 | ||
value = 510000 | ||
var fee = 0 | ||
var tx = wallet.createTx(to, value, fee) | ||
assert.equal(getFee(wallet, tx), fee) | ||
}) | ||
it('does not overestimate fees when network has dustSoftThreshold', function(){ | ||
var wallet = new Wallet(seed, networks.litecoin) | ||
var address = wallet.generateAddress() | ||
wallet.setUnspentOutputs([{ | ||
hash: fakeTxId(0), | ||
outputIndex: 0, | ||
address: address, | ||
value: 500000 | ||
}]) | ||
value = 200000 | ||
var tx = wallet.createTx(address, value) | ||
assert.equal(getFee(wallet, tx), 100000) | ||
}) | ||
function getFee(wallet, tx) { | ||
var inputValue = tx.ins.reduce(function(memo, input){ | ||
var id = Array.prototype.reverse.call(input.hash).toString('hex') | ||
return memo + wallet.outputs[id + ':' + input.index].value | ||
}, 0) | ||
return tx.outs.reduce(function(memo, output){ | ||
return memo - output.value | ||
}, inputValue) | ||
} | ||
}) | ||
describe('choosing utxo', function(){ | ||
it('takes fees into account', function(){ | ||
var tx = wallet.createTx(to, value) | ||
assert.equal(tx.ins.length, 1) | ||
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 }) | ||
assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) | ||
assert.equal(tx.ins[0].index, 0) | ||
}) | ||
it('ignores spent outputs', function(){ | ||
it('ignores pending outputs', function(){ | ||
utxo.push( | ||
{ | ||
"hash": fakeTxHash(4), | ||
"hash": fakeTxId(4), | ||
"outputIndex": 0, | ||
"address" : address2, | ||
"value": 530000 // enough but spent before createTx | ||
"value": 530000, | ||
"pending": true | ||
} | ||
) | ||
wallet.setUnspentOutputs(utxo) | ||
wallet.outputs[fakeTxHash(4) + ":" + 0].spend = fakeTxHash(5) + ":" + 0 | ||
@@ -480,6 +520,63 @@ var tx = wallet.createTx(to, value) | ||
assert.equal(tx.ins.length, 1) | ||
assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 }) | ||
assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) | ||
assert.equal(tx.ins[0].index, 0) | ||
}) | ||
}) | ||
describe('works for testnet', function(){ | ||
it('should create transaction', function(){ | ||
var wallet = new Wallet(seed, networks.testnet) | ||
var address = wallet.generateAddress() | ||
wallet.setUnspentOutputs([{ | ||
hash: fakeTxId(0), | ||
outputIndex: 0, | ||
address: address, | ||
value: value | ||
}]) | ||
var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' | ||
var toValue = value - 10000 | ||
var tx = wallet.createTx(to, toValue) | ||
assert.equal(tx.outs.length, 1) | ||
var outAddress = Address.fromOutputScript(tx.outs[0].script, networks.testnet) | ||
assert.equal(outAddress.toString(), to) | ||
assert.equal(tx.outs[0].value, toValue) | ||
}) | ||
}) | ||
describe('changeAddress', function(){ | ||
it('should allow custom changeAddress', function(){ | ||
var wallet = new Wallet(seed, networks.testnet) | ||
var address = wallet.generateAddress() | ||
wallet.setUnspentOutputs([{ | ||
hash: fakeTxId(0), | ||
outputIndex: 0, | ||
address: address, | ||
value: value | ||
}]) | ||
assert.equal(wallet.getBalance(), value) | ||
var changeAddress = 'mfrFjnKZUvTcvdAK2fUX5D8v1Epu5H8JCk' | ||
var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' | ||
var toValue = value / 2 | ||
var fee = 1e3 | ||
var tx = wallet.createTx(to, toValue, fee, changeAddress) | ||
assert.equal(tx.outs.length, 2) | ||
var outAddress0 = Address.fromOutputScript(tx.outs[0].script, networks.testnet) | ||
var outAddress1 = Address.fromOutputScript(tx.outs[1].script, networks.testnet) | ||
assert.equal(outAddress0.toString(), to) | ||
assert.equal(tx.outs[0].value, toValue) | ||
assert.equal(outAddress1.toString(), changeAddress) | ||
assert.equal(tx.outs[1].value, value - (toValue + fee)) | ||
}) | ||
}) | ||
describe('transaction outputs', function(){ | ||
@@ -491,3 +588,5 @@ it('includes the specified address and amount', function(){ | ||
var out = tx.outs[0] | ||
assert.equal(out.address, to) | ||
var outAddress = Address.fromOutputScript(out.script) | ||
assert.equal(outAddress.toString(), to) | ||
assert.equal(out.value, value) | ||
@@ -498,3 +597,3 @@ }) | ||
it('uses the last change address if there is any', function(){ | ||
var fee = 5000 | ||
var fee = 0 | ||
wallet.generateChangeAddress() | ||
@@ -506,8 +605,10 @@ wallet.generateChangeAddress() | ||
var out = tx.outs[1] | ||
assert.equal(out.address, wallet.changeAddresses[1]) | ||
assert.equal(out.value, 15000) | ||
var outAddress = Address.fromOutputScript(out.script) | ||
assert.equal(outAddress.toString(), wallet.changeAddresses[1]) | ||
assert.equal(out.value, 10000) | ||
}) | ||
it('generates a change address if there is not any', function(){ | ||
var fee = 5000 | ||
var fee = 0 | ||
assert.equal(wallet.changeAddresses.length, 0) | ||
@@ -519,4 +620,6 @@ | ||
var out = tx.outs[1] | ||
assert.equal(out.address, wallet.changeAddresses[0]) | ||
assert.equal(out.value, 15000) | ||
var outAddress = Address.fromOutputScript(out.script) | ||
assert.equal(outAddress.toString(), wallet.changeAddresses[0]) | ||
assert.equal(out.value, 10000) | ||
}) | ||
@@ -550,7 +653,7 @@ | ||
it('throws an error', function(){ | ||
var value = 5430 | ||
var value = 546 | ||
assert.throws(function() { | ||
wallet.createTx(to, value) | ||
}, /Value must be above dust threshold/) | ||
}, /546 must be above dust threshold \(546 Satoshis\)/) | ||
}) | ||
@@ -565,62 +668,7 @@ }) | ||
wallet.createTx(to, value) | ||
}, /Not enough money to send funds including transaction fee. Have: 1420000, needed: 1420001/) | ||
}, /Not enough funds \(incl. fee\): 1410000 < 1410001/) | ||
}) | ||
}) | ||
function fakeTxHash(i) { | ||
return "txtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtx" + i | ||
} | ||
}) | ||
describe('createTxAsync', function(){ | ||
var to, value, fee | ||
beforeEach(function(){ | ||
to = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' | ||
value = 500000 | ||
fee = 10000 | ||
}) | ||
afterEach(function(){ | ||
wallet.createTx.restore() | ||
}) | ||
it('calls createTx', function(done){ | ||
sinon.stub(wallet, "createTx").returns("fakeTx") | ||
var callback = function(err, tx){ | ||
assert(wallet.createTx.calledWith(to, value)) | ||
assert.equal(err, null) | ||
assert.equal(tx, "fakeTx") | ||
done() | ||
} | ||
wallet.createTxAsync(to, value, callback) | ||
}) | ||
it('calls createTx correctly when fee is specified', function(done){ | ||
sinon.stub(wallet, "createTx").returns("fakeTx") | ||
var callback = function(err, tx){ | ||
assert(wallet.createTx.calledWith(to, value, fee)) | ||
assert.equal(err, null) | ||
assert.equal(tx, "fakeTx") | ||
done() | ||
} | ||
wallet.createTxAsync(to, value, fee, callback) | ||
}) | ||
it('when createTx throws an error, it invokes callback with error', function(done){ | ||
sinon.stub(wallet, "createTx").throws() | ||
var callback = function(err, tx){ | ||
assert(err instanceof Error) | ||
done() | ||
} | ||
wallet.createTxAsync(to, value, callback) | ||
}) | ||
}) | ||
function assertEqual(obj1, obj2){ | ||
@@ -627,0 +675,0 @@ assert.equal(obj1.toString(), obj2.toString()) |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
507712
67
7883
1
168
6
9
+ Addedbigi@1.1.0
+ Addedbs58@1.1.0
+ Addedcrypto-browserify@2.1.8
+ Addedecurve@0.10.0
+ Addedbase64-js@0.0.8(transitive)
+ Addedbigi@1.1.0(transitive)
+ Addedbs58@1.1.0(transitive)
+ Addedcrypto-browserify@2.1.8(transitive)
+ Addedecurve@0.10.0(transitive)
+ Addedieee754@1.1.13(transitive)
+ Addednative-buffer-browserify@2.0.17(transitive)
+ Addedripemd160@0.2.0(transitive)
+ Addedsecure-random@1.1.1(transitive)
+ Addedsha.js@2.1.3(transitive)
- Removedsecure-random@0.2.1(transitive)
Updatedsecure-random@1.1.1