-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.ts
125 lines (113 loc) · 3.54 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
import {
Cbor,
Certificate,
compare,
HashTree,
HttpAgent,
lookup_path,
lookupResultToBuffer,
reconstruct,
} from "@dfinity/agent";
import { Principal } from "@dfinity/principal";
import logger from "./logger";
const areBuffersEqual = (buf1: ArrayBuffer, buf2: ArrayBuffer): boolean => {
return compare(buf1, buf2) === 0;
}
export const isMessageBodyValid = async (
canisterId: Principal,
path: string,
body: Uint8Array | ArrayBuffer,
certificate: ArrayBuffer,
tree: ArrayBuffer,
agent: HttpAgent,
maxCertificateAgeInMinutes: number,
): Promise<boolean> => {
let cert;
try {
cert = await Certificate.create({
certificate,
canisterId,
rootKey: agent.rootKey!,
maxAgeInMinutes: maxCertificateAgeInMinutes,
});
} catch (error) {
logger.error("[certification] Error creating certificate:", error);
return false;
}
const hashTree = Cbor.decode<HashTree>(tree);
const reconstructed = await reconstruct(hashTree);
const witnessLookupResult = cert.lookup([
"canister",
canisterId.toUint8Array(),
"certified_data"
]);
const witness = lookupResultToBuffer(witnessLookupResult);
if (!witness) {
throw new Error(
"Could not find certified data for this canister in the certificate."
);
}
// First validate that the Tree is as good as the certification.
if (!areBuffersEqual(witness, reconstructed)) {
logger.error("[certification] Witness != Tree passed in ic-certification");
return false;
}
// Next, calculate the SHA of the content.
const sha = await crypto.subtle.digest("SHA-256", body);
let treeShaLookupResult = lookup_path(["websocket", path], hashTree);
let treeSha = lookupResultToBuffer(treeShaLookupResult);
if (!treeSha) {
// Allow fallback to index path.
treeShaLookupResult = lookup_path(["websocket"], hashTree);
treeSha = lookupResultToBuffer(treeShaLookupResult);
}
if (!treeSha) {
// The tree returned in the certification header is wrong. Return false.
// We don't throw here, just invalidate the request.
logger.error(
`[certification] Invalid Tree in the header. Does not contain path ${JSON.stringify(
path
)}`
);
return false;
}
return !!treeSha && areBuffersEqual(sha, treeSha as ArrayBuffer);
};
export const safeExecute = async <T>(
fn: () => T | Promise<T>,
warnMessage: string
): Promise<T | undefined> => {
try {
return await Promise.resolve(fn());
} catch (error) {
logger.warn(warnMessage, error);
}
};
/**
* Generates a random unsigned 64-bit integer
* @returns {bigint} a random bigint
*/
export const randomBigInt = (): bigint => {
// determine whether browser crypto is available
if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
const array = new BigUint64Array(1);
window.crypto.getRandomValues(array);
return array[0];
}
// A second check for webcrypto, in case it is loaded under global instead of window
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
const array = new BigUint64Array(1);
crypto.getRandomValues(array);
return array[0];
}
// determine whether node crypto is available
// @ts-ignore
if (typeof crypto !== 'undefined' && crypto.randomBytes) {
// @ts-ignore
const randomBuffer = crypto.randomBytes(8);
const randomHexString = randomBuffer.toString('hex');
return BigInt('0x' + randomHexString);
}
// TODO: test these fallbacks in a node environment
throw new Error('Random UInt64 generation not supported in this environment');
};