diff --git a/src/transaction.js b/src/transaction.js index 6528adf..0926b84 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -9,11 +9,11 @@ var ECKey = require('./eckey') var ECSignature = require('./ecsignature') var Script = require('./script') -var DEFAULT_SEQUENCE = 0xffffffff -var SIGHASH_ALL = 0x01 -var SIGHASH_NONE = 0x02 -var SIGHASH_SINGLE = 0x03 -var SIGHASH_ANYONECANPAY = 0x80 +Transaction.DEFAULT_SEQUENCE = 0xffffffff +Transaction.SIGHASH_ALL = 0x01 +Transaction.SIGHASH_NONE = 0x02 +Transaction.SIGHASH_SINGLE = 0x03 +Transaction.SIGHASH_ANYONECANPAY = 0x80 function Transaction() { this.version = 1 @@ -32,29 +32,33 @@ function Transaction() { * * Note that this method does not sign the created input. */ -Transaction.prototype.addInput = function(tx, index) { +Transaction.prototype.addInput = function(tx, index, sequence) { + if (sequence == undefined) sequence = Transaction.DEFAULT_SEQUENCE + var hash if (typeof tx === 'string') { hash = new Buffer(tx, 'hex') - assert.equal(hash.length, 32, 'Expected Transaction or string, got ' + tx) - // TxHash hex is big-endian, we need little-endian + // TxId hex is big-endian, we need little-endian Array.prototype.reverse.call(hash) - } else { - assert(tx instanceof Transaction, 'Expected Transaction or string, got ' + tx) - hash = crypto.hash256(tx.toBuffer()) + } else if (tx instanceof Transaction) { + hash = tx.getHash() + } else { + hash = tx } + assert(Buffer.isBuffer(hash), 'Expected Transaction, txId or txHash, got ' + tx) + assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) assert.equal(typeof index, 'number', 'Expected number index, got ' + index) return (this.ins.push({ hash: hash, index: index, script: Script.EMPTY, - sequence: DEFAULT_SEQUENCE + sequence: sequence }) - 1) } @@ -140,7 +144,6 @@ Transaction.prototype.toBuffer = function () { }) writeUInt32(this.locktime) - assert.equal(offset, buffer.length, 'Invalid transaction object') return buffer } @@ -172,15 +175,15 @@ Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashTy txTmp.ins[inIndex].script = hashScript var hashTypeModifier = hashType & 0x1f - if (hashTypeModifier === SIGHASH_NONE) { + if (hashTypeModifier === Transaction.SIGHASH_NONE) { assert(false, 'SIGHASH_NONE not yet supported') - } else if (hashTypeModifier === SIGHASH_SINGLE) { + } else if (hashTypeModifier === Transaction.SIGHASH_SINGLE) { assert(false, 'SIGHASH_SINGLE not yet supported') } - if (hashType & SIGHASH_ANYONECANPAY) { + if (hashType & Transaction.SIGHASH_ANYONECANPAY) { assert(false, 'SIGHASH_ANYONECANPAY not yet supported') } @@ -191,8 +194,12 @@ Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashTy return crypto.hash256(buffer) } +Transaction.prototype.getHash = function () { + return crypto.hash256(this.toBuffer()) +} + Transaction.prototype.getId = function () { - var buffer = crypto.hash256(this.toBuffer()) + var buffer = this.getHash() // Big-endian is used for TxHash Array.prototype.reverse.call(buffer) @@ -278,7 +285,7 @@ Transaction.fromBuffer = function(buffer) { } tx.locktime = readUInt32() - assert.equal(offset, buffer.length, 'Invalid transaction') + assert.equal(offset, buffer.length, 'Transaction has unexpected data') return tx } @@ -300,7 +307,7 @@ Transaction.prototype.sign = function(index, privKey, hashType) { } Transaction.prototype.signInput = function(index, prevOutScript, privKey, hashType) { - hashType = hashType || SIGHASH_ALL + hashType = hashType || Transaction.SIGHASH_ALL var hash = this.hashForSignature(prevOutScript, index, hashType) var signature = privKey.sign(hash) diff --git a/test/fixtures/transaction.json b/test/fixtures/transaction.json new file mode 100644 index 0000000..c473323 --- /dev/null +++ b/test/fixtures/transaction.json @@ -0,0 +1,188 @@ +{ + "valid": [ + { + "description": "Standard transaction (1:1)", + "txid": "a0ff943d3f644d8832b1fa74be4d0ad2577615dc28a7ef74ff8c271b603a082a", + "hash": "2a083a601b278cff74efa728dc157657d20a4dbe74fab132884d643f3d94ffa0", + "raw": { + "version": 1, + "ins": [ + { + "hash": "f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe", + "index": 0, + "script": "4830450221008732a460737d956fd94d49a31890b2908f7ed7025a9c1d0f25e43290f1841716022004fa7d608a291d44ebbbebbadaac18f943031e7de39ef3bf9920998c43e60c0401210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "sequence": 4294967295 + } + ], + "outs": [ + { + "script": "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac", + "value": 100000 + } + ], + "locktime": 0 + }, + "hex": "0100000001f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b4830450221008732a460737d956fd94d49a31890b2908f7ed7025a9c1d0f25e43290f1841716022004fa7d608a291d44ebbbebbadaac18f943031e7de39ef3bf9920998c43e60c0401210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff01a0860100000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000" + }, + { + "description": "Standard transaction (2:2)", + "txid": "eb1c3a8b1bd7d38a6bd8f3c48e8fc950cf3ddf9b34e91594d8c1b31e0bcf8240", + "hash": "4082cf0b1eb3c1d89415e9349bdf3dcf50c98f8ec4f3d86b8ad3d71b8b3a1ceb", + "raw": { + "version": 1, + "ins": [ + { + "hash": "f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe", + "index": 0, + "script": "483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "sequence": 4294967295 + }, + { + "hash": "f2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe", + "index": 1, + "script": "483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687", + "sequence": 4294967295 + } + ], + "outs": [ + { + "script": "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac", + "value": 50000 + }, + { + "script": "a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687", + "value": 150000 + } + ], + "locktime": 0 + }, + "hex": "0100000002f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe0100000083483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687ffffffff0250c30000000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88acf04902000000000017a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc768700000000" + }, + { + "description": "Standard transaction (14:2)", + "txid": "39d57bc27f72e904d81f6b5ef7b4e6e17fa33a06b11e5114a43435830d7b5563", + "hash": "63557b0d833534a414511eb1063aa37fe1e6b4f75e6b1fd804e9727fc27bd539", + "raw": { + "version": 1, + "locktime": 0, + "ins": [ + { + "hash": "e7b73e229790c1e79a02f0c871813b3cf26a4156c5b8d942e88b38fe8d3f43a0", + "index": 0, + "script": "493046022100fd3d8fef44fb0962ba3f07bee1d4cafb84e60e38e6c7d9274504b3638a8d2f520221009fce009044e615b6883d4bf62e04c48f9fe236e19d644b082b2f0ae5c98e045c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "7bfc005f3880a606027c7cd7dd02a0f6a6572eeb84a91aa158311be13695a7ea", + "index": 1, + "script": "483045022100e2e61c40f26e2510b76dc72ea2f568ec514fce185c719e18bca9caaef2b20e9e02207f1100fc79eb0584e970c7f18fb226f178951d481767b4092d50d13c50ccba8b014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "0e0f8e6bf951fbb84d7d8ef833a1cbf5bb046ea7251973ac6e7661c755386ee3", + "index": 1, + "script": "473044022048f1611e403710f248f7caf479965a6a5f63cdfbd9a714fef4ec1b68331ade1d022074919e79376c363d4575b2fc21513d5949471703efebd4c5ca2885e810eb1fa4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "e6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b", + "index": 0, + "script": "483045022100886c07cad489dfcf4b364af561835d5cf985f07adf8bd1d5bd6ddea82b0ce6b2022045bdcbcc2b5fc55191bb997039cf59ff70e8515c56b62f293a9add770ba26738014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "e6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b", + "index": 1, + "script": "4730440220535d49b819fdf294d27d82aff2865ed4e18580f0ca9796d793f611cb43a44f47022019584d5e300c415f642e37ba2a814a1e1106b4a9b91dc2a30fb57ceafe041181014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "d3051677216ea53baa2e6d7f6a75434ac338438c59f314801c8496d1e6d1bf6d", + "index": 1, + "script": "483045022100bf612b0fa46f49e70ab318ca3458d1ed5f59727aa782f7fac5503f54d9b43a590220358d7ed0e3cee63a5a7e972d9fad41f825d95de2fd0c5560382468610848d489014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "1e751ccc4e7d973201e9174ec78ece050ef2fadd6a108f40f76a9fa314979c31", + "index": 1, + "script": "483045022006e263d5f73e05c48a603e3bd236e8314e5420721d5e9020114b93e8c9220e1102210099d3dead22f4a792123347a238c87e67b55b28a94a0bb7793144cc7ad94a0168014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "25c4cf2c61743b3f4252d921d937cca942cf32e4f3fa4a544d0b26f014337084", + "index": 1, + "script": "47304402207d6e87588be47bf2d97eaf427bdd992e9d6b306255711328aee38533366a88b50220623099595ae442cb77eaddb3f91753a4fc9df56fde69cfec584c7f97e05533c8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "ecd93c87eb43c48481e6694904305349bdea94b01104579fa9f02bff66c89663", + "index": 1, + "script": "473044022020f59498aee0cf82cb113768ef3cb721000346d381ff439adb4d405f791252510220448de723aa59412266fabbc689ec25dc94b1688c27a614982047513a80173514014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "a1fdc0a79ff98d5b6154176e321c22f4f8450dbd950bd013ad31135f5604411e", + "index": 1, + "script": "48304502210088167867f87327f9c0db0444267ff0b6a026eedd629d8f16fe44a34c18e706bf0220675c8baebf89930e2d6e4463adefc50922653af99375242e38f5ee677418738a014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "b89e8249c3573b58bf1ec7433185452dd57ab8e1daab01c3cc6ddc8b66ad3de8", + "index": 0, + "script": "4830450220073d50ac5ec8388d5b3906921f9368c31ad078c8e1fb72f26d36b533f35ee327022100c398b23e6692e11dca8a1b64aae2ff70c6a781ed5ee99181b56a2f583a967cd4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "45ee07e182084454dacfad1e61b04ffdf9c7b01003060a6c841a01f4fff8a5a0", + "index": 1, + "script": "483045022100991d1bf60c41358f08b20e53718a24e05ac0608915df4f6305a5b47cb61e5da7022003f14fc1cc5b737e2c3279a4f9be1852b49dbb3d9d6cc4c8af6a666f600dced8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "4cba12549f1d70f8e60aea8b546c8357f7c099e7c7d9d8691d6ee16e7dfa3170", + "index": 1, + "script": "493046022100f14e2b0ef8a8e206db350413d204bc0a5cd779e556b1191c2d30b5ec023cde6f022100b90b2d2bf256c98a88f7c3a653b93cec7d25bb6a517db9087d11dbd189e8851c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + }, + { + "hash": "a4b3aed39eb2a1dc6eae4609d9909724e211c153927c230d02bd33add3026959", + "index": 1, + "script": "483045022100a8cebb4f1c58f5ba1af91cb8bd4a2ed4e684e9605f5a9dc8b432ed00922d289d0220251145d2d56f06d936fd0c51fa884b4a6a5fafd0c3318f72fb05a5c9aa372195014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19f", + "sequence": 4294967295 + } + ], + "outs": [ + { + "value": 52680000, + "script": "76a914167c3e1f10cc3b691c73afbdb211e156e3e3f25c88ac" + }, + { + "value": 3032597, + "script": "76a914290f7d617b75993e770e5606335fa0999a28d71388ac" + } + ] + }, + "hex": "010000000ee7b73e229790c1e79a02f0c871813b3cf26a4156c5b8d942e88b38fe8d3f43a0000000008c493046022100fd3d8fef44fb0962ba3f07bee1d4cafb84e60e38e6c7d9274504b3638a8d2f520221009fce009044e615b6883d4bf62e04c48f9fe236e19d644b082b2f0ae5c98e045c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff7bfc005f3880a606027c7cd7dd02a0f6a6572eeb84a91aa158311be13695a7ea010000008b483045022100e2e61c40f26e2510b76dc72ea2f568ec514fce185c719e18bca9caaef2b20e9e02207f1100fc79eb0584e970c7f18fb226f178951d481767b4092d50d13c50ccba8b014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0e0f8e6bf951fbb84d7d8ef833a1cbf5bb046ea7251973ac6e7661c755386ee3010000008a473044022048f1611e403710f248f7caf479965a6a5f63cdfbd9a714fef4ec1b68331ade1d022074919e79376c363d4575b2fc21513d5949471703efebd4c5ca2885e810eb1fa4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b000000008b483045022100886c07cad489dfcf4b364af561835d5cf985f07adf8bd1d5bd6ddea82b0ce6b2022045bdcbcc2b5fc55191bb997039cf59ff70e8515c56b62f293a9add770ba26738014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffe6f17f35bf9f0aa7a4242ab3e29edbdb74c5274bf263e53043dddb8045cb585b010000008a4730440220535d49b819fdf294d27d82aff2865ed4e18580f0ca9796d793f611cb43a44f47022019584d5e300c415f642e37ba2a814a1e1106b4a9b91dc2a30fb57ceafe041181014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffd3051677216ea53baa2e6d7f6a75434ac338438c59f314801c8496d1e6d1bf6d010000008b483045022100bf612b0fa46f49e70ab318ca3458d1ed5f59727aa782f7fac5503f54d9b43a590220358d7ed0e3cee63a5a7e972d9fad41f825d95de2fd0c5560382468610848d489014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff1e751ccc4e7d973201e9174ec78ece050ef2fadd6a108f40f76a9fa314979c31010000008b483045022006e263d5f73e05c48a603e3bd236e8314e5420721d5e9020114b93e8c9220e1102210099d3dead22f4a792123347a238c87e67b55b28a94a0bb7793144cc7ad94a0168014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff25c4cf2c61743b3f4252d921d937cca942cf32e4f3fa4a544d0b26f014337084010000008a47304402207d6e87588be47bf2d97eaf427bdd992e9d6b306255711328aee38533366a88b50220623099595ae442cb77eaddb3f91753a4fc9df56fde69cfec584c7f97e05533c8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffecd93c87eb43c48481e6694904305349bdea94b01104579fa9f02bff66c89663010000008a473044022020f59498aee0cf82cb113768ef3cb721000346d381ff439adb4d405f791252510220448de723aa59412266fabbc689ec25dc94b1688c27a614982047513a80173514014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa1fdc0a79ff98d5b6154176e321c22f4f8450dbd950bd013ad31135f5604411e010000008b48304502210088167867f87327f9c0db0444267ff0b6a026eedd629d8f16fe44a34c18e706bf0220675c8baebf89930e2d6e4463adefc50922653af99375242e38f5ee677418738a014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffb89e8249c3573b58bf1ec7433185452dd57ab8e1daab01c3cc6ddc8b66ad3de8000000008b4830450220073d50ac5ec8388d5b3906921f9368c31ad078c8e1fb72f26d36b533f35ee327022100c398b23e6692e11dca8a1b64aae2ff70c6a781ed5ee99181b56a2f583a967cd4014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff45ee07e182084454dacfad1e61b04ffdf9c7b01003060a6c841a01f4fff8a5a0010000008b483045022100991d1bf60c41358f08b20e53718a24e05ac0608915df4f6305a5b47cb61e5da7022003f14fc1cc5b737e2c3279a4f9be1852b49dbb3d9d6cc4c8af6a666f600dced8014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff4cba12549f1d70f8e60aea8b546c8357f7c099e7c7d9d8691d6ee16e7dfa3170010000008c493046022100f14e2b0ef8a8e206db350413d204bc0a5cd779e556b1191c2d30b5ec023cde6f022100b90b2d2bf256c98a88f7c3a653b93cec7d25bb6a517db9087d11dbd189e8851c014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffffa4b3aed39eb2a1dc6eae4609d9909724e211c153927c230d02bd33add3026959010000008b483045022100a8cebb4f1c58f5ba1af91cb8bd4a2ed4e684e9605f5a9dc8b432ed00922d289d0220251145d2d56f06d936fd0c51fa884b4a6a5fafd0c3318f72fb05a5c9aa372195014104aa592c859fd00ed2a02609aad3a1bf72e0b42de67713e632c70a33cc488c15598a0fb419370a54d1c275b44380e8777fc01b6dc3cd43a416c6bab0e30dc1e19fffffffff0240d52303000000001976a914167c3e1f10cc3b691c73afbdb211e156e3e3f25c88ac15462e00000000001976a914290f7d617b75993e770e5606335fa0999a28d71388ac00000000" + } + ], + "invalid": { + "addInput": [ + { + "exception": "Expected hash length of 32, got 30", + "hash": "0aed1366a73b6057ee7800d737bff1bdf8c448e98d86bc0998f2b009816d", + "index": 0 + }, + { + "exception": "Expected hash length of 32, got 34", + "hash": "0aed1366a73b6057ee7800d737bff1bdf8c448e98d86bc0998f2b009816da9b0ffff", + "index": 0 + } + ], + "fromBuffer": [ + { + "exception": "Transaction has unexpected data", + "hex": "0100000002f1fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe000000006b483045022100e661badd8d2cf1af27eb3b82e61b5d3f5d5512084591796ae31487f5b82df948022006df3c2a2cac79f68e4b179f4bbb8185a0bb3c4a2486d4405c59b2ba07a74c2101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff2fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe0100000083483045022100be54a46a44fb7e6bf4ebf348061d0dace7ddcbb92d4147ce181cf4789c7061f0022068ccab2a89a47fc29bb5074bca99ae846ab446eecf3c3aaeb238a13838783c78012102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee517a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc7687ffffffff0250c30000000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88acf04902000000000017a9147ccb85f0ab2d599bc17246c98babd5a20b1cdc768700000000ffffffff" + } + ] + } +} diff --git a/test/transaction.js b/test/transaction.js index 38f031f..a8aa1d7 100644 --- a/test/transaction.js +++ b/test/transaction.js @@ -5,198 +5,224 @@ var scripts = require('../src/scripts') var Address = require('../src/address') var ECKey = require('../src/eckey') var Transaction = require('../src/transaction') -var Script = require('../src/script') -var fixtureTxes = require('./fixtures/mainnet_tx') -var fixtureTx1Hex = fixtureTxes.prevTx -var fixtureTx2Hex = fixtureTxes.tx -var fixtureTxBigHex = fixtureTxes.bigTx +var fixtures = require('./fixtures/transaction') + +// FIXME: what is a better way to do this, seems a bit odd +fixtures.valid.forEach(function(f) { + var Script = require('../src/script') + + f.raw.ins.forEach(function(fin) { + fin.hash = new Buffer(fin.hash, 'hex') + fin.script = Script.fromHex(fin.script) + }) + + f.raw.outs.forEach(function(fout) { + fout.script = Script.fromHex(fout.script) + }) +}) describe('Transaction', function() { - describe('toBuffer', function() { - it('matches the expected output', function() { - var expected = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d600000000fd1b0100483045022100e5be20d440b2bbbc886161f9095fa6d0bca749a4e41d30064f30eb97adc7a1f5022061af132890d8e4e90fedff5e9365aeeb77021afd8ef1d5c114d575512e9a130a0147304402205054e38e9d7b5c10481b6b4991fde5704cd94d49e344406e3c2ce4d18a43bf8e022051d7ba8479865b53a48bee0cce86e89a25633af5b2918aa276859489e232f51c014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0101000000000000001976a914751e76e8199196d454941c45d1b3a323f1433bd688ac00000000' + describe('fromBuffer/fromHex', function() { + fixtures.valid.forEach(function(f) { + it('imports ' + f.txid + ' correctly', function() { + var actual = Transaction.fromHex(f.hex) - var actual = Transaction.fromHex(expected).toHex() + assert.deepEqual(actual, f.raw) + }) + }) - assert.equal(actual, expected) + fixtures.invalid.fromBuffer.forEach(function(f) { + it('throws on ' + f.exception, function() { + assert.throws(function() { + Transaction.fromHex(f.hex) + }, new RegExp(f.exception)) + }) }) }) - describe('fromBuffer', function() { - var tx, serializedTx + describe('toBuffer/toHex', function() { + fixtures.valid.forEach(function(f) { + it('exports ' + f.txid + ' correctly', function() { + var actual = Transaction.prototype.toBuffer.call(f.raw) + + assert.equal(actual.toString('hex'), f.hex) + }) + }) + }) + + describe('addInput', function() { + // FIXME: not as pretty as could be + // Probably a bit representative of the API + var prevTxHash, prevTxId, prevTx beforeEach(function() { - serializedTx = '0100000001344630cbff61fbc362f7e1ff2f11a344c29326e4ee96e787dc0d4e5cc02fd069000000004a493046022100ef89701f460e8660c80808a162bbf2d676f40a331a243592c36d6bd1f81d6bdf022100d29c072f1b18e59caba6e1f0b8cadeb373fd33a25feded746832ec179880c23901ffffffff0100f2052a010000001976a914dd40dedd8f7e37466624c4dacc6362d8e7be23dd88ac00000000' - tx = Transaction.fromHex(serializedTx) + var f = fixtures.valid[0] + prevTx = Transaction.fromHex(f.hex) + prevTxHash = prevTx.getHash() + prevTxId = prevTx.getId() }) - it('does not mutate the input buffer', function() { - var buffer = new Buffer(serializedTx, 'hex') - Transaction.fromBuffer(buffer) - - assert.equal(buffer.toString('hex'), serializedTx) - }) - - it('decodes version correctly', function() { - assert.equal(tx.version, 1) - }) - - it('decodes locktime correctly', function() { - assert.equal(tx.locktime, 0) - }) - - it('decodes inputs correctly', function() { - assert.equal(tx.ins.length, 1) - - var input = tx.ins[0] - assert.equal(input.sequence, 4294967295) - - assert.equal(input.index, 0) - assert.equal(input.hash.toString('hex'), "344630cbff61fbc362f7e1ff2f11a344c29326e4ee96e787dc0d4e5cc02fd069") - - assert.equal(input.script.toHex(), "493046022100ef89701f460e8660c80808a162bbf2d676f40a331a243592c36d6bd1f81d6bdf022100d29c072f1b18e59caba6e1f0b8cadeb373fd33a25feded746832ec179880c23901") - }) - - it('decodes outputs correctly', function() { - assert.equal(tx.outs.length, 1) - - var output = tx.outs[0] - - assert.equal(output.value, 5000000000) - assert.deepEqual(output.script, Address.fromBase58Check('n1gqLjZbRH1biT5o4qiVMiNig8wcCPQeB9').toOutputScript()) - }) - - it('decodes large inputs correctly', function() { - // transaction has only 1 input + it('accepts a transaction id', function() { var tx = new Transaction() - tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57", 0) - tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3", 100) + tx.addInput(prevTxId, 0) - var buffer = tx.toBuffer() + assert.deepEqual(tx.ins[0].hash, prevTxHash) + }) - // we're going to replace the 8bit VarInt for tx.ins.length with a stretched 32bit equivalent - var mutated = Buffer.concat([ - buffer.slice(0, 4), - new Buffer([254, 1, 0, 0, 0]), - buffer.slice(5) - ]) + it('accepts a transaction hash', function() { + var tx = new Transaction() + tx.addInput(prevTxHash, 0) - // the deserialized-serialized transaction should return to its non-mutated state (== tx) - var buffer2 = Transaction.fromBuffer(mutated).toBuffer() - assert.deepEqual(buffer, buffer2) + assert.deepEqual(tx.ins[0].hash, prevTxHash) + }) + + it('accepts a Transaction object', function() { + var tx = new Transaction() + tx.addInput(prevTx, 0) + + assert.deepEqual(tx.ins[0].hash, prevTxHash) + }) + + it('returns an index', function() { + var tx = new Transaction() + assert.equal(tx.addInput(prevTxHash, 0), 0) + assert.equal(tx.addInput(prevTxHash, 0), 1) + }) + + it('defaults to DEFAULT_SEQUENCE', function() { + var tx = new Transaction() + tx.addInput(prevTxHash, 0) + + assert.equal(tx.ins[0].sequence, Transaction.DEFAULT_SEQUENCE) + }) + + fixtures.valid.forEach(function(f) { + it('should add the inputs for ' + f.txid + ' correctly', function() { + var tx = new Transaction() + + f.raw.ins.forEach(function(txIn, i) { + var j = tx.addInput(txIn.hash, txIn.index, txIn.sequence) + + assert.equal(i, j) + assert.deepEqual(tx.ins[i].hash, txIn.hash) + assert.equal(tx.ins[i].index, txIn.index) + + var sequence = txIn.sequence + if (sequence == undefined) sequence = Transaction.DEFAULT_SEQUENCE + assert.equal(tx.ins[i].sequence, sequence) + }) + }) + }) + + fixtures.invalid.addInput.forEach(function(f) { + it('throws on ' + f.exception, function() { + var tx = new Transaction() + var hash = new Buffer(f.hash, 'hex') + + assert.throws(function() { + tx.addInput(hash, f.index) + }, new RegExp(f.exception)) + }) }) }) - describe('creating a transaction', function() { - var tx, prevTx + describe('addOutput', function() { + // FIXME: not as pretty as could be + // Probably a bit representative of the API + var destAddressB58, destAddress, destScript beforeEach(function() { - prevTx = Transaction.fromHex(fixtureTx1Hex) - tx = new Transaction() + destAddressB58 = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' + destAddress = Address.fromBase58Check(destAddressB58) + destScript = destAddress.toOutputScript() }) - describe('addInput', function() { - it('accepts a transaction hash', function() { - var prevTxHash = prevTx.getId() + it('accepts an address string', function() { + var tx = new Transaction() + tx.addOutput(destAddressB58, 40000) - tx.addInput(prevTxHash, 0) - verifyTransactionIn() - }) - - it('accepts a Transaction object', function() { - tx.addInput(prevTx, 0) - verifyTransactionIn() - }) - - it('returns an index', function() { - assert.equal(tx.addInput(prevTx, 0), 0) - assert.equal(tx.addInput(prevTx, 0), 1) - }) - - function verifyTransactionIn() { - assert.equal(tx.ins.length, 1) - - var input = tx.ins[0] - assert.equal(input.sequence, 4294967295) - - assert.equal(input.index, 0) - assert.equal(input.hash.toString('hex'), "576bc3c3285dbdccd8c3cbd8c03e10d7f77a5c839c744f34c3eb00511059b80c") - - assert.equal(input.script, Script.EMPTY) - } + assert.deepEqual(tx.outs[0].script, destScript) + assert.equal(tx.outs[0].value, 40000) }) - describe('addOutput', function() { - it('accepts an address string', function() { - var dest = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' + it('accepts an Address', function() { + var tx = new Transaction() + tx.addOutput(destAddress, 40000) - tx.addOutput(dest, 40000) - verifyTransactionOut() - }) - - it('accepts an Address', function() { - var dest = Address.fromBase58Check('15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3') - - tx.addOutput(dest, 40000) - verifyTransactionOut() - }) - - it('accepts a scriptPubKey', function() { - var dest = Address.fromBase58Check('15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3').toOutputScript() - - tx.addOutput(dest, 40000) - verifyTransactionOut() - }) - - it('returns an index', function() { - var dest = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' - - assert.equal(tx.addOutput(dest, 40000), 0) - assert.equal(tx.addOutput(dest, 40000), 1) - }) - - function verifyTransactionOut() { - assert.equal(tx.outs.length, 1) - - var output = tx.outs[0] - assert.equal(output.value, 40000) - assert.equal(output.script.toHex(), "76a9143443bc45c560866cfeabf1d52f50a6ed358c69f288ac") - } + assert.deepEqual(tx.outs[0].script, destScript) + assert.equal(tx.outs[0].value, 40000) }) - describe('sign', function() { - it('works', function() { - tx.addInput("0cb859105100ebc3344f749c835c7af7d7103ec0d8cbc3d8ccbd5d28c3c36b57", 0) - tx.addOutput("15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3", 40000) - tx.addOutput("1Bu3bhwRmevHLAy1JrRB6AfcxfgDG2vXRd", 50000) + it('accepts a scriptPubKey', function() { + var tx = new Transaction() + tx.addOutput(destScript, 40000) - var key = ECKey.fromWIF('L44f7zxJ5Zw4EK9HZtyAnzCYz2vcZ5wiJf9AuwhJakiV4xVkxBeb') - tx.sign(0, key) - - var script = prevTx.outs[0].script - var sig = new Buffer(tx.ins[0].script.chunks[0]) - - assert.equal(tx.validateInput(0, script, key.pub, sig), true) - }) + assert.deepEqual(tx.outs[0].script, destScript) + assert.equal(tx.outs[0].value, 40000) }) - describe('validateInput', function() { - var validTx + it('returns an index', function() { + var tx = new Transaction() + assert.equal(tx.addOutput(destScript, 40000), 0) + assert.equal(tx.addOutput(destScript, 40000), 1) + }) - beforeEach(function() { - validTx = Transaction.fromHex(fixtureTx2Hex) - }) + fixtures.valid.forEach(function(f) { + it('should add the outputs for ' + f.txid + ' correctly', function() { + var tx = new Transaction() - it('returns true for valid signature', function() { - var key = ECKey.fromWIF('L44f7zxJ5Zw4EK9HZtyAnzCYz2vcZ5wiJf9AuwhJakiV4xVkxBeb') - var script = prevTx.outs[0].script - var sig = new Buffer(validTx.ins[0].script.chunks[0]) + f.raw.outs.forEach(function(txOut, i) { + var j = tx.addOutput(txOut.script, txOut.value) - assert.equal(validTx.validateInput(0, script, key.pub, sig), true) + assert.equal(i, j) + }) + + assert.deepEqual(tx.outs, f.raw.outs) }) }) }) - describe('signInput', function() { + describe('clone', function() { + fixtures.valid.forEach(function(f) { + var expected = Transaction.fromHex(f.hex) + var actual = expected.clone() + + it('should have value equality', function() { + assert.deepEqual(actual, expected) + }) + + it('should not have reference equality', function() { + assert.notEqual(actual, expected) + }) + }) + }) + + describe('getId', function() { + fixtures.valid.forEach(function(f) { + it('should return the txid for ' + f.txid, function() { + var tx = Transaction.fromHex(f.hex) + var actual = tx.getId() + + assert.equal(actual, f.txid) + }) + }) + }) + + describe('getHash', function() { + fixtures.valid.forEach(function(f) { + it('should return the hash for ' + f.txid, function() { + var tx = Transaction.fromHex(f.hex) + var actual = tx.getHash().toString('hex') + + assert.equal(actual, f.hash) + }) + }) + }) + + // TODO: + // hashForSignature: [Function], + + // FIXME: could be better + describe('signInput/validateInput', function() { it('works for multi-sig redeem script', function() { var tx = new Transaction() tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0) @@ -227,32 +253,4 @@ describe('Transaction', function() { assert.equal(tx.toHex(), expected) }) }) - - describe('getId', function() { - it('returns the expected txid', function() { - var tx = new Transaction() - tx.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0) - tx.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1) - - assert.equal(tx.getId(), '7c3275f1212fd1a2add614f47a1f1f7b6d9570a97cb88e0e2664ab1752976e9f') - }) - }) - - describe('clone', function() { - it('creates a new object', function() { - var txA = new Transaction() - txA.addInput('d6f72aab8ff86ff6289842a0424319bf2ddba85dc7c52757912297f948286389', 0) - txA.addOutput('mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', 1000) - - var txB = txA.clone() - - // Enforce value equality - assert.deepEqual(txA, txB) - - // Enforce reference inequality - assert.notEqual(txA.ins[0], txB.ins[0]) - assert.notEqual(txA.outs[0], txB.outs[0]) - }) - }) }) -