diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index c3c832d7..25ce4ca4 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -949,29 +949,29 @@ Pool.prototype.removeWallet = function removeWallet(w) { Pool.prototype.watchWallet = function watchWallet(w) { if (w.type === 'scripthash') { // For the redeem script hash in outputs: - this.watch(w.getFullHash()); + this.watch(w.getScriptHash()); // For the redeem script in inputs: - this.watch(w.getFullPublicKey()); + this.watch(w.getScript()); } // For the pubkey hash in outputs: - this.watch(w.getOwnHash()); + this.watch(w.getKeyHash()); // For the pubkey in inputs: - this.watch(w.getOwnPublicKey()); + this.watch(w.getPublicKey()); }; Pool.prototype.unwatchWallet = function unwatchWallet(w) { if (w.type === 'scripthash') { // For the redeem script hash in p2sh outputs: - this.unwatch(w.getFullHash()); + this.unwatch(w.getScriptHash()); // For the redeem script in p2sh inputs: - this.unwatch(w.getFullPublicKey()); + this.unwatch(w.getScript()); } // For the pubkey hash in p2pk/multisig outputs: - this.unwatch(w.getOwnHash()); + this.unwatch(w.getKeyHash()); // For the pubkey in p2pkh inputs: - this.unwatch(w.getOwnPublicKey()); + this.unwatch(w.getPublicKey()); }; Pool.prototype.searchWallet = function(w) { diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 8b4d2c71..aec5a124 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -982,6 +982,26 @@ script.size = function size(s) { return bcoin.script.encode(s).length; }; +script.normalize = function normalize(s) { + var bytes = true; + var i; + + for (i = 0; bytes && i < s.length; i++) { + if (typeof s[i] !== 'number') + bytes = false; + } + + if (bytes) + s = script.decode(s); + + s = script.subscript(s); + + if (script.lockTime(s)) + s = s.slice(3); + + return s; +}; + script.lockTime = function lockTime(s) { var lock = s[0]; var res = s.length > 3 diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 5901b314..b242887e 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -157,8 +157,8 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) { }; // Build the scriptSigs for inputs, excluding the signatures -TX.prototype.scriptInput = function scriptInput(index, pub) { - var input, s, n, i, redeem; +TX.prototype.scriptInput = function scriptInput(index, pub, redeem) { + var input, s, standard, n, i; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -174,26 +174,25 @@ TX.prototype.scriptInput = function scriptInput(index, pub) { if (input.script.length) return; - // P2PK + // P2SH + if (bcoin.script.isScripthash(s)) { + assert(redeem); + s = bcoin.script.normalize(redeem); + } else { + redeem = null; + } + if (bcoin.script.isPubkey(s)) { + // P2PK input.script = [ [] ]; - this._recalculateFee(); - return; - } - - // P2PKH - if (bcoin.script.isPubkeyhash(s)) { + } else if (bcoin.script.isPubkeyhash(s)) { + // P2PKH input.script = [ [], pub ]; - this._recalculateFee(); - return; - } - - // NOTE for multisig: Technically we should create m signature slots, - // but we create n signature slots so we can order the signatures properly. - - // Multisig - // raw format: OP_FALSE [sig-1] [sig-2] ... - if (bcoin.script.isMultisig(s)) { + } else if (bcoin.script.isMultisig(s)) { + // Bare Multisig + // Technically we should create m signature slots, + // but we create n signature slots so we can order + // the signatures properly. input.script = [ [] ]; n = s[s.length - 2]; // If using pushdata instead of OP_1-16: @@ -201,34 +200,19 @@ TX.prototype.scriptInput = function scriptInput(index, pub) { n = n[0] || 0; for (i = 0; i < n; i++) input.script[i + 1] = []; - this._recalculateFee(); - return; } - // P2SH multisig - // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] - if (bcoin.script.isScripthash(s)) { - input.script = [ [] ]; - redeem = bcoin.script.decode(pub); - n = redeem[redeem.length - 2]; - // If using pushdata instead of OP_1-16: - if (Array.isArray(n)) - n = n[0] || 0; - for (i = 0; i < n; i++) - input.script[i + 1] = []; - // P2SH requires the redeem script after signatures - input.script.push(pub); - this._recalculateFee(); - return; - } + // P2SH requires the redeem script after signatures + if (redeem) + input.script.push(redeem); - throw new Error('scriptInput(): Could not identify prev_out type'); + this._recalculateFee(); }; // Sign the now-built scriptSigs TX.prototype.signInput = function signInput(index, key, type) { var input, s, hash, signature; - var len, redeem, m, keys, pub, pubn, ki, totalSigs, i; + var len, redeem, m, keys, pub, pubn, ki, signatures, i; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -262,33 +246,30 @@ TX.prototype.signInput = function signInput(index, key, type) { // Add the sighash as a single byte to the signature signature = signature.concat(type); - // P2PK + // Script length, needed for multisig + len = input.script.length; + + // P2SH + if (bcoin.script.isScripthash(s)) { + s = bcoin.script.normalize(redeem); + // Decrement `len` to avoid the redeem script + len--; + } + if (bcoin.script.isPubkey(s)) { + // P2PK input.script[0] = signature; - return; - } - - // P2PKH - if (bcoin.script.isPubkeyhash(s)) { + } else if (bcoin.script.isPubkeyhash(s)) { + // P2PKH input.script[0] = signature; - return; - } - - // Multisig - // raw format: OP_FALSE [sig-1] [sig-2] ... - // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] - if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { - len = input.script.length; - - if (bcoin.script.isScripthash(s)) - len--; - - m = redeem[0]; + } else if (bcoin.script.isMultisig(s)) { + // Multisig + m = s[0]; // If using pushdata instead of OP_1-16: if (Array.isArray(m)) m = m[0] || 0; - keys = redeem.slice(1, -2); + keys = s.slice(1, -2); pub = key.getPublic(true, 'array'); pubn = key.getPublic(false, 'array'); @@ -309,44 +290,35 @@ TX.prototype.signInput = function signInput(index, key, type) { // Add our signature to the correct slot // and count the total number of signatures. - totalSigs = 0; + signatures = 0; for (i = 1; i < len; i++) { if (Array.isArray(input.script[i]) && input.script[i].length) { - totalSigs++; + signatures++; continue; } if (i - 1 === ki) { - if (totalSigs >= m) + if (signatures >= m) continue; input.script[i] = signature; - totalSigs++; + signatures++; } } // All signatures added. Finalize by removing empty slots. - if (totalSigs >= m) { + if (signatures >= m) { for (i = len - 1; i >= 1; i--) { if (Array.isArray(input.script[i]) && !input.script[i].length) input.script.splice(i, 1); } } - - return; } - - throw new Error('signInput(): Could not identify prev_out type'); }; // Build the scriptSig and sign it -TX.prototype.scriptSig = function scriptSig(index, key, pub, type) { +TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) { var input; - if (!utils.isBuffer(pub)) { - type = pub; - pub = key.getPublic(true, 'array'); - } - if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -355,7 +327,7 @@ TX.prototype.scriptSig = function scriptSig(index, key, pub, type) { assert(input); // Build script for input - this.scriptInput(index, pub); + this.scriptInput(index, pub, redeem); // Sign input this.signInput(index, key, type); @@ -523,7 +495,7 @@ TX.prototype.getSubscript = function getSubscript(index) { TX.prototype.subscriptHash = function subscriptHash(index, s, type) { var copy = this.clone(); - var verifyStr, hash; + var msg, hash; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -573,11 +545,11 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) { copy.inputs[0].script = s; } - verifyStr = copy.render(true); + msg = copy.render(true); - utils.writeU32(verifyStr, type, verifyStr.length); + utils.writeU32(msg, type, msg.length); - hash = utils.dsha256(verifyStr); + hash = utils.dsha256(msg); return hash; }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index f323686d..6e8c06ed 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -81,7 +81,7 @@ function Wallet(options, passphrase) { this.m = 1; this.n = 1; - this.prefix = 'bt/wallet/' + this.getOwnAddress() + '/'; + this.prefix = 'bt/wallet/' + this.getKeyAddress() + '/'; this.multisig(options.multisig || {}); @@ -126,7 +126,7 @@ Wallet.prototype._init = function init() { }; Wallet.prototype.multisig = function multisig(options) { - var pub = this.getOwnPublicKey(); + var pub = this.getPublicKey(); options.type = options.type || options.prefix; options.keys = options.keys || options.pubkeys || []; @@ -235,21 +235,21 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { return priv; }; -Wallet.prototype.getFullPublicKey = function getFullPublicKey(enc) { - var pub = this.getOwnPublicKey(); - - if (this.type === 'scripthash') - pub = bcoin.script.encode(bcoin.script.redeem(this.keys, this.m, this.n)); - - if (enc === 'base58') - return utils.toBase58(pub); - else if (enc === 'hex') - return utils.toHex(pub); - else - return pub; +Wallet.prototype.getScript = function getScript(enc) { + if (this.redeem) + return this.redeem.slice(); + return bcoin.script.encode(bcoin.script.redeem(this.keys, this.m, this.n)); }; -Wallet.prototype.getOwnPublicKey = function getOwnPublicKey(enc) { +Wallet.prototype.getScriptHash = function getScriptHash() { + return utils.ripesha(this.getScript()); +}; + +Wallet.prototype.getScriptAddress = function getScriptAddress() { + return Wallet.hash2addr(this.getScriptHash(), this.type); +}; + +Wallet.prototype.getPublicKey = function getPublicKey(enc) { var pub = this.key.getPublic(this.compressed, 'array'); if (enc === 'base58') @@ -260,32 +260,24 @@ Wallet.prototype.getOwnPublicKey = function getOwnPublicKey(enc) { return pub; }; -Wallet.prototype.getPublicKey = function getPublicKey(enc) { - return this.getFullPublicKey(enc); +Wallet.prototype.getKeyHash = function getKeyHash() { + return Wallet.key2hash(this.getPublicKey()); }; -Wallet.prototype.getFullHash = function getFullHash() { - return Wallet.key2hash(this.getFullPublicKey()); -}; - -Wallet.prototype.getFullAddress = function getFullAddress() { - return Wallet.hash2addr(this.getFullHash(), this.type); -}; - -Wallet.prototype.getOwnHash = function getOwnHash() { - return Wallet.key2hash(this.getOwnPublicKey()); -}; - -Wallet.prototype.getOwnAddress = function getOwnAddress() { - return Wallet.hash2addr(this.getOwnHash(), 'pubkeyhash'); +Wallet.prototype.getKeyAddress = function getKeyAddress() { + return Wallet.hash2addr(this.getKeyHash(), 'pubkeyhash'); }; Wallet.prototype.getHash = function getHash() { - return Wallet.key2hash(this.getFullPublicKey()); + if (this.type === 'scripthash') + return this.getScriptHash(); + return this.getKeyHash(); }; Wallet.prototype.getAddress = function getAddress() { - return Wallet.hash2addr(this.getFullHash(), this.type); + if (this.type === 'scripthash') + return this.getScriptAddress(); + return this.getKeyAddress(); }; Wallet.key2hash = function key2hash(key) { @@ -351,9 +343,9 @@ Wallet.validateAddress = function validateAddress(addr, prefix) { }; Wallet.prototype.ownOutput = function ownOutput(tx, index) { - var scriptHash = this.getFullHash(); - var hash = this.getOwnHash(); - var key = this.getOwnPublicKey(); + var scripthash = this.getScriptHash(); + var hash = this.getKeyHash(); + var key = this.getPublicKey(); var keys = this.keys; var outputs = tx.outputs.filter(function(output, i) { @@ -371,7 +363,7 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { if (bcoin.script.isMultisig(s, keys)) return true; - if (bcoin.script.isScripthash(s, scriptHash)) + if (bcoin.script.isScripthash(s, scripthash)) return true; return false; @@ -384,10 +376,10 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { }; Wallet.prototype.ownInput = function ownInput(tx, index) { - var scriptHash = this.getFullHash(); - var hash = this.getOwnHash(); - var key = this.getOwnPublicKey(); - var redeem = this.getFullPublicKey(); + var scripthash = this.getScriptHash(); + var hash = this.getKeyHash(); + var key = this.getPublicKey(); + var redeem = this.getScript(); var keys = this.keys; var inputs = tx.inputs.filter(function(input, i) { @@ -425,7 +417,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { if (bcoin.script.isMultisig(s, keys)) return true; - if (bcoin.script.isScripthash(s, scriptHash)) + if (bcoin.script.isScripthash(s, scripthash)) return true; return false; @@ -445,12 +437,13 @@ Wallet.prototype.scriptOutputs = function scriptOutputs(tx, options) { }; Wallet.prototype.fillUnspent = function fillUnspent(tx, changeAddress) { - changeAddress = changeAddress || this.changeAddress || this.getFullAddress(); + changeAddress = changeAddress || this.changeAddress || this.getAddress(); return tx.fillUnspent(this.unspent(), changeAddress); }; Wallet.prototype.scriptInputs = function scriptInputs(tx) { - var pub = this.getFullPublicKey(); + var pub = this.getPublicKey(); + var redeem = this.getScript(); var inputs = tx.inputs; inputs = inputs.filter(function(input, i) { @@ -461,7 +454,7 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx) { if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; - tx.scriptInput(i, pub); + tx.scriptInput(i, pub, redeem); return true; }, this); @@ -495,7 +488,8 @@ Wallet.prototype.sign = function sign(tx, type) { if (!type) type = 'all'; - var pub = this.getFullPublicKey(); + var pub = this.getPublicKey(); + var redeem = this.getScript(); var key = this.key; var inputs = tx.inputs; @@ -508,7 +502,7 @@ Wallet.prototype.sign = function sign(tx, type) { if (!input.out.tx || !this.ownOutput(input.out.tx)) return false; - tx.scriptSig(i, key, pub, type); + tx.scriptSig(i, key, pub, redeem, type); return true; }, this); @@ -584,8 +578,8 @@ Wallet.prototype.toAddress = function toAddress() { }); return { - address: this.getFullAddress(), - hash: utils.toHex(this.getFullHash()), + address: this.getAddress(), + hash: utils.toHex(this.getHash()), received: received, sent: sent, balance: this.balance(), @@ -602,7 +596,7 @@ Wallet.prototype.toJSON = function toJSON(encrypt) { label: this.label, address: this.getAddress(), balance: utils.toBTC(this.balance()), - pub: this.getOwnPublicKey('base58'), + pub: this.getPublicKey('base58'), priv: encrypt ? encrypt(this.getPrivateKey('base58')) : this.getPrivateKey('base58'), diff --git a/test/wallet-test.js b/test/wallet-test.js index 2383f908..07543c52 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -81,7 +81,7 @@ describe('Wallet', function() { }); var k2 = w.getPublicKey().concat(1); w.addKey(k2); - assert.equal(w.getOwnAddress(), w.getFullAddress()); + assert.equal(w.getKeyAddress(), w.getAddress()); // Input transcation var src = bcoin.tx({