mina-attestations
Version:
Private Attestations on Mina
289 lines (257 loc) • 7.05 kB
text/typescript
export {
assert,
assertDefined,
defined,
Required,
assertHasProperty,
assertHasMethod,
hasProperty,
isObject,
assertIsObject,
notImplemented,
zip,
chunk,
chunkString,
pad,
fill,
arrayEqual,
mapObject,
mapToObject,
mapEntries,
zipObjects,
assertExtendsShape,
isSubclass,
stringLength,
mod,
ByteUtils,
};
function assert(
condition: boolean,
message?: string | (() => string | undefined)
): asserts condition {
if (!condition) {
message = typeof message === 'function' ? message() : message;
throw Error(message ?? 'Assertion failed');
}
}
function assertDefined<T>(
input: T | undefined,
message?: string
): asserts input is T {
if (input === undefined) {
throw Error(message ?? 'Input is undefined');
}
}
function defined<T>(input: T | undefined, message?: string): T {
assertDefined(input, message);
return input;
}
function Required<T extends {}>(
t: T
): {
[P in keyof T]-?: T[P];
} {
return new Proxy(t, {
get(target, key) {
return defined(
(target as any)[key],
`Property "${String(key)}" is undefined`
);
},
}) as Required<T>;
}
function isObject(obj: unknown): obj is Record<string, unknown> {
return (typeof obj === 'object' && obj !== null) || typeof obj === 'function';
}
function assertIsObject(
obj: unknown,
message?: string
): asserts obj is object | Function {
assert(
(typeof obj === 'object' && obj !== null) || typeof obj === 'function',
() => {
// console.log('not an object:', obj);
return message;
}
);
}
function assertHasProperty<K extends string>(
obj: unknown,
key: K,
message?: string
): asserts obj is Record<K, unknown> {
assertIsObject(obj, message ?? `Expected value to be an object or function`);
assert(key in obj, message ?? `Expected object to have property ${key}`);
}
function assertHasMethod<K extends string>(
obj: unknown,
key: K,
message?: string
): asserts obj is Record<K, Function> {
assertHasProperty(obj, key, message);
assert(typeof obj[key] === 'function', message);
}
function hasProperty<K extends string>(
obj: unknown,
key: K
): obj is Record<K, unknown> {
return (
((typeof obj === 'object' && obj !== null) || typeof obj === 'function') &&
key in obj
);
}
function notImplemented(): never {
throw Error('Not implemented');
}
function zip<T, S>(a: T[], b: S[]) {
assert(a.length === b.length, 'zip(): arrays of unequal length');
return a.map((a, i): [T, S] => [a, b[i]!]);
}
function chunk<T>(array: T[], size: number): T[][] {
assert(
array.length % size === 0,
`${array.length} is not a multiple of ${size}`
);
return Array.from({ length: array.length / size }, (_, i) =>
array.slice(size * i, size * (i + 1))
);
}
function chunkString(str: string, size: number): string[] {
return chunk([...str], size).map((chunk) => chunk.join(''));
}
function pad<T>(array: T[], size: number, value: T | (() => T)): T[] {
assert(
array.length <= size,
`padding array of size ${array.length} larger than target size ${size}`
);
let cb: () => T =
typeof value === 'function' ? (value as () => T) : () => value;
return array.concat(Array.from({ length: size - array.length }, cb));
}
function fill<T>(size: number, value: T | (() => T)): T[] {
let cb: () => T =
typeof value === 'function' ? (value as () => T) : () => value;
return Array.from({ length: size }, cb);
}
function arrayEqual(
aI: unknown[] | Uint8Array | ArrayBuffer,
bI: unknown[] | Uint8Array | ArrayBuffer
): boolean {
let a = aI instanceof ArrayBuffer ? new Uint8Array(aI) : aI;
let b = bI instanceof ArrayBuffer ? new Uint8Array(bI) : bI;
let n = a.length;
if (n !== b.length) return false;
for (let i = 0; i < n; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
function mapObject<
T extends Record<string, any>,
S extends Record<keyof T, any>
>(obj: T, fn: <K extends keyof T>(value: T[K], key: K) => S[K]): S {
let result = {} as S;
for (let key in obj) {
result[key] = fn(obj[key], key);
}
return result;
}
function mapToObject<
Key extends string | number | symbol,
F extends <K extends Key>(key: K, i: number) => any
>(keys: Key[], fn: F) {
let s = {} as { [K in Key]: ReturnType<F> };
keys.forEach((key, i) => {
s[key] = fn(key, i);
});
return s;
}
function zipObjects<
T extends Record<string, any>,
S extends Record<keyof T, any>
>(t: T, s: S) {
assertExtendsShape(t, s);
assertExtendsShape(s, t);
return mapObject<T, { [K in keyof T]: [T[K], S[K]] }>(t, (t, key) => [
t,
s[key],
]);
}
function mapEntries<T extends Record<string, any>, S>(
obj: T,
fn: (key: keyof T & string, value: T[keyof T & string]) => S
): S[] {
return Object.entries(obj).map((entry) => fn(...entry));
}
function assertExtendsShape<B extends Record<string, any>>(
a: object,
b: B
): asserts a is Record<keyof B, any> {
for (let key in b) {
if (!(key in a)) throw Error(`Expected object to have property ${key}`);
}
}
type Constructor<T> = new (...args: any) => T;
function isSubclass<B extends Constructor<any>>(
constructor: unknown,
base: B
): constructor is B {
if (typeof constructor !== 'function') return false;
if (!hasProperty(constructor, 'prototype')) return false;
return constructor.prototype instanceof base;
}
const enc = new TextEncoder();
const dec = new TextDecoder();
function stringLength(str: string): number {
return enc.encode(str).length;
}
// modulo that properly handles negative numbers
function mod(x: bigint, p: bigint): bigint {
let z = x % p;
return z < 0 ? z + p : z;
}
const ByteUtils = {
fromString(str: string) {
return enc.encode(str);
},
toString(bytes: Uint8Array) {
return dec.decode(bytes);
},
fromHex(hex: string) {
if (hex.startsWith('0x')) hex = hex.slice(2);
let bytes = chunkString(hex, 2).map((byte) => parseInt(byte, 16));
return new Uint8Array(bytes);
},
toHex(bytes: Uint8Array) {
return bytes.reduce(
(hex, byte) => hex + byte.toString(16).padStart(2, '0'),
''
);
},
padStart(bytes: Uint8Array, size: number, value: number): Uint8Array {
assert(bytes.length <= size, 'Bytes.padStart(): bytes larger than size');
if (bytes.length === size) return bytes;
let a = new Uint8Array(size);
a.fill(value, 0, size - bytes.length);
a.set(bytes, size - bytes.length);
return a;
},
padEnd(bytes: Uint8Array, size: number, value: number): Uint8Array {
assert(bytes.length <= size, 'Bytes.padEnd(): bytes larger than size');
if (bytes.length === size) return bytes;
let a = new Uint8Array(size);
a.set(bytes);
a.fill(value, bytes.length);
return a;
},
concat(...arrays: Uint8Array[]): Uint8Array {
let size = arrays.reduce((s, a) => s + a.length, 0);
let a = new Uint8Array(size);
let offset = 0;
arrays.forEach((b) => {
a.set(b, offset);
offset += b.length;
});
return a;
},
};