forked from ethers/bitcoin-proof
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
152 lines (129 loc) · 3.98 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/** Produces a Merkle proof for a single tx within a list. */
export async function getProof(txIds: string[], txIndex: number) {
const proof = {
txId: txIds[txIndex],
txIndex: txIndex,
sibling: [],
};
let tree = new Array(txIds.length);
for (let i = 0; i < tree.length; ++i) {
tree[i] = reverse(fromHex(txIds[i]));
}
let target = tree[txIndex];
while (tree.length !== 1) {
const newTree = new Array(~~((tree.length + 1) / 2));
for (let j = 0; j < tree.length; j += 2) {
const hash1 = tree[j];
const hash2 = tree[Math.min(j + 1, tree.length - 1)];
newTree[j / 2] = await sha256x2(hash1, hash2);
if (isEqual(target, hash1)) {
proof.sibling.push(toHex(reverse(hash2)));
target = newTree[j / 2];
} else if (isEqual(target, hash2)) {
proof.sibling.push(toHex(reverse(hash1)));
target = newTree[j / 2];
}
}
tree = newTree;
}
return proof;
}
export interface TxMerkleProof {
txId: string;
txIndex: number;
sibling: string[];
}
/** Evaluates a transaction merkle proof, returning the root hash. */
export async function getTxMerkle(proofObj: TxMerkleProof) {
let target = reverse(fromHex(proofObj.txId));
let txIndex = proofObj.txIndex;
const sibling = proofObj.sibling;
for (let i = 0; i < proofObj.sibling.length; ++i, txIndex = ~~(txIndex / 2)) {
if (txIndex % 2 === 1) {
target = await sha256x2(reverse(fromHex(sibling[i])), target);
} else {
target = await sha256x2(target, reverse(fromHex(sibling[i])));
}
}
return toHex(reverse(target));
}
/** Computes the Merkle root of a list of Bitcoin transaction IDs. */
export async function getMerkleRoot(txIds: string[]) {
let tree = new Array(txIds.length) as Uint8Array[];
for (let i = 0; i < tree.length; ++i) {
tree[i] = reverse(fromHex(txIds[i]));
}
while (tree.length !== 1) {
const newTree = new Array(~~((tree.length + 1) / 2));
for (let j = 0; j < tree.length; j += 2) {
const hash1 = tree[j];
const hash2 = tree[Math.min(j + 1, tree.length - 1)];
newTree[j / 2] = await sha256x2(hash1, hash2);
}
tree = newTree;
}
const ret = toHex(reverse(tree[0]));
return ret;
}
// This ugly construction is required to make a library that bundles without
// error for browser use, but still uses Node builtins when running in node.
let nodeCrypto = null;
try {
nodeCrypto = require("crypto");
} catch (_) {}
/** Computes a double-SHA256 hash of [a, b]. Async in-browser. */
async function sha256x2(
buf1: Uint8Array,
buf2: Uint8Array
): Promise<Uint8Array> {
if (nodeCrypto) {
// Synchronous native SHA256 via require("crypto")
const hash1 = nodeCrypto
.createHash("sha256")
.update(buf1)
.update(buf2)
.digest();
const hash2 = nodeCrypto.createHash("sha256").update(hash1).digest();
return hash2;
} else {
// Asynchronous native SHA256 via SubtleCrypto
const comb = new Uint8Array(buf1.length + buf2.length);
comb.set(buf1, 0);
comb.set(buf2, buf1.length);
const hash1 = await crypto.subtle.digest("SHA-256", comb);
const hash2 = await crypto.subtle.digest("SHA-256", hash1);
return new Uint8Array(hash2);
}
}
/** Reverse a byte array in-place. */
function reverse(buf: Uint8Array) {
return buf.reverse();
}
/** Check deep equality between two byte arrays. */
function isEqual(buf1: Uint8Array, buf2: Uint8Array) {
if (buf1.length !== buf2.length) {
return false;
}
for (let i = 0; i < buf1.length; ++i) {
if (buf1[i] !== buf2[i]) {
return false;
}
}
return true;
}
/** Parses hex to a Uint8Array */
function fromHex(hex: string): Uint8Array {
return new Uint8Array(
hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16);
})
);
}
/** Print Uint8Array to hex */
function toHex(arr: Uint8Array): string {
return Array.prototype.map
.call(arr, function (byte: number) {
return ("0" + (byte & 0xff).toString(16)).slice(-2);
})
.join("");
}