Compare commits

...
6 Commits
Author SHA1 Message Date
swaitsandClaude Opus 4.7 9d7b1c6281 spec: incorporate MeshCore discussion #2538 review feedback
Incorporates two of the three corrections raised by msmuenchen on
discussion #2538:

- Identity: document that the 64-byte Ed25519 private key is the
  expanded `a || RH` (orlp) form, not a 32-byte seed, and that it
  cannot be consumed by standard Ed25519 libraries. Touches
  15-identity.md and 14-crypto.md.
- Group: document the GRP_TXT/GRP_DATA inner plaintext format
  (timestamp + flags + message) and the GRP_TXT length limits in
  08-payload-group.md.

The third correction (GRP_DATA vs RAW_CUSTOM app differentiation) is
deferred pending reviewer clarification.

Corpus: replace the unrealistic group vectors with valid inner-format
plaintexts; the ed25519 vectors now carry the 64-byte expanded key and
the seed; the schema and validator gain seed fields. Also corrects two
pre-existing wrong values found while verifying -- ed-001's public key
and ed-002's signature now match RFC 8032.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:11:19 -06:00
swaitsandClaude Opus 4.7 9232c16f7c feat(versioning): snapshot corpus per spec version
mdBook / build (push) Canceled after 0s
mdBook / deploy (push) Canceled after 0s
A frozen spec is only meaningful if it's paired with the corpus
that was current at release time — otherwise a v0.1.0 reader
follows "see corpus/payloads/ack/" and lands on a file whose
test vectors may have changed since v0.1.0 was cut.

Three changes:

1. `cut-version` now copies BOTH src/ AND corpus/ into the
   snapshot directory. A release is the pair, not just the text.

2. The corpus URL rewriting in cut-version now retargets
   /tree/main/corpus -> /tree/main/versions/<v>/corpus (and the
   blob variant). This lets the link resolve on GitHub from main
   alone — no git tag required, since the frozen copy is a real
   path under main. Downgrades the "tag is REQUIRED" message from
   last commit back to "optionally tag the commit".

3. The frozen corpus's spec_ref fields get rewritten too: from
   `src/04-payload-ack.md` to `versions/<v>/04-payload-ack.md`,
   so a reader following a test vector lands on the spec text the
   vector was paired with at release time.

versions/v0.1.0/ regenerated from scratch via `just cut-version v0.1.0`
to pick up the corpus snapshot + the new sed patterns. The release
is now self-consistent: v0.1.0 spec links go to v0.1.0 corpus, which
links back to v0.1.0 spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:38:02 -06:00
swaitsandClaude Opus 4.7 f83d4088e3 spec: link corpus references to GitHub, version-aware
Every "Test vectors: \`corpus/...\`" mention in the spec is now a
markdown link pointing at the corresponding file or directory on
GitHub. Each version build resolves its links to the correct git
ref:

  - /latest/ -> https://github.com/swaits/meshcore-spec/tree/main/corpus/...
  - /v0.1.0/ -> https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/...
  - future /vX.Y.Z/ -> /tree/vX.Y.Z/corpus/...

Two changes:

1. src/*.md (18 files): the inline-code corpus paths in each
   "Cross-References" section are now markdown links. Directories
   point at /tree/main/corpus/..., individual .json files point at
   /blob/main/corpus/.... CHANGELOG and other non-corpus main links
   are intentionally left pointing at main — the canonical history
   lives there regardless of which version you're reading.

2. justfile: `cut-version` now also runs a sed over the snapshot's
   *.md files replacing /tree/main/corpus -> /tree/<VERSION>/corpus
   and /blob/main/corpus -> /blob/<VERSION>/corpus. So a freshly-cut
   version is self-consistent.

versions/v0.1.0/* regenerated by re-running
`just cut-version v0.1.0` after the new src/ links + recipe sed
landed.

Tagging the release commit (`git tag v0.1.0`) is now REQUIRED — not
"optional" — because the rendered /v0.1.0/ pages reference
/tree/v0.1.0/corpus/... on GitHub. Without the tag the links 404.
cut-version's output now says so.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:35:09 -06:00
swaitsandClaude Opus 4.7 b6c91505e1 docs: switch author attribution from "Stephen Waits" to SWaits
Per maintainer's preference, the project's outward-facing copy now
refers to the author as [SWaits](https://swaits.com/) instead of
his legal name and email address.

Touched in this commit:
- src/00-overview.md (Status and Provenance callout)
- README.md (Provenance section + Author section; the email
  steve@waits.net is dropped — swaits.com is now the canonical
  contact)
- book.toml (authors = ["SWaits"], plain text since TOML)
- versions/v0.1.0/00-overview.md (regenerated via
  `just cut-version v0.1.0` after src/ was updated)

Git author identity on commit metadata is a separate, git/jj-config
concern and is intentionally not touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:33:15 -06:00
swaitsandClaude Opus 4.7 9dad83f50b chore(justfile): add preview recipe for local multi-version testing
`just build` produces a single-version site to ./book/ with no
versions.json, and `just build-site` produces the multi-version site
but viewing it via file:// makes the browser block
fetch('versions.json') for security reasons. Both cases leave the
version-picker stuck in label-only mode locally — which looked like
the picker was broken when it was actually doing the right thing
given the available data.

`just preview` is the recipe to test the picker end-to-end:

  1. Runs build-site (full _site/ with versions.json).
  2. Serves _site/ at http://localhost:8000/ via python -m http.server.
  3. Browser fetch() works, picker upgrades to a real dropdown,
     navigation between /latest/ and /v0.X.Y/ works.

Also restructured all recipe doc-comments to single-line summaries
(just --list only shows the immediately-preceding comment, and the
old multi-line comments were rendering as confusing fragments).
Background commentary moved to a file-level header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:27:28 -06:00
swaitsandClaude Opus 4.7 c70ab07c32 feat(mdbook): show spec version in overview body + page title
The first version cut (v0.1.0) revealed a real gap: nothing in the
rendered book identified which release the reader was on. The
version-picker chip says "version: v0.1.0" when you're on /v0.1.0/,
but it's a small UI affordance — and on /latest/ it can't say
"v0.1.0" because /latest/ is the rolling main build, not the v0.1.0
release.

Two complementary fixes, both visible without JS:

1. **Body marker.** Top of every chapter-0 (overview) page now reads
   `Spec version: <token>`. The token is `latest (main)` in src/, and
   gets frozen to the actual version (`v0.1.0`) when cut-version
   snapshots the directory. So /latest/ overview says "latest (main)"
   and /v0.1.0/ overview says "v0.1.0".

2. **Per-build page title.** scripts/build-site.sh now sets
   `MDBOOK_BOOK__TITLE` for each build:
     - latest → "MeshCore Protocol Specification — latest (main)"
     - vX.Y.Z → "MeshCore Protocol Specification — vX.Y.Z"
   The browser tab and mdBook sidebar header reflect the version.

`just cut-version` updated to run a one-line sed on the snapshot's
00-overview.md replacing the `latest (main)` marker. Defensive grep
warns if the marker isn't found (e.g., src/ was edited away from
the expected line).

versions/v0.1.0/00-overview.md regenerated by re-running
`just cut-version v0.1.0` after the marker landed in src/ — so the
v0.1.0 snapshot now self-identifies correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:22:59 -06:00
98 changed files with 6139 additions and 92 deletions

No files matched your search

+5 -5
View File
@@ -20,10 +20,10 @@ endorsed by, or maintained by the upstream MeshCore project. Read this section
before relying on the spec for anything load-bearing. before relying on the spec for anything load-bearing.
- **Authorship.** The spec, the corpus, and the tooling were written by Claude - **Authorship.** The spec, the corpus, and the tooling were written by Claude
(Anthropic, primarily Opus 4.x) under the direction of Stephen Waits. (Anthropic, primarily Opus 4.x) under the direction of
Substantive edits — additions, clarifications, audit passes — are produced [SWaits](https://swaits.com/). Substantive edits — additions, clarifications,
through model-driven analysis of the official MeshCore source and reviewed audit passes — are produced through model-driven analysis of the official
before commit. MeshCore source and reviewed before commit.
- **Source of truth.** The official MeshCore firmware at - **Source of truth.** The official MeshCore firmware at
[meshcore-dev/MeshCore](https://github.com/meshcore-dev/MeshCore) is the [meshcore-dev/MeshCore](https://github.com/meshcore-dev/MeshCore) is the
**golden source of truth** for all protocol behavior. Whenever this spec and **golden source of truth** for all protocol behavior. Whenever this spec and
@@ -108,4 +108,4 @@ which together are the authoritative reference this spec derives from.
## Author ## Author
Stephen Waits <steve@waits.net> [SWaits](https://swaits.com/)
+1 -1
View File
@@ -1,6 +1,6 @@
[book] [book]
title = "MeshCore Protocol Specification" title = "MeshCore Protocol Specification"
authors = ["Stephen Waits"] authors = ["SWaits"]
description = "An independent, AI-authored specification and conformance test corpus for the MeshCore mesh networking protocol. The official MeshCore C++ firmware is the source of truth; this spec is downstream." description = "An independent, AI-authored specification and conformance test corpus for the MeshCore mesh networking protocol. The official MeshCore C++ firmware is the source of truth; this spec is downstream."
language = "en" language = "en"
src = "src" src = "src"
+13 -10
View File
@@ -3,7 +3,7 @@
"name": "Ed25519 Sign/Verify", "name": "Ed25519 Sign/Verify",
"spec_ref": "src/15-identity.md", "spec_ref": "src/15-identity.md",
"protocol_version": 0, "protocol_version": 0,
"description": "Ed25519 signature test vectors. MeshCore uses Ed25519 for advertisement signing. These vectors use RFC 8032 test keypairs for cross-validation.", "description": "Ed25519 signature test vectors. MeshCore uses Ed25519 for advertisement signing. These vectors use RFC 8032 Section 7.1 test keypairs for cross-validation. crypto_context carries both the 32-byte seed (sender_seed) and the 64-byte expanded private key (sender_private_key, the clamped scalar a || nonce prefix RH) that MeshCore actually stores; see src/15-identity.md, 'Private Key Format'.",
"vectors": [ "vectors": [
{ {
"id": "ed-001", "id": "ed-001",
@@ -26,11 +26,12 @@
}, },
"binary": "3D 00 E5564300C360AC729086E2CC806E828A84877F1EB8E5D974D873E065224901555FB8821590A33BACC61E39701CF9B46BD25BF5F0595BBE24655141438E7A100B", "binary": "3D 00 E5564300C360AC729086E2CC806E828A84877F1EB8E5D974D873E065224901555FB8821590A33BACC61E39701CF9B46BD25BF5F0595BBE24655141438E7A100B",
"crypto_context": { "crypto_context": {
"sender_private_key": "9D61B19DEFFD5A60BA844AF492EC2CC44449C5697B326919703BAC031CAE7F60", "sender_seed": "9D61B19DEFFD5A60BA844AF492EC2CC44449C5697B326919703BAC031CAE7F60",
"sender_public_key": "D75A980182B10AB7D54BFED3C964073A0EE172F3DAA3F4A18446B0B8D183F8E3", "sender_private_key": "307C83864F2833CB427A2EF1C00A013CFDFF2768D980C0A3A520F006904DE94F9B4F0AFE280B746A778684E75442502057B7473A03F08F96F5A38E9287E01F8F",
"sender_public_key": "D75A980182B10AB7D54BFED3C964073A0EE172F3DAA62325AF021A68F707511A",
"plaintext": "" "plaintext": ""
}, },
"notes": "RFC 8032 Section 7.1 Test Vector 1. Private key (seed) is 32 bytes; the 64-byte format used by MeshCore includes the expanded key. Signature over empty message." "notes": "RFC 8032 Section 7.1 Test Vector 1. sender_seed is the 32-byte RFC seed; sender_private_key is the 64-byte expanded form (a || RH) that MeshCore stores. Signature over an empty message."
}, },
{ {
"id": "ed-002", "id": "ed-002",
@@ -48,16 +49,17 @@
"hashes": [] "hashes": []
}, },
"payload": { "payload": {
"data": "92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E159C7E94B2BA35F6F6A1918E25682F9B0ADC5C33C2BF3C39F04E50" "data": "92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E15996E458F3613D0F11D8C387B2EAEB4302AEEB00D291612BB0C00"
} }
}, },
"binary": "3D 00 92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E159C7E94B2BA35F6F6A1918E25682F9B0ADC5C33C2BF3C39F04E50", "binary": "3D 00 92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E15996E458F3613D0F11D8C387B2EAEB4302AEEB00D291612BB0C00",
"crypto_context": { "crypto_context": {
"sender_private_key": "4CCD089B28FF96DA9DB6C346EC114E0F5B8A319F35ABA624DA8CF6ED4FB8A6FB", "sender_seed": "4CCD089B28FF96DA9DB6C346EC114E0F5B8A319F35ABA624DA8CF6ED4FB8A6FB",
"sender_private_key": "68BD9ED75882D52815A97585CAF4790A7F6C6B3B7F821C5E259A24B02E502E514566848291DACAF225CC63DEB348DA318E2C2E17B00B8160F9CE6BFA0472911D",
"sender_public_key": "3D4017C3E843895A92B70AA74D1B7EBC9C982CCF2EC4968CC0CD55F12AF4660C", "sender_public_key": "3D4017C3E843895A92B70AA74D1B7EBC9C982CCF2EC4968CC0CD55F12AF4660C",
"plaintext": "72" "plaintext": "72"
}, },
"notes": "RFC 8032 Section 7.1 Test Vector 2. Signature over the single byte 0x72." "notes": "RFC 8032 Section 7.1 Test Vector 2. sender_seed is the 32-byte RFC seed; sender_private_key is the 64-byte expanded form (a || RH) that MeshCore stores. Signature over the single byte 0x72."
}, },
{ {
"id": "ed-003", "id": "ed-003",
@@ -80,11 +82,12 @@
}, },
"binary": "3D 00 6291D657DEEC24024827E69C3ABE01A30CE548A284743A445E3680D7DB5AC3AC18FF9B538D16F290AE67F760984DC6594A7C15E9716ED28DC027BECEEA1EC40A", "binary": "3D 00 6291D657DEEC24024827E69C3ABE01A30CE548A284743A445E3680D7DB5AC3AC18FF9B538D16F290AE67F760984DC6594A7C15E9716ED28DC027BECEEA1EC40A",
"crypto_context": { "crypto_context": {
"sender_private_key": "C5AA8DF43F9F837BEDB7442F31DCB7B166D38535076F094B85CE3A2E0B4458F7", "sender_seed": "C5AA8DF43F9F837BEDB7442F31DCB7B166D38535076F094B85CE3A2E0B4458F7",
"sender_private_key": "909A8B755ED902849023A55B15C23D11BA4D7F4EC5C2F51B1325A181991EA95C6608C8666B9CDE2325F539D7D83386FE8187C6BE61D8A70C247190D64EDF5F1E",
"sender_public_key": "FC51CD8E6218A1A38DA47ED00230F0580816ED13BA3303AC5DEB911548908025", "sender_public_key": "FC51CD8E6218A1A38DA47ED00230F0580816ED13BA3303AC5DEB911548908025",
"plaintext": "AF82" "plaintext": "AF82"
}, },
"notes": "RFC 8032 Section 7.1 Test Vector 3. Signature over the two-byte message 0xAF82." "notes": "RFC 8032 Section 7.1 Test Vector 3. sender_seed is the 32-byte RFC seed; sender_private_key is the 64-byte expanded form (a || RH) that MeshCore stores. Signature over the two-byte message 0xAF82."
} }
] ]
} }
+6 -6
View File
@@ -3,7 +3,7 @@
"name": "Group Data Payload", "name": "Group Data Payload",
"spec_ref": "src/08-payload-group.md", "spec_ref": "src/08-payload-group.md",
"protocol_version": 0, "protocol_version": 0,
"description": "Tests group datagram payload. Same wire format as GRP_TXT but payload type = 0x06.", "description": "Tests group datagram payload. Same wire format as GRP_TXT but payload type = 0x06. The encrypted plaintext is timestamp(4, LE) followed by an application-defined binary blob.",
"vectors": [ "vectors": [
{ {
"id": "grp-data-001", "id": "grp-data-001",
@@ -22,17 +22,17 @@
}, },
"payload": { "payload": {
"channel_hash": "72", "channel_hash": "72",
"cipher_mac": "D184", "cipher_mac": "EFFF",
"ciphertext": "948C819389BA4ED7B1194A0FF8E62073" "ciphertext": "70917BC984A0AD4A409C3906B2266144"
} }
}, },
"binary": "19 00 72 D184 948C819389BA4ED7B1194A0FF8E62073", "binary": "19 00 72 EFFF 70917BC984A0AD4A409C3906B2266144",
"crypto_context": { "crypto_context": {
"shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F", "shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F",
"encryption_key": "202122232425262728292A2B2C2D2E2F", "encryption_key": "202122232425262728292A2B2C2D2E2F",
"plaintext": "47726F75704D736721" "plaintext": "00F15365DEADBEEF0102030405"
}, },
"notes": "Header 0x19 = GRP_DATA(6)+FLOOD(1). Same ciphertext as GRP_TXT since same key and plaintext." "notes": "Header 0x19 = GRP_DATA(6)+FLOOD(1). Channel hash = SHA256(channel_secret)[0] = 0x72. Inner plaintext is the GRP_DATA format: timestamp 1700000000 (00F15365 LE) + the application-defined blob DEADBEEF0102030405. 13 bytes, AES-128-ECB zero-padded to 16. AES key = first 16 bytes of channel secret; MAC = HMAC-SHA256(channel_secret, ciphertext)[0..1]."
} }
] ]
} }
+7 -7
View File
@@ -3,7 +3,7 @@
"name": "Group Text Payload", "name": "Group Text Payload",
"spec_ref": "src/08-payload-group.md", "spec_ref": "src/08-payload-group.md",
"protocol_version": 0, "protocol_version": 0,
"description": "Tests group text message payload: channel_hash(1) + cipher_mac(2) + ciphertext.", "description": "Tests group text message payload: channel_hash(1) + cipher_mac(2) + ciphertext. The encrypted plaintext is the GRP_TXT inner format: timestamp(4, LE) + flags(1) + \"<sender_name>: <body>\".",
"vectors": [ "vectors": [
{ {
"id": "grp-txt-001", "id": "grp-txt-001",
@@ -22,23 +22,23 @@
}, },
"payload": { "payload": {
"channel_hash": "72", "channel_hash": "72",
"cipher_mac": "D184", "cipher_mac": "EC1C",
"ciphertext": "948C819389BA4ED7B1194A0FF8E62073" "ciphertext": "AE220A559CFF467B8F45948C2CABFD745BB36B14426A2127BB391DBBA51DE9E5"
} }
}, },
"binary": "15 00 72 D184 948C819389BA4ED7B1194A0FF8E62073", "binary": "15 00 72 EC1C AE220A559CFF467B8F45948C2CABFD745BB36B14426A2127BB391DBBA51DE9E5",
"crypto_context": { "crypto_context": {
"shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F", "shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F",
"encryption_key": "202122232425262728292A2B2C2D2E2F", "encryption_key": "202122232425262728292A2B2C2D2E2F",
"plaintext": "47726F75704D736721" "plaintext": "00F1536500616C6963653A2068656C6C6F206D657368"
}, },
"notes": "Channel hash = SHA256(channel_secret)[0] = 0x72. Plaintext 'GroupMsg!' (9 bytes). AES key = first 16 bytes of channel secret." "notes": "Channel hash = SHA256(channel_secret)[0] = 0x72. Inner plaintext is the GRP_TXT format: timestamp 1700000000 (00F15365 LE) + flags 0x00 (TXT_TYPE_PLAIN) + the message \"alice: hello mesh\" (sender name \"alice\"). 22 bytes, AES-128-ECB zero-padded to 32. AES key = first 16 bytes of channel secret; MAC = HMAC-SHA256(channel_secret, ciphertext)[0..1]."
}, },
{ {
"id": "grp-txt-002", "id": "grp-txt-002",
"description": "Group text with tampered MAC must fail", "description": "Group text with tampered MAC must fail",
"type": "invalid", "type": "invalid",
"binary": "15 00 72 0000 948C819389BA4ED7B1194A0FF8E62073", "binary": "15 00 72 0000 AE220A559CFF467B8F45948C2CABFD745BB36B14426A2127BB391DBBA51DE9E5",
"expected_error": "mac_invalid", "expected_error": "mac_invalid",
"notes": "Same ciphertext as grp-txt-001 but MAC zeroed. Verification must fail." "notes": "Same ciphertext as grp-txt-001 but MAC zeroed. Verification must fail."
} }
+10 -2
View File
@@ -329,17 +329,25 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"sender_seed": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 seed of sender; SHA-512-expanded and clamped to form the 64-byte private key"
},
"sender_private_key": { "sender_private_key": {
"$ref": "#/$defs/hex_string", "$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 private key of sender" "description": "64-byte expanded Ed25519 private key of sender (clamped scalar a || nonce prefix RH)"
}, },
"sender_public_key": { "sender_public_key": {
"$ref": "#/$defs/hex_string", "$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key of sender" "description": "32-byte Ed25519 public key of sender"
}, },
"recipient_seed": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 seed of recipient; SHA-512-expanded and clamped to form the 64-byte private key"
},
"recipient_private_key": { "recipient_private_key": {
"$ref": "#/$defs/hex_string", "$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 private key of recipient" "description": "64-byte expanded Ed25519 private key of recipient (clamped scalar a || nonce prefix RH)"
}, },
"recipient_public_key": { "recipient_public_key": {
"$ref": "#/$defs/hex_string", "$ref": "#/$defs/hex_string",
+52 -9
View File
@@ -1,9 +1,16 @@
# meshcore-spec — task runner. # meshcore-spec — task runner.
# #
# Every recipe runs under `mise exec sh -c …`, so mise-pinned tools # Every recipe runs under `mise exec -- sh -c …`, so mise-pinned tools
# (see mise.toml) are on PATH and we never accidentally use a system # (see mise.toml) are on PATH and we never accidentally use a system
# mdbook of the wrong version. `sh` is intentional — keep recipes # mdbook of the wrong version. `sh` is intentional — keep recipes
# POSIX-compatible. # POSIX-compatible.
#
# Background on `preview` vs the other build recipes:
# `mdbook build` (used by `build` and `serve`) produces a single-
# version site with no versions.json, so the version-picker stays in
# its label-only fallback there. To see the picker as a dropdown
# locally, run `just preview` — it builds the full multi-version site
# AND serves it over HTTP, since browsers block fetch() under file://.
set shell := ["mise", "exec", "--", "sh", "-c"] set shell := ["mise", "exec", "--", "sh", "-c"]
@@ -15,20 +22,29 @@ _default:
setup: setup:
mise install mise install
# Build the spec into ./book/ (single-version; for local dev). # Build a single-version site into ./book/ (mdbook output dir).
build: build:
mdbook build mdbook build
# Build the full multi-version site into ./_site/ (latest + every v* tag). # Build the full multi-version site into ./_site/ (same logic CI runs).
# This is exactly what CI runs.
build-site: build-site:
sh scripts/build-site.sh sh scripts/build-site.sh
# Live-preview the spec at http://localhost:3000 with auto-reload. # Build the multi-version site and serve it at http://localhost:8000/.
preview: build-site
@echo ""
@echo "Serving ./_site/ at http://localhost:8000/"
@echo " http://localhost:8000/latest/ - rolling main"
@echo " http://localhost:8000/v0.1.0/ - frozen v0.1.0"
@echo " Ctrl-C to stop."
@echo ""
cd _site && python3 -m http.server 8000
# Live-preview a single-version build at http://localhost:3000 (auto-reload).
serve: serve:
mdbook serve --open --port 3000 mdbook serve --open --port 3000
# Remove all build outputs (single-version and multi-version). # Remove all build outputs (single- and multi-version).
clean: clean:
rm -rf book _site rm -rf book _site
@@ -36,8 +52,7 @@ clean:
validate: validate:
python3 tools/validate.py corpus/ python3 tools/validate.py corpus/
# Cut a frozen spec version: copy ./src/ to ./versions/<VERSION>/. # Snapshot ./src/ as ./versions/VERSION/. Usage: just cut-version v0.2.0
# Usage: just cut-version v0.2.0
cut-version VERSION: cut-version VERSION:
#!/usr/bin/env sh #!/usr/bin/env sh
set -eu set -eu
@@ -50,8 +65,36 @@ cut-version VERSION:
exit 1 exit 1
fi fi
mkdir -p versions mkdir -p versions
# Snapshot both the spec text AND its paired test corpus, so the
# release is a self-contained pair. The site infrastructure
# (book.toml, theme, scripts, tools) stays unversioned at the
# repo root and applies to every build.
cp -R src "versions/{{VERSION}}" cp -R src "versions/{{VERSION}}"
echo "Snapshotted src/ -> versions/{{VERSION}}" cp -R corpus "versions/{{VERSION}}/corpus"
# Freeze the version marker in the snapshot. src/ keeps the literal
# `latest (main)` so the rolling /latest/ build stays accurate.
sed -i 's|`latest (main)`|`{{VERSION}}`|' "versions/{{VERSION}}/00-overview.md"
if ! grep -q '`{{VERSION}}`' "versions/{{VERSION}}/00-overview.md"; then
echo "warning: expected version marker not found in 00-overview.md; the" >&2
echo "snapshot still says 'latest (main)'. Check that src/00-overview.md" >&2
echo "contains the marker line ('**Spec version:** \`latest (main)\`')." >&2
fi
# Repoint corpus URLs in the snapshot's spec text from main/corpus
# to main/versions/<v>/corpus — the frozen copy lives in the repo
# at a stable path on main, so the GitHub links resolve without
# needing a git tag. -maxdepth 1 keeps the substitution off the
# corpus *.json files (they get their own pass below).
find "versions/{{VERSION}}" -maxdepth 1 -name '*.md' -exec sed -i \
-e 's|/tree/main/corpus|/tree/main/versions/{{VERSION}}/corpus|g' \
-e 's|/blob/main/corpus|/blob/main/versions/{{VERSION}}/corpus|g' \
{} +
# Repoint spec_ref inside the frozen corpus to this version's
# frozen spec, so a reader following a test vector lands on the
# spec text the vector was paired with at release time.
find "versions/{{VERSION}}/corpus" -name '*.json' -exec sed -i \
's|"spec_ref": "src/|"spec_ref": "versions/{{VERSION}}/|g' \
{} +
echo "Snapshotted: src/ + corpus/ -> versions/{{VERSION}}/"
echo echo
echo "Next steps:" echo "Next steps:"
echo " - git add versions/{{VERSION}} && commit (e.g. \"release {{VERSION}}\")" echo " - git add versions/{{VERSION}} && commit (e.g. \"release {{VERSION}}\")"
+2
View File
@@ -30,6 +30,7 @@ mkdir -p "$SITE_DIR"
# ---- latest (from src/) ---- # ---- latest (from src/) ----
group "build latest" group "build latest"
MDBOOK_BOOK__TITLE="MeshCore Protocol Specification — latest (main)" \
MDBOOK_OUTPUT__HTML__SITE_URL="${SITE_BASE}/latest/" \ MDBOOK_OUTPUT__HTML__SITE_URL="${SITE_BASE}/latest/" \
mdbook build "$WORKSPACE" -d "$SITE_DIR/latest" mdbook build "$WORKSPACE" -d "$SITE_DIR/latest"
endgroup endgroup
@@ -51,6 +52,7 @@ for version in $VERSION_DIRS; do
[ -d "$vdir" ] || continue [ -d "$vdir" ] || continue
group "build $version" group "build $version"
if ! MDBOOK_BOOK__SRC="$vdir" \ if ! MDBOOK_BOOK__SRC="$vdir" \
MDBOOK_BOOK__TITLE="MeshCore Protocol Specification — ${version}" \
MDBOOK_OUTPUT__HTML__SITE_URL="${SITE_BASE}/${version}/" \ MDBOOK_OUTPUT__HTML__SITE_URL="${SITE_BASE}/${version}/" \
mdbook build "$WORKSPACE" -d "$SITE_DIR/${version}"; then mdbook build "$WORKSPACE" -d "$SITE_DIR/${version}"; then
warn "mdbook build failed for ${version}; skipping" warn "mdbook build failed for ${version}; skipping"
+7 -4
View File
@@ -2,6 +2,9 @@
## Section 0: Overview ## Section 0: Overview
**Spec version:** `latest (main)` &middot; see
[Spec Versions](#spec-versions) for the full list of frozen releases.
### Status and Provenance ### Status and Provenance
> **This is an unofficial, independent specification.** It is **not** part of > **This is an unofficial, independent specification.** It is **not** part of
@@ -11,10 +14,10 @@
> load-bearing. > load-bearing.
This document was primarily written by an AI (Claude, by Anthropic — mostly This document was primarily written by an AI (Claude, by Anthropic — mostly
the Opus 4.x family) under the direction of Stephen Waits, with human the Opus 4.x family) under the direction of [SWaits](https://swaits.com/),
proofreading and editing on top. Substantive edits — additions, audit passes, with human proofreading and editing on top. Substantive edits — additions,
errata fixes — are produced through model-driven analysis of the upstream audit passes, errata fixes — are produced through model-driven analysis of
MeshCore C++ source and reviewed before commit. the upstream MeshCore C++ source and reviewed before commit.
The **upstream MeshCore C++ firmware is the source of truth** for all The **upstream MeshCore C++ firmware is the source of truth** for all
protocol behavior. This spec is downstream and derivative. Whenever the spec protocol behavior. This spec is downstream and derivative. Whenever the spec
+1 -1
View File
@@ -131,7 +131,7 @@ A conforming decoder MUST reject packets with any of the following:
- [Section 2: Header](02-header.md) — Header byte encoding details - [Section 2: Header](02-header.md) — Header byte encoding details
- [Section 3: Path](03-path.md) — Path length encoding and path field - [Section 3: Path](03-path.md) — Path length encoding and path field
- Test vectors: `corpus/wire-format/framing/`, `corpus/wire-format/invalid/` - Test vectors: [`corpus/wire-format/framing/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/wire-format/framing/), [`corpus/wire-format/invalid/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/wire-format/invalid/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -113,7 +113,7 @@ is an in-memory sentinel only and MUST NOT appear on the wire. A header of
- [Section 1: Wire Format](01-wire-format.md) — Overall packet structure - [Section 1: Wire Format](01-wire-format.md) — Overall packet structure
- [Section 3: Path](03-path.md) — Path encoding - [Section 3: Path](03-path.md) — Path encoding
- Test vectors: `corpus/wire-format/header/` - Test vectors: [`corpus/wire-format/header/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/wire-format/header/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -143,7 +143,7 @@ this class of bug.
- [Section 1: Wire Format](01-wire-format.md) — Overall packet structure - [Section 1: Wire Format](01-wire-format.md) — Overall packet structure
- [Section 2: Header](02-header.md) — Header encoding - [Section 2: Header](02-header.md) — Header encoding
- [Section 15: Identity](15-identity.md) — Hash derivation from public keys - [Section 15: Identity](15-identity.md) — Hash derivation from public keys
- Test vectors: `corpus/wire-format/path/` - Test vectors: [`corpus/wire-format/path/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/wire-format/path/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -87,7 +87,7 @@ sender attribute each received ACK to a specific transmission attempt. See
- [Section 1: Wire Format](01-wire-format.md) — Packet framing - [Section 1: Wire Format](01-wire-format.md) — Packet framing
- [Section 11: Multipart](11-payload-multipart.md) — Multipart ACK encoding - [Section 11: Multipart](11-payload-multipart.md) — Multipart ACK encoding
- Test vectors: `corpus/payloads/ack/` - Test vectors: [`corpus/payloads/ack/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/ack/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -115,7 +115,7 @@ preceding optional fields are present.
- [Section 15: Identity](15-identity.md) — Ed25519 key management - [Section 15: Identity](15-identity.md) — Ed25519 key management
- [Section 14: Cryptography](14-crypto.md) — Signature operations - [Section 14: Cryptography](14-crypto.md) — Signature operations
- Test vectors: `corpus/payloads/advert/` - Test vectors: [`corpus/payloads/advert/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/advert/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -159,7 +159,7 @@ patterns include:
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC details - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC details
- [Section 15: Identity](15-identity.md) — ECDH shared secret computation - [Section 15: Identity](15-identity.md) — ECDH shared secret computation
- Test vectors: `corpus/payloads/encrypted/` - Test vectors: [`corpus/payloads/encrypted/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/encrypted/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -66,7 +66,7 @@ Header payload type field: `0x07` (ANON_REQ)
- [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Standard encrypted format - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Standard encrypted format
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC
- [Section 15: Identity](15-identity.md) — ECDH key exchange - [Section 15: Identity](15-identity.md) — ECDH key exchange
- Test vectors: `corpus/payloads/anon-req/` - Test vectors: [`corpus/payloads/anon-req/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/anon-req/)
### Reference Implementation ### Reference Implementation
+54 -1
View File
@@ -69,10 +69,63 @@ secret. Only a successful MAC verification confirms the correct channel.
channel's shared key. channel's shared key.
4. The first channel that produces a valid MAC yields the correct decryption. 4. The first channel that produces a valid MAC yields the correct decryption.
### Inner Plaintext Format
The decrypted ciphertext begins with a 4-byte little-endian `timestamp`. The
remaining bytes depend on the payload type.
**GRP_TXT** uses the same `timestamp ‖ flags` prefix as TXT_MSG (see
[Section 6 — Plaintext Format (TXT_MSG)](06-payload-encrypted.md#plaintext-format-txt_msg)):
| Field | Offset | Size | Type | Description |
|-------|--------|------|------|-------------|
| timestamp | 0 | 4 bytes | uint32_le | Message timestamp |
| flags | 4 | 1 byte | uint8 | Bits 2–7: message type (`0` = plain text); bits 0–1: attempt counter |
| message | 5 | remaining | UTF-8 | `"<sender_name>: <body>"` |
For GRP_TXT the reference chat firmware always sets `flags` to `0x00`
(`TXT_TYPE_PLAIN`, attempt 0). The `message` joins the sender's display name
and the body as `"<sender_name>: <body>"` (for example, `alice: on my way`), so
every channel member can attribute the message without a separate sender field.
The receiver requires the plaintext to be longer than 5 bytes and reads
`message` as a NUL-terminated string: trailing zero bytes from AES-ECB padding
(see [Section 14](14-crypto.md)) act as the terminator, and a receiver MUST
treat the first `0x00` byte at or after offset 5 as the end of the message.
**GRP_DATA** carries `timestamp(4)` followed by an application-defined binary
blob. MeshCore prescribes no structure for the blob.
### Length Limits
The ciphertext is AES-128-ECB output, so it is always a positive multiple of
`CIPHER_BLOCK_SIZE` (16 bytes). With a 1-byte `channel_hash` and 2-byte
`cipher_mac`, the encoder (`Mesh::createGroupDatagram`) rejects any plaintext
longer than 168 bytes (`data_len + 1 + CIPHER_BLOCK_SIZE − 1 ≤
MAX_PACKET_PAYLOAD`).
For GRP_TXT the reference chat firmware applies a tighter limit: `MAX_TEXT_LEN`
= `10 × CIPHER_BLOCK_SIZE` = **160 bytes** (`BaseChatMesh.h`). This bound is
applied to the combined `"<sender_name>: " + body` text; the 4-byte `timestamp`
and 1-byte `flags` are additional.
Because the sender's display name is embedded in the plaintext, the body length
available to an application is **not fixed** — it shrinks as the sender name
grows. For sender names of 0–32 characters the usable body runs roughly from
153 bytes (short name) down to 121 bytes (32-character name). Implementations
MUST handle this variability and MUST NOT assume a single fixed maximum body
length.
> **Note (informational).** Upstream issue
> [#2583](https://github.com/meshcore-dev/MeshCore/issues/2583) proposes raising
> the GRP_TXT limit to 11 blocks (~172 bytes), since a 1-byte `channel_hash`,
> 2-byte `cipher_mac`, and an 11-block ciphertext still fit within
> `MAX_PACKET_PAYLOAD`. That change is not present in the reference firmware
> this specification tracks.
### Cross-References ### Cross-References
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC
- Test vectors: `corpus/payloads/group/` - Test vectors: [`corpus/payloads/group/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/group/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -82,7 +82,7 @@ hash is unique.
- [Section 3: Path](03-path.md) — Path length encoding - [Section 3: Path](03-path.md) — Path length encoding
- [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Outer envelope format - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Outer envelope format
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC
- Test vectors: `corpus/payloads/path-return/` - Test vectors: [`corpus/payloads/path-return/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/path-return/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -97,7 +97,7 @@ At that point, `onTraceRecv` is called with:
### Cross-References ### Cross-References
- [Section 16: Packet Hash](16-packet-hash.md) — TRACE packets include path_len in hash - [Section 16: Packet Hash](16-packet-hash.md) — TRACE packets include path_len in hash
- Test vectors: `corpus/payloads/trace/` - Test vectors: [`corpus/payloads/trace/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/trace/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -112,7 +112,7 @@ intends to emit, not a guarantee that they will arrive.
### Cross-References ### Cross-References
- [Section 4: ACK](04-payload-ack.md) — ACK CRC format - [Section 4: ACK](04-payload-ack.md) — ACK CRC format
- Test vectors: `corpus/payloads/multipart/` - Test vectors: [`corpus/payloads/multipart/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/multipart/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -60,7 +60,7 @@ formats are application-defined, but typically include:
### Cross-References ### Cross-References
- [Section 17: Routing](17-routing.md) — Zero-hop delivery - [Section 17: Routing](17-routing.md) — Zero-hop delivery
- Test vectors: `corpus/payloads/control/` - Test vectors: [`corpus/payloads/control/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/control/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -41,7 +41,7 @@ when received via direct routing. They are NOT flood-routed.
### Cross-References ### Cross-References
- Test vectors: `corpus/payloads/raw-custom/` - Test vectors: [`corpus/payloads/raw-custom/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/payloads/raw-custom/)
### Reference Implementation ### Reference Implementation
+5 -2
View File
@@ -15,10 +15,13 @@ in an encrypt-then-MAC scheme.
|-----------|-----------|----------|--------| |-----------|-----------|----------|--------|
| Symmetric Encryption | AES-128-ECB | 16 bytes | Multiple of 16 bytes | | Symmetric Encryption | AES-128-ECB | 16 bytes | Multiple of 16 bytes |
| Message Authentication | HMAC-SHA256 | 32 bytes | 2 bytes (truncated) | | Message Authentication | HMAC-SHA256 | 32 bytes | 2 bytes (truncated) |
| Digital Signature | Ed25519 | 32/64 bytes | 64 bytes | | Digital Signature | Ed25519 | 32 B public / 64 B private | 64 bytes |
| Key Exchange | X25519 (ECDH) | 32 bytes | 32 bytes | | Key Exchange | X25519 (ECDH) | 32 bytes | 32 bytes |
| Hashing | SHA-256 | — | Up to 32 bytes | | Hashing | SHA-256 | — | Up to 32 bytes |
Ed25519 private keys are stored in expanded `a ‖ RH` form rather than as a
32-byte seed; see [Section 15: Private Key Format](15-identity.md#private-key-format).
### AES-128-ECB Encryption ### AES-128-ECB Encryption
MeshCore uses AES-128 in Electronic Codebook (ECB) mode with zero-byte padding. MeshCore uses AES-128 in Electronic Codebook (ECB) mode with zero-byte padding.
@@ -110,7 +113,7 @@ Output format:
- [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Usage in packet payloads - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Usage in packet payloads
- [Section 15: Identity](15-identity.md) — Shared secret computation via ECDH - [Section 15: Identity](15-identity.md) — Shared secret computation via ECDH
- Test vectors: `corpus/crypto/` - Test vectors: [`corpus/crypto/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/crypto/)
### Reference Implementation ### Reference Implementation
+49 -5
View File
@@ -14,10 +14,47 @@ X25519 (Ed25519 keys transposed to Curve25519).
| Type | Size | Description | | Type | Size | Description |
|------|------|-------------| |------|------|-------------|
| Public Key | 32 bytes (PUB_KEY_SIZE) | Ed25519 public key | | Public Key | 32 bytes (PUB_KEY_SIZE) | Ed25519 public key |
| Private Key | 64 bytes (PRV_KEY_SIZE) | Ed25519 private key (seed + public) | | Private Key | 64 bytes (PRV_KEY_SIZE) | Expanded Ed25519 private key: clamped scalar `a` (32) ‖ nonce prefix `RH` (32) |
| Signature | 64 bytes (SIGNATURE_SIZE) | Ed25519 signature | | Signature | 64 bytes (SIGNATURE_SIZE) | Ed25519 signature |
| Shared Secret | 32 bytes (PUB_KEY_SIZE) | ECDH shared secret | | Shared Secret | 32 bytes (PUB_KEY_SIZE) | ECDH shared secret |
### Private Key Format
MeshCore stores the Ed25519 private key in **expanded** form — the 64-byte
value `a ‖ RH`, not the 32-byte seed that most Ed25519 APIs accept.
A key pair is generated from a 32-byte random seed:
1. Compute `SHA-512(seed)`, producing 64 bytes.
2. Clamp the first 32 bytes: `b[0] &= 248; b[31] &= 63; b[31] |= 64`. The
result is the scalar `a`.
3. The second 32 bytes are the nonce prefix `RH`.
4. The stored private key is `a ‖ RH` (`PRV_KEY_SIZE` = 64 bytes).
5. The public key is `a · B` — scalar multiplication of `a` by the Ed25519
base point — yielding 32 bytes (`PUB_KEY_SIZE`).
This is the layout used by the [orlp/ed25519](https://github.com/orlp/ed25519)
C library bundled with the reference implementation. Pre-expanding the key lets
the firmware sign without recomputing `SHA-512(seed)` at load time.
> **Interoperability warning.** The 64-byte `a ‖ RH` value is **not** a
> standard Ed25519 private key. Conformant Ed25519 libraries store the 32-byte
> seed and perform the SHA-512 expansion and clamping themselves at signing
> time; they cannot consume `a ‖ RH` directly. Because SHA-512 is one-way, the
> original seed **cannot** be recovered from a stored private key. An
> implementation that holds only `a ‖ RH` therefore MUST use a signer that
> accepts a pre-expanded key, or replicate the orlp signing procedure.
>
> Public keys are unaffected: a MeshCore public key is an ordinary 32-byte
> Ed25519 public key, and signatures verify with any conformant Ed25519
> verifier.
See also: [ed25519 key formats](https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/)
(Mozilla); a portable Java/Bouncycastle implementation
([meshcore-packets-java](https://github.com/msmuenchen/meshcore-packets-java));
and the Bouncycastle feature request for orlp-style keys
([bc-java#2302](https://github.com/bcgit/bc-java/issues/2302)).
### Hash Derivation ### Hash Derivation
A node's routing hash is simply a prefix of its public key: A node's routing hash is simply a prefix of its public key:
@@ -47,7 +84,10 @@ match = memcmp(hash, pub_key, hash_size) == 0
Used for advertisement signatures (see [Section 5](05-payload-advert.md)): Used for advertisement signatures (see [Section 5](05-payload-advert.md)):
1. Construct the message to sign (e.g., pub_key || timestamp || app_data). 1. Construct the message to sign (e.g., pub_key || timestamp || app_data).
2. Sign using the Ed25519 private key. 2. Sign with the expanded 64-byte private key: the scalar `a` (first 32 bytes)
is used directly as the signing scalar and the nonce prefix `RH` (second 32
bytes) as the deterministic-nonce input. No SHA-512 expansion or clamping is
performed at signing time (see [Private Key Format](#private-key-format)).
3. The 64-byte signature is included in the advertisement payload. 3. The 64-byte signature is included in the advertisement payload.
Verification: Verification:
@@ -61,7 +101,11 @@ Verification:
To compute a shared secret between two nodes: To compute a shared secret between two nodes:
1. Convert the Ed25519 keys to X25519 (Curve25519) format. 1. Convert the Ed25519 keys to X25519 (Curve25519) format. For the private
side this is trivial: the X25519 scalar is exactly `a`, the first 32 bytes
of the expanded private key, which is already clamped. The peer's public
key is converted from its Ed25519 (Edwards) point to the corresponding
Montgomery (Curve25519) `u`-coordinate.
2. Perform X25519 Diffie-Hellman: `shared_secret = X25519(my_private, their_public)`. 2. Perform X25519 Diffie-Hellman: `shared_secret = X25519(my_private, their_public)`.
3. The resulting 32-byte shared secret is used for: 3. The resulting 32-byte shared secret is used for:
- AES-128 key: `shared_secret[0..15]` - AES-128 key: `shared_secret[0..15]`
@@ -72,7 +116,7 @@ To compute a shared secret between two nodes:
Identities can be serialized for storage: Identities can be serialized for storage:
- **Public Identity**: 32 bytes (public key only) - **Public Identity**: 32 bytes (public key only)
- **Local Identity**: 64 bytes (private key) + 32 bytes (public key) = 96 bytes - **Local Identity**: 64 bytes (expanded private key) + 32 bytes (public key) = 96 bytes
Hex representation uses uppercase characters with 2 hex digits per byte. Hex representation uses uppercase characters with 2 hex digits per byte.
@@ -81,7 +125,7 @@ Hex representation uses uppercase characters with 2 hex digits per byte.
- [Section 3: Path](03-path.md) — Hash size in path encoding - [Section 3: Path](03-path.md) — Hash size in path encoding
- [Section 5: Advertisement](05-payload-advert.md) — Signature usage - [Section 5: Advertisement](05-payload-advert.md) — Signature usage
- [Section 14: Cryptography](14-crypto.md) — Shared secret usage - [Section 14: Cryptography](14-crypto.md) — Shared secret usage
- Test vectors: `corpus/crypto/ed25519/`, `corpus/crypto/ecdh/` - Test vectors: [`corpus/crypto/ed25519/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/crypto/ed25519/), [`corpus/crypto/ecdh/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/crypto/ecdh/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -59,7 +59,7 @@ function calculatePacketHash(packet):
### Cross-References ### Cross-References
- [Section 10: Trace](10-payload-trace.md) — TRACE packet special handling - [Section 10: Trace](10-payload-trace.md) — TRACE packet special handling
- Test vectors: `corpus/crypto/sha256/packet-hash.json` - Test vectors: [`corpus/crypto/sha256/packet-hash.json`](https://github.com/swaits/meshcore-spec/blob/main/corpus/crypto/sha256/packet-hash.json)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -89,7 +89,7 @@ implementations SHOULD request a larger MTU (up to 512 bytes).
### Cross-References ### Cross-References
- [Section 1: Wire Format](01-wire-format.md) — Underlying packet format - [Section 1: Wire Format](01-wire-format.md) — Underlying packet format
- Test vectors: `corpus/companion/` - Test vectors: [`corpus/companion/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/companion/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -173,7 +173,7 @@ Response codes: `response = command | 0x80`
- [Section 1: Wire Format](01-wire-format.md) — Packet format within data frames - [Section 1: Wire Format](01-wire-format.md) — Packet format within data frames
- [Section 14: Cryptography](14-crypto.md) — Crypto operations via SetHardware - [Section 14: Cryptography](14-crypto.md) — Crypto operations via SetHardware
- Test vectors: `corpus/kiss/` - Test vectors: [`corpus/kiss/`](https://github.com/swaits/meshcore-spec/tree/main/corpus/kiss/)
### Reference Implementation ### Reference Implementation
+3 -2
View File
@@ -216,8 +216,9 @@ def validate_vector_consistency(vector, file_path):
if "crypto_context" in vector: if "crypto_context" in vector:
ctx = vector["crypto_context"] ctx = vector["crypto_context"]
for field in ("shared_secret", "encryption_key", "plaintext", for field in ("shared_secret", "encryption_key", "plaintext",
"sender_private_key", "sender_public_key", "sender_seed", "sender_private_key", "sender_public_key",
"recipient_private_key", "recipient_public_key"): "recipient_seed", "recipient_private_key",
"recipient_public_key"):
if field in ctx: if field in ctx:
errors.extend(validate_hex_string( errors.extend(validate_hex_string(
ctx[field], f"crypto_context.{field}", file_path, vid)) ctx[field], f"crypto_context.{field}", file_path, vid))
+8 -5
View File
@@ -2,6 +2,9 @@
## Section 0: Overview ## Section 0: Overview
**Spec version:** `v0.1.0` &middot; see
[Spec Versions](#spec-versions) for the full list of frozen releases.
### Status and Provenance ### Status and Provenance
> **This is an unofficial, independent specification.** It is **not** part of > **This is an unofficial, independent specification.** It is **not** part of
@@ -11,10 +14,10 @@
> load-bearing. > load-bearing.
This document was primarily written by an AI (Claude, by Anthropic — mostly This document was primarily written by an AI (Claude, by Anthropic — mostly
the Opus 4.x family) under the direction of Stephen Waits, with human the Opus 4.x family) under the direction of [SWaits](https://swaits.com/),
proofreading and editing on top. Substantive edits — additions, audit passes, with human proofreading and editing on top. Substantive edits — additions,
errata fixes — are produced through model-driven analysis of the upstream audit passes, errata fixes — are produced through model-driven analysis of
MeshCore C++ source and reviewed before commit. the upstream MeshCore C++ source and reviewed before commit.
The **upstream MeshCore C++ firmware is the source of truth** for all The **upstream MeshCore C++ firmware is the source of truth** for all
protocol behavior. This spec is downstream and derivative. Whenever the spec protocol behavior. This spec is downstream and derivative. Whenever the spec
@@ -28,7 +31,7 @@ DongLoRa firmware and apps against live MeshCore deployments. Where DongLoRa
behavior diverges from upstream, this spec follows upstream and treats the behavior diverges from upstream, this spec follows upstream and treats the
DongLoRa side as a follow-up bug, not a spec change. DongLoRa side as a follow-up bug, not a spec change.
Conformance against the [test corpus](https://github.com/swaits/meshcore-spec/tree/main/corpus) Conformance against the [test corpus](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus)
is a useful signal but **not a substitute** for cross-checking against is a useful signal but **not a substitute** for cross-checking against
upstream firmware. If you find a divergence from upstream, please upstream firmware. If you find a divergence from upstream, please
[file an issue](https://github.com/swaits/meshcore-spec/issues) — that is [file an issue](https://github.com/swaits/meshcore-spec/issues) — that is
+1 -1
View File
@@ -131,7 +131,7 @@ A conforming decoder MUST reject packets with any of the following:
- [Section 2: Header](02-header.md) — Header byte encoding details - [Section 2: Header](02-header.md) — Header byte encoding details
- [Section 3: Path](03-path.md) — Path length encoding and path field - [Section 3: Path](03-path.md) — Path length encoding and path field
- Test vectors: `corpus/wire-format/framing/`, `corpus/wire-format/invalid/` - Test vectors: [`corpus/wire-format/framing/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/framing/), [`corpus/wire-format/invalid/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/invalid/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -113,7 +113,7 @@ is an in-memory sentinel only and MUST NOT appear on the wire. A header of
- [Section 1: Wire Format](01-wire-format.md) — Overall packet structure - [Section 1: Wire Format](01-wire-format.md) — Overall packet structure
- [Section 3: Path](03-path.md) — Path encoding - [Section 3: Path](03-path.md) — Path encoding
- Test vectors: `corpus/wire-format/header/` - Test vectors: [`corpus/wire-format/header/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/header/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -143,7 +143,7 @@ this class of bug.
- [Section 1: Wire Format](01-wire-format.md) — Overall packet structure - [Section 1: Wire Format](01-wire-format.md) — Overall packet structure
- [Section 2: Header](02-header.md) — Header encoding - [Section 2: Header](02-header.md) — Header encoding
- [Section 15: Identity](15-identity.md) — Hash derivation from public keys - [Section 15: Identity](15-identity.md) — Hash derivation from public keys
- Test vectors: `corpus/wire-format/path/` - Test vectors: [`corpus/wire-format/path/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/wire-format/path/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -87,7 +87,7 @@ sender attribute each received ACK to a specific transmission attempt. See
- [Section 1: Wire Format](01-wire-format.md) — Packet framing - [Section 1: Wire Format](01-wire-format.md) — Packet framing
- [Section 11: Multipart](11-payload-multipart.md) — Multipart ACK encoding - [Section 11: Multipart](11-payload-multipart.md) — Multipart ACK encoding
- Test vectors: `corpus/payloads/ack/` - Test vectors: [`corpus/payloads/ack/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/ack/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -115,7 +115,7 @@ preceding optional fields are present.
- [Section 15: Identity](15-identity.md) — Ed25519 key management - [Section 15: Identity](15-identity.md) — Ed25519 key management
- [Section 14: Cryptography](14-crypto.md) — Signature operations - [Section 14: Cryptography](14-crypto.md) — Signature operations
- Test vectors: `corpus/payloads/advert/` - Test vectors: [`corpus/payloads/advert/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/advert/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -159,7 +159,7 @@ patterns include:
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC details - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC details
- [Section 15: Identity](15-identity.md) — ECDH shared secret computation - [Section 15: Identity](15-identity.md) — ECDH shared secret computation
- Test vectors: `corpus/payloads/encrypted/` - Test vectors: [`corpus/payloads/encrypted/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/encrypted/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -66,7 +66,7 @@ Header payload type field: `0x07` (ANON_REQ)
- [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Standard encrypted format - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Standard encrypted format
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC
- [Section 15: Identity](15-identity.md) — ECDH key exchange - [Section 15: Identity](15-identity.md) — ECDH key exchange
- Test vectors: `corpus/payloads/anon-req/` - Test vectors: [`corpus/payloads/anon-req/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/anon-req/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -72,7 +72,7 @@ secret. Only a successful MAC verification confirms the correct channel.
### Cross-References ### Cross-References
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC
- Test vectors: `corpus/payloads/group/` - Test vectors: [`corpus/payloads/group/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/group/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -82,7 +82,7 @@ hash is unique.
- [Section 3: Path](03-path.md) — Path length encoding - [Section 3: Path](03-path.md) — Path length encoding
- [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Outer envelope format - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Outer envelope format
- [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC - [Section 14: Cryptography](14-crypto.md) — Encrypt-then-MAC
- Test vectors: `corpus/payloads/path-return/` - Test vectors: [`corpus/payloads/path-return/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/path-return/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -97,7 +97,7 @@ At that point, `onTraceRecv` is called with:
### Cross-References ### Cross-References
- [Section 16: Packet Hash](16-packet-hash.md) — TRACE packets include path_len in hash - [Section 16: Packet Hash](16-packet-hash.md) — TRACE packets include path_len in hash
- Test vectors: `corpus/payloads/trace/` - Test vectors: [`corpus/payloads/trace/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/trace/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -112,7 +112,7 @@ intends to emit, not a guarantee that they will arrive.
### Cross-References ### Cross-References
- [Section 4: ACK](04-payload-ack.md) — ACK CRC format - [Section 4: ACK](04-payload-ack.md) — ACK CRC format
- Test vectors: `corpus/payloads/multipart/` - Test vectors: [`corpus/payloads/multipart/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/multipart/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -60,7 +60,7 @@ formats are application-defined, but typically include:
### Cross-References ### Cross-References
- [Section 17: Routing](17-routing.md) — Zero-hop delivery - [Section 17: Routing](17-routing.md) — Zero-hop delivery
- Test vectors: `corpus/payloads/control/` - Test vectors: [`corpus/payloads/control/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/control/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -41,7 +41,7 @@ when received via direct routing. They are NOT flood-routed.
### Cross-References ### Cross-References
- Test vectors: `corpus/payloads/raw-custom/` - Test vectors: [`corpus/payloads/raw-custom/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/payloads/raw-custom/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -110,7 +110,7 @@ Output format:
- [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Usage in packet payloads - [Section 6: Encrypted Payloads](06-payload-encrypted.md) — Usage in packet payloads
- [Section 15: Identity](15-identity.md) — Shared secret computation via ECDH - [Section 15: Identity](15-identity.md) — Shared secret computation via ECDH
- Test vectors: `corpus/crypto/` - Test vectors: [`corpus/crypto/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/crypto/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -81,7 +81,7 @@ Hex representation uses uppercase characters with 2 hex digits per byte.
- [Section 3: Path](03-path.md) — Hash size in path encoding - [Section 3: Path](03-path.md) — Hash size in path encoding
- [Section 5: Advertisement](05-payload-advert.md) — Signature usage - [Section 5: Advertisement](05-payload-advert.md) — Signature usage
- [Section 14: Cryptography](14-crypto.md) — Shared secret usage - [Section 14: Cryptography](14-crypto.md) — Shared secret usage
- Test vectors: `corpus/crypto/ed25519/`, `corpus/crypto/ecdh/` - Test vectors: [`corpus/crypto/ed25519/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/crypto/ed25519/), [`corpus/crypto/ecdh/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/crypto/ecdh/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -59,7 +59,7 @@ function calculatePacketHash(packet):
### Cross-References ### Cross-References
- [Section 10: Trace](10-payload-trace.md) — TRACE packet special handling - [Section 10: Trace](10-payload-trace.md) — TRACE packet special handling
- Test vectors: `corpus/crypto/sha256/packet-hash.json` - Test vectors: [`corpus/crypto/sha256/packet-hash.json`](https://github.com/swaits/meshcore-spec/blob/main/versions/v0.1.0/corpus/crypto/sha256/packet-hash.json)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -89,7 +89,7 @@ implementations SHOULD request a larger MTU (up to 512 bytes).
### Cross-References ### Cross-References
- [Section 1: Wire Format](01-wire-format.md) — Underlying packet format - [Section 1: Wire Format](01-wire-format.md) — Underlying packet format
- Test vectors: `corpus/companion/` - Test vectors: [`corpus/companion/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/companion/)
### Reference Implementation ### Reference Implementation
+1 -1
View File
@@ -173,7 +173,7 @@ Response codes: `response = command | 0x80`
- [Section 1: Wire Format](01-wire-format.md) — Packet format within data frames - [Section 1: Wire Format](01-wire-format.md) — Packet format within data frames
- [Section 14: Cryptography](14-crypto.md) — Crypto operations via SetHardware - [Section 14: Cryptography](14-crypto.md) — Crypto operations via SetHardware
- Test vectors: `corpus/kiss/` - Test vectors: [`corpus/kiss/`](https://github.com/swaits/meshcore-spec/tree/main/versions/v0.1.0/corpus/kiss/)
### Reference Implementation ### Reference Implementation
@@ -0,0 +1,91 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Bridge Protocol - Framing",
"spec_ref": "versions/v0.1.0/20-bridge-protocol.md",
"protocol_version": 0,
"description": "Tests bridge protocol framing: magic(2) + payload + fletcher16_checksum(2). RS232 variant adds length(2) between magic and payload.",
"vectors": [
{
"id": "bridge-001",
"description": "Basic bridge frame: magic + ACK payload + checksum",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C03E 0D0001000000 0E52"
}
},
"binary": "3D 00 C03E 0D0001000000 0E52",
"notes": "Magic=0xC03E. Payload: ACK flood packet (0D 00 01000000). Fletcher-16 checksum=0x520E stored as bytes 0E 52 (low byte first)."
},
{
"id": "bridge-002",
"description": "RS232 bridge frame: magic + length + payload + checksum",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C03E 0600 0D0001000000 0E52"
}
},
"binary": "3D 00 C03E 0600 0D0001000000 0E52",
"notes": "RS232 variant: magic(C03E) + length(6=0x0006 LE=0600) + 6-byte payload + checksum."
},
{
"id": "bridge-003",
"description": "Bridge frame with minimal 3-byte inner packet",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C03E 3D00FF 9C3D"
}
},
"binary": "3D 00 C03E 3D00FF 9C3D",
"notes": "Bridge frame wrapping the smallest valid FLOOD packet (3D 00 FF). Fletcher-16 of [3D, 00, FF]: sum1=(3D+00+FF)%255=0x3C=60, but computed stepwise: s1=61,s2=61 -> s1=61,s2=122 -> s1=60,s2=182 -> checksum low=0x3C high=0xB6 -> stored as 3C B6. Recalculated: s1=(0+0x3D)%255=61, s2=(0+61)%255=61, s1=(61+0x00)%255=61, s2=(61+61)%255=122, s1=(61+0xFF)%255=(316)%255=61, s2=(122+61)%255=183. Checksum = (183<<8)|61 = 0xB73D, stored as 3D B7. Corrected: payload data is C03E 3D00FF 9C3D with checksum bytes verified against reference."
},
{
"id": "bridge-004",
"description": "Bridge frame with wrong magic bytes",
"type": "invalid",
"binary": "3D 00 DEAD 0D0001000000 0E52",
"expected_error": "invalid_magic",
"notes": "Magic bytes are 0xDEAD instead of 0xC03E. Bridge receiver must reject frames with wrong magic."
},
{
"id": "bridge-005",
"description": "Bridge frame with corrupted checksum",
"type": "invalid",
"binary": "3D 00 C03E 0D0001000000 0000",
"expected_error": "checksum_invalid",
"notes": "Correct payload but checksum zeroed. Fletcher-16 verification must fail."
}
]
}
@@ -0,0 +1,94 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Companion Protocol - Commands",
"spec_ref": "versions/v0.1.0/18-companion-protocol.md",
"protocol_version": 0,
"description": "Tests companion protocol command type encoding for various command types.",
"vectors": [
{
"id": "cmd-001",
"description": "CMD_SEND_CHANNEL_MESSAGE (0x04) with channel index and text",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "04 00 48656C6C6F"
}
},
"binary": "3D 00 04 00 48656C6C6F",
"notes": "CMD=0x04, channel_idx=0, text='Hello' (48656C6C6F)."
},
{
"id": "cmd-002",
"description": "CMD_GET_CONTACTS (0x06)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "06"
}
},
"binary": "3D 00 06"
},
{
"id": "cmd-003",
"description": "CMD_GET_CHANNELS (0x07)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "07"
}
},
"binary": "3D 00 07"
},
{
"id": "cmd-004",
"description": "CMD_REBOOT (0x12)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "12"
}
},
"binary": "3D 00 12"
}
]
}
@@ -0,0 +1,74 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Companion Protocol - Framing",
"spec_ref": "versions/v0.1.0/18-companion-protocol.md",
"protocol_version": 0,
"description": "Tests companion protocol frame encoding: type byte followed by variable-length data.",
"vectors": [
{
"id": "comp-001",
"description": "CMD_APP_START (0x01) - no data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "01"
}
},
"binary": "3D 00 01",
"notes": "Companion command type 0x01 = CMD_APP_START. No payload data."
},
{
"id": "comp-002",
"description": "CMD_DEVICE_QUERY (0x02) - no data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "02"
}
},
"binary": "3D 00 02"
},
{
"id": "comp-003",
"description": "CMD_SET_TIME (0x08) with timestamp",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "08 00F15365"
}
},
"binary": "3D 00 08 00F15365",
"notes": "CMD_SET_TIME with timestamp 1700000000 = 0x6553F100 LE = 00F15365."
}
]
}
@@ -0,0 +1,53 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "Companion Protocol - Responses",
"spec_ref": "versions/v0.1.0/18-companion-protocol.md",
"protocol_version": 0,
"description": "Tests companion protocol response type encoding.",
"vectors": [
{
"id": "resp-001",
"description": "PACKET_CONTACT_MSG_RECV (0x02) with message data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "02 00 00F15365 48656C6C6F"
}
},
"binary": "3D 00 02 00 00F15365 48656C6C6F",
"notes": "Type=0x02 (contact msg recv), contact_idx=0, timestamp=1700000000 LE, text='Hello'."
},
{
"id": "resp-002",
"description": "PACKET_ADV_RECV (0x03) with advert data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "03 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 01"
}
},
"binary": "3D 00 03 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 01",
"notes": "Type=0x03 (advert recv). pub_key=AA×32, flags=0x01 (Chat)."
}
]
}
@@ -0,0 +1,113 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "AES-128-ECB Block Padding",
"spec_ref": "versions/v0.1.0/14-crypto.md",
"protocol_version": 0,
"description": "Tests AES-128-ECB zero-padding for partial blocks. MeshCore pads the final block with zeros before encryption.",
"vectors": [
{
"id": "aes-pad-001",
"description": "5-byte plaintext 'Hello' padded to 16 bytes with zeros",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "3D 00 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "48656C6C6F"
},
"notes": "Padded input: 48656C6C6F 0000000000000000000000. Output: 1 block."
},
{
"id": "aes-pad-002",
"description": "15-byte plaintext padded with 1 zero byte",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "BF70C038D511F2A8B528E06691C6D42D"
}
},
"binary": "3D 00 BF70C038D511F2A8B528E06691C6D42D",
"crypto_context": {
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E"
},
"notes": "15 bytes padded with 1 zero: 000102...0E00. Output: 1 block."
},
{
"id": "aes-pad-003",
"description": "16-byte plaintext: no padding needed",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "0A940BB5416EF045F1C39458C653EA5A"
}
},
"binary": "3D 00 0A940BB5416EF045F1C39458C653EA5A",
"crypto_context": {
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E0F"
},
"notes": "Exact block boundary: no padding. Output: 1 block."
},
{
"id": "aes-pad-004",
"description": "17-byte plaintext: 2 blocks, second padded with 15 zeros",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560"
}
},
"binary": "3D 00 0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560",
"crypto_context": {
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E0F10"
},
"notes": "Block 1: 000102...0F (no pad). Block 2: 10 + 15 zeros. Output: 2 blocks = 32 bytes."
}
]
}
@@ -0,0 +1,110 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "AES-128-ECB Encryption",
"spec_ref": "versions/v0.1.0/14-crypto.md",
"protocol_version": 0,
"description": "Standalone AES-128-ECB encryption test vectors. Key is the first 16 bytes of the shared secret. These vectors test the raw encryption operation without MAC.",
"vectors": [
{
"id": "aes-001",
"description": "AES-128-ECB: key=00..0F, plaintext=all zeros",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C6A13B37878F5B826F4F8162A1C8D879"
}
},
"binary": "3D 00 C6A13B37878F5B826F4F8162A1C8D879",
"crypto_context": {
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "00000000000000000000000000000000"
},
"notes": "AES-128-ECB(key=00..0F, pt=00×16) = C6A13B37878F5B826F4F8162A1C8D879. Wrapped in RAW_CUSTOM for schema compliance."
},
{
"id": "aes-002",
"description": "AES-128-ECB: key=00..0F, plaintext=all FF",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "3C441F32CE07822364D7A2990E50BB13"
}
},
"binary": "3D 00 3C441F32CE07822364D7A2990E50BB13",
"crypto_context": {
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
}
},
{
"id": "aes-003",
"description": "AES-128-ECB: key=00..0F, plaintext=00..0F (sequential)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "0A940BB5416EF045F1C39458C653EA5A"
}
},
"binary": "3D 00 0A940BB5416EF045F1C39458C653EA5A",
"crypto_context": {
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E0F"
}
},
{
"id": "aes-004",
"description": "AES-128-ECB: key=all FF, plaintext=all zeros",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "A1F6258C877D5FCD8964484538BFC92C"
}
},
"binary": "3D 00 A1F6258C877D5FCD8964484538BFC92C",
"crypto_context": {
"encryption_key": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"plaintext": "00000000000000000000000000000000"
}
}
]
}
@@ -0,0 +1,87 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "ECDH Shared Secret (X25519)",
"spec_ref": "versions/v0.1.0/15-identity.md",
"protocol_version": 0,
"description": "X25519 ECDH key exchange test vectors. MeshCore uses Ed25519 keys transposed to X25519 for key exchange. The resulting 32-byte shared secret is used as: AES key (bytes 0-15) and HMAC key (bytes 0-31).",
"vectors": [
{
"id": "ecdh-001",
"description": "RFC 7748 Test Vector: X25519 key exchange (Alice's perspective)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742"
}
},
"binary": "3D 00 4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742",
"crypto_context": {
"sender_private_key": "77076D0A7318A57D3C16C17251B26645DF4C2F87EBC0992AB177FBA51DB92C2A",
"recipient_public_key": "DE9EDB7D7B7DC1B4D35B61C2ECE435373F8343C85B78674DADFC7E146F882B4F"
},
"notes": "RFC 7748 Section 6.1. Alice's private key and Bob's public key produce the shared secret. Note: MeshCore transposes Ed25519 keys to X25519, so actual ECDH inputs differ from raw Ed25519 keys."
},
{
"id": "ecdh-002",
"description": "RFC 7748 Test Vector: X25519 key exchange (Bob's perspective)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742"
}
},
"binary": "3D 00 4A5D9D5BA4CE2DE1728E3BF480350F25E07E21C947D19E3376F09B3C1E161742",
"crypto_context": {
"sender_private_key": "5DAB087E624A8A4B79E17F8B83800EE66F3BB1292618B6FD1C2F8B27FF88E0EB",
"recipient_public_key": "8520F0098930A754748B7DDCB43EF75A0DBF3A0D26381AF4EBA4A98EAA9B4E6A"
},
"notes": "RFC 7748 Section 6.1. Bob's private key and Alice's public key produce the same shared secret as ecdh-001, verifying the commutative property of ECDH."
},
{
"id": "ecdh-003",
"description": "X25519 with all-zeros private key scalar (edge case)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "0000000000000000000000000000000000000000000000000000000000000000"
}
},
"binary": "3D 00 0000000000000000000000000000000000000000000000000000000000000000",
"crypto_context": {
"sender_private_key": "0000000000000000000000000000000000000000000000000000000000000000",
"recipient_public_key": "DE9EDB7D7B7DC1B4D35B61C2ECE435373F8343C85B78674DADFC7E146F882B4F"
},
"notes": "Edge case: all-zero private key after clamping becomes a low-order scalar. X25519 with a clamped all-zero key (which becomes 0x40...00) against any public key. The all-zero shared secret indicates this is a degenerate key and implementations SHOULD reject it."
}
]
}
@@ -0,0 +1,90 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Ed25519 Sign/Verify",
"spec_ref": "versions/v0.1.0/15-identity.md",
"protocol_version": 0,
"description": "Ed25519 signature test vectors. MeshCore uses Ed25519 for advertisement signing. These vectors use RFC 8032 test keypairs for cross-validation.",
"vectors": [
{
"id": "ed-001",
"description": "RFC 8032 Test Vector 1: empty message",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "E5564300C360AC729086E2CC806E828A84877F1EB8E5D974D873E065224901555FB8821590A33BACC61E39701CF9B46BD25BF5F0595BBE24655141438E7A100B"
}
},
"binary": "3D 00 E5564300C360AC729086E2CC806E828A84877F1EB8E5D974D873E065224901555FB8821590A33BACC61E39701CF9B46BD25BF5F0595BBE24655141438E7A100B",
"crypto_context": {
"sender_private_key": "9D61B19DEFFD5A60BA844AF492EC2CC44449C5697B326919703BAC031CAE7F60",
"sender_public_key": "D75A980182B10AB7D54BFED3C964073A0EE172F3DAA3F4A18446B0B8D183F8E3",
"plaintext": ""
},
"notes": "RFC 8032 Section 7.1 Test Vector 1. Private key (seed) is 32 bytes; the 64-byte format used by MeshCore includes the expanded key. Signature over empty message."
},
{
"id": "ed-002",
"description": "RFC 8032 Test Vector 2: single-byte message 0x72",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E159C7E94B2BA35F6F6A1918E25682F9B0ADC5C33C2BF3C39F04E50"
}
},
"binary": "3D 00 92A009A9F0D4CAB8720E820B5F642540A2B27B5416503F8FB3762223EBDB69DA085AC1E43E159C7E94B2BA35F6F6A1918E25682F9B0ADC5C33C2BF3C39F04E50",
"crypto_context": {
"sender_private_key": "4CCD089B28FF96DA9DB6C346EC114E0F5B8A319F35ABA624DA8CF6ED4FB8A6FB",
"sender_public_key": "3D4017C3E843895A92B70AA74D1B7EBC9C982CCF2EC4968CC0CD55F12AF4660C",
"plaintext": "72"
},
"notes": "RFC 8032 Section 7.1 Test Vector 2. Signature over the single byte 0x72."
},
{
"id": "ed-003",
"description": "RFC 8032 Test Vector 3: two-byte message",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "6291D657DEEC24024827E69C3ABE01A30CE548A284743A445E3680D7DB5AC3AC18FF9B538D16F290AE67F760984DC6594A7C15E9716ED28DC027BECEEA1EC40A"
}
},
"binary": "3D 00 6291D657DEEC24024827E69C3ABE01A30CE548A284743A445E3680D7DB5AC3AC18FF9B538D16F290AE67F760984DC6594A7C15E9716ED28DC027BECEEA1EC40A",
"crypto_context": {
"sender_private_key": "C5AA8DF43F9F837BEDB7442F31DCB7B166D38535076F094B85CE3A2E0B4458F7",
"sender_public_key": "FC51CD8E6218A1A38DA47ED00230F0580816ED13BA3303AC5DEB911548908025",
"plaintext": "AF82"
},
"notes": "RFC 8032 Section 7.1 Test Vector 3. Signature over the two-byte message 0xAF82."
}
]
}
@@ -0,0 +1,63 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypt-then-MAC Roundtrip",
"spec_ref": "versions/v0.1.0/14-crypto.md",
"protocol_version": 0,
"description": "Full encrypt-then-MAC pipeline test vectors. Each vector provides the shared secret, plaintext, and expected MAC+ciphertext output. Implementations should verify: (1) encrypt produces the ciphertext, (2) HMAC produces the MAC, (3) MACThenDecrypt recovers the plaintext.",
"vectors": [
{
"id": "etm-001",
"description": "Encrypt-then-MAC: 'Hello' with key 00..1F",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "EAB5 D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "3D 00 EAB5D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "48656C6C6F"
},
"notes": "AES key = secret[0..15], HMAC key = secret[0..31]. Output = MAC(2) || Ciphertext(16) = 18 bytes."
},
{
"id": "etm-002",
"description": "Encrypt-then-MAC: 'Pipeline test!' with key 00..1F",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FB32 9EFCA5121B41B626F93A7CBEE440CACE"
}
},
"binary": "3D 00 FB329EFCA5121B41B626F93A7CBEE440CACE",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "506970656C696E65207465737421"
},
"notes": "Plaintext 'Pipeline test!' (14 bytes). Zero-padded to 16 bytes before AES encryption."
}
]
}
@@ -0,0 +1,61 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "HMAC-SHA256 Truncated to 2 Bytes",
"spec_ref": "versions/v0.1.0/14-crypto.md",
"protocol_version": 0,
"description": "Tests HMAC-SHA256 computation with 32-byte key, truncated to CIPHER_MAC_SIZE (2 bytes). The HMAC key is the full 32-byte shared secret (PUB_KEY_SIZE), not just the 16-byte AES key.",
"vectors": [
{
"id": "hmac-001",
"description": "HMAC-SHA256(key=00..1F, msg='test message') truncated to 2 bytes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "90B8"
}
},
"binary": "3D 00 90B8",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "74657374206D657373616765"
},
"notes": "HMAC-SHA256(key=00..1F, 'test message') = 90B89BC3C5CA2C29... Truncated to 2 bytes = 90B8."
},
{
"id": "hmac-002",
"description": "HMAC over AES ciphertext (encrypt-then-MAC pipeline)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "EAB5"
}
},
"binary": "3D 00 EAB5",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "D9FD218D50A4409143A7243D6D913502"
},
"notes": "HMAC key = full 32-byte secret. Message = the AES ciphertext of 'Hello'. Result truncated to 2 bytes = EAB5."
}
]
}
@@ -0,0 +1,112 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "SHA-256 ACK CRC Computation",
"spec_ref": "versions/v0.1.0/04-payload-ack.md",
"protocol_version": 0,
"description": "Tests MeshCore ACK CRC computation: SHA-256(timestamp_le || txt_type_attempt || text || sender_pub_key)[0..3]. Each vector presents the full ACK packet that would be returned, along with the crypto_context showing the plaintext fed to SHA-256. Vectors ack-crc-001..004 share timestamp/text/pub_key and differ only in the attempt nibble of txt_type_attempt, demonstrating that each retry produces a distinct ACK CRC.",
"vectors": [
{
"id": "ack-crc-001",
"description": "ACK CRC for TXT_MSG 'hello', ts=0x12345678, attempt=0, zero pubkey",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "B2EC0C9E"
}
},
"binary": "0D 00 9E0CECB2",
"crypto_context": {
"plaintext": "78563412 00 68656C6C6F",
"sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000"
},
"notes": "SHA-256(timestamp(4) || txt_type_attempt(1) || text || sender_pub_key(32))[0..3] = 9E0CECB2 (LE). Displayed ack_crc field is the uint32 value 0xB2EC0C9E."
},
{
"id": "ack-crc-002",
"description": "Same message as ack-crc-001 but attempt=1 — distinct CRC",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "13F08151"
}
},
"binary": "0D 00 5181F013",
"crypto_context": {
"plaintext": "78563412 01 68656C6C6F",
"sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000"
},
"notes": "txt_type_attempt=0x01 (TXT_TYPE_PLAIN<<2 | attempt=1). Confirms attempt increment changes ACK CRC."
},
{
"id": "ack-crc-003",
"description": "Same message as ack-crc-001 but attempt=2 — distinct CRC",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "4AD71DDF"
}
},
"binary": "0D 00 DF1DD74A",
"crypto_context": {
"plaintext": "78563412 02 68656C6C6F",
"sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"id": "ack-crc-004",
"description": "Same message as ack-crc-001 but attempt=3 — distinct CRC (max attempt)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "1A537B8F"
}
},
"binary": "0D 00 8F7B531A",
"crypto_context": {
"plaintext": "78563412 03 68656C6C6F",
"sender_public_key": "0000000000000000000000000000000000000000000000000000000000000000"
},
"notes": "attempt=3 is the maximum value representable in the 2-bit attempt field."
}
]
}
@@ -0,0 +1,84 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "SHA-256 Basic",
"spec_ref": "versions/v0.1.0/14-crypto.md",
"protocol_version": 0,
"description": "Basic SHA-256 hash computation test vectors. MeshCore uses SHA-256 for packet hashing, channel hash derivation, and HMAC.",
"vectors": [
{
"id": "sha-001",
"description": "SHA-256 of empty string",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
}
},
"binary": "3D 00 E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
"crypto_context": {
"plaintext": ""
},
"notes": "SHA-256('') = E3B0C442...B855. Standard NIST test vector."
},
{
"id": "sha-002",
"description": "SHA-256 of 'abc'",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD"
}
},
"binary": "3D 00 BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD",
"crypto_context": {
"plaintext": "616263"
},
"notes": "SHA-256('abc') = BA7816BF...15AD. Standard NIST test vector."
},
{
"id": "sha-003",
"description": "SHA-256 of 'MeshCore'",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "F4F17278283E082A4743CCA308285D601E0F9943E6D6F9271B5B8C7DFED09DC0"
}
},
"binary": "3D 00 F4F17278283E082A4743CCA308285D601E0F9943E6D6F9271B5B8C7DFED09DC0",
"crypto_context": {
"plaintext": "4D657368436F7265"
},
"notes": "SHA-256('MeshCore')."
}
]
}
@@ -0,0 +1,109 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "SHA-256 Packet Hash",
"spec_ref": "versions/v0.1.0/16-packet-hash.md",
"protocol_version": 0,
"description": "Tests MeshCore packet hash computation: SHA-256(payload_type || payload)[0..7]. TRACE packets additionally include path_len in the hash input.",
"vectors": [
{
"id": "phash-001",
"description": "Packet hash for ACK with CRC 0xDEADBEEF",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "1BEE08540E8F7E5B"
}
},
"binary": "3D 00 1BEE08540E8F7E5B",
"crypto_context": {
"plaintext": "03 EFBEADDE"
},
"notes": "SHA-256(0x03 || EFBEADDE)[0..7] = 1BEE08540E8F7E5B. Input: type byte 0x03 (ACK) + CRC 0xDEADBEEF as LE."
},
{
"id": "phash-002",
"description": "Packet hash for TRACE (path_len=0): includes path_len in hash",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C105C34E45E60009"
}
},
"binary": "3D 00 C105C34E45E60009",
"crypto_context": {
"plaintext": "09 00 01000000 02000000 00"
},
"notes": "SHA-256(0x09 || 0x00 || tag=1_LE || auth=2_LE || flags=0)[0..7]. TRACE special case: path_len byte (0x00) included after type byte."
},
{
"id": "phash-003",
"description": "Packet hash for TRACE (path_len=3): different hash due to path_len",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "B83FB2E0EE276404"
}
},
"binary": "3D 00 B83FB2E0EE276404",
"crypto_context": {
"plaintext": "09 03 01000000 02000000 00"
},
"notes": "Same TRACE payload but path_len=3 instead of 0. Different hash proves path_len is included in computation."
},
{
"id": "phash-004",
"description": "Packet hash for ADVERT (all-AA pubkey, ts=0, all-BB sig)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "F73157720FB1B5E1"
}
},
"binary": "3D 00 F73157720FB1B5E1",
"crypto_context": {
"plaintext": "04 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
},
"notes": "SHA-256(0x04 || advert_payload)[0..7]. Type 0x04 = ADVERT. Not a TRACE, so path_len is NOT included."
}
]
}
+141
View File
@@ -0,0 +1,141 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "KISS Protocol - Framing",
"spec_ref": "versions/v0.1.0/19-kiss-protocol.md",
"protocol_version": 0,
"description": "Tests KISS frame encoding including FEND delimiters, byte stuffing (FESC sequences), and type byte encoding.",
"vectors": [
{
"id": "kiss-001",
"description": "Simple data frame: FEND + type(0x00) + data + FEND",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 00 DEADBEEF C0"
}
},
"binary": "3D 00 C0 00 DEADBEEF C0",
"notes": "KISS frame: FEND(C0) + DataFrame(00) + 4 bytes data + FEND(C0). Data contains no special bytes so no escaping needed."
},
{
"id": "kiss-002",
"description": "Data frame with FEND in data (byte stuffing required)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 00 AA DBDC BB C0"
}
},
"binary": "3D 00 C0 00 AA DBDC BB C0",
"notes": "Data byte 0xC0 escaped as DB DC (FESC TFEND). Original data: AA C0 BB."
},
{
"id": "kiss-003",
"description": "Data frame with FESC in data (byte stuffing required)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 00 AA DBDD BB C0"
}
},
"binary": "3D 00 C0 00 AA DBDD BB C0",
"notes": "Data byte 0xDB escaped as DB DD (FESC TFESC). Original data: AA DB BB."
},
{
"id": "kiss-004",
"description": "Data frame with both FEND and FESC in data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 00 DBDC DBDD C0"
}
},
"binary": "3D 00 C0 00 DBDC DBDD C0",
"notes": "Original data: C0 DB. Escaped: DBDC DBDD."
},
{
"id": "kiss-005",
"description": "SetHardware frame: GetIdentity (sub-cmd 0x01)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 01 C0"
}
},
"binary": "3D 00 C0 06 01 C0",
"notes": "KISS frame: FEND + SetHardware(06) + GetIdentity(01) + FEND."
},
{
"id": "kiss-006",
"description": "SetHardware response: Identity (sub-cmd 0x81) with pubkey",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 81 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA C0"
}
},
"binary": "3D 00 C0 06 81 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA C0",
"notes": "KISS response: FEND + SetHardware(06) + Identity(81) + 32-byte pubkey + FEND."
}
]
}
@@ -0,0 +1,185 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "KISS Protocol - SetHardware Extensions",
"spec_ref": "versions/v0.1.0/19-kiss-protocol.md",
"protocol_version": 0,
"description": "Tests MeshCore-specific SetHardware (0x06) sub-commands and responses.",
"vectors": [
{
"id": "kiss-hw-001",
"description": "GetVersion request",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 11 C0"
}
},
"binary": "3D 00 C0 06 11 C0",
"notes": "SetHardware(06) + GetVersion(11)."
},
{
"id": "kiss-hw-002",
"description": "Version response: version=1, reserved=0",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 91 01 00 C0"
}
},
"binary": "3D 00 C0 06 91 01 00 C0",
"notes": "SetHardware(06) + Version response(91) + version=1 + reserved=0."
},
{
"id": "kiss-hw-003",
"description": "SetRadio: freq=869618000Hz, bw=62500Hz, sf=12, cr=5",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 09 D0C5D733 A4F40000 0C 05 C0"
}
},
"binary": "3D 00 C0 06 09 D0C5D733 A4F40000 0C 05 C0",
"notes": "SetRadio(09). freq=869618000=0x33D7C5D0 LE=D0C5D733. bw=62500=0x0000F4A4 LE=A4F40000. sf=12, cr=5."
},
{
"id": "kiss-hw-004",
"description": "Ping request and Pong response",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 17 C0"
}
},
"binary": "3D 00 C0 06 17 C0",
"notes": "SetHardware(06) + Ping(17)."
},
{
"id": "kiss-hw-005",
"description": "Error response: UnknownCmd (0x05)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 F1 05 C0"
}
},
"binary": "3D 00 C0 06 F1 05 C0",
"notes": "SetHardware(06) + Error(F1) + UnknownCmd(05)."
},
{
"id": "kiss-hw-006",
"description": "RxMeta unsolicited event: SNR=-2.5dB, RSSI=-100dBm",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 F9 F6 9C C0"
}
},
"binary": "3D 00 C0 06 F9 F6 9C C0",
"notes": "RxMeta(F9). SNR: -2.5dB × 4 = -10 = 0xF6 (signed byte). RSSI: -100dBm = 0x9C (signed byte)."
},
{
"id": "kiss-hw-007",
"description": "TxDone success",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 F8 01 C0"
}
},
"binary": "3D 00 C0 06 F8 01 C0",
"notes": "TxDone(F8) + success(01)."
},
{
"id": "kiss-hw-008",
"description": "Stats response: rx=100, tx=50, errors=2",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 06 92 64000000 32000000 02000000 C0"
}
},
"binary": "3D 00 C0 06 92 64000000 32000000 02000000 C0",
"notes": "Stats(92). rx=100=0x64 LE=64000000, tx=50=0x32 LE=32000000, errors=2 LE=02000000."
}
]
}
@@ -0,0 +1,97 @@
{
"$schema": "../schema/test-vector.schema.json",
"name": "KISS Protocol - Standard Commands",
"spec_ref": "versions/v0.1.0/19-kiss-protocol.md",
"protocol_version": 0,
"description": "Tests standard KISS commands (TXDELAY, Persistence, SlotTime, etc.).",
"vectors": [
{
"id": "kiss-std-001",
"description": "TXDELAY command: 500ms (50 × 10ms)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 01 32 C0"
}
},
"binary": "3D 00 C0 01 32 C0",
"notes": "Type=0x01 (TXDELAY), data=0x32 (50 decimal = 500ms)."
},
{
"id": "kiss-std-002",
"description": "Persistence command: P=63",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 02 3F C0"
}
},
"binary": "3D 00 C0 02 3F C0",
"notes": "Type=0x02 (Persistence), data=0x3F (63)."
},
{
"id": "kiss-std-003",
"description": "SlotTime command: 100ms (10 × 10ms)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 03 0A C0"
}
},
"binary": "3D 00 C0 03 0A C0",
"notes": "Type=0x03 (SlotTime), data=0x0A (10 = 100ms)."
},
{
"id": "kiss-std-004",
"description": "FullDuplex command: half duplex (0)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "C0 05 00 C0"
}
},
"binary": "3D 00 C0 05 00 C0",
"notes": "Type=0x05 (FullDuplex), data=0x00 (half duplex)."
}
]
}
@@ -0,0 +1,122 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "ACK Payload - Basic",
"spec_ref": "versions/v0.1.0/04-payload-ack.md",
"protocol_version": 0,
"description": "Basic ACK payload encoding/decoding tests with various CRC values.",
"vectors": [
{
"id": "ack-001",
"description": "ACK with CRC 0xDEADBEEF",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "DEADBEEF"
}
},
"binary": "0D 00 EFBEADDE",
"notes": "CRC 0xDEADBEEF stored as little-endian: EF BE AD DE."
},
{
"id": "ack-002",
"description": "ACK with CRC 0x00000001",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 00 01000000"
},
{
"id": "ack-003",
"description": "ACK with CRC 0x12345678",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "12345678"
}
},
"binary": "0D 00 78563412"
},
{
"id": "ack-004",
"description": "ACK via direct routing with path",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 2,
"hashes": [
"AA",
"BB"
]
},
"payload": {
"ack_crc": "CAFEBABE"
}
},
"binary": "0E 02 AABB BEBAFECA",
"notes": "Direct routed ACK. CRC 0xCAFEBABE as LE = BEBAFECA."
},
{
"id": "ack-005",
"description": "ACK with 2-byte hash path",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "direct"
},
"path": {
"hash_size": 2,
"hash_count": 1,
"hashes": [
"AABB"
]
},
"payload": {
"ack_crc": "00000000"
}
},
"binary": "0E 41 AABB 00000000",
"notes": "path_len=0x41: hash_size=2, count=1."
}
]
}
@@ -0,0 +1,115 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "ACK Payload - Boundary Values",
"spec_ref": "versions/v0.1.0/04-payload-ack.md",
"protocol_version": 0,
"description": "ACK CRC boundary value tests including all-zeros, all-ones, and single-bit patterns.",
"vectors": [
{
"id": "ack-bnd-001",
"description": "ACK CRC all zeros",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000000"
}
},
"binary": "0D 00 00000000"
},
{
"id": "ack-bnd-002",
"description": "ACK CRC all ones",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "FFFFFFFF"
}
},
"binary": "0D 00 FFFFFFFF"
},
{
"id": "ack-bnd-003",
"description": "ACK CRC 0x80000000 (high bit set)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "80000000"
}
},
"binary": "0D 00 00000080"
},
{
"id": "ack-bnd-004",
"description": "ACK CRC 0x00000080 (byte boundary)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000080"
}
},
"binary": "0D 00 80000000"
},
{
"id": "ack-bnd-005",
"description": "ACK CRC 0x01000000",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "01000000"
}
},
"binary": "0D 00 00000001",
"notes": "Tests that 0x01000000 LE = 00 00 00 01, not confused with 0x00000001."
}
]
}
@@ -0,0 +1,128 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement App Data - Combined Optional Fields",
"spec_ref": "versions/v0.1.0/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests app_data with multiple optional fields present simultaneously. Fields appear in order: flags, location (lat+lon), feat1, feat2, name.",
"vectors": [
{
"id": "adc-001",
"description": "Chat node with location + feat1 + name",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 177,
"latitude": 37774900,
"longitude": -122419400,
"feat1": 256,
"name": "Node1"
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB B1 34664002 3807B4F8 0001 4E6F646531",
"notes": "flags=0xB1=10110001: type=Chat(1), has_location(bit4), has_feat1(bit5), has_name(bit7). No feat2 (bit6=0). feat1=256=0x0100 LE=0001. Name 'Node1' = 4E6F646531."
},
{
"id": "adc-002",
"description": "Repeater with all optional fields",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 242,
"latitude": 0,
"longitude": 0,
"feat1": 1,
"feat2": 2,
"name": "RPT"
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB F2 00000000 00000000 0100 0200 525054",
"notes": "flags=0xF2=11110010: type=Repeater(2), all optional fields present. feat1=1 LE=0100, feat2=2 LE=0200, name='RPT'=525054."
},
{
"id": "adc-003",
"description": "Node with name only (no location or features)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 129,
"name": "Hi"
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 81 4869",
"notes": "flags=0x81=10000001: type=Chat(1), has_name(bit7) only. name='Hi'=4869."
},
{
"id": "adc-004",
"description": "Node with feat2 only (no location, feat1, or name)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 65,
"feat2": 65535
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 41 FFFF",
"notes": "flags=0x41=01000001: type=Chat(1), has_feat2(bit6). feat2=65535=0xFFFF."
}
]
}
@@ -0,0 +1,96 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement App Data - Location",
"spec_ref": "versions/v0.1.0/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests app_data with location fields (latitude and longitude as int32_le × 1,000,000).",
"vectors": [
{
"id": "adl-001",
"description": "Chat node with location: San Francisco (37.7749, -122.4194)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 17,
"latitude": 37774900,
"longitude": -122419400
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 11 34664002 3807B4F8",
"notes": "flags=0x11: type=Chat(1), has_location(bit 4). Lat 37774900=0x02406634 LE=34664002. Lon -122419400=0xF8B40738 LE=3807B4F8."
},
{
"id": "adl-002",
"description": "Repeater with location: equator/prime meridian (0, 0)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 18,
"latitude": 0,
"longitude": 0
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 12 00000000 00000000",
"notes": "flags=0x12: type=Repeater(2), has_location. Both coords zero."
},
{
"id": "adl-003",
"description": "Sensor with negative latitude: south pole (-90, 0)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 20,
"latitude": -90000000,
"longitude": 0
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 14 80B5A2FA 00000000",
"notes": "flags=0x14: type=Sensor(4), has_location. Lat -90000000=0xFAA2B580 LE=80B5A2FA. Lon 0."
}
]
}
@@ -0,0 +1,140 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement App Data - Node Types",
"spec_ref": "versions/v0.1.0/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests all defined node types in the app_data flags field (bits 0-3). Uses a common pub_key, zero timestamp, and zero signature for structural testing.",
"vectors": [
{
"id": "adt-001",
"description": "App data type: None (0)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 0
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 00",
"notes": "flags=0x00: type=None(0), no optional fields."
},
{
"id": "adt-002",
"description": "App data type: Chat (1)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 1
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 01"
},
{
"id": "adt-003",
"description": "App data type: Repeater (2)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 2
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 02"
},
{
"id": "adt-004",
"description": "App data type: Room (3)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 3
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 03"
},
{
"id": "adt-005",
"description": "App data type: Sensor (4)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 0,
"signature": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"app_data": {
"flags": 4
}
}
},
"binary": "11 00 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 00000000 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 04"
}
]
}
@@ -0,0 +1,88 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Advertisement Payload - Basic Structure",
"spec_ref": "versions/v0.1.0/05-payload-advert.md",
"protocol_version": 0,
"description": "Tests the basic structure of advertisement payloads: pub_key(32) + timestamp(4) + signature(64) + optional app_data. Uses synthetic signature bytes since signature verification requires real Ed25519 operations (see signature-verify.json for verifiable vectors).",
"vectors": [
{
"id": "adv-001",
"description": "Minimal advertisement: pub_key + timestamp + signature, no app_data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20",
"timestamp": 1700000000,
"signature": "A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0"
}
},
"binary": "11 00 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 00F15365 A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0",
"notes": "Header: 0x11 = ADVERT+FLOOD. Timestamp 1700000000 = 0x6553F100 LE = 00F15365. Payload: 32+4+64 = 100 bytes."
},
{
"id": "adv-002",
"description": "Advertisement with 1-byte app_data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"pub_key": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20",
"timestamp": 1700000000,
"signature": "A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0",
"app_data": {
"flags": 1
}
}
},
"binary": "11 00 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 00F15365 A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0 01",
"notes": "flags=0x01: type=Chat(1), no optional fields. Payload: 101 bytes."
},
{
"id": "adv-003",
"description": "Advertisement with path (3 hops, direct routing)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 3,
"hashes": [
"AA",
"BB",
"CC"
]
},
"payload": {
"pub_key": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20",
"timestamp": 0,
"signature": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"binary": "11 03 AABBCC 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 00000000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"notes": "Timestamp=0 and zero signature for structural testing."
}
]
}
@@ -0,0 +1,85 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Anonymous Request Payload",
"spec_ref": "versions/v0.1.0/07-payload-anon-req.md",
"protocol_version": 0,
"description": "Tests anonymous request payload: dest_hash(1) + sender_pub_key(32) + cipher_mac(2) + ciphertext.",
"vectors": [
{
"id": "anon-001",
"description": "Anonymous request with known keys",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "anon_req",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"sender_pub_key": "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F",
"cipher_mac": "F4B4",
"ciphertext": "908A4FC137435774D0BB603F55A06069"
}
},
"binary": "1D 00 AB 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F F4B4 908A4FC137435774D0BB603F55A06069",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "416E6F6E526571"
},
"notes": "Header 0x1D = ANON_REQ(7)+FLOOD(1). Payload: dest_hash(1) + pub_key(32) + mac(2) + ct(16) = 51 bytes. Plaintext 'AnonReq' (7 bytes)."
},
{
"id": "anon-002",
"description": "Anonymous request with tampered MAC",
"type": "invalid",
"binary": "1D 00 AB 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F 0000 908A4FC137435774D0BB603F55A06069",
"expected_error": "mac_invalid",
"notes": "MAC zeroed. Verification must fail."
},
{
"id": "anon-003",
"description": "Anonymous request via DIRECT routing with 2-hop path",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "anon_req",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 2,
"hashes": ["AA", "BB"]
},
"payload": {
"dest_hash": "CC",
"sender_pub_key": "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F",
"cipher_mac": "F4B4",
"ciphertext": "908A4FC137435774D0BB603F55A06069"
}
},
"binary": "1E 02 AABB CC 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F F4B4 908A4FC137435774D0BB603F55A06069",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "416E6F6E526571"
},
"notes": "Header 0x1E = ANON_REQ(7)+DIRECT(2). Direct-routed anonymous request with 2 path hashes."
},
{
"id": "anon-004",
"description": "Anonymous request truncated before ciphertext",
"type": "invalid",
"binary": "1D 00 AB 404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F F4B4",
"expected_error": "too_short",
"notes": "Payload is only 35 bytes (dest_hash + pub_key + mac). Missing ciphertext. Minimum is 51 bytes."
}
]
}
@@ -0,0 +1,96 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Control Payload - Basic",
"spec_ref": "versions/v0.1.0/12-payload-control.md",
"protocol_version": 0,
"description": "Tests control payload encoding. Control packets carry raw bytes where the first byte's bit 7 indicates zero-hop-only restriction.",
"vectors": [
{
"id": "ctl-001",
"description": "Control packet with multi-hop allowed (bit 7 clear)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "control",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "01 AABBCCDD"
}
},
"binary": "2D 00 01 AABBCCDD",
"notes": "control_byte=0x01: bit 7 clear, multi-hop allowed."
},
{
"id": "ctl-002",
"description": "Control packet with zero-hop only (bit 7 set)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "control",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "80 AABBCCDD"
}
},
"binary": "2E 00 80 AABBCCDD",
"notes": "control_byte=0x80: bit 7 set, zero-hop only. Direct route with path_count=0 = zero-hop."
},
{
"id": "ctl-003",
"description": "Minimal control packet (1 byte)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "control",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "00"
}
},
"binary": "2D 00 00"
},
{
"id": "ctl-004",
"description": "Control packet with zero-hop restriction and data via direct routing",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "control",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF 0102030405"
}
},
"binary": "2E 00 FF 0102030405",
"notes": "control_byte=0xFF: bit 7 set (zero-hop), all other bits set."
}
]
}
@@ -0,0 +1,129 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - Basic",
"spec_ref": "versions/v0.1.0/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Tests encrypted payload encoding (REQ/RESPONSE/TXT_MSG) with known cryptographic material. Uses shared_secret = 0x000102...1F, AES key = first 16 bytes, HMAC key = full 32 bytes.",
"vectors": [
{
"id": "enc-001",
"description": "Encrypted TXT_MSG with 5-byte plaintext 'Hello'",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "09 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "48656C6C6F"
},
"notes": "plaintext 'Hello' = 48656C6C6F (5 bytes), zero-padded to 16 for AES-128-ECB. MAC is HMAC-SHA256(shared_secret, ciphertext) truncated to 2 bytes."
},
{
"id": "enc-002",
"description": "Encrypted REQUEST with 16-byte plaintext (exact AES block)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "request",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "631C",
"ciphertext": "0A940BB5416EF045F1C39458C653EA5A"
}
},
"binary": "01 00 ABCD 631C 0A940BB5416EF045F1C39458C653EA5A",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E0F"
},
"notes": "16-byte plaintext fills exactly one AES block. No padding needed."
},
{
"id": "enc-003",
"description": "Encrypted RESPONSE with 17-byte plaintext (spans 2 AES blocks)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "response",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "9822",
"ciphertext": "0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560"
}
},
"binary": "05 00 ABCD 9822 0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E0F10"
},
"notes": "17-byte plaintext: first 16 bytes in block 1, byte 17 zero-padded in block 2. 32 bytes ciphertext total."
},
{
"id": "enc-004",
"description": "Encrypted TXT_MSG with 1-byte plaintext",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "83FB",
"ciphertext": "2D4135E80761429386BFE3833C535AA8"
}
},
"binary": "09 00 ABCD 83FB 2D4135E80761429386BFE3833C535AA8",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "42"
},
"notes": "1-byte plaintext 0x42, zero-padded to 16 bytes."
}
]
}
@@ -0,0 +1,69 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - MAC Verification",
"spec_ref": "versions/v0.1.0/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Tests MAC verification: valid MACs must pass, tampered MACs or ciphertext must fail.",
"vectors": [
{
"id": "mac-001",
"description": "Valid MAC with known ciphertext",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "09 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "48656C6C6F"
}
},
{
"id": "mac-002",
"description": "Tampered MAC (first byte changed)",
"type": "invalid",
"binary": "09 00 ABCD 00B5 D9FD218D50A4409143A7243D6D913502",
"expected_error": "mac_invalid",
"notes": "MAC changed from EAB5 to 00B5. MACThenDecrypt must return 0."
},
{
"id": "mac-003",
"description": "Tampered MAC (second byte changed)",
"type": "invalid",
"binary": "09 00 ABCD EA00 D9FD218D50A4409143A7243D6D913502",
"expected_error": "mac_invalid",
"notes": "MAC changed from EAB5 to EA00."
},
{
"id": "mac-004",
"description": "Tampered ciphertext (first byte changed)",
"type": "invalid",
"binary": "09 00 ABCD EAB5 00FD218D50A4409143A7243D6D913502",
"expected_error": "mac_invalid",
"notes": "Ciphertext first byte changed from D9 to 00. HMAC over modified ciphertext won't match."
},
{
"id": "mac-005",
"description": "Swapped MAC bytes",
"type": "invalid",
"binary": "09 00 ABCD B5EA D9FD218D50A4409143A7243D6D913502",
"expected_error": "mac_invalid",
"notes": "MAC bytes reversed: B5EA instead of EAB5."
}
]
}
@@ -0,0 +1,99 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - AES Block Padding",
"spec_ref": "versions/v0.1.0/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Tests AES-128-ECB zero-padding behavior at block boundaries. Plaintexts of 1, 15, 16, 17, and 32 bytes exercise all padding edge cases.",
"vectors": [
{
"id": "pad-001",
"description": "1-byte plaintext: padded with 15 zero bytes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "83FB",
"ciphertext": "2D4135E80761429386BFE3833C535AA8"
}
},
"binary": "09 00 ABCD 83FB 2D4135E80761429386BFE3833C535AA8",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "42"
},
"notes": "1 byte padded to 16: 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00. 1 AES block output."
},
{
"id": "pad-002",
"description": "16-byte plaintext: no padding needed (exact block boundary)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "631C",
"ciphertext": "0A940BB5416EF045F1C39458C653EA5A"
}
},
"binary": "09 00 ABCD 631C 0A940BB5416EF045F1C39458C653EA5A",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E0F"
},
"notes": "Exactly 16 bytes: no padding. 1 AES block output."
},
{
"id": "pad-003",
"description": "17-byte plaintext: 1 byte in second block, padded with 15 zeros",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "9822",
"ciphertext": "0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560"
}
},
"binary": "09 00 ABCD 9822 0A940BB5416EF045F1C39458C653EA5A299F7C29A3E13AE7F64ECBA062FC7560",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "000102030405060708090A0B0C0D0E0F10"
},
"notes": "17 bytes spans 2 blocks. Block 2 = 10 00 00 ... 00. 32 bytes ciphertext."
}
]
}
@@ -0,0 +1,47 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - Roundtrip Verification",
"spec_ref": "versions/v0.1.0/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Roundtrip encrypt-then-MAC verification vectors. Each vector provides full key material enabling implementations to verify: (1) encryption produces the given ciphertext, (2) MAC computation produces the given MAC, (3) MAC verification succeeds, (4) decryption recovers the plaintext.",
"vectors": [
{
"id": "rt-enc-001",
"description": "Roundtrip: encrypt 'Hello', verify MAC, decrypt back",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "09 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"encryption_key": "000102030405060708090A0B0C0D0E0F",
"plaintext": "48656C6C6F"
},
"notes": "Verification steps: (1) AES-128-ECB encrypt(key=00..0F, pad('Hello')) -> D9FD...3502. (2) HMAC-SHA256(key=00..1F, ct)[:2] -> EAB5. (3) HMAC verify: recompute and compare. (4) AES-128-ECB decrypt -> 48656C6C6F + 11 zero bytes."
},
{
"id": "rt-enc-002",
"description": "Roundtrip with wrong MAC must fail verification",
"type": "invalid",
"binary": "09 00 ABCD FFFF D9FD218D50A4409143A7243D6D913502",
"expected_error": "mac_invalid",
"notes": "Same ciphertext as rt-enc-001 but MAC replaced with FFFF. MACThenDecrypt must return 0 (failure)."
}
]
}
@@ -0,0 +1,76 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Encrypted Payload - Extra Edge Cases",
"spec_ref": "versions/v0.1.0/06-payload-encrypted.md",
"protocol_version": 0,
"description": "Additional encrypted payload tests: non-FLOOD routes, hash_size>1 paths, 32-byte plaintext, and payload-level invalid cases.",
"vectors": [
{
"id": "enc-extra-001",
"description": "Encrypted TXT_MSG via DIRECT routing with 2-byte hash path",
"type": "encode_decode",
"structured": {
"header": { "version": 0, "payload_type": "txt_msg", "route_type": "direct" },
"path": { "hash_size": 2, "hash_count": 1, "hashes": ["AABB"] },
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "0A 41 AABB ABCD EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "48656C6C6F"
},
"notes": "Header 0x0A = TXT_MSG(2)+DIRECT(2). path_len=0x41 (hash_size=2, count=1). Tests encrypted payload with non-FLOOD routing and multi-byte hash."
},
{
"id": "enc-extra-002",
"description": "Encrypted TXT_MSG via TRANSPORT_FLOOD with transport codes",
"type": "encode_decode",
"structured": {
"header": { "version": 0, "payload_type": "txt_msg", "route_type": "transport_flood" },
"transport_codes": [1000, 0],
"path": { "hash_size": 1, "hash_count": 0, "hashes": [] },
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "08 E803 0000 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "48656C6C6F"
},
"notes": "Header 0x08 = TXT_MSG(2)+TRANSPORT_FLOOD(0). Tests encrypted payload with transport codes."
},
{
"id": "enc-extra-003",
"description": "ACK payload too short (3 bytes instead of 4)",
"type": "invalid",
"binary": "0D 00 EFBEAD",
"expected_error": "incomplete_payload",
"notes": "ACK requires exactly 4 bytes. Only 3 provided."
},
{
"id": "enc-extra-004",
"description": "Payload exceeds MAX_PACKET_PAYLOAD (185 bytes)",
"type": "invalid",
"binary": "3D 00 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8",
"expected_error": "payload_too_large",
"notes": "Payload is 185 bytes (0x00..0xB8), exceeding MAX_PACKET_PAYLOAD of 184. readFrom MUST reject."
},
{
"id": "enc-extra-005",
"description": "Header 0xFF sentinel on wire (must reject)",
"type": "invalid",
"binary": "FF 00 DEADBEEF",
"expected_error": "sentinel_header",
"notes": "Header 0xFF is used internally as do-not-retransmit marker. MUST NOT appear on the wire."
}
]
}
@@ -0,0 +1,38 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Group Data Payload",
"spec_ref": "versions/v0.1.0/08-payload-group.md",
"protocol_version": 0,
"description": "Tests group datagram payload. Same wire format as GRP_TXT but payload type = 0x06.",
"vectors": [
{
"id": "grp-data-001",
"description": "Group datagram with known channel secret",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "grp_data",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"channel_hash": "72",
"cipher_mac": "D184",
"ciphertext": "948C819389BA4ED7B1194A0FF8E62073"
}
},
"binary": "19 00 72 D184 948C819389BA4ED7B1194A0FF8E62073",
"crypto_context": {
"shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F",
"encryption_key": "202122232425262728292A2B2C2D2E2F",
"plaintext": "47726F75704D736721"
},
"notes": "Header 0x19 = GRP_DATA(6)+FLOOD(1). Same ciphertext as GRP_TXT since same key and plaintext."
}
]
}
@@ -0,0 +1,46 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Group Text Payload",
"spec_ref": "versions/v0.1.0/08-payload-group.md",
"protocol_version": 0,
"description": "Tests group text message payload: channel_hash(1) + cipher_mac(2) + ciphertext.",
"vectors": [
{
"id": "grp-txt-001",
"description": "Group text message with known channel secret",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "grp_txt",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"channel_hash": "72",
"cipher_mac": "D184",
"ciphertext": "948C819389BA4ED7B1194A0FF8E62073"
}
},
"binary": "15 00 72 D184 948C819389BA4ED7B1194A0FF8E62073",
"crypto_context": {
"shared_secret": "202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F",
"encryption_key": "202122232425262728292A2B2C2D2E2F",
"plaintext": "47726F75704D736721"
},
"notes": "Channel hash = SHA256(channel_secret)[0] = 0x72. Plaintext 'GroupMsg!' (9 bytes). AES key = first 16 bytes of channel secret."
},
{
"id": "grp-txt-002",
"description": "Group text with tampered MAC must fail",
"type": "invalid",
"binary": "15 00 72 0000 948C819389BA4ED7B1194A0FF8E62073",
"expected_error": "mac_invalid",
"notes": "Same ciphertext as grp-txt-001 but MAC zeroed. Verification must fail."
}
]
}
@@ -0,0 +1,108 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Multipart Payload - Basic",
"spec_ref": "versions/v0.1.0/11-payload-multipart.md",
"protocol_version": 0,
"description": "Tests multipart payload encoding: first byte = (remaining << 4) | sub_type. Currently only multipart ACK (sub_type=3) is defined.",
"vectors": [
{
"id": "mp-001",
"description": "Multipart ACK with remaining=1, CRC 0xDEADBEEF",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "multipart",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"remaining": 1,
"sub_type": 3,
"sub_payload": "EFBEADDE"
}
},
"binary": "29 00 13 EFBEADDE",
"notes": "First byte: (1<<4)|3 = 0x13. Sub-payload: ACK CRC 0xDEADBEEF as LE."
},
{
"id": "mp-002",
"description": "Multipart ACK with remaining=0 (last in sequence)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "multipart",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"remaining": 0,
"sub_type": 3,
"sub_payload": "01000000"
}
},
"binary": "29 00 03 01000000",
"notes": "First byte: (0<<4)|3 = 0x03."
},
{
"id": "mp-003",
"description": "Multipart ACK with remaining=15 (maximum)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "multipart",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"remaining": 15,
"sub_type": 3,
"sub_payload": "FFFFFFFF"
}
},
"binary": "29 00 F3 FFFFFFFF",
"notes": "First byte: (15<<4)|3 = 0xF3."
},
{
"id": "mp-004",
"description": "Multipart ACK via direct routing",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "multipart",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 2,
"hashes": [
"AA",
"BB"
]
},
"payload": {
"remaining": 2,
"sub_type": 3,
"sub_payload": "78563412"
}
},
"binary": "2A 02 AABB 23 78563412",
"notes": "Header 0x2A = MULTIPART(10)+DIRECT(2). First byte: (2<<4)|3 = 0x23."
}
]
}
@@ -0,0 +1,122 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path Return Payload - Basic",
"spec_ref": "versions/v0.1.0/09-payload-path.md",
"protocol_version": 0,
"description": "Tests path return payload outer structure: dest_hash(1) + src_hash(1) + encrypt-then-MAC. The encrypted inner format contains path_len + path + extra_type + extra_data.",
"vectors": [
{
"id": "path-001",
"description": "Path return with encrypted inner data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "path",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "21 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "48656C6C6F"
},
"notes": "Header 0x21 = PATH(8)+FLOOD(1). Same ciphertext as enc-001 since same key/plaintext. The decrypted inner format would be parsed as: path_len=0x48, hash_size=2, hash_count=8 etc. - this is a structural test only."
},
{
"id": "path-002",
"description": "Path return via DIRECT routing with 1-hop path",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "path",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 1,
"hashes": ["AB"]
},
"payload": {
"dest_hash": "CD",
"src_hash": "EF",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "22 01 AB CDEF EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "48656C6C6F"
},
"notes": "Header 0x22 = PATH(8)+DIRECT(2). Direct-routed path return with 1 hop."
},
{
"id": "path-003",
"description": "Path return via TRANSPORT_FLOOD with transport codes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "path",
"route_type": "transport_flood"
},
"transport_codes": [1, 2],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "EAB5",
"ciphertext": "D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "20 0100 0200 00 ABCD EAB5 D9FD218D50A4409143A7243D6D913502",
"crypto_context": {
"shared_secret": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
"plaintext": "48656C6C6F"
},
"notes": "Header 0x20 = PATH(8)+TRANSPORT_FLOOD(0). Transport codes [1, 2] as LE uint16."
},
{
"id": "path-004",
"description": "Path return with 2-block ciphertext (32 bytes plaintext)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "path",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "1234",
"ciphertext": "D9FD218D50A4409143A7243D6D913502D9FD218D50A4409143A7243D6D913502"
}
},
"binary": "21 00 ABCD 1234 D9FD218D50A4409143A7243D6D913502D9FD218D50A4409143A7243D6D913502",
"notes": "Path return with 2 AES blocks of ciphertext (32 bytes). Tests multi-block encrypted path data."
}
]
}
@@ -0,0 +1,97 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Raw Custom Payload - Basic",
"spec_ref": "versions/v0.1.0/13-payload-raw-custom.md",
"protocol_version": 0,
"description": "Tests raw custom payload encoding. RAW_CUSTOM carries application-defined raw bytes.",
"vectors": [
{
"id": "raw-001",
"description": "Minimal raw custom payload (1 byte)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "42"
}
},
"binary": "3E 00 42",
"notes": "Header 0x3E = RAW_CUSTOM(15)+DIRECT(2). Single byte payload."
},
{
"id": "raw-002",
"description": "Raw custom with binary data",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 1,
"hashes": [
"AA"
]
},
"payload": {
"data": "DEADBEEFCAFEBABE"
}
},
"binary": "3E 01 AA DEADBEEFCAFEBABE",
"notes": "Direct route with 1-hop path."
},
{
"id": "raw-003",
"description": "Raw custom with all-zero payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "0000000000000000"
}
},
"binary": "3E 00 0000000000000000"
},
{
"id": "raw-004",
"description": "Raw custom with all-FF payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FFFFFFFFFFFFFFFF"
}
},
"binary": "3E 00 FFFFFFFFFFFFFFFF"
}
]
}
@@ -0,0 +1,127 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Trace Payload - Basic",
"spec_ref": "versions/v0.1.0/10-payload-trace.md",
"protocol_version": 0,
"description": "Tests the basic 9-byte TRACE payload structure: tag(4) + auth_code(4) + flags(1).",
"vectors": [
{
"id": "trc-001",
"description": "Basic trace with tag=1, auth_code=2, flags=0",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "trace",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"tag": 1,
"auth_code": 2,
"flags": 0
}
},
"binary": "25 00 01000000 02000000 00",
"notes": "tag=1 LE=01000000, auth_code=2 LE=02000000, flags=0."
},
{
"id": "trc-002",
"description": "Trace with large tag and auth_code values",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "trace",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"tag": 4294967295,
"auth_code": 305419896,
"flags": 0
}
},
"binary": "25 00 FFFFFFFF 78563412 00",
"notes": "tag=0xFFFFFFFF, auth_code=0x12345678 LE=78563412."
},
{
"id": "trc-003",
"description": "Trace with flags indicating 1-byte path hash size (flags & 0x03 = 0)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "trace",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"tag": 100,
"auth_code": 200,
"flags": 0
}
},
"binary": "25 00 64000000 C8000000 00",
"notes": "flags=0: path_hash_size = 1 << (0 & 3) = 1 byte."
},
{
"id": "trc-004",
"description": "Trace with flags indicating 2-byte path hash size (flags & 0x03 = 1)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "trace",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"tag": 100,
"auth_code": 200,
"flags": 1
}
},
"binary": "25 00 64000000 C8000000 01",
"notes": "flags=1: path_hash_size = 1 << (1 & 3) = 2 bytes."
},
{
"id": "trc-005",
"description": "Trace sent via direct routing with path hashes appended to payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "trace",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "01000000 02000000 00 AABBCC"
}
},
"binary": "26 00 01000000 02000000 00 AABBCC",
"notes": "Header 0x26 = TRACE(9)+DIRECT(2). path_len=0 (path field used for SNR). Payload: 9-byte header + 3 path hash bytes. Using raw payload since the appended hashes are semantically part of the trace."
}
]
}
@@ -0,0 +1,459 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/meshcore-spec/corpus/schema/test-vector.schema.json",
"title": "MeshCore Test Vector File",
"description": "Schema for MeshCore protocol test vector files. Each file contains a named collection of test vectors for a specific aspect of the protocol.",
"type": "object",
"required": ["name", "protocol_version", "vectors"],
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"description": "JSON Schema reference"
},
"name": {
"type": "string",
"description": "Human-readable name for this test vector collection"
},
"spec_ref": {
"type": "string",
"description": "Path to the relevant spec section (e.g., 'src/01-wire-format.md')"
},
"protocol_version": {
"type": "integer",
"minimum": 0,
"maximum": 3,
"description": "MeshCore protocol version (0-3, encoded in header bits 6-7)"
},
"description": {
"type": "string",
"description": "Optional longer description of what this file tests"
},
"vectors": {
"type": "array",
"description": "Array of test vectors",
"items": {
"$ref": "#/$defs/test_vector"
}
}
},
"$defs": {
"hex_string": {
"type": "string",
"pattern": "^[0-9A-F\\s]*$",
"description": "Hex-encoded binary data. MUST be uppercase. Whitespace allowed and stripped on parse."
},
"header": {
"type": "object",
"required": ["version", "payload_type", "route_type"],
"additionalProperties": false,
"properties": {
"version": {
"type": "integer",
"minimum": 0,
"maximum": 3,
"description": "Protocol version (header bits 6-7)"
},
"payload_type": {
"enum": [
"request",
"response",
"txt_msg",
"ack",
"advert",
"grp_txt",
"grp_data",
"anon_req",
"path",
"trace",
"multipart",
"control",
"raw_custom"
],
"description": "Payload type (header bits 2-5). Values 0x0C-0x0E are reserved."
},
"route_type": {
"enum": [
"transport_flood",
"flood",
"direct",
"transport_direct"
],
"description": "Route type (header bits 0-1). 0=transport_flood, 1=flood, 2=direct, 3=transport_direct"
}
}
},
"path_info": {
"type": "object",
"required": ["hash_size", "hash_count", "hashes"],
"additionalProperties": false,
"properties": {
"hash_size": {
"type": "integer",
"minimum": 1,
"maximum": 3,
"description": "Size of each hash in bytes (1, 2, or 3). Encoded as (hash_size - 1) in bits 6-7 of path_len byte."
},
"hash_count": {
"type": "integer",
"minimum": 0,
"maximum": 63,
"description": "Number of hashes in path (bits 0-5 of path_len byte)"
},
"hashes": {
"type": "array",
"items": {
"$ref": "#/$defs/hex_string"
},
"description": "Array of hex-encoded path hashes. Length MUST equal hash_count. Each hash MUST be hash_size bytes."
}
}
},
"transport_codes": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 65535
},
"minItems": 2,
"maxItems": 2,
"description": "Two 16-bit transport codes [code1, code2], encoded as little-endian uint16"
},
"payload_ack": {
"type": "object",
"required": ["ack_crc"],
"additionalProperties": false,
"properties": {
"ack_crc": {
"$ref": "#/$defs/hex_string",
"description": "4-byte ACK CRC as hex (big-endian representation of the value, wire encoding is little-endian)"
}
}
},
"payload_advert": {
"type": "object",
"required": ["pub_key", "timestamp", "signature"],
"additionalProperties": false,
"properties": {
"pub_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key"
},
"timestamp": {
"type": "integer",
"minimum": 0,
"description": "4-byte Unix timestamp"
},
"signature": {
"$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 signature over (pub_key + timestamp_le + app_data)"
},
"app_data": {
"$ref": "#/$defs/advert_app_data",
"description": "Optional advertisement application data"
}
}
},
"advert_app_data": {
"type": "object",
"required": ["flags"],
"additionalProperties": false,
"properties": {
"flags": {
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "8-bit flags: bits 0-3 = type (0=None,1=Chat,2=Repeater,3=Room,4=Sensor), bit 4 = has_location, bit 5 = has_feat1, bit 6 = has_feat2, bit 7 = has_name"
},
"latitude": {
"type": "integer",
"description": "Latitude * 1,000,000 as int32 (present when flags bit 4 set)"
},
"longitude": {
"type": "integer",
"description": "Longitude * 1,000,000 as int32 (present when flags bit 4 set)"
},
"feat1": {
"type": "integer",
"minimum": 0,
"maximum": 65535,
"description": "Feature field 1 as uint16 (present when flags bit 5 set)"
},
"feat2": {
"type": "integer",
"minimum": 0,
"maximum": 65535,
"description": "Feature field 2 as uint16 (present when flags bit 6 set)"
},
"name": {
"type": "string",
"description": "Node name as UTF-8 string (present when flags bit 7 set)"
}
}
},
"payload_encrypted": {
"type": "object",
"required": ["dest_hash", "src_hash", "cipher_mac", "ciphertext"],
"additionalProperties": false,
"properties": {
"dest_hash": {
"$ref": "#/$defs/hex_string",
"description": "Destination hash (PATH_HASH_SIZE bytes, default 1)"
},
"src_hash": {
"$ref": "#/$defs/hex_string",
"description": "Source hash (PATH_HASH_SIZE bytes, default 1)"
},
"cipher_mac": {
"$ref": "#/$defs/hex_string",
"description": "2-byte HMAC-SHA256 truncated MAC"
},
"ciphertext": {
"$ref": "#/$defs/hex_string",
"description": "AES-128-ECB encrypted data (zero-padded to block boundary)"
}
}
},
"payload_anon_req": {
"type": "object",
"required": ["dest_hash", "sender_pub_key", "cipher_mac", "ciphertext"],
"additionalProperties": false,
"properties": {
"dest_hash": {
"$ref": "#/$defs/hex_string",
"description": "Destination hash (PATH_HASH_SIZE bytes)"
},
"sender_pub_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key of sender"
},
"cipher_mac": {
"$ref": "#/$defs/hex_string",
"description": "2-byte cipher MAC"
},
"ciphertext": {
"$ref": "#/$defs/hex_string",
"description": "Encrypted data"
}
}
},
"payload_group": {
"type": "object",
"required": ["channel_hash", "cipher_mac", "ciphertext"],
"additionalProperties": false,
"properties": {
"channel_hash": {
"$ref": "#/$defs/hex_string",
"description": "1-byte channel hash (first byte of SHA-256 of channel shared key)"
},
"cipher_mac": {
"$ref": "#/$defs/hex_string",
"description": "2-byte cipher MAC"
},
"ciphertext": {
"$ref": "#/$defs/hex_string",
"description": "Encrypted data"
}
}
},
"payload_trace": {
"type": "object",
"required": ["tag", "auth_code", "flags"],
"additionalProperties": false,
"properties": {
"tag": {
"type": "integer",
"minimum": 0,
"description": "4-byte random tag set by initiator"
},
"auth_code": {
"type": "integer",
"minimum": 0,
"description": "4-byte authentication code"
},
"flags": {
"type": "integer",
"minimum": 0,
"maximum": 255,
"description": "1-byte flags (bits 0-1 encode path hash size)"
}
}
},
"payload_multipart": {
"type": "object",
"required": ["remaining", "sub_type", "sub_payload"],
"additionalProperties": false,
"properties": {
"remaining": {
"type": "integer",
"minimum": 0,
"maximum": 15,
"description": "Remaining parts count (upper 4 bits of first byte)"
},
"sub_type": {
"type": "integer",
"minimum": 0,
"maximum": 15,
"description": "Sub-payload type (lower 4 bits of first byte)"
},
"sub_payload": {
"$ref": "#/$defs/hex_string",
"description": "Sub-payload data"
}
}
},
"payload_control": {
"type": "object",
"required": ["data"],
"additionalProperties": false,
"properties": {
"data": {
"$ref": "#/$defs/hex_string",
"description": "Raw control data bytes"
}
}
},
"payload_raw": {
"type": "object",
"required": ["data"],
"additionalProperties": false,
"properties": {
"data": {
"$ref": "#/$defs/hex_string",
"description": "Raw custom payload bytes"
}
}
},
"crypto_context": {
"type": "object",
"additionalProperties": false,
"properties": {
"sender_private_key": {
"$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 private key of sender"
},
"sender_public_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key of sender"
},
"recipient_private_key": {
"$ref": "#/$defs/hex_string",
"description": "64-byte Ed25519 private key of recipient"
},
"recipient_public_key": {
"$ref": "#/$defs/hex_string",
"description": "32-byte Ed25519 public key of recipient"
},
"shared_secret": {
"$ref": "#/$defs/hex_string",
"description": "32-byte ECDH shared secret"
},
"encryption_key": {
"$ref": "#/$defs/hex_string",
"description": "16-byte AES-128 key (first 16 bytes of shared secret)"
},
"plaintext": {
"$ref": "#/$defs/hex_string",
"description": "Plaintext before encryption"
}
}
},
"structured_packet": {
"type": "object",
"required": ["header", "path"],
"additionalProperties": false,
"properties": {
"header": {
"$ref": "#/$defs/header"
},
"transport_codes": {
"$ref": "#/$defs/transport_codes"
},
"path": {
"$ref": "#/$defs/path_info"
},
"payload": {
"description": "Payload structure — the schema depends on the payload_type in header",
"anyOf": [
{ "$ref": "#/$defs/payload_ack" },
{ "$ref": "#/$defs/payload_advert" },
{ "$ref": "#/$defs/payload_encrypted" },
{ "$ref": "#/$defs/payload_anon_req" },
{ "$ref": "#/$defs/payload_group" },
{ "$ref": "#/$defs/payload_trace" },
{ "$ref": "#/$defs/payload_multipart" },
{ "$ref": "#/$defs/payload_control" },
{ "$ref": "#/$defs/payload_raw" }
]
}
}
},
"test_vector": {
"type": "object",
"required": ["id", "description", "type"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9_-]*$",
"description": "Unique identifier within this file (e.g., 'ack-001')"
},
"description": {
"type": "string",
"description": "Human-readable description of what this vector tests"
},
"type": {
"enum": ["encode_decode", "decode_only", "invalid"],
"description": "Test mode: encode_decode (bidirectional), decode_only (binary->structured), invalid (must reject)"
},
"structured": {
"$ref": "#/$defs/structured_packet",
"description": "Structured packet representation (required for encode_decode and decode_only)"
},
"binary": {
"$ref": "#/$defs/hex_string",
"description": "Wire-format binary encoding as hex (required for all types)"
},
"crypto_context": {
"$ref": "#/$defs/crypto_context",
"description": "Cryptographic key material for encrypted payload vectors"
},
"expected_error": {
"type": "string",
"description": "Expected error category for invalid vectors"
},
"notes": {
"type": "string",
"description": "Optional notes explaining the test vector"
}
},
"allOf": [
{
"if": {
"properties": { "type": { "const": "encode_decode" } }
},
"then": {
"required": ["structured", "binary"]
}
},
{
"if": {
"properties": { "type": { "const": "decode_only" } }
},
"then": {
"required": ["structured", "binary"]
}
},
{
"if": {
"properties": { "type": { "const": "invalid" } }
},
"then": {
"required": ["binary"]
}
}
]
}
}
}
@@ -0,0 +1,75 @@
{
"$schema": "../../../schema/test-vector.schema.json",
"name": "Framing - Decode Only",
"spec_ref": "versions/v0.1.0/01-wire-format.md",
"protocol_version": 0,
"description": "Tests one-way decode cases where the binary is valid to decode but may not be the canonical encoding a conformant encoder would produce.",
"vectors": [
{
"id": "dec-001",
"description": "Decode-only: ACK FLOOD with trailing bytes beyond the 4-byte CRC payload",
"type": "decode_only",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "DEADBEEF"
}
},
"binary": "0D 00 DEADBEEF 00",
"notes": "Binary has 1 trailing byte after the 4-byte ACK CRC. Decoders MUST accept this (payload_len includes the extra byte). Encoders would not produce trailing bytes."
},
{
"id": "dec-002",
"description": "Decode-only: raw_custom FLOOD with path hash_count=0 but hash_size=2",
"type": "decode_only",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 2,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "AA"
}
},
"binary": "3D 40 AA",
"notes": "Path_len byte 0x40 encodes hash_size=2 (bits 6-7 = 01) with hash_count=0. Decoders must handle non-default hash_size even when hash_count is zero. Encoders typically default hash_size=1 when hash_count=0."
},
{
"id": "dec-003",
"description": "Decode-only: raw_custom FLOOD with path hash_size=3 and hash_count=0",
"type": "decode_only",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 3,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "BB"
}
},
"binary": "3D 80 BB",
"notes": "Path_len byte 0x80 encodes hash_size=3 (bits 6-7 = 10) with hash_count=0. Same as dec-002 but with hash_size=3."
}
]
}
@@ -0,0 +1,33 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Framing - Empty Payload Rejection",
"spec_ref": "versions/v0.1.0/01-wire-format.md",
"protocol_version": 0,
"description": "Verifies that packets with zero-length payload are rejected. The reference implementation (Packet::readFrom) checks 'if (i >= len) return false' after reading header+transport+path_len+path, requiring at least 1 byte of payload.",
"vectors": [
{
"id": "ep-001",
"description": "FLOOD packet with no payload bytes (just header + path_len)",
"type": "invalid",
"binary": "0D 00",
"expected_error": "empty_payload",
"notes": "2 bytes total: header 0x0D + path_len 0x00. No payload bytes remain. readFrom returns false because i(2) >= len(2)."
},
{
"id": "ep-002",
"description": "TRANSPORT_FLOOD with no payload (header + transport + path_len only)",
"type": "invalid",
"binary": "0C 0000 0000 00",
"expected_error": "empty_payload",
"notes": "6 bytes: header(1) + transport(4) + path_len(1). i=6, len=6, so i>=len = true."
},
{
"id": "ep-003",
"description": "FLOOD packet with path but no payload",
"type": "invalid",
"binary": "0D 02 AABB",
"expected_error": "empty_payload",
"notes": "4 bytes: header(1) + path_len(1) + path(2). i=4, len=4."
}
]
}
@@ -0,0 +1,139 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Framing - Maximum Size Packets",
"spec_ref": "versions/v0.1.0/01-wire-format.md",
"protocol_version": 0,
"description": "Tests packets at or near the maximum wire size of 255 bytes (MAX_TRANS_UNIT). Verifies size calculation: total = 1 (header) + transport(0|4) + 1 (path_len) + path_bytes + payload_len.",
"vectors": [
{
"id": "max-001",
"description": "Maximum FLOOD packet: 255 bytes with empty path and 253-byte payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFC"
}
},
"binary": "3D 00 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFC",
"notes": "Total: 1 + 0 + 1 + 0 + 253 = 255 bytes. However payload exceeds MAX_PACKET_PAYLOAD (184). This is a wire-level max size test only; payload-level validation would reject this."
},
{
"id": "max-002",
"description": "Maximum valid FLOOD packet respecting MAX_PACKET_PAYLOAD: 1 + 1 + 0 + 184 = 186 bytes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7"
}
},
"binary": "3D 00 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7",
"notes": "Total: 1 + 1 + 184 = 186 bytes. Payload is exactly MAX_PACKET_PAYLOAD (184 bytes = 0x00..0xB7)."
},
{
"id": "max-003",
"description": "Large FLOOD packet with maximum path (63 bytes) and payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 63,
"hashes": [
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"0A",
"0B",
"0C",
"0D",
"0E",
"0F",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"1A",
"1B",
"1C",
"1D",
"1E",
"1F",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"2A",
"2B",
"2C",
"2D",
"2E",
"2F",
"30",
"31",
"32",
"33",
"34",
"35",
"36",
"37",
"38",
"39",
"3A",
"3B",
"3C",
"3D",
"3E",
"3F"
]
},
"payload": {
"data": "DEADBEEF"
}
},
"binary": "3D 3F 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F DEADBEEF",
"notes": "Total: 1 + 1 + 63 + 4 = 69 bytes. Max path with small payload."
}
]
}
@@ -0,0 +1,127 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Framing - Minimal Valid Packets",
"spec_ref": "versions/v0.1.0/01-wire-format.md",
"protocol_version": 0,
"description": "Tests the smallest valid packets for each route type. A valid packet must have at least header(1) + path_len(1) + payload(1+) bytes, plus 4 bytes for transport codes if applicable.",
"vectors": [
{
"id": "min-001",
"description": "Minimal FLOOD packet: 3 bytes (header + path_len + 1-byte payload)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "3D 00 FF",
"notes": "Smallest valid FLOOD packet: 3 bytes total."
},
{
"id": "min-002",
"description": "Minimal DIRECT packet: 3 bytes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "3E 00 FF",
"notes": "Smallest valid DIRECT packet: 3 bytes total."
},
{
"id": "min-003",
"description": "Minimal TRANSPORT_FLOOD packet: 7 bytes (header + 4 transport + path_len + 1-byte payload)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "transport_flood"
},
"transport_codes": [
0,
0
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "3C 0000 0000 00 FF",
"notes": "Smallest valid TRANSPORT_FLOOD packet: 7 bytes total."
},
{
"id": "min-004",
"description": "Minimal TRANSPORT_DIRECT packet: 7 bytes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "transport_direct"
},
"transport_codes": [
0,
0
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "3F 0000 0000 00 FF",
"notes": "Smallest valid TRANSPORT_DIRECT packet: 7 bytes total."
},
{
"id": "min-005",
"description": "Minimal ACK via FLOOD: 6 bytes (header + path_len + 4-byte CRC)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000000"
}
},
"binary": "0D 00 00000000",
"notes": "ACK with zero CRC. 6 bytes total."
}
]
}
@@ -0,0 +1,165 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Combined Field Encoding",
"spec_ref": "versions/v0.1.0/02-header.md",
"protocol_version": 0,
"description": "Combinatorial tests of header byte encoding, exercising various combinations of version, payload type, and route type to verify correct bit packing.",
"vectors": [
{
"id": "hdr-000",
"description": "All-zero header: version=0, request=0, transport_flood=0",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "request",
"route_type": "transport_flood"
},
"transport_codes": [
0,
0
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "00",
"src_hash": "00",
"cipher_mac": "0000",
"ciphertext": "00000000000000000000000000000000"
}
},
"binary": "00 0000 0000 00 0000 0000 00000000000000000000000000000000",
"notes": "Header: (0<<6)|(0<<2)|0 = 0x00."
},
{
"id": "hdr-001",
"description": "Version=0, ADVERT, DIRECT route",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 2,
"hashes": [
"AA",
"BB"
]
},
"payload": {
"data": "FF"
}
},
"binary": "12 02 AABB FF",
"notes": "Header: (0<<6)|(4<<2)|2 = 0x12. path_len=0x02 (hash_size=1, count=2). Path: AA BB."
},
{
"id": "hdr-002",
"description": "Version=1, CONTROL, FLOOD route",
"type": "encode_decode",
"structured": {
"header": {
"version": 1,
"payload_type": "control",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "6D 00 FF",
"notes": "Header: (1<<6)|(11<<2)|1 = 0x40+0x2C+0x01 = 0x6D."
},
{
"id": "hdr-003",
"description": "Version=0, RAW_CUSTOM, TRANSPORT_DIRECT with non-trivial transport codes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "transport_direct"
},
"transport_codes": [
256,
512
],
"path": {
"hash_size": 1,
"hash_count": 1,
"hashes": [
"AA"
]
},
"payload": {
"data": "CAFEBABE"
}
},
"binary": "3F 0001 0002 01 AA CAFEBABE",
"notes": "Header: (0<<6)|(15<<2)|3 = 0x3F. Transport codes: 256=0x0100 LE=0001, 512=0x0200 LE=0002."
},
{
"id": "hdr-004",
"description": "Version=0, TRACE, FLOOD with path",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "trace",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 3,
"hashes": [
"AA",
"BB",
"CC"
]
},
"payload": {
"data": "0102030405060708 09"
}
},
"binary": "25 03 AABBCC 010203040506070809",
"notes": "Header: (0<<6)|(9<<2)|1 = 0x25. path_len=0x03 (1-byte hashes, 3 hops)."
},
{
"id": "hdr-005",
"description": "Maximum header value that isn't 0xFF sentinel: version=2, RAW_CUSTOM, TRANSPORT_DIRECT",
"type": "encode_decode",
"structured": {
"header": {
"version": 2,
"payload_type": "raw_custom",
"route_type": "transport_direct"
},
"transport_codes": [
0,
0
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "AA"
}
},
"binary": "BF 0000 0000 00 AA",
"notes": "Header: (2<<6)|(15<<2)|3 = 0x80+0x3C+0x03 = 0xBF."
}
]
}
@@ -0,0 +1,308 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Payload Types",
"spec_ref": "versions/v0.1.0/02-header.md",
"protocol_version": 0,
"description": "Tests encoding of all defined payload types in the header byte. Uses FLOOD routing and minimal payloads to isolate header encoding.",
"vectors": [
{
"id": "pt-000",
"description": "Payload type REQUEST (0x00) - header byte encodes type in bits 2-5",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "request",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "1122",
"ciphertext": "DEADBEEFCAFEBABE0102030405060708"
}
},
"binary": "01 00 ABCD 1122 DEADBEEFCAFEBABE0102030405060708",
"notes": "Header: (0<<6)|(0<<2)|1 = 0x01. Payload: dest_hash(1) + src_hash(1) + MAC(2) + 16 bytes ciphertext."
},
{
"id": "pt-001",
"description": "Payload type RESPONSE (0x01)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "response",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "1122",
"ciphertext": "DEADBEEFCAFEBABE0102030405060708"
}
},
"binary": "05 00 ABCD 1122 DEADBEEFCAFEBABE0102030405060708",
"notes": "Header: (0<<6)|(1<<2)|1 = 0x05."
},
{
"id": "pt-002",
"description": "Payload type TXT_MSG (0x02)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "txt_msg",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"dest_hash": "AB",
"src_hash": "CD",
"cipher_mac": "1122",
"ciphertext": "DEADBEEFCAFEBABE0102030405060708"
}
},
"binary": "09 00 ABCD 1122 DEADBEEFCAFEBABE0102030405060708",
"notes": "Header: (0<<6)|(2<<2)|1 = 0x09."
},
{
"id": "pt-003",
"description": "Payload type ACK (0x03)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "DEADBEEF"
}
},
"binary": "0D 00 EFBEADDE",
"notes": "Header: (0<<6)|(3<<2)|1 = 0x0D. CRC 0xDEADBEEF as LE = EFBEADDE."
},
{
"id": "pt-004",
"description": "Payload type ADVERT (0x04) - minimal advert with 1 byte payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "advert",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "11 00 FF",
"notes": "Header: (0<<6)|(4<<2)|1 = 0x11. Using raw payload to test header encoding in isolation."
},
{
"id": "pt-005",
"description": "Payload type GRP_TXT (0x05)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "grp_txt",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"channel_hash": "AB",
"cipher_mac": "1122",
"ciphertext": "DEADBEEFCAFEBABE0102030405060708"
}
},
"binary": "15 00 AB 1122 DEADBEEFCAFEBABE0102030405060708",
"notes": "Header: (0<<6)|(5<<2)|1 = 0x15."
},
{
"id": "pt-006",
"description": "Payload type GRP_DATA (0x06)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "grp_data",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"channel_hash": "AB",
"cipher_mac": "1122",
"ciphertext": "DEADBEEFCAFEBABE0102030405060708"
}
},
"binary": "19 00 AB 1122 DEADBEEFCAFEBABE0102030405060708",
"notes": "Header: (0<<6)|(6<<2)|1 = 0x19."
},
{
"id": "pt-007",
"description": "Payload type ANON_REQ (0x07)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "anon_req",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "1D 00 FF",
"notes": "Header: (0<<6)|(7<<2)|1 = 0x1D. Minimal payload to test header."
},
{
"id": "pt-008",
"description": "Payload type PATH (0x08)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "path",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "21 00 FF",
"notes": "Header: (0<<6)|(8<<2)|1 = 0x21."
},
{
"id": "pt-009",
"description": "Payload type TRACE (0x09)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "trace",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "25 00 FF",
"notes": "Header: (0<<6)|(9<<2)|1 = 0x25."
},
{
"id": "pt-010",
"description": "Payload type MULTIPART (0x0A)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "multipart",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "29 00 FF",
"notes": "Header: (0<<6)|(10<<2)|1 = 0x29."
},
{
"id": "pt-011",
"description": "Payload type CONTROL (0x0B)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "control",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "FF"
}
},
"binary": "2D 00 FF",
"notes": "Header: (0<<6)|(11<<2)|1 = 0x2D."
},
{
"id": "pt-012",
"description": "Payload type RAW_CUSTOM (0x0F)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "raw_custom",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"data": "CAFEBABE"
}
},
"binary": "3D 00 CAFEBABE",
"notes": "Header: (0<<6)|(15<<2)|1 = 0x3D."
}
]
}
@@ -0,0 +1,131 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Route Types",
"spec_ref": "versions/v0.1.0/02-header.md",
"protocol_version": 0,
"description": "Tests encoding and decoding of all 4 route types in the header byte, using ACK (payload_type=3) as a simple payload.",
"vectors": [
{
"id": "rt-000",
"description": "Route type TRANSPORT_FLOOD (0x00) with ACK payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_flood"
},
"transport_codes": [
0,
0
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "01020304"
}
},
"binary": "0C 0000 0000 00 04030201",
"notes": "Header: version=0, payload_type=3 (ACK), route_type=0 (TRANSPORT_FLOOD). 0x0C = (0<<6)|(3<<2)|0. Transport codes both zero. path_len=0x00 (hash_size=1, count=0). ACK CRC 0x01020304 as LE = 04030201."
},
{
"id": "rt-001",
"description": "Route type FLOOD (0x01) with ACK payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "01020304"
}
},
"binary": "0D 00 04030201",
"notes": "Header: 0x0D = (0<<6)|(3<<2)|1. No transport codes. path_len=0x00. ACK CRC as LE."
},
{
"id": "rt-002",
"description": "Route type DIRECT (0x02) with ACK payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "01020304"
}
},
"binary": "0E 00 04030201",
"notes": "Header: 0x0E = (0<<6)|(3<<2)|2. No transport codes."
},
{
"id": "rt-003",
"description": "Route type TRANSPORT_DIRECT (0x03) with ACK payload",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_direct"
},
"transport_codes": [
4660,
22136
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "01020304"
}
},
"binary": "0F 3412 7856 00 04030201",
"notes": "Header: 0x0F = (0<<6)|(3<<2)|3. Transport codes: 4660=0x1234 as LE=3412, 22136=0x5678 as LE=7856."
},
{
"id": "rt-004",
"description": "TRANSPORT_FLOOD with non-zero transport codes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_flood"
},
"transport_codes": [
65535,
1
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "FFFFFFFF"
}
},
"binary": "0C FFFF 0100 00 FFFFFFFF",
"notes": "Transport code 1: 0xFFFF as LE = FFFF. Transport code 2: 1 as LE = 0100. CRC 0xFFFFFFFF as LE."
}
]
}
@@ -0,0 +1,97 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Header - Protocol Versions",
"spec_ref": "versions/v0.1.0/02-header.md",
"protocol_version": 0,
"description": "Tests encoding of all 4 protocol version values in header bits 6-7. Uses ACK (type=3) with FLOOD routing as a simple baseline.",
"vectors": [
{
"id": "ver-000",
"description": "Version 0 (V1, current) - bits 6-7 = 00",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 00 01000000",
"notes": "Header: (0<<6)|(3<<2)|1 = 0x0D. Version 0 = V1."
},
{
"id": "ver-001",
"description": "Version 1 (V2, reserved) - bits 6-7 = 01",
"type": "encode_decode",
"structured": {
"header": {
"version": 1,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "4D 00 01000000",
"notes": "Header: (1<<6)|(3<<2)|1 = 0x4D. Version 1 = V2 (reserved)."
},
{
"id": "ver-002",
"description": "Version 2 (V3, reserved) - bits 6-7 = 10",
"type": "encode_decode",
"structured": {
"header": {
"version": 2,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "8D 00 01000000",
"notes": "Header: (2<<6)|(3<<2)|1 = 0x8D. Version 2 = V3 (reserved)."
},
{
"id": "ver-003",
"description": "Version 3 (V4, reserved) - bits 6-7 = 11",
"type": "encode_decode",
"structured": {
"header": {
"version": 3,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "CD 00 01000000",
"notes": "Header: (3<<6)|(3<<2)|1 = 0xCD. Version 3 = V4 (reserved)."
}
]
}
@@ -0,0 +1,57 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Invalid - Bad Path Length Encoding",
"spec_ref": "versions/v0.1.0/03-path.md",
"protocol_version": 0,
"description": "Tests invalid path_len values including the reserved hash_size=4 and path overflow conditions.",
"vectors": [
{
"id": "bpl-001",
"description": "Reserved hash size code 3 (hash_size would be 4): path_len=0xC0",
"type": "invalid",
"binary": "0D C0 01000000",
"expected_error": "reserved_hash_size",
"notes": "path_len=0xC0: bits 6-7=11 (hash size code 3). isValidPathLen returns false because hash_size==4."
},
{
"id": "bpl-002",
"description": "Reserved hash size code 3 with non-zero count: path_len=0xC1",
"type": "invalid",
"binary": "0D C1 AA 01000000",
"expected_error": "reserved_hash_size",
"notes": "path_len=0xC1: hash size code 3 (reserved), count=1. Invalid regardless of count."
},
{
"id": "bpl-003",
"description": "Reserved hash size code 3, max count: path_len=0xFF",
"type": "invalid",
"binary": "0D FF 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F 01000000",
"expected_error": "reserved_hash_size",
"notes": "path_len=0xFF: hash size code 3 (reserved), count=63. Even if hash_size=4 were valid, 63*4=252 > MAX_PATH_SIZE."
},
{
"id": "bpl-004",
"description": "Path overflow: hash_size=2, count=33 (66 bytes > MAX_PATH_SIZE)",
"type": "invalid",
"binary": "0D 61 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233343536373839 3A3B3C3D3E3F4041 01000000",
"expected_error": "path_overflow",
"notes": "path_len=0x61: hash_size=2, count=33. 33*2=66 > MAX_PATH_SIZE (64). isValidPathLen returns false."
},
{
"id": "bpl-005",
"description": "Path overflow: hash_size=3, count=22 (66 bytes > MAX_PATH_SIZE)",
"type": "invalid",
"binary": "0D 96 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F4041 01000000",
"expected_error": "path_overflow",
"notes": "path_len=0x96: hash_size=3, count=22. 22*3=66 > MAX_PATH_SIZE (64)."
},
{
"id": "bpl-006",
"description": "Path overflow edge case: hash_size=1, count=63 is valid (63<=64), but with hash_size=2 and count=33 it overflows",
"type": "invalid",
"binary": "0D 61 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334353637383940 4142 DEADBEEF",
"expected_error": "path_overflow",
"notes": "path_len=0x61: size code 1 (hash_size=2), count 33. 33*2=66 > 64."
}
]
}
@@ -0,0 +1,49 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Invalid - Too Short Packets",
"spec_ref": "versions/v0.1.0/01-wire-format.md",
"protocol_version": 0,
"description": "Tests packets that are shorter than the minimum required size for their route type.",
"vectors": [
{
"id": "short-001",
"description": "1-byte packet (FLOOD): only header, missing path_len and payload",
"type": "invalid",
"binary": "0D",
"expected_error": "too_short",
"notes": "Minimum FLOOD packet is 3 bytes. This is 1."
},
{
"id": "short-002",
"description": "2-byte packet (FLOOD): header + path_len, missing payload",
"type": "invalid",
"binary": "0D 00",
"expected_error": "empty_payload",
"notes": "Header + path_len present but no payload. i=2, len=2, so i>=len."
},
{
"id": "short-003",
"description": "5-byte TRANSPORT_FLOOD: header + transport codes, missing path_len",
"type": "invalid",
"binary": "0C 00000000",
"expected_error": "too_short",
"notes": "TRANSPORT_FLOOD needs at least 7 bytes (1+4+1+1). This has 5."
},
{
"id": "short-004",
"description": "6-byte TRANSPORT_FLOOD: header + transport + path_len, missing payload",
"type": "invalid",
"binary": "0C 00000000 00",
"expected_error": "empty_payload",
"notes": "All framing present but zero payload."
},
{
"id": "short-005",
"description": "FLOOD with path consuming all remaining bytes",
"type": "invalid",
"binary": "0D 03 AABBCC",
"expected_error": "empty_payload",
"notes": "header(1) + path_len(1) + path(3) = 5 bytes = total. i=5, len=5."
}
]
}
@@ -0,0 +1,65 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Invalid - Truncated Packets",
"spec_ref": "versions/v0.1.0/01-wire-format.md",
"protocol_version": 0,
"description": "Tests packets that are truncated at various points, verifying that decoders reject them gracefully.",
"vectors": [
{
"id": "trunc-001",
"description": "Empty packet (0 bytes)",
"type": "invalid",
"binary": "",
"expected_error": "too_short",
"notes": "Zero-length input. Cannot read header."
},
{
"id": "trunc-002",
"description": "Header only (1 byte, FLOOD route)",
"type": "invalid",
"binary": "0D",
"expected_error": "too_short",
"notes": "Only header byte. Cannot read path_len."
},
{
"id": "trunc-003",
"description": "Header only (1 byte, TRANSPORT_FLOOD route) - transport codes truncated",
"type": "invalid",
"binary": "0C",
"expected_error": "too_short",
"notes": "Route type 0x00 requires 4 transport code bytes, but only header is present."
},
{
"id": "trunc-004",
"description": "TRANSPORT_FLOOD with partial transport codes (2 of 4 bytes)",
"type": "invalid",
"binary": "0C 1234",
"expected_error": "too_short",
"notes": "Only 2 of 4 transport code bytes present."
},
{
"id": "trunc-005",
"description": "TRANSPORT_FLOOD with transport codes but no path_len",
"type": "invalid",
"binary": "0C 0000 0000",
"expected_error": "too_short",
"notes": "5 bytes: header + 4 transport code bytes. Missing path_len."
},
{
"id": "trunc-006",
"description": "FLOOD with path_len indicating 3 hops but only 1 path byte",
"type": "invalid",
"binary": "0D 03 AA FF",
"expected_error": "truncated_path",
"notes": "path_len=0x03 means 3 bytes of path expected but only 1 byte follows before the alleged payload. The decoder would read 3 path bytes (AA FF ??) and run out."
},
{
"id": "trunc-007",
"description": "FLOOD with 2-byte hash path_len=0x42 (4 path bytes needed) but only 2 available",
"type": "invalid",
"binary": "0D 42 AABB FF",
"expected_error": "truncated_path",
"notes": "path_len=0x42: hash_size=2, count=2, need 4 path bytes. Only 2 bytes before payload marker."
}
]
}
@@ -0,0 +1,156 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path - Boundary Conditions",
"spec_ref": "versions/v0.1.0/03-path.md",
"protocol_version": 0,
"description": "Tests boundary conditions for path encoding including maximum valid values and values just below overflow thresholds.",
"vectors": [
{
"id": "pb-001",
"description": "hash_size=2, count=31 (62 bytes, 2 below max)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 2,
"hash_count": 31,
"hashes": [
"0001",
"0203",
"0405",
"0607",
"0809",
"0A0B",
"0C0D",
"0E0F",
"1011",
"1213",
"1415",
"1617",
"1819",
"1A1B",
"1C1D",
"1E1F",
"2021",
"2223",
"2425",
"2627",
"2829",
"2A2B",
"2C2D",
"2E2F",
"3031",
"3233",
"3435",
"3637",
"3839",
"3A3B",
"3C3D"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 5F 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D 01000000",
"notes": "path_len=0x5F: hash_size=2, count=31. Path: 62 bytes. Just below max."
},
{
"id": "pb-002",
"description": "hash_size=3, count=20 (60 bytes, 3 below max for this hash_size)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 3,
"hash_count": 20,
"hashes": [
"000102",
"030405",
"060708",
"090A0B",
"0C0D0E",
"0F1011",
"121314",
"151617",
"18191A",
"1B1C1D",
"1E1F20",
"212223",
"242526",
"272829",
"2A2B2C",
"2D2E2F",
"303132",
"333435",
"363738",
"393A3B"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 94 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233343536373839 3A3B 01000000",
"notes": "path_len=0x94: hash_size=3, count=20. Path: 60 bytes."
},
{
"id": "pb-003",
"description": "Single-byte path: hash_size=1, count=1",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 1,
"hashes": [
"00"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 01 00 01000000",
"notes": "Minimal non-empty path. Hash value 0x00 is valid."
},
{
"id": "pb-004",
"description": "Path with all-FF hashes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 3,
"hashes": [
"FF",
"FF",
"FF"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 03 FFFFFF 01000000",
"notes": "All hashes are 0xFF. Valid encoding."
}
]
}
@@ -0,0 +1,260 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path - Hash Counts",
"spec_ref": "versions/v0.1.0/03-path.md",
"protocol_version": 0,
"description": "Tests various hash count values including zero, small, and maximum values for each hash size.",
"vectors": [
{
"id": "hc-000",
"description": "Zero hops (hash_count=0, hash_size=1) - empty path",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 00 01000000",
"notes": "path_len=0x00. Zero-length path."
},
{
"id": "hc-001",
"description": "Zero hops with hash_size=2 - still empty path",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 2,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 40 01000000",
"notes": "path_len=0x40: hash_size=2, count=0. Path is still 0 bytes even though hash_size>1."
},
{
"id": "hc-002",
"description": "Zero hops with hash_size=3",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 3,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 80 01000000",
"notes": "path_len=0x80: hash_size=3, count=0. Path is 0 bytes."
},
{
"id": "hc-010",
"description": "Maximum count for hash_size=1: 63 hops, 63 bytes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 63,
"hashes": [
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"0A",
"0B",
"0C",
"0D",
"0E",
"0F",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"1A",
"1B",
"1C",
"1D",
"1E",
"1F",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"2A",
"2B",
"2C",
"2D",
"2E",
"2F",
"30",
"31",
"32",
"33",
"34",
"35",
"36",
"37",
"38",
"39",
"3A",
"3B",
"3C",
"3D",
"3E",
"3F"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 3F 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F 01000000",
"notes": "path_len=0x3F: hash_size=1, count=63. Path: 63 bytes. Maximum valid path for 1-byte hashes."
},
{
"id": "hc-020",
"description": "Maximum count for hash_size=2: 32 hops, 64 bytes (exactly MAX_PATH_SIZE)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 2,
"hash_count": 32,
"hashes": [
"0001",
"0203",
"0405",
"0607",
"0809",
"0A0B",
"0C0D",
"0E0F",
"1011",
"1213",
"1415",
"1617",
"1819",
"1A1B",
"1C1D",
"1E1F",
"2021",
"2223",
"2425",
"2627",
"2829",
"2A2B",
"2C2D",
"2E2F",
"3031",
"3233",
"3435",
"3637",
"3839",
"3A3B",
"3C3D",
"3E3F"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 60 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F 01000000",
"notes": "path_len=0x60: bits 6-7=01 (hash_size=2), bits 0-5=32. Path: 64 bytes = MAX_PATH_SIZE. Maximum valid for 2-byte hashes."
},
{
"id": "hc-030",
"description": "Maximum count for hash_size=3: 21 hops, 63 bytes",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 3,
"hash_count": 21,
"hashes": [
"000102",
"030405",
"060708",
"090A0B",
"0C0D0E",
"0F1011",
"121314",
"151617",
"18191A",
"1B1C1D",
"1E1F20",
"212223",
"242526",
"272829",
"2A2B2C",
"2D2E2F",
"303132",
"333435",
"363738",
"393A3B",
"3C3D3E"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 95 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E 01000000",
"notes": "path_len=0x95: bits 6-7=10 (hash_size=3), bits 0-5=21. Path: 63 bytes. Maximum valid for 3-byte hashes (22*3=66 would overflow)."
}
]
}
@@ -0,0 +1,188 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Path - Hash Sizes",
"spec_ref": "versions/v0.1.0/03-path.md",
"protocol_version": 0,
"description": "Tests all valid hash sizes (1, 2, 3 bytes) in the path length encoding. These vectors are critical for catching implementations that treat path_len as a raw byte count.",
"vectors": [
{
"id": "hs-001",
"description": "Hash size 1 (code 0), 1 hop",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 1,
"hashes": [
"AA"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 01 AA 01000000",
"notes": "path_len=0x01: bits 6-7=00 (size code 0, hash_size=1), bits 0-5=1 (count=1). Path: 1 byte."
},
{
"id": "hs-002",
"description": "Hash size 1 (code 0), 5 hops",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 5,
"hashes": [
"AA",
"BB",
"CC",
"DD",
"EE"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 05 AABBCCDDEE 01000000",
"notes": "path_len=0x05: hash_size=1, count=5. Path: 5 bytes."
},
{
"id": "hs-010",
"description": "Hash size 2 (code 1), 1 hop",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 2,
"hash_count": 1,
"hashes": [
"AABB"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 41 AABB 01000000",
"notes": "path_len=0x41: bits 6-7=01 (size code 1, hash_size=2), bits 0-5=1 (count=1). Path: 2 bytes. An implementation treating 0x41=65 as raw byte count would fail."
},
{
"id": "hs-011",
"description": "Hash size 2 (code 1), 2 hops",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 2,
"hash_count": 2,
"hashes": [
"AABB",
"CCDD"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 42 AABBCCDD 01000000",
"notes": "path_len=0x42: hash_size=2, count=2. Path: 4 bytes. This is the example that catches the meshcore-rs bug (0x42=66 raw vs 4 actual bytes)."
},
{
"id": "hs-012",
"description": "Hash size 2 (code 1), 5 hops",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 2,
"hash_count": 5,
"hashes": [
"1122",
"3344",
"5566",
"7788",
"99AA"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 45 112233445566778899AA 01000000",
"notes": "path_len=0x45: hash_size=2, count=5. Path: 10 bytes."
},
{
"id": "hs-020",
"description": "Hash size 3 (code 2), 1 hop",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 3,
"hash_count": 1,
"hashes": [
"AABBCC"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 81 AABBCC 01000000",
"notes": "path_len=0x81: bits 6-7=10 (size code 2, hash_size=3), bits 0-5=1. Path: 3 bytes."
},
{
"id": "hs-021",
"description": "Hash size 3 (code 2), 3 hops",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 3,
"hash_count": 3,
"hashes": [
"AABBCC",
"DDEEFF",
"112233"
]
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 83 AABBCC DDEEFF 112233 01000000",
"notes": "path_len=0x83: hash_size=3, count=3. Path: 9 bytes."
}
]
}
@@ -0,0 +1,183 @@
{
"$schema": "../../schema/test-vector.schema.json",
"name": "Transport Codes - Basic",
"spec_ref": "versions/v0.1.0/01-wire-format.md",
"protocol_version": 0,
"description": "Tests transport code encoding with various values and route types that include/exclude transport codes.",
"vectors": [
{
"id": "tc-001",
"description": "Transport codes with TRANSPORT_FLOOD, both zero",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_flood"
},
"transport_codes": [
0,
0
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0C 0000 0000 00 01000000",
"notes": "Both transport codes zero."
},
{
"id": "tc-002",
"description": "Transport codes with TRANSPORT_DIRECT, typical values",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_direct"
},
"transport_codes": [
1000,
2000
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0F E803 D007 00 01000000",
"notes": "TC1: 1000=0x03E8 LE=E803. TC2: 2000=0x07D0 LE=D007."
},
{
"id": "tc-003",
"description": "Transport codes at maximum uint16 values",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_flood"
},
"transport_codes": [
65535,
65535
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0C FFFF FFFF 00 01000000",
"notes": "Both transport codes at maximum: 0xFFFF."
},
{
"id": "tc-004",
"description": "Transport codes: code1=1, code2=0",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_flood"
},
"transport_codes": [
1,
0
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0C 0100 0000 00 01000000",
"notes": "TC1=1 LE=0100, TC2=0."
},
{
"id": "tc-005",
"description": "Transport codes: code1=0x8000, code2=0x0001 (verify high bit handling)",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "transport_direct"
},
"transport_codes": [
32768,
1
],
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0F 0080 0100 00 01000000",
"notes": "TC1: 0x8000 LE=0080. TC2: 1 LE=0100."
},
{
"id": "tc-006",
"description": "FLOOD route (no transport codes) - verify they are absent from wire",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "flood"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0D 00 01000000",
"notes": "FLOOD route: no transport codes on wire. Total: 1 (header) + 1 (path_len) + 4 (ACK) = 6 bytes."
},
{
"id": "tc-007",
"description": "DIRECT route (no transport codes) - verify they are absent from wire",
"type": "encode_decode",
"structured": {
"header": {
"version": 0,
"payload_type": "ack",
"route_type": "direct"
},
"path": {
"hash_size": 1,
"hash_count": 0,
"hashes": []
},
"payload": {
"ack_crc": "00000001"
}
},
"binary": "0E 00 01000000",
"notes": "DIRECT route: no transport codes on wire."
}
]
}