Skip to content

Commit b325da8

Browse files
committed
backend: replace BIP69 with random sorting for tx inputs/outputs
This commit updates the sorting method for transaction inputs and outputs, replacing the previous BIP69 lexicographical sorting with a randomized approach. BIP69, or "Lexicographic Order of Transaction Inputs and Outputs," was initially proposed to enhance privacy and mitigate blockchain fingerprinting. However, due to its limited adoption and effectiveness, it has been replaced by random shuffling of inputs and outputs, offering improved privacy benefits.
1 parent 6a7b89f commit b325da8

File tree

4 files changed

+97
-7
lines changed

4 files changed

+97
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Replace the existing BIP69 lexicographical sorting of tx inputs/outputs with a randomized sorting approach
6+
57
## 4.41.0
68
- New feature: insure your bitcoins through Bitsurance
79
- Bundle BitBox02 firmware version v9.16.0

backend/coins/btc/maketx/maketx.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
package maketx
1616

1717
import (
18+
"crypto/rand"
19+
"encoding/binary"
20+
mrand "math/rand"
1821
"sort"
22+
"time"
1923

2024
"github.com/btcsuite/btcd/btcutil"
21-
"github.com/btcsuite/btcd/btcutil/txsort"
2225
"github.com/btcsuite/btcd/chaincfg/chainhash"
2326
"github.com/btcsuite/btcd/wire"
2427
"github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/errors"
@@ -168,7 +171,10 @@ func NewTxSpendAll(
168171
TxOut: []*wire.TxOut{output},
169172
LockTime: 0,
170173
}
171-
txsort.InPlaceSort(unsignedTransaction)
174+
175+
secureRand := mrand.New(mrand.NewSource(secureSeed()))
176+
shuffleTxInputsAndOutputs(unsignedTransaction, secureRand)
177+
172178
log.WithField("fee", maxRequiredFee).Debug("Preparing transaction to spend all outputs")
173179

174180
setRBF(coin, unsignedTransaction)
@@ -249,7 +255,10 @@ func NewTx(
249255
} else {
250256
changeAddress = nil
251257
}
252-
txsort.InPlaceSort(unsignedTransaction)
258+
259+
secureRand := mrand.New(mrand.NewSource(secureSeed()))
260+
shuffleTxInputsAndOutputs(unsignedTransaction, secureRand)
261+
253262
log.WithField("fee", finalFee).Debug("Preparing transaction")
254263

255264
setRBF(coin, unsignedTransaction)
@@ -263,3 +272,26 @@ func NewTx(
263272
}, nil
264273
}
265274
}
275+
276+
// shuffleTxInputsAndOutputs shuffles both the TxIn and TxOut slices of a wire.MsgTx.
277+
func shuffleTxInputsAndOutputs(tx *wire.MsgTx, secureRand *mrand.Rand) {
278+
// Shuffle inputs
279+
secureRand.Shuffle(len(tx.TxIn), func(i, j int) {
280+
tx.TxIn[i], tx.TxIn[j] = tx.TxIn[j], tx.TxIn[i]
281+
})
282+
283+
// Shuffle outputs
284+
secureRand.Shuffle(len(tx.TxOut), func(i, j int) {
285+
tx.TxOut[i], tx.TxOut[j] = tx.TxOut[j], tx.TxOut[i]
286+
})
287+
}
288+
289+
// secureSeed generates a secure seed value.
290+
func secureSeed() int64 {
291+
var b [8]byte
292+
_, err := rand.Read(b[:])
293+
if err != nil {
294+
return time.Now().UnixNano() // use timestamp as fallback seed
295+
}
296+
return int64(binary.LittleEndian.Uint64(b[:]))
297+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2024 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package maketx
16+
17+
import (
18+
"math/rand"
19+
"testing"
20+
21+
"github.com/btcsuite/btcd/wire"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestShuffleTxInputsAndOutputs(t *testing.T) {
26+
// Create transaction inputs and outputs
27+
txIns := []*wire.TxIn{
28+
wire.NewTxIn(&wire.OutPoint{Hash: [32]byte{0x01}, Index: 0}, nil, nil),
29+
wire.NewTxIn(&wire.OutPoint{Hash: [32]byte{0x02}, Index: 0}, nil, nil),
30+
wire.NewTxIn(&wire.OutPoint{Hash: [32]byte{0x03}, Index: 0}, nil, nil),
31+
}
32+
txOuts := []*wire.TxOut{
33+
{Value: 1000000, PkScript: []byte{}},
34+
{Value: 2000000, PkScript: []byte{}},
35+
{Value: 3000000, PkScript: []byte{}},
36+
}
37+
38+
// Create a new transaction and add inputs and outputs
39+
tx := wire.NewMsgTx(wire.TxVersion)
40+
tx.TxIn = txIns
41+
tx.TxOut = txOuts
42+
// Shuffle with constant seed
43+
testRand := rand.New(rand.NewSource(1000))
44+
shuffleTxInputsAndOutputs(tx, testRand)
45+
// Expected sorted inputs and outputs
46+
expectedSortedIns := []*wire.TxIn{
47+
wire.NewTxIn(&wire.OutPoint{Hash: [32]byte{0x02}, Index: 0}, nil, nil),
48+
wire.NewTxIn(&wire.OutPoint{Hash: [32]byte{0x01}, Index: 0}, nil, nil),
49+
wire.NewTxIn(&wire.OutPoint{Hash: [32]byte{0x03}, Index: 0}, nil, nil),
50+
}
51+
expectedSortedOuts := []*wire.TxOut{
52+
{Value: 3000000, PkScript: []byte{}},
53+
{Value: 1000000, PkScript: []byte{}},
54+
{Value: 2000000, PkScript: []byte{}},
55+
}
56+
57+
// Compare the shuffled inputs and outputs with the expected sorted ones
58+
require.Equal(t, expectedSortedIns, tx.TxIn, "The transaction inputs were not successfully shuffled.")
59+
require.Equal(t, expectedSortedOuts, tx.TxOut, "The transaction outputs were not successfully shuffled.")
60+
}

backend/coins/btc/sign.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
package btc
1616

1717
import (
18-
"github.com/btcsuite/btcd/btcutil/txsort"
1918
"github.com/btcsuite/btcd/chaincfg/chainhash"
2019
"github.com/btcsuite/btcd/txscript"
2120
"github.com/btcsuite/btcd/wire"
@@ -92,9 +91,6 @@ func (account *Account) signTransaction(
9291

9392
func txValidityCheck(transaction *wire.MsgTx, previousOutputs maketx.PreviousOutputs,
9493
sigHashes *txscript.TxSigHashes) error {
95-
if !txsort.IsSorted(transaction) {
96-
return errp.New("tx not bip69 conformant")
97-
}
9894
for index, txIn := range transaction.TxIn {
9995
spentOutput, ok := previousOutputs[txIn.PreviousOutPoint]
10096
if !ok {

0 commit comments

Comments
 (0)