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.
This commit is contained in:
parent
03f6069579
commit
5fd45e8085
2 changed files with 285 additions and 0 deletions
84
txsort.go
Normal file
84
txsort.go
Normal file
|
@ -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
|
||||||
|
}
|
201
txsort_test.go
Normal file
201
txsort_test.go
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue