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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/tsp-js/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ For history before this file, see `git log` on `packages/tsp-js`.

### Fixed

- `MAX_HOPS` is 64 (was 10). The specification sets no maximum, and 12-hop
routes packed by every other implementation were refused on decode. The same
bound applies when packing a route and when decoding a hop list or reply path.
- An XSCS/XCTL body that is not exactly one Bytes primitive is refused. It was
read as its first primitive, silently dropping the rest of the `-A##` stream,
and data after the stream was ignored. See
Expand Down
7 changes: 5 additions & 2 deletions packages/tsp-js/src/cesr/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,11 @@ export function decodeVariableData(
return stream.slice(range.begin, range.end);
}

/** Max hops accepted in a routed message's hop list (bounds a hostile count). */
export const MAX_HOPS = 10;
/** Max hops accepted in a routed message's hop list or reply path (bounds a
* hostile count). The spec sets no maximum, so this is a local choice that
* caps interoperability: 64 matches the other affinidi TSP implementations.
* It was 10, which refused 12-hop routes every other implementation opens. */
export const MAX_HOPS = 64;

/** Read the `YTSP` genus marker and its version count code. Advances `cur`.
*
Expand Down
11 changes: 11 additions & 0 deletions packages/tsp-js/tests/message.routed.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ test("packRouted rejects empty and over-long routes", async () => {
await assert.rejects(packRouted(enc.encode("x"), tooMany, alice.vid, hop1.vid, packKeys(alice, hop1)));
});

test("a route at MAX_HOPS packs and opens with its hops intact", async () => {
// 12 hops was refused when the limit was 10.
const alice = party("did:web:alice");
const hop1 = party("did:web:hop1");
const route = Array.from({ length: MAX_HOPS }, (_, i) => `did:web:h${i}`);
const packed = await packRouted(enc.encode("abc"), route, alice.vid, hop1.vid, packKeys(alice, hop1));
const opened = await unpack(packed.bytes, unpackKeys(hop1, alice));
assert.equal(opened.messageType, "routed");
assert.deepEqual(opened.hops, route);
});

test("routed multi-hop round-trip: alice → hop1 → hop2 → final (inner opaque)", async () => {
const alice = party("did:web:alice");
const hop1 = party("did:web:hop1");
Expand Down