some Transaction tests fixed (canonical signatures)
This commit is contained in:
parent
7869308784
commit
b227341c12
|
@ -21,7 +21,8 @@ for (var i in Opcode.map) {
|
||||||
var intToBufferSM = Util.intToBufferSM
|
var intToBufferSM = Util.intToBufferSM
|
||||||
var bufferSMToInt = Util.bufferSMToInt;
|
var bufferSMToInt = Util.bufferSMToInt;
|
||||||
|
|
||||||
function ScriptInterpreter() {
|
function ScriptInterpreter(opts) {
|
||||||
|
this.opts = opts || {};
|
||||||
this.stack = [];
|
this.stack = [];
|
||||||
this.disableUnsafeOpcodes = true;
|
this.disableUnsafeOpcodes = true;
|
||||||
};
|
};
|
||||||
|
@ -98,8 +99,7 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
|
||||||
|
|
||||||
if (exec && Buffer.isBuffer(opcode)) {
|
if (exec && Buffer.isBuffer(opcode)) {
|
||||||
this.stack.push(opcode);
|
this.stack.push(opcode);
|
||||||
}
|
} else if (exec || (OP_IF <= opcode && opcode <= OP_ENDIF))
|
||||||
else if (exec || (OP_IF <= opcode && opcode <= OP_ENDIF))
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case OP_0:
|
case OP_0:
|
||||||
this.stack.push(new Buffer([]));
|
this.stack.push(new Buffer([]));
|
||||||
|
@ -411,10 +411,13 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
|
||||||
this.stackPop();
|
this.stackPop();
|
||||||
this.stackPop();
|
this.stackPop();
|
||||||
this.stack.push(new Buffer([value ? 1 : 0]));
|
this.stack.push(new Buffer([value ? 1 : 0]));
|
||||||
|
console.log(script.toHumanReadable());
|
||||||
if (opcode === OP_EQUALVERIFY) {
|
if (opcode === OP_EQUALVERIFY) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this.stackPop();
|
this.stackPop();
|
||||||
} else {
|
} else {
|
||||||
|
console.log(v1);
|
||||||
|
console.log(v2);
|
||||||
throw new Error("OP_EQUALVERIFY negative");
|
throw new Error("OP_EQUALVERIFY negative");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -621,7 +624,7 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
|
||||||
scriptCode.findAndDelete(sig);
|
scriptCode.findAndDelete(sig);
|
||||||
|
|
||||||
//
|
//
|
||||||
isCanonicalSignature(new Buffer(sig));
|
this.isCanonicalSignature(new Buffer(sig));
|
||||||
|
|
||||||
// Verify signature
|
// Verify signature
|
||||||
checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) {
|
checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) {
|
||||||
|
@ -695,8 +698,9 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
|
||||||
var scriptCode = Script.fromChunks(scriptChunks);
|
var scriptCode = Script.fromChunks(scriptChunks);
|
||||||
|
|
||||||
// Drop the signatures, since a signature can't sign itself
|
// Drop the signatures, since a signature can't sign itself
|
||||||
|
var that = this;
|
||||||
sigs.forEach(function(sig) {
|
sigs.forEach(function(sig) {
|
||||||
isCanonicalSignature(new Buffer(sig));
|
that.isCanonicalSignature(new Buffer(sig));
|
||||||
scriptCode.findAndDelete(sig);
|
scriptCode.findAndDelete(sig);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -811,7 +815,7 @@ ScriptInterpreter.prototype.stackTop = function stackTop(offset) {
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptInterpreter.prototype.stackBack = function stackBack() {
|
ScriptInterpreter.prototype.stackBack = function stackBack() {
|
||||||
return this.stack[this.stack.length -1];
|
return this.stack[this.stack.length - 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -882,6 +886,7 @@ ScriptInterpreter.prototype.getResult = function getResult() {
|
||||||
return castBool(this.stack[this.stack.length - 1]);
|
return castBool(this.stack[this.stack.length - 1]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Use ScriptInterpreter.verifyFull instead
|
||||||
ScriptInterpreter.verify =
|
ScriptInterpreter.verify =
|
||||||
function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback) {
|
function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback) {
|
||||||
if ("function" !== typeof callback) {
|
if ("function" !== typeof callback) {
|
||||||
|
@ -912,8 +917,8 @@ ScriptInterpreter.verify =
|
||||||
return si;
|
return si;
|
||||||
};
|
};
|
||||||
|
|
||||||
function verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
|
ScriptInterpreter.prototype.verifyStep4 = function(scriptSig, scriptPubKey,
|
||||||
hashType, opts, callback, si, siCopy) {
|
txTo, nIn, hashType, callback, siCopy) {
|
||||||
if (siCopy.stack.length == 0) {
|
if (siCopy.stack.length == 0) {
|
||||||
callback(null, false);
|
callback(null, false);
|
||||||
return;
|
return;
|
||||||
|
@ -922,19 +927,19 @@ function verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
|
||||||
callback(null, castBool(siCopy.stackBack()));
|
callback(null, castBool(siCopy.stackBack()));
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
|
ScriptInterpreter.prototype.verifyStep3 = function(scriptSig,
|
||||||
hashType, opts, callback, si, siCopy) {
|
scriptPubKey, txTo, nIn, hashType, callback, siCopy) {
|
||||||
if (si.stack.length == 0) {
|
if (this.stack.length == 0) {
|
||||||
callback(null, false);
|
callback(null, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (castBool(si.stackBack()) == false) {
|
if (castBool(this.stackBack()) == false) {
|
||||||
callback(null, false);
|
callback(null, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not P2SH, we're done
|
// if not P2SH, we're done
|
||||||
if (!opts.verifyP2SH || !scriptPubKey.isP2SH()) {
|
if (!this.opts.verifyP2SH || !scriptPubKey.isP2SH()) {
|
||||||
callback(null, true);
|
callback(null, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -949,46 +954,48 @@ function verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
|
||||||
|
|
||||||
var subscript = new Script(siCopy.stackPop());
|
var subscript = new Script(siCopy.stackPop());
|
||||||
|
|
||||||
ok = true;
|
var that = this;
|
||||||
siCopy.eval(subscript, txTo, nIn, hashType, function(err) {
|
siCopy.eval(subscript, txTo, nIn, hashType, function(err) {
|
||||||
if (err)
|
if (err) callback(err);
|
||||||
callback(err);
|
else that.verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
|
||||||
else
|
hashType, callback, siCopy);
|
||||||
verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
|
|
||||||
hashType, opts, callback, si, siCopy);
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
|
ScriptInterpreter.prototype.verifyStep2 = function(scriptSig, scriptPubKey,
|
||||||
hashType, opts, callback, si, siCopy) {
|
txTo, nIn, hashType, callback, siCopy) {
|
||||||
if (opts.verifyP2SH) {
|
if (this.opts.verifyP2SH) {
|
||||||
si.stack.forEach(function(item) {
|
this.stack.forEach(function(item) {
|
||||||
siCopy.stack.push(item);
|
siCopy.stack.push(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
si.eval(scriptPubKey, txTo, nIn, hashType, function(err) {
|
var that = this;
|
||||||
if (err)
|
this.eval(scriptPubKey, txTo, nIn, hashType, function(err) {
|
||||||
callback(err);
|
if (err) callback(err);
|
||||||
else
|
else that.verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
|
||||||
verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
|
hashType, callback, siCopy);
|
||||||
hashType, opts, callback, si, siCopy);
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
ScriptInterpreter.verifyFull =
|
ScriptInterpreter.verifyFull =
|
||||||
function verifyFull(scriptSig, scriptPubKey, txTo, nIn, hashType,
|
function verifyFull(scriptSig, scriptPubKey, txTo, nIn, hashType,
|
||||||
opts, callback) {
|
opts, callback) {
|
||||||
var si = new ScriptInterpreter();
|
var si = new ScriptInterpreter(opts);
|
||||||
var siCopy = new ScriptInterpreter();
|
si.verifyFull(scriptSig, scriptPubKey,
|
||||||
|
txTo, nIn, hashType, callback);
|
||||||
|
};
|
||||||
|
|
||||||
si.eval(scriptSig, txTo, nIn, hashType, function(err) {
|
ScriptInterpreter.prototype.verifyFull = function(scriptSig, scriptPubKey,
|
||||||
if (err)
|
txTo, nIn, hashType, callback) {
|
||||||
callback(err);
|
var siCopy = new ScriptInterpreter(this.opts);
|
||||||
else
|
var that = this;
|
||||||
verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
|
this.eval(scriptSig, txTo, nIn, hashType, function(err) {
|
||||||
hashType, opts, callback, si, siCopy);
|
if (err) callback(err);
|
||||||
|
else that.verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
|
||||||
|
hashType, callback, siCopy);
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var checkSig = ScriptInterpreter.checkSig =
|
var checkSig = ScriptInterpreter.checkSig =
|
||||||
|
@ -1019,7 +1026,7 @@ var checkSig = ScriptInterpreter.checkSig =
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var isCanonicalSignature = ScriptInterpreter.isCanonicalSignature = function(sig, opts) {
|
ScriptInterpreter.prototype.isCanonicalSignature = function(sig) {
|
||||||
// See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
// See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
|
||||||
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
|
// 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
|
// Where R and S are not negative (their first byte has its highest bit not set), and not
|
||||||
|
@ -1029,33 +1036,35 @@ var isCanonicalSignature = ScriptInterpreter.isCanonicalSignature = function(sig
|
||||||
if (!Buffer.isBuffer(sig))
|
if (!Buffer.isBuffer(sig))
|
||||||
throw new Error("arg should be a Buffer");
|
throw new Error("arg should be a Buffer");
|
||||||
|
|
||||||
opts = opts || {};
|
// TODO: change to opts.verifyStrictEnc to make the default
|
||||||
|
// behavior not verify, as in bitcoin core
|
||||||
|
if (this.opts.dontVerifyStrictEnc) return true;
|
||||||
|
|
||||||
var l = sig.length;
|
var l = sig.length;
|
||||||
if (l < 9) throw new Error("Non-canonical signature: too short");
|
if (l < 9) throw new Error("Non-canonical signature: too short");
|
||||||
if (l > 73) throw new Error("Non-canonical signature: too long");
|
if (l > 73) throw new Error("Non-canonical signature: too long");
|
||||||
|
|
||||||
var nHashType = sig[l-1] & (~(SIGHASH_ANYONECANPAY));
|
var nHashType = sig[l - 1] & (~(SIGHASH_ANYONECANPAY));
|
||||||
if (nHashType < SIGHASH_ALL || nHashType > SIGHASH_SINGLE)
|
if (nHashType < SIGHASH_ALL || nHashType > SIGHASH_SINGLE)
|
||||||
throw new Error("Non-canonical signature: unknown hashtype byte");
|
throw new Error("Non-canonical signature: unknown hashtype byte");
|
||||||
|
|
||||||
if (sig[0] !== 0x30)
|
if (sig[0] !== 0x30)
|
||||||
throw new Error("Non-canonical signature: wrong type");
|
throw new Error("Non-canonical signature: wrong type");
|
||||||
if (sig[1] !== l-3)
|
if (sig[1] !== l - 3)
|
||||||
throw new Error("Non-canonical signature: wrong length marker");
|
throw new Error("Non-canonical signature: wrong length marker");
|
||||||
|
|
||||||
var nLenR = sig[3];
|
var nLenR = sig[3];
|
||||||
if (5 + nLenR >= l)
|
if (5 + nLenR >= l)
|
||||||
throw new Error("Non-canonical signature: S length misplaced");
|
throw new Error("Non-canonical signature: S length misplaced");
|
||||||
|
|
||||||
var nLenS = sig[5+nLenR];
|
var nLenS = sig[5 + nLenR];
|
||||||
if ( (nLenR+nLenS+7) !== l)
|
if ((nLenR + nLenS + 7) !== l)
|
||||||
throw new Error("Non-canonical signature: R+S length mismatch");
|
throw new Error("Non-canonical signature: R+S length mismatch");
|
||||||
|
|
||||||
var rPos = 4;
|
var rPos = 4;
|
||||||
var R = new Buffer(nLenR);
|
var R = new Buffer(nLenR);
|
||||||
sig.copy(R, 0, rPos, rPos+ nLenR);
|
sig.copy(R, 0, rPos, rPos + nLenR);
|
||||||
if (sig[rPos-2] !== 0x02)
|
if (sig[rPos - 2] !== 0x02)
|
||||||
throw new Error("Non-canonical signature: R value type mismatch");
|
throw new Error("Non-canonical signature: R value type mismatch");
|
||||||
if (nLenR == 0)
|
if (nLenR == 0)
|
||||||
throw new Error("Non-canonical signature: R length is zero");
|
throw new Error("Non-canonical signature: R length is zero");
|
||||||
|
@ -1066,8 +1075,8 @@ var isCanonicalSignature = ScriptInterpreter.isCanonicalSignature = function(sig
|
||||||
|
|
||||||
var sPos = 6 + nLenR;
|
var sPos = 6 + nLenR;
|
||||||
var S = new Buffer(nLenS);
|
var S = new Buffer(nLenS);
|
||||||
sig.copy(S, 0, sPos, sPos+ nLenS);
|
sig.copy(S, 0, sPos, sPos + nLenS);
|
||||||
if (sig[sPos-2] != 0x02)
|
if (sig[sPos - 2] != 0x02)
|
||||||
throw new Error("Non-canonical signature: S value type mismatch");
|
throw new Error("Non-canonical signature: S value type mismatch");
|
||||||
if (nLenS == 0)
|
if (nLenS == 0)
|
||||||
throw new Error("Non-canonical signature: S length is zero");
|
throw new Error("Non-canonical signature: S length is zero");
|
||||||
|
@ -1076,8 +1085,8 @@ var isCanonicalSignature = ScriptInterpreter.isCanonicalSignature = function(sig
|
||||||
if (nLenS > 1 && (S[0] == 0x00) && !(S[1] & 0x80))
|
if (nLenS > 1 && (S[0] == 0x00) && !(S[1] & 0x80))
|
||||||
throw new Error("Non-canonical signature: S value excessively padded");
|
throw new Error("Non-canonical signature: S value excessively padded");
|
||||||
|
|
||||||
if (opts.verifyEvenS) {
|
if (this.opts.verifyEvenS) {
|
||||||
if (S[nLenS-1] & 1)
|
if (S[nLenS - 1] & 1)
|
||||||
throw new Error("Non-canonical signature: S value odd");
|
throw new Error("Non-canonical signature: S value odd");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -259,10 +259,10 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return txout;
|
return txout;
|
||||||
};
|
}
|
||||||
|
|
||||||
Step(
|
Step(
|
||||||
function verifyInputs() {
|
function verifyInputs(opts) {
|
||||||
var group = this.group();
|
var group = this.group();
|
||||||
|
|
||||||
if (self.isCoinBase()) {
|
if (self.isCoinBase()) {
|
||||||
|
@ -278,7 +278,7 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
|
||||||
|
|
||||||
outpoints.push(txin.o);
|
outpoints.push(txin.o);
|
||||||
|
|
||||||
self.verifyInput(n, txout.getScript(), group());
|
self.verifyInput(n, txout.getScript(), opts, group());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -351,11 +351,14 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, callback) {
|
Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) {
|
||||||
return ScriptInterpreter.verify(this.ins[n].getScript(),
|
var valid = ScriptInterpreter.verifyFull(
|
||||||
|
this.ins[n].getScript(),
|
||||||
scriptPubKey,
|
scriptPubKey,
|
||||||
this, n, 0,
|
this, n, 0,
|
||||||
|
opts,
|
||||||
callback);
|
callback);
|
||||||
|
return valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -81,9 +81,10 @@ describe('ScriptInterpreter', function() {
|
||||||
isHex = 1;
|
isHex = 1;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
if (isHex)
|
// ignore non-hex strings
|
||||||
ScriptInterpreter.isCanonicalSignature.bind(sig).should.
|
if (isHex) {
|
||||||
throw ();
|
ScriptInterpreter.isCanonicalSignature.bind(sig).should.throw();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ var bitcore = bitcore || require('../bitcore');
|
||||||
|
|
||||||
var should = chai.should();
|
var should = chai.should();
|
||||||
|
|
||||||
var TransactionModule = bitcore.Transaction;
|
var Transaction = bitcore.Transaction;
|
||||||
var Transaction;
|
|
||||||
var In;
|
var In;
|
||||||
var Out;
|
var Out;
|
||||||
var Script = bitcore.Script;
|
var Script = bitcore.Script;
|
||||||
|
@ -40,7 +39,7 @@ function parse_test_transaction(entry) {
|
||||||
});
|
});
|
||||||
|
|
||||||
var raw = new Buffer(entry[1], 'hex');
|
var raw = new Buffer(entry[1], 'hex');
|
||||||
var tx = new TransactionModule();
|
var tx = new Transaction();
|
||||||
tx.parse(raw);
|
tx.parse(raw);
|
||||||
|
|
||||||
// Sanity check transaction has been parsed correctly
|
// Sanity check transaction has been parsed correctly
|
||||||
|
@ -53,10 +52,6 @@ function parse_test_transaction(entry) {
|
||||||
|
|
||||||
describe('Transaction', function() {
|
describe('Transaction', function() {
|
||||||
it('should initialze the main object', function() {
|
it('should initialze the main object', function() {
|
||||||
should.exist(TransactionModule);
|
|
||||||
});
|
|
||||||
it('should be able to create class', function() {
|
|
||||||
Transaction = TransactionModule;
|
|
||||||
should.exist(Transaction);
|
should.exist(Transaction);
|
||||||
In = Transaction.In;
|
In = Transaction.In;
|
||||||
Out = Transaction.Out;
|
Out = Transaction.Out;
|
||||||
|
@ -72,7 +67,7 @@ describe('Transaction', function() {
|
||||||
|
|
||||||
|
|
||||||
it('#selectUnspent should be able to select utxos', function() {
|
it('#selectUnspent should be able to select utxos', function() {
|
||||||
var u = Transaction.selectUnspent(testdata.dataUnspent,1.0, true);
|
var u = Transaction.selectUnspent(testdata.dataUnspent, 1.0, true);
|
||||||
u.length.should.equal(3);
|
u.length.should.equal(3);
|
||||||
|
|
||||||
should.exist(u[0].amount);
|
should.exist(u[0].amount);
|
||||||
|
@ -80,37 +75,37 @@ describe('Transaction', function() {
|
||||||
should.exist(u[0].scriptPubKey);
|
should.exist(u[0].scriptPubKey);
|
||||||
should.exist(u[0].vout);
|
should.exist(u[0].vout);
|
||||||
|
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.5, true);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.5, true);
|
||||||
u.length.should.equal(3);
|
u.length.should.equal(3);
|
||||||
|
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.1, true);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.1, true);
|
||||||
u.length.should.equal(2);
|
u.length.should.equal(2);
|
||||||
|
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.05, true);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.05, true);
|
||||||
u.length.should.equal(2);
|
u.length.should.equal(2);
|
||||||
|
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.015, true);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.015, true);
|
||||||
u.length.should.equal(2);
|
u.length.should.equal(2);
|
||||||
|
|
||||||
u = Transaction.selectUnspent(testdata.dataUnspent,0.01, true);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.01, true);
|
||||||
u.length.should.equal(1);
|
u.length.should.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#selectUnspent should return null if not enough utxos', function() {
|
it('#selectUnspent should return null if not enough utxos', function() {
|
||||||
var u = Transaction.selectUnspent(testdata.dataUnspent,1.12);
|
var u = Transaction.selectUnspent(testdata.dataUnspent, 1.12);
|
||||||
should.not.exist(u);
|
should.not.exist(u);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('#selectUnspent should check confirmations', function() {
|
it('#selectUnspent should check confirmations', function() {
|
||||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.9);
|
var u = Transaction.selectUnspent(testdata.dataUnspent, 0.9);
|
||||||
should.not.exist(u);
|
should.not.exist(u);
|
||||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.9,true);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.9, true);
|
||||||
u.length.should.equal(3);
|
u.length.should.equal(3);
|
||||||
|
|
||||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.11);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.11);
|
||||||
u.length.should.equal(2);
|
u.length.should.equal(2);
|
||||||
var u = Transaction.selectUnspent(testdata.dataUnspent,0.111);
|
u = Transaction.selectUnspent(testdata.dataUnspent, 0.111);
|
||||||
should.not.exist(u);
|
should.not.exist(u);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -121,8 +116,11 @@ describe('Transaction', function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
it('#create should be able to create instance', function() {
|
it('#create should be able to create instance', function() {
|
||||||
var utxos =testdata.dataUnspent;
|
var utxos = testdata.dataUnspent;
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.08
|
||||||
|
}];
|
||||||
|
|
||||||
var ret = Transaction.create(utxos, outs, opts);
|
var ret = Transaction.create(utxos, outs, opts);
|
||||||
should.exist(ret.tx);
|
should.exist(ret.tx);
|
||||||
|
@ -143,24 +141,35 @@ describe('Transaction', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#create should fail if not enough inputs ', function() {
|
it('#create should fail if not enough inputs ', function() {
|
||||||
var utxos =testdata.dataUnspent;
|
var utxos = testdata.dataUnspent;
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:80}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 80
|
||||||
|
}];
|
||||||
Transaction
|
Transaction
|
||||||
.create
|
.create
|
||||||
.bind(utxos, outs, opts)
|
.bind(utxos, outs, opts)
|
||||||
.should.throw();
|
.should.
|
||||||
|
throw ();
|
||||||
|
|
||||||
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.5}];
|
var outs2 = [{
|
||||||
should.exist( Transaction.create(utxos, outs2, opts));
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.5
|
||||||
|
}];
|
||||||
|
should.exist(Transaction.create(utxos, outs2, opts));
|
||||||
|
|
||||||
// do not allow unconfirmed
|
// do not allow unconfirmed
|
||||||
Transaction.create.bind(utxos, outs2).should.throw();
|
Transaction.create.bind(utxos, outs2).should.
|
||||||
|
throw ();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('#create should create same output as bitcoind createrawtransaction ', function() {
|
it('#create should create same output as bitcoind createrawtransaction ', function() {
|
||||||
var utxos =testdata.dataUnspent;
|
var utxos = testdata.dataUnspent;
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.08
|
||||||
|
}];
|
||||||
var ret = Transaction.create(utxos, outs, opts);
|
var ret = Transaction.create(utxos, outs, opts);
|
||||||
var tx = ret.tx;
|
var tx = ret.tx;
|
||||||
|
|
||||||
|
@ -170,10 +179,15 @@ describe('Transaction', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#create should create same output as bitcoind createrawtransaction wo remainder', function() {
|
it('#create should create same output as bitcoind createrawtransaction wo remainder', function() {
|
||||||
var utxos =testdata.dataUnspent;
|
var utxos = testdata.dataUnspent;
|
||||||
// no remainder
|
// no remainder
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{
|
||||||
var ret = Transaction.create(utxos, outs, {fee:0.03} );
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.08
|
||||||
|
}];
|
||||||
|
var ret = Transaction.create(utxos, outs, {
|
||||||
|
fee: 0.03
|
||||||
|
});
|
||||||
var tx = ret.tx;
|
var tx = ret.tx;
|
||||||
|
|
||||||
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08}'
|
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08}'
|
||||||
|
@ -182,15 +196,21 @@ describe('Transaction', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#createAndSign should sign a tx', function() {
|
it('#createAndSign should sign a tx', function() {
|
||||||
var utxos =testdata.dataUnspentSign.unspent;
|
var utxos = testdata.dataUnspentSign.unspent;
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.08
|
||||||
|
}];
|
||||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||||
var tx = ret.tx;
|
var tx = ret.tx;
|
||||||
tx.isComplete().should.equal(true);
|
tx.isComplete().should.equal(true);
|
||||||
tx.ins.length.should.equal(1);
|
tx.ins.length.should.equal(1);
|
||||||
tx.outs.length.should.equal(2);
|
tx.outs.length.should.equal(2);
|
||||||
|
|
||||||
var outs2 = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
|
var outs2 = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 16
|
||||||
|
}];
|
||||||
var ret2 = Transaction.createAndSign(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts);
|
var ret2 = Transaction.createAndSign(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts);
|
||||||
var tx2 = ret2.tx;
|
var tx2 = ret2.tx;
|
||||||
tx2.isComplete().should.equal(true);
|
tx2.isComplete().should.equal(true);
|
||||||
|
@ -200,8 +220,11 @@ describe('Transaction', function() {
|
||||||
|
|
||||||
it('#createAndSign should sign an incomplete tx ', function() {
|
it('#createAndSign should sign an incomplete tx ', function() {
|
||||||
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
||||||
var utxos =testdata.dataUnspentSign.unspent;
|
var utxos = testdata.dataUnspentSign.unspent;
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.08
|
||||||
|
}];
|
||||||
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
||||||
var tx = ret.tx;
|
var tx = ret.tx;
|
||||||
tx.ins.length.should.equal(1);
|
tx.ins.length.should.equal(1);
|
||||||
|
@ -209,8 +232,11 @@ describe('Transaction', function() {
|
||||||
});
|
});
|
||||||
it('#isComplete should return TX signature status', function() {
|
it('#isComplete should return TX signature status', function() {
|
||||||
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
||||||
var utxos =testdata.dataUnspentSign.unspent;
|
var utxos = testdata.dataUnspentSign.unspent;
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.08
|
||||||
|
}];
|
||||||
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
||||||
var tx = ret.tx;
|
var tx = ret.tx;
|
||||||
tx.isComplete().should.equal(false);
|
tx.isComplete().should.equal(false);
|
||||||
|
@ -219,31 +245,37 @@ describe('Transaction', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#sign should sign a tx in multiple steps (case1)', function() {
|
it('#sign should sign a tx in multiple steps (case1)', function() {
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:1.08}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 1.08
|
||||||
|
}];
|
||||||
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
||||||
var tx = ret.tx;
|
var tx = ret.tx;
|
||||||
var selectedUtxos = ret.selectedUtxos;
|
var selectedUtxos = ret.selectedUtxos;
|
||||||
|
|
||||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
var k1 = testdata.dataUnspentSign.keyStrings.slice(0, 1);
|
||||||
|
|
||||||
tx.isComplete().should.equal(false);
|
tx.isComplete().should.equal(false);
|
||||||
|
|
||||||
tx.sign(selectedUtxos, k1).should.equal(false);
|
tx.sign(selectedUtxos, k1).should.equal(false);
|
||||||
|
|
||||||
var k23 = testdata.dataUnspentSign.keyStrings.slice(1,3);
|
var k23 = testdata.dataUnspentSign.keyStrings.slice(1, 3);
|
||||||
tx.sign(selectedUtxos, k23).should.equal(true);
|
tx.sign(selectedUtxos, k23).should.equal(true);
|
||||||
tx.isComplete().should.equal(true);
|
tx.isComplete().should.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#sign should sign a tx in multiple steps (case2)', function() {
|
it('#sign should sign a tx in multiple steps (case2)', function() {
|
||||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:16}];
|
var outs = [{
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 16
|
||||||
|
}];
|
||||||
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
||||||
var tx = ret.tx;
|
var tx = ret.tx;
|
||||||
var selectedUtxos = ret.selectedUtxos;
|
var selectedUtxos = ret.selectedUtxos;
|
||||||
|
|
||||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0,1);
|
var k1 = testdata.dataUnspentSign.keyStrings.slice(0, 1);
|
||||||
var k2 = testdata.dataUnspentSign.keyStrings.slice(1,2);
|
var k2 = testdata.dataUnspentSign.keyStrings.slice(1, 2);
|
||||||
var k3 = testdata.dataUnspentSign.keyStrings.slice(2,3);
|
var k3 = testdata.dataUnspentSign.keyStrings.slice(2, 3);
|
||||||
tx.sign(selectedUtxos, k1).should.equal(false);
|
tx.sign(selectedUtxos, k1).should.equal(false);
|
||||||
tx.sign(selectedUtxos, k2).should.equal(false);
|
tx.sign(selectedUtxos, k2).should.equal(false);
|
||||||
tx.sign(selectedUtxos, k3).should.equal(true);
|
tx.sign(selectedUtxos, k3).should.equal(true);
|
||||||
|
@ -253,11 +285,14 @@ describe('Transaction', function() {
|
||||||
it('#createAndSign: should generate dynamic fee and readjust (and not) the selected UTXOs', function() {
|
it('#createAndSign: should generate dynamic fee and readjust (and not) the selected UTXOs', function() {
|
||||||
//this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee,
|
//this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee,
|
||||||
//so, it should trigger adding a new 10BTC utxo
|
//so, it should trigger adding a new 10BTC utxo
|
||||||
var utxos =testdata.dataUnspentSign.unspent;
|
var utxos = testdata.dataUnspentSign.unspent;
|
||||||
var outs = [];
|
var outs = [];
|
||||||
var n =101;
|
var n = 101;
|
||||||
for (var i=0; i<n; i++) {
|
for (var i = 0; i < n; i++) {
|
||||||
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
|
outs.push({
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.01
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||||
|
@ -275,15 +310,18 @@ describe('Transaction', function() {
|
||||||
|
|
||||||
|
|
||||||
//this is the complementary case, it does not trigger a new utxo
|
//this is the complementary case, it does not trigger a new utxo
|
||||||
var utxos =testdata.dataUnspentSign.unspent;
|
utxos = testdata.dataUnspentSign.unspent;
|
||||||
var outs = [];
|
outs = [];
|
||||||
var n =100;
|
n = 100;
|
||||||
for (var i=0; i<n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
outs.push({address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.01});
|
outs.push({
|
||||||
|
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||||
|
amount: 0.01
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||||
var tx = ret.tx;
|
tx = ret.tx;
|
||||||
tx.getSize().should.equal(3485);
|
tx.getSize().should.equal(3485);
|
||||||
|
|
||||||
// ins = 1.0101 BTC (1 inputs: 1.0101);
|
// ins = 1.0101 BTC (1 inputs: 1.0101);
|
||||||
|
@ -297,21 +335,30 @@ describe('Transaction', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bitcoin core transaction tests
|
||||||
|
*/
|
||||||
// Verify that known valid transactions are intepretted correctly
|
// Verify that known valid transactions are intepretted correctly
|
||||||
testdata.dataTxValid.forEach(function(datum) {
|
testdata.dataTxValid.forEach(function(datum) {
|
||||||
var testTx = parse_test_transaction(datum);
|
var testTx = parse_test_transaction(datum);
|
||||||
if (!testTx) return;
|
if (!testTx) return;
|
||||||
|
var verifyP2SH = datum[2];
|
||||||
var transactionString = buffertools.toHex(
|
var transactionString = buffertools.toHex(
|
||||||
testTx.transaction.serialize());
|
testTx.transaction.serialize());
|
||||||
|
|
||||||
it.skip('valid tx=' + transactionString, function() {
|
it('valid tx=' + transactionString, function() {
|
||||||
// Verify that all inputs are valid
|
// Verify that all inputs are valid
|
||||||
testTx.inputs.forEach(function(input) {
|
testTx.inputs.forEach(function(input) {
|
||||||
testTx.transaction.verifyInput(input.index, input.scriptPubKey,
|
testTx.transaction.verifyInput(
|
||||||
|
input.index,
|
||||||
|
input.scriptPubKey,
|
||||||
|
{ verifyP2SH: verifyP2SH, dontVerifyStrictEnc: true},
|
||||||
function(err, results) {
|
function(err, results) {
|
||||||
// Exceptions raised inside this function will be handled
|
// Exceptions raised inside this function will be handled
|
||||||
// ...by this function, so ignore if that is the case
|
// ...by this function, so ignore if that is the case
|
||||||
if (err && err.constructor.name === "AssertionError") return;
|
if (err && err.constructor.name === 'AssertionError') return;
|
||||||
|
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(results);
|
should.exist(results);
|
||||||
|
@ -328,14 +375,14 @@ describe('Transaction', function() {
|
||||||
var transactionString = buffertools.toHex(
|
var transactionString = buffertools.toHex(
|
||||||
testTx.transaction.serialize());
|
testTx.transaction.serialize());
|
||||||
|
|
||||||
it.skip('valid tx=' + transactionString, function() {
|
it('valid tx=' + transactionString, function() {
|
||||||
// Verify that all inputs are invalid
|
// Verify that all inputs are invalid
|
||||||
testTx.inputs.forEach(function(input) {
|
testTx.inputs.forEach(function(input) {
|
||||||
testTx.transaction.verifyInput(input.index, input.scriptPubKey,
|
testTx.transaction.verifyInput(input.index, input.scriptPubKey,
|
||||||
function(err, results) {
|
function(err, results) {
|
||||||
// Exceptions raised inside this function will be handled
|
// Exceptions raised inside this function will be handled
|
||||||
// ...by this function, so ignore if that is the case
|
// ...by this function, so ignore if that is the case
|
||||||
if (err && err.constructor.name === "AssertionError") return;
|
if (err && err.constructor.name === 'AssertionError') return;
|
||||||
|
|
||||||
// There should either be an error, or the results should be false.
|
// There should either be an error, or the results should be false.
|
||||||
(err !== null || (!err && results === false)).should.equal(true);
|
(err !== null || (!err && results === false)).should.equal(true);
|
||||||
|
|
|
@ -15,8 +15,8 @@ var examples = [
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('Examples', function() {
|
describe('Examples', function() {
|
||||||
before(mute);
|
//before(mute);
|
||||||
after(unmute);
|
//after(unmute);
|
||||||
examples.forEach(function(example) {
|
examples.forEach(function(example) {
|
||||||
it('valid '+example, function() {
|
it('valid '+example, function() {
|
||||||
var ex = require('../examples/'+example);
|
var ex = require('../examples/'+example);
|
||||||
|
|
Loading…
Reference in New Issue