-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathInclusionCertificate.ts
More file actions
204 lines (171 loc) · 6.7 KB
/
Copy pathInclusionCertificate.ts
File metadata and controls
204 lines (171 loc) · 6.7 KB
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import { StateId } from './StateId.js';
import { DataHash } from '../crypto/hash/DataHash.js';
import { DataHasher } from '../crypto/hash/DataHasher.js';
import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js';
import { FinalizedBranch } from '../smt/radix/FinalizedBranch.js';
import { FinalizedLeafBranch } from '../smt/radix/FinalizedLeafBranch.js';
import { SparseMerkleTreeRootNode } from '../smt/radix/SparseMerkleTreeRootNode.js';
import { BitString } from '../util/BitString.js';
import { HexConverter } from '../util/HexConverter.js';
import { dedent } from '../util/StringUtils.js';
import { areUint8ArraysEqual } from '../util/TypedArrayUtils.js';
/**
* Inclusion certificate for a leaf in a sparse Merkle tree.
*/
export class InclusionCertificate {
private static readonly BITMAP_SIZE = 32;
private static readonly MAX_DEPTH = 255;
private constructor(
private readonly bitmap: Uint8Array,
private readonly siblings: DataHash[],
) {}
/**
* Create an InclusionCertificate from a sparse Merkle tree root and leaf key.
*
* @param {SparseMerkleTreeRootNode} root Root node of the tree.
* @param {Uint8Array} key Leaf key.
* @returns {InclusionCertificate} New certificate.
* @throws {Error} If the path cannot be constructed.
*/
public static create(root: SparseMerkleTreeRootNode, key: Uint8Array): InclusionCertificate {
let node: FinalizedBranch | SparseMerkleTreeRootNode | null = root;
const siblings: DataHash[] = [];
const bitmap = new Uint8Array(InclusionCertificate.BITMAP_SIZE);
const keyPath = BitString.fromBytesReversedLSB(key).toBigInt();
while (node != null) {
if (node instanceof FinalizedLeafBranch) {
if (!areUint8ArraysEqual(node.key, key)) {
throw new Error(`Leaf not found for key: ${HexConverter.encode(key)}`);
}
return new InclusionCertificate(bitmap, siblings);
}
const isRight: bigint = (keyPath >> BigInt(node.depth)) & 1n;
const sibling = isRight ? node.left : node.right;
if (sibling != null) {
bitmap[Math.floor(node.depth / 8)] |= 1 << (node.depth % 8);
siblings.push(sibling.hash);
}
node = isRight ? node.right : node.left;
}
throw new Error('Could not construct inclusion certificate: Invalid path');
}
/**
* Decode an InclusionCertificate from its byte encoding.
*
* @param {Uint8Array} bytes Encoded certificate.
* @returns {InclusionCertificate} Decoded certificate.
* @throws {Error} If the encoding is malformed.
*/
public static decode(bytes: Uint8Array): InclusionCertificate {
if (bytes.length < InclusionCertificate.BITMAP_SIZE) {
throw new Error('Inclusion Certificate bitmap is invalid');
}
const siblingBytesLength = bytes.length - InclusionCertificate.BITMAP_SIZE;
if (siblingBytesLength % HashAlgorithm.SHA256.length !== 0) {
throw new Error('Inclusion Certificate siblings are misaligned');
}
let siblingsCount = 0;
for (let i = 0; i < InclusionCertificate.BITMAP_SIZE; i++) {
let x = bytes[i];
x = x - ((x >>> 1) & 0x55);
x = (x & 0x33) + ((x >>> 2) & 0x33);
x = (x + (x >>> 4)) & 0x0f;
siblingsCount += x;
}
if (siblingBytesLength / HashAlgorithm.SHA256.length !== siblingsCount) {
throw new Error('Inclusion proof siblings count does not match bitmap');
}
const siblings: DataHash[] = [];
for (let i = InclusionCertificate.BITMAP_SIZE; i < bytes.length; i += HashAlgorithm.SHA256.length) {
siblings.push(new DataHash(HashAlgorithm.SHA256, bytes.slice(i, i + HashAlgorithm.SHA256.length)));
}
return new InclusionCertificate(bytes.slice(0, InclusionCertificate.BITMAP_SIZE), siblings);
}
/**
* Reconstruct the sparse Merkle tree root hash for this certificate by hashing
* from the leaf upward.
*
* @param {StateId} leafKey Leaf key.
* @param {DataHash} leafValue Leaf value hash.
* @returns {Promise<DataHash>} Reconstructed root hash.
* @throws {Error} If the certificate siblings do not match the bitmap.
*/
public async calculateRoot(leafKey: StateId, leafValue: DataHash): Promise<DataHash> {
const key = leafKey.data;
const value = leafValue.data;
let hash = await new DataHasher(HashAlgorithm.SHA256)
.update(new Uint8Array([0x00]))
.update(key)
.update(value)
.digest();
const keyPath = BitString.fromBytesReversedLSB(key).toBigInt();
const bitmapPath = BitString.fromBytesReversedLSB(this.bitmap).toBigInt();
let position = this.siblings.length;
for (let depth = InclusionCertificate.MAX_DEPTH; depth >= 0; depth--) {
if (!((bitmapPath >> BigInt(depth)) & 1n)) continue;
position -= 1;
if (position < 0) {
throw new Error('Inclusion certificate has fewer siblings than its bitmap requires.');
}
const sibling = this.siblings[position];
let left: Uint8Array, right: Uint8Array;
if ((keyPath >> BigInt(depth)) & 1n) {
left = sibling.data;
right = hash.data;
} else {
left = hash.data;
right = sibling.data;
}
hash = await new DataHasher(HashAlgorithm.SHA256)
.update(new Uint8Array([0x01, depth]))
.update(left)
.update(right)
.digest();
}
if (position !== 0) {
throw new Error('Inclusion certificate has more siblings than its bitmap requires.');
}
return hash;
}
/**
* @returns {Uint8Array} Encoded certificate bytes.
*/
public encode(): Uint8Array {
const bytes = new Uint8Array(this.bitmap.length + this.siblings.length * HashAlgorithm.SHA256.length);
bytes.set(this.bitmap);
let position = this.bitmap.length;
for (const sibling of this.siblings) {
const data = sibling.data;
bytes.set(data, position);
position += data.length;
}
return bytes;
}
/**
* @returns {string} String representation of the inclusion certificate.
*/
public toString(): string {
return dedent`
Inclusion Certificate
Bitmap: ${HexConverter.encode(this.bitmap)}
Siblings: [
${this.siblings.map((sibling) => sibling.toString()).join('\n')}
]`;
}
/**
* Verify the certificate path against the expected root hash.
*
* @param {StateId} leafKey Leaf key.
* @param {DataHash} leafValue Leaf value hash.
* @param {DataHash} expectedRootHash Expected sparse Merkle tree root hash.
* @returns {Promise<boolean>} True if the path is valid.
*/
public async verify(leafKey: StateId, leafValue: DataHash, expectedRootHash: DataHash): Promise<boolean> {
try {
const root = await this.calculateRoot(leafKey, leafValue);
return root.equals(expectedRootHash);
} catch {
return false;
}
}
}