sign unknown txs.
This commit is contained in:
parent
b1d46206a8
commit
b9041a0157
@ -1405,9 +1405,7 @@ script.isNulldata = function isNulldata(s) {
|
||||
if (s.length !== 2)
|
||||
return false;
|
||||
|
||||
res = s[0] === 'ret'
|
||||
&& Array.isArray(s[1])
|
||||
&& s[1].length <= constants.script.maxOpReturn;
|
||||
res = s[0] === 'ret' && script.isData(s[1]);
|
||||
|
||||
if (!res)
|
||||
return false;
|
||||
@ -1659,6 +1657,10 @@ script.isCoinbase = function isCoinbase(s, block, strict) {
|
||||
return coinbase;
|
||||
};
|
||||
|
||||
// Detect script array types. Note: these functions
|
||||
// are not mutually exclusive. Only use for
|
||||
// verification, not detection.
|
||||
|
||||
script.isHash = function isHash(hash) {
|
||||
if (!utils.isBuffer(hash))
|
||||
return false;
|
||||
@ -1683,6 +1685,20 @@ script.isSignature = function isSignature(sig, allowZero) {
|
||||
return sig.length >= 9 && sig.length <= 73;
|
||||
};
|
||||
|
||||
script.isEmpty = function isEmpty(data) {
|
||||
if (!utils.isBuffer(data))
|
||||
return false;
|
||||
|
||||
return data.length === 0;
|
||||
};
|
||||
|
||||
script.isData = function isData(data) {
|
||||
if (!utils.isBuffer(data))
|
||||
return false;
|
||||
|
||||
return data.length <= constants.script.maxOpReturn;
|
||||
};
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
|
||||
/**
|
||||
* A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
|
||||
|
||||
208
lib/bcoin/tx.js
208
lib/bcoin/tx.js
@ -189,7 +189,7 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) {
|
||||
// P2PKH
|
||||
input.script = [ [], pub ];
|
||||
} else if (bcoin.script.isMultisig(s)) {
|
||||
// Bare Multisig
|
||||
// Multisig
|
||||
// Technically we should create m signature slots,
|
||||
// but we create n signature slots so we can order
|
||||
// the signatures properly.
|
||||
@ -201,6 +201,12 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) {
|
||||
// Fill script with `n` signature slots.
|
||||
for (i = 0; i < n; i++)
|
||||
input.script[i + 1] = [];
|
||||
} else {
|
||||
// Likely a non-standard scripthash multisig
|
||||
// input. Just set up the empty array. Also,
|
||||
// only allow nonstandard types for scripthash.
|
||||
if (redeem)
|
||||
input.script = [ [] ];
|
||||
}
|
||||
|
||||
// P2SH requires the redeem script after signatures
|
||||
@ -313,7 +319,9 @@ TX.prototype.signInput = function signInput(index, key, type) {
|
||||
input.script[0] = signature;
|
||||
|
||||
return true;
|
||||
} else if (bcoin.script.isPubkeyhash(s)) {
|
||||
}
|
||||
|
||||
if (bcoin.script.isPubkeyhash(s)) {
|
||||
// P2PKH
|
||||
|
||||
// Something is wrong. Abort.
|
||||
@ -331,100 +339,134 @@ TX.prototype.signInput = function signInput(index, key, type) {
|
||||
input.script[0] = signature;
|
||||
|
||||
return true;
|
||||
} else if (bcoin.script.isMultisig(s)) {
|
||||
}
|
||||
|
||||
if (bcoin.script.isMultisig(s)) {
|
||||
// Multisig
|
||||
|
||||
// Grab the redeem script's keys to figure
|
||||
// out where our key should go.
|
||||
keys = s.slice(1, -2);
|
||||
|
||||
// Grab `m` value (number of sigs required).
|
||||
m = s[0];
|
||||
|
||||
// Grab `n` value (number of keys).
|
||||
n = s[s.length - 2];
|
||||
|
||||
// Something is very wrong here. Abort.
|
||||
if (len - 1 > n)
|
||||
} else {
|
||||
// Only allow non-standard signing for
|
||||
// scripthash.
|
||||
if (len !== input.script.length - 1)
|
||||
return false;
|
||||
|
||||
// Count the number of current signatures.
|
||||
signatures = 0;
|
||||
for (i = 1; i < len; i++) {
|
||||
if (Array.isArray(input.script[i]) && input.script[i].length)
|
||||
signatures++;
|
||||
keys = [];
|
||||
|
||||
for (i = 0; i < s.length; i++) {
|
||||
if (bcoin.script.isKey(s[i]))
|
||||
keys.push(s[i]);
|
||||
}
|
||||
|
||||
// Signatures are already finalized.
|
||||
if (signatures === m && len - 1 === m)
|
||||
return true;
|
||||
|
||||
// This can happen in a case where another
|
||||
// implementation adds signatures willy-nilly
|
||||
// or by `m`. Add some signature slots for
|
||||
// us to use.
|
||||
while (len - 1 < n) {
|
||||
input.script.splice(len, 0, []);
|
||||
len++;
|
||||
}
|
||||
|
||||
// Grab the redeem script's keys to figure
|
||||
// out where our key should go.
|
||||
keys = s.slice(1, -2);
|
||||
|
||||
// Find the key index so we can place
|
||||
// the signature in the same index.
|
||||
for (ki = 0; ki < keys.length; ki++) {
|
||||
if (utils.isEqual(pub, keys[ki]))
|
||||
break;
|
||||
}
|
||||
|
||||
// Our public key is not in the prev_out
|
||||
// script. We tried to sign a transaction
|
||||
// that is not redeemable by us.
|
||||
if (ki === keys.length)
|
||||
return false;
|
||||
|
||||
// Offset key index by one to turn it into
|
||||
// "sig index". Accounts for OP_0 byte at
|
||||
// the start.
|
||||
ki++;
|
||||
|
||||
// Add our signature to the correct slot
|
||||
// and increment the total number of
|
||||
// signatures.
|
||||
if (ki < len && signatures < m) {
|
||||
if (Array.isArray(input.script[ki]) && !input.script[ki].length) {
|
||||
input.script[ki] = signature;
|
||||
signatures++;
|
||||
}
|
||||
}
|
||||
|
||||
// All signatures added. Finalize.
|
||||
if (signatures >= m) {
|
||||
// Remove empty slots left over.
|
||||
for (i = len - 1; i >= 1; i--) {
|
||||
if (Array.isArray(input.script[i]) && !input.script[i].length) {
|
||||
input.script.splice(i, 1);
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove signatures which are not required.
|
||||
// This should never happen except when dealing
|
||||
// with implementations that potentially handle
|
||||
// signature slots differently.
|
||||
while (signatures > m) {
|
||||
input.script.splice(len - 1, 1);
|
||||
signatures--;
|
||||
len--;
|
||||
}
|
||||
|
||||
// Sanity checks.
|
||||
assert.equal(signatures, m);
|
||||
assert.equal(len - 1, m);
|
||||
}
|
||||
|
||||
return signatures === m;
|
||||
n = keys.length;
|
||||
m = n;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Something is very wrong here. Abort.
|
||||
if (len - 1 > n)
|
||||
return false;
|
||||
|
||||
// Count the number of current signatures.
|
||||
signatures = 0;
|
||||
for (i = 1; i < len; i++) {
|
||||
if (bcoin.script.isSignature(input.script[i]))
|
||||
signatures++;
|
||||
}
|
||||
|
||||
// Signatures are already finalized.
|
||||
if (signatures === m && len - 1 === m)
|
||||
return true;
|
||||
|
||||
// This can happen in a case where another
|
||||
// implementation adds signatures willy-nilly
|
||||
// or by `m`. Add some signature slots for
|
||||
// us to use.
|
||||
while (len - 1 < n) {
|
||||
input.script.splice(len, 0, []);
|
||||
len++;
|
||||
}
|
||||
|
||||
// Find the key index so we can place
|
||||
// the signature in the same index.
|
||||
for (ki = 0; ki < keys.length; ki++) {
|
||||
if (utils.isEqual(pub, keys[ki]))
|
||||
break;
|
||||
}
|
||||
|
||||
// Our public key is not in the prev_out
|
||||
// script. We tried to sign a transaction
|
||||
// that is not redeemable by us.
|
||||
if (ki === keys.length)
|
||||
return false;
|
||||
|
||||
// Offset key index by one to turn it into
|
||||
// "sig index". Accounts for OP_0 byte at
|
||||
// the start.
|
||||
ki++;
|
||||
|
||||
// Add our signature to the correct slot
|
||||
// and increment the total number of
|
||||
// signatures.
|
||||
if (ki < len && signatures < m) {
|
||||
if (bcoin.script.isEmpty(input.script[ki])) {
|
||||
input.script[ki] = signature;
|
||||
signatures++;
|
||||
}
|
||||
}
|
||||
|
||||
// All signatures added. Finalize.
|
||||
if (signatures >= m) {
|
||||
// Remove empty slots left over.
|
||||
for (i = len - 1; i >= 1; i--) {
|
||||
if (bcoin.script.isEmpty(input.script[i])) {
|
||||
input.script.splice(i, 1);
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove signatures which are not required.
|
||||
// This should never happen except when dealing
|
||||
// with implementations that potentially handle
|
||||
// signature slots differently.
|
||||
while (signatures > m) {
|
||||
input.script.splice(len - 1, 1);
|
||||
signatures--;
|
||||
len--;
|
||||
}
|
||||
|
||||
// Sanity checks.
|
||||
assert.equal(signatures, m);
|
||||
assert.equal(len - 1, m);
|
||||
}
|
||||
|
||||
return signatures === m;
|
||||
};
|
||||
|
||||
TX.prototype.finalize = function finalize() {
|
||||
var i, input, s, len, j;
|
||||
for (i = 0; i < this.inputs.length; i++) {
|
||||
input = this.inputs[i];
|
||||
s = input.out.tx.getSubscript(input.out.index);
|
||||
len = s.length;
|
||||
|
||||
if (bcoin.script.isScripthash(s))
|
||||
len--;
|
||||
|
||||
for (j = len - 1; j >= 1; j--) {
|
||||
if (bcoin.script.isEmpty(input.script[j])) {
|
||||
input.script.splice(j, 1);
|
||||
len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) {
|
||||
|
||||
@ -77,21 +77,59 @@ function Wallet(options, passphrase) {
|
||||
this.key = bcoin.ecdsa.genKeyPair();
|
||||
}
|
||||
|
||||
this.type = 'pubkeyhash';
|
||||
// Compatability
|
||||
if (options.multisig) {
|
||||
if (options.multisig.type)
|
||||
options.type = options.multisig.type;
|
||||
if (options.multisig.keys)
|
||||
options.keys = options.multisig.keys;
|
||||
if (options.multisig.m)
|
||||
options.m = options.multisig.m;
|
||||
if (options.multisig.n)
|
||||
options.n = options.multisig.n;
|
||||
}
|
||||
|
||||
this.type = options.type || 'pubkeyhash';
|
||||
this.subtype = options.subtype;
|
||||
this.keys = [];
|
||||
this.m = 1;
|
||||
this.n = 1;
|
||||
this.m = options.m || 1;
|
||||
this.n = options.n || 1;
|
||||
|
||||
this.prefix = 'bt/wallet/' + this.getKeyAddress() + '/';
|
||||
if (this.n > 1) {
|
||||
if (this.type !== 'multisig')
|
||||
this.type = 'scripthash';
|
||||
if (this.type === 'scripthash')
|
||||
this.subtype = 'multisig';
|
||||
}
|
||||
|
||||
this.multisig(options.multisig || {});
|
||||
if (network.prefixes[this.type] == null)
|
||||
throw new Error('Unknown prefix: ' + this.type);
|
||||
|
||||
this.nmax = this.type === 'scripthash'
|
||||
? (this.compressed ? 15 : 7)
|
||||
: 3;
|
||||
|
||||
if (this.m < 1 || this.m > this.n)
|
||||
throw new Error('m ranges between 1 and n');
|
||||
|
||||
if (this.n < 1 || this.n > this.nmax)
|
||||
throw new Error('n ranges between 1 and ' + this.nmax);
|
||||
|
||||
this.addKey(this.getPublicKey());
|
||||
|
||||
(options.keys || []).forEach(function(key) {
|
||||
this.addKey(key);
|
||||
}, this);
|
||||
|
||||
if (this.redeem) {
|
||||
if (!bcoin.script.isEncoded(this.redeem))
|
||||
this.redeem = bcoin.script.encode(this.redeem);
|
||||
this.type = 'scripthash';
|
||||
this.subtype = null;
|
||||
}
|
||||
|
||||
this.prefix = 'bt/wallet/' + this.getKeyAddress() + '/';
|
||||
|
||||
this.tx = new bcoin.txPool(this);
|
||||
|
||||
this._init();
|
||||
@ -132,40 +170,6 @@ Wallet.prototype._init = function init() {
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.multisig = function multisig(options) {
|
||||
var pub = this.getPublicKey();
|
||||
|
||||
options.type = options.type || options.prefix;
|
||||
options.keys = options.keys || options.pubkeys || [];
|
||||
|
||||
this.type = options.type || 'pubkeyhash';
|
||||
this.keys = [];
|
||||
this.m = options.m || 1;
|
||||
this.n = options.n || 1;
|
||||
this.nmax = this.type === 'scripthash'
|
||||
? (this.compressed ? 15 : 7)
|
||||
: 3;
|
||||
|
||||
this.addKey(pub);
|
||||
|
||||
if (network.prefixes[this.type] == null)
|
||||
throw new Error('Unknown prefix: ' + this.type);
|
||||
|
||||
options.keys.forEach(function(key) {
|
||||
this.addKey(key);
|
||||
}, this);
|
||||
|
||||
// Use p2sh multisig by default
|
||||
if (!options.type && (this.keys.length > 1 || this.n > 1))
|
||||
this.type = 'scripthash';
|
||||
|
||||
if (this.m < 1 || this.m > this.n)
|
||||
throw new Error('m ranges between 1 and n');
|
||||
|
||||
if (this.n < 1 || this.n > this.nmax)
|
||||
throw new Error('n ranges between 1 and ' + this.nmax);
|
||||
};
|
||||
|
||||
Wallet.prototype.addKey = function addKey(key) {
|
||||
key = utils.toBuffer(key);
|
||||
|
||||
@ -243,16 +247,39 @@ Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
|
||||
};
|
||||
|
||||
Wallet.prototype.getScript = function getScript() {
|
||||
if (this.type !== 'scripthash')
|
||||
return;
|
||||
|
||||
if (this.redeem)
|
||||
return this.redeem.slice();
|
||||
|
||||
if (this.subtype === 'pubkey')
|
||||
return bcoin.script.encode([this.getPublicKey(), 'checksig']);
|
||||
|
||||
if (this.subtype === 'pubkeyhash') {
|
||||
return bcoin.script.encode([
|
||||
'dup',
|
||||
'hash160',
|
||||
this.getKeyHash(),
|
||||
'eqverify',
|
||||
'checksig'
|
||||
]);
|
||||
}
|
||||
|
||||
return bcoin.script.encode(bcoin.script.redeem(this.keys, this.m, this.n));
|
||||
};
|
||||
|
||||
Wallet.prototype.getScriptHash = function getScriptHash() {
|
||||
if (this.type !== 'scripthash')
|
||||
return;
|
||||
|
||||
return utils.ripesha(this.getScript());
|
||||
};
|
||||
|
||||
Wallet.prototype.getScriptAddress = function getScriptAddress() {
|
||||
if (this.type !== 'scripthash')
|
||||
return;
|
||||
|
||||
return Wallet.hash2addr(this.getScriptHash(), this.type);
|
||||
};
|
||||
|
||||
@ -370,8 +397,10 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
|
||||
if (bcoin.script.isMultisig(s, keys))
|
||||
return true;
|
||||
|
||||
if (bcoin.script.isScripthash(s, scripthash))
|
||||
return true;
|
||||
if (scripthash) {
|
||||
if (bcoin.script.isScripthash(s, scripthash))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, this);
|
||||
@ -407,8 +436,10 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
|
||||
// if (bcoin.script.isMultisigInput(input.script, keys, tx, i))
|
||||
// return true;
|
||||
|
||||
if (bcoin.script.isScripthashInput(input.script, redeem))
|
||||
return true;
|
||||
if (redeem) {
|
||||
if (bcoin.script.isScripthashInput(input.script, redeem))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!input.out.tx)
|
||||
return false;
|
||||
@ -424,8 +455,10 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
|
||||
if (bcoin.script.isMultisig(s, keys))
|
||||
return true;
|
||||
|
||||
if (bcoin.script.isScripthash(s, scripthash))
|
||||
return true;
|
||||
if (scripthash) {
|
||||
if (bcoin.script.isScripthash(s, scripthash))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, this);
|
||||
@ -589,7 +622,7 @@ Wallet.prototype.toAddress = function toAddress() {
|
||||
Wallet.prototype.toJSON = function toJSON(encrypt) {
|
||||
return {
|
||||
v: 2,
|
||||
type: 'wallet',
|
||||
name: 'wallet',
|
||||
network: network.type,
|
||||
encrypted: encrypt ? true : false,
|
||||
label: this.label,
|
||||
@ -599,16 +632,15 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
|
||||
priv: encrypt
|
||||
? encrypt(this.getPrivateKey('base58'))
|
||||
: this.getPrivateKey('base58'),
|
||||
tx: this.tx.toJSON(),
|
||||
xprivkey: this.hd
|
||||
? (encrypt ? encrypt(this.hd.xprivkey) : this.hd.xprivkey)
|
||||
: null,
|
||||
multisig: this.n > 1 ? {
|
||||
type: this.type,
|
||||
keys: this.keys.map(utils.toBase58),
|
||||
m: this.m,
|
||||
n: this.n
|
||||
} : undefined
|
||||
type: this.type,
|
||||
subtype: this.subtype,
|
||||
keys: this.keys.map(utils.toBase58),
|
||||
m: this.m,
|
||||
n: this.n,
|
||||
tx: this.tx.toJSON()
|
||||
};
|
||||
};
|
||||
|
||||
@ -616,7 +648,7 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
|
||||
var priv, pub, xprivkey, multisig, compressed, key, w;
|
||||
|
||||
assert.equal(json.v, 2);
|
||||
assert.equal(json.type, 'wallet');
|
||||
assert.equal(json.name, 'wallet');
|
||||
|
||||
if (json.network)
|
||||
assert.equal(json.network, network.type);
|
||||
@ -647,15 +679,6 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
|
||||
compressed = pub[0] !== 0x04;
|
||||
}
|
||||
|
||||
if (json.multisig) {
|
||||
multisig = {
|
||||
type: json.multisig.type,
|
||||
keys: json.multisig.keys.map(utils.fromBase58),
|
||||
m: json.multisig.m,
|
||||
n: json.multisig.n
|
||||
};
|
||||
}
|
||||
|
||||
if (json.xprivkey) {
|
||||
xprivkey = json.xprivkey;
|
||||
if (json.encrypted)
|
||||
@ -668,7 +691,12 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
|
||||
priv: priv,
|
||||
pub: pub,
|
||||
compressed: compressed,
|
||||
multisig: multisig
|
||||
multisig: multisig,
|
||||
type: json.type,
|
||||
subtype: json.subtype,
|
||||
keys: json.keys.map(utils.fromBase58),
|
||||
m: json.m,
|
||||
n: json.n
|
||||
});
|
||||
|
||||
w.tx.fromJSON(json.tx);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user