Skip to content

Conversation

DrJesseGlass
Copy link
Contributor

Description

Fixes 'Uint8Array expected' error in DelegationChain.toJSON() caused by @noble/hashes v1.8+ requiring strict Uint8Array inputs.

Root Cause: The upgrade to @noble/hashes v1.8+ introduced stricter type checking where bytesToHex() only accepts Uint8Array, but delegation serialization can encounter ArrayBuffer objects from:

  • Browser crypto APIs returning ArrayBuffer instead of Uint8Array
  • Serialization/deserialization cycles converting between storage formats
  • External libraries passing mixed binary data types

Impact: Applications using delegation chain persistence (like ic-siws-js) crash with "Uint8Array expected" when calling toJSON() on delegation chains containing ArrayBuffer data.

Solution: Added safeBytesToHex() wrapper that converts ArrayBuffer to Uint8Array before calling bytesToHex(). This maintains backward compatibility while working with the stricter noble library requirements.

Changed Methods:

  • Delegation.toJSON() - handles ArrayBuffer in pubkey field
  • DelegationChain.toJSON() - handles ArrayBuffer in signature and publicKey fields

How Has This Been Tested?

  • All existing tests pass: 22 identity package tests continue to work
  • Regression test added: Simulates ArrayBuffer conditions that caused the original failure
  • Integration verified: Resolves the issue in ic-siws-js that triggered this investigation
  • Type compatibility: Handles both Uint8Array and ArrayBuffer inputs seamlessly
  • Round-trip tested: toJSON()fromJSON()toJSON() cycles work correctly

Test command: pnpm --filter @dfinity/identity test

Integration test: Verified fix resolves the original issue in ic-siws-js component that triggered this investigation.

Checklist:

  • My changes follow the guidelines in CONTRIBUTING.md.
  • The title of this PR complies with Conventional Commits.
  • I have edited the CHANGELOG accordingly.
  • I have made corresponding changes to the documentation.

@DrJesseGlass DrJesseGlass requested a review from a team as a code owner September 19, 2025 13:39
@cla-idx-bot
Copy link

cla-idx-bot bot commented Sep 19, 2025

Dear @DrJesseGlass,

In order to potentially merge your code in this open-source repository and therefore proceed with your contribution, we need to have your approval on DFINITY's CLA.

If you decide to agree with it, please visit this issue and read the instructions there. Once you have signed it, re-trigger the workflow on this PR to see if your code can be merged.

— The DFINITY Foundation

Copy link
Member

@ilbertt ilbertt left a comment

Choose a reason for hiding this comment

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

@DrJesseGlass thanks for the contribution and the detailed explanation in the PR!

I'm wondering: shouldn't the usage of ArrayBuffer instead of Uint8Array be caught by the TypeScript checks before runtime? In fact, in the tests that you added, you explicitly cast the type to any to avoid TS errors.

I think the proper fix is to convert to Uint8Array downstream, like you did in kristoferlund/ic-siws#6, leaving the @icp-sdk/core/identity code untouched.

@DrJesseGlass
Copy link
Contributor Author

Thank you @ilbertt for the review and architectural discussion.

You're correct that the test uses as any to bypass TypeScript - this is intentional to reproduce the real-world runtime condition where ArrayBuffer objects enter the delegation chain flow. The issue isn't caught by TypeScript because of structural typing limitations (see TypeScript issues #42534 and #55032).

The runtime failure occurs when ArrayBuffer objects enter delegation chains through:

  • Serialization/deserialization cycles (localStorage, IndexedDB)
  • Cross-context communication (web workers, iframes)
  • Library interoperability where different components return different binary types
  • Browser API inconsistencies across different environments

The test simulates the exact runtime condition where ArrayBuffer objects end up in delegation chain serialization paths. While TypeScript's structural typing doesn't catch this at compile time, bytesToHex() fails at runtime without proper type handling.

Core library type conversion is an established strategy:

  • @dfinity/agent accepts flexible binary types and normalizes internally
  • @dfinity/utils provides arrayBufferToUint8Array for standardization
  • The ecosystem is moving toward Uint8Array consistency with defensive type handling
    and reduces duplicated code, makes approaches consistent across applications and simplifies developer experience.

DelegationChain.toJSON() is fundamental to ICP authentication patterns:

  • Session persistence: Applications store delegation chains in localStorage to maintain authentication across browser sessions
  • Cross-context communication: Web workers and iframes need serialized delegation chains
  • Network transport: API requests include serialized delegation data

The fix is indeed minimal:

  • Single utility function: safeBytesToHex() wrapper
  • Targeted changes: Only the specific toJSON() methods that were failing
  • Zero breaking changes: Existing APIs unchanged, only runtime behavior improved
  • Backward compatible: Works with both ArrayBuffer and Uint8Array inputs

ilbertt
ilbertt previously approved these changes Sep 23, 2025
Copy link
Member

@ilbertt ilbertt left a comment

Choose a reason for hiding this comment

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

I've thought more about this and I think it makes sense to avoid developers having to manually check for the proper buffer type.

I left a few minor comments that I think can be resolved easily.

… the number of assertions at two points in test; coverted one off Hex format regexes to expect.extend custom matcher
@github-actions github-actions bot dismissed ilbertt’s stale review September 24, 2025 18:29

Review dismissed by automation script.

ilbertt
ilbertt previously approved these changes Sep 26, 2025
@ilbertt
Copy link
Member

ilbertt commented Sep 26, 2025

@DrJesseGlass the PR looks good. Can you please fix the linter error? So that we can merge it

@github-actions github-actions bot dismissed ilbertt’s stale review September 29, 2025 17:19

Review dismissed by automation script.

@ilbertt ilbertt merged commit b570114 into dfinity:main Sep 30, 2025
28 checks passed
@ilbertt
Copy link
Member

ilbertt commented Sep 30, 2025

@DrJesseGlass merged! Thanks a lot for the contribution and the iterations on the feedback. A patch containing the fix will be released soon.

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

Successfully merging this pull request may close these issues.

2 participants