Skip to content

feat(l2ps): SR-4 SDK delivery — CCI binding, channel envelope, encryp…#94

Open
Shitikyan wants to merge 4 commits into
mainfrom
feat/sr4-cci-channel-transcript
Open

feat(l2ps): SR-4 SDK delivery — CCI binding, channel envelope, encryp…#94
Shitikyan wants to merge 4 commits into
mainfrom
feat/sr4-cci-channel-transcript

Conversation

@Shitikyan

@Shitikyan Shitikyan commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

…ted transcript anchor

Closes the three 🟡 gaps in DACS-3 CORE §A.4 so negotiate-rfq and negotiate-sealed-envelope can run end-to-end on a real private channel.

The invariant from §0 — in-channel signer == on-chain party — is enforced substrate-side: every signature path here goes through the connected Demos Ed25519 key (NEVER the RSA L2PS subnet key). All signatures are domain-separated (dacs-binding:v1:, dacs-channelmsg:v1:, dacs-transcript:v1:) over RFC 8785 JCS canonical bytes per §B.7.

What is shipped:

  • WI-0 — identity/cci: ClaimReference + signWithPrimaryClaim / verifyPrimaryClaimSignature. Required because the brief assumes types that did not yet exist in SDK.

  • WI-1 — l2ps/binding: L2PSMembershipBinding + SR-2 anchor + resolveMember. Resolver enforces the dual check (signature + SP-owner matches claim address) so an impostor cannot poison a binding name.

  • WI-2 — l2ps/channel: ChannelMessage envelope (verbatim §8.3.3), monotonic per-channel sequence + InMemoryChannelIdRegistry (CH-6), ChannelSession orchestrator, transcript export + audit verifier.

  • WI-3 — l2ps/anchor: anchorEncryptedTranscript with the three §8.7 policies. Reuses L2PS subnet AES-GCM (new encryptBytes/decryptBytes primitives on the L2PS class, mirroring the per-call-nonce + AAD pattern from encryptTx). Anchored payload carries a public content hash so non-members can verify tamper-evidence without the key.

Also: add override to cause: unknown in BroadcastFailedError and TransportError — pre-existing TS4114 in main (noImplicitOverride) that was blocking the build hook.

New package exports: @kynesyslabs/demosdk/identity and @kynesyslabs/demosdk/identity/cci. The l2ps barrel now also exposes binding, channel, anchor namespaces.

Tests: 98 unit tests (jest) across the four work items — round-trips, tampering detection, replay/forking, policy gating, signing-key mismatches. Integration tests against a live chain are deferred until the dev environment is back up.

Linear: DEM-760 (epic), DEM-761/762/763/764 (WI-0..WI-3).
Brief: docs/l2ps-sr4-implementation-brief.md in the node repo.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Cross-Context Identity (CCI) system for managing claim references and Ed25519 primary claim signatures
    • Introduced L2PS membership bindings to anchor channel membership on-chain with cryptographic verification
    • Enabled encrypted transcript anchoring with tamper-evidence validation and decryption recovery
    • Launched channel messaging session framework with built-in transcript generation, signing, and comprehensive verification
  • Tests

    • Added extensive test coverage for identity, bindings, anchoring, and channel messaging functionality

…ted transcript anchor

Closes the three 🟡 gaps in DACS-3 CORE §A.4 so `negotiate-rfq` and
`negotiate-sealed-envelope` can run end-to-end on a real private channel.

The invariant from §0 — in-channel signer == on-chain party — is enforced
substrate-side: every signature path here goes through the connected
Demos Ed25519 key (NEVER the RSA L2PS subnet key). All signatures are
domain-separated (`dacs-binding:v1:`, `dacs-channelmsg:v1:`,
`dacs-transcript:v1:`) over RFC 8785 JCS canonical bytes per §B.7.

What is shipped:

- WI-0 — `identity/cci`: `ClaimReference` + `signWithPrimaryClaim` /
  `verifyPrimaryClaimSignature`. Required because the brief assumes
  types that did not yet exist in SDK.

- WI-1 — `l2ps/binding`: `L2PSMembershipBinding` + SR-2 anchor +
  `resolveMember`. Resolver enforces the dual check (signature + SP-owner
  matches claim address) so an impostor cannot poison a binding name.

- WI-2 — `l2ps/channel`: `ChannelMessage` envelope (verbatim §8.3.3),
  monotonic per-channel sequence + `InMemoryChannelIdRegistry` (CH-6),
  `ChannelSession` orchestrator, transcript export + audit verifier.

- WI-3 — `l2ps/anchor`: `anchorEncryptedTranscript` with the three §8.7
  policies. Reuses L2PS subnet AES-GCM (new `encryptBytes`/`decryptBytes`
  primitives on the L2PS class, mirroring the per-call-nonce + AAD
  pattern from `encryptTx`). Anchored payload carries a public content
  hash so non-members can verify tamper-evidence without the key.

Also: add `override` to `cause: unknown` in BroadcastFailedError and
TransportError — pre-existing TS4114 in main (noImplicitOverride) that
was blocking the build hook.

New package exports: `@kynesyslabs/demosdk/identity` and
`@kynesyslabs/demosdk/identity/cci`. The l2ps barrel now also exposes
`binding`, `channel`, `anchor` namespaces.

Tests: 98 unit tests (jest) across the four work items — round-trips,
tampering detection, replay/forking, policy gating, signing-key
mismatches. Integration tests against a live chain are deferred until
the dev environment is back up.

Linear: DEM-760 (epic), DEM-761/762/763/764 (WI-0..WI-3).
Brief: docs/l2ps-sr4-implementation-brief.md in the node repo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@SergeyG-Solicy, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 17 minutes and 1 second. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f01e197-f445-4638-ad3d-13ee60ce2d55

📥 Commits

Reviewing files that changed from the base of the PR and between e88627d and b4faa09.

📒 Files selected for processing (11)
  • src/identity/cci/claim.ts
  • src/identity/cci/signing.ts
  • src/l2ps/anchor/anchor.ts
  • src/l2ps/binding/binding.ts
  • src/l2ps/channel/envelope.ts
  • src/l2ps/channel/transcript.ts
  • src/l2ps/l2ps.ts
  • src/l2ps/utils/hex.ts
  • src/tests/l2ps/anchor.test.ts
  • src/tests/l2ps/binding.test.ts
  • src/tests/l2ps/channel.test.ts

Walkthrough

This PR introduces Cross-Context Identity (CCI) claim parsing and signing, L2PS encrypted-bytes support, membership binding with on-chain anchoring, channel messaging with session state tracking, encrypted transcript anchoring to storage programs, and comprehensive protocol test coverage across all workflows.

Changes

CCI and L2PS Secure Messaging Protocol

Layer / File(s) Summary
CCI identity types, claim parsing, and primary-claim signing
src/identity/cci/types.ts, src/identity/cci/claim.ts, src/identity/cci/signing.ts, src/identity/cci/index.ts, src/identity/index.ts, src/tests/identity/cci.test.ts
Defines CCI claim types (scheme:identifier format), implements parseClaimRef for parsing, demosClaimRefForAddress and demosAddressFromClaim for address normalization, and signWithPrimaryClaim/verifyPrimaryClaimSignature for Ed25519 signing via the Demos wallet. Tests verify parsing edge cases and end-to-end signing/verification.
L2PS encrypted-bytes operations and hex utilities
src/l2ps/l2ps.ts, src/l2ps/utils/hex.ts, src/tests/l2ps/anchor.test.ts
Adds L2PSEncryptedBytes type for byte-stream encryption payloads, encryptBytes/decryptBytes methods using AES-GCM with fresh per-call 12-byte nonce, and hex conversion utilities (bytesToHex, signatureToHex, signatureFromHex) for signature round-tripping. Tests validate round-trip correctness, nonce freshness, UID scoping, and authentication failures.
L2PS binding types and canonical signing
src/l2ps/binding/types.ts, src/l2ps/binding/canonical.ts
Defines L2PSMembershipBinding with versioning and hex-encoded signature, introduces BINDING_DOMAIN_PREFIX domain separation, implements bindingSigningBytes with SHA-256 canonical JSON hashing, and stripBindingSignature helper for signature removal.
Membership binding creation and verification
src/l2ps/binding/binding.ts, src/l2ps/binding/index.ts, src/tests/l2ps/binding.test.ts
Implements createMembershipBinding to construct and sign bindings with stored Demos claim keys, verifyMembershipBinding for pure cryptographic validation, tests covering binding correctness, field tampering detection, and signature validation across multiple scenarios.
Membership binding chain operations
src/l2ps/binding/binding.ts, src/tests/l2ps/binding.test.ts
Implements anchorMembershipBinding to deploy binding StorageProgram on-chain, resolveMember to recover bound claims from storage by deterministic naming with owner/signature validation, includes resolution regression tests for malformed candidate handling.
Channel message and transcript types
src/l2ps/channel/types.ts, src/l2ps/channel/canonical.ts
Defines ChannelMessage and ChannelTranscript with versioned signatures and corresponding unsigned variants, introduces domain-separated canonical hashing (envelopeHashHex, transcriptHashHex) and signing-byte derivation, provides signature stripping helpers.
Channel message signing, verification, and registry
src/l2ps/channel/envelope.ts, src/l2ps/channel/channelIdRegistry.ts, src/tests/l2ps/channel.test.ts
Implements signChannelMessage and verifyChannelMessage with sender identity and sequence validation, adds isSenderInMembers membership predicate, introduces ChannelIdRegistry interface and InMemoryChannelIdRegistry for CH-6 per-session uniqueness enforcement. Tests validate sign/verify round-trips, tampering detection, and registry reuse protection.
Channel session and transcript handling
src/l2ps/channel/session.ts, src/l2ps/channel/transcript.ts, src/l2ps/channel/index.ts, src/tests/l2ps/channel.test.ts
Implements ChannelSession for per-channel state with monotonic sequencing and registry integration, provides buildUnsignedTranscript/signTranscript/exportTranscript for transcript assembly, adds verifyTranscript for non-throwing end-to-end validation. Tests cover session lifecycle, send/receive validation, transcript export/verification, and concurrency safety.
Encrypted transcript anchoring
src/l2ps/anchor/types.ts, src/l2ps/anchor/anchor.ts, src/l2ps/anchor/index.ts, src/tests/l2ps/anchor.test.ts
Defines AttestationRef and AnchoredTranscriptPayload types, implements anchorEncryptedTranscript with policy gating (none/recommended/required), deployAnchorSP for StorageProgram transaction, decryptAnchoredTranscript with content-hash validation, verifyAnchorIntegrity for tamper-free checks. Tests validate policy gating, encryption/decryption round-trips, content-hash integrity, and signature verification.
Module exports and error type alignment
package.json, src/l2ps/index.ts, src/websdk/BroadcastFailedError.ts, src/websdk/TransportError.ts
Updates package.json subpath exports for ./identity and ./identity/cci, adds L2PS namespace exports for binding/channel/anchor, exports L2PSEncryptedBytes from L2PS module, and marks BroadcastFailedError and TransportError cause properties with override modifier.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant CCI as CCI.signWithPrimaryClaim
  participant Demos
  participant L2PS as L2PS.encryptBytes
  participant Anchor as anchorEncryptedTranscript
  participant Chain as StorageProgram
  
  App->>CCI: (claim, payload, demos)
  CCI->>Demos: crypto.sign("ed25519", payload)
  Demos->>CCI: signature
  
  App->>L2PS: (plaintext bytes)
  L2PS->>L2PS: generate 12-byte nonce
  L2PS->>L2PS: AES-GCM encrypt with nonce as AAD
  L2PS->>App: { ciphertext, tag, nonce, l2ps_uid }
  
  App->>Anchor: (policy, transcript, signer, l2ps, demos)
  Anchor->>L2PS: (canonicalized transcript)
  L2PS->>Anchor: encrypted bytes + contentHash
  Anchor->>CCI: (transcript bytes)
  CCI->>Anchor: signature
  Anchor->>Chain: deploy StorageProgram with payload
  Chain->>Anchor: storage address
  Anchor->>App: { anchor, contentHash }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • The main PR implements SR-4 work by adding CCI identity modules, L2PS binding/channel/anchor modules, encrypted-bytes support, and package exports described in the SR-4 ticket.
  • The main PR implements DACS-3 membership binding functionality (types, canonical signing, create/verify/anchor/resolve, StorageProgram anchoring) with corresponding test coverage.
  • The main PR implements SR-4 WI-3 encrypted transcript anchoring flow including anchorEncryptedTranscript, deployAnchorSP, content-hash handling, encryption/decryption, signing bytes, and verification helpers.
  • The main PR implements ChannelMessage envelope, monotonic per-channel sequencing, signing/verification, and transcript export matching SR-4 WI-2 requirements.

Possibly related PRs

  • kynesyslabs/sdks#90: Both PRs update BroadcastFailedError and TransportError by adding the override modifier to the cause property declaration.
  • kynesyslabs/sdks#87: Both PRs implement AES-GCM encryption with fresh per-call 12-byte nonce in L2PS encrypt/decrypt operations.
  • kynesyslabs/sdks#85: Both PRs modify BroadcastFailedError and TransportError error class declarations.

Suggested labels

Review effort 4/5

Suggested reviewers

  • tcsenpai
  • cwilvx

Poem

🐰 Identity claimed and channels sing,
Bindings anchored, transcripts ring,
With crypto's dance and nonce so bright,
Messages flow through L2PS light! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly addresses the main feature delivery: SR-4 SDK implementation covering CCI binding, channel envelope, and encrypted transcript anchoring across four work items (identity/cci, l2ps/binding, l2ps/channel, l2ps/anchor).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sr4-cci-channel-transcript

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown

Greptile Summary

This PR delivers the four SR-4 work items (WI-0 through WI-3) that close the DACS-3 CORE §A.4 gaps needed for negotiate-rfq and negotiate-sealed-envelope to run end-to-end on a real private channel. All three issues flagged in the previous review round have been resolved: normalizeDemosAddress is now guarded inside a try-catch in resolveMember, the sendOutgoing sequence slot is incremented synchronously before any await, and the hex helpers are consolidated in src/l2ps/utils/hex.ts.

  • WI-0 (identity/cci): ClaimReference type, signWithPrimaryClaim/verifyPrimaryClaimSignature using the connected Demos Ed25519 key; domain separation is the caller's responsibility per design.
  • WI-1 (l2ps/binding): L2PSMembershipBinding with two-stage resolution (signature + SP-owner == claim address), anchored via SR-2.
  • WI-2 (l2ps/channel): ChannelMessage envelope, monotonic per-channel sequence, InMemoryChannelIdRegistry (CH-6), ChannelSession orchestrator with full transcript assembly and audit verification via verifyTranscript.
  • WI-3 (l2ps/anchor): anchorEncryptedTranscript with the three §8.7 policies, new encryptBytes/decryptBytes primitives on L2PS mirroring the per-call-nonce + AAD pattern from encryptTx.

Confidence Score: 5/5

The PR is safe to merge. The cryptographic protocol paths (sign, verify, AES-GCM encrypt/decrypt) are internally consistent, all three previously flagged issues have been addressed, and the 98-unit test suite covers the key round-trips and tamper-detection cases.

No functional defects were found in the changed code. The sequence race condition, the malformed-address denial-of-service in the resolver, and the hex utility duplication are all resolved. The two remaining observations are a misleading JSDoc mode name and a minor code-reuse gap — neither affects runtime behavior.

The anchorProgramName JSDoc in src/l2ps/anchor/anchor.ts references mode: "owner" but the deployed SP uses mode: "public"; worth correcting before the comment misleads a future contributor.

Important Files Changed

Filename Overview
src/l2ps/anchor/anchor.ts New WI-3 anchor: encrypt-to-member-set, SR-2 deployment, tamper-evidence hash, and full transcript decryption+verification. JSDoc on anchorProgramName references wrong SP mode name ("owner" vs the actual "public").
src/l2ps/binding/binding.ts WI-1 binding: create/verify/anchor membership bindings with dual signature+owner check. Previously flagged normalizeDemosAddress TOCTOU is now inside a try-catch that skips bad candidates.
src/l2ps/channel/session.ts WI-2 session: monotonic sequence counter now incremented synchronously before the first await, closing the previously flagged concurrent-send race condition.
src/identity/cci/signing.ts CCI sign/verify using Cryptography.verify with UTF-8 decode path consistent with unifiedCrypto.sign. Private hexAddressToBytes duplicates signatureFromHex without validation; safe in current call sites but a reuse gap.
src/l2ps/channel/transcript.ts Transcript build, sign, export, and full audit verification. verifyTranscript validates per-message signatures, monotonic sequences, member membership, and channelId consistency in a single pass.
src/l2ps/l2ps.ts New encryptBytes/decryptBytes primitives mirror the per-call-nonce + nonce-as-AAD pattern from encryptTx, making them consistent with the existing defense against nonce-flip DoS.
src/l2ps/utils/hex.ts New shared hex helpers (bytesToHex, signatureToHex, signatureFromHex) used by binding, channel, and anchor modules; closes the previously flagged duplication across three files.
src/identity/cci/claim.ts ClaimReference parsing and normalization; normalizeDemosAddress validates hex length and charset, isDemosClaim is safely catch-guarded for arbitrary input.
src/l2ps/channel/envelope.ts Channel message sign and verify; signature covers JCS envelope hash with dacs-channelmsg:v1: domain prefix, sender-must-be-demos guard enforces the CCI invariant.
src/l2ps/binding/canonical.ts Binding canonical serialization; imports hex helpers from the shared utility, re-exports them for callers that previously imported directly from this module.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant ChannelSession
    participant envelope as signChannelMessage
    participant signWithPrimaryClaim
    participant Demos

    Caller->>ChannelSession: open()
    ChannelSession->>ChannelSession: register channelId (CH-6)

    Caller->>ChannelSession: sendOutgoing(type, body)
    ChannelSession->>ChannelSession: ++highestSeen (sync, reserves slot)
    ChannelSession->>envelope: signChannelMessage(unsigned)
    envelope->>signWithPrimaryClaim: "sign(dacs-channelmsg:v1: || sha256(JCS))"
    signWithPrimaryClaim->>Demos: crypto.sign(ed25519, payload)
    Demos-->>signWithPrimaryClaim: Ed25519 signature
    signWithPrimaryClaim-->>envelope: sigBytes
    envelope-->>ChannelSession: ChannelMessage
    ChannelSession->>ChannelSession: transcript.push(signed)

    Caller->>ChannelSession: receiveIncoming(msg)
    ChannelSession->>ChannelSession: channelId + member + seq checks
    ChannelSession->>envelope: verifyChannelMessage(msg)
    envelope->>signWithPrimaryClaim: verifyPrimaryClaimSignature
    signWithPrimaryClaim-->>envelope: true/false
    envelope-->>ChannelSession: ok

    Caller->>ChannelSession: exportTranscript
    ChannelSession-->>Caller: ChannelTranscript
    Caller->>L2PS: encryptBytes(JCS(unsignedTranscript))
    L2PS-->>Caller: L2PSEncryptedBytes (fresh nonce+AAD)
    Caller->>Demos: "signWithPrimaryClaim(dacs-transcript:v1: || hash)"
    Demos-->>Caller: TranscriptSignature
    Caller->>StorageProgram: deployAnchorSP (mode:public)
    StorageProgram-->>Caller: AttestationRef
Loading

Reviews (3): Last reviewed commit: "review: address CodeRabbit findings on P..." | Re-trigger Greptile

Comment thread src/l2ps/binding/binding.ts Outdated
Comment thread src/l2ps/channel/session.ts
Comment thread src/l2ps/binding/canonical.ts Outdated
Comment thread src/l2ps/anchor/anchor.ts Outdated
Shitikyan and others added 2 commits June 11, 2026 17:57
Two P1 correctness bugs + two P2 cleanups, all caught by the bot review.

P1 — `resolveMember` DoS via malformed SP owner
  src/l2ps/binding/binding.ts
  `normalizeDemosAddress(sp.owner)` sat outside the existing try-catch,
  so a single adversarially-crafted storage program registered under our
  deterministic name would throw and abort the entire resolution loop
  instead of being skipped. A squatter could DoS every consumer of
  `resolveMember`. Moved the owner-address comparison inside the same
  try-catch as the claim-address parse so any malformed candidate is
  skipped, the loop continues, and a legitimate later candidate still
  resolves.

P1 — `ChannelSession.sendOutgoing` sequence race
  src/l2ps/channel/session.ts
  `this.highestSeen` was read on construction and only written after the
  `await signChannelMessage` yield. Two unawaited concurrent calls would
  read the same value, sign two messages with identical sequence numbers,
  and silently violate the anti-replay invariant on the receiver side.
  Reserve the sequence slot synchronously via `++this.highestSeen` before
  any await, so concurrent calls get distinct, monotonic sequences.

P2 — duplicated hex helpers
  src/l2ps/utils/hex.ts (new)
  src/l2ps/binding/canonical.ts, src/l2ps/channel/canonical.ts,
  src/l2ps/anchor/anchor.ts
  `bytesToHex` / `signatureToHex` / `signatureFromHex` were copy-pasted
  identically in three modules. Extracted into a single shared
  `src/l2ps/utils/hex.ts`; the binding and channel canonical modules
  re-export the symbols so existing imports keep working unchanged.

P2 — `decryptAnchoredTranscript` end-to-end verification
  src/l2ps/anchor/anchor.ts
  Previously only the anchor-party transcript signature was verified;
  individual `ChannelMessage` signatures inside the transcript body were
  not, despite the JSDoc implying full verification. Pipe the assembled
  transcript through `verifyTranscript` before returning so every message
  signature, sequence monotonicity, sender ∈ members, and per-message
  channelId check is exercised. Doc comment now lists every condition
  that throws so callers don't have to re-verify.

Tests added:
- two regression tests for the resolveMember DoS (malformed candidate
  among valid ones, and all-malformed → null without throw)
- two regression tests for the sendOutgoing race (concurrent calls
  produce distinct + monotonic sequences, both for N=2 and N=5)

102/102 jest tests pass; `tsc --noEmit --skipLibCheck` clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The quality gate flipped to D Reliability because one new test sort
called `.sort()` without a compare function on a numeric array — for
sequence values up to 9 the alphabetical default coincidentally passed
the assertion, but the test would silently break the moment a sequence
reached 10. Pass `(a, b) => a - b` so the assertion is meaningful and
the reliability rating goes back to A.

While here, knock out the major code-smell cluster Sonar surfaced on the
SR-4 modules — all mechanical, no behavior change:

- Collapse `if (!x || !x.y || x.y.z !== ...)` patterns into optional-
  chain form (`x?.y?.z !== ...`) in anchor, binding, channel/envelope
  and channel/transcript verifiers — five sites Sonar flagged as MAJOR.
- Switch the two `parseInt(..., 16)` calls in `utils/hex.ts` and
  `identity/cci/signing.ts` to `Number.parseInt` so the static reference
  doesn't depend on the global.

102/102 jest tests still pass; `tsc --noEmit --skipLibCheck` clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (6)
src/identity/cci/signing.ts (1)

74-78: Clarify UTF-8 requirement for Ed25519 verification (and keep the decode step)

src/identity/cci/signing.ts (lines 74-78) decodes payload with TextDecoder because Cryptography.verify takes signed: string and verifies via forge.pki.ed25519.verify({ message: signed, encoding: "utf8" }). This matches the existing Ed25519 signing path (src/encryption/unifiedCrypto.ts decodes data with TextDecoder before calling Cryptography.sign), so the decode isn’t redundant—document that payload must be valid UTF-8 (and matches the bytes that were signed) in the relevant JSDoc (verifyPrimaryClaimSignature / Cryptography.verify).

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

In `@src/identity/cci/signing.ts` around lines 74 - 78, Keep the
TextDecoder.decode(payload) call in
verifyPrimaryClaimSignature/Cryptography.verify path and add JSDoc to both
verifyPrimaryClaimSignature and Cryptography.verify (or the verify method
implementation) clarifying that the incoming payload must be valid UTF-8 and
that the verifier expects the original signed bytes were UTF-8 decoded (so the
string passed to forge.pki.ed25519.verify uses encoding: "utf8"); update docs to
state the decoded string must exactly match the bytes that were signed and
reference the TextDecoder.decode usage in signing
(src/encryption/unifiedCrypto.ts) for consistency.
src/l2ps/index.ts (1)

2-8: ⚡ Quick win

Use the repo alias in the new barrel exports.

These new specifiers extend the public L2PS surface, so it is worth keeping them on the repo’s @/ import contract instead of adding more relative paths here.

♻️ Suggested change
-import L2PS from "./l2ps"
-import { L2PSConfig, L2PSEncryptedBytes, L2PSEncryptedPayload } from "./l2ps"
+import L2PS from "`@/l2ps/l2ps`"
+import {
+    L2PSConfig,
+    L2PSEncryptedBytes,
+    L2PSEncryptedPayload,
+} from "`@/l2ps/l2ps`"

 export { L2PS, L2PSConfig, L2PSEncryptedBytes, L2PSEncryptedPayload }

-export * as binding from "./binding"
-export * as channel from "./channel"
-export * as anchor from "./anchor"
+export * as binding from "`@/l2ps/binding`"
+export * as channel from "`@/l2ps/channel`"
+export * as anchor from "`@/l2ps/anchor`"

As per coding guidelines, **/*.{ts,tsx} should “Use @/ path aliases instead of relative imports.”

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

In `@src/l2ps/index.ts` around lines 2 - 8, The barrel exposes new symbols using
relative paths; update the imports/exports to use the repo alias (@"//")
contract instead of relative imports: import the types from "`@/l2ps`" and export
the submodules as "`@/l2ps/binding`", "`@/l2ps/channel`", and "`@/l2ps/anchor`" while
keeping exported symbols L2PS, L2PSConfig, L2PSEncryptedBytes,
L2PSEncryptedPayload and the named exports binding, channel, anchor unchanged so
the public surface uses the "`@/`..." aliases.

Source: Coding guidelines

src/l2ps/l2ps.ts (1)

433-437: ⚡ Quick win

Remove the new as any from the encryption path.

This cast punches a hole in type coverage right on the new crypto path. Prefer a typed helper or a createBuffer call that does not need an any escape hatch.

♻️ Suggested change
         cipher.update(
             forge.util.createBuffer(
                 Buffer.from(plaintext).toString('binary'),
-                'binary' as any,
             ),
         );

As per coding guidelines, **/*.{ts,tsx} should “Ensure full TypeScript type coverage.”

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

In `@src/l2ps/l2ps.ts` around lines 433 - 437, Remove the "as any" escape by
introducing a small typed helper that returns a forge ByteBuffer from the
plaintext and use it in the cipher.update call; e.g. add a helper like
binaryBufferFromPlaintext(plaintext: string): forge.util.ByteBuffer which calls
forge.util.createBuffer(Buffer.from(plaintext).toString('binary'), 'binary')
(typed correctly) and replace the inline createBuffer(...) call in the
cipher.update invocation with binaryBufferFromPlaintext(plaintext), removing the
cast.

Source: Coding guidelines

src/l2ps/channel/canonical.ts (2)

40-67: ⚡ Quick win

Add JSDoc comments for helper functions.

Four functions lack JSDoc documentation: stripChannelMessageSignature, transcriptHashHex, transcriptSigningBytes, and stripTranscriptSignatures. As per coding guidelines, all new methods and functions should use JSDoc format.

📝 Suggested JSDoc additions
+/**
+ * Remove the signature field from a ChannelMessage, returning the unsigned envelope.
+ */
 export function stripChannelMessageSignature(
     msg: ChannelMessage,
 ): UnsignedChannelMessage {

+/**
+ * Hex digest of `JCS(transcript_without_signatures)`. Used for transcript signature verification.
+ */
 export function transcriptHashHex(
     unsigned: UnsignedChannelTranscript,
 ): string {

+/**
+ * Bytes the transcript signature covers:
+ *   `dacs-transcript:v1:` || transcript_hash_hex
+ */
 export function transcriptSigningBytes(
     unsigned: UnsignedChannelTranscript,
 ): Uint8Array {

+/**
+ * Remove the signatures array from a ChannelTranscript, returning the unsigned transcript.
+ */
 export function stripTranscriptSignatures(
     t: ChannelTranscript,
 ): UnsignedChannelTranscript {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/l2ps/channel/canonical.ts` around lines 40 - 67, Add JSDoc comments to
the four helper functions so they meet project guidelines: document purpose,
parameters, return types, and any important behavior for
stripChannelMessageSignature, transcriptHashHex, transcriptSigningBytes, and
stripTranscriptSignatures. For each function include a one-line description,
`@param` for the input types (ChannelMessage, UnsignedChannelTranscript,
ChannelTranscript), and `@returns` describing the returned type/value
(UnsignedChannelMessage, hex string, Uint8Array, UnsignedChannelTranscript).
Keep descriptions concise and note any encoding/formatting details (e.g., uses
canonicalJSONStringify, sha256, TRANSCRIPT_DOMAIN_PREFIX) where relevant.

Source: Coding guidelines


69-71: 💤 Low value

Consider using export...from for re-exports.

The comment explains these are re-exported for compatibility, but SonarCloud suggests using the more idiomatic export { signatureFromHex, signatureToHex } from "../utils/hex" syntax instead of separate import + export statements.

♻️ Proposed refactor
-// Re-exported from the shared utility so existing imports of
-// `signatureFromHex` / `signatureToHex` from this module keep working.
-export { signatureFromHex, signatureToHex }
+// Re-exported from the shared utility so existing imports of
+// `signatureFromHex` / `signatureToHex` from this module keep working.
+export { signatureFromHex, signatureToHex } from "../utils/hex"

And remove the corresponding imports from line 3:

-import { bytesToHex, signatureFromHex, signatureToHex } from "../utils/hex"
+import { bytesToHex } from "../utils/hex"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/l2ps/channel/canonical.ts` around lines 69 - 71, Replace the current
import-then-export pattern for signatureFromHex and signatureToHex with a direct
re-export using the `export { signatureFromHex, signatureToHex } from "…"` form:
update the module to directly re-export these symbols (signatureFromHex,
signatureToHex) from the shared hex utils and remove the now-redundant import
statements that originally brought them into this module so only the single
`export ... from` remains.

Source: Linters/SAST tools

src/l2ps/channel/channelIdRegistry.ts (1)

16-31: ⚡ Quick win

Add JSDoc for InMemoryChannelIdRegistry class and methods.

The InMemoryChannelIdRegistry class and its register/has methods lack JSDoc documentation. As per coding guidelines, all new methods and functions should use JSDoc format.

📝 Suggested JSDoc additions
+/**
+ * In-memory implementation of ChannelIdRegistry for SDK default usage.
+ * Production deployments should provide a durable store implementation.
+ */
 export class InMemoryChannelIdRegistry implements ChannelIdRegistry {
     private readonly seen = new Set<string>()

+    /**
+     * Register a channelId, throwing if it was already registered (CH-6).
+     */
     async register(channelId: string): Promise<void> {

+    /**
+     * Check if a channelId has been registered.
+     */
     async has(channelId: string): Promise<boolean> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/l2ps/channel/channelIdRegistry.ts` around lines 16 - 31, Add JSDoc
comments to the InMemoryChannelIdRegistry class and its methods: document the
class purpose, the register(channelId: string) method (describe parameter, that
it throws if channelId is falsy, and that it throws on duplicate registration /
CH-6 violation), and the has(channelId: string): Promise<boolean> method
(describe parameter and return value). Use standard JSDoc tags like `@class` or
brief description for the class, `@param` for channelId, `@returns` for has and
register (Promise<void>), and `@throws` for the error cases; apply these comments
directly above the InMemoryChannelIdRegistry class, the register method, and the
has method.

Source: Coding guidelines

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

Inline comments:
In `@src/identity/cci/claim.ts`:
- Around line 3-57: Add JSDoc blocks for each exported function (parseClaimRef,
demosClaimRefForAddress, isDemosClaim, normalizeDemosAddress,
demosAddressFromClaim) describing their purpose, parameters (with types), return
value, and thrown exceptions; place the JSDoc immediately above each function
definition, include `@param`, `@returns`, and `@throws` tags as appropriate (e.g.,
parseClaimRef throws TypeError or Error on malformed input;
normalizeDemosAddress throws Error for non-hex or wrong length), and keep
descriptions concise and consistent with existing coding guidelines.

In `@src/identity/cci/signing.ts`:
- Around line 21-46: Add a JSDoc block for the signWithPrimaryClaim function
that documents the parameters (claim: ClaimReference, payload: Uint8Array,
demos: Demos), the Promise<Uint8Array> return value, and the possible thrown
errors (unsupported scheme from parseClaimRef, "Demos wallet not connected", and
claim mismatch after demosAddressFromClaim/normalizeDemosAddress); briefly
describe behavior (verifies scheme is "demos", checks wallet connection, ensures
connected address matches claim, and uses demos.crypto.sign to sign the payload,
returning the signature via toUint8Array).
- Around line 55-79: Add JSDoc for verifyPrimaryClaimSignature: document the
function purpose, parameters (claim: ClaimReference, payload: Uint8Array,
signature: Uint8Array), return type (boolean), and possible thrown errors
(unsupported scheme, invalid public key length). Mention expected scheme "demos"
and that the public key is a 32-byte Ed25519 key derived via
demosAddressFromClaim and hexAddressToBytes, and note that verification is
performed using Cryptography.verify with the payload decoded as UTF-8. Keep the
JSDoc immediately above the verifyPrimaryClaimSignature declaration.

In `@src/l2ps/anchor/anchor.ts`:
- Around line 37-39: Add JSDoc blocks for the public API functions
anchorProgramName, anchorEncryptedTranscript, decryptAnchoredTranscript, and
verifyAnchorIntegrity: for each function include a one-line description, `@param`
entries describing types and purpose of each parameter (e.g., channelId,
transcript, key, signature, etc.), an `@returns` describing the return value and
its type, and an `@throws` or `@returns` {Promise<...>} note if the function can
reject/errors; ensure the JSDoc matches actual parameter names and types used in
those functions and include brief examples or `@see` references where helpful to
aid IDE tooling and generated docs.

In `@src/l2ps/binding/binding.ts`:
- Around line 61-79: createMembershipBinding currently accepts opts.boundAt
without validating it's a numeric timestamp and verifyMembershipBinding (and any
verifier used by resolveMember) never asserts boundAt is a valid number,
allowing malformed or missing boundAt to pass; update createMembershipBinding to
validate opts.boundAt (if provided) is a finite integer timestamp (e.g.,
Number.isFinite and isInteger or coercible to a safe integer) and throw a clear
Error if not, and update verifyMembershipBinding to assert the incoming
binding.boundAt exists and is a valid numeric timestamp before accepting or
verifying the signature; also add regression tests alongside the tampering tests
that exercise missing, non-numeric, and out-of-range boundAt values to ensure
both creation and verification reject invalid boundAt.
- Around line 29-34: bindingProgramName currently concatenates channelId and
subnetMemberId with ":" which allows collisions (e.g. "a:b":"c" vs "a":"b:c");
change bindingProgramName to produce an injective key by encoding the two parts
unambiguously (for example prefix each part with its length, use a separator
plus escaping, or base64/URL-encode each component) so no two distinct
(channelId, subnetMemberId) pairs map to the same string; update the function
bindingProgramName and ensure callers anchorMembershipBinding and resolveMember
continue to use the new format.

In `@src/l2ps/l2ps.ts`:
- Around line 455-466: The decryptBytes function should validate that
payload.ciphertext, payload.tag, and payload.nonce exist and are strings before
calling forge.util.decode64 to avoid raw base64 errors for storage-loaded JSON;
in decryptBytes (and consider callers like decryptAnchoredTranscript) add checks
that each of ciphertext/tag/nonce is a non-empty string and throw a clear SDK
Error (e.g. "L2PS.decryptBytes: missing or invalid ciphertext/tag/nonce in
L2PSEncryptedBytes") when validation fails, then proceed to perform
forge.util.decode64 and the existing buffer/nonce length checks as before.

In `@src/l2ps/utils/hex.ts`:
- Around line 7-29: Add JSDoc comments to all exported functions: bytesToHex,
signatureToHex, and signatureFromHex; for bytesToHex describe input Uint8Array
and returned hex string, for signatureToHex document that it prefixes "0x" and
returns a hex string for a signature Uint8Array, and for signatureFromHex
document accepted input (allowing optional "0x"/"0X" prefix), validation
behavior (throws Error on invalid hex or odd length), and the returned
Uint8Array; keep descriptions concise and include parameter and return
annotations.

In `@src/tests/l2ps/channel.test.ts`:
- Around line 537-538: The test builds a numeric array `seen` from `m1.sequence`
and `m2.sequence` then calls `.sort()` which performs lexicographic string sort;
change the call on the `seen` array to use a numeric comparator (e.g.,
`.sort((a, b) => a - b)`) so `expect(seen).toEqual([1, 2])` reliably passes for
all numeric values.

---

Nitpick comments:
In `@src/identity/cci/signing.ts`:
- Around line 74-78: Keep the TextDecoder.decode(payload) call in
verifyPrimaryClaimSignature/Cryptography.verify path and add JSDoc to both
verifyPrimaryClaimSignature and Cryptography.verify (or the verify method
implementation) clarifying that the incoming payload must be valid UTF-8 and
that the verifier expects the original signed bytes were UTF-8 decoded (so the
string passed to forge.pki.ed25519.verify uses encoding: "utf8"); update docs to
state the decoded string must exactly match the bytes that were signed and
reference the TextDecoder.decode usage in signing
(src/encryption/unifiedCrypto.ts) for consistency.

In `@src/l2ps/channel/canonical.ts`:
- Around line 40-67: Add JSDoc comments to the four helper functions so they
meet project guidelines: document purpose, parameters, return types, and any
important behavior for stripChannelMessageSignature, transcriptHashHex,
transcriptSigningBytes, and stripTranscriptSignatures. For each function include
a one-line description, `@param` for the input types (ChannelMessage,
UnsignedChannelTranscript, ChannelTranscript), and `@returns` describing the
returned type/value (UnsignedChannelMessage, hex string, Uint8Array,
UnsignedChannelTranscript). Keep descriptions concise and note any
encoding/formatting details (e.g., uses canonicalJSONStringify, sha256,
TRANSCRIPT_DOMAIN_PREFIX) where relevant.
- Around line 69-71: Replace the current import-then-export pattern for
signatureFromHex and signatureToHex with a direct re-export using the `export {
signatureFromHex, signatureToHex } from "…"` form: update the module to directly
re-export these symbols (signatureFromHex, signatureToHex) from the shared hex
utils and remove the now-redundant import statements that originally brought
them into this module so only the single `export ... from` remains.

In `@src/l2ps/channel/channelIdRegistry.ts`:
- Around line 16-31: Add JSDoc comments to the InMemoryChannelIdRegistry class
and its methods: document the class purpose, the register(channelId: string)
method (describe parameter, that it throws if channelId is falsy, and that it
throws on duplicate registration / CH-6 violation), and the has(channelId:
string): Promise<boolean> method (describe parameter and return value). Use
standard JSDoc tags like `@class` or brief description for the class, `@param` for
channelId, `@returns` for has and register (Promise<void>), and `@throws` for the
error cases; apply these comments directly above the InMemoryChannelIdRegistry
class, the register method, and the has method.

In `@src/l2ps/index.ts`:
- Around line 2-8: The barrel exposes new symbols using relative paths; update
the imports/exports to use the repo alias (@"//") contract instead of relative
imports: import the types from "`@/l2ps`" and export the submodules as
"`@/l2ps/binding`", "`@/l2ps/channel`", and "`@/l2ps/anchor`" while keeping exported
symbols L2PS, L2PSConfig, L2PSEncryptedBytes, L2PSEncryptedPayload and the named
exports binding, channel, anchor unchanged so the public surface uses the
"`@/`..." aliases.

In `@src/l2ps/l2ps.ts`:
- Around line 433-437: Remove the "as any" escape by introducing a small typed
helper that returns a forge ByteBuffer from the plaintext and use it in the
cipher.update call; e.g. add a helper like binaryBufferFromPlaintext(plaintext:
string): forge.util.ByteBuffer which calls
forge.util.createBuffer(Buffer.from(plaintext).toString('binary'), 'binary')
(typed correctly) and replace the inline createBuffer(...) call in the
cipher.update invocation with binaryBufferFromPlaintext(plaintext), removing the
cast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 339f5a4c-51b4-425f-a7b1-ea04796b0445

📥 Commits

Reviewing files that changed from the base of the PR and between c9d9824 and e88627d.

📒 Files selected for processing (29)
  • package.json
  • src/identity/cci/claim.ts
  • src/identity/cci/index.ts
  • src/identity/cci/signing.ts
  • src/identity/cci/types.ts
  • src/identity/index.ts
  • src/l2ps/anchor/anchor.ts
  • src/l2ps/anchor/index.ts
  • src/l2ps/anchor/types.ts
  • src/l2ps/binding/binding.ts
  • src/l2ps/binding/canonical.ts
  • src/l2ps/binding/index.ts
  • src/l2ps/binding/types.ts
  • src/l2ps/channel/canonical.ts
  • src/l2ps/channel/channelIdRegistry.ts
  • src/l2ps/channel/envelope.ts
  • src/l2ps/channel/index.ts
  • src/l2ps/channel/session.ts
  • src/l2ps/channel/transcript.ts
  • src/l2ps/channel/types.ts
  • src/l2ps/index.ts
  • src/l2ps/l2ps.ts
  • src/l2ps/utils/hex.ts
  • src/tests/identity/cci.test.ts
  • src/tests/l2ps/anchor.test.ts
  • src/tests/l2ps/binding.test.ts
  • src/tests/l2ps/channel.test.ts
  • src/websdk/BroadcastFailedError.ts
  • src/websdk/TransportError.ts

Comment thread src/identity/cci/claim.ts
Comment thread src/identity/cci/signing.ts
Comment thread src/identity/cci/signing.ts
Comment thread src/l2ps/anchor/anchor.ts
Comment thread src/l2ps/binding/binding.ts
Comment thread src/l2ps/binding/binding.ts
Comment thread src/l2ps/l2ps.ts
Comment thread src/l2ps/utils/hex.ts
Comment thread src/tests/l2ps/channel.test.ts Outdated
CodeRabbit caught one real bug, two defensive-validation gaps, and
asked for JSDoc on every new public export. Substantive fixes first:

bindingProgramName injectivity
  src/l2ps/binding/binding.ts
  src/l2ps/anchor/anchor.ts
  Using `:` as both data and delimiter meant ("a:b","c") and
  ("a","b:c") collided on the same SP name — `resolveMember` could be
  tricked into returning a binding from a sibling context. Percent-encode
  the components so the lookup key is one-to-one. Same guard applied to
  `anchorProgramName(channelId)` for consistency.

boundAt validation
  src/l2ps/binding/binding.ts
  `createMembershipBinding` accepted any `boundAt` override; a chain-
  loaded binding with `boundAt: NaN` could pass `verifyMembershipBinding`
  because the verifier never typed-checked the field. Now reject
  non-safe-integer / negative values at create-time and treat them as
  structurally invalid at verify-time.

L2PS.decryptBytes payload shape check
  src/l2ps/l2ps.ts
  Storage-loaded JSON can violate the `L2PSEncryptedBytes` shape at
  runtime; without an explicit guard forge would throw an opaque base64
  error. Reject any payload whose `ciphertext` / `tag` / `nonce` is not
  a string so the caller sees a clear SDK-side message.

JSDoc on every new public export
  src/identity/cci/claim.ts, src/identity/cci/signing.ts,
  src/l2ps/anchor/anchor.ts, src/l2ps/utils/hex.ts
  Convert the prose-style headers into formal JSDoc blocks with @param,
  @returns, @throws — required by the project coding guideline. No
  behaviour change.

Regression tests added (7 new — total 109/109):
- bindingProgramName: collision-avoidance + backward-compat for
  no-special-char ids
- boundAt validation: rejects negative + non-integer override; verifier
  rejects NaN at rest
- L2PS.decryptBytes shape: rejects missing fields and non-string types

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant