feat(shared-core): expose the crypto primitives through SharedCoreKit - #1390
Merged
Conversation
The forward copy already existed; the facades that return bytes need the reverse. Kotlin bytes are signed, so the tests pin the high-bit range that a plain numeric conversion would trap on.
FlipcashCore has to call these through Swift types before its native copies can go, so each gets a facade over the exported Kotlin. Three findings from standing them up: - The Kotlin marker object was called SharedCore, which shadows the Swift module called SharedCore, so no facade could qualify a Kotlin type whose name it reused. Renamed to SharedCoreBuild. - There was no one-shot SHA-512 in the shared Kotlin at all — Android's was a JVM MessageDigest extension in androidMain, which cannot cross. Added Sha512 alongside Sha256Hash, over the same kotlincrypto backend. - Ed25519Kmp's doc comment described the private key as seed || publicKey. It is the clamped SHA-512 expansion of the seed; the seed is not recoverable from it. The test pins the real layout so a caller reading the old comment finds out here rather than in a wallet. Base58 and ed25519 assert against the canonical cross-platform vectors; the hashes anchor on RFC 4231 and the BIP-39 seed vector rather than on whatever the Kotlin happens to return.
Nothing ran either suite. The `CI` workflow is Ubuntu, so it covers only the JVM side of commonTest — the Kotlin/Native actuals, including ed25519's cinterop over vendored C, went untested — and `publish-shared-core.yml` assembles the XCFramework without running the SwiftPM package against it. A separate workflow rather than a job in `CI`, because path filters attach to the trigger, not the job: `CI` has an unfiltered `pull_request` trigger, so a job added there would take a macOS runner on every PR. The filter here is the set of directories that can change the answer. The facade suite runs against the XCFramework assembled from this checkout via FLIPCASH_SHARED_CORE_LOCAL, not against the last published release, so a facade written for an unreleased Kotlin change is covered before the release rather than after it. The package is iOS-only and the framework has no host slice, so it goes through `xcodebuild test` on a simulator destination picked from whatever the runner image has.
`src/iosTest/TestResources.ios.kt` did not compile — `stringWithContentsOfFile` needs `@OptIn(ExperimentalForeignApi::class)`, and nothing had ever built that source set, so the error sat there unnoticed. Fixing the opt-in would only have moved the failure to runtime: the loader reads `NSBundle.mainBundle`, and a Kotlin/Native test binary ships no resource bundle, so `pathForResource` returns null for `ed25519.json`. `flipcash.kmp.test.fixtures` already solves this — it compiles `src/commonTest/resources` into a generated `TestFixtures.kt` on `commonTest`, which every target can read with no platform code. base58 and kikcode use it; ed25519 kept the hand-rolled expect/actual it predates. Applying it here deletes all four `TestResources` files, the dead instrumented-test actual included: the module declares only `withHostTest`, so nothing compiled that one either. `Ed25519VectorTest` now passes 4/4 on `iosSimulatorArm64Test` as well as on the JVM. Found by the macOS workflow in the previous commit, on its first run.
`xcodebuild test -quiet` prints nothing when it passes, so a run that executed no tests — a scheme that stopped including the test target, a filter that matched nothing — is indistinguishable from a run that passed all of them. That is the one failure mode a gate cannot have. Writes a result bundle and reads `totalTestCount` out of it, so the log carries the number and a zero fails the job. Verified against a real bundle: `17 tests: 17 passed, 0 failed, 0 skipped`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The primitives have been KMP and exported from the framework for a while, but the Swift facade only
covered kikcode, so iOS could not reach any of them — FlipcashCore still carries its own Base58,
CommonCrypto hashes, and CodeCurves wrapper. This adds facades for Base58, SHA-256/512, HMAC,
PBKDF2 and Ed25519, plus the
KotlinByteArray -> Datadirection the return values need.Base58 and Ed25519 assert against the canonical fixtures in
code/test-vectors/; the hashes anchoron RFC 4231 case 2 and the BIP-39 all-
abandonseed vector, so the tests say "correct" rather than"unchanged". 17 tests in 5 suites.
Three things came out of building this that are worth reading past the diff:
The Kotlin marker object shadowed the Swift module.
SharedCore.Base58.shared.encode(...)doesnot compile: Kotlin exported an object named
SharedCoreinto a framework namedSharedCore, andthe type wins Swift name lookup over the module. Renamed the object to
SharedCoreBuild. Any facadewhose natural Swift name collides with the Kotlin type it wraps needs that qualification, so this
had to be fixed rather than worked around.
There was no one-shot SHA-512 in the shared Kotlin. The only one was
ByteArray.sha512()inlibs/encryption/utilsandroidMain, a JVMMessageDigestextension that cannot cross to iOS.Added
Sha512tolibs/encryption/sha512/commonMainover the same kotlincrypto backendSha256Hashuses. Pointing the Android extension at it would remove the second implementation;left out of this PR on purpose.
Ed25519Kmp's documented key layout was wrong. Both doc comments saidprivateKey = 64 bytes (seed || public key, orlp convention). It is the clamped SHA-512 expansionof the seed, and the seed is not recoverable from it. Found by asserting the documented layout and
watching it fail while every RFC 8032 vector passed. Corrected the comments, and a test now pins the
real layout — the FlipcashCore swap depends on it, since anything reading the first 32 bytes back as
a seed gets the wrong bytes.
Nothing in this repo's CI ran either suite:
CIis Ubuntu, so it covered only the JVM half ofcommonTestand never the Kotlin/Native actuals, andpublish-shared-core.ymlassembles theXCFramework without testing the package against it.
.github/workflows/shared-core-tests.ymladdsthe macOS lane — the six modules'
iosSimulatorArm64Test, then the facade suite viaxcodebuild testagainst an XCFramework assembled from the checkout. It is a separate workflow because pathfilters attach to the trigger rather than the job, and
CI'spull_requesttrigger is unfiltered;this one runs only on
kmp/shared-core/**,libs/encryption/**,libs/codes/kikcode/**and theversion catalog.