adding some signature methods for script interpreting
This commit is contained in:
parent
3de71f8558
commit
c888c3baa7
@ -10,8 +10,7 @@ var Signature = function Signature(r, s) {
|
|||||||
r: r,
|
r: r,
|
||||||
s: s
|
s: s
|
||||||
});
|
});
|
||||||
}
|
} else if (r) {
|
||||||
else if (r) {
|
|
||||||
var obj = r;
|
var obj = r;
|
||||||
this.set(obj);
|
this.set(obj);
|
||||||
}
|
}
|
||||||
@ -137,8 +136,12 @@ Signature.prototype.toCompact = function(i, compressed) {
|
|||||||
if (compressed === false)
|
if (compressed === false)
|
||||||
val = val - 4;
|
val = val - 4;
|
||||||
var b1 = new Buffer([val]);
|
var b1 = new Buffer([val]);
|
||||||
var b2 = this.r.toBuffer({size: 32});
|
var b2 = this.r.toBuffer({
|
||||||
var b3 = this.s.toBuffer({size: 32});
|
size: 32
|
||||||
|
});
|
||||||
|
var b3 = this.s.toBuffer({
|
||||||
|
size: 32
|
||||||
|
});
|
||||||
return Buffer.concat([b1, b2, b3]);
|
return Buffer.concat([b1, b2, b3]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,6 +171,115 @@ Signature.prototype.toString = function() {
|
|||||||
return buf.toString('hex');
|
return buf.toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is translated from bitcoind's IsDERSignature and is used in
|
||||||
|
* the script interpreter. This "DER" format actually includes an extra byte,
|
||||||
|
* the nhashtype, at the end. It is really the tx format, not DER format.
|
||||||
|
*
|
||||||
|
* A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] [hashtype]
|
||||||
|
* Where R and S are not negative (their first byte has its highest bit not set), and not
|
||||||
|
* excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
|
||||||
|
* in which case a single 0 byte is necessary and even required).
|
||||||
|
*
|
||||||
|
* See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
||||||
|
*/
|
||||||
|
Signature.isTxDER = function(buf) {
|
||||||
|
if (buf.length < 9) {
|
||||||
|
// Non-canonical signature: too short
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (buf.length > 73) {
|
||||||
|
// Non-canonical signature: too long
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (buf[0] !== 0x30) {
|
||||||
|
// Non-canonical signature: wrong type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (buf[1] !== buf.length - 3) {
|
||||||
|
// Non-canonical signature: wrong length marker
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var nLenR = buf[3];
|
||||||
|
if (5 + nLenR >= buf.length) {
|
||||||
|
// Non-canonical signature: S length misplaced
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var nLenS = buf[5 + nLenR];
|
||||||
|
if ((nLenR + nLenS + 7) !== buf.length) {
|
||||||
|
// Non-canonical signature: R+S length mismatch
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var R = buf.slice(4);
|
||||||
|
if (buf[4 - 2] !== 0x02) {
|
||||||
|
// Non-canonical signature: R value type mismatch
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nLenR === 0) {
|
||||||
|
// Non-canonical signature: R length is zero
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (R[0] & 0x80) {
|
||||||
|
// Non-canonical signature: R value negative
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nLenR > 1 && (R[0] === 0x00) && !(R[1] & 0x80)) {
|
||||||
|
// Non-canonical signature: R value excessively padded
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var S = buf.slice(6 + nLenR);
|
||||||
|
if (buf[6 + nLenR - 2] !== 0x02) {
|
||||||
|
// Non-canonical signature: S value type mismatch
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nLenS === 0) {
|
||||||
|
// Non-canonical signature: S length is zero
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (S[0] & 0x80) {
|
||||||
|
// Non-canonical signature: S value negative
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nLenS > 1 && (S[0] === 0x00) && !(S[1] & 0x80)) {
|
||||||
|
// Non-canonical signature: S value excessively padded
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares to bitcoind's IsLowDERSignature
|
||||||
|
* See also ECDSA signature algorithm which enforces this.
|
||||||
|
* See also BIP 62, "low S values in signatures"
|
||||||
|
*/
|
||||||
|
Signature.prototype.hasLowS = function() {
|
||||||
|
if (this.s.lt(1) ||
|
||||||
|
this.s.gt(BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0'))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if the nhashtype is exactly equal to one of the standard options or combinations thereof.
|
||||||
|
* Translated from bitcoind's IsDefinedHashtypeSignature
|
||||||
|
*/
|
||||||
|
Signature.prototype.hasDefinedHashtype = function() {
|
||||||
|
if (this.nhashtype < Signature.SIGHASH_ALL || this.nhashtype > Signature.SIGHASH_SINGLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Signature.prototype.toTxFormat = function() {
|
||||||
|
var derbuf = this.toDER();
|
||||||
|
var buf = new Buffer(1);
|
||||||
|
buf.writeUInt8(this.nhashtype, 0);
|
||||||
|
return Buffer.concat([derbuf, buf]);
|
||||||
|
};
|
||||||
|
|
||||||
Signature.SIGHASH_ALL = 0x01;
|
Signature.SIGHASH_ALL = 0x01;
|
||||||
Signature.SIGHASH_NONE = 0x02;
|
Signature.SIGHASH_NONE = 0x02;
|
||||||
Signature.SIGHASH_SINGLE = 0x03;
|
Signature.SIGHASH_SINGLE = 0x03;
|
||||||
|
|||||||
@ -264,11 +264,11 @@ Script.prototype.isPublicKeyIn = function() {
|
|||||||
* @returns true if this is a p2sh output script
|
* @returns true if this is a p2sh output script
|
||||||
*/
|
*/
|
||||||
Script.prototype.isScriptHashOut = function() {
|
Script.prototype.isScriptHashOut = function() {
|
||||||
return this.chunks.length === 3 &&
|
var buf = this.toBuffer();
|
||||||
this.chunks[0].opcodenum === Opcode.OP_HASH160 &&
|
return (buf.length === 23 &&
|
||||||
this.chunks[1].buf &&
|
buf[0] === Opcode.OP_HASH160 &&
|
||||||
this.chunks[1].buf.length === 20 &&
|
buf[1] === 0x14 &&
|
||||||
this.chunks[2].opcodenum === Opcode.OP_EQUAL;
|
buf[22] === Opcode.OP_EQUAL);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -669,4 +669,61 @@ Script.fromAddress = function(address) {
|
|||||||
throw new errors.Script.UnrecognizedAddress(address);
|
throw new errors.Script.UnrecognizedAddress(address);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analagous to bitcoind's FindAndDelete. Find and delete equivalent chunks,
|
||||||
|
* typically used with push data chunks. Note that this will find and delete
|
||||||
|
* not just the same data, but the same data with the same push data op as
|
||||||
|
* produced by default. i.e., if a pushdata in a tx does not use the minimal
|
||||||
|
* pushdata op, then when you try to remove the data it is pushing, it will not
|
||||||
|
* be removed, because they do not use the same pushdata op.
|
||||||
|
*/
|
||||||
|
Script.prototype.findAndDelete = function(script) {
|
||||||
|
var buf = script.toBuffer();
|
||||||
|
var hex = buf.toString('hex');
|
||||||
|
for (var i = 0; i < this.chunks.length; i++) {
|
||||||
|
var script2 = Script({
|
||||||
|
chunks: [this.chunks[i]]
|
||||||
|
});
|
||||||
|
var buf2 = script2.toBuffer();
|
||||||
|
var hex2 = buf2.toString('hex');
|
||||||
|
if (hex === hex2) {
|
||||||
|
this.chunks.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if the chunk {i} is the smallest way to push that particular data.
|
||||||
|
* Comes from bitcoind's script interpreter CheckMinimalPush function
|
||||||
|
*/
|
||||||
|
Script.prototype.checkMinimalPush = function(i) {
|
||||||
|
var chunk = this.chunks[i];
|
||||||
|
var buf = chunk.buf;
|
||||||
|
var opcodenum = chunk.opcodenum;
|
||||||
|
if (!buf) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (buf.length === 0) {
|
||||||
|
// Could have used OP_0.
|
||||||
|
return opcodenum === Opcode.OP_0;
|
||||||
|
} else if (buf.length === 1 && buf[0] >= 1 && buf[0] <= 16) {
|
||||||
|
// Could have used OP_1 .. OP_16.
|
||||||
|
return opcodenum === Opcode.OP_1 + (buf[0] - 1);
|
||||||
|
} else if (buf.length === 1 && buf[0] === 0x81) {
|
||||||
|
// Could have used OP_1NEGATE
|
||||||
|
return opcodenum === Opcode.OP_1NEGATE;
|
||||||
|
} else if (buf.length <= 75) {
|
||||||
|
// Could have used a direct push (opcode indicating number of bytes pushed + those bytes).
|
||||||
|
return opcodenum === buf.length;
|
||||||
|
} else if (buf.length <= 255) {
|
||||||
|
// Could have used OP_PUSHDATA.
|
||||||
|
return opcodenum === Opcode.OP_PUSHDATA1;
|
||||||
|
} else if (buf.length <= 65535) {
|
||||||
|
// Could have used OP_PUSHDATA2.
|
||||||
|
return opcodenum === Opcode.OP_PUSHDATA2;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Script;
|
module.exports = Script;
|
||||||
|
|||||||
@ -210,7 +210,7 @@ ScriptInterpreter.prototype.step = function() {
|
|||||||
this.pc++;
|
this.pc++;
|
||||||
var opcodenum = chunk.opcodenum;
|
var opcodenum = chunk.opcodenum;
|
||||||
if (_.isUndefined(opcodenum)) {
|
if (_.isUndefined(opcodenum)) {
|
||||||
this.errstr = 'SCRIPT_ERR_BAD_OPCODE';
|
this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (chunk.buf && chunk.buf.length > ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE) {
|
if (chunk.buf && chunk.buf.length > ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE) {
|
||||||
@ -257,7 +257,6 @@ ScriptInterpreter.prototype.step = function() {
|
|||||||
this.stack.push(chunk.buf);
|
this.stack.push(chunk.buf);
|
||||||
}
|
}
|
||||||
} else if (fExec || (Opcode.OP_IF <= opcodenum && opcodenum <= Opcode.OP_ENDIF)) {
|
} else if (fExec || (Opcode.OP_IF <= opcodenum && opcodenum <= Opcode.OP_ENDIF)) {
|
||||||
console.log('STEP!' + JSON.stringify(chunk));
|
|
||||||
switch (opcodenum) {
|
switch (opcodenum) {
|
||||||
// Push value
|
// Push value
|
||||||
case Opcode.OP_1NEGATE:
|
case Opcode.OP_1NEGATE:
|
||||||
@ -902,9 +901,9 @@ ScriptInterpreter.prototype.step = function() {
|
|||||||
// stack.push_back(fSuccess ? vchTrue : vchFalse);
|
// stack.push_back(fSuccess ? vchTrue : vchFalse);
|
||||||
this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false);
|
this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false);
|
||||||
if (opcodenum === Opcode.OP_CHECKSIGVERIFY) {
|
if (opcodenum === Opcode.OP_CHECKSIGVERIFY) {
|
||||||
if (fSuccess)
|
if (fSuccess) {
|
||||||
this.stack.pop();
|
this.stack.pop();
|
||||||
else {
|
} else {
|
||||||
this.errstr = 'SCRIPT_ERR_CHECKSIGVERIFY';
|
this.errstr = 'SCRIPT_ERR_CHECKSIGVERIFY';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -962,7 +961,7 @@ ScriptInterpreter.prototype.step = function() {
|
|||||||
// Drop the signatures, since there's no way for a signature to sign itself
|
// Drop the signatures, since there's no way for a signature to sign itself
|
||||||
for (var k = 0; k < nSigsCount; k++) {
|
for (var k = 0; k < nSigsCount; k++) {
|
||||||
var bufSig = this.stack[this.stack.length - isig - k];
|
var bufSig = this.stack[this.stack.length - isig - k];
|
||||||
subscript.findAndDelete(Script().writeBuffer(bufSig));
|
subscript.findAndDelete(Script().add(bufSig));
|
||||||
}
|
}
|
||||||
|
|
||||||
var fSuccess = true;
|
var fSuccess = true;
|
||||||
@ -1064,8 +1063,10 @@ ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.evaluate())
|
// evaluate scriptSig
|
||||||
|
if (!this.evaluate()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH)
|
if (flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH)
|
||||||
var stackCopy = this.stack.slice();
|
var stackCopy = this.stack.slice();
|
||||||
@ -1080,11 +1081,11 @@ ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin,
|
|||||||
flags: flags
|
flags: flags
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// evaluate scriptPubkey
|
||||||
if (!this.evaluate())
|
if (!this.evaluate())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (this.stack.length === 0) {
|
if (this.stack.length === 0) {
|
||||||
console.log('stack 0');
|
|
||||||
this.errstr = 'SCRIPT_ERR_EVAL_FALSE';
|
this.errstr = 'SCRIPT_ERR_EVAL_FALSE';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1109,19 +1110,20 @@ ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin,
|
|||||||
if (stackCopy.length === 0)
|
if (stackCopy.length === 0)
|
||||||
throw new Error('internal error - stack copy empty');
|
throw new Error('internal error - stack copy empty');
|
||||||
|
|
||||||
var pubkeySerialized = stackCopy[stackCopy.length - 1];
|
var redeemScriptSerialized = stackCopy[stackCopy.length - 1];
|
||||||
var scriptPubkey2 = Script.fromBuffer(pubkeySerialized);
|
var redeemScript = Script.fromBuffer(redeemScriptSerialized);
|
||||||
stackCopy.pop();
|
stackCopy.pop();
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
this.set({
|
this.set({
|
||||||
script: scriptPubkey2,
|
script: redeemScript,
|
||||||
stack: stackCopy,
|
stack: stackCopy,
|
||||||
tx: tx,
|
tx: tx,
|
||||||
nin: nin,
|
nin: nin,
|
||||||
flags: flags
|
flags: flags
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// evaluate redeemScript
|
||||||
if (!this.evaluate())
|
if (!this.evaluate())
|
||||||
// serror is set
|
// serror is set
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -151,7 +151,6 @@ describe('Signature', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('#toString', function() {
|
describe('#toString', function() {
|
||||||
|
|
||||||
it('should convert this signature in to hex DER', function() {
|
it('should convert this signature in to hex DER', function() {
|
||||||
var r = BN('63173831029936981022572627018246571655303050627048489594159321588908385378810');
|
var r = BN('63173831029936981022572627018246571655303050627048489594159321588908385378810');
|
||||||
var s = BN('4331694221846364448463828256391194279133231453999942381442030409253074198130');
|
var s = BN('4331694221846364448463828256391194279133231453999942381442030409253074198130');
|
||||||
@ -162,7 +161,41 @@ describe('Signature', function() {
|
|||||||
var hex = sig.toString();
|
var hex = sig.toString();
|
||||||
hex.should.equal('30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72');
|
hex.should.equal('30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('@isTxDER', function() {
|
||||||
|
it('should know this is a DER signature', function() {
|
||||||
|
var sighex = '3042021e17cfe77536c3fb0526bd1a72d7a8e0973f463add210be14063c8a9c37632022061bfa677f825ded82ba0863fb0c46ca1388dd3e647f6a93c038168b59d131a5101';
|
||||||
|
var sigbuf = new Buffer(sighex, 'hex');
|
||||||
|
Signature.isTxDER(sigbuf).should.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should know this is not a DER signature', function() {
|
||||||
|
//for more extensive tests, see the script interpreter
|
||||||
|
var sighex = '3042021e17cfe77536c3fb0526bd1a72d7a8e0973f463add210be14063c8a9c37632022061bfa677f825ded82ba0863fb0c46ca1388dd3e647f6a93c038168b59d131a5101';
|
||||||
|
var sigbuf = new Buffer(sighex, 'hex');
|
||||||
|
sigbuf[0] = 0x31;
|
||||||
|
Signature.isTxDER(sigbuf).should.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#hasLowS', function() {
|
||||||
|
it('should detect high and low S', function() {
|
||||||
|
var r = BN('63173831029936981022572627018246571655303050627048489594159321588908385378810');
|
||||||
|
var s = BN('4331694221846364448463828256391194279133231453999942381442030409253074198130');
|
||||||
|
var s2 = BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B2000');
|
||||||
|
var sig = new Signature({
|
||||||
|
r: r,
|
||||||
|
s: s
|
||||||
|
});
|
||||||
|
var sig2 = new Signature({
|
||||||
|
r: r,
|
||||||
|
s: s2
|
||||||
|
});
|
||||||
|
sig2.hasLowS().should.equal(true);
|
||||||
|
sig.hasLowS().should.equal(false);
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -524,4 +524,20 @@ describe('Script', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('#findAndDelete', function() {
|
||||||
|
it('should find and delete this buffer', function() {
|
||||||
|
Script('OP_RETURN 2 0xf0f0')
|
||||||
|
.findAndDelete(Script('2 0xf0f0'))
|
||||||
|
.toString()
|
||||||
|
.should.equal('OP_RETURN');
|
||||||
|
});
|
||||||
|
it('should do nothing', function() {
|
||||||
|
Script('OP_RETURN 2 0xf0f0')
|
||||||
|
.findAndDelete(Script('2 0xffff'))
|
||||||
|
.toString()
|
||||||
|
.should.equal('OP_RETURN 2 0xf0f0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -243,6 +243,7 @@ describe('ScriptInterpreter', function() {
|
|||||||
var spendtx = Transaction();
|
var spendtx = Transaction();
|
||||||
|
|
||||||
var interp = ScriptInterpreter();
|
var interp = ScriptInterpreter();
|
||||||
|
console.log(scriptSig.toString() + ' ' + scriptPubkey.toString());
|
||||||
var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags);
|
var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags);
|
||||||
console.log(interp.errstr);
|
console.log(interp.errstr);
|
||||||
verified.should.equal(true);
|
verified.should.equal(true);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user