From 9fd54758a2318d7ed22bcdcbbdd239f9290565f6 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Mon, 5 May 2014 03:14:31 +0400 Subject: [PATCH] tx: wip sign/verify --- lib/bcoin/script.js | 66 +++++++++++++++++++++++++++++++++++++++++++++ lib/bcoin/tx.js | 40 ++++++++++++++++++++++----- lib/bcoin/utils.js | 11 ++++++++ lib/bcoin/wallet.js | 16 +++++------ package.json | 4 +-- test/wallet-test.js | 3 ++- 6 files changed, 122 insertions(+), 18 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index f91acc47..d02e278b 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -70,3 +70,69 @@ script.encode = function encode(s) { return res; }; + +script.subscript = function subscript(s) { + if (!s) + return []; + + var lastSep = -1; + for (var i = 0; i < s.length; i++) { + if (s[i] === 'codesep') + lastSep = i; + else if (s[i] === 'checksig' || + s[i] === 'checksigverify' || + s[i] === 'checkmultisig' || + s[i] === 'checkmultisigverify') { + break; + } + } + + var res = []; + for (var i = lastSep + 1; i < s.length; i++) + if (s[i] !== 'codesep') + res.push(s[i]); + + return res; +}; + +script.execute = function execute(s, stack, tx) { + for (var i = 0; i < s.length; i++) { + var o = s[i]; + if (Array.isArray(o)) { + stack.push(o); + } else if (o === 'dup') { + if (stack.length === 0) + return false; + + stack.push(stack[stack.length - 1]); + } else if (o === 'hash160') { + if (stack.length === 0) + return false; + + stack.push(bcoin.utils.ripesha(stack.pop())); + } else if (o === 'eqverify' || o === 'eq') { + if (stack.length < 2) + return false; + + var res = bcoin.utils.isEqual(stack.pop(), stack.pop()); + stack.push([ res ? 1 : 0 ]); + if (!res && o === 'eqverify') + return false; + } else if (o === 'checksigverify' || o === 'checksig') { + if (!tx || stack.length < 2) + return false; + + var pub = stack.pop(); + var sig = stack.pop(); + var res = bcoin.ecdsa.verify(tx, sig, pub); + stack.push([ res ? 1 : 0 ]); + if (!res && o ==='checksigverify') + return false; + } else { + // Unknown operation + return false; + } + } + + return true; +}; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 44787120..6a389639 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -1,3 +1,4 @@ +var assert = require('assert'); var bn = require('bn.js'); var bcoin = require('../bcoin'); @@ -53,7 +54,7 @@ TX.prototype.render = function render() { TX.prototype.input = function input(i, index) { if (i instanceof TX) - i = { tx: i, index: i }; + i = { tx: i, index: index }; var hash; if (i.tx) @@ -106,10 +107,37 @@ TX.prototype.out = function out(output, value) { }; TX.prototype.getSubscript = function getSubscript(index) { - var input = this.inputs[index]; - assert(input); + var output = this.outputs[index]; + assert(output); - var script = input.script; - - for (var i = 0; input.script. + var script = output.script; + return bcoin.script.subscript(script); +}; + +TX.prototype.subscriptHash = function subscriptHash(index, s, type) { + var copy = this.clone(); + + copy.inputs.forEach(function(input, i) { + input.script = index === i ? s : []; + }); + var verifyStr = copy.render(); + verifyStr = verifyStr.concat(bcoin.protocol.constants.hashType[type]); + var hash = utils.dsha256(verifyStr); + + return hash; +}; + +TX.prototype.validate = function validate() { + return this.inputs.every(function(input, i) { + assert(input.out.tx); + assert(input.out.tx.outputs.length > input.out.index); + + var subscript = input.out.tx.getSubscript(input.out.index); + var hash = this.subscriptHash(i, subscript, 'all'); + + var stack = []; + bcoin.script.execute(input.script, stack); + var prev = input.out.tx.outputs[input.out.index].script; + return bcoin.script.execute(prev, stack, hash); + }, this); }; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 6970759f..13837cf3 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -272,6 +272,17 @@ function testTarget(target, hash) { } utils.testTarget = testTarget; +utils.isEqual = function isEqual(a, b) { + if (a.length !== b.length) + return false; + + for (var i = 0; i < a.length; i++) + if (a[i] !== b[i]) + return false; + + return true; +}; + // TODO(indutny): use process.nextTick in node.js utils.nextTick = function nextTick(fn) { setTimeout(fn, 0); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 935b43fc..41e7fe8e 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -56,7 +56,7 @@ Wallet.prototype.own = function own(tx) { return output.script.length === 5 && output.script[0] === 'dup' && output.script[1] === 'hash160' && - utils.toHex(output.script[2]) === utils.toHex(this.getHash()) && + utils.isEqual(output.script[2], this.getHash()) && output.script[3] === 'eqverify' && output.script[4] === 'checksig'; }, this); @@ -75,17 +75,15 @@ Wallet.prototype.sign = function sign(tx, type) { // Add signature script to each input inputs.forEach(function(input, i) { - var copy = tx.clone(); var s = input.out.tx.getSubscript(input.out.index); - copy.inputs.forEach(function(input, j) { - input.script = i === j ? s : []; - }); + var hash = tx.subscriptHash(i, s, type); + var signature = bcoin.ecdsa.sign(hash, this.key).toDER(); - var verifyStr = copy.render(); - verifyStr = verifyStr.concat(bcoin.protocol.constants.hashType[type]); - var hash = utils.dsha256(verifyStr); - var signature = this.key.sign(hash).toDER(); + input.script = [ + signature, + pub + ]; }, this); return inputs.length; diff --git a/package.json b/package.json index 5bc27f04..1fab6dea 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "homepage": "https://github.com/indutny/bcoin", "dependencies": { "async": "^0.8.0", - "bn.js": "^0.2.0", - "elliptic": "^0.7.0", + "bn.js": "^0.3.0", + "elliptic": "^0.8.0", "hash.js": "^0.2.0" }, "devDependencies": { diff --git a/test/wallet-test.js b/test/wallet-test.js index f25c476d..e1f73151 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -29,9 +29,10 @@ describe('Wallet', function() { assert(w.own(src)); var tx = bcoin.tx() - .input(src, 1) + .input(src, 0) .out(w.getAddress(), 5460); w.sign(tx); + assert(tx.validate()); }); });