Skip to content

Java / Kotlin

Java 17+ client SDK for [Quidnug](https://github.com/bhmortim/quidnug), a decentralized protocol for relational, per-observer trust.

full maven / gradle clients/java →

Quidnug Java SDK

Java 17+ client SDK for Quidnug, a decentralized protocol for relational, per-observer trust.

Covers the full v2 protocol surface (QDPs 0001–0010): identity, trust, titles, event streams, anchors, guardian sets + recovery, cross-domain gossip, K-of-K bootstrap, fork-block activation, and compact Merkle inclusion proofs.

Install

Maven:

<dependency>
<groupId>com.quidnug</groupId>
<artifactId>quidnug-client</artifactId>
<version>2.0.0</version>
</dependency>

Gradle (Kotlin DSL):

dependencies {
implementation("com.quidnug:quidnug-client:2.0.0")
}

Requires Java 17+. Works on Kotlin, Scala, Clojure, and any JVM language via the same Maven coordinate.

Thirty-second example

import com.quidnug.client.Quid;
import com.quidnug.client.QuidnugClient;
import com.quidnug.client.QuidnugClient.IdentityParams;
import com.quidnug.client.QuidnugClient.TrustParams;
import com.quidnug.client.Types.TrustResult;
QuidnugClient client = QuidnugClient.builder()
.baseUrl("http://localhost:8080")
.build();
Quid alice = Quid.generate();
Quid bob = Quid.generate();
client.registerIdentity(alice, IdentityParams.name("Alice").homeDomain("contractors.home"));
client.registerIdentity(bob, IdentityParams.name("Bob").homeDomain("contractors.home"));
client.grantTrust(alice, TrustParams.of(bob.id(), 0.9, "contractors.home"));
TrustResult tr = client.getTrust(alice.id(), bob.id(), "contractors.home", 5);
System.out.printf("%.3f via %s%n", tr.trustLevel, String.join(" -> ", tr.path));

Runnable examples under examples/:

FileWhat it shows
Quickstart.javaTwo-party trust + relational trust query.
EnterpriseOnboarding.javaVendor KYC → threshold-gated PO release.
AuditStream.javaEmit events onto a service quid; read them back.

What ships in the SDK

Quid, ECDSA P-256 identity

Quid alice = Quid.generate(); // fresh keypair
Quid bob = Quid.fromPrivateHex(storedHex); // reconstruct
Quid carol = Quid.fromPublicHex(networkPubHex); // read-only
String sig = alice.sign(data);
boolean ok = alice.verify(data, sig);

P-256 + SHA-256, DER-encoded hex signatures. The quid ID is sha256(publicKey)[0..8], byte-for-byte compatible with Go, Python, JavaScript, and Rust SDKs. A signature produced by one SDK verifies against any other.

QuidnugClient, HTTP surface

Thread-safe, builder-constructed. Every endpoint has a typed method.

AreaMethods
Healthhealth, info, nodes, blocks, pendingTransactions, listDomains
IdentityregisterIdentity, getIdentity
TrustgrantTrust, getTrust, getTrustEdges
TitleregisterTitle, getTitle
EventsemitEvent, getEventStream, getStreamEvents
Guardians (QDP-0002)submitGuardianSetUpdate, submitRecoveryInit/Veto/Commit, submitGuardianResignation, getGuardianSet, getPendingRecovery
Gossip (QDP-0003/5)submitDomainFingerprint, getLatestDomainFingerprint, submitAnchorGossip, pushAnchor, pushFingerprint
Bootstrap (QDP-0008)submitNonceSnapshot, getLatestNonceSnapshot, bootstrapStatus
Fork-block (QDP-0009)submitForkBlock, forkBlockStatus

CanonicalBytes, signable-bytes encoder

byte[] signable = CanonicalBytes.of(tx, "signature", "txId");
String sig = signer.sign(signable);

Matches the Go / Python / JS / Rust canonicalization byte-for-byte (sorted keys, nested recursively, named top-level fields excluded). See schemas/types/canonicalization.md.

Merkle.verifyInclusionProof, QDP-0010

List<Merkle.Frame> frames = List.of(
new Merkle.Frame("aabb..", "right"),
new Merkle.Frame("ccdd..", "left")
);
boolean ok = Merkle.verifyInclusionProof(canonicalTxBytes, frames, rootHex);

Error handling

try {
client.grantTrust(alice, TrustParams.of(bob.id(), 0.9, "contractors.home"));
} catch (QuidnugException.ConflictException e) {
// Nonce replay, quorum not met, guardian-set-hash mismatch, etc.
} catch (QuidnugException.UnavailableException e) {
// 503 / feature not yet active / bootstrapping
} catch (QuidnugException.NodeException e) {
// Transport / unexpected 5xx
System.err.println("HTTP " + e.statusCode());
} catch (QuidnugException.ValidationException e) {
// Local precondition failed
}

All exceptions inherit from QuidnugException. Catch that for a blanket handler.

Retry policy

  • GETs retry up to maxRetries times (default 3) on 5xx and 429. Exponential backoff + ±100 ms jitter, capped at 60s. Respects Retry-After.
  • POSTs are not retried. Reconcile with a follow-up GET before replaying a write, Quidnug transactions are not all idempotent.
QuidnugClient client = QuidnugClient.builder()
.baseUrl("https://node.example.com")
.timeout(Duration.ofSeconds(60))
.maxRetries(5)
.retryBaseDelay(Duration.ofMillis(500))
.authToken(System.getenv("QUIDNUG_TOKEN"))
.userAgent("my-app/1.0")
.build();

Build

Terminal window
cd clients/java
# Maven
mvn test
mvn package
# Gradle (alternative)
./gradlew test
./gradlew build

Verifying tests

The SDK ships 20 unit tests covering keypair generation, signing, canonicalization, Merkle proof verification, HTTP envelope parsing, error taxonomy, and retry behavior:

Tests run: 20, Failures: 0, Errors: 0, Skipped: 0
CanonicalBytesTest: 3/3
MerkleTest: 5/5
QuidnugClientTest: 7/7
QuidTest: 5/5

The client tests use the built-in jdk.httpserver to stub responses no WireMock or MockWebServer dependency.

Java-specific patterns

Spring Boot wiring

@Configuration
public class QuidnugConfig {
@Bean
public QuidnugClient quidnugClient(@Value("${quidnug.node}") String nodeUrl,
@Value("${quidnug.token:}") String token) {
QuidnugClient.Builder b = QuidnugClient.builder().baseUrl(nodeUrl);
if (!token.isEmpty()) b.authToken(token);
return b.build();
}
}

Kotlin callers

val client = QuidnugClient.builder().baseUrl("http://localhost:8080").build()
val alice = Quid.generate()
val bob = Quid.generate()
client.grantTrust(alice, QuidnugClient.TrustParams.of(bob.id(), 0.9, "demo.home"))

Using an HSM for signing

Because the Quid API takes a PrivateKey, you can substitute a JCA-backed HSM-held key (e.g. via PKCS11 provider or AWS CloudHSM provider) anywhere a quid would be used. For a ready-made abstraction, see the Go pkg/signer/hsm/ package, the JVM equivalent lives on the roadmap.

Protocol version compatibility

SDKNodeQDPs
2.x2.x0001–0010

Contributing

PRs welcome. Run mvn test before submitting. See the Python SDK (clients/python/) for the canonical reference implementation.

License

Apache-2.0.