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,
|
||||
s: s
|
||||
});
|
||||
}
|
||||
else if (r) {
|
||||
} else if (r) {
|
||||
var obj = r;
|
||||
this.set(obj);
|
||||
}
|
||||
@ -132,13 +131,17 @@ Signature.prototype.toCompact = function(i, compressed) {
|
||||
|
||||
if (!(i === 0 || i === 1 || i === 2 || i === 3))
|
||||
throw new Error('i must be equal to 0, 1, 2, or 3');
|
||||
|
||||
|
||||
var val = i + 27 + 4;
|
||||
if (compressed === false)
|
||||
val = val - 4;
|
||||
var b1 = new Buffer([val]);
|
||||
var b2 = this.r.toBuffer({size: 32});
|
||||
var b3 = this.s.toBuffer({size: 32});
|
||||
var b2 = this.r.toBuffer({
|
||||
size: 32
|
||||
});
|
||||
var b3 = this.s.toBuffer({
|
||||
size: 32
|
||||
});
|
||||
return Buffer.concat([b1, b2, b3]);
|
||||
};
|
||||
|
||||
@ -168,6 +171,115 @@ Signature.prototype.toString = function() {
|
||||
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_NONE = 0x02;
|
||||
Signature.SIGHASH_SINGLE = 0x03;
|
||||
|
||||
@ -264,11 +264,11 @@ Script.prototype.isPublicKeyIn = function() {
|
||||
* @returns true if this is a p2sh output script
|
||||
*/
|
||||
Script.prototype.isScriptHashOut = function() {
|
||||
return this.chunks.length === 3 &&
|
||||
this.chunks[0].opcodenum === Opcode.OP_HASH160 &&
|
||||
this.chunks[1].buf &&
|
||||
this.chunks[1].buf.length === 20 &&
|
||||
this.chunks[2].opcodenum === Opcode.OP_EQUAL;
|
||||
var buf = this.toBuffer();
|
||||
return (buf.length === 23 &&
|
||||
buf[0] === Opcode.OP_HASH160 &&
|
||||
buf[1] === 0x14 &&
|
||||
buf[22] === Opcode.OP_EQUAL);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -669,4 +669,61 @@ Script.fromAddress = function(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;
|
||||
|
||||
@ -210,7 +210,7 @@ ScriptInterpreter.prototype.step = function() {
|
||||
this.pc++;
|
||||
var opcodenum = chunk.opcodenum;
|
||||
if (_.isUndefined(opcodenum)) {
|
||||
this.errstr = 'SCRIPT_ERR_BAD_OPCODE';
|
||||
this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE';
|
||||
return false;
|
||||
}
|
||||
if (chunk.buf && chunk.buf.length > ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE) {
|
||||
@ -257,7 +257,6 @@ ScriptInterpreter.prototype.step = function() {
|
||||
this.stack.push(chunk.buf);
|
||||
}
|
||||
} else if (fExec || (Opcode.OP_IF <= opcodenum && opcodenum <= Opcode.OP_ENDIF)) {
|
||||
console.log('STEP!' + JSON.stringify(chunk));
|
||||
switch (opcodenum) {
|
||||
// Push value
|
||||
case Opcode.OP_1NEGATE:
|
||||
@ -902,9 +901,9 @@ ScriptInterpreter.prototype.step = function() {
|
||||
// stack.push_back(fSuccess ? vchTrue : vchFalse);
|
||||
this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false);
|
||||
if (opcodenum === Opcode.OP_CHECKSIGVERIFY) {
|
||||
if (fSuccess)
|
||||
if (fSuccess) {
|
||||
this.stack.pop();
|
||||
else {
|
||||
} else {
|
||||
this.errstr = 'SCRIPT_ERR_CHECKSIGVERIFY';
|
||||
return false;
|
||||
}
|
||||
@ -962,7 +961,7 @@ ScriptInterpreter.prototype.step = function() {
|
||||
// Drop the signatures, since there's no way for a signature to sign itself
|
||||
for (var k = 0; k < nSigsCount; k++) {
|
||||
var bufSig = this.stack[this.stack.length - isig - k];
|
||||
subscript.findAndDelete(Script().writeBuffer(bufSig));
|
||||
subscript.findAndDelete(Script().add(bufSig));
|
||||
}
|
||||
|
||||
var fSuccess = true;
|
||||
@ -1064,8 +1063,10 @@ ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.evaluate())
|
||||
// evaluate scriptSig
|
||||
if (!this.evaluate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH)
|
||||
var stackCopy = this.stack.slice();
|
||||
@ -1080,11 +1081,11 @@ ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin,
|
||||
flags: flags
|
||||
});
|
||||
|
||||
// evaluate scriptPubkey
|
||||
if (!this.evaluate())
|
||||
return false;
|
||||
|
||||
if (this.stack.length === 0) {
|
||||
console.log('stack 0');
|
||||
this.errstr = 'SCRIPT_ERR_EVAL_FALSE';
|
||||
return false;
|
||||
}
|
||||
@ -1109,19 +1110,20 @@ ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin,
|
||||
if (stackCopy.length === 0)
|
||||
throw new Error('internal error - stack copy empty');
|
||||
|
||||
var pubkeySerialized = stackCopy[stackCopy.length - 1];
|
||||
var scriptPubkey2 = Script.fromBuffer(pubkeySerialized);
|
||||
var redeemScriptSerialized = stackCopy[stackCopy.length - 1];
|
||||
var redeemScript = Script.fromBuffer(redeemScriptSerialized);
|
||||
stackCopy.pop();
|
||||
|
||||
this.initialize();
|
||||
this.set({
|
||||
script: scriptPubkey2,
|
||||
script: redeemScript,
|
||||
stack: stackCopy,
|
||||
tx: tx,
|
||||
nin: nin,
|
||||
flags: flags
|
||||
});
|
||||
|
||||
// evaluate redeemScript
|
||||
if (!this.evaluate())
|
||||
// serror is set
|
||||
return false;
|
||||
|
||||
@ -151,7 +151,6 @@ describe('Signature', function() {
|
||||
});
|
||||
|
||||
describe('#toString', function() {
|
||||
|
||||
it('should convert this signature in to hex DER', function() {
|
||||
var r = BN('63173831029936981022572627018246571655303050627048489594159321588908385378810');
|
||||
var s = BN('4331694221846364448463828256391194279133231453999942381442030409253074198130');
|
||||
@ -162,7 +161,41 @@ describe('Signature', function() {
|
||||
var hex = sig.toString();
|
||||
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 interp = ScriptInterpreter();
|
||||
console.log(scriptSig.toString() + ' ' + scriptPubkey.toString());
|
||||
var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags);
|
||||
console.log(interp.errstr);
|
||||
verified.should.equal(true);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user