Coverage Summary for Class: MerkleTreeUtils (co.rsk.peg.utils)
Class |
Class, %
|
Method, %
|
Line, %
|
MerkleTreeUtils |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/37)
|
1 package co.rsk.peg.utils;
2
3 import co.rsk.bitcoinj.core.Sha256Hash;
4 import co.rsk.bitcoinj.core.Utils;
5 import co.rsk.bitcoinj.core.VerificationException;
6
7 import static co.rsk.bitcoinj.core.Utils.reverseBytes;
8
9 public class MerkleTreeUtils {
10 // coinbase to check is part of a valid 64 byte tx
11 private static long COINBASE_CHECKED = Utils.readUint32(new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF}, 0);
12
13 private MerkleTreeUtils() {}
14
15 /**
16 * Combines two hashes (representing nodes in a merkle tree) to produce a single hash
17 * that would be the parent of these two nodes.
18 *
19 * @param left The left hand side node bytes
20 * @param right The right hand side node bytes
21 * @return
22 */
23 public static Sha256Hash combineLeftRight(Sha256Hash left, Sha256Hash right) {
24 byte[] leftBytes = left.getBytes();
25 byte[] rightBytes = right.getBytes();
26 checkNotAValid64ByteTransaction(leftBytes, rightBytes);
27 return Sha256Hash.wrapReversed(
28 Sha256Hash.hashTwice(
29 reverseBytes(leftBytes), 0, 32,
30 reverseBytes(rightBytes), 0, 32
31 )
32 );
33 }
34
35
36 /**
37 * Checks supplied bytes DO NOT represent a valid bitcoin transaction.
38 * Fixes attack described on https://bitslog.wordpress.com/2018/06/09/leaf-node-weakness-in-bitcoin-merkle-tree-design/
39 * @throws VerificationException if bytes DO represent a valid bitcoin transaction.
40 */
41 private static void checkNotAValid64ByteTransaction(byte[] left, byte[] right) {
42 byte[] leftAndRight = new byte[left.length + right.length];
43 System.arraycopy(left, 0, leftAndRight, 0, 32);
44 System.arraycopy(right, 0, leftAndRight, 32, 32);
45
46 int _offset = 0;
47
48 // Skip version
49 _offset += 4;
50
51 // Check inputs count
52 byte inputCount = leftAndRight[_offset];
53 _offset += 1;
54 if (inputCount != 1) {
55 // Only 1 input fits in 64 bytes
56 return;
57 }
58
59 // Skip input 0 outpoint hash
60 _offset += 32;
61
62 // Check output 0 index
63 long output0Index = Utils.readUint32(leftAndRight, _offset);
64 _offset += 4;
65 if (output0Index > 1000000 && output0Index != COINBASE_CHECKED){
66 // this value is capped by max btc tx size
67 // and should check also is not a valid coinbase expressed in a byte array
68 return;
69 }
70
71 // Check input 0 script length
72 byte input0ScriptLength = leftAndRight[_offset];
73 _offset += 1;
74 if (input0ScriptLength < 0 || input0ScriptLength > 4) {
75 // Script length should be 0 to 4 to create a valid 64 byte tx
76 return;
77 }
78
79 // Skip input 0 script
80 _offset += input0ScriptLength;
81
82 // Skip input 0 sequence
83 _offset += 4;
84
85 // Check output count
86 byte outputCount = leftAndRight[_offset];
87 _offset += 1;
88 if (outputCount != 1) {
89 // Only 1 output fits in 64 bytes
90 return;
91 }
92
93 // check output 0 value
94 long output0Value = Utils.readInt64(leftAndRight, _offset);
95 _offset += 8;
96 long maxNumberOfSatoshis = 21000000l * 100000000l;
97 if (output0Value < 1 || output0Value > maxNumberOfSatoshis) {
98 // 0 < value < 21 millions (expressed in satoshis) validation failed
99 return;
100 }
101
102 // check output 0 script length
103 byte output0ScriptLength = leftAndRight[_offset];
104 if (output0ScriptLength < 0 || output0ScriptLength > 4) {
105 // Script length should be 0 to 4 to create a valid 64 byte tx
106 return;
107 }
108
109 // check input 0 script length + output 0 script length
110 if ((input0ScriptLength + output0ScriptLength) != 4) {
111 // Script length should be 0 to 4
112 return;
113 }
114
115 throw new VerificationException("Supplied nodes form a valid btc transaction");
116 }
117 }