From fe2a6899e800cc844424c6424db805e31eb595c5 Mon Sep 17 00:00:00 2001 From: Matthew Brisebois Date: Thu, 27 Jun 2024 13:40:52 -0600 Subject: [PATCH 1/4] Use @spartan-hc/holo-hash for base64 utils --- package-lock.json | 20 +++++++++++++++++--- package.json | 1 + src/utils/base64.ts | 13 ++++++++----- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7cd4562c..89a49952 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@bitgo/blake2b": "^3.2.4", "@holochain/serialization": "^0.1.0-beta-rc.3", "@msgpack/msgpack": "^2.8.0", + "@spartan-hc/holo-hash": "^0.7.0", "emittery": "^1.0.1", "isomorphic-ws": "^5.0.0", "js-base64": "^3.7.5", @@ -806,6 +807,14 @@ "string-argv": "~0.3.1" } }, + "node_modules/@spartan-hc/holo-hash": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@spartan-hc/holo-hash/-/holo-hash-0.7.0.tgz", + "integrity": "sha512-RtA+OXiteatppEvpGQ0WvAUf+MA5TLa/C8ota5S41W0xmKQfe9IQ9n/hTFRd0tBG4eYXfnz65TDDR1q3VIVOTw==", + "dependencies": { + "@whi/xor-digest": "^0.1.0" + } + }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -1076,6 +1085,11 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@whi/xor-digest": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@whi/xor-digest/-/xor-digest-0.1.0.tgz", + "integrity": "sha512-FKPxyYK79TIxkVHmXnyJ4IljW+5sc+HX2hxMu5+UxlN0p1vOOdwFagDOhONC4ILysm1bXhvF4cPsTGSmlTKenQ==" + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -4089,9 +4103,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index a8ea7329..6fd713e1 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@bitgo/blake2b": "^3.2.4", "@holochain/serialization": "^0.1.0-beta-rc.3", "@msgpack/msgpack": "^2.8.0", + "@spartan-hc/holo-hash": "^0.7.0", "emittery": "^1.0.1", "isomorphic-ws": "^5.0.0", "js-base64": "^3.7.5", diff --git a/src/utils/base64.ts b/src/utils/base64.ts index 959230bd..6c53fa2a 100644 --- a/src/utils/base64.ts +++ b/src/utils/base64.ts @@ -1,5 +1,6 @@ import { Base64 } from "js-base64"; -import { HoloHash, HoloHashB64 } from "../types.js"; +import { HoloHashB64 } from "../types.js"; +import { HoloHash } from "@spartan-hc/holo-hash"; /** * Decodes a Base64 encoded string to a byte array hash. @@ -9,8 +10,8 @@ import { HoloHash, HoloHashB64 } from "../types.js"; * * @public */ -export function decodeHashFromBase64(hash: HoloHashB64): HoloHash { - return Base64.toUint8Array(hash.slice(1)); +export function decodeHashFromBase64(hash: string): HoloHash { + return new HoloHash( hash ); } /** @@ -21,6 +22,8 @@ export function decodeHashFromBase64(hash: HoloHashB64): HoloHash { * * @public */ -export function encodeHashToBase64(hash: HoloHash): HoloHashB64 { - return `u${Base64.fromUint8Array(hash, true)}`; +export function encodeHashToBase64(hash: Uint8Array): HoloHashB64 { + return String(new HoloHash( hash )); } + +export * from "@spartan-hc/holo-hash"; From b1e28aee307bfeaac837f4b849c3dcbac76262d3 Mon Sep 17 00:00:00 2001 From: Matthew Brisebois Date: Tue, 2 Jul 2024 14:00:48 -0600 Subject: [PATCH 2/4] Update hash-parts.ts methods to support HoloHash classes --- src/utils/base64.ts | 2 -- src/utils/hash-parts.ts | 29 ++++++++++++++++------------- test/e2e/utils.ts | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/utils/base64.ts b/src/utils/base64.ts index 6c53fa2a..080b6952 100644 --- a/src/utils/base64.ts +++ b/src/utils/base64.ts @@ -25,5 +25,3 @@ export function decodeHashFromBase64(hash: string): HoloHash { export function encodeHashToBase64(hash: Uint8Array): HoloHashB64 { return String(new HoloHash( hash )); } - -export * from "@spartan-hc/holo-hash"; diff --git a/src/utils/hash-parts.ts b/src/utils/hash-parts.ts index 7cc340da..322d5e10 100644 --- a/src/utils/hash-parts.ts +++ b/src/utils/hash-parts.ts @@ -1,4 +1,4 @@ -import { ActionHash, AgentPubKey, EntryHash } from "../types.js"; +import { HoloHash, ActionHash, AgentPubKey, EntryHash } from "@spartan-hc/holo-hash"; import blake2b from "@bitgo/blake2b"; /** @@ -27,9 +27,11 @@ export const HASH_TYPE_PREFIX = { * @public */ export function sliceDhtLocation( - hash: AgentPubKey | EntryHash | ActionHash + hash: HoloHash, ): Uint8Array { - return Uint8Array.from(hash.slice(36, 40)); + return hash?.getDHTAddress + ? hash.getDHTAddress() + : Uint8Array.from(hash.slice(36, 40)); } /** @@ -43,9 +45,11 @@ export function sliceDhtLocation( * @public */ export function sliceCore32( - hash: AgentPubKey | EntryHash | ActionHash + hash: HoloHash, ): Uint8Array { - return Uint8Array.from(hash.slice(3, 36)); + return hash?.getHash + ? hash.getHash() + : Uint8Array.from(hash.slice(3, 36)); } /** @@ -59,9 +63,11 @@ export function sliceCore32( * @public */ export function sliceHashType( - hash: AgentPubKey | EntryHash | ActionHash + hash: HoloHash, ): Uint8Array { - return Uint8Array.from(hash.slice(0, 3)); + return hash?.getPrefix + ? hash.getPrefix() + : Uint8Array.from(hash.slice(0, 3)); } /** @@ -101,12 +107,9 @@ export function dhtLocationFrom32(hashCore: Uint8Array): Uint8Array { * @public */ export function hashFrom32AndType( - hashCore: AgentPubKey | EntryHash | ActionHash, + hashCore: Uint8Array, hashType: "Agent" | "Entry" | "Dna" | "Action" | "External" ): Uint8Array { - return Uint8Array.from([ - ...HASH_TYPE_PREFIX[hashType], - ...hashCore, - ...dhtLocationFrom32(hashCore), - ]); + return (new HoloHash(hashCore)) + .toType(hashType === "Agent" ? "AgentPubKey" : hashType + "Hash"); } diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts index eecee514..b734cdae 100644 --- a/test/e2e/utils.ts +++ b/test/e2e/utils.ts @@ -222,7 +222,7 @@ test("hashFrom32AndType generates valid hash with type and 32 core bytes", async const fullHash = hashFrom32AndType(core, "Agent"); t.deepEqual( - fullHash, + fullHash.bytes(), Uint8Array.from([ 132, 32, 36, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 126, 207, 206, 190, From 31deded1558fea127f35185fcfd9f28fbe3316ce Mon Sep 17 00:00:00 2001 From: Matthew Brisebois Date: Thu, 11 Jul 2024 08:23:28 -0600 Subject: [PATCH 3/4] Export HoloHash class types but keep support for Uint8Array input --- src/api/zome-call-signing.ts | 25 +++++++---------- src/hdk/link.ts | 6 +---- src/types.ts | 52 ++++++++++++++++-------------------- src/utils/fake-hash.ts | 48 ++++++++++++++++----------------- src/utils/hash-parts.ts | 26 +++++++++--------- test/e2e/index.ts | 11 +++----- test/e2e/utils.ts | 2 +- 7 files changed, 75 insertions(+), 95 deletions(-) diff --git a/src/api/zome-call-signing.ts b/src/api/zome-call-signing.ts index 7987fbc0..d962eb53 100644 --- a/src/api/zome-call-signing.ts +++ b/src/api/zome-call-signing.ts @@ -1,6 +1,7 @@ import _sodium, { type KeyPair } from "libsodium-wrappers"; import type { CapSecret } from "../hdk/capabilities.js"; -import type { AgentPubKey, CellId } from "../types.js"; +import type { CellId } from "../types.js"; +import { AgentPubKey } from "../types.js"; import { encodeHashToBase64 } from "../utils/base64.js"; /** @@ -54,24 +55,18 @@ export const setSigningCredentials = ( /** * Generates a key pair for signing zome calls. * - * @param agentPubKey - The agent pub key to take 4 last bytes (= DHT location) - * from (optional). * @returns The signing key pair and an agent pub key based on the public key. * * @public */ -export const generateSigningKeyPair: ( - agentPubKey?: AgentPubKey -) => Promise<[KeyPair, AgentPubKey]> = async (agentPubKey?: AgentPubKey) => { - await _sodium.ready; - const sodium = _sodium; - const keyPair = sodium.crypto_sign_keypair(); - const locationBytes = agentPubKey ? agentPubKey.subarray(35) : [0, 0, 0, 0]; - const signingKey = new Uint8Array( - [132, 32, 36].concat(...keyPair.publicKey).concat(...locationBytes) - ); - return [keyPair, signingKey]; -}; +export const generateSigningKeyPair: () => Promise<[KeyPair, AgentPubKey]> = + async () => { + await _sodium.ready; + const sodium = _sodium; + const keyPair = sodium.crypto_sign_keypair(); + const signingKey = new AgentPubKey(keyPair.publicKey); + return [keyPair, signingKey]; + }; /** * @public diff --git a/src/hdk/link.ts b/src/hdk/link.ts index 28de5dd9..ec750a10 100644 --- a/src/hdk/link.ts +++ b/src/hdk/link.ts @@ -4,13 +4,9 @@ import { EntryHash, ExternalHash, Timestamp, + AnyLinkableHash, } from "../types.js"; -/** - * @public - */ -export type AnyLinkableHash = EntryHash | ActionHash | ExternalHash; - /** * An internal zome index within the DNA, from 0 to 255. * diff --git a/src/types.ts b/src/types.ts index fdd75064..128613e3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,35 +1,29 @@ +import { + HoloHash, + AgentPubKey, + DnaHash, + WasmHash, + EntryHash, + ActionHash, + AnyDhtHash, + AnyLinkableHash, + ExternalHash, +} from "@spartan-hc/holo-hash"; + /** * @public */ -export type HoloHash = Uint8Array; // length 39 -/** - * @public - */ -export type AgentPubKey = HoloHash; -/** - * @public - */ -export type DnaHash = HoloHash; -/** - * @public - */ -export type WasmHash = HoloHash; -/** - * @public - */ -export type EntryHash = HoloHash; -/** - * @public - */ -export type ActionHash = HoloHash; -/** - * @public - */ -export type AnyDhtHash = HoloHash; -/** - * @public - */ -export type ExternalHash = HoloHash; +export { + HoloHash, + AgentPubKey, + DnaHash, + WasmHash, + EntryHash, + ActionHash, + AnyDhtHash, + AnyLinkableHash, + ExternalHash, +} /** * @public diff --git a/src/utils/fake-hash.ts b/src/utils/fake-hash.ts index 40e48374..b568559e 100644 --- a/src/utils/fake-hash.ts +++ b/src/utils/fake-hash.ts @@ -1,22 +1,6 @@ import { range } from "lodash-es"; import { randomByteArray } from "../api/zome-call-signing.js"; import { DnaHash, ActionHash, AgentPubKey, EntryHash } from "../types.js"; -import { dhtLocationFrom32 } from "./hash-parts.js"; - -async function fakeValidHash( - prefix: number[], - coreByte: number | undefined -): Promise { - let core; - if (coreByte === undefined) { - core = await randomByteArray(32); - } else { - core = Uint8Array.from(range(0, 32).map(() => coreByte)); - } - const checksum = dhtLocationFrom32(core); - - return new Uint8Array([...prefix, ...core, ...Array.from(checksum)]) as T; -} /** * Generate a valid hash of a non-existing entry. @@ -24,54 +8,70 @@ async function fakeValidHash( * From https://github.com/holochain/holochain/blob/develop/crates/holo_hash/src/hash_type/primitive.rs * * @param coreByte - Optionally specify a byte to repeat for all core 32 bytes. If undefined will generate random core 32 bytes. - * @returns An {@link EntryHash}. + * @returns An instance of EntryHash. * * @public */ export async function fakeEntryHash( coreByte: number | undefined = undefined ): Promise { - return fakeValidHash([0x84, 0x21, 0x24], coreByte); + return new EntryHash( + coreByte + ? Uint8Array.from(range(0, 32).map(() => coreByte)) + : await randomByteArray(32) + ); } /** * Generate a valid agent key of a non-existing agent. * * @param coreByte - Optionally specify a byte to repeat for all core 32 bytes. If undefined will generate random core 32 bytes. - * @returns An {@link AgentPubKey}. + * @returns An instance ofAgentPubKey. * * @public */ export async function fakeAgentPubKey( coreByte: number | undefined = undefined ): Promise { - return fakeValidHash([0x84, 0x20, 0x24], coreByte); + return new AgentPubKey( + coreByte + ? Uint8Array.from(range(0, 32).map(() => coreByte)) + : await randomByteArray(32) + ); } /** * Generate a valid hash of a non-existing action. * * @param coreByte - Optionally specify a byte to repeat for all core 32 bytes. If undefined will generate random core 32 bytes. - * @returns An {@link ActionHash}. + * @returns An instance of ActionHash. * * @public */ export async function fakeActionHash( coreByte: number | undefined = undefined ): Promise { - return fakeValidHash([0x84, 0x29, 0x24], coreByte); + return new ActionHash( + coreByte + ? Uint8Array.from(range(0, 32).map(() => coreByte)) + : await randomByteArray(32) + ); } /** * Generate a valid hash of a non-existing DNA. * * @param coreByte - Optionally specify a byte to repeat for all core 32 bytes. If undefined will generate random core 32 bytes. - * @returns A {@link DnaHash}. + * @returns A instance of DnaHash. * * @public */ export async function fakeDnaHash( coreByte: number | undefined = undefined ): Promise { - return fakeValidHash([0x84, 0x2d, 0x24], coreByte); + return new DnaHash( + coreByte + ? Uint8Array.from(range(0, 32).map(() => coreByte)) + : await randomByteArray(32) + ); } diff --git a/src/utils/hash-parts.ts b/src/utils/hash-parts.ts index cd5222da..52279309 100644 --- a/src/utils/hash-parts.ts +++ b/src/utils/hash-parts.ts @@ -1,5 +1,5 @@ -import { HoloHash, ActionHash, AgentPubKey, EntryHash } from "@spartan-hc/holo-hash"; import blake2b from "@bitgo/blake2b"; +import { HoloHash } from "../types.js"; const HASH_TYPE_START = 0; const HASH_TYPE_BYTE_LENGTH = 3; @@ -32,11 +32,11 @@ export const HASH_TYPE_PREFIX = { * @public */ export function sliceHashType( - hash: HoloHash, + hash: HoloHash | Uint8Array, ): Uint8Array { - return hash?.getPrefix - ? hash.getPrefix() - : Uint8Array.from(hash.slice(0, 3)); + if (!(hash instanceof HoloHash)) + hash = new HoloHash(hash); + return (hash as HoloHash).getPrefix() } /** @@ -50,13 +50,13 @@ export function sliceHashType( * @public */ export function sliceCore32( - hash: HoloHash, + hash: HoloHash | Uint8Array, ): Uint8Array { + if (!(hash instanceof HoloHash)) + hash = new HoloHash(hash); const start = HASH_TYPE_START + HASH_TYPE_BYTE_LENGTH; const end = start + CORE_HASH_BYTE_LENGTH; - return hash?.getHash - ? hash.getHash() - : Uint8Array.from(hash.slice(3, 36)); + return (hash as HoloHash).bytes(start, end); } /** @@ -70,13 +70,13 @@ export function sliceCore32( * @public */ export function sliceDhtLocation( - hash: HoloHash, + hash: HoloHash | Uint8Array, ): Uint8Array { + if (!(hash instanceof HoloHash)) + hash = new HoloHash(hash); const start = HASH_TYPE_START + HASH_TYPE_BYTE_LENGTH + CORE_HASH_BYTE_LENGTH; const end = start + DHT_LOCATION_BYTE_LENGTH; - return hash?.getDHTAddress - ? hash.getDHTAddress() - : Uint8Array.from(hash.slice(36, 40)); + return (hash as HoloHash).bytes(start, end); } /** diff --git a/test/e2e/index.ts b/test/e2e/index.ts index ed1ce18e..40fe5261 100644 --- a/test/e2e/index.ts +++ b/test/e2e/index.ts @@ -562,15 +562,10 @@ test( ); test( - "generated signing key has same location bytes as original agent pub key", + "generated signing key", withConductor(ADMIN_PORT, async (t) => { - const admin = await AdminWebsocket.connect({ - url: ADMIN_WS_URL, - wsClientOptions: { origin: "client-test-admin" }, - }); - const agent = await admin.generateAgentPubKey(); - const [, signingKey] = await generateSigningKeyPair(agent); - t.deepEqual(signingKey.subarray(35), Uint8Array.from(agent.subarray(35))); + const [, signingKey] = await generateSigningKeyPair(); + t.assert(signingKey?.constructor.name === "AgentPubKey", "expected an AgentPubKey"); }) ); diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts index 2c55167e..33c89149 100644 --- a/test/e2e/utils.ts +++ b/test/e2e/utils.ts @@ -211,7 +211,7 @@ test("sliceDhtLocation, sliceCore32, sliceHashType extract components of a hash" const dhtLocation = sliceDhtLocation(fakeHash); t.deepEqual( - fakeHash, + fakeHash.bytes(), Uint8Array.from([...prefix, ...hash, ...dhtLocation]), "extracted hash type, core hash, and dht location components of a hash concat back into the original hash" ); From b132ed3314ad3235888956c25752bc9812441c97 Mon Sep 17 00:00:00 2001 From: Matthew Brisebois Date: Wed, 17 Jul 2024 14:49:46 -0600 Subject: [PATCH 4/4] Use explicit check for expected type Co-authored-by: Jost Schulte --- src/utils/hash-parts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/hash-parts.ts b/src/utils/hash-parts.ts index 52279309..0fbfe81a 100644 --- a/src/utils/hash-parts.ts +++ b/src/utils/hash-parts.ts @@ -72,7 +72,7 @@ export function sliceCore32( export function sliceDhtLocation( hash: HoloHash | Uint8Array, ): Uint8Array { - if (!(hash instanceof HoloHash)) + if (hash instanceof Uint8Array) hash = new HoloHash(hash); const start = HASH_TYPE_START + HASH_TYPE_BYTE_LENGTH + CORE_HASH_BYTE_LENGTH; const end = start + DHT_LOCATION_BYTE_LENGTH;