Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
164c884
scaffold, crypto lib, wallet
Bushstar Jan 8, 2026
74c379b
make mutateWallet thread safe
Bushstar Jan 8, 2026
86c0575
fix deprecation warning
Bushstar Jan 8, 2026
7618bba
add minimal gatekeeperclient
Bushstar Jan 8, 2026
706da4a
complete types
Bushstar Jan 8, 2026
41c0296
operation signing utils
Bushstar Jan 8, 2026
2e68bc4
add minimal keymaster functions
Bushstar Jan 9, 2026
6db59fa
add asset related calls
Bushstar Jan 9, 2026
a88ec9a
add schema calls
Bushstar Jan 9, 2026
bf41895
schema and credential issuance
Bushstar Jan 9, 2026
d9c8fc9
credential calls
Bushstar Jan 9, 2026
c83c3de
create and integrate CID lib
Bushstar Jan 9, 2026
b0f9985
keymaster wallet tests
Bushstar Jan 9, 2026
513bec2
add READMEs
Bushstar Jan 9, 2026
e22287c
create wallet in mutate if none present
Bushstar Jan 9, 2026
69562df
create testutil
Bushstar Jan 12, 2026
c750f8b
add KeymasterTestSupport
Bushstar Jan 12, 2026
4d9988c
add credential tests
Bushstar Jan 12, 2026
015a2c7
add credential test vector
Bushstar Jan 12, 2026
6640c8f
add live credential tests
Bushstar Jan 12, 2026
e8f4df9
add remaining tests
Bushstar Jan 12, 2026
ac78198
jdk 11
Bushstar Jan 12, 2026
abef7ac
add missing id calls. add id tests.
Bushstar Jan 12, 2026
d1ec35f
use local registry in live credential tests
Bushstar Jan 12, 2026
2055c8a
fix tests. add more keymaster calls
Bushstar Jan 12, 2026
1e72708
add remaining calls. remove notices
Bushstar Jan 13, 2026
3a9b342
use live tests only
Bushstar Jan 13, 2026
ff86a1c
live tests
Bushstar Jan 13, 2026
56743e9
resolve warnings / remove unused code
Bushstar Jan 14, 2026
dcf657a
complete gatekeeper interface and client implementation
Bushstar Jan 14, 2026
13afd4c
add groups / resolve keymaster errors
Bushstar Jan 15, 2026
a2d649e
demo
Bushstar Jan 22, 2026
6903b79
demo set default registry set to hyperswarm
Bushstar Jan 23, 2026
4e05025
add registry option to issue credential
Bushstar Jan 23, 2026
00c7fea
extend encryptMessage/encryptJSON options
Bushstar Jan 23, 2026
2f62d53
additional call arguments
Bushstar Jan 23, 2026
4ed88c6
Set didDocsArea in local function
Bushstar Jan 23, 2026
2615c3d
reset forms on change user
Bushstar Jan 23, 2026
4996759
do not display fields to the user
Bushstar Jan 23, 2026
0a8df58
better revoked credential handling
Bushstar Jan 23, 2026
16c6411
add missing cancel buttons
Bushstar Jan 23, 2026
ee31750
exclude java directory from eslint
Bushstar Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
**/data/
**/build/
**/dist/

java/
19 changes: 19 additions & 0 deletions java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.gradle/
/build/
**/build/
**/out/

# IDEs
.idea/
*.iml
*.ipr
*.iws
.vscode/

# OS
.DS_Store
Thumbs.db

# Logs
*.log
.gradle-user/
74 changes: 74 additions & 0 deletions java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Java Libraries

This folder contains the Java implementations of Keymaster and the Gatekeeper REST client.

Modules:
- `cid` — minimal CID validation.
- `crypto` — crypto primitives.
- `gatekeeper` — Gatekeeper REST client.
- `keymaster` — wallet + credential operations.

Compatibility and runtime
- JDK 17 is required (targeted bytecode and test runtime).
- Dependencies are pure-Java artifacts (OkHttp, Jackson, Bouncy Castle, Tink, bitcoinj).
- Gatekeeper default base URL is `http://localhost:4224` (see `GatekeeperClientOptions`).

Quickstart (Gradle)
```gradle
dependencies {
implementation("org.keychain:cid:<version>")
implementation("org.keychain:crypto:<version>")
implementation("org.keychain:gatekeeper:<version>")
implementation("org.keychain:keymaster:<version>")
}
```

Quickstart (Java)
```java
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.keychain.gatekeeper.GatekeeperClient;
import org.keychain.gatekeeper.GatekeeperClientOptions;
import org.keychain.keymaster.Keymaster;
import org.keychain.keymaster.model.WalletEncFile;
import org.keychain.keymaster.store.WalletJson;
import org.keychain.keymaster.store.WalletStore;

Path dataDir = Paths.get(System.getProperty("user.home"), ".keymaster");
WalletStore<WalletEncFile> store = new WalletJson<>(WalletEncFile.class, dataDir, "wallet.json");

GatekeeperClientOptions options = new GatekeeperClientOptions();
options.baseUrl = "http://localhost:4224";
GatekeeperClient gatekeeper = new GatekeeperClient(options);

Keymaster keymaster = new Keymaster(store, gatekeeper, "passphrase");

String aliceDid = keymaster.createId("Alice", "hyperswarm");
System.out.println("DID: " + aliceDid);

Map<String, Object> emailSchema = new HashMap<>();
emailSchema.put("$schema", "http://json-schema.org/draft-07/schema#");
Map<String, Object> properties = new HashMap<>();
Map<String, Object> email = new HashMap<>();
email.put("format", "email");
email.put("type", "string");
properties.put("email", email);
emailSchema.put("properties", properties);
emailSchema.put("required", List.of("email"));
emailSchema.put("type", "object");

String schemaDid = keymaster.createSchema(emailSchema, "hyperswarm");
Map<String, Object> bound = keymaster.bindCredential(schemaDid, aliceDid);
String credentialDid = keymaster.issueCredential(bound);

keymaster.publishCredential(credentialDid, true);
```

See each module README for usage details:
- `java/cid/README.md`
- `java/crypto/README.md`
- `java/gatekeeper/README.md`
- `java/keymaster/README.md`
31 changes: 31 additions & 0 deletions java/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
allprojects {
repositories {
mavenCentral()
maven { url = 'https://jitpack.io' }
}
}

subprojects {
apply plugin: 'java-library'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}

tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
options.release = 11
}

tasks.withType(Test).configureEach {
useJUnitPlatform()
}

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2'
}
}
15 changes: 15 additions & 0 deletions java/cid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# CID (Java)

Minimal CID validator used by Keymaster for DID checks.

## Usage

```java
import org.keychain.cid.Cid;

boolean ok = Cid.isValid("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
```

Notes:
- Supports CIDv0 (base58btc) and CIDv1 (base32 lower-case).
- Intended for validation only (no full CID object model yet).
2 changes: 2 additions & 0 deletions java/cid/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dependencies {
}
56 changes: 56 additions & 0 deletions java/cid/src/main/java/org/keychain/cid/Base32Lower.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.keychain.cid;

import java.io.ByteArrayOutputStream;
import java.util.Arrays;

public final class Base32Lower {
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
private static final int[] INDEXES = new int[128];

static {
Arrays.fill(INDEXES, -1);
for (int i = 0; i < ALPHABET.length(); i += 1) {
char c = ALPHABET.charAt(i);
INDEXES[c] = i;
INDEXES[Character.toUpperCase(c)] = i;
}
}

private Base32Lower() {
}

public static byte[] decode(String input) {
if (input == null || input.isEmpty()) {
throw new IllegalArgumentException("input");
}

ByteArrayOutputStream out = new ByteArrayOutputStream();
int buffer = 0;
int bitsLeft = 0;

for (int i = 0; i < input.length(); i += 1) {
char c = input.charAt(i);
if (c == '=') {
continue;
}
if (c >= 128 || INDEXES[c] < 0) {
throw new IllegalArgumentException("input");
}

buffer = (buffer << 5) | INDEXES[c];
bitsLeft += 5;

while (bitsLeft >= 8) {
bitsLeft -= 8;
out.write((buffer >> bitsLeft) & 0xFF);
if (bitsLeft > 0) {
buffer &= (1 << bitsLeft) - 1;
} else {
buffer = 0;
}
}
}

return out.toByteArray();
}
}
68 changes: 68 additions & 0 deletions java/cid/src/main/java/org/keychain/cid/Base58Btc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.keychain.cid;

import java.util.Arrays;

public final class Base58Btc {
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
private static final int[] INDEXES = new int[128];

static {
Arrays.fill(INDEXES, -1);
for (int i = 0; i < ALPHABET.length(); i += 1) {
INDEXES[ALPHABET.charAt(i)] = i;
}
}

private Base58Btc() {
}

public static byte[] decode(String input) {
if (input == null || input.isEmpty()) {
throw new IllegalArgumentException("input");
}

int zeros = 0;
while (zeros < input.length() && input.charAt(zeros) == '1') {
zeros += 1;
}

byte[] input58 = new byte[input.length()];
for (int i = 0; i < input.length(); i += 1) {
char c = input.charAt(i);
if (c >= 128 || INDEXES[c] < 0) {
throw new IllegalArgumentException("input");
}
input58[i] = (byte) INDEXES[c];
}

byte[] decoded = new byte[input.length()];
int outputStart = decoded.length;
int inputStart = zeros;
while (inputStart < input58.length) {
int mod = divmod256(input58, inputStart);
if (input58[inputStart] == 0) {
inputStart += 1;
}
decoded[--outputStart] = (byte) mod;
}

while (outputStart < decoded.length && decoded[outputStart] == 0) {
outputStart += 1;
}

byte[] output = new byte[zeros + (decoded.length - outputStart)];
System.arraycopy(decoded, outputStart, output, zeros, decoded.length - outputStart);
return output;
}

private static int divmod256(byte[] number, int startAt) {
int remainder = 0;
for (int i = startAt; i < number.length; i += 1) {
int digit = number[i] & 0xFF;
int temp = remainder * 58 + digit;
number[i] = (byte) (temp / 256);
remainder = temp % 256;
}
return remainder;
}
}
64 changes: 64 additions & 0 deletions java/cid/src/main/java/org/keychain/cid/Cid.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.keychain.cid;

public final class Cid {
private Cid() {
}

public static boolean isValid(String cid) {
if (cid == null || cid.isBlank()) {
return false;
}

try {
if (cid.startsWith("Q")) {
return validateV0(cid);
}
return validateV1(cid);
} catch (IllegalArgumentException e) {
return false;
}
}

private static boolean validateV0(String cid) {
byte[] bytes = Base58Btc.decode(cid);
if (bytes.length != 34) {
return false;
}
return (bytes[0] == 0x12) && (bytes[1] == 0x20);
}

private static boolean validateV1(String cid) {
byte[] bytes = Multibase.decode(cid);
if (bytes.length < 4) {
return false;
}

Varint.Decoded version = Varint.decodeUnsigned(bytes, 0);
if (version.value != 1) {
return false;
}

int offset = version.length;
Varint.Decoded codec = Varint.decodeUnsigned(bytes, offset);
offset += codec.length;
if (offset >= bytes.length) {
return false;
}

Varint.Decoded multihashCode = Varint.decodeUnsigned(bytes, offset);
offset += multihashCode.length;
if (offset >= bytes.length) {
return false;
}

Varint.Decoded digestLength = Varint.decodeUnsigned(bytes, offset);
offset += digestLength.length;

if (digestLength.value < 0 || digestLength.value > Integer.MAX_VALUE) {
return false;
}

int remaining = bytes.length - offset;
return remaining == (int) digestLength.value;
}
}
24 changes: 24 additions & 0 deletions java/cid/src/main/java/org/keychain/cid/Multibase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.keychain.cid;

public final class Multibase {
private Multibase() {
}

public static byte[] decode(String value) {
if (value == null || value.length() < 2) {
throw new IllegalArgumentException("value");
}

char prefix = value.charAt(0);
String payload = value.substring(1);

if (prefix == 'z') {
return Base58Btc.decode(payload);
}
if (prefix == 'b') {
return Base32Lower.decode(payload);
}

throw new IllegalArgumentException("value");
}
}
Loading
Loading