| conformance | init | |
| gradle/wrapper | Add JitPack publishing config, Gradle wrapper, and install docs | |
| src | fix epoch bug | |
| .gitignore | Add JitPack publishing config, Gradle wrapper, and install docs | |
| build.gradle | Add JitPack publishing config, Gradle wrapper, and install docs | |
| gradlew | Add JitPack publishing config, Gradle wrapper, and install docs | |
| gradlew.bat | Add JitPack publishing config, Gradle wrapper, and install docs | |
| jitpack.yml | Add JitPack publishing config, Gradle wrapper, and install docs | |
| LICENSE | update license | |
| README.md | Pin README JitPack version to v0.0.1 | |
| settings.gradle | init | |
unimo-sdk-java
Java/Android port of the TypeScript E2EE client SDK (sdk/ts). Post-quantum (ML-DSA-87 +
ML-KEM-1024), cSHAKE256-derived identities, AES-256-GCM content encryption — wire-compatible
with the gateway and the TS SDK.
Status
| Phase | Scope | State |
|---|---|---|
| 0 | Scaffold + cross-language conformance harness | ✅ done |
| 1 | crypto + shared (byte-exact interop foundation) |
✅ done |
| 2 | Identity: Members, Validators, register/login, deriveKeys unlock, ApiClient (OkHttp) |
✅ done — live-verified vs gateway (register→login→unlock) |
| 3 | VaultController: manifest CAS PUT/GET, member add/remove + key rotation, reauth |
✅ done — live-verified (addMember+rotation, new-member login, reauth, removeMember) |
| 4 | Tasker: chunked storage upload/download + CAS |
✅ done — live-verified (single + multi-chunk 8.4 MiB round-trips, CAS bump) |
| 5 | Collections/KV + ReactiveValue observable (dependency-free) |
✅ done — live-verified (create/persist/reload + survives key rotation) |
| 6a | Billing + Invites (manager-area read/write) | ✅ done — live-verified (create→claim→finalize→login; billing catalog/state) |
| 6b | WebSocket Connection (vault:event push) + minimal Account |
✅ done — live-verified (real blob_put push received; Account upload/download) |
Feature-complete vs the TypeScript SDK (the only TS gaps are also stubs there: VFS/LIST/CRDTLIST collection types, and the Tasker reactive task-queue/progress UI convenience).
Live coverage: 31/31 in IntegrationRunner (register/login/unlock · member add/remove + key rotation · reauth · single + multi-chunk encrypted storage + CAS · KV collection create/reload/rotate · billing catalog/state · full invite create→claim→finalize→login · WebSocket vault:event push · Account wrapper). Offline cross-language conformance: 45/45.
UI binding: ReactiveValue<T> is a neutral, dependency-free observable (get/set/update/subscribe/onChange). Adapt at the UI edge — MutableLiveData (rv.subscribe(ld::postValue)) for Views/Java, or MutableStateFlow (rv.subscribe { flow.value = it }, read via collectAsState()) for Compose. The core stays Android-free and pure-JVM-testable.
Dependencies (two)
org.bouncycastle:bcprov-jdk18on:1.81— ML-DSA-87, ML-KEM-1024, cSHAKE256. Used via low-level APIs (no JCA provider registration) to avoid clashing with Android's bundledcom.android.org.bouncycastle.com.squareup.okhttp3:okhttp:4.12.0— HTTP + WebSocket (Phase 2+).
Everything else is core JVM: AES-GCM/SHA-256 (javax.crypto/java.security), Base64/Hex
(BouncyCastle encoders), JSON (hand-rolled shared/Json — canonical writer + parser),
CompletableFuture for the async network surface.
Why not native PQC?
javax.crypto.KEM (JDK 21) is only a generic API; ML-KEM/ML-DSA implementations are
JDK 24+ (SunJCE) and Android 17 (Keystore) only. Neither works here: cSHAKE256 has no
native implementation in any JDK/Android provider, and the SDK requires seed-derived,
exportable keypairs (recovery devices re-derive keys from a seed) — the opposite of
Android Keystore's non-exportable, hardware-bound keys. BouncyCastle is the only fit.
Layout (mirrors sdk/ts/src_ts)
src/main/java/com/unimo/sdk/
├── crypto/ CryptoPQ (ML-DSA/ML-KEM), AEAD (AES-GCM), CryptoUtils (sha256, cSHAKE256,
│ derivations), SeedRandom (deterministic keygen), CodedException
├── shared/ Consts, Helpers (hex/base64/concat/u32be/chunkId), Json (canonical + parse)
└── client/ (Phase 2+)
conformance/ ConformanceRunner + vectors.json (golden oracle from the TS SDK)
Install (JitPack)
Published from this Codeberg repo via JitPack. Add the JitPack repository, then the dependency — BouncyCastle and OkHttp come in transitively.
Gradle
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'org.codeberg.thinking_tools:unimo-sdk-java:v0.0.1'
}
Maven
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>org.codeberg.thinking_tools</groupId>
<artifactId>unimo-sdk-java</artifactId>
<version>v0.0.1</version>
</dependency>
Any pushed tag, main-SNAPSHOT (latest commit), or a commit hash works as the version.
Build & test
# Gradle (consumer path)
gradle test
# No-Gradle path (what CI/dev can run with just a JDK): fetch the 4 compile jars into libs/
M=https://repo1.maven.org/maven2
curl -sLo libs/bcprov-jdk18on-1.81.jar $M/org/bouncycastle/bcprov-jdk18on/1.81/bcprov-jdk18on-1.81.jar
curl -sLo libs/okhttp-4.12.0.jar $M/com/squareup/okhttp3/okhttp/4.12.0/okhttp-4.12.0.jar
curl -sLo libs/okio-jvm-3.6.0.jar $M/com/squareup/okio/okio-jvm/3.6.0/okio-jvm-3.6.0.jar
curl -sLo libs/kotlin-stdlib-1.9.10.jar $M/org/jetbrains/kotlin/kotlin-stdlib/1.9.10/kotlin-stdlib-1.9.10.jar
CP=$(printf 'libs/%s:' bcprov-jdk18on-1.81.jar okhttp-4.12.0.jar okio-jvm-3.6.0.jar kotlin-stdlib-1.9.10.jar)
javac -cp "$CP" -d out $(find src/main/java -name '*.java') conformance/ConformanceRunner.java conformance/IntegrationRunner.java
java -cp "out:$CP" conformance.ConformanceRunner conformance/vectors.json
# Live end-to-end test (needs the `bun run dev:local` gateway up on :3000):
java -cp "out:$CP" conformance.IntegrationRunner http://localhost:3000
Conformance workflow
conformance/vectors.json is generated from the real noble/WebCrypto crypto:
bun run sdk/ts/tools/gen-conformance.ts # regenerate after any TS crypto change
The Java port must reproduce every vector byte-for-byte (keygen-from-seed public keys, cSHAKE outputs, canonical JSON, AEAD wire bytes, cross-language sign/verify + decapsulate).
Interop contract (verified)
- ML-DSA-87: FIPS-204 pure, empty context (
0x00‖0x00‖M), hedged signing. Sign the raw 32-byte SHA-256 digest of the canonical JSON;payloadHash = base64(digest). - ML-KEM-1024: FIPS-203; 64-byte seed =
d‖z. - cSHAKE256:
N="",S=personalization(SP 800-185). - AEAD: AES-256-GCM,
[12B IV][ciphertext][16B tag]. - Canonical JSON: recursive key sort + compact
JSON.stringify.