Browse Source

adding some signature methods for script interpreting

patch-2
Manuel Araoz 10 years ago
parent
commit
c888c3baa7
  1. 122
      lib/crypto/signature.js
  2. 67
      lib/script.js
  3. 22
      lib/script_interpreter.js
  4. 35
      test/crypto/signature.js
  5. 16
      test/script.js
  6. 1
      test/script_interpreter.js

122
lib/crypto/signature.js

@ -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;

67
lib/script.js

@ -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;

22
lib/script_interpreter.js

@ -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;

35
test/crypto/signature.js

@ -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);
});
});
});

16
test/script.js

@ -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');
});
});
});

1
test/script_interpreter.js

@ -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…
Cancel
Save