From 5fd45e8085e53fa58c8b979762e45aa4e8879e7e Mon Sep 17 00:00:00 2001 From: tx Date: Thu, 22 Oct 2015 13:45:13 -0700 Subject: [PATCH 1/6] Add TxSort and TxIsSorted functions to btcutil These functions sort transaction inputs and outputs according to BIP LI01 (https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki) This is useful to standardize transactions for faster multi-party agreement as well as preventing information leaks in a single-party use case. --- txsort.go | 84 +++++++++++++++++++++ txsort_test.go | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 txsort.go create mode 100644 txsort_test.go diff --git a/txsort.go b/txsort.go new file mode 100644 index 00000000..fe1201f7 --- /dev/null +++ b/txsort.go @@ -0,0 +1,84 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcutil + +import ( + "bytes" + "sort" + + "github.com/btcsuite/btcd/wire" +) + +// TxSort +// Provides functions for sorting tx inputs and outputs according to BIP LI01 +// (https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki) + +// TxSort sorts the inputs and outputs of a tx based on BIP LI01 +// It does not modify the transaction given, but returns a new copy +// which has been sorted and may have a different txid. +func TxSort(tx *wire.MsgTx) *wire.MsgTx { + txCopy := tx.Copy() + sort.Sort(sortableInputSlice(txCopy.TxIn)) + sort.Sort(sortableOutputSlice(txCopy.TxOut)) + return txCopy +} + +// TxIsSorted checks whether tx has inputs and outputs sorted according +// to BIP LI01. +func TxIsSorted(tx *wire.MsgTx) bool { + if !sort.IsSorted(sortableInputSlice(tx.TxIn)) { + return false + } + if !sort.IsSorted(sortableOutputSlice(tx.TxOut)) { + return false + } + return true +} + +type sortableInputSlice []*wire.TxIn +type sortableOutputSlice []*wire.TxOut + +// for SortableInputSlice and SortableOutputSlice, three functions are needed +// to make it sortable with sort.Sort() -- Len, Less, and Swap +// Len and Swap are trivial. Less is BIP LI01 specific. +func (ins sortableInputSlice) Len() int { + return len(ins) +} +func (outs sortableOutputSlice) Len() int { + return len(outs) +} + +func (ins sortableInputSlice) Swap(i, j int) { + ins[i], ins[j] = ins[j], ins[i] +} +func (outs sortableOutputSlice) Swap(i, j int) { + outs[i], outs[j] = outs[j], outs[i] +} + +// Input comparison function. +// First sort based on input txid (reversed / rpc-style), then index +func (ins sortableInputSlice) Less(i, j int) bool { + ihash := ins[i].PreviousOutPoint.Hash + jhash := ins[j].PreviousOutPoint.Hash + for b := 0; b < wire.HashSize/2; b++ { + ihash[b], ihash[wire.HashSize-1-b] = ihash[wire.HashSize-1-b], ihash[b] + jhash[b], jhash[wire.HashSize-1-b] = jhash[wire.HashSize-1-b], jhash[b] + } + c := bytes.Compare(ihash[:], jhash[:]) + if c == 0 { + // input txids are the same, compare index + return ins[i].PreviousOutPoint.Index < ins[j].PreviousOutPoint.Index + } + return c == -1. +} + +// Output comparison function. +// First sort based on amount (smallest first), then PkScript +func (outs sortableOutputSlice) Less(i, j int) bool { + if outs[i].Value == outs[j].Value { + return bytes.Compare(outs[i].PkScript, outs[j].PkScript) < 0 + } + return outs[i].Value < outs[j].Value +} diff --git a/txsort_test.go b/txsort_test.go new file mode 100644 index 00000000..113f90d1 --- /dev/null +++ b/txsort_test.go @@ -0,0 +1,201 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcutil_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +// TestSortTx tests SortTx function +func TestSortTx(t *testing.T) { + // Use block 100,000 transaction 1. Already sorted. + testTx := Block100000.Transactions[1] + sortedTx := btcutil.TxSort(testTx) + + testTxid := testTx.TxSha() + sortedTxid := sortedTx.TxSha() + if !testTxid.IsEqual(&sortedTxid) { + t.Errorf("Sorted TxSha mismatch - got %v, want %v", + testTxid.String(), sortedTxid.String()) + } + if !btcutil.TxIsSorted(testTx) { + t.Errorf("testTx %v is sorted but reported as unsorted", + testTxid.String()) + } + + // Example 1 0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3 + // test transaction 0a6a357e... which is the first test case of BIPLI01 + li01Tx1bytes, err := hex.DecodeString(LI01Hex1) + if err != nil { + t.Errorf("Error in hardcoded hex") + } + + var li01Tx1 wire.MsgTx + err = li01Tx1.Deserialize(bytes.NewReader(li01Tx1bytes)) + if err != nil { + t.Errorf("Failed to Deserialize li01Tx from byte slice") + } + + if btcutil.TxIsSorted(&li01Tx1) { + t.Errorf("LI01 Test Transaction 1 seen as sorted, but isn't") + } + + li01Tx1Sorted := btcutil.TxSort(&li01Tx1) + // txid of 0a6a357e... becomes 839503c... when sorted + wantSha := newShaHashFromStr("839503cb611a3e3734bd521c608f881be2293ff77b7384057ab994c794fce623") + + li01Tx1SortedSha := li01Tx1Sorted.TxSha() + if !wantSha.IsEqual(&li01Tx1SortedSha) { + t.Errorf("Sorted tx 1 txid mismatch. Got %v, want %v", + li01Tx1SortedSha, wantSha) + } + + // check that original transaction is not modified + wantSha = newShaHashFromStr("0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3") + + li01Tx1Sha := li01Tx1.TxSha() + if !wantSha.IsEqual(&li01Tx1Sha) { + t.Errorf("Original tx 1 txid mismatch. Got %v, want %v", + li01Tx1Sha, wantSha) + } + + // Example 2 28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f + // test transaction 28204cad..., the second test case of BIPLI01 + li01Tx2bytes, err := hex.DecodeString(LI01Hex2) + if err != nil { + t.Errorf("Error in hardcoded hex") + } + + var li01Tx2 wire.MsgTx + err = li01Tx2.Deserialize(bytes.NewReader(li01Tx2bytes)) + if err != nil { + t.Errorf("Failed to Deserialize li01Tx from byte slice") + } + if !btcutil.TxIsSorted(&li01Tx2) { + t.Errorf("LI01 Test Transaction 2 seen as unsorted, but it is sorted") + } + + li01Tx2Sorted := btcutil.TxSort(&li01Tx2) + + // txid of 28204cad... stays the same when sorted + wantSha = newShaHashFromStr("28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f") + + li01Tx2SortedSha := li01Tx2Sorted.TxSha() + if !wantSha.IsEqual(&li01Tx2SortedSha) { + t.Errorf("Example tx 2 txid mismatch. Got %v, want %v", + li01Tx2SortedSha, wantSha) + } + + // Example 3 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb + li01Tx3bytes, err := hex.DecodeString(LI01Hex3) + if err != nil { + t.Errorf("Error in hardcoded hex") + } + + var li01Tx3 wire.MsgTx + err = li01Tx3.Deserialize(bytes.NewReader(li01Tx3bytes)) + if err != nil { + t.Errorf("Failed to Deserialize li01Tx3 from byte slice") + } + if btcutil.TxIsSorted(&li01Tx3) { + t.Errorf("LI01 Test Transaction 3 seen as sorted, but isn't") + } + + li01Tx3Sorted := btcutil.TxSort(&li01Tx3) + + // txid of 8131ffb0... changes to 0a8c246... when sorted + wantSha = newShaHashFromStr("0a8c246c55f6b82f094d211f4f57167bf2ea4898741d218b09bdb2536fd8d13f") + + li01Tx3SortedSha := li01Tx3Sorted.TxSha() + if !wantSha.IsEqual(&li01Tx3SortedSha) { + t.Errorf("Example tx 3 txid mismatch. Got %v, want %v", + li01Tx3SortedSha, wantSha) + } + + // Example 4 fbde5d03b027d2b9ba4cf5d4fecab9a99864df2637b25ea4cbcb1796ff6550ca + li01Tx4bytes, err := hex.DecodeString(LI01Hex4) + if err != nil { + t.Errorf("Error in hardcoded hex") + } + + var li01Tx4 wire.MsgTx + err = li01Tx4.Deserialize(bytes.NewReader(li01Tx4bytes)) + if err != nil { + t.Errorf("Failed to Deserialize li01Tx4 from byte slice") + } + if btcutil.TxIsSorted(&li01Tx4) { + t.Errorf("LI01 Test Transaction 4 seen as sorted, but isn't") + } + + li01Tx4Sorted := btcutil.TxSort(&li01Tx4) + + // txid of 8131ffb0... changes to 0a8c246... when sorted + wantSha = newShaHashFromStr("a3196553b928b0b6154b002fa9a1ce875adabc486fedaaaf4c17430fd4486329") + + li01Tx4SortedSha := li01Tx4Sorted.TxSha() + if !wantSha.IsEqual(&li01Tx4SortedSha) { + t.Errorf("Example tx 4 txid mismatch. Got %v, want %v", + li01Tx4SortedSha, wantSha) + } + + // Example 5 ff85e8fc92e71bbc217e3ea9a3bacb86b435e52b6df0b089d67302c293a2b81d + li01Tx5bytes, err := hex.DecodeString(LI01Hex5) + if err != nil { + t.Errorf("Error in hardcoded hex") + } + + var li01Tx5 wire.MsgTx + err = li01Tx5.Deserialize(bytes.NewReader(li01Tx5bytes)) + if err != nil { + t.Errorf("Failed to Deserialize li01Tx5 from byte slice") + } + if btcutil.TxIsSorted(&li01Tx5) { + t.Errorf("LI01 Test Transaction 5 seen as sorted, but isn't") + } + + li01Tx5Sorted := btcutil.TxSort(&li01Tx5) + + // txid of ff85e8f... changes to 9a6c247... when sorted + wantSha = newShaHashFromStr("9a6c24746de024f77cac9b2138694f11101d1c66289261224ca52a25155a7c94") + + li01Tx5SortedSha := li01Tx5Sorted.TxSha() + if !wantSha.IsEqual(&li01Tx5SortedSha) { + t.Errorf("Example tx 5 txid mismatch. Got %v, want %v", + li01Tx5SortedSha, wantSha) + } +} + +// newShaHashFromStr converts the passed big-endian hex string into a +// wire.ShaHash. It only differs from the one available in wire in that +// it panics on an error since it will only (and must only) be called with +// hard-coded, and therefore known good, hashes. +// Copied from chaincfg tests +func newShaHashFromStr(hexStr string) *wire.ShaHash { + sha, err := wire.NewShaHashFromStr(hexStr) + if err != nil { + panic(err) + } + return sha +} + +// Example 1 sorts inputs, but leaves outputs unchanged +var LI01Hex1 = "0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000" + +// Example 2 is already sorted +var LI01Hex2 = "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000" + +// Example 3 sorts outputs only. Block 10001 tx[2] +var LI01Hex3 = "0100000001d992e5a888a86d4c7a6a69167a4728ee69497509740fc5f456a24528c340219a000000008b483045022100f0519bdc9282ff476da1323b8ef7ffe33f495c1a8d52cc522b437022d83f6a230220159b61d197fbae01b4a66622a23bc3f1def65d5fa24efd5c26fa872f3a246b8e014104839f9023296a1fabb133140128ca2709f6818c7d099491690bd8ac0fd55279def6a2ceb6ab7b5e4a71889b6e739f09509565eec789e86886f6f936fa42097adeffffffff02000fe208010000001976a914948c765a6914d43f2a7ac177da2c2f6b52de3d7c88ac00e32321000000001976a9140c34f4e29ab5a615d5ea28d4817f12b137d62ed588ac00000000" + +// Example 4 sorts both inputs and outputs. Block 10001 tx[1] +var LI01Hex4 = "01000000059daf0abe7a92618546a9dbcfd65869b6178c66ec21ccfda878c1175979cfd9ef000000004a493046022100c2f7f25be5de6ce88ac3c1a519514379e91f39b31ddff279a3db0b1a229b708b022100b29efbdbd9837cc6a6c7318aa4900ed7e4d65662c34d1622a2035a3a5534a99a01ffffffffd516330ebdf075948da56db13d22632a4fb941122df2884397dda45d451acefb0000000048473044022051243debe6d4f2b433bee0cee78c5c4073ead0e3bde54296dbed6176e128659c022044417bfe16f44eb7b6eb0cdf077b9ce972a332e15395c09ca5e4f602958d266101ffffffffe1f5aa33961227b3c344e57179417ce01b7ccd421117fe2336289b70489883f900000000484730440220593252bb992ce3c85baf28d6e3aa32065816271d2c822398fe7ee28a856bc943022066d429dd5025d3c86fd8fd8a58e183a844bd94aa312cefe00388f57c85b0ca3201ffffffffe207e83718129505e6a7484831442f668164ae659fddb82e9e5421a081fb90d50000000049483045022067cf27eb733e5bcae412a586b25a74417c237161a084167c2a0b439abfebdcb2022100efcc6baa6824b4c5205aa967e0b76d31abf89e738d4b6b014e788c9a8cccaf0c01ffffffffe23b8d9d80a9e9d977fab3c94dbe37befee63822443c3ec5ae5a713ede66c3940000000049483045022020f2eb35036666b1debe0d1d2e77a36d5d9c4e96c1dba23f5100f193dbf524790221008ce79bc1321fb4357c6daee818038d41544749127751726e46b2b320c8b565a201ffffffff0200ba1dd2050000001976a914366a27645806e817a6cd40bc869bdad92fe5509188ac40420f00000000001976a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac00000000" + +// Example 5 Sorts outputs only, based on output script. Block 100998 tx[6] +var LI01Hex5 = "01000000011f636d0003f673b3aeea4971daef16b8eed784cf6e8019a5ae7da4985fbb06e5000000008a47304402205103941e2b11e746dfa817888d422f6e7f4d16dbbfb8ffa61d15ffb924a84b8802202fe861b0f23f17139d15a3374bfc6c7196d371f3d1a324e31cc0aadbba87e53c0141049e7e1b251a7e26cae9ee7553b278ef58ef3c28b4b20134d51b747d9b18b0a19b94b66cef320e2549dec0ea3d725cb4c742f368928b1fb74b4603e24a1e262c80ffffffff0240420f00000000001976a914bcfa0e27218a7c97257b351b03a9eac95c25a23988ac40420f00000000001976a9140c6a68f20bafc678164d171ee4f077adfa9b091688ac00000000" From 98fd0a0661ca0cb393382e061735798dafa93ed0 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 23 Oct 2015 04:09:09 -0500 Subject: [PATCH 2/6] txsort: Convert to package This converts the txsort code into a separate package and renames 'TxSort' and 'IsTxSorted' to 'Sort' an `IsSorted`, respectively. It also adds a 'doc.go' and 'README.md' so it is consistent with the other packages. --- txsort/README.md | 44 +++++++++++++++++++++++++ txsort/doc.go | 20 +++++++++++ txsort.go => txsort/txsort.go | 10 +++--- txsort_test.go => txsort/txsort_test.go | 43 ++++++++---------------- 4 files changed, 83 insertions(+), 34 deletions(-) create mode 100644 txsort/README.md create mode 100644 txsort/doc.go rename txsort.go => txsort/txsort.go (90%) rename txsort_test.go => txsort/txsort_test.go (93%) diff --git a/txsort/README.md b/txsort/README.md new file mode 100644 index 00000000..29a8fe6a --- /dev/null +++ b/txsort/README.md @@ -0,0 +1,44 @@ +txsort +====== + +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] +(https://travis-ci.org/btcsuite/btcutil) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) + +Package txsort provides the transaction sorting according to BIPLI01. + +BIPLI01 defines a standard lexicographical sort order of transaction inputs and +outputs. This is useful to standardize transactions for faster multi-party +agreement as well as preventing information leaks in a single-party use case. + +The BIP goes into more detail, but for a quick and simplistic overview, the +order for inputs is defined as first sorting on the previous output hash and +then on the index as a tie breaker. The order for outputs is defined as first +sorting on the amount and then on the raw public key script bytes as a tie +breaker. + +A comprehensive suite of tests is provided to ensure proper functionality. + +## Documentation + +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)] +(http://godoc.org/github.com/btcsuite/btcutil/txsort) + +Full `go doc` style documentation for the project can be viewed online without +installing this package by using the GoDoc site here: +http://godoc.org/github.com/btcsuite/btcutil/txsort + +You can also view the documentation locally once the package is installed with +the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to +http://localhost:6060/pkg/github.com/btcsuite/btcutil/txsort + +## Installation and Updating + +```bash +$ go get -u github.com/btcsuite/btcutil/txsort +``` + +## License + +Package txsort is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/txsort/doc.go b/txsort/doc.go new file mode 100644 index 00000000..0aaa084b --- /dev/null +++ b/txsort/doc.go @@ -0,0 +1,20 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package txsort provides the transaction sorting according to BIPLI01. + +Overview + +BIPLI01 defines a standard lexicographical sort order of transaction inputs and +outputs. This is useful to standardize transactions for faster multi-party +agreement as well as preventing information leaks in a single-party use case. + +The BIP goes into more detail, but for a quick and simplistic overview, the +order for inputs is defined as first sorting on the previous output hash and +then on the index as a tie breaker. The order for outputs is defined as first +sorting on the amount and then on the raw public key script bytes as a tie +breaker. +*/ +package txsort diff --git a/txsort.go b/txsort/txsort.go similarity index 90% rename from txsort.go rename to txsort/txsort.go index fe1201f7..87d5071d 100644 --- a/txsort.go +++ b/txsort/txsort.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package btcutil +package txsort import ( "bytes" @@ -15,19 +15,19 @@ import ( // Provides functions for sorting tx inputs and outputs according to BIP LI01 // (https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki) -// TxSort sorts the inputs and outputs of a tx based on BIP LI01 +// Sort sorts the inputs and outputs of a tx based on BIP LI01 // It does not modify the transaction given, but returns a new copy // which has been sorted and may have a different txid. -func TxSort(tx *wire.MsgTx) *wire.MsgTx { +func Sort(tx *wire.MsgTx) *wire.MsgTx { txCopy := tx.Copy() sort.Sort(sortableInputSlice(txCopy.TxIn)) sort.Sort(sortableOutputSlice(txCopy.TxOut)) return txCopy } -// TxIsSorted checks whether tx has inputs and outputs sorted according +// IsSorted checks whether tx has inputs and outputs sorted according // to BIP LI01. -func TxIsSorted(tx *wire.MsgTx) bool { +func IsSorted(tx *wire.MsgTx) bool { if !sort.IsSorted(sortableInputSlice(tx.TxIn)) { return false } diff --git a/txsort_test.go b/txsort/txsort_test.go similarity index 93% rename from txsort_test.go rename to txsort/txsort_test.go index 113f90d1..0ca40e10 100644 --- a/txsort_test.go +++ b/txsort/txsort_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package btcutil_test +package txsort_test import ( "bytes" @@ -10,26 +10,11 @@ import ( "testing" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/txsort" ) -// TestSortTx tests SortTx function -func TestSortTx(t *testing.T) { - // Use block 100,000 transaction 1. Already sorted. - testTx := Block100000.Transactions[1] - sortedTx := btcutil.TxSort(testTx) - - testTxid := testTx.TxSha() - sortedTxid := sortedTx.TxSha() - if !testTxid.IsEqual(&sortedTxid) { - t.Errorf("Sorted TxSha mismatch - got %v, want %v", - testTxid.String(), sortedTxid.String()) - } - if !btcutil.TxIsSorted(testTx) { - t.Errorf("testTx %v is sorted but reported as unsorted", - testTxid.String()) - } - +// TestSort tests Sort function +func TestSort(t *testing.T) { // Example 1 0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3 // test transaction 0a6a357e... which is the first test case of BIPLI01 li01Tx1bytes, err := hex.DecodeString(LI01Hex1) @@ -43,11 +28,11 @@ func TestSortTx(t *testing.T) { t.Errorf("Failed to Deserialize li01Tx from byte slice") } - if btcutil.TxIsSorted(&li01Tx1) { + if txsort.IsSorted(&li01Tx1) { t.Errorf("LI01 Test Transaction 1 seen as sorted, but isn't") } - li01Tx1Sorted := btcutil.TxSort(&li01Tx1) + li01Tx1Sorted := txsort.Sort(&li01Tx1) // txid of 0a6a357e... becomes 839503c... when sorted wantSha := newShaHashFromStr("839503cb611a3e3734bd521c608f881be2293ff77b7384057ab994c794fce623") @@ -78,11 +63,11 @@ func TestSortTx(t *testing.T) { if err != nil { t.Errorf("Failed to Deserialize li01Tx from byte slice") } - if !btcutil.TxIsSorted(&li01Tx2) { + if !txsort.IsSorted(&li01Tx2) { t.Errorf("LI01 Test Transaction 2 seen as unsorted, but it is sorted") } - li01Tx2Sorted := btcutil.TxSort(&li01Tx2) + li01Tx2Sorted := txsort.Sort(&li01Tx2) // txid of 28204cad... stays the same when sorted wantSha = newShaHashFromStr("28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f") @@ -104,11 +89,11 @@ func TestSortTx(t *testing.T) { if err != nil { t.Errorf("Failed to Deserialize li01Tx3 from byte slice") } - if btcutil.TxIsSorted(&li01Tx3) { + if txsort.IsSorted(&li01Tx3) { t.Errorf("LI01 Test Transaction 3 seen as sorted, but isn't") } - li01Tx3Sorted := btcutil.TxSort(&li01Tx3) + li01Tx3Sorted := txsort.Sort(&li01Tx3) // txid of 8131ffb0... changes to 0a8c246... when sorted wantSha = newShaHashFromStr("0a8c246c55f6b82f094d211f4f57167bf2ea4898741d218b09bdb2536fd8d13f") @@ -130,11 +115,11 @@ func TestSortTx(t *testing.T) { if err != nil { t.Errorf("Failed to Deserialize li01Tx4 from byte slice") } - if btcutil.TxIsSorted(&li01Tx4) { + if txsort.IsSorted(&li01Tx4) { t.Errorf("LI01 Test Transaction 4 seen as sorted, but isn't") } - li01Tx4Sorted := btcutil.TxSort(&li01Tx4) + li01Tx4Sorted := txsort.Sort(&li01Tx4) // txid of 8131ffb0... changes to 0a8c246... when sorted wantSha = newShaHashFromStr("a3196553b928b0b6154b002fa9a1ce875adabc486fedaaaf4c17430fd4486329") @@ -156,11 +141,11 @@ func TestSortTx(t *testing.T) { if err != nil { t.Errorf("Failed to Deserialize li01Tx5 from byte slice") } - if btcutil.TxIsSorted(&li01Tx5) { + if txsort.IsSorted(&li01Tx5) { t.Errorf("LI01 Test Transaction 5 seen as sorted, but isn't") } - li01Tx5Sorted := btcutil.TxSort(&li01Tx5) + li01Tx5Sorted := txsort.Sort(&li01Tx5) // txid of ff85e8f... changes to 9a6c247... when sorted wantSha = newShaHashFromStr("9a6c24746de024f77cac9b2138694f11101d1c66289261224ca52a25155a7c94") From e0e9257790367184e97012a52d174c3be8314b93 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 23 Oct 2015 02:55:56 -0500 Subject: [PATCH 3/6] txsort: Convert tests, optimize, and cleanup code. - Move hex for test txns into separate files in the testdata directory - Convert tests to table-driven tests - Make comments more consistent with the rest of the codebase - Optimize the input sorting function to perform the hash equivalence check before reversing the bytes so it can be avoided in that case --- txsort/testdata/li01-1.hex | 1 + txsort/testdata/li01-2.hex | 1 + txsort/testdata/li01-3.hex | 1 + txsort/testdata/li01-4.hex | 1 + txsort/testdata/li01-5.hex | 1 + txsort/txsort.go | 73 +++++------ txsort/txsort_test.go | 254 +++++++++++++------------------------ 7 files changed, 129 insertions(+), 203 deletions(-) create mode 100644 txsort/testdata/li01-1.hex create mode 100644 txsort/testdata/li01-2.hex create mode 100644 txsort/testdata/li01-3.hex create mode 100644 txsort/testdata/li01-4.hex create mode 100644 txsort/testdata/li01-5.hex diff --git a/txsort/testdata/li01-1.hex b/txsort/testdata/li01-1.hex new file mode 100644 index 00000000..dbd18422 --- /dev/null +++ b/txsort/testdata/li01-1.hex @@ -0,0 +1 @@ +0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000 \ No newline at end of file diff --git a/txsort/testdata/li01-2.hex b/txsort/testdata/li01-2.hex new file mode 100644 index 00000000..c42d0d2d --- /dev/null +++ b/txsort/testdata/li01-2.hex @@ -0,0 +1 @@ +010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000 \ No newline at end of file diff --git a/txsort/testdata/li01-3.hex b/txsort/testdata/li01-3.hex new file mode 100644 index 00000000..6fd859a9 --- /dev/null +++ b/txsort/testdata/li01-3.hex @@ -0,0 +1 @@ +0100000001d992e5a888a86d4c7a6a69167a4728ee69497509740fc5f456a24528c340219a000000008b483045022100f0519bdc9282ff476da1323b8ef7ffe33f495c1a8d52cc522b437022d83f6a230220159b61d197fbae01b4a66622a23bc3f1def65d5fa24efd5c26fa872f3a246b8e014104839f9023296a1fabb133140128ca2709f6818c7d099491690bd8ac0fd55279def6a2ceb6ab7b5e4a71889b6e739f09509565eec789e86886f6f936fa42097adeffffffff02000fe208010000001976a914948c765a6914d43f2a7ac177da2c2f6b52de3d7c88ac00e32321000000001976a9140c34f4e29ab5a615d5ea28d4817f12b137d62ed588ac00000000 \ No newline at end of file diff --git a/txsort/testdata/li01-4.hex b/txsort/testdata/li01-4.hex new file mode 100644 index 00000000..f41d23f8 --- /dev/null +++ b/txsort/testdata/li01-4.hex @@ -0,0 +1 @@ +01000000059daf0abe7a92618546a9dbcfd65869b6178c66ec21ccfda878c1175979cfd9ef000000004a493046022100c2f7f25be5de6ce88ac3c1a519514379e91f39b31ddff279a3db0b1a229b708b022100b29efbdbd9837cc6a6c7318aa4900ed7e4d65662c34d1622a2035a3a5534a99a01ffffffffd516330ebdf075948da56db13d22632a4fb941122df2884397dda45d451acefb0000000048473044022051243debe6d4f2b433bee0cee78c5c4073ead0e3bde54296dbed6176e128659c022044417bfe16f44eb7b6eb0cdf077b9ce972a332e15395c09ca5e4f602958d266101ffffffffe1f5aa33961227b3c344e57179417ce01b7ccd421117fe2336289b70489883f900000000484730440220593252bb992ce3c85baf28d6e3aa32065816271d2c822398fe7ee28a856bc943022066d429dd5025d3c86fd8fd8a58e183a844bd94aa312cefe00388f57c85b0ca3201ffffffffe207e83718129505e6a7484831442f668164ae659fddb82e9e5421a081fb90d50000000049483045022067cf27eb733e5bcae412a586b25a74417c237161a084167c2a0b439abfebdcb2022100efcc6baa6824b4c5205aa967e0b76d31abf89e738d4b6b014e788c9a8cccaf0c01ffffffffe23b8d9d80a9e9d977fab3c94dbe37befee63822443c3ec5ae5a713ede66c3940000000049483045022020f2eb35036666b1debe0d1d2e77a36d5d9c4e96c1dba23f5100f193dbf524790221008ce79bc1321fb4357c6daee818038d41544749127751726e46b2b320c8b565a201ffffffff0200ba1dd2050000001976a914366a27645806e817a6cd40bc869bdad92fe5509188ac40420f00000000001976a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac00000000 \ No newline at end of file diff --git a/txsort/testdata/li01-5.hex b/txsort/testdata/li01-5.hex new file mode 100644 index 00000000..e1cb4d61 --- /dev/null +++ b/txsort/testdata/li01-5.hex @@ -0,0 +1 @@ +01000000011f636d0003f673b3aeea4971daef16b8eed784cf6e8019a5ae7da4985fbb06e5000000008a47304402205103941e2b11e746dfa817888d422f6e7f4d16dbbfb8ffa61d15ffb924a84b8802202fe861b0f23f17139d15a3374bfc6c7196d371f3d1a324e31cc0aadbba87e53c0141049e7e1b251a7e26cae9ee7553b278ef58ef3c28b4b20134d51b747d9b18b0a19b94b66cef320e2549dec0ea3d725cb4c742f368928b1fb74b4603e24a1e262c80ffffffff0240420f00000000001976a914bcfa0e27218a7c97257b351b03a9eac95c25a23988ac40420f00000000001976a9140c6a68f20bafc678164d171ee4f077adfa9b091688ac00000000 \ No newline at end of file diff --git a/txsort/txsort.go b/txsort/txsort.go index 87d5071d..b915cb5d 100644 --- a/txsort/txsort.go +++ b/txsort/txsort.go @@ -2,6 +2,9 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. +// Provides functions for sorting tx inputs and outputs according to BIP LI01 +// (https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki) + package txsort import ( @@ -11,13 +14,9 @@ import ( "github.com/btcsuite/btcd/wire" ) -// TxSort -// Provides functions for sorting tx inputs and outputs according to BIP LI01 -// (https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki) - -// Sort sorts the inputs and outputs of a tx based on BIP LI01 -// It does not modify the transaction given, but returns a new copy -// which has been sorted and may have a different txid. +// Sort returns a new transaction with the inputs and outputs sorted based on +// BIP LI01. The passed transaction is not modified and the new transaction +// might have a different hash if any sorting was done. func Sort(tx *wire.MsgTx) *wire.MsgTx { txCopy := tx.Copy() sort.Sort(sortableInputSlice(txCopy.TxIn)) @@ -25,8 +24,8 @@ func Sort(tx *wire.MsgTx) *wire.MsgTx { return txCopy } -// IsSorted checks whether tx has inputs and outputs sorted according -// to BIP LI01. +// IsSorted checks whether tx has inputs and outputs sorted according to BIP +// LI01. func IsSorted(tx *wire.MsgTx) bool { if !sort.IsSorted(sortableInputSlice(tx.TxIn)) { return false @@ -40,45 +39,39 @@ func IsSorted(tx *wire.MsgTx) bool { type sortableInputSlice []*wire.TxIn type sortableOutputSlice []*wire.TxOut -// for SortableInputSlice and SortableOutputSlice, three functions are needed +// For SortableInputSlice and SortableOutputSlice, three functions are needed // to make it sortable with sort.Sort() -- Len, Less, and Swap // Len and Swap are trivial. Less is BIP LI01 specific. -func (ins sortableInputSlice) Len() int { - return len(ins) -} -func (outs sortableOutputSlice) Len() int { - return len(outs) -} - -func (ins sortableInputSlice) Swap(i, j int) { - ins[i], ins[j] = ins[j], ins[i] -} -func (outs sortableOutputSlice) Swap(i, j int) { - outs[i], outs[j] = outs[j], outs[i] -} +func (s sortableInputSlice) Len() int { return len(s) } +func (s sortableOutputSlice) Len() int { return len(s) } +func (s sortableOutputSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s sortableInputSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Input comparison function. -// First sort based on input txid (reversed / rpc-style), then index -func (ins sortableInputSlice) Less(i, j int) bool { - ihash := ins[i].PreviousOutPoint.Hash - jhash := ins[j].PreviousOutPoint.Hash - for b := 0; b < wire.HashSize/2; b++ { - ihash[b], ihash[wire.HashSize-1-b] = ihash[wire.HashSize-1-b], ihash[b] - jhash[b], jhash[wire.HashSize-1-b] = jhash[wire.HashSize-1-b], jhash[b] +// First sort based on input hash (reversed / rpc-style), then index. +func (s sortableInputSlice) Less(i, j int) bool { + // Input hashes are the same, so compare the index. + ihash := s[i].PreviousOutPoint.Hash + jhash := s[j].PreviousOutPoint.Hash + if ihash == jhash { + return s[i].PreviousOutPoint.Index < s[j].PreviousOutPoint.Index } - c := bytes.Compare(ihash[:], jhash[:]) - if c == 0 { - // input txids are the same, compare index - return ins[i].PreviousOutPoint.Index < ins[j].PreviousOutPoint.Index + + // At this point, the hashes are not equal, so reverse them to + // big-endian and return the result of the comparison. + const hashSize = wire.HashSize + for b := 0; b < hashSize/2; b++ { + ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b] + jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b] } - return c == -1. + return bytes.Compare(ihash[:], jhash[:]) == -1 } // Output comparison function. -// First sort based on amount (smallest first), then PkScript -func (outs sortableOutputSlice) Less(i, j int) bool { - if outs[i].Value == outs[j].Value { - return bytes.Compare(outs[i].PkScript, outs[j].PkScript) < 0 +// First sort based on amount (smallest first), then PkScript. +func (s sortableOutputSlice) Less(i, j int) bool { + if s[i].Value == s[j].Value { + return bytes.Compare(s[i].PkScript, s[j].PkScript) < 0 } - return outs[i].Value < outs[j].Value + return s[i].Value < s[j].Value } diff --git a/txsort/txsort_test.go b/txsort/txsort_test.go index 0ca40e10..ef5db189 100644 --- a/txsort/txsort_test.go +++ b/txsort/txsort_test.go @@ -7,180 +7,108 @@ package txsort_test import ( "bytes" "encoding/hex" + "io/ioutil" + "path/filepath" "testing" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil/txsort" ) -// TestSort tests Sort function +// TestSort ensures the transaction sorting works according to the BIP. func TestSort(t *testing.T) { - // Example 1 0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3 - // test transaction 0a6a357e... which is the first test case of BIPLI01 - li01Tx1bytes, err := hex.DecodeString(LI01Hex1) - if err != nil { - t.Errorf("Error in hardcoded hex") + tests := []struct { + name string + hexFile string + isSorted bool + unsortedHash string + sortedHash string + }{ + { + name: "first test case from BIPLI01 - sorts inputs only", + hexFile: "li01-1.hex", + isSorted: false, + unsortedHash: "0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3", + sortedHash: "839503cb611a3e3734bd521c608f881be2293ff77b7384057ab994c794fce623", + }, + { + name: "second test case from BIPLI01 - already sorted", + hexFile: "li01-2.hex", + isSorted: true, + unsortedHash: "28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f", + sortedHash: "28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f", + }, + { + name: "block 10001 tx[1] - sorts both inputs and outputs", + hexFile: "li01-3.hex", + isSorted: false, + unsortedHash: "fbde5d03b027d2b9ba4cf5d4fecab9a99864df2637b25ea4cbcb1796ff6550ca", + sortedHash: "0a8c246c55f6b82f094d211f4f57167bf2ea4898741d218b09bdb2536fd8d13f", + }, + { + name: "block 10001 tx[2] - sorts outputs only", + hexFile: "li01-4.hex", + isSorted: false, + unsortedHash: "8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb", + sortedHash: "a3196553b928b0b6154b002fa9a1ce875adabc486fedaaaf4c17430fd4486329", + }, + { + name: "block 100998 tx[6] - sorts outputs only, based on output script", + hexFile: "li01-5.hex", + isSorted: false, + unsortedHash: "ff85e8fc92e71bbc217e3ea9a3bacb86b435e52b6df0b089d67302c293a2b81d", + sortedHash: "9a6c24746de024f77cac9b2138694f11101d1c66289261224ca52a25155a7c94", + }, } - var li01Tx1 wire.MsgTx - err = li01Tx1.Deserialize(bytes.NewReader(li01Tx1bytes)) - if err != nil { - t.Errorf("Failed to Deserialize li01Tx from byte slice") - } + for _, test := range tests { + // Load and deserialize the test transaction. + filePath := filepath.Join("testdata", test.hexFile) + txHexBytes, err := ioutil.ReadFile(filePath) + if err != nil { + t.Errorf("ReadFile (%s): failed to read test file: %v", + test.name, err) + continue + } + txBytes, err := hex.DecodeString(string(txHexBytes)) + if err != nil { + t.Errorf("DecodeString (%s): failed to decode tx: %v", + test.name, err) + continue + } + var tx wire.MsgTx + err = tx.Deserialize(bytes.NewReader(txBytes)) + if err != nil { + t.Errorf("Deserialize (%s): unexpected error %v", + test.name, err) + continue + } - if txsort.IsSorted(&li01Tx1) { - t.Errorf("LI01 Test Transaction 1 seen as sorted, but isn't") - } + // Ensure the sort order of the original transaction matches the + // expected value. + if got := txsort.IsSorted(&tx); got != test.isSorted { + t.Errorf("IsSorted (%s): sort does not match "+ + "expected - got %v, want %v", test.name, got, + test.isSorted) + continue + } - li01Tx1Sorted := txsort.Sort(&li01Tx1) - // txid of 0a6a357e... becomes 839503c... when sorted - wantSha := newShaHashFromStr("839503cb611a3e3734bd521c608f881be2293ff77b7384057ab994c794fce623") + // Sort the transaction and ensure the resulting hash is the + // expected value. + sortedTx := txsort.Sort(&tx) + if got := sortedTx.TxSha().String(); got != test.sortedHash { + t.Errorf("Sort (%s): sorted hash does not match "+ + "expected - got %v, want %v", test.name, got, + test.sortedHash) + continue + } - li01Tx1SortedSha := li01Tx1Sorted.TxSha() - if !wantSha.IsEqual(&li01Tx1SortedSha) { - t.Errorf("Sorted tx 1 txid mismatch. Got %v, want %v", - li01Tx1SortedSha, wantSha) - } - - // check that original transaction is not modified - wantSha = newShaHashFromStr("0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3") - - li01Tx1Sha := li01Tx1.TxSha() - if !wantSha.IsEqual(&li01Tx1Sha) { - t.Errorf("Original tx 1 txid mismatch. Got %v, want %v", - li01Tx1Sha, wantSha) - } - - // Example 2 28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f - // test transaction 28204cad..., the second test case of BIPLI01 - li01Tx2bytes, err := hex.DecodeString(LI01Hex2) - if err != nil { - t.Errorf("Error in hardcoded hex") - } - - var li01Tx2 wire.MsgTx - err = li01Tx2.Deserialize(bytes.NewReader(li01Tx2bytes)) - if err != nil { - t.Errorf("Failed to Deserialize li01Tx from byte slice") - } - if !txsort.IsSorted(&li01Tx2) { - t.Errorf("LI01 Test Transaction 2 seen as unsorted, but it is sorted") - } - - li01Tx2Sorted := txsort.Sort(&li01Tx2) - - // txid of 28204cad... stays the same when sorted - wantSha = newShaHashFromStr("28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f") - - li01Tx2SortedSha := li01Tx2Sorted.TxSha() - if !wantSha.IsEqual(&li01Tx2SortedSha) { - t.Errorf("Example tx 2 txid mismatch. Got %v, want %v", - li01Tx2SortedSha, wantSha) - } - - // Example 3 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb - li01Tx3bytes, err := hex.DecodeString(LI01Hex3) - if err != nil { - t.Errorf("Error in hardcoded hex") - } - - var li01Tx3 wire.MsgTx - err = li01Tx3.Deserialize(bytes.NewReader(li01Tx3bytes)) - if err != nil { - t.Errorf("Failed to Deserialize li01Tx3 from byte slice") - } - if txsort.IsSorted(&li01Tx3) { - t.Errorf("LI01 Test Transaction 3 seen as sorted, but isn't") - } - - li01Tx3Sorted := txsort.Sort(&li01Tx3) - - // txid of 8131ffb0... changes to 0a8c246... when sorted - wantSha = newShaHashFromStr("0a8c246c55f6b82f094d211f4f57167bf2ea4898741d218b09bdb2536fd8d13f") - - li01Tx3SortedSha := li01Tx3Sorted.TxSha() - if !wantSha.IsEqual(&li01Tx3SortedSha) { - t.Errorf("Example tx 3 txid mismatch. Got %v, want %v", - li01Tx3SortedSha, wantSha) - } - - // Example 4 fbde5d03b027d2b9ba4cf5d4fecab9a99864df2637b25ea4cbcb1796ff6550ca - li01Tx4bytes, err := hex.DecodeString(LI01Hex4) - if err != nil { - t.Errorf("Error in hardcoded hex") - } - - var li01Tx4 wire.MsgTx - err = li01Tx4.Deserialize(bytes.NewReader(li01Tx4bytes)) - if err != nil { - t.Errorf("Failed to Deserialize li01Tx4 from byte slice") - } - if txsort.IsSorted(&li01Tx4) { - t.Errorf("LI01 Test Transaction 4 seen as sorted, but isn't") - } - - li01Tx4Sorted := txsort.Sort(&li01Tx4) - - // txid of 8131ffb0... changes to 0a8c246... when sorted - wantSha = newShaHashFromStr("a3196553b928b0b6154b002fa9a1ce875adabc486fedaaaf4c17430fd4486329") - - li01Tx4SortedSha := li01Tx4Sorted.TxSha() - if !wantSha.IsEqual(&li01Tx4SortedSha) { - t.Errorf("Example tx 4 txid mismatch. Got %v, want %v", - li01Tx4SortedSha, wantSha) - } - - // Example 5 ff85e8fc92e71bbc217e3ea9a3bacb86b435e52b6df0b089d67302c293a2b81d - li01Tx5bytes, err := hex.DecodeString(LI01Hex5) - if err != nil { - t.Errorf("Error in hardcoded hex") - } - - var li01Tx5 wire.MsgTx - err = li01Tx5.Deserialize(bytes.NewReader(li01Tx5bytes)) - if err != nil { - t.Errorf("Failed to Deserialize li01Tx5 from byte slice") - } - if txsort.IsSorted(&li01Tx5) { - t.Errorf("LI01 Test Transaction 5 seen as sorted, but isn't") - } - - li01Tx5Sorted := txsort.Sort(&li01Tx5) - - // txid of ff85e8f... changes to 9a6c247... when sorted - wantSha = newShaHashFromStr("9a6c24746de024f77cac9b2138694f11101d1c66289261224ca52a25155a7c94") - - li01Tx5SortedSha := li01Tx5Sorted.TxSha() - if !wantSha.IsEqual(&li01Tx5SortedSha) { - t.Errorf("Example tx 5 txid mismatch. Got %v, want %v", - li01Tx5SortedSha, wantSha) + // Ensure the original transaction is not modified. + if got := tx.TxSha().String(); got != test.unsortedHash { + t.Errorf("Sort (%s): unsorted hash does not match "+ + "expected - got %v, want %v", test.name, got, + test.unsortedHash) + continue + } } } - -// newShaHashFromStr converts the passed big-endian hex string into a -// wire.ShaHash. It only differs from the one available in wire in that -// it panics on an error since it will only (and must only) be called with -// hard-coded, and therefore known good, hashes. -// Copied from chaincfg tests -func newShaHashFromStr(hexStr string) *wire.ShaHash { - sha, err := wire.NewShaHashFromStr(hexStr) - if err != nil { - panic(err) - } - return sha -} - -// Example 1 sorts inputs, but leaves outputs unchanged -var LI01Hex1 = "0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000" - -// Example 2 is already sorted -var LI01Hex2 = "010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000" - -// Example 3 sorts outputs only. Block 10001 tx[2] -var LI01Hex3 = "0100000001d992e5a888a86d4c7a6a69167a4728ee69497509740fc5f456a24528c340219a000000008b483045022100f0519bdc9282ff476da1323b8ef7ffe33f495c1a8d52cc522b437022d83f6a230220159b61d197fbae01b4a66622a23bc3f1def65d5fa24efd5c26fa872f3a246b8e014104839f9023296a1fabb133140128ca2709f6818c7d099491690bd8ac0fd55279def6a2ceb6ab7b5e4a71889b6e739f09509565eec789e86886f6f936fa42097adeffffffff02000fe208010000001976a914948c765a6914d43f2a7ac177da2c2f6b52de3d7c88ac00e32321000000001976a9140c34f4e29ab5a615d5ea28d4817f12b137d62ed588ac00000000" - -// Example 4 sorts both inputs and outputs. Block 10001 tx[1] -var LI01Hex4 = "01000000059daf0abe7a92618546a9dbcfd65869b6178c66ec21ccfda878c1175979cfd9ef000000004a493046022100c2f7f25be5de6ce88ac3c1a519514379e91f39b31ddff279a3db0b1a229b708b022100b29efbdbd9837cc6a6c7318aa4900ed7e4d65662c34d1622a2035a3a5534a99a01ffffffffd516330ebdf075948da56db13d22632a4fb941122df2884397dda45d451acefb0000000048473044022051243debe6d4f2b433bee0cee78c5c4073ead0e3bde54296dbed6176e128659c022044417bfe16f44eb7b6eb0cdf077b9ce972a332e15395c09ca5e4f602958d266101ffffffffe1f5aa33961227b3c344e57179417ce01b7ccd421117fe2336289b70489883f900000000484730440220593252bb992ce3c85baf28d6e3aa32065816271d2c822398fe7ee28a856bc943022066d429dd5025d3c86fd8fd8a58e183a844bd94aa312cefe00388f57c85b0ca3201ffffffffe207e83718129505e6a7484831442f668164ae659fddb82e9e5421a081fb90d50000000049483045022067cf27eb733e5bcae412a586b25a74417c237161a084167c2a0b439abfebdcb2022100efcc6baa6824b4c5205aa967e0b76d31abf89e738d4b6b014e788c9a8cccaf0c01ffffffffe23b8d9d80a9e9d977fab3c94dbe37befee63822443c3ec5ae5a713ede66c3940000000049483045022020f2eb35036666b1debe0d1d2e77a36d5d9c4e96c1dba23f5100f193dbf524790221008ce79bc1321fb4357c6daee818038d41544749127751726e46b2b320c8b565a201ffffffff0200ba1dd2050000001976a914366a27645806e817a6cd40bc869bdad92fe5509188ac40420f00000000001976a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac00000000" - -// Example 5 Sorts outputs only, based on output script. Block 100998 tx[6] -var LI01Hex5 = "01000000011f636d0003f673b3aeea4971daef16b8eed784cf6e8019a5ae7da4985fbb06e5000000008a47304402205103941e2b11e746dfa817888d422f6e7f4d16dbbfb8ffa61d15ffb924a84b8802202fe861b0f23f17139d15a3374bfc6c7196d371f3d1a324e31cc0aadbba87e53c0141049e7e1b251a7e26cae9ee7553b278ef58ef3c28b4b20134d51b747d9b18b0a19b94b66cef320e2549dec0ea3d725cb4c742f368928b1fb74b4603e24a1e262c80ffffffff0240420f00000000001976a914bcfa0e27218a7c97257b351b03a9eac95c25a23988ac40420f00000000001976a9140c6a68f20bafc678164d171ee4f077adfa9b091688ac00000000" From 0df67ee0646e5fb24d4d25cb0b58abc642cd80a9 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 23 Oct 2015 04:42:59 -0500 Subject: [PATCH 4/6] txsort: Add InPlaceSort function. This adds a new function with loud warnings which allows sorting a transaction in place by mutating it. This is more efficient for the caller if they are the ones creating the transaction and are sure it will not invalid any cache or containing structures. The Sort function which makes a copy and is therefore does not mutate the passed transaction is still available and the default method. --- txsort/txsort.go | 17 +++++++++++++++++ txsort/txsort_test.go | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/txsort/txsort.go b/txsort/txsort.go index b915cb5d..9a32036c 100644 --- a/txsort/txsort.go +++ b/txsort/txsort.go @@ -14,6 +14,23 @@ import ( "github.com/btcsuite/btcd/wire" ) +// InPlaceSort modifies the passed transaction inputs and outputs to be sorted +// based on BIP LI01. +// +// WARNING: This function must NOT be called with published transactions since +// it will mutate the transaction if it's not already sorted. This can cause +// issues if you mutate a tx in a block, for example, which would invalidate the +// block. It could also cause cached hashes, such as in a btcutil.Tx to become +// invalidated. +// +// The function should only be used if the caller is creating the transaction or +// is otherwise 100% positive mutating will not cause adverse affects due to +// other dependencies. +func InPlaceSort(tx *wire.MsgTx) { + sort.Sort(sortableInputSlice(tx.TxIn)) + sort.Sort(sortableOutputSlice(tx.TxOut)) +} + // Sort returns a new transaction with the inputs and outputs sorted based on // BIP LI01. The passed transaction is not modified and the new transaction // might have a different hash if any sorting was done. diff --git a/txsort/txsort_test.go b/txsort/txsort_test.go index ef5db189..bcf6d2b9 100644 --- a/txsort/txsort_test.go +++ b/txsort/txsort_test.go @@ -110,5 +110,15 @@ func TestSort(t *testing.T) { test.unsortedHash) continue } + + // Now sort the transaction using the mutable version and ensure + // the resulting hash is the expected value. + txsort.InPlaceSort(&tx) + if got := tx.TxSha().String(); got != test.sortedHash { + t.Errorf("SortMutate (%s): sorted hash does not match "+ + "expected - got %v, want %v", test.name, got, + test.sortedHash) + continue + } } } From d6989ebc716421498125f078e6c2b9846505c09e Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 23 Oct 2015 14:34:12 -0500 Subject: [PATCH 5/6] txsort: Correct the names of the tests. --- txsort/txsort_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/txsort/txsort_test.go b/txsort/txsort_test.go index bcf6d2b9..1df55c4f 100644 --- a/txsort/txsort_test.go +++ b/txsort/txsort_test.go @@ -25,7 +25,7 @@ func TestSort(t *testing.T) { sortedHash string }{ { - name: "first test case from BIPLI01 - sorts inputs only", + name: "first test case from BIPLI01 - sorts inputs only, based on hash", hexFile: "li01-1.hex", isSorted: false, unsortedHash: "0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3", @@ -39,14 +39,14 @@ func TestSort(t *testing.T) { sortedHash: "28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f", }, { - name: "block 10001 tx[1] - sorts both inputs and outputs", + name: "block 100001 tx[1] - sorts outputs only, based on amount", hexFile: "li01-3.hex", isSorted: false, unsortedHash: "fbde5d03b027d2b9ba4cf5d4fecab9a99864df2637b25ea4cbcb1796ff6550ca", sortedHash: "0a8c246c55f6b82f094d211f4f57167bf2ea4898741d218b09bdb2536fd8d13f", }, { - name: "block 10001 tx[2] - sorts outputs only", + name: "block 100001 tx[2] - sorts both inputs and outputs", hexFile: "li01-4.hex", isSorted: false, unsortedHash: "8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb", From e8bab6bc196dcb7e1c17c91b0ff9d50e76c797e3 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Fri, 23 Oct 2015 17:10:35 -0500 Subject: [PATCH 6/6] docs: Make various README.md files consistent. First, it removes the documentation section from all the README.md files and instead puts a web-based godoc badge and link at the top with the other badges. This is being done since the local godoc tool no longer ships with Go by default, so the instructions no longer work without first installing godoc. Due to this, pretty much everyone uses the web-based godoc these days anyways. Anyone who has manually installed godoc won't need instructions. Second, it makes sure the ISC license badge is at the top with the other badges and removes the textual reference in the overview section. Third, it's modifies the Installation section to Installation and Updating and adds a -u to the go get command since it works for both and thus is simpler. Finally, it replaces the badges with SVG versions from shields.io so they are consistent. --- README.md | 28 +++++++++------------------- base58/README.md | 25 +++++++------------------ bloom/README.md | 26 ++++++++------------------ coinset/README.md | 26 ++++++++------------------ hdkeychain/README.md | 26 ++++++++------------------ txsort/README.md | 15 ++------------- 6 files changed, 42 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 145cdb61..f0faf75f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ btcutil ======= -[![Build Status](https://travis-ci.org/btcsuite/btcutil.png?branch=master)] +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] (https://travis-ci.org/btcsuite/btcutil) [![Coverage Status] -(https://coveralls.io/repos/btcsuite/btcutil/badge.png?branch=master)] -(https://coveralls.io/r/btcsuite/btcutil?branch=master) +(http://img.shields.io/coveralls/btcsuite/btcutil.svg)] +(https://coveralls.io/r/btcsuite/btcutil?branch=master) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)] +(http://godoc.org/github.com/btcsuite/btcutil) Package btcutil provides bitcoin-specific convenience functions and types. A comprehensive suite of tests is provided to ensure proper functionality. See `test_coverage.txt` for the gocov coverage report. Alternatively, if you are running a POSIX OS, you can run the `cov_report.sh` script for a real-time -report. Package btcutil is licensed under the liberal ISC license. +report. This package was developed for btcd, an alternative full-node implementation of bitcoin which is under active development by Conformal. Although it was @@ -18,23 +21,10 @@ primarily written for btcd, this package has intentionally been designed so it can be used as a standalone package for any projects needing the functionality provided. -## Documentation - -[![GoDoc](https://godoc.org/github.com/btcsuite/btcutil?status.png)] -(http://godoc.org/github.com/btcsuite/btcutil) - -Full `go doc` style documentation for the project can be viewed online without -installing this package by using the GoDoc site here: -http://godoc.org/github.com/btcsuite/btcutil - -You can also view the documentation locally once the package is installed with -the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to -http://localhost:6060/pkg/github.com/btcsuite/btcutil - -## Installation +## Installation and Updating ```bash -$ go get github.com/btcsuite/btcutil +$ go get -u github.com/btcsuite/btcutil ``` ## GPG Verification Key diff --git a/base58/README.md b/base58/README.md index e119315c..fdb24312 100644 --- a/base58/README.md +++ b/base58/README.md @@ -1,33 +1,22 @@ base58 ========== -[![Build Status](https://travis-ci.org/btcsuite/btcutil.png?branch=master)] -(https://travis-ci.org/btcsuite/btcutil) +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] +(https://travis-ci.org/btcsuite/btcutil) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://godoc.org/github.com/btcsuite/btcutil/base58?status.png)] +(http://godoc.org/github.com/btcsuite/btcutil/base58) Package base58 provides an API for encoding and decoding to and from the modified base58 encoding. It also provides an API to do Base58Check encoding, as described [here](https://en.bitcoin.it/wiki/Base58Check_encoding). A comprehensive suite of tests is provided to ensure proper functionality. -Package base58 is licensed under the copyfree ISC license. -## Documentation - -[![GoDoc](https://godoc.org/github.com/btcsuite/btcutil/base58?status.png)] -(http://godoc.org/github.com/btcsuite/btcutil/base58) - -Full `go doc` style documentation for the project can be viewed online without -installing this package by using the GoDoc site here: -http://godoc.org/github.com/btcsuite/btcutil/base58 - -You can also view the documentation locally once the package is installed with -the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to -http://localhost:6060/pkg/github.com/btcsuite/btcutil/base58 - -## Installation +## Installation and Updating ```bash -$ go get github.com/btcsuite/btcutil/base58 +$ go get -u github.com/btcsuite/btcutil/base58 ``` ## Examples diff --git a/bloom/README.md b/bloom/README.md index 81086b42..4c7123cb 100644 --- a/bloom/README.md +++ b/bloom/README.md @@ -1,33 +1,23 @@ bloom ===== -[![Build Status](https://travis-ci.org/btcsuite/btcutil.png?branch=master)] -(https://travis-ci.org/btcsuite/btcutil) +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] +(https://travis-ci.org/btcsuite/btcutil) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)] +(http://godoc.org/github.com/btcsuite/btcutil/bloom) Package bloom provides an API for dealing with bitcoin-specific bloom filters. A comprehensive suite of tests is provided to ensure proper functionality. See `test_coverage.txt` for the gocov coverage report. Alternatively, if you are running a POSIX OS, you can run the `cov_report.sh` script for a real-time -report. Package coinset is licensed under the liberal ISC license. +report. -## Documentation - -[![GoDoc](https://godoc.org/github.com/btcsuite/btcutil/bloom?status.png)] -(http://godoc.org/github.com/btcsuite/btcutil/bloom) - -Full `go doc` style documentation for the project can be viewed online without -installing this package by using the GoDoc site here: -http://godoc.org/github.com/btcsuite/btcutil/bloom - -You can also view the documentation locally once the package is installed with -the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to -http://localhost:6060/pkg/github.com/btcsuite/btcutil/bloom - -## Installation +## Installation and Updating ```bash -$ go get github.com/btcsuite/btcutil/bloom +$ go get -u github.com/btcsuite/btcutil/bloom ``` ## Examples diff --git a/coinset/README.md b/coinset/README.md index d4ee38b2..94c867d1 100644 --- a/coinset/README.md +++ b/coinset/README.md @@ -1,8 +1,11 @@ coinset ======= -[![Build Status](https://travis-ci.org/btcsuite/btcutil.png?branch=master)] -(https://travis-ci.org/btcsuite/btcutil) +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] +(https://travis-ci.org/btcsuite/btcutil) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)] +(http://godoc.org/github.com/btcsuite/btcutil/coinset) Package coinset provides bitcoin-specific convenience functions for selecting from and managing sets of unspent transaction outpoints (UTXOs). @@ -10,25 +13,12 @@ from and managing sets of unspent transaction outpoints (UTXOs). A comprehensive suite of tests is provided to ensure proper functionality. See `test_coverage.txt` for the gocov coverage report. Alternatively, if you are running a POSIX OS, you can run the `cov_report.sh` script for a real-time -report. Package coinset is licensed under the liberal ISC license. +report. -## Documentation - -[![GoDoc](https://godoc.org/github.com/btcsuite/btcutil/coinset?status.png)] -(http://godoc.org/github.com/btcsuite/btcutil/coinset) - -Full `go doc` style documentation for the project can be viewed online without -installing this package by using the GoDoc site here: -http://godoc.org/github.com/btcsuite/btcutil/coinset - -You can also view the documentation locally once the package is installed with -the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to -http://localhost:6060/pkg/github.com/btcsuite/btcutil/coinset - -## Installation +## Installation and Updating ```bash -$ go get github.com/btcsuite/btcutil/coinset +$ go get -u github.com/btcsuite/btcutil/coinset ``` ## Usage diff --git a/hdkeychain/README.md b/hdkeychain/README.md index 4b913c18..842a2386 100644 --- a/hdkeychain/README.md +++ b/hdkeychain/README.md @@ -1,8 +1,11 @@ hdkeychain ========== -[![Build Status](https://travis-ci.org/btcsuite/btcutil.png?branch=master)] -(https://travis-ci.org/btcsuite/btcutil) +[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] +(https://travis-ci.org/btcsuite/btcutil) [![ISC License] +(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)] +(http://godoc.org/github.com/btcsuite/btcutil/hdkeychain) Package hdkeychain provides an API for bitcoin hierarchical deterministic extended keys (BIP0032). @@ -10,7 +13,7 @@ extended keys (BIP0032). A comprehensive suite of tests is provided to ensure proper functionality. See `test_coverage.txt` for the gocov coverage report. Alternatively, if you are running a POSIX OS, you can run the `cov_report.sh` script for a real-time -report. Package hdkeychain is licensed under the liberal ISC license. +report. ## Feature Overview @@ -35,23 +38,10 @@ report. Package hdkeychain is licensed under the liberal ISC license. - Comprehensive test coverage including the BIP0032 test vectors - Benchmarks -## Documentation - -[![GoDoc](https://godoc.org/github.com/btcsuite/btcutil/hdkeychain?status.png)] -(http://godoc.org/github.com/btcsuite/btcutil/hdkeychain) - -Full `go doc` style documentation for the project can be viewed online without -installing this package by using the GoDoc site here: -http://godoc.org/github.com/btcsuite/btcutil/hdkeychain - -You can also view the documentation locally once the package is installed with -the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to -http://localhost:6060/pkg/github.com/btcsuite/btcutil/hdkeychain - -## Installation +## Installation and Updating ```bash -$ go get github.com/btcsuite/btcutil/hdkeychain +$ go get -u github.com/btcsuite/btcutil/hdkeychain ``` ## Examples diff --git a/txsort/README.md b/txsort/README.md index 29a8fe6a..f180dd59 100644 --- a/txsort/README.md +++ b/txsort/README.md @@ -4,6 +4,8 @@ txsort [![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)] (https://travis-ci.org/btcsuite/btcutil) [![ISC License] (http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)] +(http://godoc.org/github.com/btcsuite/btcutil/txsort) Package txsort provides the transaction sorting according to BIPLI01. @@ -19,19 +21,6 @@ breaker. A comprehensive suite of tests is provided to ensure proper functionality. -## Documentation - -[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)] -(http://godoc.org/github.com/btcsuite/btcutil/txsort) - -Full `go doc` style documentation for the project can be viewed online without -installing this package by using the GoDoc site here: -http://godoc.org/github.com/btcsuite/btcutil/txsort - -You can also view the documentation locally once the package is installed with -the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to -http://localhost:6060/pkg/github.com/btcsuite/btcutil/txsort - ## Installation and Updating ```bash