parent
161127d65a
commit
93fe1b4c78
25 changed files with 2092 additions and 2142 deletions
|
@ -4,8 +4,9 @@ var error = require('./util').error
|
|||
var mainnet = require('./network').mainnet.addressVersion
|
||||
|
||||
function Address(bytes, version) {
|
||||
if (!(this instanceof Address))
|
||||
if (!(this instanceof Address)) {
|
||||
return new Address(bytes, version)
|
||||
}
|
||||
|
||||
if (bytes instanceof Address) {
|
||||
this.hash = bytes.hash
|
||||
|
|
|
@ -1,109 +1,108 @@
|
|||
// https://en.bitcoin.it/wiki/Base58Check_encoding
|
||||
|
||||
var BigInteger = require('./jsbn/jsbn');
|
||||
var Crypto = require('crypto-js');
|
||||
var convert = require('./convert');
|
||||
var SHA256 = Crypto.SHA256;
|
||||
var BigInteger = require('./jsbn/jsbn')
|
||||
var Crypto = require('crypto-js')
|
||||
var convert = require('./convert')
|
||||
var SHA256 = Crypto.SHA256
|
||||
|
||||
var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
var base = BigInteger.valueOf(58);
|
||||
var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
var base = BigInteger.valueOf(58)
|
||||
|
||||
var positions = {};
|
||||
for (var i=0 ; i < alphabet.length ; ++i) {
|
||||
positions[alphabet[i]] = i;
|
||||
var positions = {}
|
||||
for (var i=0; i<alphabet.length; ++i) {
|
||||
positions[alphabet[i]] = i
|
||||
}
|
||||
|
||||
// Convert a byte array to a base58-encoded string.
|
||||
// Written by Mike Hearn for BitcoinJ.
|
||||
// Copyright (c) 2011 Google Inc.
|
||||
// Copyright (c) 2011 Google Inc.
|
||||
// Ported to JavaScript by Stefan Thomas.
|
||||
function encode(input) {
|
||||
var bi = BigInteger.fromByteArrayUnsigned(input);
|
||||
var chars = [];
|
||||
var bi = BigInteger.fromByteArrayUnsigned(input)
|
||||
var chars = []
|
||||
|
||||
while (bi.compareTo(base) >= 0) {
|
||||
var mod = bi.mod(base);
|
||||
chars.push(alphabet[mod.intValue()]);
|
||||
bi = bi.subtract(mod).divide(base);
|
||||
var mod = bi.mod(base)
|
||||
chars.push(alphabet[mod.intValue()])
|
||||
bi = bi.subtract(mod).divide(base)
|
||||
}
|
||||
|
||||
chars.push(alphabet[bi.intValue()]);
|
||||
chars.push(alphabet[bi.intValue()])
|
||||
|
||||
// Convert leading zeros too.
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
for (var i=0; i<input.length; i++) {
|
||||
if (input[i] == 0x00) {
|
||||
chars.push(alphabet[0]);
|
||||
} else break;
|
||||
chars.push(alphabet[0])
|
||||
} else break
|
||||
}
|
||||
|
||||
return chars.reverse().join('');
|
||||
return chars.reverse().join('')
|
||||
}
|
||||
|
||||
// decode a base58 string into a byte array
|
||||
// input should be a base58 encoded string
|
||||
// @return Array
|
||||
function decode(input) {
|
||||
var base = BigInteger.valueOf(58);
|
||||
var base = BigInteger.valueOf(58)
|
||||
|
||||
var length = input.length;
|
||||
var num = BigInteger.valueOf(0);
|
||||
var leading_zero = 0;
|
||||
var seen_other = false;
|
||||
for (var i=0; i<length ; ++i) {
|
||||
var chr = input[i];
|
||||
var p = positions[chr];
|
||||
var length = input.length
|
||||
var num = BigInteger.valueOf(0)
|
||||
var leading_zero = 0
|
||||
var seen_other = false
|
||||
for (var i=0; i<length; ++i) {
|
||||
var chr = input[i]
|
||||
var p = positions[chr]
|
||||
|
||||
// if we encounter an invalid character, decoding fails
|
||||
if (p === undefined) {
|
||||
throw new Error('invalid base58 string: ' + input);
|
||||
throw new Error('invalid base58 string: ' + input)
|
||||
}
|
||||
|
||||
num = num.multiply(base).add(BigInteger.valueOf(p));
|
||||
num = num.multiply(base).add(BigInteger.valueOf(p))
|
||||
|
||||
if (chr == '1' && !seen_other) {
|
||||
++leading_zero;
|
||||
}
|
||||
else {
|
||||
seen_other = true;
|
||||
++leading_zero
|
||||
} else {
|
||||
seen_other = true
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = num.toByteArrayUnsigned();
|
||||
var bytes = num.toByteArrayUnsigned()
|
||||
|
||||
// remove leading zeros
|
||||
while (leading_zero-- > 0) {
|
||||
bytes.unshift(0);
|
||||
bytes.unshift(0)
|
||||
}
|
||||
|
||||
return bytes;
|
||||
return bytes
|
||||
}
|
||||
|
||||
function checkEncode(input, vbyte) {
|
||||
vbyte = vbyte || 0;
|
||||
vbyte = vbyte || 0
|
||||
|
||||
var front = [vbyte].concat(input);
|
||||
return encode(front.concat(getChecksum(front)));
|
||||
var front = [vbyte].concat(input)
|
||||
return encode(front.concat(getChecksum(front)))
|
||||
}
|
||||
|
||||
function checkDecode(input) {
|
||||
var bytes = decode(input),
|
||||
front = bytes.slice(0,bytes.length-4),
|
||||
back = bytes.slice(bytes.length-4);
|
||||
front = bytes.slice(0, bytes.length-4),
|
||||
back = bytes.slice(bytes.length-4)
|
||||
|
||||
var checksum = getChecksum(front);
|
||||
var checksum = getChecksum(front)
|
||||
|
||||
if ("" + checksum != "" + back) {
|
||||
throw new Error("Checksum failed");
|
||||
throw new Error("Checksum failed")
|
||||
}
|
||||
|
||||
var o = front.slice(1);
|
||||
o.version = front[0];
|
||||
return o;
|
||||
var o = front.slice(1)
|
||||
o.version = front[0]
|
||||
return o
|
||||
}
|
||||
|
||||
function getChecksum(bytes) {
|
||||
var wordArray = convert.bytesToWordArray(bytes)
|
||||
return convert.hexToBytes(SHA256(SHA256(wordArray)).toString()).slice(0,4);
|
||||
return convert.hexToBytes(SHA256(SHA256(wordArray)).toString()).slice(0, 4)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,74 +1,62 @@
|
|||
var Crypto = require('crypto-js');
|
||||
var WordArray = Crypto.lib.WordArray;
|
||||
var base64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
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;
|
||||
while (str.length < length) str = padString + str
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to a hex string
|
||||
*/
|
||||
function bytesToHex(bytes) {
|
||||
return bytes.map(function(x) {
|
||||
return lpad(x.toString(16), '0', 2)
|
||||
}).join('');
|
||||
};
|
||||
}).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a hex string to a byte array
|
||||
*/
|
||||
function hexToBytes(hex) {
|
||||
return hex.match(/../g).map(function(x) {
|
||||
return parseInt(x,16)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to a base-64 string
|
||||
*/
|
||||
function bytesToBase64(bytes) {
|
||||
var base64 = []
|
||||
|
||||
for (var i = 0; i < bytes.length; i += 3) {
|
||||
var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
||||
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));
|
||||
base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F))
|
||||
} else {
|
||||
base64.push('=');
|
||||
base64.push('=')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base64.join('');
|
||||
return base64.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a base-64 string to a byte array
|
||||
*/
|
||||
function base64ToBytes(base64) {
|
||||
// Remove non-base-64 characters
|
||||
base64 = base64.replace(/[^A-Z0-9+\/]/ig, '');
|
||||
base64 = base64.replace(/[^A-Z0-9+\/]/ig, '')
|
||||
|
||||
var bytes = [];
|
||||
var imod4 = 0;
|
||||
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)
|
||||
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;
|
||||
return bytes
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,48 +64,48 @@ function base64ToBytes(base64) {
|
|||
*/
|
||||
function coerceToBytes(input) {
|
||||
if (typeof input != 'string') return input
|
||||
return hexToBytes(input);
|
||||
return hexToBytes(input)
|
||||
}
|
||||
|
||||
function binToBytes(bin) {
|
||||
return bin.match(/......../g).map(function(x) {
|
||||
return parseInt(x,2)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function bytesToBin(bytes) {
|
||||
return bytes.map(function(x) {
|
||||
return lpad(x.toString(2), '0', 8)
|
||||
}).join('');
|
||||
}).join('')
|
||||
}
|
||||
|
||||
function bytesToString(bytes) {
|
||||
return bytes.map(function(x){
|
||||
return String.fromCharCode(x)
|
||||
}).join('');
|
||||
}).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));
|
||||
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));
|
||||
if (bytes.length === 0) return 0
|
||||
return bytes[0] + 256 * bytesToNum(bytes.slice(1))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,10 +116,10 @@ function bytesToNum(bytes) {
|
|||
* 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));
|
||||
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))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,7 +136,7 @@ function varIntToNum(bytes) {
|
|||
prefix < 253 ? bytes.slice(0, 1)
|
||||
: prefix === 253 ? bytes.slice(1, 3)
|
||||
: prefix === 254 ? bytes.slice(1, 5)
|
||||
: bytes.slice(1, 9)
|
||||
: bytes.slice(1, 9)
|
||||
|
||||
return {
|
||||
bytes: prefix < 253 ? viBytes : bytes.slice(0, viBytes.length + 1),
|
||||
|
@ -157,19 +145,19 @@ function varIntToNum(bytes) {
|
|||
}
|
||||
|
||||
function bytesToWords(bytes) {
|
||||
var words = [];
|
||||
var words = []
|
||||
for (var i = 0, b = 0; i < bytes.length; i++, b += 8) {
|
||||
words[b >>> 5] |= bytes[i] << (24 - b % 32);
|
||||
words[b >>> 5] |= bytes[i] << (24 - b % 32)
|
||||
}
|
||||
return words;
|
||||
return words
|
||||
}
|
||||
|
||||
function wordsToBytes(words) {
|
||||
var bytes = [];
|
||||
var bytes = []
|
||||
for (var b = 0; b < words.length * 32; b += 8) {
|
||||
bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
|
||||
bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF)
|
||||
}
|
||||
return bytes;
|
||||
return bytes
|
||||
}
|
||||
|
||||
function bytesToWordArray(bytes) {
|
||||
|
|
265
src/ecdsa.js
265
src/ecdsa.js
|
@ -1,133 +1,129 @@
|
|||
var sec = require('./jsbn/sec');
|
||||
var rng = require('secure-random');
|
||||
var BigInteger = require('./jsbn/jsbn');
|
||||
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 HmacSHA256 = require('crypto-js/hmac-sha256')
|
||||
var ECPointFp = require('./jsbn/ec').ECPointFp
|
||||
var ecparams = sec("secp256k1")
|
||||
var P_OVER_FOUR = null
|
||||
|
||||
var ECPointFp = require('./jsbn/ec').ECPointFp;
|
||||
|
||||
var ecparams = sec("secp256k1");
|
||||
var P_OVER_FOUR = null;
|
||||
|
||||
function implShamirsTrick(P, k, Q, l)
|
||||
{
|
||||
var m = Math.max(k.bitLength(), l.bitLength());
|
||||
var Z = P.add2D(Q);
|
||||
var R = P.curve.getInfinity();
|
||||
function implShamirsTrick(P, k, Q, l) {
|
||||
var m = Math.max(k.bitLength(), l.bitLength())
|
||||
var Z = P.add2D(Q)
|
||||
var R = P.curve.getInfinity()
|
||||
|
||||
for (var i = m - 1; i >= 0; --i) {
|
||||
R = R.twice2D();
|
||||
R = R.twice2D()
|
||||
|
||||
R.z = BigInteger.ONE;
|
||||
R.z = BigInteger.ONE
|
||||
|
||||
if (k.testBit(i)) {
|
||||
if (l.testBit(i)) {
|
||||
R = R.add2D(Z);
|
||||
R = R.add2D(Z)
|
||||
} else {
|
||||
R = R.add2D(P);
|
||||
R = R.add2D(P)
|
||||
}
|
||||
} else {
|
||||
if (l.testBit(i)) {
|
||||
R = R.add2D(Q);
|
||||
R = R.add2D(Q)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return R;
|
||||
};
|
||||
return R
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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);
|
||||
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)
|
||||
}
|
||||
|
||||
var ECDSA = {
|
||||
getBigRandom: function (limit) {
|
||||
return new BigInteger(limit.bitLength(), rng)
|
||||
.mod(limit.subtract(BigInteger.ONE))
|
||||
.add(BigInteger.ONE)
|
||||
;
|
||||
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);
|
||||
var d = priv
|
||||
var n = ecparams.getN()
|
||||
var e = BigInteger.fromByteArrayUnsigned(hash)
|
||||
|
||||
var k = deterministicGenerateK(hash,priv.toByteArrayUnsigned())
|
||||
var G = ecparams.getG();
|
||||
var Q = G.multiply(k);
|
||||
var r = Q.getX().toBigInteger().mod(n);
|
||||
var G = ecparams.getG()
|
||||
var Q = G.multiply(k)
|
||||
var r = Q.getX().toBigInteger().mod(n)
|
||||
|
||||
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
|
||||
var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n)
|
||||
|
||||
return ECDSA.serializeSig(r, s);
|
||||
return ECDSA.serializeSig(r, s)
|
||||
},
|
||||
|
||||
verify: function (hash, sig, pubkey) {
|
||||
var r,s;
|
||||
var r,s
|
||||
if (Array.isArray(sig)) {
|
||||
var obj = ECDSA.parseSig(sig);
|
||||
r = obj.r;
|
||||
s = obj.s;
|
||||
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;
|
||||
r = sig.r
|
||||
s = sig.s
|
||||
} else {
|
||||
throw new Error("Invalid value for signature");
|
||||
throw new Error("Invalid value for signature")
|
||||
}
|
||||
|
||||
var Q;
|
||||
var Q
|
||||
if (pubkey instanceof ECPointFp) {
|
||||
Q = pubkey;
|
||||
Q = pubkey
|
||||
} else if (Array.isArray(pubkey)) {
|
||||
Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey);
|
||||
Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey)
|
||||
} else {
|
||||
throw new Error("Invalid format for pubkey value, must be byte array or ECPointFp");
|
||||
throw new Error("Invalid format for pubkey value, must be byte array or ECPointFp")
|
||||
}
|
||||
var e = BigInteger.fromByteArrayUnsigned(hash);
|
||||
var e = BigInteger.fromByteArrayUnsigned(hash)
|
||||
|
||||
return ECDSA.verifyRaw(e, r, s, Q);
|
||||
return ECDSA.verifyRaw(e, r, s, Q)
|
||||
},
|
||||
|
||||
verifyRaw: function (e, r, s, Q) {
|
||||
var n = ecparams.getN();
|
||||
var G = ecparams.getG();
|
||||
var n = ecparams.getN()
|
||||
var G = ecparams.getG()
|
||||
|
||||
if (r.compareTo(BigInteger.ONE) < 0 ||
|
||||
r.compareTo(n) >= 0)
|
||||
return false;
|
||||
if (r.compareTo(BigInteger.ONE) < 0 || r.compareTo(n) >= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (s.compareTo(BigInteger.ONE) < 0 ||
|
||||
s.compareTo(n) >= 0)
|
||||
return false;
|
||||
if (s.compareTo(BigInteger.ONE) < 0 || s.compareTo(n) >= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
var c = s.modInverse(n);
|
||||
var c = s.modInverse(n)
|
||||
|
||||
var u1 = e.multiply(c).mod(n);
|
||||
var u2 = r.multiply(c).mod(n);
|
||||
var u1 = e.multiply(c).mod(n)
|
||||
var u2 = r.multiply(c).mod(n)
|
||||
|
||||
// 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));
|
||||
//var point = implShamirsTrick(G, u1, Q, u2)
|
||||
var point = G.multiply(u1).add(Q.multiply(u2))
|
||||
|
||||
var v = point.getX().toBigInteger().mod(n);
|
||||
var v = point.getX().toBigInteger().mod(n)
|
||||
|
||||
return v.equals(r);
|
||||
return v.equals(r)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -136,22 +132,22 @@ var ECDSA = {
|
|||
* Takes two BigIntegers representing r and s and returns a byte array.
|
||||
*/
|
||||
serializeSig: function (r, s) {
|
||||
var rBa = r.toByteArraySigned();
|
||||
var sBa = s.toByteArraySigned();
|
||||
var rBa = r.toByteArraySigned()
|
||||
var sBa = s.toByteArraySigned()
|
||||
|
||||
var sequence = [];
|
||||
var sequence = []
|
||||
sequence.push(0x02); // INTEGER
|
||||
sequence.push(rBa.length);
|
||||
sequence = sequence.concat(rBa);
|
||||
sequence.push(rBa.length)
|
||||
sequence = sequence.concat(rBa)
|
||||
|
||||
sequence.push(0x02); // INTEGER
|
||||
sequence.push(sBa.length);
|
||||
sequence = sequence.concat(sBa);
|
||||
sequence.push(sBa.length)
|
||||
sequence = sequence.concat(sBa)
|
||||
|
||||
sequence.unshift(sequence.length);
|
||||
sequence.unshift(sequence.length)
|
||||
sequence.unshift(0x30); // SEQUENCE
|
||||
|
||||
return sequence;
|
||||
return sequence
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -165,48 +161,51 @@ var ECDSA = {
|
|||
* }
|
||||
*/
|
||||
parseSig: function (sig) {
|
||||
var cursor;
|
||||
if (sig[0] != 0x30)
|
||||
throw new Error("Signature not a valid DERSequence");
|
||||
var cursor
|
||||
if (sig[0] != 0x30) {
|
||||
throw new Error("Signature not a valid DERSequence")
|
||||
}
|
||||
|
||||
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]);
|
||||
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])
|
||||
|
||||
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]);
|
||||
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])
|
||||
|
||||
cursor += 2+sig[cursor+1];
|
||||
cursor += 2+sig[cursor+1]
|
||||
|
||||
//if (cursor != sig.length)
|
||||
// throw new Error("Extra bytes in signature");
|
||||
// throw new Error("Extra bytes in signature")
|
||||
|
||||
var r = BigInteger.fromByteArrayUnsigned(rBa);
|
||||
var s = BigInteger.fromByteArrayUnsigned(sBa);
|
||||
var r = BigInteger.fromByteArrayUnsigned(rBa)
|
||||
var s = BigInteger.fromByteArrayUnsigned(sBa)
|
||||
|
||||
return {r: r, s: s};
|
||||
return {r: r, s: s}
|
||||
},
|
||||
|
||||
parseSigCompact: function (sig) {
|
||||
if (sig.length !== 65) {
|
||||
throw new Error("Signature has the wrong length");
|
||||
throw new Error("Signature has the wrong length")
|
||||
}
|
||||
|
||||
// Signature is prefixed with a type byte storing three bits of
|
||||
// information.
|
||||
var i = sig[0] - 27;
|
||||
var i = sig[0] - 27
|
||||
if (i < 0 || i > 7) {
|
||||
throw new Error("Invalid signature type");
|
||||
throw new Error("Invalid signature type")
|
||||
}
|
||||
|
||||
var n = ecparams.getN();
|
||||
var r = BigInteger.fromByteArrayUnsigned(sig.slice(1, 33)).mod(n);
|
||||
var s = BigInteger.fromByteArrayUnsigned(sig.slice(33, 65)).mod(n);
|
||||
var n = ecparams.getN()
|
||||
var r = BigInteger.fromByteArrayUnsigned(sig.slice(1, 33)).mod(n)
|
||||
var s = BigInteger.fromByteArrayUnsigned(sig.slice(33, 65)).mod(n)
|
||||
|
||||
return {r: r, s: s, i: i};
|
||||
return {r: r, s: s, i: i}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -219,57 +218,55 @@ var ECDSA = {
|
|||
*/
|
||||
recoverPubKey: function (r, s, hash, i) {
|
||||
// The recovery parameter i has two bits.
|
||||
i = i & 3;
|
||||
i = i & 3
|
||||
|
||||
// The less significant bit specifies whether the y coordinate
|
||||
// of the compressed point is even or not.
|
||||
var isYEven = i & 1;
|
||||
var isYEven = i & 1
|
||||
|
||||
// The more significant bit specifies whether we should use the
|
||||
// first or second candidate key.
|
||||
var isSecondKey = i >> 1;
|
||||
var isSecondKey = i >> 1
|
||||
|
||||
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();
|
||||
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()
|
||||
|
||||
// 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));
|
||||
P_OVER_FOUR = p.add(BigInteger.ONE).divide(BigInteger.valueOf(4))
|
||||
}
|
||||
|
||||
// 1.1 Compute x
|
||||
var x = isSecondKey ? r.add(n) : r;
|
||||
var x = isSecondKey ? r.add(n) : r
|
||||
|
||||
// 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 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);
|
||||
// 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);
|
||||
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();
|
||||
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);
|
||||
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);
|
||||
var rInv = r.modInverse(n)
|
||||
var Q = implShamirsTrick(R, s, G, eNeg).multiply(rInv)
|
||||
|
||||
Q.validate();
|
||||
Q.validate()
|
||||
if (!ECDSA.verifyRaw(e, r, s, Q)) {
|
||||
throw new Error("Pubkey recovery unsuccessful");
|
||||
throw new Error("Pubkey recovery unsuccessful")
|
||||
}
|
||||
|
||||
return Q
|
||||
|
@ -297,6 +294,6 @@ var ECDSA = {
|
|||
|
||||
throw new Error("Unable to find valid recovery factor")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ECDSA;
|
||||
module.exports = ECDSA
|
||||
|
|
49
src/eckey.js
49
src/eckey.js
|
@ -8,7 +8,6 @@ var ECPointFp = require('./jsbn/ec').ECPointFp
|
|||
var sec = require('./jsbn/sec')
|
||||
var Network = require('./network')
|
||||
var util = require('./util')
|
||||
|
||||
var ecparams = sec("secp256k1")
|
||||
|
||||
// input can be nothing, array of bytes, hex string, or base58 string
|
||||
|
@ -61,12 +60,12 @@ ECKey.prototype.import = function (input, compressed) {
|
|||
}
|
||||
|
||||
ECKey.prototype.getPub = function(compressed) {
|
||||
if (compressed === undefined) compressed = this.compressed
|
||||
return ECPubKey(ecparams.getG().multiply(this.priv), compressed)
|
||||
if (compressed === undefined) compressed = this.compressed
|
||||
return ECPubKey(ecparams.getG().multiply(this.priv), compressed)
|
||||
}
|
||||
|
||||
ECKey.prototype.toBin = function() {
|
||||
return convert.bytesToString(this.toBytes())
|
||||
return convert.bytesToString(this.toBytes())
|
||||
}
|
||||
|
||||
ECKey.version_bytes = {
|
||||
|
@ -75,41 +74,41 @@ ECKey.version_bytes = {
|
|||
}
|
||||
|
||||
ECKey.prototype.toWif = function(version) {
|
||||
version = version || Network.mainnet.addressVersion;
|
||||
version = version || Network.mainnet.addressVersion
|
||||
|
||||
return base58.checkEncode(this.toBytes(), ECKey.version_bytes[version])
|
||||
return base58.checkEncode(this.toBytes(), ECKey.version_bytes[version])
|
||||
}
|
||||
|
||||
ECKey.prototype.toHex = function() {
|
||||
return convert.bytesToHex(this.toBytes())
|
||||
return convert.bytesToHex(this.toBytes())
|
||||
}
|
||||
|
||||
ECKey.prototype.toBytes = function() {
|
||||
var bytes = this.priv.toByteArrayUnsigned();
|
||||
if (this.compressed) bytes.push(1)
|
||||
return bytes
|
||||
var bytes = this.priv.toByteArrayUnsigned()
|
||||
if (this.compressed) bytes.push(1)
|
||||
return bytes
|
||||
}
|
||||
|
||||
ECKey.prototype.toBase64 = function() {
|
||||
return convert.bytesToBase64(this.toBytes())
|
||||
return convert.bytesToBase64(this.toBytes())
|
||||
}
|
||||
|
||||
ECKey.prototype.toString = ECKey.prototype.toHex
|
||||
|
||||
ECKey.prototype.getAddress = function(version) {
|
||||
return this.getPub().getAddress(version)
|
||||
return this.getPub().getAddress(version)
|
||||
}
|
||||
|
||||
ECKey.prototype.add = function(key) {
|
||||
return ECKey(this.priv.add(ECKey(key).priv), this.compressed)
|
||||
return ECKey(this.priv.add(ECKey(key).priv), this.compressed)
|
||||
}
|
||||
|
||||
ECKey.prototype.multiply = function(key) {
|
||||
return ECKey(this.priv.multiply(ECKey(key).priv), this.compressed)
|
||||
return ECKey(this.priv.multiply(ECKey(key).priv), this.compressed)
|
||||
}
|
||||
|
||||
ECKey.prototype.sign = function(hash) {
|
||||
return ecdsa.sign(hash, this.priv);
|
||||
return ecdsa.sign(hash, this.priv)
|
||||
}
|
||||
|
||||
ECKey.prototype.verify = function(hash, sig) {
|
||||
|
@ -145,38 +144,38 @@ ECPubKey.prototype.import = function(input, compressed) {
|
|||
}
|
||||
|
||||
ECPubKey.prototype.add = function(key) {
|
||||
return ECPubKey(this.pub.add(ECPubKey(key).pub), this.compressed)
|
||||
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)
|
||||
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)
|
||||
if (compressed === undefined) compressed = this.compressed
|
||||
return this.pub.getEncoded(compressed)
|
||||
}
|
||||
|
||||
ECPubKey.prototype.toHex = function(compressed) {
|
||||
return convert.bytesToHex(this.toBytes(compressed))
|
||||
return convert.bytesToHex(this.toBytes(compressed))
|
||||
}
|
||||
|
||||
ECPubKey.prototype.toBin = function(compressed) {
|
||||
return convert.bytesToString(this.toBytes(compressed))
|
||||
return convert.bytesToString(this.toBytes(compressed))
|
||||
}
|
||||
|
||||
ECPubKey.prototype.toWif = function(version) {
|
||||
version = version || Network.mainnet.addressVersion;
|
||||
version = version || Network.mainnet.addressVersion
|
||||
|
||||
return base58.checkEncode(this.toBytes(), version)
|
||||
return base58.checkEncode(this.toBytes(), version)
|
||||
}
|
||||
|
||||
ECPubKey.prototype.toString = ECPubKey.prototype.toHex
|
||||
|
||||
ECPubKey.prototype.getAddress = function(version) {
|
||||
version = version || Network.mainnet.addressVersion;
|
||||
version = version || Network.mainnet.addressVersion
|
||||
|
||||
return new Address(util.sha256ripe160(this.toBytes()), version);
|
||||
return new Address(util.sha256ripe160(this.toBytes()), version)
|
||||
}
|
||||
|
||||
ECPubKey.prototype.verify = function(hash, sig) {
|
||||
|
|
292
src/hdwallet.js
292
src/hdwallet.js
|
@ -3,7 +3,7 @@ var base58 = require('./base58.js')
|
|||
var assert = require('assert')
|
||||
var format = require('util').format
|
||||
var util = require('./util.js')
|
||||
var Crypto = require('crypto-js');
|
||||
var Crypto = require('crypto-js')
|
||||
var HmacSHA512 = Crypto.HmacSHA512
|
||||
var HMAC= Crypto.algo.HMAC
|
||||
var ECKey = require('./eckey.js').ECKey
|
||||
|
@ -12,234 +12,234 @@ var Address = require('./address.js')
|
|||
var Network = require('./network')
|
||||
|
||||
var HDWallet = module.exports = function(seed, network) {
|
||||
if (seed === undefined) return
|
||||
if (seed === undefined) return;
|
||||
|
||||
var seedWords = convert.bytesToWordArray(seed)
|
||||
var I = convert.wordArrayToBytes(HmacSHA512(seedWords, 'Bitcoin seed'))
|
||||
this.chaincode = I.slice(32)
|
||||
this.network = network || 'mainnet'
|
||||
if(!Network.hasOwnProperty(this.network)) {
|
||||
throw new Error("Unknown network: " + this.network)
|
||||
}
|
||||
var seedWords = convert.bytesToWordArray(seed)
|
||||
var I = convert.wordArrayToBytes(HmacSHA512(seedWords, 'Bitcoin seed'))
|
||||
this.chaincode = I.slice(32)
|
||||
this.network = network || 'mainnet'
|
||||
if(!Network.hasOwnProperty(this.network)) {
|
||||
throw new Error("Unknown network: " + this.network)
|
||||
}
|
||||
|
||||
this.priv = new ECKey(I.slice(0, 32).concat([1]), true)
|
||||
this.pub = this.priv.getPub()
|
||||
this.index = 0
|
||||
this.depth = 0
|
||||
this.priv = new ECKey(I.slice(0, 32).concat([1]), true)
|
||||
this.pub = this.priv.getPub()
|
||||
this.index = 0
|
||||
this.depth = 0
|
||||
}
|
||||
|
||||
HDWallet.HIGHEST_BIT = 0x80000000
|
||||
HDWallet.LENGTH = 78
|
||||
|
||||
function arrayEqual(a, b) {
|
||||
return !(a < b || a > b)
|
||||
return !(a < b || a > b)
|
||||
}
|
||||
|
||||
HDWallet.getChecksum = base58.getChecksum;
|
||||
HDWallet.getChecksum = base58.getChecksum
|
||||
|
||||
HDWallet.fromSeedHex = function(hex, network) {
|
||||
return new HDWallet(convert.hexToBytes(hex), network)
|
||||
return new HDWallet(convert.hexToBytes(hex), network)
|
||||
}
|
||||
|
||||
HDWallet.fromSeedString = function(string, network) {
|
||||
return new HDWallet(convert.stringToBytes(string), network)
|
||||
return new HDWallet(convert.stringToBytes(string), network)
|
||||
}
|
||||
|
||||
HDWallet.fromBase58 = function(input) {
|
||||
var buffer = base58.decode(input)
|
||||
var buffer = base58.decode(input)
|
||||
|
||||
if (buffer.length == HDWallet.LENGTH + 4) {
|
||||
var expectedChecksum = buffer.slice(HDWallet.LENGTH, HDWallet.LENGTH + 4)
|
||||
buffer = buffer.slice(0, HDWallet.LENGTH)
|
||||
var actualChecksum = HDWallet.getChecksum(buffer)
|
||||
if (buffer.length == HDWallet.LENGTH + 4) {
|
||||
var expectedChecksum = buffer.slice(HDWallet.LENGTH, HDWallet.LENGTH + 4)
|
||||
buffer = buffer.slice(0, HDWallet.LENGTH)
|
||||
var actualChecksum = HDWallet.getChecksum(buffer)
|
||||
|
||||
if (!arrayEqual(expectedChecksum, actualChecksum)) {
|
||||
throw new Error('Checksum mismatch')
|
||||
}
|
||||
if (!arrayEqual(expectedChecksum, actualChecksum)) {
|
||||
throw new Error('Checksum mismatch')
|
||||
}
|
||||
}
|
||||
|
||||
return HDWallet.fromBytes(buffer)
|
||||
return HDWallet.fromBytes(buffer)
|
||||
}
|
||||
|
||||
HDWallet.fromHex = function(input) {
|
||||
return HDWallet.fromBytes(convert.hexToBytes(input))
|
||||
return HDWallet.fromBytes(convert.hexToBytes(input))
|
||||
}
|
||||
|
||||
HDWallet.fromBytes = function(input) {
|
||||
// This 78 byte structure can be encoded like other Bitcoin data in Base58. (+32 bits checksum)
|
||||
if (input.length != HDWallet.LENGTH) {
|
||||
throw new Error(format('Invalid input length, %s. Expected %s.', input.length, HDWallet.LENGTH))
|
||||
// This 78 byte structure can be encoded like other Bitcoin data in Base58. (+32 bits checksum)
|
||||
if (input.length != HDWallet.LENGTH) {
|
||||
throw new Error(format('Invalid input length, %s. Expected %s.', input.length, HDWallet.LENGTH))
|
||||
}
|
||||
|
||||
var hd = new HDWallet()
|
||||
|
||||
// 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private
|
||||
// testnet: 0x043587CF public, 0x04358394 private)
|
||||
var versionBytes = input.slice(0, 4)
|
||||
var versionWord = convert.bytesToWords(versionBytes)[0]
|
||||
var type
|
||||
|
||||
for(var name in Network) {
|
||||
var network = Network[name]
|
||||
for(var t in network.hdVersions) {
|
||||
if (versionWord != network.hdVersions[t]) continue
|
||||
type = t
|
||||
hd.network = name
|
||||
}
|
||||
}
|
||||
|
||||
var hd = new HDWallet()
|
||||
if (!hd.network) {
|
||||
throw new Error(format('Could not find version %s', convert.bytesToHex(versionBytes)))
|
||||
}
|
||||
|
||||
// 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private;
|
||||
// testnet: 0x043587CF public, 0x04358394 private)
|
||||
var versionBytes = input.slice(0, 4)
|
||||
var versionWord = convert.bytesToWords(versionBytes)[0]
|
||||
var type
|
||||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ...
|
||||
hd.depth = input[4]
|
||||
|
||||
for(var name in Network) {
|
||||
var network = Network[name]
|
||||
for(var t in network.hdVersions) {
|
||||
if (versionWord != network.hdVersions[t]) continue
|
||||
type = t
|
||||
hd.network = name
|
||||
}
|
||||
}
|
||||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
||||
hd.parentFingerprint = input.slice(5, 9)
|
||||
assert((hd.depth === 0) == arrayEqual(hd.parentFingerprint, [0, 0, 0, 0]))
|
||||
|
||||
if (!hd.network) {
|
||||
throw new Error(format('Could not find version %s', convert.bytesToHex(versionBytes)))
|
||||
}
|
||||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
||||
// This is encoded in MSB order. (0x00000000 if master key)
|
||||
hd.index = convert.bytesToNum(input.slice(9, 13).reverse())
|
||||
assert(hd.depth > 0 || hd.index === 0)
|
||||
|
||||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ...
|
||||
hd.depth = input[4]
|
||||
// 32 bytes: the chain code
|
||||
hd.chaincode = input.slice(13, 45)
|
||||
|
||||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
||||
hd.parentFingerprint = input.slice(5, 9)
|
||||
assert((hd.depth === 0) == arrayEqual(hd.parentFingerprint, [0, 0, 0, 0]))
|
||||
// 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for
|
||||
// public keys, 0x00 + k for private keys)
|
||||
if (type == 'priv') {
|
||||
hd.priv = new ECKey(input.slice(46, 78).concat([1]), true)
|
||||
hd.pub = hd.priv.getPub()
|
||||
} else {
|
||||
hd.pub = new ECPubKey(input.slice(45, 78), true)
|
||||
}
|
||||
|
||||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
||||
// This is encoded in MSB order. (0x00000000 if master key)
|
||||
hd.index = convert.bytesToNum(input.slice(9, 13).reverse())
|
||||
assert(hd.depth > 0 || hd.index === 0)
|
||||
|
||||
// 32 bytes: the chain code
|
||||
hd.chaincode = input.slice(13, 45)
|
||||
|
||||
// 33 bytes: the public key or private key data (0x02 + X or 0x03 + X for
|
||||
// public keys, 0x00 + k for private keys)
|
||||
if (type == 'priv') {
|
||||
hd.priv = new ECKey(input.slice(46, 78).concat([1]), true)
|
||||
hd.pub = hd.priv.getPub()
|
||||
} else {
|
||||
hd.pub = new ECPubKey(input.slice(45, 78), true)
|
||||
}
|
||||
|
||||
return hd
|
||||
return hd
|
||||
}
|
||||
|
||||
HDWallet.prototype.getIdentifier = function() {
|
||||
return util.sha256ripe160(this.pub.toBytes())
|
||||
return util.sha256ripe160(this.pub.toBytes())
|
||||
}
|
||||
|
||||
HDWallet.prototype.getFingerprint = function() {
|
||||
return this.getIdentifier().slice(0, 4)
|
||||
return this.getIdentifier().slice(0, 4)
|
||||
}
|
||||
|
||||
HDWallet.prototype.getAddress = function() {
|
||||
return new Address(util.sha256ripe160(this.pub.toBytes()), this.getKeyVersion())
|
||||
return new Address(util.sha256ripe160(this.pub.toBytes()), this.getKeyVersion())
|
||||
}
|
||||
|
||||
HDWallet.prototype.toBytes = function(priv) {
|
||||
var buffer = []
|
||||
var buffer = []
|
||||
|
||||
// Version
|
||||
// 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public,
|
||||
// 0x04358394 private)
|
||||
var version = Network[this.network].hdVersions[priv ? 'priv' : 'pub']
|
||||
var vBytes = convert.wordsToBytes([version])
|
||||
// Version
|
||||
// 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public,
|
||||
// 0x04358394 private)
|
||||
var version = Network[this.network].hdVersions[priv ? 'priv' : 'pub']
|
||||
var vBytes = convert.wordsToBytes([version])
|
||||
|
||||
buffer = buffer.concat(vBytes)
|
||||
assert.equal(buffer.length, 4)
|
||||
buffer = buffer.concat(vBytes)
|
||||
assert.equal(buffer.length, 4)
|
||||
|
||||
// Depth
|
||||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
|
||||
buffer.push(this.depth)
|
||||
assert.equal(buffer.length, 4 + 1)
|
||||
// Depth
|
||||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ....
|
||||
buffer.push(this.depth)
|
||||
assert.equal(buffer.length, 4 + 1)
|
||||
|
||||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
||||
buffer = buffer.concat(this.depth ? this.parentFingerprint : [0, 0, 0, 0])
|
||||
assert.equal(buffer.length, 4 + 1 + 4)
|
||||
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
|
||||
buffer = buffer.concat(this.depth ? this.parentFingerprint : [0, 0, 0, 0])
|
||||
assert.equal(buffer.length, 4 + 1 + 4)
|
||||
|
||||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
||||
// This is encoded in MSB order. (0x00000000 if master key)
|
||||
buffer = buffer.concat(convert.numToBytes(this.index, 4).reverse())
|
||||
assert.equal(buffer.length, 4 + 1 + 4 + 4)
|
||||
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
|
||||
// This is encoded in MSB order. (0x00000000 if master key)
|
||||
buffer = buffer.concat(convert.numToBytes(this.index, 4).reverse())
|
||||
assert.equal(buffer.length, 4 + 1 + 4 + 4)
|
||||
|
||||
// 32 bytes: the chain code
|
||||
buffer = buffer.concat(this.chaincode)
|
||||
assert.equal(buffer.length, 4 + 1 + 4 + 4 + 32)
|
||||
// 32 bytes: the chain code
|
||||
buffer = buffer.concat(this.chaincode)
|
||||
assert.equal(buffer.length, 4 + 1 + 4 + 4 + 32)
|
||||
|
||||
// 33 bytes: the public key or private key data
|
||||
// (0x02 + X or 0x03 + X for public keys, 0x00 + k for private keys)
|
||||
if (priv) {
|
||||
assert(this.priv, 'Cannot serialize to private without private key')
|
||||
buffer.push(0)
|
||||
buffer = buffer.concat(this.priv.toBytes().slice(0, 32))
|
||||
} else {
|
||||
buffer = buffer.concat(this.pub.toBytes(true))
|
||||
}
|
||||
// 33 bytes: the public key or private key data
|
||||
// (0x02 + X or 0x03 + X for public keys, 0x00 + k for private keys)
|
||||
if (priv) {
|
||||
assert(this.priv, 'Cannot serialize to private without private key')
|
||||
buffer.push(0)
|
||||
buffer = buffer.concat(this.priv.toBytes().slice(0, 32))
|
||||
} else {
|
||||
buffer = buffer.concat(this.pub.toBytes(true))
|
||||
}
|
||||
|
||||
return buffer
|
||||
return buffer
|
||||
}
|
||||
|
||||
HDWallet.prototype.toHex = function(priv) {
|
||||
var bytes = this.toBytes(priv)
|
||||
return convert.bytesToHex(bytes)
|
||||
var bytes = this.toBytes(priv)
|
||||
return convert.bytesToHex(bytes)
|
||||
}
|
||||
|
||||
HDWallet.prototype.toBase58 = function(priv) {
|
||||
var buffer = this.toBytes(priv)
|
||||
var buffer = this.toBytes(priv)
|
||||
, checksum = HDWallet.getChecksum(buffer)
|
||||
buffer = buffer.concat(checksum)
|
||||
return base58.encode(buffer)
|
||||
buffer = buffer.concat(checksum)
|
||||
return base58.encode(buffer)
|
||||
}
|
||||
|
||||
HDWallet.prototype.derive = function(i) {
|
||||
var I
|
||||
var I
|
||||
, iBytes = convert.numToBytes(i, 4).reverse()
|
||||
, cPar = this.chaincode
|
||||
, usePriv = i >= HDWallet.HIGHEST_BIT
|
||||
, SHA512 = Crypto.algo.SHA512
|
||||
|
||||
if (usePriv) {
|
||||
assert(this.priv, 'Private derive on public key')
|
||||
if (usePriv) {
|
||||
assert(this.priv, 'Private derive on public key')
|
||||
|
||||
// If 1, private derivation is used:
|
||||
// let I = HMAC-SHA512(Key = cpar, Data = 0x00 || kpar || i) [Note:]
|
||||
var kPar = this.priv.toBytes().slice(0, 32)
|
||||
I = HmacFromBytesToBytes(SHA512, [0].concat(kPar, iBytes), cPar)
|
||||
} else {
|
||||
// If 0, public derivation is used:
|
||||
// let I = HMAC-SHA512(Key = cpar, Data = χ(kpar*G) || i)
|
||||
var KPar = this.pub.toBytes(true)
|
||||
I = HmacFromBytesToBytes(SHA512, KPar.concat(iBytes), cPar)
|
||||
}
|
||||
// If 1, private derivation is used:
|
||||
// let I = HMAC-SHA512(Key = cpar, Data = 0x00 || kpar || i) [Note:]
|
||||
var kPar = this.priv.toBytes().slice(0, 32)
|
||||
I = HmacFromBytesToBytes(SHA512, [0].concat(kPar, iBytes), cPar)
|
||||
} else {
|
||||
// If 0, public derivation is used:
|
||||
// let I = HMAC-SHA512(Key = cpar, Data = χ(kpar*G) || i)
|
||||
var KPar = this.pub.toBytes(true)
|
||||
I = HmacFromBytesToBytes(SHA512, KPar.concat(iBytes), cPar)
|
||||
}
|
||||
|
||||
// Split I = IL || IR into two 32-byte sequences, IL and IR.
|
||||
var IL = I.slice(0, 32)
|
||||
// Split I = IL || IR into two 32-byte sequences, IL and IR.
|
||||
var IL = I.slice(0, 32)
|
||||
, IR = I.slice(32)
|
||||
|
||||
var hd = new HDWallet()
|
||||
hd.network = this.network
|
||||
var hd = new HDWallet()
|
||||
hd.network = this.network
|
||||
|
||||
if (this.priv) {
|
||||
// ki = IL + kpar (mod n).
|
||||
hd.priv = this.priv.add(new ECKey(IL.concat([1])))
|
||||
hd.priv.compressed = true
|
||||
hd.priv.version = this.getKeyVersion()
|
||||
hd.pub = hd.priv.getPub()
|
||||
} else {
|
||||
// Ki = (IL + kpar)*G = IL*G + Kpar
|
||||
hd.pub = this.pub.add(new ECKey(IL.concat([1]), true).getPub())
|
||||
}
|
||||
if (this.priv) {
|
||||
// ki = IL + kpar (mod n).
|
||||
hd.priv = this.priv.add(new ECKey(IL.concat([1])))
|
||||
hd.priv.compressed = true
|
||||
hd.priv.version = this.getKeyVersion()
|
||||
hd.pub = hd.priv.getPub()
|
||||
} else {
|
||||
// Ki = (IL + kpar)*G = IL*G + Kpar
|
||||
hd.pub = this.pub.add(new ECKey(IL.concat([1]), true).getPub())
|
||||
}
|
||||
|
||||
// ci = IR.
|
||||
hd.chaincode = IR
|
||||
hd.parentFingerprint = this.getFingerprint()
|
||||
hd.depth = this.depth + 1
|
||||
hd.index = i
|
||||
hd.pub.compressed = true
|
||||
return hd
|
||||
// ci = IR.
|
||||
hd.chaincode = IR
|
||||
hd.parentFingerprint = this.getFingerprint()
|
||||
hd.depth = this.depth + 1
|
||||
hd.index = i
|
||||
hd.pub.compressed = true
|
||||
return hd
|
||||
}
|
||||
|
||||
HDWallet.prototype.derivePrivate = function(index) {
|
||||
return this.derive(index + HDWallet.HIGHEST_BIT)
|
||||
return this.derive(index + HDWallet.HIGHEST_BIT)
|
||||
}
|
||||
|
||||
HDWallet.prototype.getKeyVersion = function() {
|
||||
return Network[this.network].addressVersion
|
||||
return Network[this.network].addressVersion
|
||||
}
|
||||
|
||||
HDWallet.prototype.toString = HDWallet.prototype.toBase58
|
||||
|
|
48
src/index.js
48
src/index.js
|
@ -1,29 +1,25 @@
|
|||
var Key = require('./eckey');
|
||||
var Key = require('./eckey')
|
||||
var T = require('./transaction')
|
||||
|
||||
module.exports = {
|
||||
Address: require('./address'),
|
||||
Key: Key.ECKey,
|
||||
ECKey: Key.ECKey,
|
||||
ECPubKey: Key.ECPubKey,
|
||||
Message: require('./message'),
|
||||
BigInteger: require('./jsbn/jsbn'),
|
||||
Crypto: require('crypto-js'), //should we expose this at all?
|
||||
Script: require('./script'),
|
||||
Opcode: require('./opcode'),
|
||||
Transaction: require('./transaction').Transaction,
|
||||
Util: require('./util'),
|
||||
TransactionIn: require('./transaction').TransactionIn,
|
||||
TransactionOut: require('./transaction').TransactionOut,
|
||||
ECPointFp: require('./jsbn/ec').ECPointFp,
|
||||
Wallet: require('./wallet'),
|
||||
network: require('./network'),
|
||||
|
||||
ecdsa: require('./ecdsa'),
|
||||
HDWallet: require('./hdwallet.js'),
|
||||
|
||||
// base58 encoding/decoding to bytes
|
||||
base58: require('./base58'),
|
||||
|
||||
// conversions
|
||||
convert: require('./convert')
|
||||
Address: require('./address'),
|
||||
Key: Key.ECKey,
|
||||
ECKey: Key.ECKey,
|
||||
ECPubKey: Key.ECPubKey,
|
||||
Message: require('./message'),
|
||||
BigInteger: require('./jsbn/jsbn'),
|
||||
Crypto: require('crypto-js'), //should we expose this at all?
|
||||
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'),
|
||||
convert: require('./convert')
|
||||
}
|
||||
|
|
|
@ -38,8 +38,8 @@ function sign(key, message) {
|
|||
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)
|
||||
while (rBa.length < 32) rBa.unshift(0);
|
||||
while (sBa.length < 32) sBa.unshift(0);
|
||||
|
||||
sig = [i].concat(rBa, sBa)
|
||||
|
||||
|
|
|
@ -15,5 +15,5 @@ module.exports = {
|
|||
priv: 0x04358394
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
260
src/opcode.js
260
src/opcode.js
|
@ -1,147 +1,147 @@
|
|||
var Opcode = {
|
||||
map: {
|
||||
// push value
|
||||
OP_0 : 0,
|
||||
OP_FALSE : 0,
|
||||
OP_PUSHDATA1 : 76,
|
||||
OP_PUSHDATA2 : 77,
|
||||
OP_PUSHDATA4 : 78,
|
||||
OP_1NEGATE : 79,
|
||||
OP_RESERVED : 80,
|
||||
OP_1 : 81,
|
||||
OP_TRUE : 81,
|
||||
OP_2 : 82,
|
||||
OP_3 : 83,
|
||||
OP_4 : 84,
|
||||
OP_5 : 85,
|
||||
OP_6 : 86,
|
||||
OP_7 : 87,
|
||||
OP_8 : 88,
|
||||
OP_9 : 89,
|
||||
OP_10 : 90,
|
||||
OP_11 : 91,
|
||||
OP_12 : 92,
|
||||
OP_13 : 93,
|
||||
OP_14 : 94,
|
||||
OP_15 : 95,
|
||||
OP_16 : 96,
|
||||
map: {
|
||||
// push value
|
||||
OP_0 : 0,
|
||||
OP_FALSE : 0,
|
||||
OP_PUSHDATA1 : 76,
|
||||
OP_PUSHDATA2 : 77,
|
||||
OP_PUSHDATA4 : 78,
|
||||
OP_1NEGATE : 79,
|
||||
OP_RESERVED : 80,
|
||||
OP_1 : 81,
|
||||
OP_TRUE : 81,
|
||||
OP_2 : 82,
|
||||
OP_3 : 83,
|
||||
OP_4 : 84,
|
||||
OP_5 : 85,
|
||||
OP_6 : 86,
|
||||
OP_7 : 87,
|
||||
OP_8 : 88,
|
||||
OP_9 : 89,
|
||||
OP_10 : 90,
|
||||
OP_11 : 91,
|
||||
OP_12 : 92,
|
||||
OP_13 : 93,
|
||||
OP_14 : 94,
|
||||
OP_15 : 95,
|
||||
OP_16 : 96,
|
||||
|
||||
// control
|
||||
OP_NOP : 97,
|
||||
OP_VER : 98,
|
||||
OP_IF : 99,
|
||||
OP_NOTIF : 100,
|
||||
OP_VERIF : 101,
|
||||
OP_VERNOTIF : 102,
|
||||
OP_ELSE : 103,
|
||||
OP_ENDIF : 104,
|
||||
OP_VERIFY : 105,
|
||||
OP_RETURN : 106,
|
||||
// control
|
||||
OP_NOP : 97,
|
||||
OP_VER : 98,
|
||||
OP_IF : 99,
|
||||
OP_NOTIF : 100,
|
||||
OP_VERIF : 101,
|
||||
OP_VERNOTIF : 102,
|
||||
OP_ELSE : 103,
|
||||
OP_ENDIF : 104,
|
||||
OP_VERIFY : 105,
|
||||
OP_RETURN : 106,
|
||||
|
||||
// stack ops
|
||||
OP_TOALTSTACK : 107,
|
||||
OP_FROMALTSTACK : 108,
|
||||
OP_2DROP : 109,
|
||||
OP_2DUP : 110,
|
||||
OP_3DUP : 111,
|
||||
OP_2OVER : 112,
|
||||
OP_2ROT : 113,
|
||||
OP_2SWAP : 114,
|
||||
OP_IFDUP : 115,
|
||||
OP_DEPTH : 116,
|
||||
OP_DROP : 117,
|
||||
OP_DUP : 118,
|
||||
OP_NIP : 119,
|
||||
OP_OVER : 120,
|
||||
OP_PICK : 121,
|
||||
OP_ROLL : 122,
|
||||
OP_ROT : 123,
|
||||
OP_SWAP : 124,
|
||||
OP_TUCK : 125,
|
||||
// stack ops
|
||||
OP_TOALTSTACK : 107,
|
||||
OP_FROMALTSTACK : 108,
|
||||
OP_2DROP : 109,
|
||||
OP_2DUP : 110,
|
||||
OP_3DUP : 111,
|
||||
OP_2OVER : 112,
|
||||
OP_2ROT : 113,
|
||||
OP_2SWAP : 114,
|
||||
OP_IFDUP : 115,
|
||||
OP_DEPTH : 116,
|
||||
OP_DROP : 117,
|
||||
OP_DUP : 118,
|
||||
OP_NIP : 119,
|
||||
OP_OVER : 120,
|
||||
OP_PICK : 121,
|
||||
OP_ROLL : 122,
|
||||
OP_ROT : 123,
|
||||
OP_SWAP : 124,
|
||||
OP_TUCK : 125,
|
||||
|
||||
// splice ops
|
||||
OP_CAT : 126,
|
||||
OP_SUBSTR : 127,
|
||||
OP_LEFT : 128,
|
||||
OP_RIGHT : 129,
|
||||
OP_SIZE : 130,
|
||||
// splice ops
|
||||
OP_CAT : 126,
|
||||
OP_SUBSTR : 127,
|
||||
OP_LEFT : 128,
|
||||
OP_RIGHT : 129,
|
||||
OP_SIZE : 130,
|
||||
|
||||
// bit logic
|
||||
OP_INVERT : 131,
|
||||
OP_AND : 132,
|
||||
OP_OR : 133,
|
||||
OP_XOR : 134,
|
||||
OP_EQUAL : 135,
|
||||
OP_EQUALVERIFY : 136,
|
||||
OP_RESERVED1 : 137,
|
||||
OP_RESERVED2 : 138,
|
||||
// bit logic
|
||||
OP_INVERT : 131,
|
||||
OP_AND : 132,
|
||||
OP_OR : 133,
|
||||
OP_XOR : 134,
|
||||
OP_EQUAL : 135,
|
||||
OP_EQUALVERIFY : 136,
|
||||
OP_RESERVED1 : 137,
|
||||
OP_RESERVED2 : 138,
|
||||
|
||||
// numeric
|
||||
OP_1ADD : 139,
|
||||
OP_1SUB : 140,
|
||||
OP_2MUL : 141,
|
||||
OP_2DIV : 142,
|
||||
OP_NEGATE : 143,
|
||||
OP_ABS : 144,
|
||||
OP_NOT : 145,
|
||||
OP_0NOTEQUAL : 146,
|
||||
// numeric
|
||||
OP_1ADD : 139,
|
||||
OP_1SUB : 140,
|
||||
OP_2MUL : 141,
|
||||
OP_2DIV : 142,
|
||||
OP_NEGATE : 143,
|
||||
OP_ABS : 144,
|
||||
OP_NOT : 145,
|
||||
OP_0NOTEQUAL : 146,
|
||||
|
||||
OP_ADD : 147,
|
||||
OP_SUB : 148,
|
||||
OP_MUL : 149,
|
||||
OP_DIV : 150,
|
||||
OP_MOD : 151,
|
||||
OP_LSHIFT : 152,
|
||||
OP_RSHIFT : 153,
|
||||
OP_ADD : 147,
|
||||
OP_SUB : 148,
|
||||
OP_MUL : 149,
|
||||
OP_DIV : 150,
|
||||
OP_MOD : 151,
|
||||
OP_LSHIFT : 152,
|
||||
OP_RSHIFT : 153,
|
||||
|
||||
OP_BOOLAND : 154,
|
||||
OP_BOOLOR : 155,
|
||||
OP_NUMEQUAL : 156,
|
||||
OP_NUMEQUALVERIFY : 157,
|
||||
OP_NUMNOTEQUAL : 158,
|
||||
OP_LESSTHAN : 159,
|
||||
OP_GREATERTHAN : 160,
|
||||
OP_LESSTHANOREQUAL : 161,
|
||||
OP_GREATERTHANOREQUAL : 162,
|
||||
OP_MIN : 163,
|
||||
OP_MAX : 164,
|
||||
OP_BOOLAND : 154,
|
||||
OP_BOOLOR : 155,
|
||||
OP_NUMEQUAL : 156,
|
||||
OP_NUMEQUALVERIFY : 157,
|
||||
OP_NUMNOTEQUAL : 158,
|
||||
OP_LESSTHAN : 159,
|
||||
OP_GREATERTHAN : 160,
|
||||
OP_LESSTHANOREQUAL : 161,
|
||||
OP_GREATERTHANOREQUAL : 162,
|
||||
OP_MIN : 163,
|
||||
OP_MAX : 164,
|
||||
|
||||
OP_WITHIN : 165,
|
||||
OP_WITHIN : 165,
|
||||
|
||||
// crypto
|
||||
OP_RIPEMD160 : 166,
|
||||
OP_SHA1 : 167,
|
||||
OP_SHA256 : 168,
|
||||
OP_HASH160 : 169,
|
||||
OP_HASH256 : 170,
|
||||
OP_CODESEPARATOR : 171,
|
||||
OP_CHECKSIG : 172,
|
||||
OP_CHECKSIGVERIFY : 173,
|
||||
OP_CHECKMULTISIG : 174,
|
||||
OP_CHECKMULTISIGVERIFY : 175,
|
||||
// crypto
|
||||
OP_RIPEMD160 : 166,
|
||||
OP_SHA1 : 167,
|
||||
OP_SHA256 : 168,
|
||||
OP_HASH160 : 169,
|
||||
OP_HASH256 : 170,
|
||||
OP_CODESEPARATOR : 171,
|
||||
OP_CHECKSIG : 172,
|
||||
OP_CHECKSIGVERIFY : 173,
|
||||
OP_CHECKMULTISIG : 174,
|
||||
OP_CHECKMULTISIGVERIFY : 175,
|
||||
|
||||
// expansion
|
||||
OP_NOP1 : 176,
|
||||
OP_NOP2 : 177,
|
||||
OP_NOP3 : 178,
|
||||
OP_NOP4 : 179,
|
||||
OP_NOP5 : 180,
|
||||
OP_NOP6 : 181,
|
||||
OP_NOP7 : 182,
|
||||
OP_NOP8 : 183,
|
||||
OP_NOP9 : 184,
|
||||
OP_NOP10 : 185,
|
||||
// expansion
|
||||
OP_NOP1 : 176,
|
||||
OP_NOP2 : 177,
|
||||
OP_NOP3 : 178,
|
||||
OP_NOP4 : 179,
|
||||
OP_NOP5 : 180,
|
||||
OP_NOP6 : 181,
|
||||
OP_NOP7 : 182,
|
||||
OP_NOP8 : 183,
|
||||
OP_NOP9 : 184,
|
||||
OP_NOP10 : 185,
|
||||
|
||||
// template matching params
|
||||
OP_PUBKEYHASH : 253,
|
||||
OP_PUBKEY : 254,
|
||||
OP_INVALIDOPCODE : 255
|
||||
},
|
||||
reverseMap: []
|
||||
// template matching params
|
||||
OP_PUBKEYHASH : 253,
|
||||
OP_PUBKEY : 254,
|
||||
OP_INVALIDOPCODE : 255
|
||||
},
|
||||
reverseMap: []
|
||||
}
|
||||
|
||||
for(var i in Opcode.map) {
|
||||
Opcode.reverseMap[Opcode.map[i]] = i
|
||||
Opcode.reverseMap[Opcode.map[i]] = i
|
||||
}
|
||||
|
||||
module.exports = Opcode;
|
||||
module.exports = Opcode
|
||||
|
|
407
src/script.js
407
src/script.js
|
@ -1,46 +1,46 @@
|
|||
var Opcode = require('./opcode');
|
||||
var util = require('./util');
|
||||
var convert = require('./convert');
|
||||
var Address = require('./address');
|
||||
var network = require('./network');
|
||||
var Opcode = require('./opcode')
|
||||
var util = require('./util')
|
||||
var convert = require('./convert')
|
||||
var Address = require('./address')
|
||||
var network = require('./network')
|
||||
|
||||
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();
|
||||
};
|
||||
this.buffer = data || []
|
||||
if(!Array.isArray(this.buffer)) {
|
||||
throw new Error('expect Script to be initialized with Array, but got ' + data)
|
||||
}
|
||||
this.parse()
|
||||
}
|
||||
|
||||
Script.fromHex = function(data) {
|
||||
return new Script(convert.hexToBytes(data))
|
||||
};
|
||||
return new Script(convert.hexToBytes(data))
|
||||
}
|
||||
|
||||
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]]);
|
||||
} else {
|
||||
script.writeBytes(convert.hexToBytes(s[i]));
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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]));
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the parsed script representation.
|
||||
|
@ -54,47 +54,47 @@ Script.fromScriptSig = function(str) {
|
|||
* the script buffer manually, you should update the chunks using this method.
|
||||
*/
|
||||
Script.prototype.parse = function() {
|
||||
var self = this;
|
||||
var self = this
|
||||
|
||||
this.chunks = [];
|
||||
this.chunks = []
|
||||
|
||||
// Cursor
|
||||
var i = 0;
|
||||
// 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;
|
||||
// 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 < this.buffer.length) {
|
||||
var opcode = this.buffer[i++]
|
||||
if (opcode >= 0xF0) {
|
||||
// Two byte opcode
|
||||
opcode = (opcode << 8) | this.buffer[i++]
|
||||
}
|
||||
|
||||
while (i < this.buffer.length) {
|
||||
var opcode = this.buffer[i++];
|
||||
if (opcode >= 0xF0) {
|
||||
// Two byte opcode
|
||||
opcode = (opcode << 8) | this.buffer[i++];
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the script to known templates of scriptPubKey.
|
||||
|
@ -115,21 +115,21 @@ Script.prototype.parse = function() {
|
|||
* 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';
|
||||
}
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,37 +137,37 @@ Script.prototype.getOutType = function() {
|
|||
* Assumes strange scripts are P2SH
|
||||
*/
|
||||
Script.prototype.toScriptHash = function() {
|
||||
var outType = this.getOutType();
|
||||
var outType = this.getOutType()
|
||||
|
||||
if (outType == 'Pubkey') {
|
||||
return this.chunks[2]
|
||||
}
|
||||
|
||||
if (outType == 'P2SH') {
|
||||
return util.sha256ripe160(this.buffer)
|
||||
}
|
||||
if (outType == 'Pubkey') {
|
||||
return this.chunks[2]
|
||||
}
|
||||
|
||||
if (outType == 'P2SH') {
|
||||
return util.sha256ripe160(this.buffer)
|
||||
}
|
||||
|
||||
return util.sha256ripe160(this.buffer)
|
||||
}
|
||||
|
||||
//TODO: support testnet
|
||||
Script.prototype.getToAddress = function() {
|
||||
var outType = this.getOutType();
|
||||
var outType = this.getOutType()
|
||||
|
||||
if (outType == 'Pubkey') {
|
||||
return new Address(this.chunks[2])
|
||||
}
|
||||
|
||||
if (outType == 'P2SH') {
|
||||
return new Address(this.chunks[1], 5)
|
||||
}
|
||||
if (outType == 'Pubkey') {
|
||||
return new Address(this.chunks[2])
|
||||
}
|
||||
|
||||
if (outType == 'P2SH') {
|
||||
return new Address(this.chunks[1], 5)
|
||||
}
|
||||
|
||||
return new Address(this.chunks[1], 5)
|
||||
}
|
||||
|
||||
//TODO: support testnet
|
||||
Script.prototype.getFromAddress = function(){
|
||||
return new Address(this.simpleInHash());
|
||||
return new Address(this.simpleInHash())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,24 +196,24 @@ Script.prototype.getFromAddress = function(){
|
|||
* 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';
|
||||
}
|
||||
};
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the affected public key for this input.
|
||||
|
@ -229,17 +229,17 @@ Script.prototype.getInType = function() {
|
|||
* @deprecated
|
||||
*/
|
||||
Script.prototype.simpleInPubKey = function() {
|
||||
switch (this.getInType()) {
|
||||
switch (this.getInType()) {
|
||||
case 'Address':
|
||||
return this.chunks[1];
|
||||
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');
|
||||
// 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');
|
||||
}
|
||||
};
|
||||
throw new Error('Encountered non-standard scriptSig')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the affected address hash for this input.
|
||||
|
@ -257,133 +257,130 @@ Script.prototype.simpleInPubKey = function() {
|
|||
* This method is useful for indexing transactions.
|
||||
*/
|
||||
Script.prototype.simpleInHash = function() {
|
||||
return util.sha256ripe160(this.simpleInPubKey());
|
||||
};
|
||||
return util.sha256ripe160(this.simpleInPubKey())
|
||||
}
|
||||
|
||||
/**
|
||||
* Old name for Script#simpleInHash.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
Script.prototype.simpleInPubKeyHash = Script.prototype.simpleInHash;
|
||||
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);
|
||||
};
|
||||
this.buffer.push(opcode)
|
||||
this.chunks.push(opcode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a data chunk to the script.
|
||||
*/
|
||||
Script.prototype.writeBytes = function(data) {
|
||||
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);
|
||||
};
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
});
|
||||
return this.chunks.filter(function(chunk) {
|
||||
return(chunk[0] == 4 && chunk.length == 65 || chunk[0] < 4 && chunk.length == 33)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an m-of-n output script
|
||||
*/
|
||||
Script.createMultiSigOutputScript = function(m, pubkeys) {
|
||||
var script = new Script();
|
||||
var script = new Script()
|
||||
pubkeys = pubkeys.sort()
|
||||
|
||||
pubkeys = pubkeys.sort();
|
||||
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)
|
||||
|
||||
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;
|
||||
};
|
||||
return script
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standard payToPubKeyHash input.
|
||||
*/
|
||||
Script.createInputScript = function(signature, pubKey) {
|
||||
var script = new Script();
|
||||
script.writeBytes(signature);
|
||||
script.writeBytes(pubKey);
|
||||
return script;
|
||||
};
|
||||
var script = new Script()
|
||||
script.writeBytes(signature)
|
||||
script.writeBytes(pubKey)
|
||||
return script
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a multisig input
|
||||
*/
|
||||
Script.createMultiSigInputScript = function(signatures, script) {
|
||||
script = new Script(script);
|
||||
var k = script.chunks[0][0];
|
||||
script = new Script(script)
|
||||
var k = script.chunks[0][0]
|
||||
|
||||
//Not enough sigs
|
||||
if (signatures.length < k) return false;
|
||||
//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;
|
||||
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.clone = function() {
|
||||
return new Script(this.buffer);
|
||||
};
|
||||
return new Script(this.buffer)
|
||||
}
|
||||
|
||||
module.exports = Script;
|
||||
module.exports = Script
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
var BigInteger = require('./jsbn/jsbn');
|
||||
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 BigInteger = require('./jsbn/jsbn')
|
||||
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); }
|
||||
this.version = 1;
|
||||
this.locktime = 0;
|
||||
this.ins = [];
|
||||
this.outs = [];
|
||||
this.defaultSequence = [255, 255, 255, 255]; // 0xFFFFFFFF
|
||||
if (!(this instanceof Transaction)) { return new Transaction(doc) }
|
||||
this.version = 1
|
||||
this.locktime = 0
|
||||
this.ins = []
|
||||
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();
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new txin.
|
||||
|
@ -52,27 +52,27 @@ var Transaction = function (doc) {
|
|||
* 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]);
|
||||
}
|
||||
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;
|
||||
if (arguments[0] instanceof TransactionIn) {
|
||||
this.ins.push(arguments[0])
|
||||
}
|
||||
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
|
||||
}));
|
||||
}
|
||||
};
|
||||
this.ins.push(new TransactionIn({
|
||||
outpoint: {
|
||||
hash: hash,
|
||||
index: outIndex
|
||||
},
|
||||
script: new Script(),
|
||||
sequence: this.defaultSequence
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new txout.
|
||||
|
@ -85,22 +85,22 @@ Transaction.prototype.addInput = function (tx, outIndex) {
|
|||
*
|
||||
*/
|
||||
Transaction.prototype.addOutput = function (address, value) {
|
||||
if (arguments[0] instanceof TransactionOut) {
|
||||
this.outs.push(arguments[0]);
|
||||
return;
|
||||
}
|
||||
if (arguments[0] instanceof TransactionOut) {
|
||||
this.outs.push(arguments[0])
|
||||
return
|
||||
}
|
||||
|
||||
if (arguments[0].indexOf(':') >= 0) {
|
||||
var args = arguments[0].split(':');
|
||||
address = args[0];
|
||||
value = parseInt(args[1]);
|
||||
}
|
||||
if (arguments[0].indexOf(':') >= 0) {
|
||||
var args = arguments[0].split(':')
|
||||
address = args[0]
|
||||
value = parseInt(args[1])
|
||||
}
|
||||
|
||||
this.outs.push(new TransactionOut({
|
||||
value: value,
|
||||
script: Script.createOutputScript(address)
|
||||
}));
|
||||
};
|
||||
this.outs.push(new TransactionOut({
|
||||
value: value,
|
||||
script: Script.createOutputScript(address)
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this transaction.
|
||||
|
@ -110,48 +110,48 @@ Transaction.prototype.addOutput = function (address, value) {
|
|||
* 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));
|
||||
var buffer = []
|
||||
buffer = buffer.concat(convert.numToBytes(parseInt(this.version), 4))
|
||||
buffer = buffer.concat(convert.numToVarInt(this.ins.length))
|
||||
|
||||
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());
|
||||
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())
|
||||
|
||||
buffer = buffer.concat(convert.numToBytes(parseInt(txin.outpoint.index), 4));
|
||||
buffer = buffer.concat(convert.numToBytes(parseInt(txin.outpoint.index), 4))
|
||||
|
||||
var scriptBytes = txin.script.buffer;
|
||||
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length));
|
||||
buffer = buffer.concat(scriptBytes);
|
||||
buffer = buffer.concat(txin.sequence);
|
||||
});
|
||||
var scriptBytes = txin.script.buffer
|
||||
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length))
|
||||
buffer = buffer.concat(scriptBytes)
|
||||
buffer = buffer.concat(txin.sequence)
|
||||
})
|
||||
|
||||
buffer = buffer.concat(convert.numToVarInt(this.outs.length));
|
||||
buffer = buffer.concat(convert.numToVarInt(this.outs.length))
|
||||
|
||||
this.outs.forEach(function(txout) {
|
||||
buffer = buffer.concat(convert.numToBytes(txout.value,8));
|
||||
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);
|
||||
});
|
||||
var scriptBytes = txout.script.buffer
|
||||
buffer = buffer.concat(convert.numToVarInt(scriptBytes.length))
|
||||
buffer = buffer.concat(scriptBytes)
|
||||
})
|
||||
|
||||
buffer = buffer.concat(convert.numToBytes(parseInt(this.locktime), 4));
|
||||
buffer = buffer.concat(convert.numToBytes(parseInt(this.locktime), 4))
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Transaction.prototype.serializeHex = function() {
|
||||
return convert.bytesToHex(this.serialize());
|
||||
return buffer
|
||||
}
|
||||
|
||||
//var OP_CODESEPARATOR = 171;
|
||||
Transaction.prototype.serializeHex = function() {
|
||||
return convert.bytesToHex(this.serialize())
|
||||
}
|
||||
|
||||
var SIGHASH_ALL = 1;
|
||||
var SIGHASH_NONE = 2;
|
||||
var SIGHASH_SINGLE = 3;
|
||||
var SIGHASH_ANYONECANPAY = 80;
|
||||
//var OP_CODESEPARATOR = 171
|
||||
|
||||
var SIGHASH_ALL = 1
|
||||
var SIGHASH_NONE = 2
|
||||
var SIGHASH_SINGLE = 3
|
||||
var SIGHASH_ANYONECANPAY = 80
|
||||
|
||||
/**
|
||||
* Hash transaction for signing a specific input.
|
||||
|
@ -162,34 +162,34 @@ var SIGHASH_ANYONECANPAY = 80;
|
|||
* used to sign the transaction input in question.
|
||||
*/
|
||||
Transaction.prototype.hashTransactionForSignature =
|
||||
function (connectedScript, inIndex, hashType)
|
||||
function (connectedScript, inIndex, hashType)
|
||||
{
|
||||
var txTmp = this.clone();
|
||||
var txTmp = this.clone()
|
||||
|
||||
// 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;
|
||||
});*/
|
||||
return val !== OP_CODESEPARATOR
|
||||
});*/
|
||||
|
||||
// Blank out other inputs' signatures
|
||||
txTmp.ins.forEach(function(txin) {
|
||||
txin.script = new Script();
|
||||
});
|
||||
txin.script = new Script()
|
||||
})
|
||||
|
||||
txTmp.ins[inIndex].script = connectedScript;
|
||||
txTmp.ins[inIndex].script = connectedScript
|
||||
|
||||
// Blank out some of the outputs
|
||||
if ((hashType & 0x1f) == SIGHASH_NONE) {
|
||||
txTmp.outs = [];
|
||||
txTmp.outs = []
|
||||
|
||||
// Let the others update at will
|
||||
txTmp.ins.forEach(function(txin, i) {
|
||||
if (i != inIndex) {
|
||||
txTmp.ins[i].sequence = 0;
|
||||
txTmp.ins[i].sequence = 0
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
} else if ((hashType & 0x1f) == SIGHASH_SINGLE) {
|
||||
// TODO: Implement
|
||||
|
@ -197,16 +197,16 @@ function (connectedScript, inIndex, hashType)
|
|||
|
||||
// Blank out other inputs completely, not recommended for open transactions
|
||||
if (hashType & SIGHASH_ANYONECANPAY) {
|
||||
txTmp.ins = [txTmp.ins[inIndex]];
|
||||
txTmp.ins = [txTmp.ins[inIndex]]
|
||||
}
|
||||
|
||||
var buffer = txTmp.serialize();
|
||||
var buffer = txTmp.serialize()
|
||||
|
||||
buffer = buffer.concat(convert.numToBytes(parseInt(hashType), 4));
|
||||
buffer = convert.bytesToWordArray(buffer);
|
||||
buffer = buffer.concat(convert.numToBytes(parseInt(hashType), 4))
|
||||
buffer = convert.bytesToWordArray(buffer)
|
||||
|
||||
return convert.wordArrayToBytes(SHA256(SHA256(buffer)));
|
||||
};
|
||||
return convert.wordArrayToBytes(SHA256(SHA256(buffer)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and return the transaction's hash.
|
||||
|
@ -215,166 +215,157 @@ function (connectedScript, inIndex, hashType)
|
|||
*/
|
||||
Transaction.prototype.getHash = function ()
|
||||
{
|
||||
var buffer = convert.bytesToWordArray(this.serialize());
|
||||
return convert.wordArrayToBytes(SHA256(SHA256(buffer))).reverse();
|
||||
};
|
||||
var buffer = convert.bytesToWordArray(this.serialize())
|
||||
return convert.wordArrayToBytes(SHA256(SHA256(buffer))).reverse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of this transaction object.
|
||||
*/
|
||||
Transaction.prototype.clone = function ()
|
||||
{
|
||||
var newTx = new Transaction();
|
||||
newTx.version = this.version;
|
||||
newTx.locktime = this.locktime;
|
||||
var newTx = new Transaction()
|
||||
newTx.version = this.version
|
||||
newTx.locktime = this.locktime
|
||||
|
||||
this.ins.forEach(function(txin) {
|
||||
newTx.addInput(txin.clone());
|
||||
});
|
||||
newTx.addInput(txin.clone())
|
||||
})
|
||||
|
||||
this.outs.forEach(function(txout) {
|
||||
newTx.addOutput(txout.clone());
|
||||
});
|
||||
newTx.addOutput(txout.clone())
|
||||
})
|
||||
|
||||
return newTx;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a serialized transaction into a transaction object
|
||||
*/
|
||||
return newTx
|
||||
}
|
||||
|
||||
Transaction.deserialize = function(buffer) {
|
||||
if (typeof buffer == "string") {
|
||||
buffer = convert.hexToBytes(buffer)
|
||||
}
|
||||
var pos = 0;
|
||||
var readAsInt = function(bytes) {
|
||||
if (bytes === 0) return 0;
|
||||
pos++;
|
||||
return buffer[pos-1] + readAsInt(bytes-1) * 256;
|
||||
}
|
||||
var readVarInt = function() {
|
||||
var bytes = buffer.slice(pos, pos + 9) // maximum possible number of bytes to read
|
||||
var result = convert.varIntToNum(bytes)
|
||||
if (typeof buffer == "string") {
|
||||
buffer = convert.hexToBytes(buffer)
|
||||
}
|
||||
var pos = 0
|
||||
var readAsInt = function(bytes) {
|
||||
if (bytes === 0) return 0;
|
||||
pos++;
|
||||
return buffer[pos-1] + readAsInt(bytes-1) * 256
|
||||
}
|
||||
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
|
||||
}
|
||||
var readBytes = function(bytes) {
|
||||
pos += bytes;
|
||||
return buffer.slice(pos - bytes, pos);
|
||||
}
|
||||
var readVarString = function() {
|
||||
var size = readVarInt();
|
||||
return readBytes(size);
|
||||
}
|
||||
var obj = {
|
||||
ins: [],
|
||||
outs: []
|
||||
}
|
||||
obj.version = readAsInt(4);
|
||||
var ins = readVarInt();
|
||||
var i;
|
||||
pos += result.bytes.length
|
||||
return result.number
|
||||
}
|
||||
var readBytes = function(bytes) {
|
||||
pos += bytes
|
||||
return buffer.slice(pos - bytes, pos)
|
||||
}
|
||||
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 outs = readVarInt();
|
||||
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 outs = readVarInt()
|
||||
|
||||
for (i = 0; i < outs; i++) {
|
||||
obj.outs.push({
|
||||
value: convert.bytesToNum(readBytes(8)),
|
||||
script: new Script(readVarString())
|
||||
});
|
||||
}
|
||||
for (i = 0; i < outs; i++) {
|
||||
obj.outs.push({
|
||||
value: convert.bytesToNum(readBytes(8)),
|
||||
script: new Script(readVarString())
|
||||
})
|
||||
}
|
||||
|
||||
obj.locktime = readAsInt(4);
|
||||
obj.locktime = readAsInt(4)
|
||||
|
||||
return new Transaction(obj);
|
||||
return new Transaction(obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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);
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Takes outputs of the form [{ output: 'txhash:index', address: 'address' },...]
|
||||
Transaction.prototype.signWithKeys = function(keys, outputs, type) {
|
||||
type = type || SIGHASH_ALL;
|
||||
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);
|
||||
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
|
||||
*/
|
||||
|
||||
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;
|
||||
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.multisign = Transaction.prototype.p2shsign;
|
||||
Transaction.prototype.multisign = Transaction.prototype.p2shsign
|
||||
|
||||
Transaction.prototype.applyMultisigs = function(index, script, sigs/*, type*/) {
|
||||
this.ins[index].script = Script.createMultiSigInputScript(sigs, script);
|
||||
this.ins[index].script = Script.createMultiSigInputScript(sigs, script)
|
||||
}
|
||||
|
||||
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));
|
||||
script = new Script(script)
|
||||
var hash = this.hashTransactionForSignature(script,index,1)
|
||||
return ECDSA.verify(hash, convert.coerceToBytes(sig),
|
||||
convert.coerceToBytes(pub))
|
||||
}
|
||||
|
||||
Transaction.feePerKb = 20000
|
||||
|
@ -383,66 +374,68 @@ Transaction.prototype.estimateFee = function(feePerKb){
|
|||
var outSize = 34
|
||||
var fixedPadding = 34
|
||||
|
||||
if(feePerKb == undefined) feePerKb = Transaction.feePerKb
|
||||
if(feePerKb == undefined) feePerKb = Transaction.feePerKb;
|
||||
var size = this.ins.length * uncompressedInSize + this.outs.length * outSize + fixedPadding
|
||||
|
||||
return feePerKb * Math.ceil(size / 1000)
|
||||
}
|
||||
|
||||
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 (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)
|
||||
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
|
||||
};
|
||||
this.sequence = data.sequence || this.defaultSequence
|
||||
}
|
||||
|
||||
TransactionIn.prototype.clone = function () {
|
||||
return new TransactionIn({
|
||||
outpoint: {
|
||||
hash: this.outpoint.hash,
|
||||
index: this.outpoint.index
|
||||
},
|
||||
script: this.script.clone(),
|
||||
sequence: this.sequence
|
||||
});
|
||||
};
|
||||
return new TransactionIn({
|
||||
outpoint: {
|
||||
hash: this.outpoint.hash,
|
||||
index: this.outpoint.index
|
||||
},
|
||||
script: this.script.clone(),
|
||||
sequence: this.sequence
|
||||
})
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
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;
|
||||
};
|
||||
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
|
||||
}
|
||||
|
||||
TransactionOut.prototype.clone = function() {
|
||||
var newTxout = new TransactionOut({
|
||||
script: this.script.clone(),
|
||||
value: this.value
|
||||
});
|
||||
return newTxout;
|
||||
};
|
||||
})
|
||||
return newTxout
|
||||
}
|
||||
|
||||
TransactionOut.prototype.scriptPubKey = function() {
|
||||
return convert.bytesToHex(this.script.buffer)
|
||||
|
|
10
src/util.js
10
src/util.js
|
@ -3,21 +3,11 @@ var Crypto = require('crypto-js')
|
|||
var RIPEMD160 = Crypto.RIPEMD160
|
||||
var SHA256 = Crypto.SHA256
|
||||
|
||||
/**
|
||||
* Calculate RIPEMD160(SHA256(data)).
|
||||
*
|
||||
* Takes an arbitrary byte array as inputs and returns the hash as a byte
|
||||
* array.
|
||||
*/
|
||||
exports.sha256ripe160 = function (data) {
|
||||
var wordArray = RIPEMD160(SHA256(convert.bytesToWordArray(data)))
|
||||
return convert.wordArrayToBytes(wordArray)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for throw new Error('some message'), e.g.
|
||||
* error('something went wrong')
|
||||
*/
|
||||
exports.error = function (msg) {
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
|
544
src/wallet.js
544
src/wallet.js
|
@ -1,311 +1,311 @@
|
|||
var convert = require('./convert');
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var convert = require('./convert')
|
||||
var Transaction = require('./transaction').Transaction
|
||||
var HDNode = require('./hdwallet.js')
|
||||
var rng = require('secure-random');
|
||||
var rng = require('secure-random')
|
||||
|
||||
var Wallet = function (seed, options) {
|
||||
if (!(this instanceof Wallet)) { return new Wallet(seed, options); }
|
||||
if (!(this instanceof Wallet)) { return new Wallet(seed, options); }
|
||||
|
||||
var options = options || {}
|
||||
var network = options.network || 'mainnet'
|
||||
var options = options || {}
|
||||
var network = options.network || 'mainnet'
|
||||
|
||||
// Stored in a closure to make accidental serialization less likely
|
||||
var masterkey = null;
|
||||
var me = this;
|
||||
var accountZero = null;
|
||||
var internalAccount = null;
|
||||
var externalAccount = null;
|
||||
// Stored in a closure to make accidental serialization less likely
|
||||
var masterkey = null
|
||||
var me = this
|
||||
var accountZero = null
|
||||
var internalAccount = null
|
||||
var externalAccount = null
|
||||
|
||||
// Addresses
|
||||
this.addresses = [];
|
||||
this.changeAddresses = [];
|
||||
// Addresses
|
||||
this.addresses = []
|
||||
this.changeAddresses = []
|
||||
|
||||
// Transaction output data
|
||||
this.outputs = {};
|
||||
// Transaction output data
|
||||
this.outputs = {}
|
||||
|
||||
// Make a new master key
|
||||
this.newMasterKey = function(seed, network) {
|
||||
if (!seed) seed= rng(32, { array: true })
|
||||
masterkey = new HDNode(seed, network);
|
||||
// Make a new master key
|
||||
this.newMasterKey = function(seed, network) {
|
||||
if (!seed) seed = rng(32, { array: true });
|
||||
masterkey = new HDNode(seed, network)
|
||||
|
||||
// HD first-level child derivation method should be private
|
||||
// See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254
|
||||
accountZero = masterkey.derivePrivate(0)
|
||||
externalAccount = accountZero.derive(0)
|
||||
internalAccount = accountZero.derive(1)
|
||||
// HD first-level child derivation method should be private
|
||||
// See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254
|
||||
accountZero = masterkey.derivePrivate(0)
|
||||
externalAccount = accountZero.derive(0)
|
||||
internalAccount = accountZero.derive(1)
|
||||
|
||||
me.addresses = [];
|
||||
me.changeAddresses = [];
|
||||
me.addresses = []
|
||||
me.changeAddresses = []
|
||||
|
||||
me.outputs = {};
|
||||
}
|
||||
this.newMasterKey(seed, network)
|
||||
me.outputs = {}
|
||||
}
|
||||
this.newMasterKey(seed, network)
|
||||
|
||||
|
||||
this.generateAddress = function() {
|
||||
var key = externalAccount.derive(this.addresses.length)
|
||||
this.addresses.push(key.getAddress().toString())
|
||||
return this.addresses[this.addresses.length - 1]
|
||||
this.generateAddress = function() {
|
||||
var key = externalAccount.derive(this.addresses.length)
|
||||
this.addresses.push(key.getAddress().toString())
|
||||
return this.addresses[this.addresses.length - 1]
|
||||
}
|
||||
|
||||
this.generateChangeAddress = function() {
|
||||
var key = internalAccount.derive(this.changeAddresses.length)
|
||||
this.changeAddresses.push(key.getAddress().toString())
|
||||
return this.changeAddresses[this.changeAddresses.length - 1]
|
||||
}
|
||||
|
||||
this.getBalance = function() {
|
||||
return this.getUnspentOutputs().reduce(function(memo, output){
|
||||
return memo + output.value
|
||||
}, 0)
|
||||
}
|
||||
|
||||
this.getUnspentOutputs = function() {
|
||||
var utxo = []
|
||||
|
||||
for(var key in this.outputs){
|
||||
var output = this.outputs[key]
|
||||
if(!output.spend) utxo.push(outputToUnspentOutput(output))
|
||||
}
|
||||
|
||||
this.generateChangeAddress = function() {
|
||||
var key = internalAccount.derive(this.changeAddresses.length)
|
||||
this.changeAddresses.push(key.getAddress().toString())
|
||||
return this.changeAddresses[this.changeAddresses.length - 1]
|
||||
return utxo
|
||||
}
|
||||
|
||||
this.setUnspentOutputs = function(utxo) {
|
||||
var outputs = {}
|
||||
|
||||
utxo.forEach(function(uo){
|
||||
validateUnspentOutput(uo)
|
||||
var o = unspentOutputToOutput(uo)
|
||||
outputs[o.receive] = o
|
||||
})
|
||||
|
||||
this.outputs = outputs
|
||||
}
|
||||
|
||||
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(":")
|
||||
|
||||
return {
|
||||
hash: hashAndIndex[0],
|
||||
hashLittleEndian: convert.reverseEndian(hashAndIndex[0]),
|
||||
outputIndex: parseInt(hashAndIndex[1]),
|
||||
address: output.address,
|
||||
value: output.value
|
||||
}
|
||||
}
|
||||
|
||||
function unspentOutputToOutput(o) {
|
||||
var hash = o.hash || convert.reverseEndian(o.hashLittleEndian)
|
||||
var key = hash + ":" + o.outputIndex
|
||||
return {
|
||||
receive: key,
|
||||
address: o.address,
|
||||
value: o.value
|
||||
}
|
||||
}
|
||||
|
||||
function validateUnspentOutput(uo) {
|
||||
var missingField
|
||||
|
||||
if(isNullOrUndefined(uo.hash) && isNullOrUndefined(uo.hashLittleEndian)){
|
||||
missingField = "hash(or hashLittleEndian)"
|
||||
}
|
||||
|
||||
this.getBalance = function() {
|
||||
return this.getUnspentOutputs().reduce(function(memo, output){
|
||||
return memo + output.value
|
||||
}, 0)
|
||||
}
|
||||
|
||||
this.getUnspentOutputs = function() {
|
||||
var utxo = []
|
||||
|
||||
for(var key in this.outputs){
|
||||
var output = this.outputs[key]
|
||||
if(!output.spend) utxo.push(outputToUnspentOutput(output))
|
||||
var requiredKeys = ['outputIndex', 'address', 'value']
|
||||
requiredKeys.forEach(function(key){
|
||||
if(isNullOrUndefined(uo[key])){
|
||||
missingField = key
|
||||
}
|
||||
})
|
||||
|
||||
return utxo
|
||||
if(missingField) {
|
||||
var message = [
|
||||
'Invalid unspent output: key', field, 'is missing.',
|
||||
'A valid unspent output must contain'
|
||||
]
|
||||
message.push(requiredKeys.join(', '))
|
||||
message.push("and hash(or hashLittleEndian)")
|
||||
throw new Error(message.join(' '))
|
||||
}
|
||||
}
|
||||
|
||||
this.setUnspentOutputs = function(utxo) {
|
||||
var outputs = {}
|
||||
function isNullOrUndefined(value){
|
||||
return value == undefined
|
||||
}
|
||||
|
||||
utxo.forEach(function(uo){
|
||||
validateUnspentOutput(uo)
|
||||
var o = unspentOutputToOutput(uo)
|
||||
outputs[o.receive] = o
|
||||
})
|
||||
this.processTx = function(tx) {
|
||||
var txhash = convert.bytesToHex(tx.getHash())
|
||||
|
||||
this.outputs = outputs
|
||||
}
|
||||
|
||||
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(":")
|
||||
|
||||
return {
|
||||
hash: hashAndIndex[0],
|
||||
hashLittleEndian: convert.reverseEndian(hashAndIndex[0]),
|
||||
outputIndex: parseInt(hashAndIndex[1]),
|
||||
address: output.address,
|
||||
value: output.value
|
||||
}
|
||||
}
|
||||
|
||||
function unspentOutputToOutput(o) {
|
||||
var hash = o.hash || convert.reverseEndian(o.hashLittleEndian)
|
||||
var key = hash + ":" + o.outputIndex
|
||||
return {
|
||||
receive: key,
|
||||
address: o.address,
|
||||
value: o.value
|
||||
}
|
||||
}
|
||||
|
||||
function validateUnspentOutput(uo) {
|
||||
var missingField;
|
||||
|
||||
if(isNullOrUndefined(uo.hash) && isNullOrUndefined(uo.hashLittleEndian)){
|
||||
missingField = "hash(or hashLittleEndian)"
|
||||
}
|
||||
|
||||
var requiredKeys = ['outputIndex', 'address', 'value']
|
||||
requiredKeys.forEach(function(key){
|
||||
if(isNullOrUndefined(uo[key])){
|
||||
missingField = key
|
||||
tx.outs.forEach(function(txOut, i){
|
||||
var address = txOut.address.toString()
|
||||
if (isMyAddress(address)) {
|
||||
var output = txhash+':'+i
|
||||
me.outputs[output] = {
|
||||
receive: output,
|
||||
value: txOut.value,
|
||||
address: address,
|
||||
}
|
||||
})
|
||||
|
||||
if(missingField) {
|
||||
var message = [
|
||||
'Invalid unspent output: key', field, 'is missing.',
|
||||
'A valid unspent output must contain'
|
||||
]
|
||||
message.push(requiredKeys.join(', '))
|
||||
message.push("and hash(or hashLittleEndian)")
|
||||
throw new Error(message.join(' '))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function isNullOrUndefined(value){
|
||||
return value == undefined
|
||||
}
|
||||
|
||||
this.processTx = function(tx) {
|
||||
var txhash = convert.bytesToHex(tx.getHash())
|
||||
|
||||
tx.outs.forEach(function(txOut, i){
|
||||
var address = txOut.address.toString()
|
||||
if (isMyAddress(address)) {
|
||||
var output = txhash+':'+i
|
||||
me.outputs[output] = {
|
||||
receive: output,
|
||||
value: txOut.value,
|
||||
address: address,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tx.ins.forEach(function(txIn, i){
|
||||
var op = txIn.outpoint
|
||||
var o = me.outputs[op.hash+':'+op.index]
|
||||
if (o) {
|
||||
o.spend = txhash+':'+i
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.createTx = function(to, value, fixedFee) {
|
||||
checkDust(value)
|
||||
|
||||
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)
|
||||
|
||||
totalInValue += output.value
|
||||
if(totalInValue < value) continue;
|
||||
|
||||
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)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
checkInsufficientFund(totalInValue, value, fee)
|
||||
|
||||
this.sign(tx)
|
||||
|
||||
return tx
|
||||
}
|
||||
|
||||
this.createTxAsync = function(to, value, fixedFee, callback){
|
||||
if(fixedFee instanceof Function) {
|
||||
callback = fixedFee
|
||||
fixedFee = undefined
|
||||
tx.ins.forEach(function(txIn, i){
|
||||
var op = txIn.outpoint
|
||||
var o = me.outputs[op.hash+':'+op.index]
|
||||
if (o) {
|
||||
o.spend = txhash+':'+i
|
||||
}
|
||||
var tx = null
|
||||
var error = null
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
tx = this.createTx(to, value, fixedFee)
|
||||
} catch(err) {
|
||||
error = err
|
||||
} finally {
|
||||
process.nextTick(function(){ callback(error, tx) })
|
||||
this.createTx = function(to, value, fixedFee) {
|
||||
checkDust(value)
|
||||
|
||||
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)
|
||||
|
||||
totalInValue += output.value
|
||||
if(totalInValue < value) continue
|
||||
|
||||
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)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
this.dustThreshold = 5430
|
||||
function isDust(amount) {
|
||||
return amount <= me.dustThreshold
|
||||
checkInsufficientFund(totalInValue, value, fee)
|
||||
|
||||
this.sign(tx)
|
||||
|
||||
return tx
|
||||
}
|
||||
|
||||
this.createTxAsync = function(to, value, fixedFee, callback){
|
||||
if(fixedFee instanceof Function) {
|
||||
callback = fixedFee
|
||||
fixedFee = undefined
|
||||
}
|
||||
var tx = null
|
||||
var error = null
|
||||
|
||||
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){
|
||||
var output = me.outputs[key]
|
||||
if(!output.spend) unspent.push(output)
|
||||
}
|
||||
|
||||
function checkDust(value){
|
||||
if (isNullOrUndefined(value) || isDust(value)) {
|
||||
throw new Error("Value must be above dust threshold")
|
||||
var sortByValueDesc = unspent.sort(function(o1, o2){
|
||||
return o2.value - o1.value
|
||||
})
|
||||
|
||||
return sortByValueDesc
|
||||
}
|
||||
|
||||
function estimateFeePadChangeOutput(tx){
|
||||
var tmpTx = tx.clone()
|
||||
tmpTx.addOutput(getChangeAddress(), 0)
|
||||
return tmpTx.estimateFee()
|
||||
}
|
||||
|
||||
function getChangeAddress() {
|
||||
if(me.changeAddresses.length === 0) me.generateChangeAddress();
|
||||
return me.changeAddresses[me.changeAddresses.length - 1]
|
||||
}
|
||||
|
||||
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.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))
|
||||
}
|
||||
})
|
||||
return tx
|
||||
}
|
||||
|
||||
this.getMasterKey = function() { return masterkey }
|
||||
this.getAccountZero = function() { return accountZero }
|
||||
this.getInternalAccount = function() { return internalAccount }
|
||||
this.getExternalAccount = function() { return externalAccount }
|
||||
|
||||
this.getPrivateKey = function(index) {
|
||||
return externalAccount.derive(index).priv
|
||||
}
|
||||
|
||||
this.getInternalPrivateKey = function(index) {
|
||||
return internalAccount.derive(index).priv
|
||||
}
|
||||
|
||||
this.getPrivateKeyForAddress = function(address) {
|
||||
var index
|
||||
if((index = this.addresses.indexOf(address)) > -1) {
|
||||
return this.getPrivateKey(index)
|
||||
} else if((index = this.changeAddresses.indexOf(address)) > -1) {
|
||||
return this.getInternalPrivateKey(index)
|
||||
} else {
|
||||
throw new Error('Unknown address. Make sure the address is from the keychain and has been generated.')
|
||||
}
|
||||
}
|
||||
|
||||
function getCandidateOutputs(value){
|
||||
var unspent = []
|
||||
for (var key in me.outputs){
|
||||
var output = me.outputs[key]
|
||||
if(!output.spend) unspent.push(output)
|
||||
}
|
||||
function isReceiveAddress(address){
|
||||
return me.addresses.indexOf(address) > -1
|
||||
}
|
||||
|
||||
var sortByValueDesc = unspent.sort(function(o1, o2){
|
||||
return o2.value - o1.value
|
||||
})
|
||||
function isChangeAddress(address){
|
||||
return me.changeAddresses.indexOf(address) > -1
|
||||
}
|
||||
|
||||
return sortByValueDesc;
|
||||
}
|
||||
function isMyAddress(address) {
|
||||
return isReceiveAddress(address) || isChangeAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
function estimateFeePadChangeOutput(tx){
|
||||
var tmpTx = tx.clone()
|
||||
tmpTx.addOutput(getChangeAddress(), 0)
|
||||
return tmpTx.estimateFee()
|
||||
}
|
||||
|
||||
function getChangeAddress() {
|
||||
if(me.changeAddresses.length === 0) me.generateChangeAddress()
|
||||
return me.changeAddresses[me.changeAddresses.length - 1]
|
||||
}
|
||||
|
||||
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.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))
|
||||
}
|
||||
})
|
||||
return tx;
|
||||
}
|
||||
|
||||
this.getMasterKey = function() { return masterkey }
|
||||
this.getAccountZero = function() { return accountZero }
|
||||
this.getInternalAccount = function() { return internalAccount }
|
||||
this.getExternalAccount = function() { return externalAccount }
|
||||
|
||||
this.getPrivateKey = function(index) {
|
||||
return externalAccount.derive(index).priv
|
||||
}
|
||||
|
||||
this.getInternalPrivateKey = function(index) {
|
||||
return internalAccount.derive(index).priv
|
||||
}
|
||||
|
||||
this.getPrivateKeyForAddress = function(address) {
|
||||
var index;
|
||||
if((index = this.addresses.indexOf(address)) > -1) {
|
||||
return this.getPrivateKey(index)
|
||||
} else if((index = this.changeAddresses.indexOf(address)) > -1) {
|
||||
return this.getInternalPrivateKey(index)
|
||||
} else {
|
||||
throw new Error('Unknown address. Make sure the address is from the keychain and has been generated.')
|
||||
}
|
||||
}
|
||||
|
||||
function isReceiveAddress(address){
|
||||
return me.addresses.indexOf(address) > -1
|
||||
}
|
||||
|
||||
function isChangeAddress(address){
|
||||
return me.changeAddresses.indexOf(address) > -1
|
||||
}
|
||||
|
||||
function isMyAddress(address) {
|
||||
return isReceiveAddress(address) || isChangeAddress(address)
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Wallet;
|
||||
module.exports = Wallet
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue