From 30fc37d07eeeb11224ef130b6f055030ca4fe3b1 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Fri, 30 Aug 2013 14:40:01 -0400 Subject: [PATCH 1/9] Add Sign.Transaction() helper, for transaction signing including P2SH --- Sign.js | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ Transaction.js | 2 +- 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 Sign.js diff --git a/Sign.js b/Sign.js new file mode 100644 index 0000000..5097954 --- /dev/null +++ b/Sign.js @@ -0,0 +1,118 @@ + +function signOne(hash, addrStr, keys) +{ + var keyObj = keys[addrStr]; + var rawPrivKey = new Buffer(keyObj.priv, 'hex'); + var key = new KeyModule.Key(); + key.private = rawPrivKey; + var signature = key.signSync(hash); + + return signature; +} + +function signTxIn(nIn, tx, txInputs, network, keys, scripts) +{ + // locate TX input needing a signature + var txin = tx.ins[nIn]; + var scriptSig = txin.getScript(); + + // locate TX output, within txInputs + var txoutHash = txin.getOutpointHash(); + if (!(txoutHash in txInputs)) + throw new Error("signTxIn missing input hash"); + var txFrom = txInputs[txoutHash]; + var txoutIndex = txin.getOutpointIndex(); + if (txFrom.outs.length >= txoutIndex) + throw new Error("signTxIn missing input index"); + var txout = txFrom.outs[txoutIndex]; + var scriptPubKey = txout.getScript(); + + // detect type of transaction, and extract useful elements + var txType = scriptPubKey.classify(); + if (txType == TX_UNKNOWN) + throw new Error("unknown TX type"); + var scriptData = scriptPubKey.capture(); + + // if P2SH, lookup the script + var subscriptRaw = undefined; + var subscript = undefined; + var subType = undefined; + var subData = undefined; + if (txType == TX_SCRIPTHASH) { + var addr = new Address(network.addressScript, scriptData[0]); + var addrStr = addr.toString(); + if (!(addrStr in scripts)) + throw new Error("unknown script hash address"); + + subscriptRaw = new Buffer(scripts[addrStr], 'hex'); + subscript = new Script(subscriptRaw); + subType = subscript.classify(); + if (subType == TX_UNKNOWN) + throw new Error("unknown subscript TX type"); + subData = subscript.capture(); + } + + var hash = tx.hashForSignature(scriptPubKey, i, 0); + + switch (txType) { + case TX_PUBKEYHASH: + // already signed + if (scriptSig.chunks.length > 0) + return; + + var addr = new Address(network.addressPubkey, scriptData[0]); + var addrStr = addr.toString(); + if (!(addrStr in keys)) + throw new Error("unknown pubkey hash address"); + + var signature = signOne(hash, addrStr, keys); + scriptSig.writeBytes(signature); + scriptSig.writeBytes(key.public); + break; + + case TX_SCRIPTHASH: + // already signed + if (scriptSig.chunks.length > 0) + return; + + var addr = new Address(network.addressPubkey, subData[0]); + var addrStr = addr.toString(); + if (!(addrStr in keys)) + throw new Error("unknown script(pubkey hash) address"); + + var signature = signOne(hash, addrStr, keys); + scriptSig.writeBytes(signature); + scriptSig.writeBytes(key.public); + break; + + case TX_MULTISIG: + while (scriptSig.chunks.length < scriptData.length) { + scriptSig.writeBytes(util.EMPTY_BUFFER); + } + for (var i = 0; i < scriptData.length; i++) { + // skip already signed + if (scriptSig.chunks[i].length > 0) + continue; + + var pubkeyhash = util.sha256ripe160(scriptSig.chunks[i]); + var addr = new Address(network.addressPubkey, pubkeyhash); + var addrStr = addr.toString(); + if (!(addrStr in keys)) + continue; + + var signature = signOne(hash, addrStr, keys); + scriptSig.chunks[i] = signature; + } + break; + } + + if (txtype == TX_SCRIPTHASH) + scriptSig.writeBytes(subscriptRaw); +} + +exports.Transaction = function Transaction(tx, txInputs, network, keys, scripts) +{ + for (var i = 0; i < tx.ins.length; i++) + signTxIn(i, tx, txInputs, network, keys, scripts); +}; + diff --git a/Transaction.js b/Transaction.js index b96dcef..b4580b3 100644 --- a/Transaction.js +++ b/Transaction.js @@ -45,7 +45,7 @@ function spec(b) { return Buffer.concat([this.o, slen, this.s, qbuf]); }; - TransactionIn.prototype.getOutpointHash = function getOutpointIndex() { + TransactionIn.prototype.getOutpointHash = function getOutpointHash() { if ("undefined" !== typeof this.o.outHashCache) { return this.o.outHashCache; } From 2fda3c11872b0b871903a3f5370fcb568a5d83b1 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Fri, 30 Aug 2013 14:55:04 -0400 Subject: [PATCH 2/9] Sign: add support for signing ancient TX_PUBKEY transactions --- Sign.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Sign.js b/Sign.js index 5097954..24889dc 100644 --- a/Sign.js +++ b/Sign.js @@ -55,6 +55,21 @@ function signTxIn(nIn, tx, txInputs, network, keys, scripts) var hash = tx.hashForSignature(scriptPubKey, i, 0); switch (txType) { + case TX_PUBKEY: + // already signed + if (scriptSig.chunks.length > 0) + return; + + var pubkeyhash = util.sha256ripe160(scriptData[0]); + var addr = new Address(network.addressPubkey, pubkeyhash); + var addrStr = addr.toString(); + if (!(addrStr in keys)) + throw new Error("unknown pubkey"); + + var signature = signOne(hash, addrStr, keys); + scriptSig.writeBytes(signature); + break; + case TX_PUBKEYHASH: // already signed if (scriptSig.chunks.length > 0) From b38863e4db6c6629222046f056bf20f3e520744d Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Fri, 13 Sep 2013 15:32:35 -0400 Subject: [PATCH 3/9] util/util: remove buf64(), both incorrect and unnecessary --- util/util.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/util/util.js b/util/util.js index fe170a1..076951f 100644 --- a/util/util.js +++ b/util/util.js @@ -323,15 +323,6 @@ var varStrBuf = exports.varStrBuf = function varStrBuf(s) { return Buffer.concat(varIntBuf(s.length), s); }; -var buf64 = exports.buf64 = function buf64(n) { - var lo = n & 0xffffffff; - var hi = (n >>> 32); - var buf = new Buffer(4 + 4); - buf.writeUInt32LE(lo, 0); - buf.writeUInt32LE(hi, 4); - return buf; -}; - // Initializations exports.NULL_HASH = new Buffer(32).fill(0); exports.EMPTY_BUFFER = new Buffer(0); From fde1cb75a5c2e8bc34cbb853d3f3b607bcce50bc Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sat, 14 Sep 2013 22:58:23 -0400 Subject: [PATCH 4/9] RpcClient: getAccountAddress takes string param --- RpcClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RpcClient.js b/RpcClient.js index 631c089..2137bfb 100644 --- a/RpcClient.js +++ b/RpcClient.js @@ -35,7 +35,7 @@ function ClassSpec(b) { dumpPrivKey: '', encryptWallet: '', getAccount: '', - getAccountAddress: '', + getAccountAddress: 'str', getAddedNodeInfo: '', getAddressesByAccount: '', getBalance: 'str int', From da5719249f03ecae63e99abe1b60fbca20c5e025 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sat, 14 Sep 2013 22:59:29 -0400 Subject: [PATCH 5/9] util/util: helper parseValue() converts bitcoin decimals into bigint --- test/basic.js | 19 +++++++++++++++++++ test/values.json | 7 +++++++ util/util.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 test/values.json diff --git a/test/basic.js b/test/basic.js index cdf1e71..f34a2a7 100644 --- a/test/basic.js +++ b/test/basic.js @@ -5,6 +5,7 @@ var Address = require('../Address').class(); var PrivateKey = require('../PrivateKey').class(); var networks = require('../networks'); var KeyModule = require('../Key'); +var coinUtil = require('../util/util'); suite('basic'); @@ -110,8 +111,22 @@ function is_invalid(datum) assert.equal(valid, false); } +function test_value(datum) +{ + if (datum.length != 2) + throw new Error("Bad test"); + + var decimal = datum[0]; + var intStr = datum[1]; + + var bn = coinUtil.parseValue(decimal); + assert.notEqual(bn, undefined); + assert.equal(bn.toString(), intStr); +} + var dataValid = JSON.parse(fs.readFileSync('test/base58_keys_valid.json')); var dataInvalid = JSON.parse(fs.readFileSync('test/base58_keys_invalid.json')); +var dataValues = JSON.parse(fs.readFileSync('test/values.json')); test('valid', function() { dataValid.forEach(function(datum) { is_valid(datum); }); @@ -121,3 +136,7 @@ test('invalid', function() { dataInvalid.forEach(function(datum) { is_invalid(datum); }); }); +test('values', function() { + dataValues.forEach(function(datum) { test_value(datum); }); +}); + diff --git a/test/values.json b/test/values.json new file mode 100644 index 0000000..517acbc --- /dev/null +++ b/test/values.json @@ -0,0 +1,7 @@ +[ + [ "0", "0" ], + [ "1.0", "100000000" ], + [ "0.1", "10000000" ], + [ ".1", "10000000" ], + [ "0.0005", "50000" ] +] diff --git a/util/util.js b/util/util.js index 076951f..974a93d 100644 --- a/util/util.js +++ b/util/util.js @@ -107,6 +107,49 @@ var formatValue = exports.formatValue = function (valueBuffer) { return integerPart+"."+decimalPart; }; +var reFullVal = /^\s*(\d+)\.(\d+)/; +var reFracVal = /^\s*\.(\d+)/; +var reWholeVal = /^\s*(\d+)/; + +function padFrac(frac) +{ + while (frac.length < 8) + frac = frac + '0'; + return frac; +} + +function parseFullValue(res) +{ + return bignum(res[1]).mul('100000000').add(padFrac(res[2])); +} + +function parseFracValue(res) +{ + return bignum(padFrac(res[1])); +} + +function parseWholeValue(res) +{ + return bignum(res[1]).mul('100000000'); +} + +exports.parseValue = function parseValue(valueStr) +{ + var res = valueStr.match(reFullVal); + if (res) + return parseFullValue(res); + + res = valueStr.match(reFracVal); + if (res) + return parseFracValue(res); + + res = valueStr.match(reWholeVal); + if (res) + return parseWholeValue(res); + + return undefined; +}; + var pubKeyHashToAddress = exports.pubKeyHashToAddress = function (pubKeyHash, addressVersion) { if (!pubKeyHash) return ""; From f7afb9a91afd7e04bf01906ebf7b142409d116d8 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Mon, 23 Sep 2013 09:28:21 -0400 Subject: [PATCH 6/9] Transaction: helper to return parsed-out list of inputs --- Transaction.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Transaction.js b/Transaction.js index b4580b3..7c83c5d 100644 --- a/Transaction.js +++ b/Transaction.js @@ -181,6 +181,19 @@ function spec(b) { return this.hash; }; + // convert encoded list of inputs to easy-to-use JS list-of-lists + Transaction.prototype.inputs = function inputs() { + var res = []; + for (var i = 0; i < this.ins.length; i++) { + var txin = this.ins[i]; + var outHash = txin.getOutpointHash(); + var outIndex = txin.getOutpointIndex(); + res.push([outHash, outIndex]); + } + + return res; + } + /** * Load and cache transaction inputs. * From 688f3f91fc3ad884ba9aeec28751ffeadfc0942e Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 3 Oct 2013 14:36:38 -0400 Subject: [PATCH 7/9] Fixes to package.json Upped version number so that changes to npm compatibility can be reflected in a new npm version --- package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index cd150f6..7b9dc7b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "libcoin", "description": "Bitcoin Library", - "version": "0.1.3", + "version": "0.1.4", "author": { "name": "Stephen Pair", "email": "stephen@bitpay.com" @@ -25,15 +25,15 @@ }, "scripts": {}, "dependencies": { - "classtool": ">=1.0.0", - "base58-native": ">=0.1.1", - "bindings": ">=1.1.0", - "bufferput": ">=0.1.1", - "bignum": "0.6.1", - "binary": "0.3.0", - "step": "0.0.4", - "buffers": ">=0.1.1", - "buffertools": "1.1.1" + "classtool": "=1.0.0", + "base58-native": "=0.1.1", + "bindings": "=1.1.0", + "bufferput": "=0.1.1", + "bignum": "=0.6.1", + "binary": "=0.3.0", + "step": "=0.0.4", + "buffers": "=0.1.1", + "buffertools": "=1.1.1" }, "devDependencies": {}, "license": "MIT" From ec9a12f0a7850fa625e9c3b3bbccf8e64a2c6be5 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 3 Oct 2013 15:21:51 -0400 Subject: [PATCH 8/9] fixed index issue with package.json Removed the "main: 'index'" property from package.json since there is no index.js. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 7b9dc7b..3fa7852 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ {"name": "Stefan Thomas", "email": "moon@justmoon.net"}, {"name": "Jeff Garzik", "email": "jgarzik@bitpay.com"} ], - "main": "./index", "keywords": [ "bitcoin", "btc", From 2430bde72e05fb8080ea417863c63ad2b37050f1 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Thu, 31 Oct 2013 09:40:50 -0400 Subject: [PATCH 9/9] WalletKey: internalize external JSON database object --- WalletKey.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WalletKey.js b/WalletKey.js index ec78153..1fb8c23 100644 --- a/WalletKey.js +++ b/WalletKey.js @@ -31,6 +31,12 @@ function ClassSpec(b) { return obj; }; + WalletKey.prototype.fromObj = function(obj) { + this.created = obj.created; + this.privKey = new KeyModule.Key(); + this.privKey.private = new Buffer(obj.priv, 'hex'); + }; + return WalletKey; }; module.defineClass(ClassSpec);