chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* §5.3. The single canonical byte producer for a contract set.
|
||||
*
|
||||
* Node build scripts and the browser runtime share this function. Only the hash
|
||||
* adapter differs (Node `crypto` vs Web Crypto), so a digest can never diverge
|
||||
* because of JSON property order or a locale-sensitive sort.
|
||||
*/
|
||||
|
||||
export type ContractSetPackage = Readonly<{
|
||||
packageId: string;
|
||||
version: string;
|
||||
digest: `sha256:${string}`;
|
||||
runtimeProtocolVersion: 1;
|
||||
sourceRevision: string;
|
||||
}>;
|
||||
|
||||
export const CONTRACT_SET_ALGORITHM = "CA_CONTRACT_SET_V1" as const;
|
||||
|
||||
const HEADER = "CA_FRONTEND_CONTRACT_SET_V1\u0000";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
function compareUtf8(left: string, right: string): number {
|
||||
const a = encoder.encode(left);
|
||||
const b = encoder.encode(right);
|
||||
const shared = Math.min(a.length, b.length);
|
||||
for (let index = 0; index < shared; index += 1) {
|
||||
const difference = (a[index] as number) - (b[index] as number);
|
||||
if (difference !== 0) return difference;
|
||||
}
|
||||
return a.length - b.length;
|
||||
}
|
||||
|
||||
export function canonicalizeContractSet(
|
||||
packages: readonly ContractSetPackage[],
|
||||
): Uint8Array {
|
||||
const seen = new Set<string>();
|
||||
for (const entry of packages) {
|
||||
if (seen.has(entry.packageId)) {
|
||||
throw new TypeError("Duplicate contract set package identity.");
|
||||
}
|
||||
seen.add(entry.packageId);
|
||||
}
|
||||
|
||||
const sorted = [...packages].sort((left, right) =>
|
||||
compareUtf8(left.packageId, right.packageId),
|
||||
);
|
||||
|
||||
const chunks: Uint8Array[] = [encoder.encode(HEADER)];
|
||||
for (const entry of sorted) {
|
||||
appendString(chunks, entry.packageId);
|
||||
appendString(chunks, entry.version);
|
||||
appendString(chunks, entry.digest);
|
||||
chunks.push(u32be(entry.runtimeProtocolVersion));
|
||||
appendString(chunks, entry.sourceRevision);
|
||||
}
|
||||
|
||||
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
||||
const output = new Uint8Array(total);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
output.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function appendString(chunks: Uint8Array[], value: string): void {
|
||||
const bytes = encoder.encode(value);
|
||||
chunks.push(u32be(bytes.length));
|
||||
chunks.push(bytes);
|
||||
}
|
||||
|
||||
function u32be(value: number): Uint8Array {
|
||||
if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {
|
||||
throw new TypeError("Contract set length prefix is out of range.");
|
||||
}
|
||||
const bytes = new Uint8Array(4);
|
||||
new DataView(bytes.buffer).setUint32(0, value, false);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function toLowerHex(digest: ArrayBuffer | Uint8Array): string {
|
||||
const bytes =
|
||||
digest instanceof Uint8Array ? digest : new Uint8Array(digest);
|
||||
let output = "";
|
||||
for (const byte of bytes) output += byte.toString(16).padStart(2, "0");
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Browser-side digest. Node callers pass their own `crypto.createHash` adapter
|
||||
* through {@link computeContractSetDigestWith}.
|
||||
*/
|
||||
export async function computeContractSetDigest(
|
||||
packages: readonly ContractSetPackage[],
|
||||
): Promise<`sha256:${string}`> {
|
||||
const bytes = canonicalizeContractSet(packages);
|
||||
const buffer = await crypto.subtle.digest(
|
||||
"SHA-256",
|
||||
bytes.slice().buffer as ArrayBuffer,
|
||||
);
|
||||
return `sha256:${toLowerHex(buffer)}`;
|
||||
}
|
||||
|
||||
export function computeContractSetDigestWith(
|
||||
packages: readonly ContractSetPackage[],
|
||||
sha256: (bytes: Uint8Array) => Uint8Array,
|
||||
): `sha256:${string}` {
|
||||
return `sha256:${toLowerHex(sha256(canonicalizeContractSet(packages)))}`;
|
||||
}
|
||||
Reference in New Issue
Block a user