diff --git a/package.json b/package.json index 779378b..1ada12a 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "bitcoinjs-lib", "version": "0.1.3", "description": "Client-side Bitcoin JavaScript library", + "main": "./src/index.js", "keywords": [ "bitcoin", @@ -18,7 +19,10 @@ }, "devDependencies" : { - "pkginfo" : ">=0.2.1", - "jake-uglify" : ">=1.0.0" + "mocha": "1.8.1" + }, + + "scripts": { + "test": "mocha test/*.js" } } diff --git a/src/address.js b/src/address.js index 3013764..e793943 100644 --- a/src/address.js +++ b/src/address.js @@ -1,6 +1,20 @@ -Bitcoin.Address = function (bytes) { - if ("string" == typeof bytes) { - bytes = Bitcoin.Address.decodeString(bytes); +var base58 = require('./base58'); +var Crypto = require('./crypto-js/crypto'); +var conv = require('./convert'); + +var address_types = { + prod: 0, + testnet: 111 +}; + +var p2sh_types = { + prod: 5, + testnet: 196 +}; + +var Address = function (bytes) { + if (typeof bytes === 'string') { + bytes = Address.decodeString(bytes); } this.hash = bytes; @@ -12,7 +26,7 @@ Bitcoin.Address = function (bytes) { * * Returns the address as a base58-encoded string in the standardized format. */ -Bitcoin.Address.prototype.toString = function () { +Address.prototype.toString = function () { // Get a copy of the hash var hash = this.hash.slice(0); @@ -23,18 +37,46 @@ Bitcoin.Address.prototype.toString = function () { var bytes = hash.concat(checksum.slice(0,4)); - return Bitcoin.Base58.encode(bytes); + return base58.encode(bytes); +}; + +Address.prototype.getHashBase64 = function () { + return conv.bytesToBase64(this.hash); }; -Bitcoin.Address.prototype.getHashBase64 = function () { - return Crypto.util.bytesToBase64(this.hash); +// TODO(shtylman) isValid? +Address.validate = function(string, type) { + try { + var bytes = base58.decode(string); + } catch (e) { + return false; + } + + var hash = bytes.slice(0, 21); + + var checksum = Crypto.SHA256(Crypto.SHA256(hash, {asBytes: true}), {asBytes: true}); + + if (checksum[0] != bytes[21] || + checksum[1] != bytes[22] || + checksum[2] != bytes[23] || + checksum[3] != bytes[24]) { + return false; + } + + var version = hash[0]; + + if (type && version !== address_types[type] && version !== p2sh_types[type]) { + return false; + } + + return true; }; /** * Parse a Bitcoin address contained in a string. */ -Bitcoin.Address.decodeString = function (string) { - var bytes = Bitcoin.Base58.decode(string); +Address.decodeString = function (string) { + var bytes = base58.decode(string); var hash = bytes.slice(0, 21); @@ -44,14 +86,16 @@ Bitcoin.Address.decodeString = function (string) { checksum[1] != bytes[22] || checksum[2] != bytes[23] || checksum[3] != bytes[24]) { - throw "Checksum validation failed!"; + throw new Error('Address Checksum validation failed: ' + string); } var version = hash.shift(); - + // TODO(shtylman) allow for specific version decoding same as validate above if (version != 0) { - throw "Version "+version+" not supported!"; + throw new Error('Address version not supported: ' + string); } return hash; }; + +module.exports = Address; diff --git a/src/base58.js b/src/base58.js index 9313c55..6729c62 100644 --- a/src/base58.js +++ b/src/base58.js @@ -1,71 +1,78 @@ -(function (Bitcoin) { - Bitcoin.Base58 = { - alphabet: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", - validRegex: /^[1-9A-HJ-NP-Za-km-z]+$/, - base: BigInteger.valueOf(58), - - /** - * Convert a byte array to a base58-encoded string. - * - * Written by Mike Hearn for BitcoinJ. - * Copyright (c) 2011 Google Inc. - * - * Ported to JavaScript by Stefan Thomas. - */ - encode: function (input) { - var bi = BigInteger.fromByteArrayUnsigned(input); - var chars = []; - - while (bi.compareTo(B58.base) >= 0) { - var mod = bi.mod(B58.base); - chars.unshift(B58.alphabet[mod.intValue()]); - bi = bi.subtract(mod).divide(B58.base); - } - chars.unshift(B58.alphabet[bi.intValue()]); - // Convert leading zeros too. - for (var i = 0; i < input.length; i++) { +// https://en.bitcoin.it/wiki/Base58Check_encoding + +var BigInteger = require('./jsbn/jsbn'); + +var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +var base = BigInteger.valueOf(58); + +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. +// Ported to JavaScript by Stefan Thomas. +module.exports.encode = function (input) { + 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); + } + chars.push(alphabet[bi.intValue()]); + + // Convert leading zeros too. + for (var i = 0; i < input.length; i++) { if (input[i] == 0x00) { - chars.unshift(B58.alphabet[0]); + chars.push(alphabet[0]); } else break; + } + + return chars.reverse().join(''); +}, + +// decode a base58 string into a byte array +// input should be a base58 encoded string +// @return Array +module.exports.decode = function (input) { + + 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= 0; i--) { - var alphaIndex = B58.alphabet.indexOf(input[i]); - if (alphaIndex < 0) { - throw "Invalid character"; - } - bi = bi.add(BigInteger.valueOf(alphaIndex) - .multiply(B58.base.pow(input.length - 1 -i))); - - // This counts leading zero bytes - if (input[i] == "1") leadingZerosNum++; - else leadingZerosNum = 0; + num = num.multiply(base).add(BigInteger.valueOf(p)); + + if (char == '1' && !seen_other) { + ++leading_zero; + } + else { + seen_other = true; } - var bytes = bi.toByteArrayUnsigned(); + } - // Add leading zeros - while (leadingZerosNum-- > 0) bytes.unshift(0); + var bytes = num.toByteArrayUnsigned(); - return bytes; - } - }; + // remove leading zeros + while (leading_zero-- > 0) { + bytes.unshift(0); + } + + return bytes; +} - var B58 = Bitcoin.Base58; -})( - 'undefined' != typeof Bitcoin ? Bitcoin : module.exports -); diff --git a/src/bitcoin.js b/src/bitcoin.js old mode 100755 new mode 100644 index e14d36f..3bbf6cd --- a/src/bitcoin.js +++ b/src/bitcoin.js @@ -1,13 +1,3 @@ -(function (exports) { - var Bitcoin = exports; - - if ('object' !== typeof module) { - Bitcoin.EventEmitter = EventEmitter; - } -})( - 'object' === typeof module ? module.exports : (window.Bitcoin = {}) -); - /* function makeKeypair() { diff --git a/src/convert.js b/src/convert.js new file mode 100644 index 0000000..37e634d --- /dev/null +++ b/src/convert.js @@ -0,0 +1,59 @@ +// convert to/from various values + +var base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Convert a byte array to a hex string +module.exports.bytesToHex = function(bytes) { + for (var hex = [], i = 0; i < bytes.length; i++) { + hex.push((bytes[i] >>> 4).toString(16)); + hex.push((bytes[i] & 0xF).toString(16)); + } + return hex.join(""); +}; + +// Convert a hex string to a byte array +module.exports.hexToBytes = function(hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) + bytes.push(parseInt(hex.substr(c, 2), 16)); + return bytes; +} + + // Convert a byte array to a base-64 string +module.exports.bytesToBase64 = function(bytes) { + // Use browser-native function if it exists + if (typeof btoa == "function") return btoa(Binary.bytesToString(bytes)); + + for(var base64 = [], i = 0; i < bytes.length; i += 3) { + var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; + for (var j = 0; j < 4; j++) { + if (i * 8 + j * 6 <= bytes.length * 8) + base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F)); + else base64.push("="); + } + } + + return base64.join(""); +} + + + // Convert a base-64 string to a byte array +module.exports.base64ToBytes = function(base64) { + // Use browser-native function if it exists + if (typeof atob == "function") return Binary.stringToBytes(atob(base64)); + + // Remove non-base-64 characters + base64 = base64.replace(/[^A-Z0-9+\/]/ig, ""); + + for (var bytes = [], i = 0, imod4 = 0; i < base64.length; imod4 = ++i % 4) { + if (imod4 == 0) continue; + bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2)) | + (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2))); + } + + return bytes; +} + +// utf8 and binary? +//stringToBytes +//bytesToString + diff --git a/src/crypto-js/crypto-min.js b/src/crypto-js/crypto-min.js deleted file mode 100755 index dc5b80d..0000000 --- a/src/crypto-js/crypto-min.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Crypto-JS v2.0.0 - * http://code.google.com/p/crypto-js/ - * Copyright (c) 2009, Jeff Mott. All rights reserved. - * http://code.google.com/p/crypto-js/wiki/License - */ -(function(){var c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var d=window.Crypto={};var a=d.util={rotl:function(h,g){return(h<>>(32-g))},rotr:function(h,g){return(h<<(32-g))|(h>>>g)},endian:function(h){if(h.constructor==Number){return a.rotl(h,8)&16711935|a.rotl(h,24)&4278255360}for(var g=0;g0;h--){g.push(Math.floor(Math.random()*256))}return g},bytesToWords:function(h){for(var k=[],j=0,g=0;j>>5]|=h[j]<<(24-g%32)}return k},wordsToBytes:function(i){for(var h=[],g=0;g>>5]>>>(24-g%32))&255)}return h},bytesToHex:function(g){for(var j=[],h=0;h>>4).toString(16));j.push((g[h]&15).toString(16))}return j.join("")},hexToBytes:function(h){for(var g=[],i=0;i>>6*(3-k))&63))}else{g.push("=")}}}return g.join("")},base64ToBytes:function(h){if(typeof atob=="function"){return e.stringToBytes(atob(h))}h=h.replace(/[^A-Z0-9+\/]/ig,"");for(var g=[],j=0,k=0;j>>(6-k*2)))}return g}};d.mode={};var b=d.charenc={};var f=b.UTF8={stringToBytes:function(g){return e.stringToBytes(unescape(encodeURIComponent(g)))},bytesToString:function(g){return decodeURIComponent(escape(e.bytesToString(g)))}};var e=b.Binary={stringToBytes:function(j){for(var g=[],h=0;h>> (32 - b)); - }, - - // Bit-wise rotate right - rotr: function (n, b) { - return (n << (32 - b)) | (n >>> b); - }, - - // Swap big-endian to little-endian and vice versa - endian: function (n) { - - // If number given, swap endian - if (n.constructor == Number) { - return util.rotl(n, 8) & 0x00FF00FF | - util.rotl(n, 24) & 0xFF00FF00; - } - - // Else, assume array and swap all items - for (var i = 0; i < n.length; i++) - n[i] = util.endian(n[i]); - return n; - - }, - - // Generate an array of any length of random bytes - randomBytes: function (n) { - for (var bytes = []; n > 0; n--) - bytes.push(Math.floor(Math.random() * 256)); - return bytes; - }, - - // Convert a byte array to big-endian 32-bit words - bytesToWords: function (bytes) { - for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8) - words[b >>> 5] |= bytes[i] << (24 - b % 32); - return words; - }, - - // Convert big-endian 32-bit words to a byte array - wordsToBytes: function (words) { - for (var bytes = [], b = 0; b < words.length * 32; b += 8) - bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF); - return bytes; - }, - - // Convert a byte array to a hex string - bytesToHex: function (bytes) { - for (var hex = [], i = 0; i < bytes.length; i++) { - hex.push((bytes[i] >>> 4).toString(16)); - hex.push((bytes[i] & 0xF).toString(16)); - } - return hex.join(""); - }, - - // Convert a hex string to a byte array - hexToBytes: function (hex) { - for (var bytes = [], c = 0; c < hex.length; c += 2) - bytes.push(parseInt(hex.substr(c, 2), 16)); - return bytes; - }, - - // Convert a byte array to a base-64 string - bytesToBase64: function (bytes) { - - // Use browser-native function if it exists - if (typeof btoa == "function") return btoa(Binary.bytesToString(bytes)); - - for(var base64 = [], i = 0; i < bytes.length; i += 3) { - var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]; - for (var j = 0; j < 4; j++) { - if (i * 8 + j * 6 <= bytes.length * 8) - base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F)); - else base64.push("="); - } - } - - return base64.join(""); - - }, - - // Convert a base-64 string to a byte array - base64ToBytes: function (base64) { - - // Use browser-native function if it exists - if (typeof atob == "function") return Binary.stringToBytes(atob(base64)); - - // Remove non-base-64 characters - base64 = base64.replace(/[^A-Z0-9+\/]/ig, ""); - - for (var bytes = [], i = 0, imod4 = 0; i < base64.length; imod4 = ++i % 4) { - if (imod4 == 0) continue; - bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2)) | - (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2))); - } - - return bytes; - - } - -}; - -// Crypto mode namespace -Crypto.mode = {}; - -// Crypto character encodings -var charenc = Crypto.charenc = {}; - -// UTF-8 encoding -var UTF8 = charenc.UTF8 = { - - // Convert a string to a byte array - stringToBytes: function (str) { - return Binary.stringToBytes(unescape(encodeURIComponent(str))); - }, - - // Convert a byte array to a string - bytesToString: function (bytes) { - return decodeURIComponent(escape(Binary.bytesToString(bytes))); - } - -}; - -// Binary encoding -var Binary = charenc.Binary = { - - // Convert a string to a byte array - stringToBytes: function (str) { - for (var bytes = [], i = 0; i < str.length; i++) - bytes.push(str.charCodeAt(i)); - return bytes; - }, - - // Convert a byte array to a string - bytesToString: function (bytes) { - for (var str = [], i = 0; i < bytes.length; i++) - str.push(String.fromCharCode(bytes[i])); - return str.join(""); - } - -}; - -})(); +/*! + * Crypto-JS v2.0.0 + * http://code.google.com/p/crypto-js/ + * Copyright (c) 2009, Jeff Mott. All rights reserved. + * http://code.google.com/p/crypto-js/wiki/License + */ + +// Global Crypto object +var Crypto = module.exports = {}; + +// Crypto utilities +var util = Crypto.util = { + + // Generate an array of any length of random bytes + randomBytes: function (n) { + for (var bytes = []; n > 0; n--) + bytes.push(Math.floor(Math.random() * 256)); + return bytes; + }, + +}; + +// Crypto mode namespace +Crypto.mode = {}; + +// Crypto character encodings +var charenc = Crypto.charenc = {}; + +// UTF-8 encoding +var UTF8 = charenc.UTF8 = { + + // Convert a string to a byte array + stringToBytes: function (str) { + return Binary.stringToBytes(unescape(encodeURIComponent(str))); + }, + + // Convert a byte array to a string + bytesToString: function (bytes) { + return decodeURIComponent(escape(Binary.bytesToString(bytes))); + } + +}; + +// Binary encoding +var Binary = charenc.Binary = { + + // Convert a string to a byte array + stringToBytes: function (str) { + for (var bytes = [], i = 0; i < str.length; i++) + bytes.push(str.charCodeAt(i)); + return bytes; + }, + + // Convert a byte array to a string + bytesToString: function (bytes) { + for (var str = [], i = 0; i < bytes.length; i++) + str.push(String.fromCharCode(bytes[i])); + return str.join(""); + } + +}; + +module.exports.SHA256 = require('./sha256'); +module.exports.RIPEMD160 = require('./ripemd160'); diff --git a/src/crypto-js/ripemd160.js b/src/crypto-js/ripemd160.js old mode 100755 new mode 100644 index ff4b2a6..97a2a2f --- a/src/crypto-js/ripemd160.js +++ b/src/crypto-js/ripemd160.js @@ -13,7 +13,8 @@ * Ported to Crypto-JS by Stefan Thomas. */ -(function () { +var Crypto = require('./crypto'); + // Shortcuts var C = Crypto, util = C.util, @@ -22,7 +23,7 @@ Binary = charenc.Binary; // Convert a byte array to little-endian 32-bit words - util.bytesToLWords = function (bytes) { + var bytesToLWords = function (bytes) { var output = Array(bytes.length >> 2); for (var i = 0; i < output.length; i++) @@ -33,7 +34,7 @@ }; // Convert little-endian 32-bit words to a byte array - util.lWordsToBytes = function (words) { + var lWordsToBytes = function (words) { var output = []; for (var i = 0; i < words.length * 32; i += 8) output.push((words[i>>5] >>> (i % 32)) & 0xff); @@ -42,7 +43,7 @@ // Public API var RIPEMD160 = C.RIPEMD160 = function (message, options) { - var digestbytes = util.lWordsToBytes(RIPEMD160._rmd160(message)); + var digestbytes = lWordsToBytes(RIPEMD160._rmd160(message)); return options && options.asBytes ? digestbytes : options && options.asString ? Binary.bytesToString(digestbytes) : util.bytesToHex(digestbytes); @@ -54,7 +55,7 @@ // Convert to byte array if (message.constructor == String) message = UTF8.stringToBytes(message); - var x = util.bytesToLWords(message), + var x = bytesToLWords(message), len = message.length * 8; /* append padding */ @@ -167,4 +168,5 @@ { return (num << cnt) | (num >>> (32 - cnt)); } -})(); + + module.exports = RIPEMD160 diff --git a/src/crypto-js/sha256-min.js b/src/crypto-js/sha256-min.js deleted file mode 100755 index d3a4118..0000000 --- a/src/crypto-js/sha256-min.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Crypto-JS v2.0.0 - * http://code.google.com/p/crypto-js/ - * Copyright (c) 2009, Jeff Mott. All rights reserved. - * http://code.google.com/p/crypto-js/wiki/License - */ -(function(){var g=Crypto,b=g.util,c=g.charenc,f=c.UTF8,e=c.Binary;var a=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];var d=g.SHA256=function(j,h){var i=b.wordsToBytes(d._sha256(j));return h&&h.asBytes?i:h&&h.asString?e.bytesToString(i):b.bytesToHex(i)};d._sha256=function(q){if(q.constructor==String){q=f.stringToBytes(q)}var y=b.bytesToWords(q),z=q.length*8,r=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],s=[],K,J,I,G,F,E,D,C,B,A,p,o;y[z>>5]|=128<<(24-z%32);y[((z+64>>9)<<4)+15]=z;for(var B=0;B>>7))^((n<<14)|(n>>>18))^(n>>>3),L=((u<<15)|(u>>>17))^((u<<13)|(u>>>19))^(u>>>10);s[A]=M+(s[A-7]>>>0)+L+(s[A-16]>>>0)}var t=F&E^~F&D,k=K&J^K&I^J&I,x=((K<<30)|(K>>>2))^((K<<19)|(K>>>13))^((K<<10)|(K>>>22)),v=((F<<26)|(F>>>6))^((F<<21)|(F>>>11))^((F<<7)|(F>>>25));p=(C>>>0)+v+t+(a[A])+(s[A]>>>0);o=x+k;C=D;D=E;E=F;F=G+p;G=I;I=J;J=K;K=p+o}r[0]+=K;r[1]+=J;r[2]+=I;r[3]+=G;r[4]+=F;r[5]+=E;r[6]+=D;r[7]+=C}return r};d._blocksize=16})(); \ No newline at end of file diff --git a/src/crypto-js/sha256.js b/src/crypto-js/sha256.js old mode 100755 new mode 100644 index c5a2d01..aa850c3 --- a/src/crypto-js/sha256.js +++ b/src/crypto-js/sha256.js @@ -1,133 +1,148 @@ -/*! - * Crypto-JS v2.0.0 - * http://code.google.com/p/crypto-js/ - * Copyright (c) 2009, Jeff Mott. All rights reserved. - * http://code.google.com/p/crypto-js/wiki/License - */ -(function(){ - -// Shortcuts -var C = Crypto, - util = C.util, - charenc = C.charenc, - UTF8 = charenc.UTF8, - Binary = charenc.Binary; - -// Constants -var K = [ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, - 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, - 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, - 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, - 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, - 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, - 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, - 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, - 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, - 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, - 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, - 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, - 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, - 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, - 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, - 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 ]; - -// Public API -var SHA256 = C.SHA256 = function (message, options) { - var digestbytes = util.wordsToBytes(SHA256._sha256(message)); - return options && options.asBytes ? digestbytes : - options && options.asString ? Binary.bytesToString(digestbytes) : - util.bytesToHex(digestbytes); -}; - -// The core -SHA256._sha256 = function (message) { - - // Convert to byte array - if (message.constructor == String) message = UTF8.stringToBytes(message); - /* else, assume byte array already */ - - var m = util.bytesToWords(message), - l = message.length * 8, - H = [ 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, - 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 ], - w = [], - a, b, c, d, e, f, g, h, i, j, - t1, t2; - - // Padding - m[l >> 5] |= 0x80 << (24 - l % 32); - m[((l + 64 >> 9) << 4) + 15] = l; - - for (var i = 0; i < m.length; i += 16) { - - a = H[0]; - b = H[1]; - c = H[2]; - d = H[3]; - e = H[4]; - f = H[5]; - g = H[6]; - h = H[7]; - - for (var j = 0; j < 64; j++) { - - if (j < 16) w[j] = m[j + i]; - else { - - var gamma0x = w[j - 15], - gamma1x = w[j - 2], - gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ - ((gamma0x << 14) | (gamma0x >>> 18)) ^ - (gamma0x >>> 3), - gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ - ((gamma1x << 13) | (gamma1x >>> 19)) ^ - (gamma1x >>> 10); - - w[j] = gamma0 + (w[j - 7] >>> 0) + - gamma1 + (w[j - 16] >>> 0); - - } - - var ch = e & f ^ ~e & g, - maj = a & b ^ a & c ^ b & c, - sigma0 = ((a << 30) | (a >>> 2)) ^ - ((a << 19) | (a >>> 13)) ^ - ((a << 10) | (a >>> 22)), - sigma1 = ((e << 26) | (e >>> 6)) ^ - ((e << 21) | (e >>> 11)) ^ - ((e << 7) | (e >>> 25)); - - - t1 = (h >>> 0) + sigma1 + ch + (K[j]) + (w[j] >>> 0); - t2 = sigma0 + maj; - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - - } - - H[0] += a; - H[1] += b; - H[2] += c; - H[3] += d; - H[4] += e; - H[5] += f; - H[6] += g; - H[7] += h; - - } - - return H; - -}; - -// Package private blocksize -SHA256._blocksize = 16; - -})(); +/*! + * Crypto-JS v2.0.0 + * http://code.google.com/p/crypto-js/ + * Copyright (c) 2009, Jeff Mott. All rights reserved. + * http://code.google.com/p/crypto-js/wiki/License + */ + + // Convert a byte array to big-endian 32-bit words +var bytesToWords = function (bytes) { + for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8) + words[b >>> 5] |= bytes[i] << (24 - b % 32); + return words; +}; + + // Convert big-endian 32-bit words to a byte array +var wordsToBytes = function (words) { + for (var bytes = [], b = 0; b < words.length * 32; b += 8) + bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF); + return bytes; +}; + +var Crypto = require('./crypto'); + +// Shortcuts +var C = Crypto, + util = C.util, + charenc = C.charenc, + UTF8 = charenc.UTF8, + Binary = charenc.Binary; + +// Constants +var K = [ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 ]; + +// Public API +var SHA256 = C.SHA256 = function (message, options) { + var digestbytes = wordsToBytes(SHA256._sha256(message)); + return options && options.asBytes ? digestbytes : + options && options.asString ? Binary.bytesToString(digestbytes) : + util.bytesToHex(digestbytes); +}; + +// The core +SHA256._sha256 = function (message) { + + // Convert to byte array + if (message.constructor == String) message = UTF8.stringToBytes(message); + /* else, assume byte array already */ + + var m = bytesToWords(message), + l = message.length * 8, + H = [ 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 ], + w = [], + a, b, c, d, e, f, g, h, i, j, + t1, t2; + + // Padding + m[l >> 5] |= 0x80 << (24 - l % 32); + m[((l + 64 >> 9) << 4) + 15] = l; + + for (var i = 0; i < m.length; i += 16) { + + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + + for (var j = 0; j < 64; j++) { + + if (j < 16) w[j] = m[j + i]; + else { + + var gamma0x = w[j - 15], + gamma1x = w[j - 2], + gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ + ((gamma0x << 14) | (gamma0x >>> 18)) ^ + (gamma0x >>> 3), + gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ + ((gamma1x << 13) | (gamma1x >>> 19)) ^ + (gamma1x >>> 10); + + w[j] = gamma0 + (w[j - 7] >>> 0) + + gamma1 + (w[j - 16] >>> 0); + + } + + var ch = e & f ^ ~e & g, + maj = a & b ^ a & c ^ b & c, + sigma0 = ((a << 30) | (a >>> 2)) ^ + ((a << 19) | (a >>> 13)) ^ + ((a << 10) | (a >>> 22)), + sigma1 = ((e << 26) | (e >>> 6)) ^ + ((e << 21) | (e >>> 11)) ^ + ((e << 7) | (e >>> 25)); + + + t1 = (h >>> 0) + sigma1 + ch + (K[j]) + (w[j] >>> 0); + t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + + } + + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; + + } + + return H; + +}; + +// Package private blocksize +SHA256._blocksize = 16; + +module.exports = SHA256; diff --git a/src/ecdsa.js b/src/ecdsa.js index a2497bf..3f6877f 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -1,475 +1,290 @@ -function integerToBytes(i, len) { - var bytes = i.toByteArrayUnsigned(); +var sec = require('./jsbn/sec'); +var util = require('./util'); +var SecureRandom = require('./jsbn/rng'); +var BigInteger = require('./jsbn/jsbn'); - if (len < bytes.length) { - bytes = bytes.slice(bytes.length-len); - } else while (len > bytes.length) { - bytes.unshift(0); - } +var ECPointFp = require('./jsbn/ec').ECPointFp; - return bytes; -}; +var rng = new SecureRandom(); +var ecparams = sec("secp256k1"); +var P_OVER_FOUR = null; -ECFieldElementFp.prototype.getByteLength = function () { - return Math.floor((this.toBigInteger().bitLength() + 7) / 8); -}; +function implShamirsTrick(P, k, Q, l) +{ + var m = Math.max(k.bitLength(), l.bitLength()); + var Z = P.add2D(Q); + var R = P.curve.getInfinity(); -ECPointFp.prototype.getEncoded = function (compressed) { - var x = this.getX().toBigInteger(); - var y = this.getY().toBigInteger(); + for (var i = m - 1; i >= 0; --i) { + R = R.twice2D(); - // Get value as a 32-byte Buffer - // Fixed length based on a patch by bitaddress.org and Casascius - var enc = integerToBytes(x, 32); + R.z = BigInteger.ONE; - if (compressed) { - if (y.isEven()) { - // Compressed even pubkey - // M = 02 || X - enc.unshift(0x02); + if (k.testBit(i)) { + if (l.testBit(i)) { + R = R.add2D(Z); + } else { + R = R.add2D(P); + } } else { - // Compressed uneven pubkey - // M = 03 || X - enc.unshift(0x03); + if (l.testBit(i)) { + R = R.add2D(Q); + } } - } else { - // Uncompressed pubkey - // M = 04 || X || Y - enc.unshift(0x04); - enc = enc.concat(integerToBytes(y, 32)); } - return enc; -}; - -ECPointFp.decodeFrom = function (curve, enc) { - var type = enc[0]; - var dataLen = enc.length-1; - - // Extract x and y as byte arrays - var xBa = enc.slice(1, 1 + dataLen/2); - var yBa = enc.slice(1 + dataLen/2, 1 + dataLen); - - // Prepend zero byte to prevent interpretation as negative integer - xBa.unshift(0); - yBa.unshift(0); - - // Convert to BigIntegers - var x = new BigInteger(xBa); - var y = new BigInteger(yBa); - // Return point - return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y)); + return R; }; -ECPointFp.prototype.add2D = function (b) { - if(this.isInfinity()) return b; - if(b.isInfinity()) return this; - - if (this.x.equals(b.x)) { - if (this.y.equals(b.y)) { - // this = b, i.e. this must be doubled - return this.twice(); +var ECDSA = { + getBigRandom: function (limit) { + return new BigInteger(limit.bitLength(), rng) + .mod(limit.subtract(BigInteger.ONE)) + .add(BigInteger.ONE) + ; + }, + sign: function (hash, priv) { + var d = priv; + var n = ecparams.getN(); + var e = BigInteger.fromByteArrayUnsigned(hash); + + do { + var k = ECDSA.getBigRandom(n); + var G = ecparams.getG(); + var Q = G.multiply(k); + var r = Q.getX().toBigInteger().mod(n); + } while (r.compareTo(BigInteger.ZERO) <= 0); + + var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); + + return ECDSA.serializeSig(r, s); + }, + + verify: function (hash, sig, pubkey) { + var r,s; + if (util.isArray(sig)) { + var obj = ECDSA.parseSig(sig); + r = obj.r; + s = obj.s; + } else if ("object" === typeof sig && sig.r && sig.s) { + r = sig.r; + s = sig.s; + } else { + throw "Invalid value for signature"; } - // this = -b, i.e. the result is the point at infinity - return this.curve.getInfinity(); - } - - var x_x = b.x.subtract(this.x); - var y_y = b.y.subtract(this.y); - var gamma = y_y.divide(x_x); - - var x3 = gamma.square().subtract(this.x).subtract(b.x); - var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); - - return new ECPointFp(this.curve, x3, y3); -}; - -ECPointFp.prototype.twice2D = function () { - if (this.isInfinity()) return this; - if (this.y.toBigInteger().signum() == 0) { - // if y1 == 0, then (x1, y1) == (x1, -y1) - // and hence this = -this and thus 2(x1, y1) == infinity - return this.curve.getInfinity(); - } - - var TWO = this.curve.fromBigInteger(BigInteger.valueOf(2)); - var THREE = this.curve.fromBigInteger(BigInteger.valueOf(3)); - var gamma = this.x.square().multiply(THREE).add(this.curve.a).divide(this.y.multiply(TWO)); - - var x3 = gamma.square().subtract(this.x.multiply(TWO)); - var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); - - return new ECPointFp(this.curve, x3, y3); -}; - -ECPointFp.prototype.multiply2D = function (k) { - if(this.isInfinity()) return this; - if(k.signum() == 0) return this.curve.getInfinity(); - var e = k; - var h = e.multiply(new BigInteger("3")); - - var neg = this.negate(); - var R = this; - - var i; - for (i = h.bitLength() - 2; i > 0; --i) { - R = R.twice(); - - var hBit = h.testBit(i); - var eBit = e.testBit(i); - - if (hBit != eBit) { - R = R.add2D(hBit ? this : neg); + var Q; + if (pubkey instanceof ECPointFp) { + Q = pubkey; + } else if (util.isArray(pubkey)) { + Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey); + } else { + throw "Invalid format for pubkey value, must be byte array or ECPointFp"; + } + var e = BigInteger.fromByteArrayUnsigned(hash); + + return ECDSA.verifyRaw(e, r, s, Q); + }, + + verifyRaw: function (e, r, s, Q) { + var n = ecparams.getN(); + var G = ecparams.getG(); + + if (r.compareTo(BigInteger.ONE) < 0 || + r.compareTo(n) >= 0) + return false; + + if (s.compareTo(BigInteger.ONE) < 0 || + s.compareTo(n) >= 0) + return false; + + var c = s.modInverse(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 v = point.getX().toBigInteger().mod(n); + + return v.equals(r); + }, + + /** + * Serialize a signature into DER format. + * + * Takes two BigIntegers representing r and s and returns a byte array. + */ + serializeSig: function (r, s) { + var rBa = r.toByteArraySigned(); + var sBa = s.toByteArraySigned(); + + var sequence = []; + sequence.push(0x02); // INTEGER + sequence.push(rBa.length); + sequence = sequence.concat(rBa); + + sequence.push(0x02); // INTEGER + sequence.push(sBa.length); + sequence = sequence.concat(sBa); + + sequence.unshift(sequence.length); + sequence.unshift(0x30); // SEQUENCE + + return sequence; + }, + + /** + * Parses a byte array containing a DER-encoded signature. + * + * This function will return an object of the form: + * + * { + * r: BigInteger, + * s: BigInteger + * } + */ + parseSig: function (sig) { + var cursor; + if (sig[0] != 0x30) + throw new Error("Signature not a valid DERSequence"); + + 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 (cursor != sig.length) + // throw new Error("Extra bytes in signature"); + + var r = BigInteger.fromByteArrayUnsigned(rBa); + var s = BigInteger.fromByteArrayUnsigned(sBa); + + return {r: r, s: s}; + }, + + parseSigCompact: function (sig) { + if (sig.length !== 65) { + throw "Signature has the wrong length"; } - } - - return R; -}; - -ECPointFp.prototype.isOnCurve = function () { - var x = this.getX().toBigInteger(); - var y = this.getY().toBigInteger(); - var a = this.curve.getA().toBigInteger(); - var b = this.curve.getB().toBigInteger(); - var n = this.curve.getQ(); - var lhs = y.multiply(y).mod(n); - var rhs = x.multiply(x).multiply(x) - .add(a.multiply(x)).add(b).mod(n); - return lhs.equals(rhs); -}; -ECPointFp.prototype.toString = function () { - return '('+this.getX().toBigInteger().toString()+','+ - this.getY().toBigInteger().toString()+')'; -}; + // Signature is prefixed with a type byte storing three bits of + // information. + var i = sig[0] - 27; + if (i < 0 || i > 7) { + throw "Invalid signature type"; + } -/** - * Validate an elliptic curve point. - * - * See SEC 1, section 3.2.2.1: Elliptic Curve Public Key Validation Primitive - */ -ECPointFp.prototype.validate = function () { - var n = this.curve.getQ(); - - // Check Q != O - if (this.isInfinity()) { - throw new Error("Point is at infinity."); - } + 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}; + }, + + /** + * Recover a public key from a signature. + * + * See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public + * Key Recovery Operation". + * + * http://www.secg.org/download/aid-780/sec1-v2.pdf + */ + recoverPubKey: function (r, s, hash, i) { + // The recovery parameter i has two bits. + i = i & 3; + + // The less significant bit specifies whether the y coordinate + // of the compressed point is even or not. + var isYEven = i & 1; + + // The more significant bit specifies whether we should use the + // first or second candidate key. + 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(); + + // 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)); + } - // Check coordinate bounds - var x = this.getX().toBigInteger(); - var y = this.getY().toBigInteger(); - if (x.compareTo(BigInteger.ONE) < 0 || - x.compareTo(n.subtract(BigInteger.ONE)) > 0) { - throw new Error('x coordinate out of bounds'); - } - if (y.compareTo(BigInteger.ONE) < 0 || - y.compareTo(n.subtract(BigInteger.ONE)) > 0) { - throw new Error('y coordinate out of bounds'); - } + // 1.1 Compute x + var x = isSecondKey ? r.add(n) : r; - // Check y^2 = x^3 + ax + b (mod n) - if (!this.isOnCurve()) { - throw new Error("Point is not on the curve."); - } + // 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); - // Check nQ = 0 (Q is a scalar multiple of G) - if (this.multiply(n).isInfinity()) { - // TODO: This check doesn't work - fix. - throw new Error("Point is not a scalar multiple of G."); - } + 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); - return true; -}; + // 1.4 Check that nR is at infinity + var R = new ECPointFp(curve, + curve.fromBigInteger(x), + curve.fromBigInteger(y)); + R.validate(); -function dmp(v) { - if (!(v instanceof BigInteger)) v = v.toBigInteger(); - return Crypto.util.bytesToHex(v.toByteArrayUnsigned()); -}; + // 1.5 Compute e from M + var e = BigInteger.fromByteArrayUnsigned(hash); + var eNeg = BigInteger.ZERO.subtract(e).mod(n); -Bitcoin.ECDSA = (function () { - var ecparams = getSECCurveByName("secp256k1"); - var rng = new SecureRandom(); + // 1.6 Compute Q = r^-1 (sR - eG) + var rInv = r.modInverse(n); + var Q = implShamirsTrick(R, s, G, eNeg).multiply(rInv); - var P_OVER_FOUR = null; + Q.validate(); + if (!ECDSA.verifyRaw(e, r, s, Q)) { + throw "Pubkey recovery unsuccessful"; + } - function implShamirsTrick(P, k, Q, l) + var pubKey = new Bitcoin.ECKey(); + pubKey.pub = Q; + return pubKey; + }, + + /** + * Calculate pubkey extraction parameter. + * + * When extracting a pubkey from a signature, we have to + * distinguish four different cases. Rather than putting this + * burden on the verifier, Bitcoin includes a 2-bit value with the + * signature. + * + * This function simply tries all four cases and returns the value + * that resulted in a successful pubkey recovery. + */ + calcPubkeyRecoveryParam: function (address, r, s, hash) { - 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.z = BigInteger.ONE; - - if (k.testBit(i)) { - if (l.testBit(i)) { - R = R.add2D(Z); - } else { - R = R.add2D(P); + for (var i = 0; i < 4; i++) { + try { + var pubkey = Bitcoin.ECDSA.recoverPubKey(r, s, hash, i); + if (pubkey.getBitcoinAddress().toString() == address) { + return i; } - } else { - if (l.testBit(i)) { - R = R.add2D(Q); - } - } + } catch (e) {} } + throw "Unable to find valid recovery factor"; + } +}; - return R; - }; - - var ECDSA = { - getBigRandom: function (limit) { - return new BigInteger(limit.bitLength(), rng) - .mod(limit.subtract(BigInteger.ONE)) - .add(BigInteger.ONE) - ; - }, - sign: function (hash, priv) { - var d = priv; - var n = ecparams.getN(); - var e = BigInteger.fromByteArrayUnsigned(hash); - - do { - var k = ECDSA.getBigRandom(n); - var G = ecparams.getG(); - var Q = G.multiply(k); - var r = Q.getX().toBigInteger().mod(n); - } while (r.compareTo(BigInteger.ZERO) <= 0); - - var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); - - return ECDSA.serializeSig(r, s); - }, - - verify: function (hash, sig, pubkey) { - var r,s; - if (Bitcoin.Util.isArray(sig)) { - var obj = ECDSA.parseSig(sig); - r = obj.r; - s = obj.s; - } else if ("object" === typeof sig && sig.r && sig.s) { - r = sig.r; - s = sig.s; - } else { - throw "Invalid value for signature"; - } - - var Q; - if (pubkey instanceof ECPointFp) { - Q = pubkey; - } else if (Bitcoin.Util.isArray(pubkey)) { - Q = ECPointFp.decodeFrom(ecparams.getCurve(), pubkey); - } else { - throw "Invalid format for pubkey value, must be byte array or ECPointFp"; - } - var e = BigInteger.fromByteArrayUnsigned(hash); - - return ECDSA.verifyRaw(e, r, s, Q); - }, - - verifyRaw: function (e, r, s, Q) { - var n = ecparams.getN(); - var G = ecparams.getG(); - - if (r.compareTo(BigInteger.ONE) < 0 || - r.compareTo(n) >= 0) - return false; - - if (s.compareTo(BigInteger.ONE) < 0 || - s.compareTo(n) >= 0) - return false; - - var c = s.modInverse(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 v = point.getX().toBigInteger().mod(n); - - return v.equals(r); - }, - - /** - * Serialize a signature into DER format. - * - * Takes two BigIntegers representing r and s and returns a byte array. - */ - serializeSig: function (r, s) { - var rBa = r.toByteArraySigned(); - var sBa = s.toByteArraySigned(); - - var sequence = []; - sequence.push(0x02); // INTEGER - sequence.push(rBa.length); - sequence = sequence.concat(rBa); - - sequence.push(0x02); // INTEGER - sequence.push(sBa.length); - sequence = sequence.concat(sBa); - - sequence.unshift(sequence.length); - sequence.unshift(0x30); // SEQUENCE - - return sequence; - }, - - /** - * Parses a byte array containing a DER-encoded signature. - * - * This function will return an object of the form: - * - * { - * r: BigInteger, - * s: BigInteger - * } - */ - parseSig: function (sig) { - var cursor; - if (sig[0] != 0x30) - throw new Error("Signature not a valid DERSequence"); - - 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 (cursor != sig.length) - // throw new Error("Extra bytes in signature"); - - var r = BigInteger.fromByteArrayUnsigned(rBa); - var s = BigInteger.fromByteArrayUnsigned(sBa); - - return {r: r, s: s}; - }, - - parseSigCompact: function (sig) { - if (sig.length !== 65) { - throw "Signature has the wrong length"; - } - - // Signature is prefixed with a type byte storing three bits of - // information. - var i = sig[0] - 27; - if (i < 0 || i > 7) { - throw "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); - - return {r: r, s: s, i: i}; - }, - - /** - * Recover a public key from a signature. - * - * See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public - * Key Recovery Operation". - * - * http://www.secg.org/download/aid-780/sec1-v2.pdf - */ - recoverPubKey: function (r, s, hash, i) { - // The recovery parameter i has two bits. - i = i & 3; - - // The less significant bit specifies whether the y coordinate - // of the compressed point is even or not. - var isYEven = i & 1; - - // The more significant bit specifies whether we should use the - // first or second candidate key. - 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(); - - // We precalculate (p + 1) / 4 where p is if the field order - if (!P_OVER_FOUR) { - P_OVER_FOUR = p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)); - } - - // 1.1 Compute x - 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 xorOdd = beta.isEven() ? (i % 2) : ((i+1) % 2); - // If beta is even, but y isn't or vice versa, then convert it, - // otherwise we're done and y == beta. - var y = (beta.isEven() ? !isYEven : isYEven) ? beta : p.subtract(beta); - - // 1.4 Check that nR is at infinity - var R = new ECPointFp(curve, - curve.fromBigInteger(x), - curve.fromBigInteger(y)); - R.validate(); - - // 1.5 Compute e from M - var e = BigInteger.fromByteArrayUnsigned(hash); - var eNeg = BigInteger.ZERO.subtract(e).mod(n); - - // 1.6 Compute Q = r^-1 (sR - eG) - var rInv = r.modInverse(n); - var Q = implShamirsTrick(R, s, G, eNeg).multiply(rInv); - - Q.validate(); - if (!ECDSA.verifyRaw(e, r, s, Q)) { - throw "Pubkey recovery unsuccessful"; - } - - var pubKey = new Bitcoin.ECKey(); - pubKey.pub = Q; - return pubKey; - }, - - /** - * Calculate pubkey extraction parameter. - * - * When extracting a pubkey from a signature, we have to - * distinguish four different cases. Rather than putting this - * burden on the verifier, Bitcoin includes a 2-bit value with the - * signature. - * - * This function simply tries all four cases and returns the value - * that resulted in a successful pubkey recovery. - */ - calcPubkeyRecoveryParam: function (address, r, s, hash) - { - for (var i = 0; i < 4; i++) { - try { - var pubkey = Bitcoin.ECDSA.recoverPubKey(r, s, hash, i); - if (pubkey.getBitcoinAddress().toString() == address) { - return i; - } - } catch (e) {} - } - throw "Unable to find valid recovery factor"; - } - }; +module.exports = ECDSA; - return ECDSA; -})(); diff --git a/src/eckey.js b/src/eckey.js index a01367b..a7914d9 100644 --- a/src/eckey.js +++ b/src/eckey.js @@ -1,131 +1,176 @@ -Bitcoin.ECKey = (function () { - var ECDSA = Bitcoin.ECDSA; - var ecparams = getSECCurveByName("secp256k1"); - var rng = new SecureRandom(); - - var ECKey = function (input) { - if (!input) { - // Generate new key - var n = ecparams.getN(); - this.priv = ECDSA.getBigRandom(n); - } else if (input instanceof BigInteger) { - // Input is a private key value - this.priv = input; - } else if (Bitcoin.Util.isArray(input)) { - // Prepend zero byte to prevent interpretation as negative integer - this.priv = BigInteger.fromByteArrayUnsigned(input); - } else if ("string" == typeof input) { - if (input.length == 51 && input[0] == '5') { - // Base58 encoded private key - this.priv = BigInteger.fromByteArrayUnsigned(ECKey.decodeString(input)); - } else { - // Prepend zero byte to prevent interpretation as negative integer - this.priv = BigInteger.fromByteArrayUnsigned(Crypto.util.base64ToBytes(input)); - } +var BigInteger = require('./jsbn/jsbn'); +var sec = require('./jsbn/sec'); +var base58 = require('./base58'); +var Crypto = require('./crypto-js/crypto'); +var util = require('./util'); +var conv = require('./convert'); +var Address = require('./address'); +var ecdsa = require('./ecdsa'); + +var ecparams = sec("secp256k1"); + +// input can be nothing, array of bytes, hex string, or base58 string +var ECKey = function (input) { + if (!(this instanceof ECKey)) { + return new ECKey(input); + } + + this.compressed = !!ECKey.compressByDefault; + + if (!input) { + // Generate new key + var n = ecparams.getN(); + this.priv = ecdsa.getBigRandom(n); + } else if (input instanceof BigInteger) { + // Input is a private key value + this.priv = input; + } else if (util.isArray(input)) { + // Prepend zero byte to prevent interpretation as negative integer + this.priv = BigInteger.fromByteArrayUnsigned(input); + this.compressed = false; + } else if ("string" == typeof input) { + if (input.length == 51 && input[0] == '5') { + // Base58 encoded private key + this.priv = BigInteger.fromByteArrayUnsigned(ECKey.decodeString(input)); + this.compressed = false; } - this.compressed = !!ECKey.compressByDefault; - }; - - /** - * Whether public keys should be returned compressed by default. - */ - ECKey.compressByDefault = false; - - /** - * Set whether the public key should be returned compressed or not. - */ - ECKey.prototype.setCompressed = function (v) { - this.compressed = !!v; - }; - - /** - * Return public key in DER encoding. - */ - ECKey.prototype.getPub = function () { - return this.getPubPoint().getEncoded(this.compressed); - }; - - /** - * Return public point as ECPoint object. - */ - ECKey.prototype.getPubPoint = function () { - if (!this.pub) this.pub = ecparams.getG().multiply(this.priv); - - return this.pub; - }; - - /** - * Get the pubKeyHash for this key. - * - * This is calculated as RIPE160(SHA256([encoded pubkey])) and returned as - * a byte array. - */ - ECKey.prototype.getPubKeyHash = function () { - if (this.pubKeyHash) return this.pubKeyHash; - - return this.pubKeyHash = Bitcoin.Util.sha256ripe160(this.getPub()); - }; - - ECKey.prototype.getBitcoinAddress = function () { - var hash = this.getPubKeyHash(); - var addr = new Bitcoin.Address(hash); - return addr; - }; - - ECKey.prototype.getExportedPrivateKey = function () { - var hash = this.priv.toByteArrayUnsigned(); - while (hash.length < 32) hash.unshift(0); - hash.unshift(0x80); - var checksum = Crypto.SHA256(Crypto.SHA256(hash, {asBytes: true}), {asBytes: true}); - var bytes = hash.concat(checksum.slice(0,4)); - return Bitcoin.Base58.encode(bytes); - }; - - ECKey.prototype.setPub = function (pub) { - this.pub = ECPointFp.decodeFrom(ecparams.getCurve(), pub); - }; - - ECKey.prototype.toString = function (format) { - if (format === "base64") { - return Crypto.util.bytesToBase64(this.priv.toByteArrayUnsigned()); + else if (input.length == 52 && (input[0] === 'K' || input[0] === 'L')) { + // Base58 encoded private key + this.priv = BigInteger.fromByteArrayUnsigned(ECKey.decodeString(input)); + this.compressed = true; } else { - return Crypto.util.bytesToHex(this.priv.toByteArrayUnsigned()); - } - }; - - ECKey.prototype.sign = function (hash) { - return ECDSA.sign(hash, this.priv); - }; - - ECKey.prototype.verify = function (hash, sig) { - return ECDSA.verify(hash, sig, this.getPub()); - }; - - /** - * Parse an exported private key contained in a string. - */ - ECKey.decodeString = function (string) { - var bytes = Bitcoin.Base58.decode(string); - - var hash = bytes.slice(0, 33); - - var checksum = Crypto.SHA256(Crypto.SHA256(hash, {asBytes: true}), {asBytes: true}); - - if (checksum[0] != bytes[33] || - checksum[1] != bytes[34] || - checksum[2] != bytes[35] || - checksum[3] != bytes[36]) { - throw "Checksum validation failed!"; - } - - var version = hash.shift(); - - if (version != 0x80) { - throw "Version "+version+" not supported!"; + // hex string? + // //wtf is base64 here for? + // Prepend zero byte to prevent interpretation as negative integer + this.priv = BigInteger.fromByteArrayUnsigned(conv.base64ToBytes(input)); } - - return hash; - }; - - return ECKey; -})(); + } +}; + +// TODO(shtylman) methods +// wallet import format (base58 check with meta info) +// fromWIF +// toWIF +// fromBytes +// toBytes +// fromHex +// toHex + +/** + * Whether public keys should be returned compressed by default. + */ +ECKey.compressByDefault = false; + +/** + * Set whether the public key should be returned compressed or not. + */ +ECKey.prototype.setCompressed = function (v) { + this.compressed = !!v; +}; + +/** + * Return public key in DER encoding. + */ +ECKey.prototype.getPub = function () { + return this.getPubPoint().getEncoded(this.compressed); +}; + +/** + * Return public point as ECPoint object. + */ +ECKey.prototype.getPubPoint = function () { + if (!this.pub) this.pub = ecparams.getG().multiply(this.priv); + + return this.pub; +}; + +/** + * Get the pubKeyHash for this key. + * + * This is calculated as RIPE160(SHA256([encoded pubkey])) and returned as + * a byte array. + */ +ECKey.prototype.getPubKeyHash = function () { + if (this.pubKeyHash) return this.pubKeyHash; + + return this.pubKeyHash = util.sha256ripe160(this.getPub()); +}; + +ECKey.prototype.getBitcoinAddress = function () { + var hash = this.getPubKeyHash(); + var addr = new Address(hash); + return addr; +}; + +ECKey.prototype.getExportedPrivateKey = function () { + var hash = this.priv.toByteArrayUnsigned(); + while (hash.length < 32) hash.unshift(0); + hash.unshift(0x80); + var checksum = Crypto.SHA256(Crypto.SHA256(hash, {asBytes: true}), {asBytes: true}); + var bytes = hash.concat(checksum.slice(0,4)); + return Bitcoin.Base58.encode(bytes); +}; + +ECKey.prototype.setPub = function (pub) { + this.pub = ECPointFp.decodeFrom(ecparams.getCurve(), pub); +}; + +ECKey.prototype.toString = function (format) { + if (format === "base64") { + return conv.bytesToBase64(this.priv.toByteArrayUnsigned()); + } else { + return Crypto.util.bytesToHex(this.priv.toByteArrayUnsigned()); + } +}; + +ECKey.prototype.sign = function (hash) { + return ecdsa.sign(hash, this.priv); +}; + +ECKey.prototype.verify = function (hash, sig) { + return ecdsa.verify(hash, sig, this.getPub()); +}; + +/** + * Parse an exported private key contained in a string. + */ +ECKey.decodeString = function (string) { + var bytes = base58.decode(string); + + if (bytes.length !== 37 && bytes.length !== 38) { + throw new Error('not a valid base58 encoded private key'); + } + + //Format: + //* uncompressed: 0x80 + [32-byte secret] + [4 bytes of Hash() of + //previous 33 bytes], base58 encoded + //* compressed: 0x80 + [32-byte secret] + 0x01 + [4 bytes of Hash() + //previous 34 bytes], base58 encoded + + if (bytes[33] === 0x01) { + // compressed + } + + var hash = bytes.slice(0, 33); + + /* + var checksum = Crypto.SHA256(Crypto.SHA256(hash, {asBytes: true}), {asBytes: true}); + + if (checksum[0] != bytes[33] || + checksum[1] != bytes[34] || + checksum[2] != bytes[35] || + checksum[3] != bytes[36]) { + throw "Checksum validation failed!"; + } + */ + + var version = hash.shift(); + + if (version != 0x80) { + throw "Version "+version+" not supported!"; + } + + return hash; +}; + +module.exports = ECKey; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..7b7443e --- /dev/null +++ b/src/index.js @@ -0,0 +1,46 @@ +// Bit-wise rotate left +var rotl = function (n, b) { + return (n << b) | (n >>> (32 - b)); +}; + +// Bit-wise rotate right +var rotr = function (n, b) { + return (n << (32 - b)) | (n >>> b); +}; + +// Swap big-endian to little-endian and vice versa +var endian = function (n) { + // If number given, swap endian + if (n.constructor == Number) { + return rotl(n, 8) & 0x00FF00FF | rotl(n, 24) & 0xFF00FF00; + } + + // Else, assume array and swap all items + for (var i = 0; i < n.length; i++) { + n[i] = endian(n[i]); + } + return n; +} + +module.exports = { + Address: require('./address'), + Key: require('./eckey'), + BigInteger: require('./jsbn/jsbn'), + Script: require('./script'), + Opcode: require('./opcode'), + Transaction: require('./transaction').Transaction, + TransactionIn: require('./transaction').TransactionIn, + TransactionOut: require('./transaction').TransactionOut, + ECPointFp: require('./jsbn/ec').ECPointFp, + Wallet: require('./wallet'), + + ecdsa: require('./ecdsa'), + + // base58 encoding/decoding to bytes + base58: require('./base58'), + + // conversions + convert: require('./convert'), + + endian: endian +} diff --git a/src/jsbn/ec.js b/src/jsbn/ec.js index 43ded3e..6c9fbe1 100644 --- a/src/jsbn/ec.js +++ b/src/jsbn/ec.js @@ -2,7 +2,7 @@ // Ported loosely from BouncyCastle's Java EC code // Only Fp curves implemented for now -// Requires jsbn.js and jsbn2.js +var BigInteger = require('./jsbn'); // ---------------- // ECFieldElementFp @@ -314,3 +314,194 @@ ECCurveFp.prototype.equals = curveFpEquals; ECCurveFp.prototype.getInfinity = curveFpGetInfinity; ECCurveFp.prototype.fromBigInteger = curveFpFromBigInteger; ECCurveFp.prototype.decodePointHex = curveFpDecodePointHex; + +// prepends 0 if bytes < len +// cuts off start if bytes > len +function integerToBytes(i, len) { + var bytes = i.toByteArrayUnsigned(); + + if (len < bytes.length) { + bytes = bytes.slice(bytes.length-len); + } else while (len > bytes.length) { + bytes.unshift(0); + } + + return bytes; +}; + +ECFieldElementFp.prototype.getByteLength = function () { + return Math.floor((this.toBigInteger().bitLength() + 7) / 8); +}; + +ECPointFp.prototype.getEncoded = function (compressed) { + var x = this.getX().toBigInteger(); + var y = this.getY().toBigInteger(); + + // Get value as a 32-byte Buffer + // Fixed length based on a patch by bitaddress.org and Casascius + var enc = integerToBytes(x, 32); + + if (compressed) { + if (y.isEven()) { + // Compressed even pubkey + // M = 02 || X + enc.unshift(0x02); + } else { + // Compressed uneven pubkey + // M = 03 || X + enc.unshift(0x03); + } + } else { + // Uncompressed pubkey + // M = 04 || X || Y + enc.unshift(0x04); + enc = enc.concat(integerToBytes(y, 32)); + } + return enc; +}; + +ECPointFp.decodeFrom = function (curve, enc) { + var type = enc[0]; + var dataLen = enc.length-1; + + // Extract x and y as byte arrays + var xBa = enc.slice(1, 1 + dataLen/2); + var yBa = enc.slice(1 + dataLen/2, 1 + dataLen); + + // Prepend zero byte to prevent interpretation as negative integer + xBa.unshift(0); + yBa.unshift(0); + + // Convert to BigIntegers + var x = new BigInteger(xBa); + var y = new BigInteger(yBa); + + // Return point + return new ECPointFp(curve, curve.fromBigInteger(x), curve.fromBigInteger(y)); +}; + +ECPointFp.prototype.add2D = function (b) { + if(this.isInfinity()) return b; + if(b.isInfinity()) return this; + + if (this.x.equals(b.x)) { + if (this.y.equals(b.y)) { + // this = b, i.e. this must be doubled + return this.twice(); + } + // this = -b, i.e. the result is the point at infinity + return this.curve.getInfinity(); + } + + var x_x = b.x.subtract(this.x); + var y_y = b.y.subtract(this.y); + var gamma = y_y.divide(x_x); + + var x3 = gamma.square().subtract(this.x).subtract(b.x); + var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); + + return new ECPointFp(this.curve, x3, y3); +}; + +ECPointFp.prototype.twice2D = function () { + if (this.isInfinity()) return this; + if (this.y.toBigInteger().signum() == 0) { + // if y1 == 0, then (x1, y1) == (x1, -y1) + // and hence this = -this and thus 2(x1, y1) == infinity + return this.curve.getInfinity(); + } + + var TWO = this.curve.fromBigInteger(BigInteger.valueOf(2)); + var THREE = this.curve.fromBigInteger(BigInteger.valueOf(3)); + var gamma = this.x.square().multiply(THREE).add(this.curve.a).divide(this.y.multiply(TWO)); + + var x3 = gamma.square().subtract(this.x.multiply(TWO)); + var y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); + + return new ECPointFp(this.curve, x3, y3); +}; + +ECPointFp.prototype.multiply2D = function (k) { + if(this.isInfinity()) return this; + if(k.signum() == 0) return this.curve.getInfinity(); + + var e = k; + var h = e.multiply(new BigInteger("3")); + + var neg = this.negate(); + var R = this; + + var i; + for (i = h.bitLength() - 2; i > 0; --i) { + R = R.twice(); + + var hBit = h.testBit(i); + var eBit = e.testBit(i); + + if (hBit != eBit) { + R = R.add2D(hBit ? this : neg); + } + } + + return R; +}; + +ECPointFp.prototype.isOnCurve = function () { + var x = this.getX().toBigInteger(); + var y = this.getY().toBigInteger(); + var a = this.curve.getA().toBigInteger(); + var b = this.curve.getB().toBigInteger(); + var n = this.curve.getQ(); + var lhs = y.multiply(y).mod(n); + var rhs = x.multiply(x).multiply(x) + .add(a.multiply(x)).add(b).mod(n); + return lhs.equals(rhs); +}; + +ECPointFp.prototype.toString = function () { + return '('+this.getX().toBigInteger().toString()+','+ + this.getY().toBigInteger().toString()+')'; +}; + +/** + * Validate an elliptic curve point. + * + * See SEC 1, section 3.2.2.1: Elliptic Curve Public Key Validation Primitive + */ +ECPointFp.prototype.validate = function () { + var n = this.curve.getQ(); + + // Check Q != O + if (this.isInfinity()) { + throw new Error("Point is at infinity."); + } + + // Check coordinate bounds + var x = this.getX().toBigInteger(); + var y = this.getY().toBigInteger(); + if (x.compareTo(BigInteger.ONE) < 0 || + x.compareTo(n.subtract(BigInteger.ONE)) > 0) { + throw new Error('x coordinate out of bounds'); + } + if (y.compareTo(BigInteger.ONE) < 0 || + y.compareTo(n.subtract(BigInteger.ONE)) > 0) { + throw new Error('y coordinate out of bounds'); + } + + // Check y^2 = x^3 + ax + b (mod n) + if (!this.isOnCurve()) { + throw new Error("Point is not on the curve."); + } + + // Check nQ = 0 (Q is a scalar multiple of G) + if (this.multiply(n).isInfinity()) { + // TODO: This check doesn't work - fix. + throw new Error("Point is not a scalar multiple of G."); + } + + return true; +}; + + +module.exports = ECCurveFp; +module.exports.ECPointFp = ECPointFp; diff --git a/src/jsbn/jsbn.js b/src/jsbn/jsbn.js index 40bb9e2..6618f37 100644 --- a/src/jsbn/jsbn.js +++ b/src/jsbn/jsbn.js @@ -13,12 +13,19 @@ var j_lm = ((canary&0xffffff)==0xefcafe); // (public) Constructor function BigInteger(a,b,c) { - if(a != null) + if (!(this instanceof BigInteger)) { + return new BigInteger(a, b, c); + } + + if(a != null) { if("number" == typeof a) this.fromNumber(a,b,c); else if(b == null && "string" != typeof a) this.fromString(a,256); else this.fromString(a,b); + } } +var proto = BigInteger.prototype; + // return new, unset BigInteger function nbi() { return new BigInteger(null); } @@ -67,6 +74,12 @@ function am3(i,x,w,j,c,n) { } return c; } + +// wtf? +BigInteger.prototype.am = am1; +dbits = 26; + +/* if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { BigInteger.prototype.am = am2; dbits = 30; @@ -79,10 +92,11 @@ else { // Mozilla/Netscape seems to prefer am3 BigInteger.prototype.am = am3; dbits = 28; } +*/ BigInteger.prototype.DB = dbits; BigInteger.prototype.DM = ((1<= 0) { var x = (k==8)?s[i]&0xff:intAt(s,i); @@ -146,22 +162,22 @@ function bnpFromString(s,b) { } mi = false; if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); + self[self.t++] = x; + else if(sh+k > self.DB) { + self[self.t-1] |= (x&((1<<(self.DB-sh))-1))<>(self.DB-sh)); } else - this[this.t-1] |= x<= this.DB) sh -= this.DB; + if(sh >= self.DB) sh -= self.DB; } if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0) self[self.t-1] |= ((1<<(self.DB-sh))-1)< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } + if(p < self.DB && (d = self[i]>>p) > 0) { m = true; r = int2char(d); } while(i >= 0) { if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); + d = (self[i]&((1<>(p+=self.DB-k); } else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } + d = (self[i]>>(p-=k))&km; + if(p <= 0) { p += self.DB; --i; } } if(d > 0) m = true; if(m) r += int2char(d); @@ -252,67 +269,70 @@ function bnpDRShiftTo(n,r) { // (protected) r = this << n function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; + var self = this; + var bs = n%self.DB; + var cbs = self.DB-bs; var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) { + r[i+ds+1] = (self[i]>>cbs)|c; + c = (self[i]&bm)<= 0; --i) r[i] = 0; r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; + r.t = self.t+ds+1; + r.s = self.s; r.clamp(); } // (protected) r = this >> n function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; + var self = this; + r.s = self.s; + var ds = Math.floor(n/self.DB); + if(ds >= self.t) { r.t = 0; return; } + var bs = n%self.DB; + var cbs = self.DB-bs; var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; + r[0] = self[ds]>>bs; + for(var i = ds+1; i < self.t; ++i) { + r[i-ds-1] |= (self[i]&bm)<>bs; } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)< 0) r[self.t-ds-1] |= (self.s&bm)<>= this.DB; + c += self[i]-a[i]; + r[i++] = c&self.DM; + c >>= self.DB; } - if(a.t < this.t) { + if(a.t < self.t) { c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; + while(i < self.t) { + c += self[i]; + r[i++] = c&self.DM; + c >>= self.DB; } - c += this.s; + c += self.s; } else { - c += this.s; + c += self.s; while(i < a.t) { c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; + r[i++] = c&self.DM; + c >>= self.DB; } c -= a.s; } r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; + if(c < -1) r[i++] = self.DV+c; else if(c > 0) r[i++] = c; r.t = i; r.clamp(); @@ -351,24 +371,25 @@ function bnpSquareTo(r) { // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. function bnpDivRemTo(m,q,r) { + var self = this; var pm = m.abs(); if(pm.t <= 0) return; - var pt = this.abs(); + var pt = self.abs(); if(pt.t < pm.t) { if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); + if(r != null) self.copyTo(r); return; } if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus + var y = nbi(), ts = self.s, ms = m.s; + var nsh = self.DB-nbits(pm[pm.t-1]); // normalize modulus if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; var y0 = y[ys-1]; if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<1)?y[ys-2]>>self.F2:0); + var d1 = self.FV/yt, d2 = (1<= 0) { @@ -380,7 +401,7 @@ function bnpDivRemTo(m,q,r) { while(y.t < ys) y[y.t++] = 0; while(--j >= 0) { // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); + var qd = (r[--i]==y0)?self.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out y.dlShiftTo(j,t); r.subTo(t,r); @@ -529,31 +550,750 @@ function bnModPowInt(e,m) { } // protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; +proto.copyTo = bnpCopyTo; +proto.fromInt = bnpFromInt; +proto.fromString = bnpFromString; +proto.clamp = bnpClamp; +proto.dlShiftTo = bnpDLShiftTo; +proto.drShiftTo = bnpDRShiftTo; +proto.lShiftTo = bnpLShiftTo; +proto.rShiftTo = bnpRShiftTo; +proto.subTo = bnpSubTo; +proto.multiplyTo = bnpMultiplyTo; +proto.squareTo = bnpSquareTo; +proto.divRemTo = bnpDivRemTo; +proto.invDigit = bnpInvDigit; +proto.isEven = bnpIsEven; +proto.exp = bnpExp; // public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; +proto.toString = bnToString; +proto.negate = bnNegate; +proto.abs = bnAbs; +proto.compareTo = bnCompareTo; +proto.bitLength = bnBitLength; +proto.mod = bnMod; +proto.modPowInt = bnModPowInt; + +//// jsbn2 + +function nbi() { return new BigInteger(null); } + +// (public) +function bnClone() { var r = nbi(); this.copyTo(r); return r; } + +// (public) return value as integer +function bnIntValue() { + if(this.s < 0) { + if(this.t == 1) return this[0]-this.DV; + else if(this.t == 0) return -1; + } + else if(this.t == 1) return this[0]; + else if(this.t == 0) return 0; + // assumes 16 < DB < 32 + return ((this[1]&((1<<(32-this.DB))-1))<>24; } + +// (public) return value as short (assumes DB>=16) +function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } + +// (protected) return x s.t. r^x < DV +function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } + +// (public) 0 if this == 0, 1 if this > 0 +function bnSigNum() { + if(this.s < 0) return -1; + else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; + else return 1; +} + +// (protected) convert to radix string +function bnpToRadix(b) { + if(b == null) b = 10; + if(this.signum() == 0 || b < 2 || b > 36) return "0"; + var cs = this.chunkSize(b); + var a = Math.pow(b,cs); + var d = nbv(a), y = nbi(), z = nbi(), r = ""; + this.divRemTo(d,y,z); + while(y.signum() > 0) { + r = (a+z.intValue()).toString(b).substr(1) + r; + y.divRemTo(d,y,z); + } + return z.intValue().toString(b) + r; +} + +// (protected) convert from radix string +function bnpFromRadix(s,b) { + var self = this; + self.fromInt(0); + if(b == null) b = 10; + var cs = self.chunkSize(b); + var d = Math.pow(b,cs), mi = false, j = 0, w = 0; + for(var i = 0; i < s.length; ++i) { + var x = intAt(s,i); + if(x < 0) { + if(s.charAt(i) == "-" && self.signum() == 0) mi = true; + continue; + } + w = b*w+x; + if(++j >= cs) { + self.dMultiply(d); + self.dAddOffset(w,0); + j = 0; + w = 0; + } + } + if(j > 0) { + self.dMultiply(Math.pow(b,j)); + self.dAddOffset(w,0); + } + if(mi) BigInteger.ZERO.subTo(self,self); +} + +// (protected) alternate constructor +function bnpFromNumber(a,b,c) { + var self = this; + if("number" == typeof b) { + // new BigInteger(int,int,RNG) + if(a < 2) self.fromInt(1); + else { + self.fromNumber(a,c); + if(!self.testBit(a-1)) // force MSB set + self.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,self); + if(self.isEven()) self.dAddOffset(1,0); // force odd + while(!self.isProbablePrime(b)) { + self.dAddOffset(2,0); + if(self.bitLength() > a) self.subTo(BigInteger.ONE.shiftLeft(a-1),self); + } + } + } + else { + // new BigInteger(int,RNG) + var x = new Array(), t = a&7; + x.length = (a>>3)+1; + b.nextBytes(x); + if(t > 0) x[0] &= ((1< 0) { + if(p < self.DB && (d = self[i]>>p) != (self.s&self.DM)>>p) + r[k++] = d|(self.s<<(self.DB-p)); + while(i >= 0) { + if(p < 8) { + d = (self[i]&((1<>(p+=self.DB-8); + } + else { + d = (self[i]>>(p-=8))&0xff; + if(p <= 0) { p += self.DB; --i; } + } + if((d&0x80) != 0) d |= -256; + if(k === 0 && (self.s&0x80) != (d&0x80)) ++k; + if(k > 0 || d != self.s) r[k++] = d; + } + } + return r; +} + +function bnEquals(a) { return(this.compareTo(a)==0); } +function bnMin(a) { return(this.compareTo(a)<0)?this:a; } +function bnMax(a) { return(this.compareTo(a)>0)?this:a; } + +// (protected) r = this op a (bitwise) +function bnpBitwiseTo(a,op,r) { + var self = this; + var i, f, m = Math.min(a.t,self.t); + for(i = 0; i < m; ++i) r[i] = op(self[i],a[i]); + if(a.t < self.t) { + f = a.s&self.DM; + for(i = m; i < self.t; ++i) r[i] = op(self[i],f); + r.t = self.t; + } + else { + f = self.s&self.DM; + for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); + r.t = a.t; + } + r.s = op(self.s,a.s); + r.clamp(); +} + +// (public) this & a +function op_and(x,y) { return x&y; } +function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } + +// (public) this | a +function op_or(x,y) { return x|y; } +function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } + +// (public) this ^ a +function op_xor(x,y) { return x^y; } +function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } + +// (public) this & ~a +function op_andnot(x,y) { return x&~y; } +function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } + +// (public) ~this +function bnNot() { + var r = nbi(); + for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; + r.t = this.t; + r.s = ~this.s; + return r; +} + +// (public) this << n +function bnShiftLeft(n) { + var r = nbi(); + if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); + return r; +} + +// (public) this >> n +function bnShiftRight(n) { + var r = nbi(); + if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); + return r; +} + +// return index of lowest 1-bit in x, x < 2^31 +function lbit(x) { + if(x == 0) return -1; + var r = 0; + if((x&0xffff) == 0) { x >>= 16; r += 16; } + if((x&0xff) == 0) { x >>= 8; r += 8; } + if((x&0xf) == 0) { x >>= 4; r += 4; } + if((x&3) == 0) { x >>= 2; r += 2; } + if((x&1) == 0) ++r; + return r; +} + +// (public) returns index of lowest 1-bit (or -1 if none) +function bnGetLowestSetBit() { + for(var i = 0; i < this.t; ++i) + if(this[i] != 0) return i*this.DB+lbit(this[i]); + if(this.s < 0) return this.t*this.DB; + return -1; +} + +// return number of 1 bits in x +function cbit(x) { + var r = 0; + while(x != 0) { x &= x-1; ++r; } + return r; +} + +// (public) return number of set bits +function bnBitCount() { + var r = 0, x = this.s&this.DM; + for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); + return r; +} + +// (public) true iff nth bit is set +function bnTestBit(n) { + var j = Math.floor(n/this.DB); + if(j >= this.t) return(this.s!=0); + return((this[j]&(1<<(n%this.DB)))!=0); +} + +// (protected) this op (1<>= self.DB; + } + if(a.t < self.t) { + c += a.s; + while(i < self.t) { + c += self[i]; + r[i++] = c&self.DM; + c >>= self.DB; + } + c += self.s; + } + else { + c += self.s; + while(i < a.t) { + c += a[i]; + r[i++] = c&self.DM; + c >>= self.DB; + } + c += a.s; + } + r.s = (c<0)?-1:0; + if(c > 0) r[i++] = c; + else if(c < -1) r[i++] = self.DV+c; + r.t = i; + r.clamp(); +} + +// (public) this + a +function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } + +// (public) this - a +function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } + +// (public) this * a +function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } + +// (public) this^2 +function bnSquare() { var r = nbi(); this.squareTo(r); return r; } + +// (public) this / a +function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } + +// (public) this % a +function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } + +// (public) [this/a,this%a] +function bnDivideAndRemainder(a) { + var q = nbi(), r = nbi(); + this.divRemTo(a,q,r); + return new Array(q,r); +} + +// (protected) this *= n, this >= 0, 1 < n < DV +function bnpDMultiply(n) { + this[this.t] = this.am(0,n-1,this,0,0,this.t); + ++this.t; + this.clamp(); +} + +// (protected) this += n << w words, this >= 0 +function bnpDAddOffset(n,w) { + if(n == 0) return; + while(this.t <= w) this[this.t++] = 0; + this[w] += n; + while(this[w] >= this.DV) { + this[w] -= this.DV; + if(++w >= this.t) this[this.t++] = 0; + ++this[w]; + } +} + +// A "null" reducer +function NullExp() {} +function nNop(x) { return x; } +function nMulTo(x,y,r) { x.multiplyTo(y,r); } +function nSqrTo(x,r) { x.squareTo(r); } + +NullExp.prototype.convert = nNop; +NullExp.prototype.revert = nNop; +NullExp.prototype.mulTo = nMulTo; +NullExp.prototype.sqrTo = nSqrTo; + +// (public) this^e +function bnPow(e) { return this.exp(e,new NullExp()); } + +// (protected) r = lower n words of "this * a", a.t <= n +// "this" should be the larger one if appropriate. +function bnpMultiplyLowerTo(a,n,r) { + var i = Math.min(this.t+a.t,n); + r.s = 0; // assumes a,this >= 0 + r.t = i; + while(i > 0) r[--i] = 0; + var j; + for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); + for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); + r.clamp(); +} + +// (protected) r = "this * a" without lower n words, n > 0 +// "this" should be the larger one if appropriate. +function bnpMultiplyUpperTo(a,n,r) { + --n; + var i = r.t = this.t+a.t-n; + r.s = 0; // assumes a,this >= 0 + while(--i >= 0) r[i] = 0; + for(i = Math.max(n-this.t,0); i < a.t; ++i) + r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); + r.clamp(); + r.drShiftTo(1,r); +} + +// Barrett modular reduction +function Barrett(m) { + // setup Barrett + this.r2 = nbi(); + this.q3 = nbi(); + BigInteger.ONE.dlShiftTo(2*m.t,this.r2); + this.mu = this.r2.divide(m); + this.m = m; +} + +function barrettConvert(x) { + if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); + else if(x.compareTo(this.m) < 0) return x; + else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } +} + +function barrettRevert(x) { return x; } + +// x = x mod m (HAC 14.42) +function barrettReduce(x) { + var self = this; + x.drShiftTo(self.m.t-1,self.r2); + if(x.t > self.m.t+1) { x.t = self.m.t+1; x.clamp(); } + self.mu.multiplyUpperTo(self.r2,self.m.t+1,self.q3); + self.m.multiplyLowerTo(self.q3,self.m.t+1,self.r2); + while(x.compareTo(self.r2) < 0) x.dAddOffset(1,self.m.t+1); + x.subTo(self.r2,x); + while(x.compareTo(self.m) >= 0) x.subTo(self.m,x); +} + +// r = x^2 mod m; x != r +function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +// r = x*y mod m; x,y != r +function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } + +Barrett.prototype.convert = barrettConvert; +Barrett.prototype.revert = barrettRevert; +Barrett.prototype.reduce = barrettReduce; +Barrett.prototype.mulTo = barrettMulTo; +Barrett.prototype.sqrTo = barrettSqrTo; + +// (public) this^e % m (HAC 14.85) +function bnModPow(e,m) { + var i = e.bitLength(), k, r = nbv(1), z; + if(i <= 0) return r; + else if(i < 18) k = 1; + else if(i < 48) k = 3; + else if(i < 144) k = 4; + else if(i < 768) k = 5; + else k = 6; + if(i < 8) + z = new Classic(m); + else if(m.isEven()) + z = new Barrett(m); + else + z = new Montgomery(m); + + // precomputation + var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { + var g2 = nbi(); + z.sqrTo(g[1],g2); + while(n <= km) { + g[n] = nbi(); + z.mulTo(g2,g[n-2],g[n]); + n += 2; + } + } + + var j = e.t-1, w, is1 = true, r2 = nbi(), t; + i = nbits(e[j])-1; + while(j >= 0) { + if(i >= k1) w = (e[j]>>(i-k1))&km; + else { + w = (e[j]&((1<<(i+1))-1))<<(k1-i); + if(j > 0) w |= e[j-1]>>(this.DB+i-k1); + } + + n = k; + while((w&1) == 0) { w >>= 1; --n; } + if((i -= n) < 0) { i += this.DB; --j; } + if(is1) { // ret == 1, don't bother squaring or multiplying it + g[w].copyTo(r); + is1 = false; + } + else { + while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } + if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } + z.mulTo(r2,g[w],r); + } + + while(j >= 0 && (e[j]&(1< 0) { + x.rShiftTo(g,x); + y.rShiftTo(g,y); + } + while(x.signum() > 0) { + if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); + if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); + if(x.compareTo(y) >= 0) { + x.subTo(y,x); + x.rShiftTo(1,x); + } + else { + y.subTo(x,y); + y.rShiftTo(1,y); + } + } + if(g > 0) y.lShiftTo(g,y); + return y; +} + +// (protected) this % n, n < 2^26 +function bnpModInt(n) { + if(n <= 0) return 0; + var d = this.DV%n, r = (this.s<0)?n-1:0; + if(this.t > 0) + if(d == 0) r = this[0]%n; + else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; + return r; +} + +// (public) 1/this % m (HAC 14.61) +function bnModInverse(m) { + var ac = m.isEven(); + if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; + var u = m.clone(), v = this.clone(); + var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); + while(u.signum() != 0) { + while(u.isEven()) { + u.rShiftTo(1,u); + if(ac) { + if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } + a.rShiftTo(1,a); + } + else if(!b.isEven()) b.subTo(m,b); + b.rShiftTo(1,b); + } + while(v.isEven()) { + v.rShiftTo(1,v); + if(ac) { + if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } + c.rShiftTo(1,c); + } + else if(!d.isEven()) d.subTo(m,d); + d.rShiftTo(1,d); + } + if(u.compareTo(v) >= 0) { + u.subTo(v,u); + if(ac) a.subTo(c,a); + b.subTo(d,b); + } + else { + v.subTo(u,v); + if(ac) c.subTo(a,c); + d.subTo(b,d); + } + } + if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; + if(d.compareTo(m) >= 0) return d.subtract(m); + if(d.signum() < 0) d.addTo(m,d); else return d; + if(d.signum() < 0) return d.add(m); else return d; +} + +// protected +proto.chunkSize = bnpChunkSize; +proto.toRadix = bnpToRadix; +proto.fromRadix = bnpFromRadix; +proto.fromNumber = bnpFromNumber; +proto.bitwiseTo = bnpBitwiseTo; +proto.changeBit = bnpChangeBit; +proto.addTo = bnpAddTo; +proto.dMultiply = bnpDMultiply; +proto.dAddOffset = bnpDAddOffset; +proto.multiplyLowerTo = bnpMultiplyLowerTo; +proto.multiplyUpperTo = bnpMultiplyUpperTo; +proto.modInt = bnpModInt; + +// public +proto.clone = bnClone; +proto.intValue = bnIntValue; +proto.byteValue = bnByteValue; +proto.shortValue = bnShortValue; +proto.signum = bnSigNum; +proto.toByteArray = bnToByteArray; +proto.equals = bnEquals; +proto.min = bnMin; +proto.max = bnMax; +proto.and = bnAnd; +proto.or = bnOr; +proto.xor = bnXor; +proto.andNot = bnAndNot; +proto.not = bnNot; +proto.shiftLeft = bnShiftLeft; +proto.shiftRight = bnShiftRight; +proto.getLowestSetBit = bnGetLowestSetBit; +proto.bitCount = bnBitCount; +proto.testBit = bnTestBit; +proto.setBit = bnSetBit; +proto.clearBit = bnClearBit; +proto.flipBit = bnFlipBit; +proto.add = bnAdd; +proto.subtract = bnSubtract; +proto.multiply = bnMultiply; +proto.divide = bnDivide; +proto.remainder = bnRemainder; +proto.divideAndRemainder = bnDivideAndRemainder; +proto.modPow = bnModPow; +proto.modInverse = bnModInverse; +proto.pow = bnPow; +proto.gcd = bnGCD; + +// JSBN-specific extension +proto.square = bnSquare; + +// BigInteger interfaces not implemented in jsbn: + +// BigInteger(int signum, byte[] magnitude) +// double doubleValue() +// float floatValue() +// int hashCode() +// long longValue() +// static BigInteger valueOf(long val) // "constants" BigInteger.ZERO = nbv(0); BigInteger.ONE = nbv(1); +BigInteger.valueOf = nbv; + + +/// bitcoinjs addons + +/** + * Turns a byte array into a big integer. + * + * This function will interpret a byte array as a big integer in big + * endian notation and ignore leading zeros. + */ +BigInteger.fromByteArrayUnsigned = function(ba) { + if (!ba.length) { + return new BigInteger.valueOf(0); + } else if (ba[0] & 0x80) { + // Prepend a zero so the BigInteger class doesn't mistake this + // for a negative integer. + return new BigInteger([0].concat(ba)); + } else { + return new BigInteger(ba); + } +}; + +/** + * Parse a signed big integer byte representation. + * + * For details on the format please see BigInteger.toByteArraySigned. + */ +BigInteger.fromByteArraySigned = function(ba) { + // Check for negative value + if (ba[0] & 0x80) { + // Remove sign bit + ba[0] &= 0x7f; + + return BigInteger.fromByteArrayUnsigned(ba).negate(); + } else { + return BigInteger.fromByteArrayUnsigned(ba); + } +}; + +/** + * Returns a byte array representation of the big integer. + * + * This returns the absolute of the contained value in big endian + * form. A value of zero results in an empty array. + */ +BigInteger.prototype.toByteArrayUnsigned = function() { + var ba = this.abs().toByteArray(); + + // Empty array, nothing to do + if (!ba.length) { + return ba; + } + + // remove leading 0 + if (ba[0] === 0) { + ba = ba.slice(1); + } + + // all values must be positive + for (var i=0 ; i 0x00 + * 1 => 0x01 + * -1 => 0x81 + * 127 => 0x7f + * -127 => 0xff + * 128 => 0x0080 + * -128 => 0x8080 + * 255 => 0x00ff + * -255 => 0x80ff + * 16300 => 0x3fac + * -16300 => 0xbfac + * 62300 => 0x00f35c + * -62300 => 0x80f35c +*/ +BigInteger.prototype.toByteArraySigned = function() { + var val = this.toByteArrayUnsigned(); + var neg = this.s < 0; + + // if the first bit is set, we always unshift + // either unshift 0x80 or 0x00 + if (val[0] & 0x80) { + val.unshift((neg) ? 0x80 : 0x00); + } + // if the first bit isn't set, set it if negative + else if (neg) { + val[0] |= 0x80; + } + + return val; +}; + +module.exports = BigInteger; diff --git a/src/jsbn/jsbn2.js b/src/jsbn/jsbn2.js deleted file mode 100644 index 5b2b725..0000000 --- a/src/jsbn/jsbn2.js +++ /dev/null @@ -1,656 +0,0 @@ -// Copyright (c) 2005-2009 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Extended JavaScript BN functions, required for RSA private ops. - -// Version 1.1: new BigInteger("0", 10) returns "proper" zero -// Version 1.2: square() API, isProbablePrime fix - -// (public) -function bnClone() { var r = nbi(); this.copyTo(r); return r; } - -// (public) return value as integer -function bnIntValue() { - if(this.s < 0) { - if(this.t == 1) return this[0]-this.DV; - else if(this.t == 0) return -1; - } - else if(this.t == 1) return this[0]; - else if(this.t == 0) return 0; - // assumes 16 < DB < 32 - return ((this[1]&((1<<(32-this.DB))-1))<>24; } - -// (public) return value as short (assumes DB>=16) -function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } - -// (protected) return x s.t. r^x < DV -function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } - -// (public) 0 if this == 0, 1 if this > 0 -function bnSigNum() { - if(this.s < 0) return -1; - else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; - else return 1; -} - -// (protected) convert to radix string -function bnpToRadix(b) { - if(b == null) b = 10; - if(this.signum() == 0 || b < 2 || b > 36) return "0"; - var cs = this.chunkSize(b); - var a = Math.pow(b,cs); - var d = nbv(a), y = nbi(), z = nbi(), r = ""; - this.divRemTo(d,y,z); - while(y.signum() > 0) { - r = (a+z.intValue()).toString(b).substr(1) + r; - y.divRemTo(d,y,z); - } - return z.intValue().toString(b) + r; -} - -// (protected) convert from radix string -function bnpFromRadix(s,b) { - this.fromInt(0); - if(b == null) b = 10; - var cs = this.chunkSize(b); - var d = Math.pow(b,cs), mi = false, j = 0, w = 0; - for(var i = 0; i < s.length; ++i) { - var x = intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-" && this.signum() == 0) mi = true; - continue; - } - w = b*w+x; - if(++j >= cs) { - this.dMultiply(d); - this.dAddOffset(w,0); - j = 0; - w = 0; - } - } - if(j > 0) { - this.dMultiply(Math.pow(b,j)); - this.dAddOffset(w,0); - } - if(mi) BigInteger.ZERO.subTo(this,this); -} - -// (protected) alternate constructor -function bnpFromNumber(a,b,c) { - if("number" == typeof b) { - // new BigInteger(int,int,RNG) - if(a < 2) this.fromInt(1); - else { - this.fromNumber(a,c); - if(!this.testBit(a-1)) // force MSB set - this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); - if(this.isEven()) this.dAddOffset(1,0); // force odd - while(!this.isProbablePrime(b)) { - this.dAddOffset(2,0); - if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); - } - } - } - else { - // new BigInteger(int,RNG) - var x = new Array(), t = a&7; - x.length = (a>>3)+1; - b.nextBytes(x); - if(t > 0) x[0] &= ((1< 0) { - if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) - r[k++] = d|(this.s<<(this.DB-p)); - while(i >= 0) { - if(p < 8) { - d = (this[i]&((1<>(p+=this.DB-8); - } - else { - d = (this[i]>>(p-=8))&0xff; - if(p <= 0) { p += this.DB; --i; } - } - if((d&0x80) != 0) d |= -256; - if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; - if(k > 0 || d != this.s) r[k++] = d; - } - } - return r; -} - -function bnEquals(a) { return(this.compareTo(a)==0); } -function bnMin(a) { return(this.compareTo(a)<0)?this:a; } -function bnMax(a) { return(this.compareTo(a)>0)?this:a; } - -// (protected) r = this op a (bitwise) -function bnpBitwiseTo(a,op,r) { - var i, f, m = Math.min(a.t,this.t); - for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); - if(a.t < this.t) { - f = a.s&this.DM; - for(i = m; i < this.t; ++i) r[i] = op(this[i],f); - r.t = this.t; - } - else { - f = this.s&this.DM; - for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); - r.t = a.t; - } - r.s = op(this.s,a.s); - r.clamp(); -} - -// (public) this & a -function op_and(x,y) { return x&y; } -function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } - -// (public) this | a -function op_or(x,y) { return x|y; } -function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } - -// (public) this ^ a -function op_xor(x,y) { return x^y; } -function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } - -// (public) this & ~a -function op_andnot(x,y) { return x&~y; } -function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } - -// (public) ~this -function bnNot() { - var r = nbi(); - for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; - r.t = this.t; - r.s = ~this.s; - return r; -} - -// (public) this << n -function bnShiftLeft(n) { - var r = nbi(); - if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); - return r; -} - -// (public) this >> n -function bnShiftRight(n) { - var r = nbi(); - if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); - return r; -} - -// return index of lowest 1-bit in x, x < 2^31 -function lbit(x) { - if(x == 0) return -1; - var r = 0; - if((x&0xffff) == 0) { x >>= 16; r += 16; } - if((x&0xff) == 0) { x >>= 8; r += 8; } - if((x&0xf) == 0) { x >>= 4; r += 4; } - if((x&3) == 0) { x >>= 2; r += 2; } - if((x&1) == 0) ++r; - return r; -} - -// (public) returns index of lowest 1-bit (or -1 if none) -function bnGetLowestSetBit() { - for(var i = 0; i < this.t; ++i) - if(this[i] != 0) return i*this.DB+lbit(this[i]); - if(this.s < 0) return this.t*this.DB; - return -1; -} - -// return number of 1 bits in x -function cbit(x) { - var r = 0; - while(x != 0) { x &= x-1; ++r; } - return r; -} - -// (public) return number of set bits -function bnBitCount() { - var r = 0, x = this.s&this.DM; - for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); - return r; -} - -// (public) true iff nth bit is set -function bnTestBit(n) { - var j = Math.floor(n/this.DB); - if(j >= this.t) return(this.s!=0); - return((this[j]&(1<<(n%this.DB)))!=0); -} - -// (protected) this op (1<>= this.DB; - } - if(a.t < this.t) { - c += a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c += a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += a.s; - } - r.s = (c<0)?-1:0; - if(c > 0) r[i++] = c; - else if(c < -1) r[i++] = this.DV+c; - r.t = i; - r.clamp(); -} - -// (public) this + a -function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } - -// (public) this - a -function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } - -// (public) this * a -function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } - -// (public) this^2 -function bnSquare() { var r = nbi(); this.squareTo(r); return r; } - -// (public) this / a -function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } - -// (public) this % a -function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } - -// (public) [this/a,this%a] -function bnDivideAndRemainder(a) { - var q = nbi(), r = nbi(); - this.divRemTo(a,q,r); - return new Array(q,r); -} - -// (protected) this *= n, this >= 0, 1 < n < DV -function bnpDMultiply(n) { - this[this.t] = this.am(0,n-1,this,0,0,this.t); - ++this.t; - this.clamp(); -} - -// (protected) this += n << w words, this >= 0 -function bnpDAddOffset(n,w) { - if(n == 0) return; - while(this.t <= w) this[this.t++] = 0; - this[w] += n; - while(this[w] >= this.DV) { - this[w] -= this.DV; - if(++w >= this.t) this[this.t++] = 0; - ++this[w]; - } -} - -// A "null" reducer -function NullExp() {} -function nNop(x) { return x; } -function nMulTo(x,y,r) { x.multiplyTo(y,r); } -function nSqrTo(x,r) { x.squareTo(r); } - -NullExp.prototype.convert = nNop; -NullExp.prototype.revert = nNop; -NullExp.prototype.mulTo = nMulTo; -NullExp.prototype.sqrTo = nSqrTo; - -// (public) this^e -function bnPow(e) { return this.exp(e,new NullExp()); } - -// (protected) r = lower n words of "this * a", a.t <= n -// "this" should be the larger one if appropriate. -function bnpMultiplyLowerTo(a,n,r) { - var i = Math.min(this.t+a.t,n); - r.s = 0; // assumes a,this >= 0 - r.t = i; - while(i > 0) r[--i] = 0; - var j; - for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); - for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); - r.clamp(); -} - -// (protected) r = "this * a" without lower n words, n > 0 -// "this" should be the larger one if appropriate. -function bnpMultiplyUpperTo(a,n,r) { - --n; - var i = r.t = this.t+a.t-n; - r.s = 0; // assumes a,this >= 0 - while(--i >= 0) r[i] = 0; - for(i = Math.max(n-this.t,0); i < a.t; ++i) - r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); - r.clamp(); - r.drShiftTo(1,r); -} - -// Barrett modular reduction -function Barrett(m) { - // setup Barrett - this.r2 = nbi(); - this.q3 = nbi(); - BigInteger.ONE.dlShiftTo(2*m.t,this.r2); - this.mu = this.r2.divide(m); - this.m = m; -} - -function barrettConvert(x) { - if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); - else if(x.compareTo(this.m) < 0) return x; - else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } -} - -function barrettRevert(x) { return x; } - -// x = x mod m (HAC 14.42) -function barrettReduce(x) { - x.drShiftTo(this.m.t-1,this.r2); - if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } - this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); - this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); - while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); - x.subTo(this.r2,x); - while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = x^2 mod m; x != r -function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = x*y mod m; x,y != r -function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Barrett.prototype.convert = barrettConvert; -Barrett.prototype.revert = barrettRevert; -Barrett.prototype.reduce = barrettReduce; -Barrett.prototype.mulTo = barrettMulTo; -Barrett.prototype.sqrTo = barrettSqrTo; - -// (public) this^e % m (HAC 14.85) -function bnModPow(e,m) { - var i = e.bitLength(), k, r = nbv(1), z; - if(i <= 0) return r; - else if(i < 18) k = 1; - else if(i < 48) k = 3; - else if(i < 144) k = 4; - else if(i < 768) k = 5; - else k = 6; - if(i < 8) - z = new Classic(m); - else if(m.isEven()) - z = new Barrett(m); - else - z = new Montgomery(m); - - // precomputation - var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { - var g2 = nbi(); - z.sqrTo(g[1],g2); - while(n <= km) { - g[n] = nbi(); - z.mulTo(g2,g[n-2],g[n]); - n += 2; - } - } - - var j = e.t-1, w, is1 = true, r2 = nbi(), t; - i = nbits(e[j])-1; - while(j >= 0) { - if(i >= k1) w = (e[j]>>(i-k1))&km; - else { - w = (e[j]&((1<<(i+1))-1))<<(k1-i); - if(j > 0) w |= e[j-1]>>(this.DB+i-k1); - } - - n = k; - while((w&1) == 0) { w >>= 1; --n; } - if((i -= n) < 0) { i += this.DB; --j; } - if(is1) { // ret == 1, don't bother squaring or multiplying it - g[w].copyTo(r); - is1 = false; - } - else { - while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } - if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } - z.mulTo(r2,g[w],r); - } - - while(j >= 0 && (e[j]&(1< 0) { - x.rShiftTo(g,x); - y.rShiftTo(g,y); - } - while(x.signum() > 0) { - if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); - if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); - if(x.compareTo(y) >= 0) { - x.subTo(y,x); - x.rShiftTo(1,x); - } - else { - y.subTo(x,y); - y.rShiftTo(1,y); - } - } - if(g > 0) y.lShiftTo(g,y); - return y; -} - -// (protected) this % n, n < 2^26 -function bnpModInt(n) { - if(n <= 0) return 0; - var d = this.DV%n, r = (this.s<0)?n-1:0; - if(this.t > 0) - if(d == 0) r = this[0]%n; - else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; - return r; -} - -// (public) 1/this % m (HAC 14.61) -function bnModInverse(m) { - var ac = m.isEven(); - if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; - var u = m.clone(), v = this.clone(); - var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); - while(u.signum() != 0) { - while(u.isEven()) { - u.rShiftTo(1,u); - if(ac) { - if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } - a.rShiftTo(1,a); - } - else if(!b.isEven()) b.subTo(m,b); - b.rShiftTo(1,b); - } - while(v.isEven()) { - v.rShiftTo(1,v); - if(ac) { - if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } - c.rShiftTo(1,c); - } - else if(!d.isEven()) d.subTo(m,d); - d.rShiftTo(1,d); - } - if(u.compareTo(v) >= 0) { - u.subTo(v,u); - if(ac) a.subTo(c,a); - b.subTo(d,b); - } - else { - v.subTo(u,v); - if(ac) c.subTo(a,c); - d.subTo(b,d); - } - } - if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; - if(d.compareTo(m) >= 0) return d.subtract(m); - if(d.signum() < 0) d.addTo(m,d); else return d; - if(d.signum() < 0) return d.add(m); else return d; -} - -var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; -var lplim = (1<<26)/lowprimes[lowprimes.length-1]; - -// (public) test primality with certainty >= 1-.5^t -function bnIsProbablePrime(t) { - var i, x = this.abs(); - if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { - for(i = 0; i < lowprimes.length; ++i) - if(x[0] == lowprimes[i]) return true; - return false; - } - if(x.isEven()) return false; - i = 1; - while(i < lowprimes.length) { - var m = lowprimes[i], j = i+1; - while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; - m = x.modInt(m); - while(i < j) if(m%lowprimes[i++] == 0) return false; - } - return x.millerRabin(t); -} - -// (protected) true if probably prime (HAC 4.24, Miller-Rabin) -function bnpMillerRabin(t) { - var n1 = this.subtract(BigInteger.ONE); - var k = n1.getLowestSetBit(); - if(k <= 0) return false; - var r = n1.shiftRight(k); - t = (t+1)>>1; - if(t > lowprimes.length) t = lowprimes.length; - var a = nbi(); - for(var i = 0; i < t; ++i) { - //Pick bases at random, instead of starting at 2 - a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); - var y = a.modPow(r,this); - if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { - var j = 1; - while(j++ < k && y.compareTo(n1) != 0) { - y = y.modPowInt(2,this); - if(y.compareTo(BigInteger.ONE) == 0) return false; - } - if(y.compareTo(n1) != 0) return false; - } - } - return true; -} - -// protected -BigInteger.prototype.chunkSize = bnpChunkSize; -BigInteger.prototype.toRadix = bnpToRadix; -BigInteger.prototype.fromRadix = bnpFromRadix; -BigInteger.prototype.fromNumber = bnpFromNumber; -BigInteger.prototype.bitwiseTo = bnpBitwiseTo; -BigInteger.prototype.changeBit = bnpChangeBit; -BigInteger.prototype.addTo = bnpAddTo; -BigInteger.prototype.dMultiply = bnpDMultiply; -BigInteger.prototype.dAddOffset = bnpDAddOffset; -BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; -BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; -BigInteger.prototype.modInt = bnpModInt; -BigInteger.prototype.millerRabin = bnpMillerRabin; - -// public -BigInteger.prototype.clone = bnClone; -BigInteger.prototype.intValue = bnIntValue; -BigInteger.prototype.byteValue = bnByteValue; -BigInteger.prototype.shortValue = bnShortValue; -BigInteger.prototype.signum = bnSigNum; -BigInteger.prototype.toByteArray = bnToByteArray; -BigInteger.prototype.equals = bnEquals; -BigInteger.prototype.min = bnMin; -BigInteger.prototype.max = bnMax; -BigInteger.prototype.and = bnAnd; -BigInteger.prototype.or = bnOr; -BigInteger.prototype.xor = bnXor; -BigInteger.prototype.andNot = bnAndNot; -BigInteger.prototype.not = bnNot; -BigInteger.prototype.shiftLeft = bnShiftLeft; -BigInteger.prototype.shiftRight = bnShiftRight; -BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; -BigInteger.prototype.bitCount = bnBitCount; -BigInteger.prototype.testBit = bnTestBit; -BigInteger.prototype.setBit = bnSetBit; -BigInteger.prototype.clearBit = bnClearBit; -BigInteger.prototype.flipBit = bnFlipBit; -BigInteger.prototype.add = bnAdd; -BigInteger.prototype.subtract = bnSubtract; -BigInteger.prototype.multiply = bnMultiply; -BigInteger.prototype.divide = bnDivide; -BigInteger.prototype.remainder = bnRemainder; -BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; -BigInteger.prototype.modPow = bnModPow; -BigInteger.prototype.modInverse = bnModInverse; -BigInteger.prototype.pow = bnPow; -BigInteger.prototype.gcd = bnGCD; -BigInteger.prototype.isProbablePrime = bnIsProbablePrime; - -// JSBN-specific extension -BigInteger.prototype.square = bnSquare; - -// BigInteger interfaces not implemented in jsbn: - -// BigInteger(int signum, byte[] magnitude) -// double doubleValue() -// float floatValue() -// int hashCode() -// long longValue() -// static BigInteger valueOf(long val) diff --git a/src/jsbn/prng4.js b/src/jsbn/prng4.js index 3034f3f..32bdb5e 100644 --- a/src/jsbn/prng4.js +++ b/src/jsbn/prng4.js @@ -35,11 +35,5 @@ function ARC4next() { Arcfour.prototype.init = ARC4init; Arcfour.prototype.next = ARC4next; -// Plug in your RNG constructor here -function prng_newstate() { - return new Arcfour(); -} +module.exports = Arcfour; -// Pool size must be a multiple of 4 and greater than 32. -// An array of bytes the size of the pool will be passed to init() -var rng_psize = 256; diff --git a/src/jsbn/rng.js b/src/jsbn/rng.js index 03afc3a..bba8fb2 100644 --- a/src/jsbn/rng.js +++ b/src/jsbn/rng.js @@ -1,4 +1,16 @@ // Random number generator - requires a PRNG backend, e.g. prng4.js +// prng4.js - uses Arcfour as a PRNG + +var Arcfour = require('./prng4'); + +// Plug in your RNG constructor here +function prng_newstate() { + return new Arcfour(); +} + +// Pool size must be a multiple of 4 and greater than 32. +// An array of bytes the size of the pool will be passed to init() +var rng_psize = 256; // For best results, put code like // @@ -27,12 +39,15 @@ if(rng_pool == null) { rng_pool = new Array(); rng_pptr = 0; var t; + // TODO(shtylman) use browser crypto if available + /* if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { // Extract entropy (256 bits) from NS4 RNG if available var z = window.crypto.random(32); for(t = 0; t < z.length; ++t) rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; - } + } + */ while(rng_pptr < rng_psize) { // extract some randomness from Math.random() t = Math.floor(65536 * Math.random()); rng_pool[rng_pptr++] = t >>> 8; @@ -66,3 +81,5 @@ function rng_get_bytes(ba) { function SecureRandom() {} SecureRandom.prototype.nextBytes = rng_get_bytes; + +module.exports = SecureRandom; diff --git a/src/jsbn/sec.js b/src/jsbn/sec.js old mode 100755 new mode 100644 index e496571..5a95700 --- a/src/jsbn/sec.js +++ b/src/jsbn/sec.js @@ -2,6 +2,9 @@ // Requires ec.js, jsbn.js, and jsbn2.js +var ECCurveFp = require('./ec'); +var BigInteger = require('./jsbn'); + // ---------------- // X9ECParameters @@ -171,3 +174,5 @@ function getSECCurveByName(name) { if(name == "secp256r1") return secp256r1(); return null; } + +module.exports = getSECCurveByName; diff --git a/src/message.js b/src/message.js index a2dabc0..0cbd581 100644 --- a/src/message.js +++ b/src/message.js @@ -1,69 +1,67 @@ /** * Implements Bitcoin's feature for signing arbitrary messages. */ -Bitcoin.Message = (function () { - var Message = {}; +var Message = {}; - Message.magicPrefix = "Bitcoin Signed Message:\n"; +Message.magicPrefix = "Bitcoin Signed Message:\n"; - Message.makeMagicMessage = function (message) { - var magicBytes = Crypto.charenc.UTF8.stringToBytes(Message.magicPrefix); - var messageBytes = Crypto.charenc.UTF8.stringToBytes(message); +Message.makeMagicMessage = function (message) { + var magicBytes = Crypto.charenc.UTF8.stringToBytes(Message.magicPrefix); + var messageBytes = Crypto.charenc.UTF8.stringToBytes(message); - var buffer = []; - buffer = buffer.concat(Bitcoin.Util.numToVarInt(magicBytes.length)); - buffer = buffer.concat(magicBytes); - buffer = buffer.concat(Bitcoin.Util.numToVarInt(messageBytes.length)); - buffer = buffer.concat(messageBytes); + var buffer = []; + buffer = buffer.concat(Bitcoin.Util.numToVarInt(magicBytes.length)); + buffer = buffer.concat(magicBytes); + buffer = buffer.concat(Bitcoin.Util.numToVarInt(messageBytes.length)); + buffer = buffer.concat(messageBytes); - return buffer; - }; + return buffer; +}; - Message.getHash = function (message) { - var buffer = Message.makeMagicMessage(message); - return Crypto.SHA256(Crypto.SHA256(buffer, {asBytes: true}), {asBytes: true}); - }; +Message.getHash = function (message) { + var buffer = Message.makeMagicMessage(message); + return Crypto.SHA256(Crypto.SHA256(buffer, {asBytes: true}), {asBytes: true}); +}; - Message.signMessage = function (key, message, compressed) { - var hash = Message.getHash(message); +Message.signMessage = function (key, message, compressed) { + var hash = Message.getHash(message); - var sig = key.sign(hash); + var sig = key.sign(hash); - var obj = Bitcoin.ECDSA.parseSig(sig); + var obj = Bitcoin.ECDSA.parseSig(sig); - var address = key.getBitcoinAddress().toString(); - var i = Bitcoin.ECDSA.calcPubkeyRecoveryParam(address, obj.r, obj.s, hash); + var address = key.getBitcoinAddress().toString(); + var i = Bitcoin.ECDSA.calcPubkeyRecoveryParam(address, obj.r, obj.s, hash); - i += 27; - if (compressed) i += 4; + i += 27; + if (compressed) i += 4; - var rBa = obj.r.toByteArrayUnsigned(); - var sBa = obj.s.toByteArrayUnsigned(); + var rBa = obj.r.toByteArrayUnsigned(); + var sBa = obj.s.toByteArrayUnsigned(); - // Pad to 32 bytes per value - while (rBa.length < 32) rBa.unshift(0); - while (sBa.length < 32) sBa.unshift(0); + // Pad to 32 bytes per value + while (rBa.length < 32) rBa.unshift(0); + while (sBa.length < 32) sBa.unshift(0); - sig = [i].concat(rBa).concat(sBa); + sig = [i].concat(rBa).concat(sBa); - return Crypto.util.bytesToBase64(sig); - }; + return Crypto.util.bytesToBase64(sig); +}; - Message.verifyMessage = function (address, sig, message) { - sig = Crypto.util.base64ToBytes(sig); - sig = Bitcoin.ECDSA.parseSigCompact(sig); +Message.verifyMessage = function (address, sig, message) { + sig = Crypto.util.base64ToBytes(sig); + sig = Bitcoin.ECDSA.parseSigCompact(sig); - var hash = Message.getHash(message); + var hash = Message.getHash(message); - var isCompressed = !!(sig.i & 4); - var pubKey = Bitcoin.ECDSA.recoverPubKey(sig.r, sig.s, hash, sig.i); + var isCompressed = !!(sig.i & 4); + var pubKey = Bitcoin.ECDSA.recoverPubKey(sig.r, sig.s, hash, sig.i); - pubKey.setCompressed(isCompressed); + pubKey.setCompressed(isCompressed); - var expectedAddress = pubKey.getBitcoinAddress().toString(); + var expectedAddress = pubKey.getBitcoinAddress().toString(); - return (address === expectedAddress); - }; + return (address === expectedAddress); +}; - return Message; -})(); +module.exports = Message; diff --git a/src/opcode.js b/src/opcode.js index b21da46..0de4dcc 100644 --- a/src/opcode.js +++ b/src/opcode.js @@ -1,154 +1,154 @@ -(function () { - var Opcode = Bitcoin.Opcode = function (num) { - this.code = num; - }; - - Opcode.prototype.toString = function () { - return Opcode.reverseMap[this.code]; - }; - - 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, - - // control - OP_NOP : 97, - OP_VER : 98, - OP_IF : 99, - OP_NOTIF : 100, - OP_VERIF : 101, - OP_VERNOTIF : 102, - OP_ELSE : 103, - OP_ENDIF : 104, - OP_VERIFY : 105, - OP_RETURN : 106, - - // stack ops - OP_TOALTSTACK : 107, - OP_FROMALTSTACK : 108, - OP_2DROP : 109, - OP_2DUP : 110, - OP_3DUP : 111, - OP_2OVER : 112, - OP_2ROT : 113, - OP_2SWAP : 114, - OP_IFDUP : 115, - OP_DEPTH : 116, - OP_DROP : 117, - OP_DUP : 118, - OP_NIP : 119, - OP_OVER : 120, - OP_PICK : 121, - OP_ROLL : 122, - OP_ROT : 123, - OP_SWAP : 124, - OP_TUCK : 125, - - // splice ops - OP_CAT : 126, - OP_SUBSTR : 127, - OP_LEFT : 128, - OP_RIGHT : 129, - OP_SIZE : 130, - - // bit logic - OP_INVERT : 131, - OP_AND : 132, - OP_OR : 133, - OP_XOR : 134, - OP_EQUAL : 135, - OP_EQUALVERIFY : 136, - OP_RESERVED1 : 137, - OP_RESERVED2 : 138, - - // numeric - OP_1ADD : 139, - OP_1SUB : 140, - OP_2MUL : 141, - OP_2DIV : 142, - OP_NEGATE : 143, - OP_ABS : 144, - OP_NOT : 145, - OP_0NOTEQUAL : 146, - - OP_ADD : 147, - OP_SUB : 148, - OP_MUL : 149, - OP_DIV : 150, - OP_MOD : 151, - OP_LSHIFT : 152, - OP_RSHIFT : 153, - - OP_BOOLAND : 154, - OP_BOOLOR : 155, - OP_NUMEQUAL : 156, - OP_NUMEQUALVERIFY : 157, - OP_NUMNOTEQUAL : 158, - OP_LESSTHAN : 159, - OP_GREATERTHAN : 160, - OP_LESSTHANOREQUAL : 161, - OP_GREATERTHANOREQUAL : 162, - OP_MIN : 163, - OP_MAX : 164, - - OP_WITHIN : 165, - - // crypto - OP_RIPEMD160 : 166, - OP_SHA1 : 167, - OP_SHA256 : 168, - OP_HASH160 : 169, - OP_HASH256 : 170, - OP_CODESEPARATOR : 171, - OP_CHECKSIG : 172, - OP_CHECKSIGVERIFY : 173, - OP_CHECKMULTISIG : 174, - OP_CHECKMULTISIGVERIFY : 175, - - // expansion - OP_NOP1 : 176, - OP_NOP2 : 177, - OP_NOP3 : 178, - OP_NOP4 : 179, - OP_NOP5 : 180, - OP_NOP6 : 181, - OP_NOP7 : 182, - OP_NOP8 : 183, - OP_NOP9 : 184, - OP_NOP10 : 185, - - // template matching params - OP_PUBKEYHASH : 253, - OP_PUBKEY : 254, - OP_INVALIDOPCODE : 255 - }; - - Opcode.reverseMap = []; - - for (var i in Opcode.map) { - Opcode.reverseMap[Opcode.map[i]] = i; - } -})(); +var Opcode = function (num) { + this.code = num; +}; + +Opcode.prototype.toString = function () { + return Opcode.reverseMap[this.code]; +}; + +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, + + // control + OP_NOP : 97, + OP_VER : 98, + OP_IF : 99, + OP_NOTIF : 100, + OP_VERIF : 101, + OP_VERNOTIF : 102, + OP_ELSE : 103, + OP_ENDIF : 104, + OP_VERIFY : 105, + OP_RETURN : 106, + + // stack ops + OP_TOALTSTACK : 107, + OP_FROMALTSTACK : 108, + OP_2DROP : 109, + OP_2DUP : 110, + OP_3DUP : 111, + OP_2OVER : 112, + OP_2ROT : 113, + OP_2SWAP : 114, + OP_IFDUP : 115, + OP_DEPTH : 116, + OP_DROP : 117, + OP_DUP : 118, + OP_NIP : 119, + OP_OVER : 120, + OP_PICK : 121, + OP_ROLL : 122, + OP_ROT : 123, + OP_SWAP : 124, + OP_TUCK : 125, + + // splice ops + OP_CAT : 126, + OP_SUBSTR : 127, + OP_LEFT : 128, + OP_RIGHT : 129, + OP_SIZE : 130, + + // bit logic + OP_INVERT : 131, + OP_AND : 132, + OP_OR : 133, + OP_XOR : 134, + OP_EQUAL : 135, + OP_EQUALVERIFY : 136, + OP_RESERVED1 : 137, + OP_RESERVED2 : 138, + + // numeric + OP_1ADD : 139, + OP_1SUB : 140, + OP_2MUL : 141, + OP_2DIV : 142, + OP_NEGATE : 143, + OP_ABS : 144, + OP_NOT : 145, + OP_0NOTEQUAL : 146, + + OP_ADD : 147, + OP_SUB : 148, + OP_MUL : 149, + OP_DIV : 150, + OP_MOD : 151, + OP_LSHIFT : 152, + OP_RSHIFT : 153, + + OP_BOOLAND : 154, + OP_BOOLOR : 155, + OP_NUMEQUAL : 156, + OP_NUMEQUALVERIFY : 157, + OP_NUMNOTEQUAL : 158, + OP_LESSTHAN : 159, + OP_GREATERTHAN : 160, + OP_LESSTHANOREQUAL : 161, + OP_GREATERTHANOREQUAL : 162, + OP_MIN : 163, + OP_MAX : 164, + + OP_WITHIN : 165, + + // crypto + OP_RIPEMD160 : 166, + OP_SHA1 : 167, + OP_SHA256 : 168, + OP_HASH160 : 169, + OP_HASH256 : 170, + OP_CODESEPARATOR : 171, + OP_CHECKSIG : 172, + OP_CHECKSIGVERIFY : 173, + OP_CHECKMULTISIG : 174, + OP_CHECKMULTISIGVERIFY : 175, + + // expansion + OP_NOP1 : 176, + OP_NOP2 : 177, + OP_NOP3 : 178, + OP_NOP4 : 179, + OP_NOP5 : 180, + OP_NOP6 : 181, + OP_NOP7 : 182, + OP_NOP8 : 183, + OP_NOP9 : 184, + OP_NOP10 : 185, + + // template matching params + OP_PUBKEYHASH : 253, + OP_PUBKEY : 254, + OP_INVALIDOPCODE : 255 +}; + +Opcode.reverseMap = []; + +for (var i in Opcode.map) { + Opcode.reverseMap[Opcode.map[i]] = i; +} + +module.exports = Opcode; diff --git a/src/script.js b/src/script.js index 71f1ef5..7990026 100644 --- a/src/script.js +++ b/src/script.js @@ -1,347 +1,369 @@ -(function () { - var Opcode = Bitcoin.Opcode; +var Opcode = require('./opcode'); +var util = require('./util'); +var conv = require('./convert'); - // Make opcodes available as pseudo-constants - for (var i in Opcode.map) { - eval("var " + i + " = " + Opcode.map[i] + ";"); +var Script = function (data) { + if (!data) { + this.buffer = []; + } else if ("string" == typeof data) { + this.buffer = Crypto.util.base64ToBytes(data); + } else if (util.isArray(data)) { + this.buffer = data; + } else if (data instanceof Script) { + this.buffer = data.buffer; + } else { + throw new Error("Invalid script"); } - var Script = Bitcoin.Script = function (data) { - if (!data) { - this.buffer = []; - } else if ("string" == typeof data) { - this.buffer = Crypto.util.base64ToBytes(data); - } else if (Bitcoin.Util.isArray(data)) { - this.buffer = data; - } else if (data instanceof Script) { - this.buffer = data.buffer; + this.parse(); +}; + +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 { - throw new Error("Invalid script"); + script.writeBytes(conv.hexToBytes(s[i])); } + } + return script; +}; - this.parse(); - }; +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(conv.hexToBytes(s[i])); + } + } + return script; +}; - /** - * Update the parsed script representation. - * - * Each Script object stores the script in two formats. First as a raw byte - * array and second as an array of "chunks", such as opcodes and pieces of - * data. - * - * This method updates the chunks cache. Normally this is called by the - * constructor and you don't need to worry about it. However, if you change - * the script buffer manually, you should update the chunks using this method. - */ - Script.prototype.parse = function () { - var self = this; +/** + * Update the parsed script representation. + * + * Each Script object stores the script in two formats. First as a raw byte + * array and second as an array of "chunks", such as opcodes and pieces of + * data. + * + * This method updates the chunks cache. Normally this is called by the + * constructor and you don't need to worry about it. However, if you change + * the script buffer manually, you should update the chunks using this method. + */ +Script.prototype.parse = function () { + var self = this; - 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 < OP_PUSHDATA1) { - // Read some bytes of data, opcode value is the length of data - readChunk(opcode); - } else if (opcode == OP_PUSHDATA1) { - len = this.buffer[i++]; - readChunk(len); - } else if (opcode == OP_PUSHDATA2) { - len = (this.buffer[i++] << 8) | this.buffer[i++]; - readChunk(len); - } else if (opcode == 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. - * - * This method will compare the script to a small number of standard script - * templates and return a string naming the detected type. - * - * Currently supported are: - * Address: - * Paying to a Bitcoin address which is the hash of a pubkey. - * OP_DUP OP_HASH160 [pubKeyHash] OP_EQUALVERIFY OP_CHECKSIG - * - * Pubkey: - * Paying to a public key directly. - * [pubKey] OP_CHECKSIG - * - * Strange: - * Any other script (no template matched). - */ - Script.prototype.getOutType = function () { +/** + * Compare the script to known templates of scriptPubKey. + * + * This method will compare the script to a small number of standard script + * templates and return a string naming the detected type. + * + * Currently supported are: + * Address: + * Paying to a Bitcoin address which is the hash of a pubkey. + * OP_DUP OP_HASH160 [pubKeyHash] OP_EQUALVERIFY OP_CHECKSIG + * + * Pubkey: + * Paying to a public key directly. + * [pubKey] OP_CHECKSIG + * + * Strange: + * Any other script (no template matched). + */ +Script.prototype.getOutType = function () { +if (this.chunks[this.chunks.length-1] == Opcode.map.OP_CHECKMULTISIG && this.chunks[this.chunks.length-2] <= 3) { + // Transfer to M-OF-N + return 'Multisig'; +} 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 'Address'; +} else if (this.chunks.length == 2 && + this.chunks[1] == Opcode.map.OP_CHECKSIG) { + // Transfer to IP address + return 'Pubkey'; +} else { + return 'Strange'; +} +} - if (this.chunks[this.chunks.length-1] == OP_CHECKMULTISIG && this.chunks[this.chunks.length-2] <= 3) { - // Transfer to M-OF-N - return 'Multisig'; - } else if (this.chunks.length == 5 && - this.chunks[0] == OP_DUP && - this.chunks[1] == OP_HASH160 && - this.chunks[3] == OP_EQUALVERIFY && - this.chunks[4] == OP_CHECKSIG) { - // Transfer to Bitcoin address - return 'Address'; - } else if (this.chunks.length == 2 && - this.chunks[1] == OP_CHECKSIG) { - // Transfer to IP address +/** + * Returns the affected address hash for this output. + * + * For standard transactions, this will return the hash of the pubKey that + * can spend this output. + * + * In the future, for payToScriptHash outputs, this will return the + * scriptHash. Note that non-standard and standard payToScriptHash transactions + * look the same + * + * This method is useful for indexing transactions. + */ +Script.prototype.simpleOutHash = function () +{ + switch (this.getOutType()) { + case 'Address': + return this.chunks[2]; + case 'Pubkey': + return Bitcoin.Util.sha256ripe160(this.chunks[0]); + default: + throw new Error("Encountered non-standard scriptPubKey: " + this.getOutType()); + } +}; + +/** + * Old name for Script#simpleOutHash. + * + * @deprecated + */ +Script.prototype.simpleOutPubKeyHash = Script.prototype.simpleOutHash; + +/** + * Compare the script to known templates of scriptSig. + * + * This method will compare the script to a small number of standard script + * templates and return a string naming the detected type. + * + * WARNING: Use this method with caution. It merely represents a heuristic + * based on common transaction formats. A non-standard transaction could + * very easily match one of these templates by accident. + * + * Currently supported are: + * Address: + * Paying to a Bitcoin address which is the hash of a pubkey. + * [sig] [pubKey] + * + * Pubkey: + * Paying to a public key directly. + * [sig] + * + * Strange: + * Any other script (no template matched). + */ +Script.prototype.getInType = function () +{ + if (this.chunks.length == 1 && + Bitcoin.Util.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 && + util.isArray(this.chunks[0]) && + util.isArray(this.chunks[1])) { + return 'Address'; } else { return 'Strange'; - } -} + } +}; - /** - * Returns the affected address hash for this output. - * - * For standard transactions, this will return the hash of the pubKey that - * can spend this output. - * - * In the future, for payToScriptHash outputs, this will return the - * scriptHash. Note that non-standard and standard payToScriptHash transactions - * look the same - * - * This method is useful for indexing transactions. - */ - Script.prototype.simpleOutHash = function () - { - switch (this.getOutType()) { - case 'Address': - return this.chunks[2]; - case 'Pubkey': - return Bitcoin.Util.sha256ripe160(this.chunks[0]); - default: - throw new Error("Encountered non-standard scriptPubKey"); - } - }; +/** + * Returns the affected public key for this input. + * + * This currently only works with payToPubKeyHash transactions. It will also + * work in the future for standard payToScriptHash transactions that use a + * single public key. + * + * However for multi-key and other complex transactions, this will only return + * one of the keys or raise an error. Therefore, it is recommended for indexing + * purposes to use Script#simpleInHash or Script#simpleOutHash instead. + * + * @deprecated + */ +Script.prototype.simpleInPubKey = function () +{ + switch (this.getInType()) { + case 'Address': + return this.chunks[1]; + case 'Pubkey': + // TODO: Theoretically, we could recover the pubkey from the sig here. + // See https://bitcointalk.org/?topic=6430.0 + throw new Error("Script does not contain pubkey."); + default: + throw new Error("Encountered non-standard scriptSig"); + } +}; - /** - * Old name for Script#simpleOutHash. - * - * @deprecated - */ - Script.prototype.simpleOutPubKeyHash = Script.prototype.simpleOutHash; +/** + * Returns the affected address hash for this input. + * + * For standard transactions, this will return the hash of the pubKey that + * can spend this output. + * + * In the future, for standard payToScriptHash inputs, this will return the + * scriptHash. + * + * Note: This function provided for convenience. If you have the corresponding + * scriptPubKey available, you are urged to use Script#simpleOutHash instead + * as it is more reliable for non-standard payToScriptHash transactions. + * + * This method is useful for indexing transactions. + */ +Script.prototype.simpleInHash = function () +{ + return Bitcoin.Util.sha256ripe160(this.simpleInPubKey()); +}; - /** - * Compare the script to known templates of scriptSig. - * - * This method will compare the script to a small number of standard script - * templates and return a string naming the detected type. - * - * WARNING: Use this method with caution. It merely represents a heuristic - * based on common transaction formats. A non-standard transaction could - * very easily match one of these templates by accident. - * - * Currently supported are: - * Address: - * Paying to a Bitcoin address which is the hash of a pubkey. - * [sig] [pubKey] - * - * Pubkey: - * Paying to a public key directly. - * [sig] - * - * Strange: - * Any other script (no template matched). - */ - Script.prototype.getInType = function () - { - if (this.chunks.length == 1 && - Bitcoin.Util.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 && - Bitcoin.Util.isArray(this.chunks[0]) && - Bitcoin.Util.isArray(this.chunks[1])) { - return 'Address'; - } else { - return 'Strange'; - } - }; +/** + * Old name for Script#simpleInHash. + * + * @deprecated + */ +Script.prototype.simpleInPubKeyHash = Script.prototype.simpleInHash; - /** - * Returns the affected public key for this input. - * - * This currently only works with payToPubKeyHash transactions. It will also - * work in the future for standard payToScriptHash transactions that use a - * single public key. - * - * However for multi-key and other complex transactions, this will only return - * one of the keys or raise an error. Therefore, it is recommended for indexing - * purposes to use Script#simpleInHash or Script#simpleOutHash instead. - * - * @deprecated - */ - Script.prototype.simpleInPubKey = function () - { - switch (this.getInType()) { - case 'Address': - return this.chunks[1]; - case 'Pubkey': - // TODO: Theoretically, we could recover the pubkey from the sig here. - // See https://bitcointalk.org/?topic=6430.0 - throw new Error("Script does not contain pubkey."); - default: - throw new Error("Encountered non-standard scriptSig"); - } - }; +/** + * Add an op code to the script. + */ +Script.prototype.writeOp = function (opcode) +{ + this.buffer.push(opcode); + this.chunks.push(opcode); +}; - /** - * Returns the affected address hash for this input. - * - * For standard transactions, this will return the hash of the pubKey that - * can spend this output. - * - * In the future, for standard payToScriptHash inputs, this will return the - * scriptHash. - * - * Note: This function provided for convenience. If you have the corresponding - * scriptPubKey available, you are urged to use Script#simpleOutHash instead - * as it is more reliable for non-standard payToScriptHash transactions. - * - * This method is useful for indexing transactions. - */ - Script.prototype.simpleInHash = function () - { - return Bitcoin.Util.sha256ripe160(this.simpleInPubKey()); - }; +/** + * 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); +}; - /** - * Old name for Script#simpleInHash. - * - * @deprecated - */ - Script.prototype.simpleInPubKeyHash = Script.prototype.simpleInHash; +/** + * Create a standard payToPubKeyHash output. + */ +Script.createOutputScript = function (address) +{ + var script = new Script(); + 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; +}; - /** - * Add an op code to the script. - */ - Script.prototype.writeOp = function (opcode) - { - this.buffer.push(opcode); - this.chunks.push(opcode); - }; - /** - * Add a data chunk to the script. - */ - Script.prototype.writeBytes = function (data) - { - if (data.length < OP_PUSHDATA1) { - this.buffer.push(data.length); - } else if (data.length <= 0xff) { - this.buffer.push(OP_PUSHDATA1); - this.buffer.push(data.length); - } else if (data.length <= 0xffff) { - this.buffer.push(OP_PUSHDATA2); - this.buffer.push(data.length & 0xff); - this.buffer.push((data.length >>> 8) & 0xff); - } else { - this.buffer.push(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); +/** + * Extract bitcoin addresses from an output script + */ +Script.prototype.extractAddresses = function (addresses) +{ + switch (this.getOutType()) { + case 'Address': + addresses.push(new Address(this.chunks[2])); + return 1; + case 'Pubkey': + addresses.push(new Address(Util.sha256ripe160(this.chunks[0]))); + return 1; + case 'Multisig': + for (var i = 1; i < this.chunks.length-2; ++i) { + addresses.push(new Address(Util.sha256ripe160(this.chunks[i]))); } - this.buffer = this.buffer.concat(data); - this.chunks.push(data); - }; + return this.chunks[0] - OP_1 + 1; + default: + throw new Error("Encountered non-standard scriptPubKey"); + } +}; - /** - * Create a standard payToPubKeyHash output. - */ - Script.createOutputScript = function (address) - { - var script = new Script(); - script.writeOp(OP_DUP); - script.writeOp(OP_HASH160); - script.writeBytes(address.hash); - script.writeOp(OP_EQUALVERIFY); - script.writeOp(OP_CHECKSIG); - return script; - }; +/** + * Create an m-of-n output script + */ +Script.createMultiSigOutputScript = function (m, pubkeys) +{ + var script = new Script(); + script.writeOp(Opcode.map.OP_1 + m - 1); - /** - * Extract bitcoin addresses from an output script - */ - Script.prototype.extractAddresses = function (addresses) - { - switch (this.getOutType()) { - case 'Address': - addresses.push(new Address(this.chunks[2])); - return 1; - case 'Pubkey': - addresses.push(new Address(Util.sha256ripe160(this.chunks[0]))); - return 1; - case 'Multisig': - for (var i = 1; i < this.chunks.length-2; ++i) { - addresses.push(new Address(Util.sha256ripe160(this.chunks[i]))); - } - return this.chunks[0] - OP_1 + 1; - default: - throw new Error("Encountered non-standard scriptPubKey"); - } - }; + for (var i = 0; i < pubkeys.length; ++i) { + script.writeBytes(pubkeys[i]); + } + + script.writeOp(Opcode.map.OP_1 + pubkeys.length - 1); - /** - * Create an m-of-n output script - */ - Script.createMultiSigOutputScript = function (m, pubkeys) - { - var script = new Bitcoin.Script(); - - script.writeOp(OP_1 + m - 1); - - for (var i = 0; i < pubkeys.length; ++i) { - script.writeBytes(pubkeys[i]); - } - - script.writeOp(OP_1 + pubkeys.length - 1); + script.writeOp(Opcode.map.OP_CHECKMULTISIG); - script.writeOp(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; +}; - /** - * Create a standard payToPubKeyHash input. - */ - Script.createInputScript = function (signature, pubKey) - { - var script = new Script(); - script.writeBytes(signature); - script.writeBytes(pubKey); - return script; - }; +Script.prototype.clone = function () +{ + return new Script(this.buffer); +}; - Script.prototype.clone = function () - { - return new Script(this.buffer); - }; -})(); +module.exports = Script; diff --git a/src/transaction.js b/src/transaction.js index c06dc87..45a8b12 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,445 +1,467 @@ -(function () { - var Script = Bitcoin.Script; - - var Transaction = Bitcoin.Transaction = function (doc) { - this.version = 1; - this.lock_time = 0; - this.ins = []; - this.outs = []; - this.timestamp = null; - this.block = null; - - if (doc) { - if (doc.hash) this.hash = doc.hash; - if (doc.version) this.version = doc.version; - if (doc.lock_time) this.lock_time = doc.lock_time; - if (doc.ins && doc.ins.length) { - for (var i = 0; i < doc.ins.length; i++) { - this.addInput(new TransactionIn(doc.ins[i])); - } +var BigInteger = require('./jsbn/jsbn'); +var Script = require('./script'); +var util = require('./util'); +var conv = require('./convert'); +var Crypto = require('./crypto-js/crypto'); + +var Transaction = function (doc) { + this.version = 1; + this.lock_time = 0; + this.ins = []; + this.outs = []; + this.timestamp = null; + this.block = null; + + if (doc) { + if (doc.hash) this.hash = doc.hash; + if (doc.version) this.version = doc.version; + if (doc.lock_time) this.lock_time = doc.lock_time; + if (doc.ins && doc.ins.length) { + for (var i = 0; i < doc.ins.length; i++) { + this.addInput(new TransactionIn(doc.ins[i])); } - if (doc.outs && doc.outs.length) { - for (var i = 0; i < doc.outs.length; i++) { - this.addOutput(new TransactionOut(doc.outs[i])); - } - } - if (doc.timestamp) this.timestamp = doc.timestamp; - if (doc.block) this.block = doc.block; - } - }; - - /** - * Turn transaction data into Transaction objects. - * - * Takes an array of plain JavaScript objects containing transaction data and - * returns an array of Transaction objects. - */ - Transaction.objectify = function (txs) { - var objs = []; - for (var i = 0; i < txs.length; i++) { - objs.push(new Transaction(txs[i])); - } - return objs; - }; - - /** - * Create a new txin. - * - * Can be called with an existing TransactionIn object to add it to the - * transaction. Or it can be called with a Transaction object and an integer - * output index, in which case a new TransactionIn object pointing to the - * referenced output will be created. - * - * 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 { - this.ins.push(new TransactionIn({ - outpoint: { - hash: tx.hash, - index: outIndex - }, - script: new Bitcoin.Script(), - sequence: 4294967295 - })); } - }; - - /** - * Create a new txout. - * - * Can be called with an existing TransactionOut object to add it to the - * transaction. Or it can be called with an Address object and a BigInteger - * for the amount, in which case a new TransactionOut object with those - * values will be created. - */ - Transaction.prototype.addOutput = function (address, value) { - if (arguments[0] instanceof TransactionOut) { - this.outs.push(arguments[0]); - } else { - if (value instanceof BigInteger) { - value = value.toByteArrayUnsigned().reverse(); - while (value.length < 8) value.push(0); - } else if (Bitcoin.Util.isArray(value)) { - // Nothing to do + if (doc.outs && doc.outs.length) { + for (var i = 0; i < doc.outs.length; i++) { + this.addOutput(new TransactionOut(doc.outs[i])); } - - this.outs.push(new TransactionOut({ - value: value, - script: Script.createOutputScript(address) - })); - } - }; - - /** - * Serialize this transaction. - * - * Returns the transaction as a byte array in the standard Bitcoin binary - * format. This method is byte-perfect, i.e. the resulting byte array can - * be hashed to get the transaction's standard Bitcoin hash. - */ - Transaction.prototype.serialize = function () - { - var buffer = []; - buffer = buffer.concat(Crypto.util.wordsToBytes([parseInt(this.version)]).reverse()); - buffer = buffer.concat(Bitcoin.Util.numToVarInt(this.ins.length)); - for (var i = 0; i < this.ins.length; i++) { - var txin = this.ins[i]; - buffer = buffer.concat(Crypto.util.base64ToBytes(txin.outpoint.hash)); - buffer = buffer.concat(Crypto.util.wordsToBytes([parseInt(txin.outpoint.index)]).reverse()); - var scriptBytes = txin.script.buffer; - buffer = buffer.concat(Bitcoin.Util.numToVarInt(scriptBytes.length)); - buffer = buffer.concat(scriptBytes); - buffer = buffer.concat(Crypto.util.wordsToBytes([parseInt(txin.sequence)]).reverse()); - } - buffer = buffer.concat(Bitcoin.Util.numToVarInt(this.outs.length)); - for (var i = 0; i < this.outs.length; i++) { - var txout = this.outs[i]; - buffer = buffer.concat(txout.value); - var scriptBytes = txout.script.buffer; - buffer = buffer.concat(Bitcoin.Util.numToVarInt(scriptBytes.length)); - buffer = buffer.concat(scriptBytes); - } - buffer = buffer.concat(Crypto.util.wordsToBytes([parseInt(this.lock_time)]).reverse()); - - return buffer; - }; - - 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. - * - * Bitcoin uses a different hash for each signed transaction input. This - * method copies the transaction, makes the necessary changes based on the - * hashType, serializes and finally hashes the result. This hash can then be - * used to sign the transaction input in question. - */ - Transaction.prototype.hashTransactionForSignature = - function (connectedScript, inIndex, hashType) - { - 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; - });*/ - - // Blank out other inputs' signatures - for (var i = 0; i < txTmp.ins.length; i++) { - txTmp.ins[i].script = new Script(); - } - - txTmp.ins[inIndex].script = connectedScript; - - // Blank out some of the outputs - if ((hashType & 0x1f) == SIGHASH_NONE) { - txTmp.outs = []; - - // Let the others update at will - for (var i = 0; i < txTmp.ins.length; i++) - if (i != inIndex) - txTmp.ins[i].sequence = 0; - } else if ((hashType & 0x1f) == SIGHASH_SINGLE) { - // TODO: Implement } - - // Blank out other inputs completely, not recommended for open transactions - if (hashType & SIGHASH_ANYONECANPAY) { - txTmp.ins = [txTmp.ins[inIndex]]; - } - - var buffer = txTmp.serialize(); - - buffer = buffer.concat(Crypto.util.wordsToBytes([parseInt(hashType)]).reverse()); - - var hash1 = Crypto.SHA256(buffer, {asBytes: true}); - - return Crypto.SHA256(hash1, {asBytes: true}); - }; - - /** - * Calculate and return the transaction's hash. - */ - Transaction.prototype.getHash = function () - { - var buffer = this.serialize(); - return Crypto.SHA256(Crypto.SHA256(buffer, {asBytes: true}), {asBytes: true}); - }; - - /** - * Create a copy of this transaction object. - */ - Transaction.prototype.clone = function () - { - var newTx = new Transaction(); - newTx.version = this.version; - newTx.lock_time = this.lock_time; - for (var i = 0; i < this.ins.length; i++) { - var txin = this.ins[i].clone(); - newTx.addInput(txin); - } - for (var i = 0; i < this.outs.length; i++) { - var txout = this.outs[i].clone(); - newTx.addOutput(txout); - } - return newTx; - }; - - /** - * Analyze how this transaction affects a wallet. - * - * Returns an object with properties 'impact', 'type' and 'addr'. - * - * 'impact' is an object, see Transaction#calcImpact. - * - * 'type' can be one of the following: - * - * recv: - * This is an incoming transaction, the wallet received money. - * 'addr' contains the first address in the wallet that receives money - * from this transaction. - * - * self: - * This is an internal transaction, money was sent within the wallet. - * 'addr' is undefined. - * - * sent: - * This is an outgoing transaction, money was sent out from the wallet. - * 'addr' contains the first external address, i.e. the recipient. - * - * other: - * This method was unable to detect what the transaction does. Either it - */ - Transaction.prototype.analyze = function (wallet) { - if (!(wallet instanceof Bitcoin.Wallet)) return null; - - var allFromMe = true, - allToMe = true, - firstRecvHash = null, - firstMeRecvHash = null, - firstSendHash = null; - - for (var i = this.outs.length-1; i >= 0; i--) { - var txout = this.outs[i]; - var hash = txout.script.simpleOutPubKeyHash(); - if (!wallet.hasHash(hash)) { - allToMe = false; - } else { - firstMeRecvHash = hash; - } - firstRecvHash = hash; - } - for (var i = this.ins.length-1; i >= 0; i--) { - var txin = this.ins[i]; - firstSendHash = txin.script.simpleInPubKeyHash(); - if (!wallet.hasHash(firstSendHash)) { - allFromMe = false; - break; - } + if (doc.timestamp) this.timestamp = doc.timestamp; + if (doc.block) this.block = doc.block; + } +}; + +/** + * Turn transaction data into Transaction objects. + * + * Takes an array of plain JavaScript objects containing transaction data and + * returns an array of Transaction objects. + */ +Transaction.objectify = function (txs) { + var objs = []; + for (var i = 0; i < txs.length; i++) { + objs.push(new Transaction(txs[i])); + } + return objs; +}; + +/** + * Create a new txin. + * + * Can be called with an existing TransactionIn object to add it to the + * transaction. Or it can be called with a Transaction object and an integer + * output index, in which case a new TransactionIn object pointing to the + * referenced output will be created. + * + * 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 { + this.ins.push(new TransactionIn({ + outpoint: { + hash: tx.hash, + index: outIndex + }, + script: new Script(), + sequence: 4294967295 + })); + } +}; + +/** + * Create a new txout. + * + * Can be called with an existing TransactionOut object to add it to the + * transaction. Or it can be called with an Address object and a BigInteger + * for the amount, in which case a new TransactionOut object with those + * values will be created. + */ +Transaction.prototype.addOutput = function (address, value) { + if (arguments[0] instanceof TransactionOut) { + this.outs.push(arguments[0]); + } else { + if (value instanceof BigInteger) { + value = value.toByteArrayUnsigned().reverse(); + while (value.length < 8) value.push(0); + } else if (Bitcoin.Util.isArray(value)) { + // Nothing to do } - var impact = this.calcImpact(wallet); - - var analysis = {}; - - analysis.impact = impact; - - if (impact.sign > 0 && impact.value.compareTo(BigInteger.ZERO) > 0) { - analysis.type = 'recv'; - analysis.addr = new Bitcoin.Address(firstMeRecvHash); - } else if (allFromMe && allToMe) { - analysis.type = 'self'; - } else if (allFromMe) { - analysis.type = 'sent'; - // TODO: Right now, firstRecvHash is the first output, which - if the - // transaction was not generated by this library could be the - // change address. - analysis.addr = new Bitcoin.Address(firstRecvHash); - } else { - analysis.type = "other"; + this.outs.push(new TransactionOut({ + value: value, + script: Script.createOutputScript(address) + })); + } +}; + +// TODO(shtylman) crypto sha uses this also +// Convert a byte array to big-endian 32-bit words +var bytesToWords = function (bytes) { + for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8) + words[b >>> 5] |= bytes[i] << (24 - b % 32); + return words; +}; + + // Convert big-endian 32-bit words to a byte array +var wordsToBytes = function (words) { + for (var bytes = [], b = 0; b < words.length * 32; b += 8) + bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF); + return bytes; +}; + +/** + * Serialize this transaction. + * + * Returns the transaction as a byte array in the standard Bitcoin binary + * format. This method is byte-perfect, i.e. the resulting byte array can + * be hashed to get the transaction's standard Bitcoin hash. + */ +Transaction.prototype.serialize = function () +{ + var buffer = []; + buffer = buffer.concat(wordsToBytes([parseInt(this.version)]).reverse()); + buffer = buffer.concat(util.numToVarInt(this.ins.length)); + for (var i = 0; i < this.ins.length; i++) { + var txin = this.ins[i]; + buffer = buffer.concat(conv.base64ToBytes(txin.outpoint.hash)); + buffer = buffer.concat(wordsToBytes([parseInt(txin.outpoint.index)]).reverse()); + var scriptBytes = txin.script.buffer; + buffer = buffer.concat(util.numToVarInt(scriptBytes.length)); + buffer = buffer.concat(scriptBytes); + buffer = buffer.concat(wordsToBytes([parseInt(txin.sequence)]).reverse()); + } + buffer = buffer.concat(util.numToVarInt(this.outs.length)); + for (var i = 0; i < this.outs.length; i++) { + var txout = this.outs[i]; + buffer = buffer.concat(txout.value); + var scriptBytes = txout.script.buffer; + buffer = buffer.concat(util.numToVarInt(scriptBytes.length)); + buffer = buffer.concat(scriptBytes); + } + buffer = buffer.concat(wordsToBytes([parseInt(this.lock_time)]).reverse()); + + return buffer; +}; + +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. + * + * Bitcoin uses a different hash for each signed transaction input. This + * method copies the transaction, makes the necessary changes based on the + * hashType, serializes and finally hashes the result. This hash can then be + * used to sign the transaction input in question. + */ +Transaction.prototype.hashTransactionForSignature = +function (connectedScript, inIndex, hashType) +{ + 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; + });*/ + + // Blank out other inputs' signatures + for (var i = 0; i < txTmp.ins.length; i++) { + txTmp.ins[i].script = new Script(); + } + + txTmp.ins[inIndex].script = connectedScript; + + // Blank out some of the outputs + if ((hashType & 0x1f) == SIGHASH_NONE) { + txTmp.outs = []; + + // Let the others update at will + for (var i = 0; i < txTmp.ins.length; i++) + if (i != inIndex) + txTmp.ins[i].sequence = 0; + } else if ((hashType & 0x1f) == SIGHASH_SINGLE) { + // TODO: Implement + } + + // Blank out other inputs completely, not recommended for open transactions + if (hashType & SIGHASH_ANYONECANPAY) { + txTmp.ins = [txTmp.ins[inIndex]]; + } + + var buffer = txTmp.serialize(); + + buffer = buffer.concat(wordsToBytes([parseInt(hashType)]).reverse()); + + var hash1 = Crypto.SHA256(buffer, {asBytes: true}); + + return Crypto.SHA256(hash1, {asBytes: true}); +}; + +/** + * Calculate and return the transaction's hash. + */ +Transaction.prototype.getHash = function () +{ + var buffer = this.serialize(); + return Crypto.SHA256(Crypto.SHA256(buffer, {asBytes: true}), {asBytes: true}); +}; + +/** + * Create a copy of this transaction object. + */ +Transaction.prototype.clone = function () +{ + var newTx = new Transaction(); + newTx.version = this.version; + newTx.lock_time = this.lock_time; + for (var i = 0; i < this.ins.length; i++) { + var txin = this.ins[i].clone(); + newTx.addInput(txin); + } + for (var i = 0; i < this.outs.length; i++) { + var txout = this.outs[i].clone(); + newTx.addOutput(txout); + } + return newTx; +}; + +/** + * Analyze how this transaction affects a wallet. + * + * Returns an object with properties 'impact', 'type' and 'addr'. + * + * 'impact' is an object, see Transaction#calcImpact. + * + * 'type' can be one of the following: + * + * recv: + * This is an incoming transaction, the wallet received money. + * 'addr' contains the first address in the wallet that receives money + * from this transaction. + * + * self: + * This is an internal transaction, money was sent within the wallet. + * 'addr' is undefined. + * + * sent: + * This is an outgoing transaction, money was sent out from the wallet. + * 'addr' contains the first external address, i.e. the recipient. + * + * other: + * This method was unable to detect what the transaction does. Either it + */ +Transaction.prototype.analyze = function (wallet) { + if (!(wallet instanceof Bitcoin.Wallet)) return null; + + var allFromMe = true, + allToMe = true, + firstRecvHash = null, + firstMeRecvHash = null, + firstSendHash = null; + + for (var i = this.outs.length-1; i >= 0; i--) { + var txout = this.outs[i]; + var hash = txout.script.simpleOutPubKeyHash(); + if (!wallet.hasHash(hash)) { + allToMe = false; + } else { + firstMeRecvHash = hash; } - - return analysis; - }; - - /** - * Get a human-readable version of the data returned by Transaction#analyze. - * - * This is merely a convenience function. Clients should consider implementing - * this themselves based on their UI, I18N, etc. - */ - Transaction.prototype.getDescription = function (wallet) { - var analysis = this.analyze(wallet); - - if (!analysis) return ""; - - switch (analysis.type) { - case 'recv': - return "Received with "+analysis.addr; - break; - - case 'sent': - return "Payment to "+analysis.addr; - break; - - case 'self': - return "Payment to yourself"; + firstRecvHash = hash; + } + for (var i = this.ins.length-1; i >= 0; i--) { + var txin = this.ins[i]; + firstSendHash = txin.script.simpleInPubKeyHash(); + if (!wallet.hasHash(firstSendHash)) { + allFromMe = false; break; - - case 'other': - default: - return ""; - } - }; - - /** - * Get the total amount of a transaction's outputs. - */ - Transaction.prototype.getTotalOutValue = function () { - var totalValue = BigInteger.ZERO; - for (var j = 0; j < this.outs.length; j++) { - var txout = this.outs[j]; - totalValue = totalValue.add(Bitcoin.Util.valueToBigInt(txout.value)); } - return totalValue; - }; - - /** - * Old name for Transaction#getTotalOutValue. - * - * @deprecated - */ - Transaction.prototype.getTotalValue = Transaction.prototype.getTotalOutValue; - - /** - * Calculates the impact a transaction has on this wallet. - * - * Based on the its public keys, the wallet will calculate the - * credit or debit of this transaction. - * - * It will return an object with two properties: - * - sign: 1 or -1 depending on sign of the calculated impact. - * - value: amount of calculated impact - * - * @returns Object Impact on wallet - */ - Transaction.prototype.calcImpact = function (wallet) { - if (!(wallet instanceof Bitcoin.Wallet)) return BigInteger.ZERO; - - // Calculate credit to us from all outputs - var valueOut = BigInteger.ZERO; - for (var j = 0; j < this.outs.length; j++) { - var txout = this.outs[j]; - var hash = Crypto.util.bytesToBase64(txout.script.simpleOutPubKeyHash()); - if (wallet.hasHash(hash)) { - valueOut = valueOut.add(Bitcoin.Util.valueToBigInt(txout.value)); - } + } + + var impact = this.calcImpact(wallet); + + var analysis = {}; + + analysis.impact = impact; + + if (impact.sign > 0 && impact.value.compareTo(BigInteger.ZERO) > 0) { + analysis.type = 'recv'; + analysis.addr = new Bitcoin.Address(firstMeRecvHash); + } else if (allFromMe && allToMe) { + analysis.type = 'self'; + } else if (allFromMe) { + analysis.type = 'sent'; + // TODO: Right now, firstRecvHash is the first output, which - if the + // transaction was not generated by this library could be the + // change address. + analysis.addr = new Bitcoin.Address(firstRecvHash); + } else { + analysis.type = "other"; + } + + return analysis; +}; + +/** + * Get a human-readable version of the data returned by Transaction#analyze. + * + * This is merely a convenience function. Clients should consider implementing + * this themselves based on their UI, I18N, etc. + */ +Transaction.prototype.getDescription = function (wallet) { + var analysis = this.analyze(wallet); + + if (!analysis) return ""; + + switch (analysis.type) { + case 'recv': + return "Received with "+analysis.addr; + break; + + case 'sent': + return "Payment to "+analysis.addr; + break; + + case 'self': + return "Payment to yourself"; + break; + + case 'other': + default: + return ""; + } +}; + +/** + * Get the total amount of a transaction's outputs. + */ +Transaction.prototype.getTotalOutValue = function () { + var totalValue = BigInteger.ZERO; + for (var j = 0; j < this.outs.length; j++) { + var txout = this.outs[j]; + totalValue = totalValue.add(Bitcoin.Util.valueToBigInt(txout.value)); + } + return totalValue; +}; + + /** + * Old name for Transaction#getTotalOutValue. + * + * @deprecated + */ + Transaction.prototype.getTotalValue = Transaction.prototype.getTotalOutValue; + +/** + * Calculates the impact a transaction has on this wallet. + * + * Based on the its public keys, the wallet will calculate the + * credit or debit of this transaction. + * + * It will return an object with two properties: + * - sign: 1 or -1 depending on sign of the calculated impact. + * - value: amount of calculated impact + * + * @returns Object Impact on wallet + */ +Transaction.prototype.calcImpact = function (wallet) { + if (!(wallet instanceof Bitcoin.Wallet)) return BigInteger.ZERO; + + // Calculate credit to us from all outputs + var valueOut = BigInteger.ZERO; + for (var j = 0; j < this.outs.length; j++) { + var txout = this.outs[j]; + var hash = Crypto.util.bytesToBase64(txout.script.simpleOutPubKeyHash()); + if (wallet.hasHash(hash)) { + valueOut = valueOut.add(Bitcoin.Util.valueToBigInt(txout.value)); } - - // Calculate debit to us from all ins - var valueIn = BigInteger.ZERO; - for (var j = 0; j < this.ins.length; j++) { - var txin = this.ins[j]; - var hash = Crypto.util.bytesToBase64(txin.script.simpleInPubKeyHash()); - if (wallet.hasHash(hash)) { - var fromTx = wallet.txIndex[txin.outpoint.hash]; - if (fromTx) { - valueIn = valueIn.add(Bitcoin.Util.valueToBigInt(fromTx.outs[txin.outpoint.index].value)); - } + } + + // Calculate debit to us from all ins + var valueIn = BigInteger.ZERO; + for (var j = 0; j < this.ins.length; j++) { + var txin = this.ins[j]; + var hash = Crypto.util.bytesToBase64(txin.script.simpleInPubKeyHash()); + if (wallet.hasHash(hash)) { + var fromTx = wallet.txIndex[txin.outpoint.hash]; + if (fromTx) { + valueIn = valueIn.add(Bitcoin.Util.valueToBigInt(fromTx.outs[txin.outpoint.index].value)); } } - if (valueOut.compareTo(valueIn) >= 0) { - return { - sign: 1, - value: valueOut.subtract(valueIn) - }; - } else { - return { - sign: -1, - value: valueIn.subtract(valueOut) - }; - } - }; - - var TransactionIn = Bitcoin.TransactionIn = function (data) - { - this.outpoint = data.outpoint; - if (data.script instanceof Script) { - this.script = data.script; - } else { - this.script = new Script(data.script); - } - this.sequence = data.sequence; - }; - - TransactionIn.prototype.clone = function () - { - var newTxin = new TransactionIn({ - outpoint: { - hash: this.outpoint.hash, - index: this.outpoint.index - }, - script: this.script.clone(), - sequence: this.sequence - }); - return newTxin; - }; - - var TransactionOut = Bitcoin.TransactionOut = function (data) - { - if (data.script instanceof Script) { - this.script = data.script; - } else { - this.script = new Script(data.script); - } - - if (Bitcoin.Util.isArray(data.value)) { - this.value = data.value; - } else if ("string" == typeof data.value) { - var valueHex = (new BigInteger(data.value, 10)).toString(16); - while (valueHex.length < 16) valueHex = "0" + valueHex; - this.value = Crypto.util.hexToBytes(valueHex); - } - }; - - TransactionOut.prototype.clone = function () - { - var newTxout = new TransactionOut({ - script: this.script.clone(), - value: this.value.slice(0) - }); - return newTxout; - }; -})(); - + } + if (valueOut.compareTo(valueIn) >= 0) { + return { + sign: 1, + value: valueOut.subtract(valueIn) + }; + } else { + return { + sign: -1, + value: valueIn.subtract(valueOut) + }; + } +}; + +var TransactionIn = function (data) +{ + this.outpoint = data.outpoint; + if (data.script instanceof Script) { + this.script = data.script; + } else { + //this.script = new Script(data.script); + this.script = Script.fromScriptSig(data.scriptSig); + } + this.sequence = data.sequence; +}; + +TransactionIn.prototype.clone = function () +{ + var newTxin = new TransactionIn({ + outpoint: { + hash: this.outpoint.hash, + index: this.outpoint.index + }, + script: this.script.clone(), + sequence: this.sequence + }); + return newTxin; +}; + +var TransactionOut = function (data) +{ + if (data.script instanceof Script) { + this.script = data.script; + } else { + //this.script = new Script(data.script); + this.script = Script.fromPubKey(data.scriptPubKey); + } + + if (util.isArray(data.value)) { + this.value = data.value; + } else if ("string" == typeof data.value) { + var valueHex = (new BigInteger(data.value, 10)).toString(16); + while (valueHex.length < 16) valueHex = "0" + valueHex; + this.value = conv.hexToBytes(valueHex); + } +}; + +TransactionOut.prototype.clone = function () +{ + var newTxout = new TransactionOut({ + script: this.script.clone(), + value: this.value.slice(0) + }); + return newTxout; +}; + +module.exports.Transaction = Transaction; +module.exports.TransactionIn = TransactionIn; +module.exports.TransactionOut = TransactionOut; diff --git a/src/txdb.js b/src/txdb.js old mode 100755 new mode 100644 diff --git a/src/util.js b/src/util.js index 4c468a0..a52652b 100644 --- a/src/util.js +++ b/src/util.js @@ -1,117 +1,7 @@ -// BigInteger monkey patching -BigInteger.valueOf = nbv; +var BigInteger = require('./jsbn/jsbn'); +var Crypto = require('./crypto-js/crypto'); -/** - * Returns a byte array representation of the big integer. - * - * This returns the absolute of the contained value in big endian - * form. A value of zero results in an empty array. - */ -BigInteger.prototype.toByteArrayUnsigned = function () { - var ba = this.abs().toByteArray(); - if (ba.length) { - if (ba[0] == 0) { - ba = ba.slice(1); - } - return ba.map(function (v) { - return (v < 0) ? v + 256 : v; - }); - } else { - // Empty array, nothing to do - return ba; - } -}; - -/** - * Turns a byte array into a big integer. - * - * This function will interpret a byte array as a big integer in big - * endian notation and ignore leading zeros. - */ -BigInteger.fromByteArrayUnsigned = function (ba) { - if (!ba.length) { - return ba.valueOf(0); - } else if (ba[0] & 0x80) { - // Prepend a zero so the BigInteger class doesn't mistake this - // for a negative integer. - return new BigInteger([0].concat(ba)); - } else { - return new BigInteger(ba); - } -}; - -/** - * Converts big integer to signed byte representation. - * - * The format for this value uses a the most significant bit as a sign - * bit. If the most significant bit is already occupied by the - * absolute value, an extra byte is prepended and the sign bit is set - * there. - * - * Examples: - * - * 0 => 0x00 - * 1 => 0x01 - * -1 => 0x81 - * 127 => 0x7f - * -127 => 0xff - * 128 => 0x0080 - * -128 => 0x8080 - * 255 => 0x00ff - * -255 => 0x80ff - * 16300 => 0x3fac - * -16300 => 0xbfac - * 62300 => 0x00f35c - * -62300 => 0x80f35c - */ -BigInteger.prototype.toByteArraySigned = function () { - var val = this.abs().toByteArrayUnsigned(); - var neg = this.compareTo(BigInteger.ZERO) < 0; - - if (neg) { - if (val[0] & 0x80) { - val.unshift(0x80); - } else { - val[0] |= 0x80; - } - } else { - if (val[0] & 0x80) { - val.unshift(0x00); - } - } - - return val; -}; - -/** - * Parse a signed big integer byte representation. - * - * For details on the format please see BigInteger.toByteArraySigned. - */ -BigInteger.fromByteArraySigned = function (ba) { - // Check for negative value - if (ba[0] & 0x80) { - // Remove sign bit - ba[0] &= 0x7f; - - return BigInteger.fromByteArrayUnsigned(ba).negate(); - } else { - return BigInteger.fromByteArrayUnsigned(ba); - } -}; - -// Console ignore -var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", - "trace", "profile", "profileEnd"]; - -if ("undefined" == typeof window.console) window.console = {}; -for (var i = 0; i < names.length; ++i) - if ("undefined" == typeof window.console[names[i]]) - window.console[names[i]] = function() {}; - -// Bitcoin utility functions -Bitcoin.Util = { +module.exports = { /** * Cross-browser compatibility version of Array.isArray. */ @@ -220,9 +110,3 @@ Bitcoin.Util = { return Crypto.RIPEMD160(Crypto.SHA256(data, {asBytes: true}), {asBytes: true}); } }; - -for (var i in Crypto.util) { - if (Crypto.util.hasOwnProperty(i)) { - Bitcoin.Util[i] = Crypto.util[i]; - } -} diff --git a/src/wallet.js b/src/wallet.js old mode 100755 new mode 100644 index 501ec34..40d1891 --- a/src/wallet.js +++ b/src/wallet.js @@ -1,308 +1,323 @@ -Bitcoin.Wallet = (function () { - var Script = Bitcoin.Script, - TransactionIn = Bitcoin.TransactionIn, - TransactionOut = Bitcoin.TransactionOut; - - var Wallet = function () { - // Keychain - // - // The keychain is stored as a var in this closure to make accidental - // serialization less likely. - // - // Any functions accessing this value therefore have to be defined in - // the closure of this constructor. - var keys = []; - - // Public hashes of our keys - this.addressHashes = []; - - // Transaction data - this.txIndex = {}; - this.unspentOuts = []; - - // Other fields - this.addressPointer = 0; - - /** - * Add a key to the keychain. - * - * The corresponding public key can be provided as a second parameter. This - * adds it to the cache in the ECKey object and avoid the need to - * expensively calculate it later. - */ - this.addKey = function (key, pub) { - if (!(key instanceof Bitcoin.ECKey)) { - key = new Bitcoin.ECKey(key); - } - keys.push(key); - - if (pub) { - if ("string" === typeof pub) { - pub = Crypto.util.base64ToBytes(pub); - } - key.setPub(pub); - } - - this.addressHashes.push(key.getBitcoinAddress().getHashBase64()); - }; +var Script = require('./script'); +var ECKey = require('./eckey'); +var conv = require('./convert'); +var util = require('./util'); - /** - * Add multiple keys at once. - */ - this.addKeys = function (keys, pubs) { - if ("string" === typeof keys) { - keys = keys.split(','); - } - if ("string" === typeof pubs) { - pubs = pubs.split(','); - } - var i; - if (Array.isArray(pubs) && keys.length == pubs.length) { - for (i = 0; i < keys.length; i++) { - this.addKey(keys[i], pubs[i]); - } - } else { - for (i = 0; i < keys.length; i++) { - this.addKey(keys[i]); - } - } - }; - - /** - * Get the key chain. - * - * Returns an array of base64-encoded private values. - */ - this.getKeys = function () { - var serializedWallet = []; - - for (var i = 0; i < keys.length; i++) { - serializedWallet.push(keys[i].toString('base64')); - } +var BigInteger = require('./jsbn/jsbn'); - return serializedWallet; - }; +var Transaction = require('./transaction').Transaction; +var TransactionIn = require('./transaction').TransactionIn; +var TransactionOut = require('./transaction').TransactionOut; - /** - * Get the public keys. - * - * Returns an array of base64-encoded public keys. - */ - this.getPubKeys = function () { - var pubs = []; +var Wallet = function () { + // Keychain + // + // The keychain is stored as a var in this closure to make accidental + // serialization less likely. + // + // Any functions accessing this value therefore have to be defined in + // the closure of this constructor. + var keys = []; - for (var i = 0; i < keys.length; i++) { - pubs.push(Crypto.util.bytesToBase64(keys[i].getPub())); - } + // Public hashes of our keys + this.addressHashes = []; - return pubs; - }; - - /** - * Delete all keys. - */ - this.clear = function () { - keys = []; - }; - - /** - * Return the number of keys in this wallet. - */ - this.getLength = function () { - return keys.length; - }; - - /** - * Get the addresses for this wallet. - * - * Returns an array of Address objects. - */ - this.getAllAddresses = function () { - var addresses = []; - for (var i = 0; i < keys.length; i++) { - addresses.push(keys[i].getBitcoinAddress()); - } - return addresses; - }; - - this.getCurAddress = function () { - if (keys[this.addressPointer]) { - return keys[this.addressPointer].getBitcoinAddress(); - } else { - return null; - } - }; - - /** - * Go to the next address. - * - * If there are no more new addresses available, one will be generated - * automatically. - */ - this.getNextAddress = function () { - this.addressPointer++; - if (!keys[this.addressPointer]) { - this.generateAddress(); - } - return keys[this.addressPointer].getBitcoinAddress(); - }; - - /** - * Sign a hash with a key. - * - * This method expects the pubKeyHash as the first parameter and the hash - * to be signed as the second parameter. - */ - this.signWithKey = function (pubKeyHash, hash) { - pubKeyHash = Crypto.util.bytesToBase64(pubKeyHash); - for (var i = 0; i < this.addressHashes.length; i++) { - if (this.addressHashes[i] == pubKeyHash) { - return keys[i].sign(hash); - } - } - throw new Error("Missing key for signature"); - }; - - /** - * Retrieve the corresponding pubKey for a pubKeyHash. - * - * This function only works if the pubKey in question is part of this - * wallet. - */ - this.getPubKeyFromHash = function (pubKeyHash) { - pubKeyHash = Crypto.util.bytesToBase64(pubKeyHash); - for (var i = 0; i < this.addressHashes.length; i++) { - if (this.addressHashes[i] == pubKeyHash) { - return keys[i].getPub(); - } - } - throw new Error("Hash unknown"); - }; - }; + // Transaction data + this.txIndex = {}; + this.unspentOuts = []; - Wallet.prototype.generateAddress = function () { - this.addKey(new Bitcoin.ECKey()); - }; + // Other fields + this.addressPointer = 0; /** - * Add a transaction to the wallet's processed transaction. + * Add a key to the keychain. * - * This will add a transaction to the wallet, updating its balance and - * available unspent outputs. + * The corresponding public key can be provided as a second parameter. This + * adds it to the cache in the ECKey object and avoid the need to + * expensively calculate it later. */ - Wallet.prototype.process = function (tx) { - if (this.txIndex[tx.hash]) return; - - var j; - var k; - var hash; - // Gather outputs - for (j = 0; j < tx.outs.length; j++) { - var txout = new TransactionOut(tx.outs[j]); - hash = Crypto.util.bytesToBase64(txout.script.simpleOutPubKeyHash()); - for (k = 0; k < this.addressHashes.length; k++) { - if (this.addressHashes[k] === hash) { - this.unspentOuts.push({tx: tx, index: j, out: txout}); - break; - } - } + this.addKey = function (key, pub) { + if (!(key instanceof ECKey)) { + key = new ECKey(key); } + keys.push(key); - // Remove spent outputs - for (j = 0; j < tx.ins.length; j++) { - var txin = new TransactionIn(tx.ins[j]); - var pubkey = txin.script.simpleInPubKey(); - hash = Crypto.util.bytesToBase64(Bitcoin.Util.sha256ripe160(pubkey)); - for (k = 0; k < this.addressHashes.length; k++) { - if (this.addressHashes[k] === hash) { - for (var l = 0; l < this.unspentOuts.length; l++) { - if (txin.outpoint.hash == this.unspentOuts[l].tx.hash && - txin.outpoint.index == this.unspentOuts[l].index) { - this.unspentOuts.splice(l, 1); - } - } - break; - } + if (pub) { + if ("string" === typeof pub) { + pub = Crypto.util.base64ToBytes(pub); } + key.setPub(pub); } - // Index transaction - this.txIndex[tx.hash] = tx; + this.addressHashes.push(key.getBitcoinAddress().getHashBase64()); }; - Wallet.prototype.getBalance = function () { - var balance = BigInteger.valueOf(0); - for (var i = 0; i < this.unspentOuts.length; i++) { - var txout = this.unspentOuts[i].out; - balance = balance.add(Bitcoin.Util.valueToBigInt(txout.value)); + /** + * Add multiple keys at once. + */ + this.addKeys = function (keys, pubs) { + if ("string" === typeof keys) { + keys = keys.split(','); } - return balance; - }; - - Wallet.prototype.createSend = function (address, sendValue, feeValue) { - var selectedOuts = []; - var txValue = sendValue.add(feeValue); - var availableValue = BigInteger.ZERO; - var i; - for (i = 0; i < this.unspentOuts.length; i++) { - selectedOuts.push(this.unspentOuts[i]); - availableValue = availableValue.add(Bitcoin.Util.valueToBigInt(this.unspentOuts[i].out.value)); - - if (availableValue.compareTo(txValue) >= 0) break; + if ("string" === typeof pubs) { + pubs = pubs.split(','); } - - if (availableValue.compareTo(txValue) < 0) { - throw new Error('Insufficient funds.'); + var i; + if (Array.isArray(pubs) && keys.length == pubs.length) { + for (i = 0; i < keys.length; i++) { + this.addKey(keys[i], pubs[i]); + } + } else { + for (i = 0; i < keys.length; i++) { + this.addKey(keys[i]); + } } + }; + /** + * Get the key chain. + * + * Returns an array of base64-encoded private values. + */ + this.getKeys = function () { + var serializedWallet = []; - var changeValue = availableValue.subtract(txValue); + for (var i = 0; i < keys.length; i++) { + serializedWallet.push(keys[i].toString('base64')); + } - var sendTx = new Bitcoin.Transaction(); + return serializedWallet; + }; - for (i = 0; i < selectedOuts.length; i++) { - sendTx.addInput(selectedOuts[i].tx, selectedOuts[i].index); - } + /** + * Get the public keys. + * + * Returns an array of base64-encoded public keys. + */ + this.getPubKeys = function () { + var pubs = []; - sendTx.addOutput(address, sendValue); - if (changeValue.compareTo(BigInteger.ZERO) > 0) { - sendTx.addOutput(this.getNextAddress(), changeValue); + for (var i = 0; i < keys.length; i++) { + pubs.push(Crypto.util.bytesToBase64(keys[i].getPub())); } - var hashType = 1; // SIGHASH_ALL + return pubs; + }; - for (i = 0; i < sendTx.ins.length; i++) { - var hash = sendTx.hashTransactionForSignature(selectedOuts[i].out.script, i, hashType); - var pubKeyHash = selectedOuts[i].out.script.simpleOutPubKeyHash(); - var signature = this.signWithKey(pubKeyHash, hash); + /** + * Delete all keys. + */ + this.clear = function () { + keys = []; + }; - // Append hash type - signature.push(parseInt(hashType, 10)); + /** + * Return the number of keys in this wallet. + */ + this.getLength = function () { + return keys.length; + }; - sendTx.ins[i].script = Script.createInputScript(signature, this.getPubKeyFromHash(pubKeyHash)); + /** + * Get the addresses for this wallet. + * + * Returns an array of Address objects. + */ + this.getAllAddresses = function () { + var addresses = []; + for (var i = 0; i < keys.length; i++) { + addresses.push(keys[i].getBitcoinAddress()); } - - return sendTx; + return addresses; }; - Wallet.prototype.clearTransactions = function () { - this.txIndex = {}; - this.unspentOuts = []; + this.getCurAddress = function () { + if (keys[this.addressPointer]) { + return keys[this.addressPointer].getBitcoinAddress(); + } else { + return null; + } }; /** - * Check to see if a pubKeyHash belongs to this wallet. + * Go to the next address. + * + * If there are no more new addresses available, one will be generated + * automatically. */ - Wallet.prototype.hasHash = function (hash) { - if (Bitcoin.Util.isArray(hash)) hash = Crypto.util.bytesToBase64(hash); + this.getNextAddress = function () { + if (keys.length === 0) { + this.generateAddress(); + } + else { + } - // TODO: Just create an object with base64 hashes as keys for faster lookup - for (var k = 0; k < this.addressHashes.length; k++) { - if (this.addressHashes[k] === hash) return true; + /* + this.addressPointer++; + if (!keys[this.addressPointer]) { + this.generateAddress(); } - return false; + */ + // TODO(shtylman) this shit is trying to be too smart + // it is making a new address when it shouldn't + // it should just stop being so "smart" and just do what it is told + return keys[this.addressPointer].getBitcoinAddress(); }; - return Wallet; -})(); + /** + * Sign a hash with a key. + * + * This method expects the pubKeyHash as the first parameter and the hash + * to be signed as the second parameter. + */ + this.signWithKey = function (pubKeyHash, hash) { + pubKeyHash = conv.bytesToBase64(pubKeyHash); + for (var i = 0; i < this.addressHashes.length; i++) { + if (this.addressHashes[i] == pubKeyHash) { + return keys[i].sign(hash); + } + } + throw new Error("Missing key for signature"); + }; + /** + * Retrieve the corresponding pubKey for a pubKeyHash. + * + * This function only works if the pubKey in question is part of this + * wallet. + */ + this.getPubKeyFromHash = function (pubKeyHash) { + pubKeyHash = conv.bytesToBase64(pubKeyHash); + for (var i = 0; i < this.addressHashes.length; i++) { + if (this.addressHashes[i] == pubKeyHash) { + return keys[i].getPub(); + } + } + throw new Error("Hash unknown"); + }; +}; + +Wallet.prototype.generateAddress = function () { + this.addKey(new ECKey()); +}; + +/** + * Add a transaction to the wallet's processed transaction. + * + * This will add a transaction to the wallet, updating its balance and + * available unspent outputs. + */ +Wallet.prototype.process = function (tx) { + if (this.txIndex[tx.hash]) return; + + var j; + var k; + var hash; + // Gather outputs + for (j = 0; j < tx.out.length; j++) { + var txout = new TransactionOut(tx.out[j]); + hash = conv.bytesToBase64(txout.script.simpleOutPubKeyHash()); + for (k = 0; k < this.addressHashes.length; k++) { + if (this.addressHashes[k] === hash) { + this.unspentOuts.push({tx: tx, index: j, out: txout}); + break; + } + } + } + + // Remove spent outputs + for (j = 0; j < tx.in.length; j++) { + var txin = new TransactionIn(tx.in[j]); + var pubkey = txin.script.simpleInPubKey(); + hash = conv.bytesToBase64(util.sha256ripe160(pubkey)); + for (k = 0; k < this.addressHashes.length; k++) { + if (this.addressHashes[k] === hash) { + for (var l = 0; l < this.unspentOuts.length; l++) { + if (txin.outpoint.hash == this.unspentOuts[l].tx.hash && + txin.outpoint.index == this.unspentOuts[l].index) { + this.unspentOuts.splice(l, 1); + } + } + break; + } + } + } + + // Index transaction + this.txIndex[tx.hash] = tx; +}; + +Wallet.prototype.getBalance = function () { + var balance = BigInteger.valueOf(0); + for (var i = 0; i < this.unspentOuts.length; i++) { + var txout = this.unspentOuts[i].out; + balance = balance.add(util.valueToBigInt(txout.value)); + } + return balance; +}; + +Wallet.prototype.createSend = function (address, sendValue, feeValue) { + var selectedOuts = []; + var txValue = sendValue.add(feeValue); + var availableValue = BigInteger.ZERO; + var i; + for (i = 0; i < this.unspentOuts.length; i++) { + var txout = this.unspentOuts[i]; + selectedOuts.push(txout); + availableValue = availableValue.add(util.valueToBigInt(txout.out.value)); + + if (availableValue.compareTo(txValue) >= 0) break; + } + + if (availableValue.compareTo(txValue) < 0) { + throw new Error('Insufficient funds.'); + } + + var changeValue = availableValue.subtract(txValue); + + var sendTx = new Transaction(); + + for (i = 0; i < selectedOuts.length; i++) { + sendTx.addInput(selectedOuts[i].tx, selectedOuts[i].index); + } + + sendTx.addOutput(address, sendValue); + if (changeValue.compareTo(BigInteger.ZERO) > 0) { + sendTx.addOutput(this.getNextAddress(), changeValue); + } + + var hashType = 1; // SIGHASH_ALL + + for (i = 0; i < sendTx.ins.length; i++) { + var hash = sendTx.hashTransactionForSignature(selectedOuts[i].out.script, i, hashType); + var pubKeyHash = selectedOuts[i].out.script.simpleOutPubKeyHash(); + var signature = this.signWithKey(pubKeyHash, hash); + + // Append hash type + signature.push(parseInt(hashType, 10)); + + sendTx.ins[i].script = Script.createInputScript(signature, this.getPubKeyFromHash(pubKeyHash)); + } + + return sendTx; +}; + +Wallet.prototype.clearTransactions = function () { + this.txIndex = {}; + this.unspentOuts = []; +}; + +/** + * Check to see if a pubKeyHash belongs to this wallet. + */ +Wallet.prototype.hasHash = function (hash) { + if (Bitcoin.Util.isArray(hash)) hash = Crypto.util.bytesToBase64(hash); + + // TODO: Just create an object with base64 hashes as keys for faster lookup + for (var k = 0; k < this.addressHashes.length; k++) { + if (this.addressHashes[k] === hash) return true; + } + return false; +}; + +module.exports = Wallet; diff --git a/test/address.js b/test/address.js new file mode 100644 index 0000000..510a8d7 --- /dev/null +++ b/test/address.js @@ -0,0 +1,46 @@ +var assert = require('assert'); +var Address = require('../').Address; + +test('string', function() { + var addr = '18fN1QTGWmHWCA9r2dyDH6FbMEyc7XHmQQ'; + assert.equal((new Address(addr)).toString(), addr); +}); + +test('valid', function() { + function validate(addr, type) { + assert.ok(Address.validate(addr, type)); + }; + + validate('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'); + validate('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', 'prod'); + validate('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef'); + validate('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef', 'testnet'); + + validate('12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP'); + validate('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y'); + validate('1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs'); + validate('1SQHtwR5oJRKLfiWQ2APsAd9miUc4k2ez'); + validate('116CGDLddrZhMrTwhCVJXtXQpxygTT1kHd'); + + // p2sh addresses + validate('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt'); + validate('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'prod'); + validate('2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7'); + validate('2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7', 'testnet'); +}); + + +test('invalid', function() { + function invalid(addr, type) { + assert.ok(!Address.validate(addr, type)); + }; + + invalid(''); + invalid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhe'); + invalid('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', 'testnet'); + invalid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef', 'prod'); + + // invalid base58 string + invalid('%%@'); +}); + diff --git a/test/base58.js b/test/base58.js new file mode 100644 index 0000000..b7a0cef --- /dev/null +++ b/test/base58.js @@ -0,0 +1,15 @@ +var assert = require('assert'); +var base58 = require('../').base58; +var conv = require('../').convert; + +test('decode base58', function() { + var enc = '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ'; + var hex = '800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d507a5b8d'; + assert.deepEqual(base58.decode(enc), conv.hexToBytes(hex)); +}); + +test('encode base58', function() { + var enc = '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ'; + var hex = '800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d507a5b8d'; + assert.equal(base58.encode(conv.hexToBytes(hex)), enc); +}); diff --git a/test/convert.js b/test/convert.js new file mode 100644 index 0000000..aa7eafe --- /dev/null +++ b/test/convert.js @@ -0,0 +1,24 @@ +var assert = require('assert'); +var conv = require('../').convert; + +var bytesToHex = conv.bytesToHex; +var hexToBytes = conv.hexToBytes; + +test('bytesToHex', function() { + assert.equal(bytesToHex([0, 1, 2, 255]), '000102ff'); +}); + +test('hexToBytes', function() { + assert.deepEqual(hexToBytes('000102ff'), [0, 1, 2, 255]); +}); + +test('bytesToHex - hexToBytes', function() { + var bytes = []; + for (var i=0 ; i<256 ; ++i) { + bytes.push(i); + } + + var hex = bytesToHex(bytes); + assert.equal(hex.length, 512); + assert.deepEqual(hexToBytes(hex), bytes); +}); diff --git a/test/ec.js b/test/ec.js new file mode 100644 index 0000000..6ae6e74 --- /dev/null +++ b/test/ec.js @@ -0,0 +1,20 @@ +var assert = require('assert'); +var sec = require('../src/jsbn/sec'); +var ecdsa = require('../').ecdsa; + +var ecparams = sec('secp256k1'); + +test("Point multiplication", function () { + var G = ecparams.getG(); + var n = ecparams.getN(); + + assert.ok(G.multiply(n).isInfinity(), "Gn is infinite"); + + var k = ecdsa.getBigRandom(n); + var P = G.multiply(k); + assert.ok(!P.isInfinity(), "kG is not infinite"); + assert.ok(P.isOnCurve(), "kG on curve"); + assert.ok(P.multiply(n).isInfinity(), "kGn is infinite"); + + assert.ok(P.validate(), "kG validates as a public key"); +}); diff --git a/test/index.html b/test/index.html deleted file mode 100755 index cfa6c67..0000000 --- a/test/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - BitcoinJS-lib Test Suite - - - - - - - - - - - - - - - - - - - - - - - - -

BitcoinJS-lib Test Suite

-

-
-

-
    -
    - - diff --git a/test/integer.js b/test/integer.js new file mode 100644 index 0000000..7b1746f --- /dev/null +++ b/test/integer.js @@ -0,0 +1,24 @@ +var assert = require('assert'); +var BigInteger = require('../').BigInteger; +var bytesToHex = require('../').convert.bytesToHex; + +test('toByteArraySigned', function() { + function hex(num) { + var bytes = BigInteger.valueOf(num).toByteArraySigned(); + var hex = bytesToHex(bytes); + return '0x' + hex; + } + + assert.equal(hex( 0), '0x'); + assert.equal(hex( 1), '0x01'); + assert.equal(hex(-1), '0x81'); + assert.equal(hex( 127), '0x7f'); + assert.equal(hex(-127), '0xff'); + assert.equal(hex( 255), '0x00ff'); + assert.equal(hex(-255), '0x80ff'); + assert.equal(hex( 16300), '0x3fac'); + assert.equal(hex(-16300), '0xbfac'); + assert.equal(hex( 62300), '0x00f35c'); + assert.equal(hex(-62300), '0x80f35c'); +}); + diff --git a/test/key.js b/test/key.js new file mode 100644 index 0000000..acca4d7 --- /dev/null +++ b/test/key.js @@ -0,0 +1,35 @@ +var assert = require('assert'); +var Key = require('../').Key; +var bytesToHex = require('../').convert.bytesToHex; +var hexToBytes = require('../').convert.hexToBytes; +var base58 = require('../').base58; + +// get public key from private key +test('from private base58', function() { + + var priv = '18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725'; + var pub = '0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6'; + var key = Key(hexToBytes(priv)); + + assert.equal(bytesToHex(key.getPub()), pub); + assert.equal(key.compressed, false); + + var priv = '5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh'; + var pub = '044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1'; + var addr = '1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a'; + var key = Key(priv); + + assert.equal(key.compressed, false); + assert.equal(bytesToHex(key.getPub()), pub); + assert.equal(key.getBitcoinAddress().toString(), addr); + + var priv = 'KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp'; + var pub = '034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa' + var addr = '1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9'; + var key = Key(priv); + + assert.equal(key.compressed, true); + assert.equal(bytesToHex(key.getPub()), pub); + assert.equal(key.getBitcoinAddress().toString(), addr); +}); + diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..e2bfcc5 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1 @@ +--ui qunit diff --git a/test/test.js b/test/test.js old mode 100755 new mode 100644 index 8d6e21b..d3b361b --- a/test/test.js +++ b/test/test.js @@ -1,84 +1,50 @@ -// -// Testing elliptic curve math -// ----------------------------------------------------------------------------- -module("ec"); +var assert = require('assert'); +var bitcoinjs = require('../'); +var sec = require('../src/jsbn/sec'); +var BigInteger = require('../src/jsbn/jsbn'); -var ecparams = getSECCurveByName("secp256k1"); -var rng = new SecureRandom(); - -test("Classes", function () { - expect(3); - ok(ECPointFp, "ECPointFp"); - ok(ECFieldElementFp, "ECFieldElementFp"); - ok(ECCurveFp, "ECCurveFp"); -}); - -test("Point multiplication", function () { - expect(5); - - var G = ecparams.getG(); - var n = ecparams.getN(); - - ok(G.multiply(n).isInfinity(), "Gn is infinite"); - - var k = Bitcoin.ECDSA.getBigRandom(n); - var P = G.multiply(k); - ok(!P.isInfinity(), "kG is not infinite"); - ok(P.isOnCurve(), "kG on curve"); - ok(P.multiply(n).isInfinity(), "kGn is infinite"); - - ok(P.validate(), "kG validates as a public key"); -}); +var Crypto = require('../src/crypto-js/crypto'); +var SecureRandom = require('../src/jsbn/rng'); +var rng = new SecureRandom(); -// -// Testing ECDSA -// ----------------------------------------------------------------------------- -module("ecdsa"); - -test("Classes", function () { - expect(2); - ok(Bitcoin.ECDSA, "Bitcoin.ECDSA"); - ok(Bitcoin.ECKey, "Bitcoin.ECKey"); -}); +var ecparams = sec('secp256k1'); +var ECPointFp = bitcoinjs.ECPointFp; test("Keys & Key Management", function () { - expect(5); - var s1 = new Bitcoin.ECKey(); - var p1 = s1.getPub(); - equals(p1.length, 65, "Public key is correct length"); + var p1 = bitcoinjs.Key().getPub(); + assert.equal(p1.length, 65); var p1_q = ECPointFp.decodeFrom(ecparams.getCurve(), p1); - ok(p1_q, "Decode point from generated bytestring"); - ok(p1_q.validate(), "Is a valid public point"); + assert.ok(p1_q); + assert.ok(p1_q.validate()); - var p2 = Crypto.util.hexToBytes( + var p2 = bitcoinjs.convert.hexToBytes( "0486f356006a38b847bedec1bf47013776925d939d5a35a97a4d1263e550c7f1a" + "b5aba44ab74d22892097a0e851addf07ba97e33416df5affaceeb35d5607cd23c" ); + var p2_q = ECPointFp.decodeFrom(ecparams.getCurve(), p2); - ok(p2_q, "Decode point from constant"); - ok(p2_q.validate(), "Is a valid public point"); + assert.ok(p2_q); + assert.ok(p2_q.validate()); }); test("Signing and Verifying", function () { - expect(7); - var s1 = new Bitcoin.ECKey(); + var s1 = bitcoinjs.Key(); var sig_a = s1.sign(BigInteger.ZERO); - ok(sig_a, "Sign null"); - equals(sig_a.length, 70, "Signature is correct length"); - ok(s1.verify(BigInteger.ZERO, sig_a)); - + assert.ok(sig_a, "Sign null"); + + assert.ok(s1.verify(BigInteger.ZERO, sig_a)); + var message = new BigInteger(1024, rng).toByteArrayUnsigned(); var hash = Crypto.SHA256(message, {asBytes: true}); var sig_b = s1.sign(hash); - ok(sig_b, "Sign random string"); - equals(sig_b.length, 70, "Signature is correct length"); - ok(s1.verify(hash, sig_b)); + assert.ok(sig_b, "Sign random string"); + assert.ok(s1.verify(hash, sig_b)); - var message2 = Crypto.util.hexToBytes( + var message2 = bitcoinjs.convert.hexToBytes( "12dce2c169986b3346827ffb2305cf393984627f5f9722a1b1368e933c8d" + "d296653fbe5d7ac031c4962ad0eb1c4298c3b91d244e1116b4a76a130c13" + "1e7aec7fa70184a71a2e66797052831511b93c6e8d72ae58a1980eaacb66" + @@ -86,27 +52,15 @@ test("Signing and Verifying", function () { "3d82507b932b84e4" ); var hash2 = Crypto.SHA256(message2, {asBytes: true}); - var sig_c = Crypto.util.hexToBytes( + var sig_c = bitcoinjs.convert.hexToBytes( "3044022038d9b8dd5c9fbf330565c1f51d72a59ba869aeb2c2001be959d3" + "79e861ec71960220a73945f32cf90d03127d2c3410d16cee120fa1a4b4c3" + "f273ab082801a95506c4" ); - var s2 = Crypto.util.hexToBytes( + var s2 = bitcoinjs.convert.hexToBytes( "045a1594316e433fb91f35ef4874610d22177c3f1a1060f6c1e70a609d51" + "b20be5795cd2a5eae0d6b872ba42db95e9afaeea3fbb89e98099575b6828" + "609a978528" ); - ok(Bitcoin.ECDSA.verify(hash2, sig_c, s2), "Verify constant signature"); -}); - -// -// Testing Paillier -// ----------------------------------------------------------------------------- -module("paillier"); - -test("Classes", function () { - expect(3); - ok(Bitcoin.Paillier, "Bitcoin.Paillier"); - ok(Bitcoin.Paillier.PublicKey, "Bitcoin.Paillier.PublicKey"); - ok(Bitcoin.Paillier.PrivateKey, "Bitcoin.Paillier.PrivateKey"); + assert.ok(bitcoinjs.ecdsa.verify(hash2, sig_c, s2), "Verify constant signature"); });