Skip to content

Commit

Permalink
feat: use @spartan-hc/holo-hash for managing holo-hashes (#259)
Browse files Browse the repository at this point in the history
* Use @spartan-hc/holo-hash for base64 utils

* Update hash-parts.ts methods to support HoloHash classes

* Export HoloHash class types but keep support for Uint8Array input

* Use explicit check for expected type

Co-authored-by: Jost Schulte <[email protected]>

---------

Co-authored-by: Jost Schulte <[email protected]>
  • Loading branch information
mjbrisebois and jost-s authored Jul 19, 2024
1 parent d01fc80 commit b410db4
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 101 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 10 additions & 15 deletions src/api/zome-call-signing.ts
Original file line number Diff line number Diff line change
@@ -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";

/**
Expand Down Expand Up @@ -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
Expand Down
6 changes: 1 addition & 5 deletions src/hdk/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
52 changes: 23 additions & 29 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
11 changes: 6 additions & 5 deletions src/utils/base64.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 );
}

/**
Expand All @@ -21,6 +22,6 @@ 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 ));
}
48 changes: 24 additions & 24 deletions src/utils/fake-hash.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,77 @@
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<T extends Uint8Array>(
prefix: number[],
coreByte: number | undefined
): Promise<Uint8Array> {
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.
*
* 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<EntryHash> {
return fakeValidHash<EntryHash>([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<AgentPubKey> {
return fakeValidHash<AgentPubKey>([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<ActionHash> {
return fakeValidHash<ActionHash>([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<DnaHash> {
return fakeValidHash<DnaHash>([0x84, 0x2d, 0x24], coreByte);
return new DnaHash(
coreByte
? Uint8Array.from(range(0, 32).map(() => coreByte))
: await randomByteArray(32)
);
}
29 changes: 16 additions & 13 deletions src/utils/hash-parts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ActionHash, AgentPubKey, EntryHash } from "../types.js";
import blake2b from "@bitgo/blake2b";
import { HoloHash } from "../types.js";

const HASH_TYPE_START = 0;
const HASH_TYPE_BYTE_LENGTH = 3;
Expand Down Expand Up @@ -32,9 +32,11 @@ export const HASH_TYPE_PREFIX = {
* @public
*/
export function sliceHashType(
hash: AgentPubKey | EntryHash | ActionHash
hash: HoloHash | Uint8Array,
): Uint8Array {
return Uint8Array.from(hash.slice(0, 3));
if (!(hash instanceof HoloHash))
hash = new HoloHash(hash);
return (hash as HoloHash).getPrefix()
}

/**
Expand All @@ -48,11 +50,13 @@ export function sliceHashType(
* @public
*/
export function sliceCore32(
hash: AgentPubKey | EntryHash | ActionHash
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 Uint8Array.from(hash.slice(start, end));
return (hash as HoloHash).bytes(start, end);
}

/**
Expand All @@ -66,11 +70,13 @@ export function sliceCore32(
* @public
*/
export function sliceDhtLocation(
hash: AgentPubKey | EntryHash | ActionHash
hash: HoloHash | Uint8Array,
): Uint8Array {
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;
return Uint8Array.from(hash.slice(start, end));
return (hash as HoloHash).bytes(start, end);
}

/**
Expand Down Expand Up @@ -110,12 +116,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");
}
11 changes: 3 additions & 8 deletions test/e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,15 +583,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");
})
);

Expand Down
4 changes: 2 additions & 2 deletions test/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
Expand All @@ -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,
Expand Down

0 comments on commit b410db4

Please sign in to comment.