-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.ts
177 lines (147 loc) · 4.39 KB
/
utils.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
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
import * as blake3 from "blake3";
import { createHash } from "crypto";
export type PubkeyBytes = Uint8Array;
export type Hash = Uint8Array;
export interface Proof {
path: number[];
siblings: Hash[][];
}
export interface Data {
pubkey: PubkeyBytes;
hash: Hash;
account: AccountInfo;
}
export interface AccountDeltaProof {
key: PubkeyBytes;
data: Data;
proof: Proof;
}
export interface BankHashProof {
proofs: AccountDeltaProof[];
numSigs: bigint; // u64 is represented as bigint in TypeScript
accountDeltaRoot: Hash;
parentBankhash: Hash;
blockhash: Hash;
}
export interface Update {
slot: bigint; // u64 is represented as bigint in TypeScript
root: Hash;
proof: BankHashProof;
}
export interface AccountInfo {
pubkey: Uint8Array;
lamports: number;
owner: Uint8Array;
executable: boolean;
rent_epoch: number;
data: Uint8Array;
}
export interface AccountData {
account: AccountInfo;
hash: Hash;
}
// Util helper function to calculate the hash of a Solana account
// https://github.com/solana-labs/solana/blob/v1.16.15/runtime/src/accounts_db.rs#L6076-L6118
// We can see as we make the code more resilient to see if we can also make
// the structures match and use the function from solana-sdk, but currently it seems a bit more
// complicated and lower priority, since getting a stable version working is top priority
export async function hashSolanaAccount(
lamports: number,
owner: Uint8Array,
executable: boolean,
rent_epoch: number,
data: Uint8Array,
pubkey: Uint8Array,
): Promise<Uint8Array> {
if (lamports === 0) {
return new Uint8Array(32).fill(8);
}
await blake3.load();
const hasher = blake3.createHash();
hasher.update(new Uint8Array(new BigUint64Array([BigInt(lamports)]).buffer));
hasher.update(
new Uint8Array(new BigUint64Array([BigInt(rent_epoch)]).buffer),
);
hasher.update(data);
if (executable) {
hasher.update(new Uint8Array([1]));
} else {
hasher.update(new Uint8Array([0]));
}
hasher.update(owner);
hasher.update(pubkey);
return hasher.digest();
}
export function hashv(hashes: Uint8Array[]): Uint8Array {
const hasher = createHash("sha256");
for (const hash of hashes) {
hasher.update(Buffer.from(hash));
}
return hasher.digest();
}
export function verifyProof(leafHash: Hash, proof: Proof, root: Hash): boolean {
if (proof.path.length !== proof.siblings.length) {
return false;
}
let currentHash = new Uint8Array(leafHash);
for (let i = 0; i < proof.path.length; i++) {
const indexInChunk = proof.path[i];
const siblingHashes = proof.siblings[i];
const hasher = createHash("sha256");
for (let j = 0; j < indexInChunk; j++) {
hasher.update(Buffer.from(siblingHashes[j]));
}
hasher.update(Buffer.from(currentHash));
for (let j = indexInChunk; j < siblingHashes.length; j++) {
hasher.update(Buffer.from(siblingHashes[j]));
}
currentHash = hasher.digest();
}
return Buffer.from(currentHash).equals(Buffer.from(root));
}
export async function verifyLeavesAgainstBankhash(
accountProof: AccountDeltaProof,
bankhash: Hash,
numSigs: bigint,
accountDeltaRoot: Hash,
parentBankhash: Hash,
blockhash: Hash,
): Promise<void> {
const pubkey = accountProof.key;
const data = accountProof.data;
const proof = accountProof.proof;
if (!Buffer.from(data.account.pubkey).equals(Buffer.from(pubkey))) {
throw new Error(
"account info pubkey doesn't match pubkey in provided update",
);
}
const computedAccountHash = await hashSolanaAccount(
data.account.lamports,
data.account.owner,
data.account.executable,
data.account.rent_epoch,
data.account.data,
data.account.pubkey,
);
if (!Buffer.from(data.hash).equals(Buffer.from(computedAccountHash))) {
throw new Error("account data does not match account hash");
}
const computedBankhash = hashv([
parentBankhash,
accountDeltaRoot,
new Uint8Array(new BigUint64Array([numSigs]).buffer),
blockhash,
]);
if (!Buffer.from(bankhash).equals(Buffer.from(computedBankhash))) {
throw new Error("bank hash does not match data");
}
if (!verifyProof(data.hash, proof, accountDeltaRoot)) {
throw new Error("account merkle proof verification failure");
}
}
export function int32ToBytesLE(num: number): Uint8Array {
const bytes = new Uint8Array(4);
const view = new DataView(bytes.buffer);
view.setUint32(0, num, true);
return bytes;
}