UNPKG

flocore-lib

Version:

A pure and powerful JavaScript Florincoin library.

946 lines (876 loc) 101 kB
'use strict'; /* jshint unused: false */ /* jshint latedef: false */ var should = require('chai').should(); var expect = require('chai').expect; var _ = require('lodash'); var sinon = require('sinon'); var fs = require('fs'); var flocore = require('../..'); var BN = flocore.crypto.BN; var Transaction = flocore.Transaction; var Input = flocore.Transaction.Input; var Output = flocore.Transaction.Output; var PrivateKey = flocore.PrivateKey; var Script = flocore.Script; var Interpreter = flocore.Script.Interpreter; var Address = flocore.Address; var Networks = flocore.Networks; var Opcode = flocore.Opcode; var errors = flocore.errors; var transactionVector = require('../data/tx_creation'); // Testnet Flo Transaction var segwitTXID = "a001bbf5f456e2678d54065385de5082a62d3dd061676540f3f8af774f7560a3"; var segwitWitnessHash = "fc22bf04bd1f49a42675ed1b125fa5a871f5ab70e32dcf3480a38141b748fc46"; var segwitTransactionHex = "0200000000012f0a7ba7b4bcecab54734a87aba325f2258f822804bc3cf55d59d4e6cce876d1dc010000006b4830450221008b9d036483e2bd0117eb172e5fc29185eaa458a6a4f0e27f97c2d5666761b6af02207301c36df9408f605c805495f3107c05bbac16e978ce53a80fda34ce88004f77012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff10ffa93808647ba0ebb5cf6c489acf86ab74d81ff7c25438591209d4e6463e83030000006b483045022100bf838a458a5e0a878a792c28fa9378e1b77f6a0b1499d8b864018bbdf8b440d40220129164c5a857245bad2dda25e28b25b386acd0ea64d1548b48984319fff26d46012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff1a4bc4e151ea5c34fe7ca509986cb12f890b572efaf447d8721191c967a06c70010000006a47304402204c26d201ece317005998ec129314418a363ebbb51446714ca9b692879b2a1cb6022015570dbf0b576ce8f3ff8a0422a9c2f91619fa3ca1e5f3cbb236368efa4a6278012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff1af1166c31bcc86767021e226323c3d3f4676f016dfbbe6efc759421034ea327010000006b483045022100c1b47b442ae19421d813179921694981bc21174c203276ef4d9f682e28547570022077b3f366621f9e2eba44118dd769f072ded98b60630f772c486a13c07827d515012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff1c8cc1cf0ffe8a961c771eb671df1404b08b5d092dfc18d391e46f08b4b1e2ce020000006a4730440220285b1a1920275c389f8bd538360464a83d02992782a9fc5473a88d69d4f3f276022039523994f96adb0379458fc75cac16ca43e09443ab0fa259d1318d24e1a71ac9012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff34c3d8bb721fd4b094f73964c77eacab6c8cf712c88584cb2e16d0c2379e9430020000006a4730440220032f24c3a82f1caf566bffb2ed6caf2b8a126e60291f9482a1accf9422ae3105022034edcbae9ed5975cd678c61515942e5ca4eac1db91d1198f91b96f214f03674e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff44d033d5b7e74d6da2199656c7c281baf11e8a245a5f6182ee7fc5789fdad96b030000006a4730440220567fdf527445b231ec5827155abcfa609d34159f7d96bc4da73c7401cf428f7f02202094abaa27040bd01518bfc2e5c4527edeb72d10b74de0adf620b43467ba22a4012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff4de1169a6bc11044604ed2ccef1b53a8b3889c7dff0f87977014dc5f59db9301020000006a4730440220205b719ae63124e4189f967acfb9620d39fef3a35824b718adfa793fbf09b1b602203462619e416e7c6c7aa5781f27e2ed20078698f04574d6a12d6224cfd69eecca012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff52c8cc2667f711591e5c0931d508314c0152cbc7227dcb53bea5a155683e2f95020000006a4730440220234e3ea302f7b79afe75b0c402cc92ef464231488417eb47090aa143b729221c022046f5eaf0ef095d8cf0955941e0dd864dbaafb14f0d931be1625f92cc4380318d012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff57fd25b88882d83870a2e4728717d6a1ffbc5d6dde3af9de2ccfc80b1ecc939b010000006a47304402200f0d6f30ed48826afa1c1e90f37a748c94bfa9c23acd97614cbfaba997eb0f5d02205d60ece6901b145ac74404308c6e8b7990a1e097d4f3ae78a30f7315122418db01210208ca8f9233ada056b65b8dc23641e2fa5968571ac582a5f869d00d3bf8a84541feffffff6149ebbab2c932c0cae14170235e06f8c216208279c1a209733482e877ff930a010000006a473044022007be5b9b28dff5617c64d51239e7ac39ed8acc8a06731fd474302947a894c66c02203d51edb433e2747123de7e0ef6175335a879100514c599979374fac4a69cb944012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff666b74ed41f1df24a4fa0868058f5d4c55dd3bee8b2131d0b2cd1f71dbc7366c020000006a473044022029915a3218cd249ded5691ec4ec68c544fbd72abf38ad0561d48e65a45b984370220074ddf61bdbf00a39d4d2237c6ec2c5faaf2756e9cc7a6630052d44e29b40f67012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff6df5260159a2367caa41a23e1fd49a59c197839a7d602c782c6174d7de1ac12e030000006b483045022100d5711eb8c24a654eb1eaa3199d3e16723c14be92b96c0687f6945c1a132e4ced022034971c74793b7d262c5481f0c9798a629df43df52cc93b5983896818c2f16323012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff72c12e260417b87e7ddec5458e789847c76d0320e42082b9af544395c496b2c7020000006b483045022100c163fa8df0edf21ab5b8f49d172b8b07b8115b6ea5f4bfc6c03a2d7661e587ef022014a785a590253db283a02df842f7749080be3faef1549222bd05af6b13f28b7e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff78b0e4620398dce034d704b7f3db06c1453edd61cf0d7a3703b05607d86483f1030000006b483045022100e59e0b09d8f580898c8a34e1d7c6c956537fe16f1da0a421ee8dea72e4366f9c02200f2950f913f481e81d4a916709c118d6f5bf3501d391fbba57abc59b9c1edc74012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff8a03d7647874b6f845965ef2036f47d6bbddbb62a896370feba8c62983b0cb4f000000006b483045022100fd2bcaf55f237c208f0490c936231a5b14ebeac63e4c8769e074754d00231353022062006093ab7c2c799b8245fa4de8f5c164926da35e1cd32b6a4e5949f1b6edff012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff91631c42763b570d7f7aa1b7d27f60e693710892fabe9198c82804a953fe70dc020000006b4830450221009cc144b1ce8c6c25f1287c169cad086996e6e1595d087c0808eb534a081598a90220474b49cf16757ef035550460a95024e3622072f7ed7b9cdbbf4157a8d7652724012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff93e980535669f571bf43e6f42cce912ff7e70b56265cdc062726a6f2cb3dc084020000006a47304402202d445a066c7feff2f825bb7b326b9a09cee8e6590ff7fcfc8136f7cb409fa86f02204c82178a12fbd588daab8b13a4051adff9948f51bea8b09d4a38e64ad69b15f4012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff9aa74db1df731a5bdc35c2ed176303d80f4be1c88e89e68aacbd454870d77d51030000006a4730440220283111fe0dcf871ae939aa5ac806daf0537295ba542aa38d000dc70d1a82279902202f3120b3232b74514b0bc4cdc396b9a85e4fb5fb834babef7f30a7a542304f0d012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff9cc365224f84bc0eb0797c09b5f16e6140e18413a4d69b72c7050e9806cd5e0a020000006a4730440220433810e3c2fc4567b54daf71b117f583d968d34c495d57958e785ad5a55f8d050220066c83169a417657fc3addbbb0db81611fa9cd9fc6f36c005195d0fc2a34b94e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff9d3d9a7eb2a874b79fb522ee5707cc653faa6e04b806fba172aa0dda9e807785020000006a47304402201382a9f32825098768fac5bbf13a99b507fcf85e2da78a9f2b2a3b212fa61f210220543fff140a8b3f4c6f60d852cb61a63bab133751d03c044af8322a9cdece9b2d012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffa1a1f2f4685dfd2d74c7215d2ac825b7c5660a19775a0bbcf65ebbab7b73768d020000006b4830450221008f53f3f141c87e260fc8b201330af09aaed86fd683104e2cf10ee70d64d4c2ac02205ad175506358ae6d615fd87934d40cee88282ecb009e4c1ccad92e95c959e003012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffa49ee406b2202beb8bcdfacb17d3d244065b0dfc276ee1f2fb5d254d26af3b770000000017160014f3aae9630c650c54e82c539b792a895a9df1ccdbfeffffffa78384a3babf5a6632641ec35a73ff17373bafd4a87396fa406aef4be8558380030000006b483045022100bc8b9c02a707790cf01efae8d9f0f2cc36f1d6661208ecd29b8fb0f4e96b66b202202b547fba674cc4460c41512b3bfea6b760cd091d05175dd092eacef41565f77e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffa8b1cb744a5b9e59d708046ee5c3f41bdb2f22bd314df4637f72cb6237454ac3030000006a47304402205bd3443e20dcbc9ae0b3c0c1399d2f6378a360729698674cf55f46d5f6696c0e0220297c7a9917847f380f75ca595394ba72d49bb8afb9e574adce86e5683e96dce3012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffabc536792b6c313baec01f8a10167c45cf5b5377054c30eac07c1dbbd3052e17030000006b483045022100e11090f7f39d5ecd3a9338cb48155ab43d6abcc16d04ca6c1cb88a675be2c0d602205e2c88196cb1b66a5e6604b181ef7540d113a3a292435782336bf50a4d3bfe49012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb174f4bde7a7219278c8f7e20af307b352535f78834acd7f13927a4cbf434a9d030000006b483045022100d15bc603d4e43b6d25d43dcd4393566a101735ab2d5128065e92bee77429162f02200f0c4e4ed70a6626e312e3ad1849669e95ef93c6db3c8fad50ae5103ffa0548a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb1f8cd44e462a07bd060eb933065ed9914bb5e3c9c14e8ebfa7ca4b141a9b09f030000006a47304402202a3117f82d124160cb184a02a713856b37a08f7b21581380571204b935eb8211022062b2cf9cb593131ecca756d6e64d19603eb031e5b61ae2bba79ce049077db182012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb2d037a667c95a308358842a40617b26694c550073a0e76513b2f85e27bb2101010000006a47304402202b80561d473bbbd07210bdf6c6a59b4076ab032bd22c5ebd8299e8f687b9ce6f02201b0404c1411b0c7f2f30a4a5f26aadb0b4084e98d9dce907334f9255a74eaa4f012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb83f1cb0dec621e041a8df9d023bb0b1abf1186f5be83ada43af05bf6c1b1e10010000006a47304402203df9e73f0d02eb7389fabc194cd18181586d96d331de92cd90562652c0e3c79102202214f108badb47f21426ac0c3544dabcfe84e59310fa7196fc1ed10094bcbf94012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffbb07d5153c6c76dd58add91f3d6a8ef44689fb7caedbedc48175c73d13297fa7030000006b483045022100e532ace6422be5d7fcba55e4c7953fbbea2901e2c4fd1bf8000fe93010141cb50220079f77b1e1ba4066246819a0758e21b984dc48bedc9cb46b184ecb17570d6622012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffc0664186cf0365a26c72c81e7645115749e35114b5d1a0d4d38b24f733dbc97e030000006b483045022100f1b14ef60c0eae0c07d3ba91741a9145fbed192371cfc2d240f8c2d96489840102207d06ec6eed9b41791e810078ec27b534e1d7cb76eca3bfa0f3f3fc9ae8a28243012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffc155e20a588a06162d2621d5da4e11295f07bf0602f423d70517531817fd998c030000006b483045022100f04df0a562b7e6cfb6fbb69e9391361256870b0074edb4f0f4c2058b8db04c4c0220286770dd104ca532ea22e9e3d7d3208e04e2a3a676a07fca18a1bfe566e7093a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffc20264c340fed0688706a33a07c11a5db15a0c17421472723da56239bc5bc368010000006a4730440220287d3eac747df632bd7ad7b341e44058a11550f5bce94fa4f0b9b7da3b8399fd022068ac78a38f845446270be182e7319492f93a30aea80b815dd02f334ad5f38d0f012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffcf5cac84ef07520c391d19dd7b5faddba32a09f1fc1290a5d4da2c4ca06726e9020000006b483045022100f79aac33d70368a4b16925568aa6a9e648ec59792f34a9eb5b1b53cc0f85a93f0220641e4bdd3e7592ecc966d82e596b3f28a11851b0caf91688e31858ce7fcf9e79012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffd62abab7c61549818aacff0a16797f01bfcc3c9fadcfb80a77b325e41da30c2b010000006a473044022047642203baa9e3b45f3679998b7489304a8b5da5908519fbc160cd8e998da7bb02200420e0f03d2ade718be661c9e28f299fa72d6dd6e21b506b6df1acea459b7be1012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffd90808ca51027440dbc7438788f824c65488aae0544034386f4053e19d77d4b1010000006a473044022077552ae0512a06bcbbbecb0a5af2692f1c8c4d812993fd756c5dc6418df49fad02204e14ff51bbe914608bdb924d898d476863fe480e707626ca164bef33a2985eae012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffda233186bf4c4f43262f918f6b52f538d7aaba3317607e10b87e8957ea9bf883020000006a473044022049dbc8b8fba8c461a74b10d99cb793fb6b660cf2d6c271d99a961410e50ed26d0220457e9d40f83e56a9440472ceea04a191b38e666ea615bba86fae3ed62f8ddcd6012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffdb7062d78170fdc669b46eb325a680923e0008ff83f32592f5c3dcd47b427601030000006a473044022065faa5c120e8e3d78d6e79d37ea72890096819a426e2b04508a1dc52935c8b1c0220246fe63b7c5d8a7b8eb4c6f422709a1fa977c3b00bb724bd699e0137e8b3cd85012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffe03aca08fcc1b108b6dad891632aeb8cd045961f4aa68152eb7ecc1aedc201ab000000006a47304402206db7242b08f02d65a8b2b2bf0fff2963f1b14ca72274118bef9c416bc5b4a70f0220342323154080c540485c70124da6258d8aef9a363fc0f3bc34916c59ed12ea8c0121038f1ef341be736541e59fb19d42219a7eae96ac14cac282bd27cfe9631aa9b08cfeffffffe46f327e312879073016bb2b29f077c1bd7ec83545ba5267ae2ff3466aab7cb9020000006b483045022100bb3791a4815b0cb09a836030685feb9ca10597685cebf6d5a81d0cfbfc8b574002202900c99adfdd12a19dc21ab7a28a72ed06e87af93ddfd38af94ec71d9f4d4eaf012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffe8a399ebeb841744480678c24d805edc314f927a94af60b31b1f3e9e1aa07b52020000006a47304402202002c939cc0099ab23553673a56519e7164b894b500b1825ad8821af6416a0f0022041b26590575924b830bea0b851db17614282a3444cd1febf0852b80a34e83238012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffeefdd139ab656d5822d93ec8304693618f42eed82cbb3f8803745988b4988538020000006b483045022100976efe634bbf5771fc817766c4e3c9d7a5b9e7fa463ca6f550c143cc4a7efa9a02200a52ef2c29a9efa9af7929485f5ccd656924be3883f3fc43c701edcae84dd97a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffff04b6e6ee4e15728be511f5bce03fe42964d4c053271b4528a5368f01c5dfc5b010000006a47304402200850873d023e6ac0d8b0138cde597088061bcdb3816c6cc1286ad94e3928aaf102204e9aec6a3664f582275ed33846a59896bf7247ef5df0306d0e7ed5194ce457c7012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffff517d99ffdbd3bd448103feffcf6a9b9990e0b3d8ab3d30662555367602fcf06010000006a47304402207d5799c70a602a5925a17737ba9c09dd8f09bf640e2e634060336bffd936f94e0220230aff2a3ebe7cca658df4d7c28a46fc809aad50fcb832ae35ccfcbb475087c1012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffff9d6e5dc3d24f0cb8095d67bb8dd99ead644daeb39b9558b2fdcaf8fd0f69268020000006a473044022063905b5659d910032d42e129c26c80c8846fe0d1cf373d37880a2f4dadc9402c022013934744935d313c276d624d24a9ddff652b72f840456b4203a79e1f3d5c014a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffffc8e6e83c94cb3572780bdd6ca7600064eced923053be5401ab416e43d6aa9b5000000006a47304402203b6edfa764998485614ff1762c946baf35ae9c05bde24dd10bd362131a2d183102204f3d0e2622bc8f5e40a940d78b4ee86fc1d530975864b195af9190653342ec40012103a0915d65b5cd997e95f669af096d6ce6e3a1ac9001bdced574fd637d86be0cfefeffffff026a640d00000000001976a9142408f30fa7a95a0b3d7e1d9cfc1351d9197271df88ac005039278c0400001976a914ef0ffd96cfbf83354760f399e5a45160baeb27f788ac0000000000000000000000000000000000000000000002483045022100e63491bc48fb7d3ab6c9377c94650014031d41145f4d0b9cb3da653916dcb43602203e8afbb9c3a624851b6b224f602be4327dd0af3325c926b90f20ec42acaf54b3012103b0c4aaa3eb2fcd3ffb3361d8a1d9ea7ffba5fd83915f8835261d160513cf61bb00000000000000000000000000000000000000000000000080aa000000"; describe('Transaction', function() { it('should serialize and deserialize correctly a given transaction', function() { var transaction = new Transaction(tx_1_hex); transaction.uncheckedSerialize().should.equal(tx_1_hex); }); it('should parse the version as a signed integer', function () { var transaction = Transaction('ffffffff0000ffffffff') transaction.version.should.equal(-1); transaction.nLockTime.should.equal(0xffffffff); }); it('fails if an invalid parameter is passed to constructor', function() { expect(function() { return new Transaction(1); }).to.throw(errors.InvalidArgument); }); var testScript = 'OP_DUP OP_HASH160 7dcd23de5408d28edf0f45da2b8f7c57a398df31 OP_EQUALVERIFY OP_CHECKSIG'; var testScriptHex = '76a9147dcd23de5408d28edf0f45da2b8f7c57a398df3188ac'; var testPrevTx = 'ed3fa66cbaef9333135a42d6deb7c8a5eb3675aac028b6a943ab4574618a70d1'; var testAmount = 1 * 1e8; var testTransaction = new Transaction() .from({ 'txId': testPrevTx, 'outputIndex': 0, 'script': testScript, 'satoshis': testAmount }) .to('oYVNuMx5VUrVkkZJv6jPWxoTD3r4w7bnMf', testAmount - 10000); it('can serialize to a plain javascript object', function() { var object = testTransaction.toObject(); object.inputs[0].output.satoshis.should.equal(testAmount); object.inputs[0].output.script.should.equal(testScriptHex); object.inputs[0].prevTxId.should.equal(testPrevTx); object.inputs[0].outputIndex.should.equal(0); object.outputs[0].satoshis.should.equal(testAmount - 10000); }); it('will not accept NaN as an amount', function() { (function() { var stringTx = new Transaction().to('oYVNuMx5VUrVkkZJv6jPWxoTD3r4w7bnMf', NaN); }).should.throw('Amount is expected to be a positive integer'); }); it('returns the fee correctly', function() { testTransaction.getFee().should.equal(10000); }); it('will return zero as the fee for a coinbase', function() { // block #2: 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098 var coinbaseTransaction = new Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000'); coinbaseTransaction.getFee().should.equal(0); }); it('serialize to Object roundtrip', function() { var a = testTransaction.toObject(); var newTransaction = new Transaction(a); var b = newTransaction.toObject(); a.should.deep.equal(b); }); it('toObject/fromObject with signatures and custom fee', function() { var tx = new Transaction() .from(simpleUtxoWith100000Satoshis) .to([{address: toAddress, satoshis: 50000}]) .fee(15000) .change(changeAddress) .sign(privateKey); var txData = JSON.stringify(tx); var tx2 = new Transaction(JSON.parse(txData)); var txData2 = JSON.stringify(tx2); txData.should.equal(txData2); }); it('toObject/fromObject with p2sh signatures and custom fee', function() { var tx = new Transaction() .from(p2shUtxoWith1FLO, [p2shPublicKey1, p2shPublicKey2, p2shPublicKey3], 2) .to([{address: toAddress, satoshis: 50000}]) .fee(15000) .change(changeAddress) .sign(p2shPrivateKey1) .sign(p2shPrivateKey2); var txData = JSON.stringify(tx); var tx2 = new Transaction(JSON.parse(txData)); var tx2Data = JSON.stringify(tx2); txData.should.equal(tx2Data); }); it('fromObject with pay-to-public-key previous outputs', function() { var tx = flocore.Transaction({ hash: '132856bf03d6415562a556437d22ac63c37a4595fd986c796eb8e02dc031aa25', version: 1, inputs: [ { prevTxId: 'e30ac3db24ef28500f023775d8eb06ad8a26241690080260308208a4020012a4', outputIndex: 0, sequenceNumber: 4294967294, script: '473044022024dbcf41ccd4f3fe325bebb7a87d0bf359eefa03826482008e0fe7795586ad440220676f5f211ebbc311cfa631f14a8223a343cbadc6fa97d6d17f8d2531308b533201', scriptString: '71 0x3044022024dbcf41ccd4f3fe325bebb7a87d0bf359eefa03826482008e0fe7795586ad440220676f5f211ebbc311cfa631f14a8223a343cbadc6fa97d6d17f8d2531308b533201', output: { satoshis: 5000000000, script: '2103b1c65d65f1ff3fe145a4ede692460ae0606671d04e8449e99dd11c66ab55a7feac' } } ], outputs: [ { satoshis: 3999999040, script: '76a914fa1e0abfb8d26e494375f47e04b4883c44dd44d988ac' }, { satoshis: 1000000000, script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac' } ], nLockTime: 139 }); tx.inputs[0].should.be.instanceof(flocore.Transaction.Input.PublicKey); tx.inputs[0].output.satoshis.should.equal(5000000000); tx.inputs[0].output.script.toHex().should.equal('2103b1c65d65f1ff3fe145a4ede692460ae0606671d04e8449e99dd11c66ab55a7feac'); }); it('constructor returns a shallow copy of another transaction', function() { var transaction = new Transaction(tx_1_hex); var copy = new Transaction(transaction); copy.uncheckedSerialize().should.equal(transaction.uncheckedSerialize()); }); it('should display correctly in console', function() { var transaction = new Transaction(tx_1_hex); transaction.inspect().should.equal('<Transaction: ' + tx_1_hex + '>'); }); it('standard hash of transaction should be decoded correctly', function() { var transaction = new Transaction(tx_1_hex); transaction.id.should.equal(tx_1_id); }); it('serializes an empty transaction', function() { var transaction = new Transaction(); transaction.uncheckedSerialize().should.equal(tx_empty_hex); }); it('serializes and deserializes correctly', function() { var transaction = new Transaction(tx_1_hex); transaction.uncheckedSerialize().should.equal(tx_1_hex); }); describe('transaction creation test vector', function() { this.timeout(5000); var index = 0; transactionVector.forEach(function(vector) { index++; it('case ' + index, function() { var i = 0; var transaction = new Transaction(); while (i < vector.length) { var command = vector[i]; var args = vector[i + 1]; if (command === 'serialize') { transaction.serialize().should.equal(args); } else { transaction[command].apply(transaction, args); } i += 2; } }); }); }); // TODO: Migrate this into a test for inputs var fromAddress = 'oPoBiZJxdHtNYVNhNTspRE8a4id6ytMKzY'; var simpleUtxoWith100000Satoshis = { address: fromAddress, txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f', outputIndex: 0, script: Script.buildPublicKeyHashOut(fromAddress).toString(), satoshis: 100000 }; var simpleUtxoWith1000000Satoshis = { address: fromAddress, txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f', outputIndex: 0, script: Script.buildPublicKeyHashOut(fromAddress).toString(), satoshis: 1000000 }; var anyoneCanSpendUTXO = JSON.parse(JSON.stringify(simpleUtxoWith100000Satoshis)); anyoneCanSpendUTXO.script = new Script().add('OP_TRUE'); var toAddress = 'oYVNuMx5VUrVkkZJv6jPWxoTD3r4w7bnMf'; var changeAddress = 'oPoBiZJxdHtNYVNhNTspRE8a4id6ytMKzY'; var changeAddressP2SH = 'oPoBiZJxdHtNYVNhNTspRE8a4id6ytMKzY'; var privateKey = 'cSY2jbTDyPhmEx94TD72NwuHQ4J4pxzMEjj8G1Nc7c1eptCnevza'; var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976'; var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890'; var public1 = new PrivateKey(private1).publicKey; var public2 = new PrivateKey(private2).publicKey; var simpleUtxoWith1FLO = { address: fromAddress, txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f', outputIndex: 1, script: Script.buildPublicKeyHashOut(fromAddress).toString(), satoshis: 1e8 }; var tenth = 1e7; var fourth = 25e6; var half = 5e7; var p2shPrivateKey1 = PrivateKey.fromWIF('cWA4VXpGArqr2Y2Z4KvhGx8nNEjAgzycaqS6ZGrWFKCYBdtL2FV8'); var p2shPublicKey1 = p2shPrivateKey1.toPublicKey(); var p2shPrivateKey2 = PrivateKey.fromWIF('cRRfp177BL8fjhSsYBDymSjb5rG1p8F1AD51vrwhoRiCNU6ur59r'); var p2shPublicKey2 = p2shPrivateKey2.toPublicKey(); var p2shPrivateKey3 = PrivateKey.fromWIF('cTKLF5W5SLaGTTMs7bWQ73nR4Zs118W1HZA57CWzW16A7KPqoZTv'); var p2shPublicKey3 = p2shPrivateKey3.toPublicKey(); var p2shAddress = Address.createMultisig([ p2shPublicKey1, p2shPublicKey2, p2shPublicKey3 ], 2, 'testnet'); var p2shUtxoWith1FLO = { address: p2shAddress.toString(), txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f', outputIndex: 0, script: Script(p2shAddress).toString(), satoshis: 1e8 }; describe('adding inputs', function() { it('adds just once one utxo', function() { var tx = new Transaction(); tx.from(simpleUtxoWith1FLO); tx.from(simpleUtxoWith1FLO); tx.inputs.length.should.equal(1); }); describe('isFullySigned', function() { it('works for normal p2pkh', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to([{address: toAddress, satoshis: 50000}]) .change(changeAddress) .sign(privateKey); transaction.isFullySigned().should.equal(true); }); it('fails when Inputs are not subclassed and isFullySigned is called', function() { var tx = new Transaction(tx_1_hex); expect(function() { return tx.isFullySigned(); }).to.throw(errors.Transaction.UnableToVerifySignature); }); it('fails when Inputs are not subclassed and verifySignature is called', function() { var tx = new Transaction(tx_1_hex); expect(function() { return tx.isValidSignature({ inputIndex: 0 }); }).to.throw(errors.Transaction.UnableToVerifySignature); }); it('passes result of input.isValidSignature', function() { var tx = new Transaction(tx_1_hex); tx.from(simpleUtxoWith1FLO); tx.inputs[0].isValidSignature = sinon.stub().returns(true); var sig = { inputIndex: 0 }; tx.isValidSignature(sig).should.equal(true); }); }); }); describe('change address', function() { it('can calculate simply the output amount', function() { var transaction = new Transaction() .from(simpleUtxoWith1000000Satoshis) .to(toAddress, 500000) .change(changeAddress) .sign(privateKey); transaction.outputs.length.should.equal(2); transaction.outputs[1].satoshis.should.equal(400000); transaction.outputs[1].script.toString() .should.equal(Script.fromAddress(changeAddress).toString()); var actual = transaction.getChangeOutput().script.toString(); var expected = Script.fromAddress(changeAddress).toString(); actual.should.equal(expected); }); it('accepts a P2SH address for change', function() { var transaction = new Transaction() .from(simpleUtxoWith1000000Satoshis) .to(toAddress, 500000) .change(changeAddressP2SH) .sign(privateKey); transaction.outputs.length.should.equal(2); transaction.outputs[1].script.isScriptHashOut().should.equal(true); }); it('can recalculate the change amount', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 50000) .change(changeAddress) .fee(0) .sign(privateKey); transaction.getChangeOutput().satoshis.should.equal(50000); transaction = transaction .to(toAddress, 20000) .sign(privateKey); transaction.outputs.length.should.equal(3); transaction.outputs[2].satoshis.should.equal(30000); transaction.outputs[2].script.toString() .should.equal(Script.fromAddress(changeAddress).toString()); }); it('adds no fee if no change is available', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 99000) .sign(privateKey); transaction.outputs.length.should.equal(1); }); it('adds no fee if no money is available', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 100000) .change(changeAddress) .sign(privateKey); transaction.outputs.length.should.equal(1); }); it('fee can be set up manually', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 80000) .fee(10000) .change(changeAddress) .sign(privateKey); transaction.outputs.length.should.equal(2); transaction.outputs[1].satoshis.should.equal(10000); }); it('fee per kb can be set up manually', function() { var inputs = _.map(_.range(10), function(i) { var utxo = _.clone(simpleUtxoWith100000Satoshis); utxo.outputIndex = i; return utxo; }); var transaction = new Transaction() .from(inputs) .to(toAddress, 950000) .feePerKb(8000) .change(changeAddress) .sign(privateKey); transaction._estimateSize().should.be.within(1000, 1999); transaction.outputs.length.should.equal(2); transaction.outputs[1].satoshis.should.equal(34000); }); it('if satoshis are invalid', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 99999) .change(changeAddress) .sign(privateKey); transaction.outputs[0]._satoshis = 100; transaction.outputs[0]._satoshisBN = new BN(101, 10); expect(function() { return transaction.serialize(); }).to.throw(errors.Transaction.InvalidSatoshis); }); it('if fee is too small, fail serialization', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 99999) .change(changeAddress) .sign(privateKey); expect(function() { return transaction.serialize(); }).to.throw(errors.Transaction.FeeError.TooSmall); }); it('on second call to sign, change is not recalculated', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 100000) .change(changeAddress) .sign(privateKey) .sign(privateKey); transaction.outputs.length.should.equal(1); }); it('getFee() returns the difference between inputs and outputs if no change address set', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 1000); transaction.getFee().should.equal(99000); }); }); describe('serialization', function() { it('stores the change address correctly', function() { var serialized = new Transaction() .change(changeAddress) .toObject(); var deserialized = new Transaction(serialized); expect(deserialized._changeScript.toString()).to.equal(Script.fromAddress(changeAddress).toString()); expect(deserialized.getChangeOutput()).to.equal(null); }); it('can avoid checked serialize', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .to(fromAddress, 1); expect(function() { return transaction.serialize(); }).to.throw(); expect(function() { return transaction.serialize(true); }).to.not.throw(); }); it('stores the fee set by the user', function() { var fee = 1000000; var serialized = new Transaction() .fee(fee) .toObject(); var deserialized = new Transaction(serialized); expect(deserialized._fee).to.equal(fee); }); }); describe('checked serialize', function() { it('fails if no change address was set', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .to(toAddress, 1); expect(function() { return transaction.serialize(); }).to.throw(errors.Transaction.ChangeAddressMissing); }); it('fails if a high fee was set', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .change(changeAddress) .fee(50000000) .to(toAddress, 40000000); expect(function() { return transaction.serialize(); }).to.throw(errors.Transaction.FeeError.TooLarge); }); it('fails if a dust output is created', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .to(toAddress, 545) .change(changeAddress) .sign(privateKey); expect(function() { return transaction.serialize(); }).to.throw(errors.Transaction.DustOutputs); }); it('doesn\'t fail if a dust output is not dust', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .to(toAddress, 546) .change(changeAddress) .sign(privateKey); expect(function() { return transaction.serialize(); }).to.not.throw(errors.Transaction.DustOutputs); }); it('doesn\'t fail if a dust output is an op_return', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .addData('not dust!') .change(changeAddress) .sign(privateKey); expect(function() { return transaction.serialize(); }).to.not.throw(errors.Transaction.DustOutputs); }); it('fails when outputs and fee don\'t add to total input', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .to(toAddress, 99900000) .fee(99999) .sign(privateKey); expect(function() { return transaction.serialize(); }).to.throw(errors.Transaction.FeeError.Different); }); it('checks output amount before fee errors', function() { var transaction = new Transaction(); transaction.from(simpleUtxoWith1FLO); transaction .to(toAddress, 10000000000000) .change(changeAddress) .fee(5); expect(function() { return transaction.serialize(); }).to.throw(errors.Transaction.InvalidOutputAmountSum); }); it('will throw fee error with disableMoreOutputThanInput enabled (but not triggered)', function() { var transaction = new Transaction(); transaction.from(simpleUtxoWith1FLO); transaction .to(toAddress, 84000000) .change(changeAddress) .fee(16000000); expect(function() { return transaction.serialize({ disableMoreOutputThanInput: true }); }).to.throw(errors.Transaction.FeeError.TooLarge); }); describe('skipping checks', function() { var buildSkipTest = function(builder, check, expectedError) { return function() { var transaction = new Transaction(); transaction.from(simpleUtxoWith1FLO); builder(transaction); var options = {}; options[check] = true; expect(function() { return transaction.serialize(options); }).not.to.throw(); expect(function() { return transaction.serialize(); }).to.throw(expectedError); }; }; it('can skip the check for too much fee', buildSkipTest( function(transaction) { return transaction .fee(50000000) .change(changeAddress) .sign(privateKey); }, 'disableLargeFees', errors.Transaction.FeeError.TooLarge )); it('can skip the check for a fee that is too small', buildSkipTest( function(transaction) { return transaction .fee(1) .change(changeAddress) .sign(privateKey); }, 'disableSmallFees', errors.Transaction.FeeError.TooSmall )); it('can skip the check that prevents dust outputs', buildSkipTest( function(transaction) { return transaction .to(toAddress, 100) .change(changeAddress) .sign(privateKey); }, 'disableDustOutputs', errors.Transaction.DustOutputs )); it('can skip the check that prevents unsigned outputs', buildSkipTest( function(transaction) { return transaction .to(toAddress, 10000) .change(changeAddress); }, 'disableIsFullySigned', errors.Transaction.MissingSignatures )); it('can skip the check that avoids spending more florincoins than the inputs for a transaction', buildSkipTest( function(transaction) { return transaction .to(toAddress, 10000000000000) .change(changeAddress) .sign(privateKey); }, 'disableMoreOutputThanInput', errors.Transaction.InvalidOutputAmountSum )); }); }); describe('#verify', function() { it('not if _satoshis and _satoshisBN have different values', function() { var tx = new Transaction() .from({ 'txId': testPrevTx, 'outputIndex': 0, 'script': testScript, 'satoshis': testAmount }) .to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000); tx.outputs[0]._satoshis = 100; tx.outputs[0]._satoshisBN = new BN('fffffffffffffff', 16); var verify = tx.verify(); verify.should.equal('transaction txout 0 satoshis is invalid'); }); it('not if _satoshis is negative', function() { var tx = new Transaction() .from({ 'txId': testPrevTx, 'outputIndex': 0, 'script': testScript, 'satoshis': testAmount }) .to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000); tx.outputs[0]._satoshis = -100; tx.outputs[0]._satoshisBN = new BN(-100, 10); var verify = tx.verify(); verify.should.equal('transaction txout 0 satoshis is invalid'); }); it('not if transaction is greater than max block size', function() { var tx = new Transaction() .from({ 'txId': testPrevTx, 'outputIndex': 0, 'script': testScript, 'satoshis': testAmount }) .to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000); tx.toBuffer = sinon.stub().returns({ length: 10000000 }); var verify = tx.verify(); verify.should.equal('transaction over the maximum block size'); }); it('not if has null input (and not coinbase)', function() { var tx = new Transaction() .from({ 'txId': testPrevTx, 'outputIndex': 0, 'script': testScript, 'satoshis': testAmount }) .to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000); tx.isCoinbase = sinon.stub().returns(false); tx.inputs[0].isNull = sinon.stub().returns(true); var verify = tx.verify(); verify.should.equal('transaction input 0 has null input'); }); }); describe('to and from JSON', function() { it('takes a string that is a valid JSON and deserializes from it', function() { var simple = new Transaction(); expect(new Transaction(simple.toJSON()).uncheckedSerialize()).to.equal(simple.uncheckedSerialize()); var complex = new Transaction() .from(simpleUtxoWith100000Satoshis) .to(toAddress, 50000) .change(changeAddress) .sign(privateKey); var cj = complex.toJSON(); var ctx = new Transaction(cj); expect(ctx.uncheckedSerialize()).to.equal(complex.uncheckedSerialize()); }); it('serializes the `change` information', function() { var transaction = new Transaction(); transaction.change(changeAddress); expect(transaction.toJSON().changeScript).to.equal(Script.fromAddress(changeAddress).toString()); expect(new Transaction(transaction.toJSON()).uncheckedSerialize()).to.equal(transaction.uncheckedSerialize()); }); it('serializes correctly p2sh multisig signed tx', function() { var t = new Transaction(tx2hex); expect(t.toString()).to.equal(tx2hex); var r = new Transaction(t); expect(r.toString()).to.equal(tx2hex); var j = new Transaction(t.toObject()); expect(j.toString()).to.equal(tx2hex); }); }); describe('serialization of inputs', function() { it('can serialize and deserialize a P2PKH input', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO); var deserialized = new Transaction(transaction.toObject()); expect(deserialized.inputs[0] instanceof Transaction.Input.PublicKeyHash).to.equal(true); }); it('can serialize and deserialize a P2SH input', function() { var transaction = new Transaction() .from({ txId: '0000', // Not relevant outputIndex: 0, script: Script.buildMultisigOut([public1, public2], 2).toScriptHashOut(), satoshis: 10000 }, [public1, public2], 2); var deserialized = new Transaction(transaction.toObject()); expect(deserialized.inputs[0] instanceof Transaction.Input.MultiSigScriptHash).to.equal(true); }); }); describe('checks on adding inputs', function() { var transaction = new Transaction(); it('fails if no output script is provided', function() { expect(function() { transaction.addInput(new Transaction.Input()); }).to.throw(errors.Transaction.NeedMoreInfo); }); it('fails if no satoshi amount is provided', function() { var input = new Transaction.Input(); expect(function() { transaction.addInput(input); }).to.throw(errors.Transaction.NeedMoreInfo); expect(function() { transaction.addInput(new Transaction.Input(), Script.empty()); }).to.throw(errors.Transaction.NeedMoreInfo); }); it('allows output and transaction to be feed as arguments', function() { expect(function() { transaction.addInput(new Transaction.Input(), Script.empty(), 0); }).to.not.throw(); }); it('does not allow a threshold number greater than the amount of public keys', function() { expect(function() { transaction = new Transaction(); return transaction.from({ txId: '0000000000000000000000000000000000000000000000000000000000000000', outputIndex: 0, script: Script(), satoshis: 10000 }, [], 1); }).to.throw('Number of required signatures must be greater than the number of public keys'); }); it('will add an empty script if not supplied', function() { transaction = new Transaction(); var outputScriptString = 'OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39' + 'cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de23' + '8d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL'; transaction.addInput(new Transaction.Input({ prevTxId: '0000000000000000000000000000000000000000000000000000000000000000', outputIndex: 0, script: new Script() }), outputScriptString, 10000); transaction.inputs[0].output.script.should.be.instanceof(flocore.Script); transaction.inputs[0].output.script.toString().should.equal(outputScriptString); }); }); describe('removeInput and removeOutput', function() { it('can remove an input by index', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO); transaction.inputs.length.should.equal(1); transaction.inputAmount.should.equal(simpleUtxoWith1FLO.satoshis); transaction.removeInput(0); transaction.inputs.length.should.equal(0); transaction.inputAmount.should.equal(0); }); it('can remove an input by transaction id', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO); transaction.inputs.length.should.equal(1); transaction.inputAmount.should.equal(simpleUtxoWith1FLO.satoshis); transaction.removeInput(simpleUtxoWith1FLO.txId, simpleUtxoWith1FLO.outputIndex); transaction.inputs.length.should.equal(0); transaction.inputAmount.should.equal(0); }); it('fails if the index provided is invalid', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO); expect(function() { transaction.removeInput(2); }).to.throw(errors.Transaction.InvalidIndex); }); it('an output can be removed by index', function() { var transaction = new Transaction() .to([ {address: toAddress, satoshis: 40000000}, {address: toAddress, satoshis: 40000000} ]) transaction.outputs.length.should.equal(2); transaction.outputAmount.should.equal(80000000); transaction.removeOutput(0); transaction.outputs.length.should.equal(1); transaction.outputAmount.should.equal(40000000); }); }); describe('handling the nLockTime', function() { var MILLIS_IN_SECOND = 1000; var timestamp = 1423504946; var blockHeight = 342734; var date = new Date(timestamp * MILLIS_IN_SECOND); it('handles a null locktime', function() { var transaction = new Transaction(); expect(transaction.getLockTime()).to.equal(null); }); it('handles a simple example', function() { var future = new Date(2025, 10, 30); // Sun Nov 30 2025 var transaction = new Transaction() .lockUntilDate(future); transaction.nLockTime.should.equal(future.getTime() / 1000); transaction.getLockTime().should.deep.equal(future); }); it('accepts a date instance', function() { var transaction = new Transaction() .lockUntilDate(date); transaction.nLockTime.should.equal(timestamp); transaction.getLockTime().should.deep.equal(date); }); it('accepts a number instance with a timestamp', function() { var transaction = new Transaction() .lockUntilDate(timestamp); transaction.nLockTime.should.equal(timestamp); transaction.getLockTime().should.deep.equal(new Date(timestamp * 1000)); }); it('accepts a block height', function() { var transaction = new Transaction() .lockUntilBlockHeight(blockHeight); transaction.nLockTime.should.equal(blockHeight); transaction.getLockTime().should.deep.equal(blockHeight); }); it('fails if the block height is too high', function() { expect(function() { return new Transaction().lockUntilBlockHeight(5e8); }).to.throw(errors.Transaction.BlockHeightTooHigh); }); it('fails if the date is too early', function() { expect(function() { return new Transaction().lockUntilDate(1); }).to.throw(errors.Transaction.LockTimeTooEarly); expect(function() { return new Transaction().lockUntilDate(499999999); }).to.throw(errors.Transaction.LockTimeTooEarly); }); it('fails if the block height is negative', function() { expect(function() { return new Transaction().lockUntilBlockHeight(-1); }).to.throw(errors.Transaction.NLockTimeOutOfRange); }); it('has a non-max sequenceNumber for effective date locktime tx', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .lockUntilDate(date); transaction.inputs[0].sequenceNumber .should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER); }); it('has a non-max sequenceNumber for effective blockheight locktime tx', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .lockUntilBlockHeight(blockHeight); transaction.inputs[0].sequenceNumber .should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER); }); it('should serialize correctly for date locktime ', function() { var transaction= new Transaction() .from(simpleUtxoWith1FLO) .lockUntilDate(date); var serialized_tx = transaction.uncheckedSerialize(); var copy = new Transaction(serialized_tx); serialized_tx.should.equal(copy.uncheckedSerialize()); copy.inputs[0].sequenceNumber .should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER) }); it('should serialize correctly for a block height locktime', function() { var transaction= new Transaction() .from(simpleUtxoWith1FLO) .lockUntilBlockHeight(blockHeight); var serialized_tx = transaction.uncheckedSerialize(); var copy = new Transaction(serialized_tx); serialized_tx.should.equal(copy.uncheckedSerialize()); copy.inputs[0].sequenceNumber .should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER) }); }); it('handles anyone-can-spend utxo', function() { var transaction = new Transaction() .from(anyoneCanSpendUTXO) .to(toAddress, 50000); should.exist(transaction); }); it('handles unsupported utxo in tx object', function() { var transaction = new Transaction(); transaction.fromObject.bind(transaction, JSON.parse(unsupportedTxObj)) .should.throw('Unsupported input script type: OP_1 OP_ADD OP_2 OP_EQUAL'); }); it('will error if object hash does not match transaction hash', function() { var tx = new Transaction(tx_1_hex); var txObj = tx.toObject(); txObj.hash = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458'; (function() { var tx2 = new Transaction(txObj); }).should.throw('Hash in object does not match transaction hash'); }); describe('inputAmount + outputAmount', function() { it('returns correct values for simple transaction', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .to(toAddress, 40000000); transaction.inputAmount.should.equal(100000000); transaction.outputAmount.should.equal(40000000); }); it('returns correct values for transaction with change', function() { var transaction = new Transaction() .from(simpleUtxoWith1FLO) .change(changeAddress) .to(toAddress, 1000); transaction.inputAmount.should.equal(100000000); transaction.outputAmount.should.equal(99900000); }); it('returns correct values for coinjoin tr