UNPKG

@dfinity/agent

Version:

JavaScript and TypeScript library to interact with the Internet Computer

93 lines (82 loc) 3.39 kB
import { lebEncode, compare } from '@dfinity/candid'; import { Principal } from '@dfinity/principal'; import { HashValueErrorCode, InputError } from './errors.ts'; import { uint8FromBufLike } from './utils/buffer.ts'; import { concatBytes } from '@noble/hashes/utils'; import { sha256 } from '@noble/hashes/sha2'; export type RequestId = Uint8Array & { __requestId__: void }; interface ToHashable { toHash(): unknown; } /** * * @param value unknown value * @returns Uint8Array */ export function hashValue(value: unknown): Uint8Array { if (typeof value === 'string') { return hashString(value); } else if (typeof value === 'number') { return sha256(lebEncode(value)); } else if (value instanceof Uint8Array || ArrayBuffer.isView(value)) { return sha256(uint8FromBufLike(value)); } else if (Array.isArray(value)) { const vals = value.map(hashValue); return sha256(concatBytes(...vals)); } else if (value && typeof value === 'object' && (value as Principal)._isPrincipal) { return sha256((value as Principal).toUint8Array()); } else if ( typeof value === 'object' && value !== null && typeof (value as ToHashable).toHash === 'function' ) { return hashValue((value as ToHashable).toHash()); // TODO This should be move to a specific async method as the webauthn flow required // the flow to be synchronous to ensure Safari touch id works. // } else if (value instanceof Promise) { // return value.then(x => hashValue(x)); } else if (typeof value === 'object') { return hashOfMap(value as Record<string, unknown>); } else if (typeof value === 'bigint') { // Do this check much later than the other bigint check because this one is much less // type-safe. // So we want to try all the high-assurance type guards before this 'probable' one. return sha256(lebEncode(value)); } throw InputError.fromCode(new HashValueErrorCode(value)); } const hashString = (value: string): Uint8Array => { const encoded = new TextEncoder().encode(value); return sha256(encoded); }; /** * Get the RequestId of the provided ic-ref request. * RequestId is the result of the representation-independent-hash function. * https://sdk.dfinity.org/docs/interface-spec/index.html#hash-of-map * @param request - ic-ref request to hash into RequestId */ export function requestIdOf(request: Record<string, unknown>): RequestId { return hashOfMap(request) as RequestId; } /** * Hash a map into a Uint8Array using the representation-independent-hash function. * https://sdk.dfinity.org/docs/interface-spec/index.html#hash-of-map * @param map - Any non-nested object * @returns Uint8Array */ export function hashOfMap(map: Record<string, unknown>): Uint8Array { const hashed: Array<[Uint8Array, Uint8Array]> = Object.entries(map) .filter(([, value]) => value !== undefined) .map(([key, value]: [string, unknown]) => { const hashedKey = hashString(key); const hashedValue = hashValue(value); return [hashedKey, hashedValue] as [Uint8Array, Uint8Array]; }); const traversed: Array<[Uint8Array, Uint8Array]> = hashed; const sorted: Array<[Uint8Array, Uint8Array]> = traversed.sort(([k1], [k2]) => { return compare(k1, k2); }); const concatenated = concatBytes(...sorted.map(x => concatBytes(...x))); const result = sha256(concatenated); return result; }