Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions bin/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ The following modules are available as subpath imports (e.g., `@typeberry/lib/bl
- `config` - Configuration types
- `config-node` - Node configuration utilities
- `crypto` - Cryptographic primitives (Ed25519, Sr25519, BLS)
- `crypto-browser` - Browser-safe cryptographic sizes and opaque data types
- `database` - Database abstractions
- `erasure-coding` - Erasure coding implementation
- `fuzz-proto` - Fuzzing protocol support
Expand All @@ -61,6 +62,7 @@ The following modules are available as subpath imports (e.g., `@typeberry/lib/bl
- `json-parser` - JSON parsing utilities
- `logger` - Logging framework
- `mmr` - Merkle Mountain Range implementation
- `networking-offline` - Programmatically controlled offline JAM networking implementation
- `numbers` - Fixed-size numeric types
- `ordering` - Ordering and comparison utilities
- `pvm-host-calls` - PVM host call implementations
Expand All @@ -76,6 +78,76 @@ The following modules are available as subpath imports (e.g., `@typeberry/lib/bl
- `utils` - General utilities
- `workers-api` - Workers API utilities

### Browser-safe crypto metadata

Use `@typeberry/lib/crypto-browser` when browser code only needs encoded
cryptographic sizes or opaque key and signature types:

```typescript
import { ED25519_SIGNATURE_BYTES } from "@typeberry/lib/crypto-browser";
import type { Ed25519Signature } from "@typeberry/lib/crypto-browser";
```

This entry point does not load signing, verification, native bindings, or WASM
initialization. Use `@typeberry/lib/crypto` when those operations are required.

### Offline networking controller

The offline networking module is a browser-safe implementation of the standard
networking-worker protocol. A node host can wire its `network` endpoint to an
importer and best-header source, while callers control peer traffic through
`offline`:

<!-- example-code:networking-offline -->
```typescript
import { startOfflineNetworkingWorker } from "@typeberry/lib/networking-offline";
import { Block, emptyBlock, reencodeAsView } from "@typeberry/lib/block";
import { Bytes } from "@typeberry/lib/bytes";
import { tinyChainSpec } from "@typeberry/lib/config";
import { HASH_SIZE, WithHash } from "@typeberry/lib/hash";
import { DirectPort } from "@typeberry/lib/workers-api";

type BlockView = import("@typeberry/lib/block").BlockView;
type HeaderHash = import("@typeberry/lib/block").HeaderHash;

const authorshipPorts = DirectPort.pair();
const worker = startOfflineNetworkingWorker(authorshipPorts[0]);
// Connect authorshipPorts[1] to block authorship.

// Connect blocks received through offline networking to the importer.
const importedBlocks: BlockView[] = [];
worker.network.setOnBlocks(async (blocks) => {
importedBlocks.push(...blocks);
});

const block = reencodeAsView(Block.Codec, emptyBlock(), tinyChainSpec);
await worker.offline.submitBlock(block);
assert.strictEqual(importedBlocks[0], block);

// Outgoing online-network announcements are observable programmatically.
let announcedHeader: unknown = null;
worker.offline.announcedHeaders.once((header) => {
announcedHeader = header;
});
const header = WithHash.new(Bytes.zero(HASH_SIZE).asOpaque<HeaderHash>(), block.header.view());
await worker.network.sendNewHeader(header);
assert.strictEqual(announcedHeader, header);
Comment on lines +123 to +134

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the README example self-contained.

Lines [125] and [134] call assert.strictEqual, but this snippet never imports or defines assert, and browsers do not provide it globally. Use explicit checks or add an environment-appropriate assertion import.

Proposed browser-safe replacement
-assert.strictEqual(importedBlocks[0], block);
+if (importedBlocks[0] !== block) {
+  throw new Error("Offline networking did not deliver the block");
+}
...
-assert.strictEqual(announcedHeader, header);
+if (announcedHeader !== header) {
+  throw new Error("Header announcement was not observed");
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const block = reencodeAsView(Block.Codec, emptyBlock(), tinyChainSpec);
await worker.offline.submitBlock(block);
assert.strictEqual(importedBlocks[0], block);
// Outgoing online-network announcements are observable programmatically.
let announcedHeader: unknown = null;
worker.offline.announcedHeaders.once((header) => {
announcedHeader = header;
});
const header = WithHash.new(Bytes.zero(HASH_SIZE).asOpaque<HeaderHash>(), block.header.view());
await worker.network.sendNewHeader(header);
assert.strictEqual(announcedHeader, header);
const block = reencodeAsView(Block.Codec, emptyBlock(), tinyChainSpec);
await worker.offline.submitBlock(block);
if (importedBlocks[0] !== block) {
throw new Error("Offline networking did not deliver the block");
}
// Outgoing online-network announcements are observable programmatically.
let announcedHeader: unknown = null;
worker.offline.announcedHeaders.once((header) => {
announcedHeader = header;
});
const header = WithHash.new(Bytes.zero(HASH_SIZE).asOpaque<HeaderHash>(), block.header.view());
await worker.network.sendNewHeader(header);
if (announcedHeader !== header) {
throw new Error("Header announcement was not observed");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/lib/README.md` around lines 123 - 134, Make the README example
self-contained by replacing the two assert.strictEqual calls around
importedBlocks and announcedHeader with explicit browser-safe equality checks,
or define an appropriate assertion dependency within the example. Preserve both
validations: each value must equal the expected block or header.


await worker.finish();
```
<!-- /example-code:networking-offline -->

`submitBlock` and `submitBlocks` resolve once the standard networking protocol
has delivered the blocks; importer acceptance remains intentionally separate.
`submitTickets` returns the connected authorship module's validation decision.
The announcement events expose headers and ticket messages that online
networking would distribute to peers. Calling `finish()` is idempotent, and
submission methods reject after shutdown.

The concrete `@typeberry/node` package deliberately does not compose this
controller yet. A future node integration should expose an RPC or equivalent
control surface so external callers can submit offline network traffic.

## Examples

All examples below are extracted from actual test files in `examples/` directory, ensuring they compile and work correctly.
Expand Down
42 changes: 42 additions & 0 deletions bin/lib/examples/networking-offline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import assert from "node:assert";
import { describe, it } from "node:test";

describe("Offline Networking Example", () => {
it("should submit blocks and observe outgoing announcements", async () => {
// <!-- example:networking-offline -->
const { startOfflineNetworkingWorker } = await import("@typeberry/lib/networking-offline");
const { Block, emptyBlock, reencodeAsView } = await import("@typeberry/lib/block");
type BlockView = import("@typeberry/lib/block").BlockView;
type HeaderHash = import("@typeberry/lib/block").HeaderHash;
const { Bytes } = await import("@typeberry/lib/bytes");
const { tinyChainSpec } = await import("@typeberry/lib/config");
const { HASH_SIZE, WithHash } = await import("@typeberry/lib/hash");
const { DirectPort } = await import("@typeberry/lib/workers-api");

const authorshipPorts = DirectPort.pair();
const worker = startOfflineNetworkingWorker(authorshipPorts[0]);
// Connect authorshipPorts[1] to block authorship.

// Connect blocks received through offline networking to the importer.
const importedBlocks: BlockView[] = [];
worker.network.setOnBlocks(async (blocks) => {
importedBlocks.push(...blocks);
});

const block = reencodeAsView(Block.Codec, emptyBlock(), tinyChainSpec);
await worker.offline.submitBlock(block);
assert.strictEqual(importedBlocks[0], block);

// Outgoing online-network announcements are observable programmatically.
let announcedHeader: unknown = null;
worker.offline.announcedHeaders.once((header) => {
announcedHeader = header;
});
const header = WithHash.new(Bytes.zero(HASH_SIZE).asOpaque<HeaderHash>(), block.header.view());
await worker.network.sendNewHeader(header);
assert.strictEqual(announcedHeader, header);

await worker.finish();
// <!-- /example:networking-offline -->
});
});
8 changes: 8 additions & 0 deletions bin/lib/exports/crypto-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Browser-safe cryptographic sizes and opaque key/signature types.
*
* This module does not expose signing, verification, or native/WASM setup.
*
* @module crypto-browser
*/
export * from "@typeberry/crypto/browser.js";
6 changes: 6 additions & 0 deletions bin/lib/exports/networking-offline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Programmatically controlled offline implementation of the JAM networking protocol.
*
* @module networking-offline
*/
export * from "@typeberry/networking-offline";
2 changes: 2 additions & 0 deletions bin/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * as collections from "./exports/collections.js";
export * as config from "./exports/config.js";
export * as config_node from "./exports/config-node.js";
export * as crypto from "./exports/crypto.js";
export * as crypto_browser from "./exports/crypto-browser.js";
export * as database from "./exports/database.js";
export * as erasure_coding from "./exports/erasure-coding.js";
export * as fuzz_proto from "./exports/fuzz-proto.js";
Expand All @@ -18,6 +19,7 @@ export * as jam_host_calls from "./exports/jam-host-calls.js";
export * as json_parser from "./exports/json-parser.js";
export * as logger from "./exports/logger.js";
export * as mmr from "./exports/mmr.js";
export * as networking_offline from "./exports/networking-offline.js";
export * as numbers from "./exports/numbers.js";
export * as ordering from "./exports/ordering.js";
export * as pvm_host_calls from "./exports/pvm-host-calls.js";
Expand Down
3 changes: 3 additions & 0 deletions bin/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"./config": "./exports/config.js",
"./config-node": "./exports/config-node.js",
"./crypto": "./exports/crypto.js",
"./crypto-browser": "./exports/crypto-browser.js",
"./database": "./exports/database.js",
"./erasure-coding": "./exports/erasure-coding.js",
"./fuzz-proto": "./exports/fuzz-proto.js",
Expand All @@ -26,6 +27,7 @@
"./json-parser": "./exports/json-parser.js",
"./logger": "./exports/logger.js",
"./mmr": "./exports/mmr.js",
"./networking-offline": "./exports/networking-offline.js",
"./numbers": "./exports/numbers.js",
"./ordering": "./exports/ordering.js",
"./package.json": "./package.json",
Expand Down Expand Up @@ -66,6 +68,7 @@
"@typeberry/json-parser": "*",
"@typeberry/logger": "*",
"@typeberry/mmr": "*",
"@typeberry/networking-offline": "*",
"@typeberry/numbers": "*",
"@typeberry/ordering": "*",
"@typeberry/pvm-host-calls": "*",
Expand Down
28 changes: 27 additions & 1 deletion 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 @@ -46,6 +46,7 @@
"packages/jam/in-core",
"packages/jam/jam-host-calls",
"packages/jam/jamnp-s",
"packages/jam/networking-offline",
"packages/jam/node",
"packages/jam/rpc",
"packages/jam/rpc-client",
Expand Down
14 changes: 14 additions & 0 deletions packages/core/crypto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Crypto entry points

`@typeberry/crypto` is the complete cryptographic implementation. It includes
native/WASM initialization and signing or verification operations.

`@typeberry/crypto/browser.js` is the dependency-light entry point for code
which only needs encoded key/signature sizes and their opaque TypeScript types.
It has no runtime imports and does not expose cryptographic operations. Its
constants intentionally mirror the canonical values in `ed25519.ts` and
`bandersnatch.ts`; their literal type annotations and unit test catch drift.
Comment on lines +6 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify the encoded-size wording.

Replace “only needs encoded key/signature sizes” with “only needs the sizes of encoded keys/signatures” to avoid ambiguity.

🧰 Tools
🪛 LanguageTool

[style] ~7-~7: The double modal “needs encoded” is nonstandard (only accepted in certain dialects). Consider “to be encoded”.
Context: ...t entry point for code which only needs encoded key/signature sizes and their opaque Ty...

(NEEDS_FIXED)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/crypto/README.md` around lines 6 - 10, Update the description
of the `@typeberry/crypto/browser.js` entry point to say it is for code that only
needs the sizes of encoded keys/signatures, leaving the surrounding
documentation unchanged.

Source: Linters/SAST tools


Browser-facing codecs should import from `@typeberry/crypto/browser.js` rather
than the package root, so merely describing encoded data does not load native
implementations.
18 changes: 18 additions & 0 deletions packages/core/crypto/browser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import assert from "node:assert";
import { describe, it } from "node:test";
Comment on lines +1 to +2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep this shared test runtime-neutral.

node:assert and node:test make browser.test.ts Node-only. Use the repository’s cross-runtime test/assertion utilities, or split this into explicit Node and web test files.

As per path instructions, *.test.ts files must remain compatible with both Node and web unless they use an explicit *.node.ts / *.web.ts convention.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/crypto/browser.test.ts` around lines 1 - 2, Replace the
node:assert and node:test imports in browser.test.ts with the repository’s
runtime-neutral test and assertion utilities. Keep the test compatible with both
Node and web runtimes without renaming it or introducing separate
runtime-specific files.

Source: Path instructions

import * as bandersnatch from "./bandersnatch.js";
import * as browser from "./browser.js";
import * as ed25519 from "./ed25519.js";

describe("browser-safe crypto constants", () => {
it("match their canonical implementation values", () => {
assert.strictEqual(browser.ED25519_PRIV_KEY_BYTES, ed25519.ED25519_PRIV_KEY_BYTES);
assert.strictEqual(browser.ED25519_KEY_BYTES, ed25519.ED25519_KEY_BYTES);
assert.strictEqual(browser.ED25519_SIGNATURE_BYTES, ed25519.ED25519_SIGNATURE_BYTES);
assert.strictEqual(browser.BANDERSNATCH_KEY_BYTES, bandersnatch.BANDERSNATCH_KEY_BYTES);
assert.strictEqual(browser.BANDERSNATCH_VRF_SIGNATURE_BYTES, bandersnatch.BANDERSNATCH_VRF_SIGNATURE_BYTES);
assert.strictEqual(browser.BANDERSNATCH_RING_ROOT_BYTES, bandersnatch.BANDERSNATCH_RING_ROOT_BYTES);
assert.strictEqual(browser.BANDERSNATCH_PROOF_BYTES, bandersnatch.BANDERSNATCH_PROOF_BYTES);
assert.strictEqual(browser.BLS_KEY_BYTES, bandersnatch.BLS_KEY_BYTES);
});
});
49 changes: 49 additions & 0 deletions packages/core/crypto/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Browser-safe cryptographic sizes and opaque data types.
*
* This module intentionally has no runtime imports. The annotations on the
* mirrored constants make TypeScript fail if their canonical values in the
* implementation modules change.
*/

type CanonicalEd25519PrivateKeyBytes = typeof import("./ed25519.js").ED25519_PRIV_KEY_BYTES;
type CanonicalEd25519KeyBytes = typeof import("./ed25519.js").ED25519_KEY_BYTES;
type CanonicalEd25519SignatureBytes = typeof import("./ed25519.js").ED25519_SIGNATURE_BYTES;
type CanonicalBandersnatchKeyBytes = typeof import("./bandersnatch.js").BANDERSNATCH_KEY_BYTES;
type CanonicalBandersnatchVrfSignatureBytes = typeof import("./bandersnatch.js").BANDERSNATCH_VRF_SIGNATURE_BYTES;
type CanonicalBandersnatchRingRootBytes = typeof import("./bandersnatch.js").BANDERSNATCH_RING_ROOT_BYTES;
type CanonicalBandersnatchProofBytes = typeof import("./bandersnatch.js").BANDERSNATCH_PROOF_BYTES;
type CanonicalBlsKeyBytes = typeof import("./bandersnatch.js").BLS_KEY_BYTES;

export const ED25519_PRIV_KEY_BYTES: CanonicalEd25519PrivateKeyBytes = 32;
export type ED25519_PRIV_KEY_BYTES = typeof ED25519_PRIV_KEY_BYTES;

export const ED25519_KEY_BYTES: CanonicalEd25519KeyBytes = 32;
export type ED25519_KEY_BYTES = typeof ED25519_KEY_BYTES;

export const ED25519_SIGNATURE_BYTES: CanonicalEd25519SignatureBytes = 64;
export type ED25519_SIGNATURE_BYTES = typeof ED25519_SIGNATURE_BYTES;

export const BANDERSNATCH_KEY_BYTES: CanonicalBandersnatchKeyBytes = 32;
export type BANDERSNATCH_KEY_BYTES = typeof BANDERSNATCH_KEY_BYTES;

export const BANDERSNATCH_VRF_SIGNATURE_BYTES: CanonicalBandersnatchVrfSignatureBytes = 96;
export type BANDERSNATCH_VRF_SIGNATURE_BYTES = typeof BANDERSNATCH_VRF_SIGNATURE_BYTES;

export const BANDERSNATCH_RING_ROOT_BYTES: CanonicalBandersnatchRingRootBytes = 144;
export type BANDERSNATCH_RING_ROOT_BYTES = typeof BANDERSNATCH_RING_ROOT_BYTES;

export const BANDERSNATCH_PROOF_BYTES: CanonicalBandersnatchProofBytes = 784;
export type BANDERSNATCH_PROOF_BYTES = typeof BANDERSNATCH_PROOF_BYTES;

export const BLS_KEY_BYTES: CanonicalBlsKeyBytes = 144;
export type BLS_KEY_BYTES = typeof BLS_KEY_BYTES;

export type {
BandersnatchKey,
BandersnatchProof,
BandersnatchRingRoot,
BandersnatchVrfSignature,
BlsKey,
} from "./bandersnatch.js";
export type { Ed25519Key, Ed25519Signature } from "./ed25519.js";
Loading
Loading