webcrypto: add SHA-3 digests and SubtleCrypto.supports()#29222
webcrypto: add SHA-3 digests and SubtleCrypto.supports()#29222
Conversation
Implements the first slice of the WICG "Modern Algorithms in the Web Cryptography API" specification (https://wicg.github.io/webcrypto-modern-algos/): - SHA3-256, SHA3-384 and SHA3-512 as digest algorithms on `crypto.subtle.digest`. Output matches the FIPS 202 test vectors. Implementation is a small self-contained Keccak-f[1600] sponge in CryptoDigest.cpp, which can later be extended with SHAKE/cSHAKE/ TurboSHAKE when those land. - `crypto.subtle.supports(operation, algorithm)`, a synchronous feature detection method that reports whether a (operation, algorithm) pair is supported. Unknown operations and algorithms, and malformed algorithm parameter dictionaries, all return false rather than throwing — matching the WICG spec's progressive-enhancement intent. KEM operations (encapsulateKey/encapsulateBits/decapsulateKey/ decapsulateBits) return false for now and will flip to true when ML-KEM lands in a follow-up. Refs #29218
|
Updated 9:40 PM PT - Apr 12th, 2026
❌ @robobun, your commit bcad1c2 has 3 failures in
🧪 To try this PR locally: bunx bun-pr 29222That installs a local version of the PR into your bun-29222 --bun |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds SHA‑3 (SHA3‑256/384/512) support to Bun's WebCrypto: new algorithm identifiers and digest enum values, a Keccak‑based SHA‑3 digest backend, algorithm classes and registration, adjustments to key/serialization handling for SHA‑3, a SubtleCrypto.supports(operation, algorithm) feature‑detection API, and tests validating digest outputs and supports semantics. Changes
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 50-51: The comment in the test describing SHA-3 rates is
incorrect: it states "largest SHA-3 rate (72 bytes for SHA3-512)" but SHA3-512
actually has the smallest rate (72 bytes); update the comment text in the
test/regression/issue/29218.test.ts test block (the multi-block absorption
comment near the SHA3-512 test) to say "smallest rate (72 bytes for SHA3-512)"
or otherwise correct the wording so it accurately reflects that SHA3-512 uses
the smallest sponge rate.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c4d20dff-7a29-4887-8eac-3d38f41fa628
📒 Files selected for processing (17)
src/bun.js/bindings/AsymmetricKeyValue.cppsrc/bun.js/bindings/webcore/SerializedScriptValue.cppsrc/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.hsrc/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cppsrc/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_256.cppsrc/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_256.hsrc/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_384.cppsrc/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_384.hsrc/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_512.cppsrc/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3_512.hsrc/bun.js/bindings/webcrypto/CryptoDigest.cppsrc/bun.js/bindings/webcrypto/CryptoDigest.hsrc/bun.js/bindings/webcrypto/JSSubtleCrypto.cppsrc/bun.js/bindings/webcrypto/SubtleCrypto.cppsrc/bun.js/bindings/webcrypto/SubtleCrypto.hsrc/bun.js/bindings/webcrypto/SubtleCrypto.idltest/regression/issue/29218.test.ts
|
@robobun use the available WPTs |
…cKey Addresses review feedback on #29222: - wrapKey/unwrapKey now mirror the two-step normalize-and-fallback dispatch the real methods perform. supports("wrapKey", ...) reports true for AES-GCM/CBC/CTR/CFB-8 and RSA-OAEP (via the Encrypt fallback), matching what a subsequent wrapKey() call would accept. Before, only AES-KW was reported. - exportKey no longer proxies through ImportKey normalization. It resolves the algorithm name to an identifier and then runs it through isSupportedExportKey(), the same gate the real exportKey() uses. HKDF/PBKDF2 and hash-only algorithms now correctly report false rather than returning a false positive that the underlying method would reject at runtime. - getPublicKey returns false for every algorithm. The method is not implemented yet (it lands with ML-KEM/ML-DSA in a later slice) and it is conceptually asymmetric-only, so symmetric algorithms must never report true. Regression test coverage expands to 33 tests / 101 expects and now cross-checks multi-block SHA-3 output against node:crypto (OpenSSL), exercises the wrap/unwrap fallback on AES-GCM/CBC/CTR and RSA-OAEP, the exportKey HKDF/PBKDF2 rejection, the getPublicKey negative cases, and an end-to-end sanity check that every supports()==true pair actually succeeds in the underlying method. Refs #29218
|
@robobun SubtleCrypto.supports is a static method on the SubtleCrypto global, not a prototype method. |
Two corrections on top of c1f35b0: 1. Move supports() to the interface object. The WICG "Modern Algorithms" spec defines supports() as a static method on the SubtleCrypto constructor, not an instance method on the prototype. It is now exposed as `SubtleCrypto.supports(op, alg)` rather than `crypto.subtle.supports(...)`. Implementation moves the hashtable entry from JSSubtleCryptoPrototypeTableValues to a JSFunction installed in JSSubtleCryptoDOMConstructor:: initializeProperties(), rewrites the host function to not require a `this` cast, and marks SubtleCrypto::supports() as a C++ static. 2. Reject SHA-3 as a hash sub-algorithm for HMAC / RSA / ECDSA. Accepting SHA-3 as a top-level digest operation in normalize made toHashIdentifier() start resolving "SHA3-256" etc. successfully, which let HMAC / RSA-PSS / RSA-OAEP / ECDSA importKey and generateKey build CryptoKey instances with m_hash = SHA3_*. Those keys then: - crash sign()/verify() with a cryptic OperationError because OpenSSLUtilities::digestAlgorithm() has no SHA-3 branch; - hit ASSERT_NOT_REACHED() in CryptoKeyHMAC::getKeyLengthFromHash(); - crash the process via RELEASE_ASSERT_NOT_REACHED() in SerializedScriptValue when structured-cloned or postMessage'd. toHashIdentifier() now returns NotSupportedError for SHA-3 identifiers. SHA-3 as a top-level digest still works; only the hash-subalgorithm slot is gated. The gate will lift in a follow-up PR that wires SHA-3 into digestAlgorithm(), getKeyLengthFromHash(), and the structured-clone tag table. Regression test grows to 34 tests / 113 expects: new test asserts that HMAC / RSA-PSS / HMAC-generateKey reject with NotSupportedError for each SHA-3 variant, and all existing supports() cases flip from `crypto.subtle.supports` to `SubtleCrypto.supports`. Refs #29218
|
@robobun start work on adding a WPT suite runner here, if not a generic one then one for WebCrypto WPTs specifically, there's no way we can confirm these behaviours are correct without running the shared WPT test suite. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 119-135: The ECDSA branch currently repeats the same call and
never uses the loop variable `alg`; update the crypto.subtle.generateKey call
for ECDSA (the call that currently uses { name: "ECDSA", namedCurve: "P-256" })
to include the hash property (e.g., { name: "ECDSA", namedCurve: "P-256", hash:
alg }) so the test actually exercises SHA-3 handling, and keep the existing
expectation (resolves.toBeDefined()) so it asserts key generation succeeds while
normalization of sign/verify will be tested elsewhere.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 501035b4-e7a5-47eb-bded-f1409c5f849f
📒 Files selected for processing (5)
src/bun.js/bindings/webcrypto/JSSubtleCrypto.cppsrc/bun.js/bindings/webcrypto/SubtleCrypto.cppsrc/bun.js/bindings/webcrypto/SubtleCrypto.hsrc/bun.js/bindings/webcrypto/SubtleCrypto.idltest/regression/issue/29218.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
test/regression/issue/29218.test.ts (1)
112-135:⚠️ Potential issue | 🟠 MajorThe ECDSA branch still does not exercise the SHA-3 rejection path.
Lines 133-135 rerun the same happy-path
generateKey()on every loop iteration;algonly affects HMAC and RSA-PSS here. That means theECDSApart of the test name is still unverified.Suggested change
- await expect( - crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign"]), - ).resolves.toBeDefined(); // sanity: real ECDSA still works + const keyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]); + await expect( + crypto.subtle.sign({ name: "ECDSA", hash: alg }, keyPair.privateKey, te.encode("abc")), + ).rejects.toMatchObject({ name: "NotSupportedError" });#!/bin/bash echo "== current test ==" rg -n -C2 'ECDSA|hash: alg|generateKey\(' test/regression/issue/29218.test.ts echo echo "== rejection hook ==" rg -n -C3 'toHashIdentifier|SHA3_256|SHA3_384|SHA3_512|ECDSA' src/bun.js/bindings/webcrypto/SubtleCrypto.cpp🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/regression/issue/29218.test.ts` around lines 112 - 135, The ECDSA case in the loop never uses the loop variable alg so it doesn't test SHA‑3 rejection; update the ECDSA call that currently reads crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, ...) to include the hash: alg parameter (i.e. crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256", hash: alg }, ...)) and change the expectation to reject with NotSupportedError (like the HMAC/RSA-PSS cases) so the ECDSA path is actually exercised for "SHA3-256", "SHA3-384", and "SHA3-512".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 280-289: Add the new test file test/regression/issue/29218.test.ts
to the allowlist file test/no-validate-exceptions.txt under the "#
normalizeCryptoAlgorithmParameters" section so the malformed-algorithm-path
exercised by SubtleCrypto.supports(...) (null, number, array, object cases) is
exempt from validation; specifically, insert the filename as an entry under that
section to prevent ASAN/JSC throw-scope SIGILL when
BUN_JSC_validateExceptionChecks=1 triggers the normalization
exception-swallowing path in
normalizeCryptoAlgorithmParameters/SubtleCrypto.supports.
- Around line 141-144: Add negative assertions to ensure the static-only binding
isn't exposed on instances or the prototype: after the existing checks for
SubtleCrypto.supports, assert that SubtleCrypto.prototype.supports is undefined
(or not present) and that an instance (e.g., new SubtleCrypto()) does not have a
supports property; reference the SubtleCrypto.supports symbol and
SubtleCrypto.prototype and the SubtleCrypto constructor when adding these
assertions so future regressions that leak the method onto the prototype or
instance are caught.
---
Duplicate comments:
In `@test/regression/issue/29218.test.ts`:
- Around line 112-135: The ECDSA case in the loop never uses the loop variable
alg so it doesn't test SHA‑3 rejection; update the ECDSA call that currently
reads crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, ...) to
include the hash: alg parameter (i.e. crypto.subtle.generateKey({ name: "ECDSA",
namedCurve: "P-256", hash: alg }, ...)) and change the expectation to reject
with NotSupportedError (like the HMAC/RSA-PSS cases) so the ECDSA path is
actually exercised for "SHA3-256", "SHA3-384", and "SHA3-512".
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: d394f1f0-2db5-4571-9ccf-884663fed075
📒 Files selected for processing (1)
test/regression/issue/29218.test.ts
…on checks
The regression test exercises normalizeCryptoAlgorithmParameters along
two paths that trigger BUN_JSC_validateExceptionChecks=1 in ASAN CI:
- digest("SHA3-224", ...) hits the default NotSupportedError arm in the
Digest switch.
- Every SubtleCrypto.supports() call routes through the
normalizesWithoutException() helper, which clears exceptions.
Without an entry in test/no-validate-exceptions.txt the pre-existing
throw-scope bookkeeping issue in normalizeCryptoAlgorithmParameters
trips WTFCrash -> ud2 -> SIGILL on ASAN runs. Match the pattern used
for every other webcrypto test in the same section.
Refs #29218
…nits
Two small follow-ups on review feedback:
- The ECDSA arm of the SHA-3 sub-hash rejection test was a no-op: it
called generateKey({name:"ECDSA", namedCurve:"P-256"}) three times in
the loop without referencing the SHA-3 loop variable at all. For ECDSA
the hash is supplied at sign()/verify() time, not generateKey() time,
so the real assertion is that sign({name:"ECDSA", hash:alg}, ...)
rejects with NotSupportedError. Generate one P-256 key pair outside
the loop, then call sign() inside it.
- Two comments in CryptoDigest.cpp SHA-3 sponge were misleading:
* The buffer[144] comment referred to SHA3-224 as if it were
registered. Reword to say it is sized for SHA3-224's rate as
forward-compatibility for later XOF additions; the three variants
this file ships only need up to SHA3-256's 136 bytes.
* The squeeze comment claimed "rate >= 104 bytes", which is the
SHA3-384 rate; the smallest SHA-3 rate is SHA3-512's 72 bytes.
The single-squeeze logic is still correct (72 >= 64), but the
bound was wrong — rewrite to state the real minimum.
(Separately, a reviewer suggested adding DontEnum to the constructor-
level supports property. Per WebIDL § 3.7.7 "define the operations",
static operations get {Enumerable: true}, so the existing attribute-0
putDirect is already correct and is intentionally unchanged.)
34 tests / 114 expects pass.
Refs #29218
|
@robobun the PR's description still uses prototype method |
|
@robobun since you can't run WPTs at least look at internalizing these WPT tests |
Ports the full (algorithm × input-size) test matrix from WebCryptoAPI/digest/sha3.tentative.https.any.js in web-platform-tests into the regression test. Each of the 12 (SHA3-256/384/512 × empty/short/medium/long) SHA-3 digests now has to match the exact bytes shipped with the WPT, including the 87040-byte long input that exercises multi-block absorption across 512 sponge blocks. The bytes are copied from: https://github.com/web-platform-tests/wpt/blob/master/WebCryptoAPI/digest/sha3.tentative.https.any.js We do not ship a full WPT harness in this slice — that is tracked separately under #19673 — but the vectors are the important part for this PR and are now asserted in every bun bd test run. 35 tests / 126 expects pass. Refs #29218
…ment The previous comment on `buffer[144]` said the extra capacity was there so "future SHA3-224 / SHAKE128 support" could reuse the struct without a resize. SHAKE128 does not fit: its security level is 128 bits, so its capacity is 2*128 = 256 bits = 32 bytes, and its rate is 200 - 32 = 168 bytes -- 24 bytes larger than this buffer. A developer naively adding SHAKE128 by following the comment would overflow the buffer inside sha3Update. Reword the comment to call out that SHAKE variants do NOT fit as-is: SHAKE128's rate overflows the buffer, and both SHAKE variants also need a different digestLength model because their output is variable-length. Adding SHAKE means resizing the buffer AND teaching sha3Final to squeeze across multiple rate-sized blocks. Refs #29218
…ck squeeze The current sha3Final only squeezes one rate-sized block, which is correct for all three registered algorithms (SHA3-256: 32 < 136, SHA3-384: 48 < 104, SHA3-512: 64 < 72) but silently wrong if the invariant ever breaks — the squeeze loop would read the capacity portion of the state and emit those bits verbatim, producing a cryptographically wrong hash with no diagnostic. The Sha3Context comment already warns XOF variants need a multi-block squeeze. Add ASSERT(ctx.digestLength <= ctx.rateBytes) at the top of sha3Final so that if a follow-up slice wires SHAKE/cSHAKE/TurboSHAKE through this path without also extending the squeeze, debug and ASAN builds fail loudly instead of producing silent garbage. 35 tests / 126 expects pass (assert does not fire for any registered variant). Refs #29218
|
CI status note (final — approved PR, pre-existing flakes only) PR is approved by @panva ("ship it"). Every CI run has been red on a different set of pre-existing flakes, never touching webcrypto. Summary so far:
The only potentially new thing is the ASAN report on build 45397: Both halves of the sha3 sponge now have matching defensive asserts (bcad1c2) and I have already pushed one empty-commit retry (ca842ba). Further retries are a maintainer call — pushing more no-op commits from my side is a waste of CI budget when the same six pre-existing flakes rotate through on every run. Ready to merge; happy to land any real review feedback as a follow-up. |
|
@robobun @Jarred-Sumner I've verified the functionality locally. @robobun let's start working on the next slice building on top of this one, i recommend finishing up the digest algorithms first, so - TurboSHAKE (maybe KangarooTwelve, probably not tho) that you've got the bases for in this PR already, and cSHAKE at the minimum without support for the |
Build 45381 failed on four unrelated jobs: two darwin-aarch64 runners expired waiting for an agent, linux-aarch64-build-cpp timed out at 14 minutes (infra), and debian-13-x64-asan hit the pre-existing flake in test/js/node/test/parallel/test-cluster-primary-kill.js (a Node cluster primary-kill timing test with no webcrypto / SHA-3 touch points). None of these are related to the ASSERT I added in 5ddb4f6, which only fires when digestLength > rateBytes — provably false for every registered SHA-3 variant (32<136, 48<104, 64<72). Local ASAN debug run shows 35 tests / 126 expects pass. Kicking CI onto fresh agents.
Symmetric to the guard added in 5ddb4f6 on the squeeze side. sha3Init computes rateBytes = 200 - 2*outputBytes and stores it; sha3Update then memcpys up to rateBytes at a time into buffer[144]. If a future caller wires SHAKE128 (rateBytes = 168) through this path, absorption would silently overflow the buffer by 24 bytes on the stack before the sha3Final digest-length guard would ever fire. Add ASSERT(ctx.rateBytes <= sizeof(ctx.buffer)) at the end of sha3Init so both halves of the sponge have matching defensive checks. Still 35 tests / 126 expects pass — the assert is false for none of the three registered variants (SHA3-256: 136, SHA3-384: 104, SHA3-512: 72; all <= 144). Refs #29218
First slice of the WICG "Modern Algorithms in the Web Cryptography API" specification, as requested in #29218.
What
Adds two things to Web Crypto:
SHA3-256/SHA3-384/SHA3-512digests — available throughcrypto.subtle.digest. Output matches the FIPS 202 test vectors for the empty string and for "abc":SubtleCrypto.supports(operation, algorithm)— a synchronous feature detection method, defined by the WICG spec as a static on theSubtleCryptointerface object rather than an instance method. Returns a boolean. Never throws for well-formed argument counts; malformed input and unknown algorithms/operations returnfalse.KEM operations (
encapsulateKey/encapsulateBits/decapsulateKey/decapsulateBits) andgetPublicKeyreturnfalsefromsupports()for now, so callers can start shipping progressive-enhancement paths today. They will flip totruewhen those algorithms land in a follow-up PR.Why first
The WICG spec is large — ML-KEM, ML-DSA, ChaCha20-Poly1305, SHA-3, SHAKE, cSHAKE, TurboSHAKE, plus
supports()andgetPublicKey(). Landing it as one PR would be hard to review. SHA-3 andsupports()are a small, self-contained starting point: SHA-3 has no new method surface (it rides on the existingdigestoperation), andsupports()is what lets callers progressively adopt every later algorithm without feature-detecting by try/catch. Later PRs against #29218 will add the rest.How
BORINGSSL_keccak_*helpers only expose SHA3-256 and SHA3-512 — SHA3-384 is missing — so I added a small self-contained Keccak-f[1600] permutation and a fixed-output sponge toCryptoDigest.cpp. The same state machine can be extended with the XOF padding byte (0x1f) when SHAKE/cSHAKE/TurboSHAKE land.SHA3_{256,384,512}entries inCryptoAlgorithmIdentifier,CryptoAlgorithm{SHA3_256,SHA3_384,SHA3_512}.{h,cpp}that mirror the existing SHA-256 algorithm class, and registry wiring inCryptoAlgorithmRegistryOpenSSL.cpp.SubtleCrypto::supportsis installed as a static on theJSSubtleCryptoDOMConstructorininitializeProperties()(not on the prototype hashtable), with attribute0so its property descriptor is{Writable: true, Enumerable: true, Configurable: true}— matching WebIDL § 3.7.7 "define the operations" step 4. The C++ implementation is a static method that doesn't touch any instance state; the host function dispatches directly without going throughIDLOperation<JSSubtleCrypto>::call.supports()never reportstruefor something the underlying method would reject:wrapKey/unwrapKeyretry theEncrypt/Decryptfallback the real methods use;exportKeyruns the algorithm throughisSupportedExportKey()(not just theImportKeynormalization path);getPublicKeyand the KEM ops all returnfalseuntil their algorithms land.toHashIdentifier()returnsNotSupportedErrorfor SHA3 identifiers so those paths can't build brokenCryptoKeyinstances that would later crash inOpenSSLUtilities::digestAlgorithm,CryptoKeyHMAC::getKeyLengthFromHash, or the structured-clone tag table. SHA-3 as a top-leveldigeststill works. The gate lifts in a follow-up PR that wires the missing bits.CryptoAlgorithmIdentifier(inAsymmetricKeyValue.cppandSerializedScriptValue.cpp) get newcases for SHA3. Since noCryptoKeycan be built with a SHA3 algorithm today, the serialization path isASSERT_NOT_REACHEDfor now.test/regression/issue/29218.test.tsis added totest/no-validate-exceptions.txtunder the# normalizeCryptoAlgorithmParameterssection, matching every other webcrypto regression test, so the exception-swallowing paths insupports()don't tripBUN_JSC_validateExceptionChecks=1on ASAN CI.Tests
test/regression/issue/29218.test.tscovers:node:cryptoimportKey/generateKey, RSA-PSSgenerateKey, and ECDSAsign()(using a real P-256 key pair so the rejection lives in the sign-path normalization, which is where ECDSA's hash parameter is resolved)SubtleCrypto.supports()covers classic algorithms, SHA-3 digests, dictionary input, unknown algorithms, unknown operations, KEM operations,getPublicKeyon symmetric and asymmetric algorithms, wrap/unwrap fallback,exportKeyvs.isSupportedExportKey, malformed input, and the required-argument-count TypeErrorsupports() == truepair actually succeeds when the underlying method is called, and that everysupports() == falsepair actually rejects34 tests / 114
expect()calls, all passing.Refs #29218. This PR only lands the first slice; ML-KEM / ML-DSA / ChaCha20-Poly1305 / SHAKE / cSHAKE / TurboSHAKE /
getPublicKey()will follow in separate PRs against the same issue.