order keys and signatures for multisig correctly.
This commit is contained in:
parent
77623bb8d6
commit
7016991366
@ -1,4 +1,5 @@
|
|||||||
var bcoin = require('../bcoin');
|
var bcoin = require('../bcoin');
|
||||||
|
var bn = require('bn.js');
|
||||||
var constants = bcoin.protocol.constants;
|
var constants = bcoin.protocol.constants;
|
||||||
var utils = bcoin.utils;
|
var utils = bcoin.utils;
|
||||||
var assert = bcoin.utils.assert;
|
var assert = bcoin.utils.assert;
|
||||||
@ -265,6 +266,10 @@ script.multisig = function(keys, m, n) {
|
|||||||
// [ [ n ], 'checkmultisig' ]
|
// [ [ n ], 'checkmultisig' ]
|
||||||
// );
|
// );
|
||||||
|
|
||||||
|
keys = keys.sort(function(a, b) {
|
||||||
|
return new bn(a).cmp(new bn(b)) > 0;
|
||||||
|
});
|
||||||
|
|
||||||
// Using OP_1-16 for m and n:
|
// Using OP_1-16 for m and n:
|
||||||
return [ m ].concat(
|
return [ m ].concat(
|
||||||
keys,
|
keys,
|
||||||
|
|||||||
@ -144,25 +144,32 @@ TX.prototype.signature = function(input, key, type) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Build the scriptSigs for inputs, excluding the signatures
|
// Build the scriptSigs for inputs, excluding the signatures
|
||||||
TX.prototype.scriptInput = function(input, pub, nsigs) {
|
TX.prototype.scriptInput = function(input, pub) {
|
||||||
// Get the previous output's subscript
|
// Get the previous output's subscript
|
||||||
var s = input.out.tx.getSubscript(input.out.index);
|
var s = input.out.tx.getSubscript(input.out.index);
|
||||||
|
|
||||||
|
// Already has a script template (at least)
|
||||||
|
if (input.script.length)
|
||||||
|
return;
|
||||||
|
|
||||||
// P2PKH and simple tx
|
// P2PKH and simple tx
|
||||||
if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) {
|
if (bcoin.script.isPubkeyhash(s) || bcoin.script.isSimplePubkeyhash(s)) {
|
||||||
input.script = [ [], pub ];
|
input.script = [ [], pub ];
|
||||||
return;
|
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
|
// Multisig
|
||||||
// raw format: OP_FALSE [sig-1] [sig-2] ...
|
// raw format: OP_FALSE [sig-1] [sig-2] ...
|
||||||
if (bcoin.script.isMultisig(s)) {
|
if (bcoin.script.isMultisig(s)) {
|
||||||
input.script = [ [] ];
|
input.script = [ [] ];
|
||||||
var m = s[0];
|
var n = s[s.length - 2];
|
||||||
// If using pushdata instead of OP_1-16:
|
// If using pushdata instead of OP_1-16:
|
||||||
if (Array.isArray(m))
|
if (Array.isArray(n))
|
||||||
m = m[0];
|
n = n[0];
|
||||||
for (var i = 0; i < m; i++)
|
for (var i = 0; i < n; i++)
|
||||||
input.script[i + 1] = [];
|
input.script[i + 1] = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -171,8 +178,8 @@ TX.prototype.scriptInput = function(input, pub, nsigs) {
|
|||||||
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
|
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
|
||||||
if (bcoin.script.isScripthash(s)) {
|
if (bcoin.script.isScripthash(s)) {
|
||||||
input.script = [ [] ];
|
input.script = [ [] ];
|
||||||
var m = pub[0] - constants.opcodes['1'] + 1;
|
var n = pub[pub.length - 2] - constants.opcodes['1'] + 1;
|
||||||
for (var i = 0; i < m; i++)
|
for (var i = 0; i < n; i++)
|
||||||
input.script[i + 1] = [];
|
input.script[i + 1] = [];
|
||||||
// P2SH requires the redeem script after signatures
|
// P2SH requires the redeem script after signatures
|
||||||
input.script.push(pub);
|
input.script.push(pub);
|
||||||
@ -213,20 +220,65 @@ TX.prototype.signInput = function(input, key, type) {
|
|||||||
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
|
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
|
||||||
if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) {
|
if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) {
|
||||||
var len = input.script.length;
|
var len = input.script.length;
|
||||||
|
var redeem;
|
||||||
|
|
||||||
if (bcoin.script.isScripthash(s))
|
if (bcoin.script.isScripthash(s)) {
|
||||||
len--;
|
len--;
|
||||||
|
redeem = bcoin.script.decode(input.script[input.script.length - 1]);
|
||||||
|
} else {
|
||||||
|
redeem = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = redeem[0];
|
||||||
|
var n = redeem[s.length - 2];
|
||||||
|
// If using pushdata instead of OP_1-16:
|
||||||
|
if (Array.isArray(m))
|
||||||
|
m = m[0];
|
||||||
|
if (Array.isArray(n))
|
||||||
|
n = n[0];
|
||||||
|
|
||||||
|
var keys = redeem.slice(1, -2);
|
||||||
|
var pub = key.getPublic(true, 'array');
|
||||||
|
var pubn = key.getPublic(false, 'array');
|
||||||
|
|
||||||
|
// Find the key index so we can place
|
||||||
|
// the signature in the same index.
|
||||||
|
for (var ki = 0; ki < keys.length; ki++) {
|
||||||
|
if (utils.isEqual(pub, keys[ki]) || utils.isEqual(pubn, keys[ki]))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ki === keys.length)
|
||||||
|
throw new Error('Public key is not in the prev_out script');
|
||||||
|
|
||||||
|
if (ki + 1 > len - 1)
|
||||||
|
throw new Error('No signature slot available');
|
||||||
|
|
||||||
|
// Add our signature to the correct slot
|
||||||
|
// and count the total number of signatures.
|
||||||
|
var totalSigs = 0;
|
||||||
for (var i = 1; i < len; i++) {
|
for (var i = 1; i < len; i++) {
|
||||||
// Already signed
|
if (input.script[i].length) {
|
||||||
if (utils.isEqual(input.script[i], signature))
|
totalSigs++;
|
||||||
break;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (input.script[i].length === 0) {
|
if (i - 1 === ki) {
|
||||||
|
if (totalSigs >= m)
|
||||||
|
continue;
|
||||||
input.script[i] = signature;
|
input.script[i] = signature;
|
||||||
break;
|
totalSigs++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All signatures added. Finalize by removing empty slots.
|
||||||
|
if (totalSigs >= m) {
|
||||||
|
for (var i = len - 1; i >= 1; i--) {
|
||||||
|
if (!input.script[i].length)
|
||||||
|
input.script.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,9 +286,9 @@ TX.prototype.signInput = function(input, key, type) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Build the scriptSig and sign it
|
// Build the scriptSig and sign it
|
||||||
TX.prototype.scriptSig = function(input, key, pub, type, nsigs) {
|
TX.prototype.scriptSig = function(input, key, pub, type) {
|
||||||
// Build script for input
|
// Build script for input
|
||||||
this.scriptInput(input, pub, nsigs);
|
this.scriptInput(input, pub);
|
||||||
|
|
||||||
// Sign input
|
// Sign input
|
||||||
this.signInput(input, key, type);
|
this.signInput(input, key, type);
|
||||||
@ -403,7 +455,7 @@ TX.prototype.maxSize = function maxSize() {
|
|||||||
var size = copy.render().length;
|
var size = copy.render().length;
|
||||||
|
|
||||||
// Add size for signatures and public keys
|
// Add size for signatures and public keys
|
||||||
copy.inputs.forEach(function(input) {
|
copy.inputs.forEach(function(input, i) {
|
||||||
// Get the previous output's script
|
// Get the previous output's script
|
||||||
// var s = input.out.tx.outputs[input.out.index].script;
|
// var s = input.out.tx.outputs[input.out.index].script;
|
||||||
|
|
||||||
@ -435,7 +487,7 @@ TX.prototype.maxSize = function maxSize() {
|
|||||||
if (bcoin.script.isScripthash(s)) {
|
if (bcoin.script.isScripthash(s)) {
|
||||||
var script = this.inputs[i].script;
|
var script = this.inputs[i].script;
|
||||||
var redeem, m, n;
|
var redeem, m, n;
|
||||||
if (script) {
|
if (script.length) {
|
||||||
redeem = script[script.length - 1];
|
redeem = script[script.length - 1];
|
||||||
m = redeem[0];
|
m = redeem[0];
|
||||||
n = redeem[redeem.length - 2];
|
n = redeem[redeem.length - 2];
|
||||||
@ -475,7 +527,7 @@ TX.prototype.maxSize = function maxSize() {
|
|||||||
// 2. Add inputs with utxos and change output:
|
// 2. Add inputs with utxos and change output:
|
||||||
// - this.fillUnspent(unspentItems, [changeAddr]);
|
// - this.fillUnspent(unspentItems, [changeAddr]);
|
||||||
// 3. Fill input scripts (for each input):
|
// 3. Fill input scripts (for each input):
|
||||||
// - this.scriptInput(input, pub, [nsigs])
|
// - this.scriptInput(input, pub)
|
||||||
// - this.signInput(input, key, [sigHashType])
|
// - this.signInput(input, key, [sigHashType])
|
||||||
TX.prototype.utxos = function utxos(unspent) {
|
TX.prototype.utxos = function utxos(unspent) {
|
||||||
// NOTE: tx should be prefilled with all outputs
|
// NOTE: tx should be prefilled with all outputs
|
||||||
|
|||||||
@ -417,6 +417,8 @@ RequestCache.prototype.fullfill = function fullfill(id, err, data) {
|
|||||||
|
|
||||||
utils.asyncify = function asyncify(fn) {
|
utils.asyncify = function asyncify(fn) {
|
||||||
return function _asynicifedFn(err, data1, data2) {
|
return function _asynicifedFn(err, data1, data2) {
|
||||||
|
if (!fn)
|
||||||
|
return;
|
||||||
utils.nextTick(function() {
|
utils.nextTick(function() {
|
||||||
fn(err, data1, data2);
|
fn(err, data1, data2);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -40,6 +40,8 @@ function Wallet(options, passphrase) {
|
|||||||
} else if (options.hd) {
|
} else if (options.hd) {
|
||||||
this.hd = bcoin.hd.priv(options);
|
this.hd = bcoin.hd.priv(options);
|
||||||
this.key = this.hd.pair;
|
this.key = this.hd.pair;
|
||||||
|
} else if (options.key) {
|
||||||
|
this.key = options.key;
|
||||||
} else if (options.passphrase) {
|
} else if (options.passphrase) {
|
||||||
this.key = bcoin.ecdsa.genKeyPair({
|
this.key = bcoin.ecdsa.genKeyPair({
|
||||||
pers: options.scope,
|
pers: options.scope,
|
||||||
@ -74,7 +76,7 @@ function Wallet(options, passphrase) {
|
|||||||
throw new Error('n ranges between 1 and 7');
|
throw new Error('n ranges between 1 and 7');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sharedKeys.length !== this.m - 1) {
|
if (this.sharedKeys.length < this.m - 1) {
|
||||||
throw new Error(this.m + ' public keys required');
|
throw new Error(this.m + ' public keys required');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +155,7 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) {
|
|||||||
|
|
||||||
if (this.addressType === 'p2sh') {
|
if (this.addressType === 'p2sh') {
|
||||||
var keys = this.getPublicKeys();
|
var keys = this.getPublicKeys();
|
||||||
pub = bcoin.script.encode(bcoin.script.multisig(keys, m, n));
|
pub = bcoin.script.encode(bcoin.script.multisig(keys, this.m, this.n));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enc === 'base58')
|
if (enc === 'base58')
|
||||||
@ -166,10 +168,15 @@ Wallet.prototype.getPublicKey = function getPublicKey(enc) {
|
|||||||
|
|
||||||
Wallet.prototype.getPublicKeys = function() {
|
Wallet.prototype.getPublicKeys = function() {
|
||||||
var keys = this.sharedKeys.slice().map(utils.toKeyArray);
|
var keys = this.sharedKeys.slice().map(utils.toKeyArray);
|
||||||
if (keys.length < this.m) {
|
|
||||||
var pub = this.key.getPublic(this.compressed, 'array');
|
// if (keys.length < this.m) {
|
||||||
keys.push(pub);
|
var pub = this.key.getPublic(this.compressed, 'array');
|
||||||
}
|
keys.push(pub);
|
||||||
|
|
||||||
|
keys = keys.sort(function(a, b) {
|
||||||
|
return new bn(a).cmp(new bn(b)) > 0;
|
||||||
|
});
|
||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -286,7 +293,6 @@ Wallet.prototype.sign = function sign(tx, type, inputs) {
|
|||||||
|
|
||||||
var pub = this.getPublicKey();
|
var pub = this.getPublicKey();
|
||||||
var key = this.key;
|
var key = this.key;
|
||||||
var nsigs = this.m;
|
|
||||||
|
|
||||||
inputs = inputs || tx.inputs;
|
inputs = inputs || tx.inputs;
|
||||||
|
|
||||||
@ -296,7 +302,7 @@ Wallet.prototype.sign = function sign(tx, type, inputs) {
|
|||||||
if (!input.out.tx || !this.ownOutput(input.out.tx))
|
if (!input.out.tx || !this.ownOutput(input.out.tx))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
tx.scriptSig(input, key, pub, type, nsigs);
|
tx.scriptSig(input, key, pub, type);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, this);
|
}, this);
|
||||||
|
|||||||
@ -46,6 +46,7 @@ describe('Wallet', function() {
|
|||||||
|
|
||||||
it('should multisign/verify TX', function() {
|
it('should multisign/verify TX', function() {
|
||||||
var w = bcoin.wallet();
|
var w = bcoin.wallet();
|
||||||
|
var k2 = bcoin.wallet().getPublicKey();
|
||||||
|
|
||||||
// Input transcation
|
// Input transcation
|
||||||
var src = bcoin.tx({
|
var src = bcoin.tx({
|
||||||
@ -53,6 +54,7 @@ describe('Wallet', function() {
|
|||||||
value: 5460 * 2,
|
value: 5460 * 2,
|
||||||
minSignatures: 1,
|
minSignatures: 1,
|
||||||
keys: [ w.getPublicKey(), w.getPublicKey().concat(1) ]
|
keys: [ w.getPublicKey(), w.getPublicKey().concat(1) ]
|
||||||
|
// keys: [ w.getPublicKey(), k2 ]
|
||||||
}, {
|
}, {
|
||||||
value: 5460 * 2,
|
value: 5460 * 2,
|
||||||
address: w.getAddress() + 'x'
|
address: w.getAddress() + 'x'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user