// Java 17+; standard library only. Copy into your backend service. import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.time.Duration; import java.time.Instant; import java.util.Base64; import java.util.LinkedHashMap; import java.util.Map; public class InfiniClient { static PrivateKey loadKey(String pem) throws Exception { pem = pem.trim(); if (!pem.matches("-----BEGIN PRIVATE KEY-----[A-Za-z0-9+/=\\r\\n]+-----END PRIVATE KEY-----")) { throw new IllegalArgumentException("Expected one unencrypted PKCS#8 PEM private key"); } String encoded = pem.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "").replaceAll("\\s", ""); return KeyFactory.getInstance("Ed25519").generatePrivate( new PKCS8EncodedKeySpec(Base64.getDecoder().decode(encoded))); } static Map sign(PrivateKey key, String keyId, String method, String target, byte[] body, String timestamp, String nonce) throws Exception { String digest = "SHA-256=" + Base64.getEncoder().encodeToString( MessageDigest.getInstance("SHA-256").digest(body)); String payload = String.join("\n", "INFINI-OPENAPI-V2", keyId, method, target, timestamp, nonce, digest) + "\n"; Signature signer = Signature.getInstance("Ed25519"); signer.initSign(key); signer.update(payload.getBytes(StandardCharsets.UTF_8)); Map headers = new LinkedHashMap<>(); headers.put("X-API-Key-ID", keyId); headers.put("X-API-Timestamp", timestamp); headers.put("X-API-Nonce", nonce); headers.put("Digest", digest); headers.put("Authorization", "Ed25519 signature=\"" + Base64.getEncoder().encodeToString(signer.sign()) + "\""); headers.put("Content-Type", "application/json"); return headers; } private final URI origin; private final String keyId; private final PrivateKey key; private final HttpClient http; public InfiniClient(String origin, String keyId, String privateKeyPEM) throws Exception { this.origin = URI.create(origin.replaceAll("/+$", "")); if (!"https".equals(this.origin.getScheme()) || this.origin.getHost() == null || this.origin.getRawUserInfo() != null || this.origin.getRawQuery() != null || this.origin.getRawFragment() != null || !this.origin.getRawPath().isEmpty()) { throw new IllegalArgumentException("Expected an HTTPS API origin"); } if (!keyId.matches("[1-9A-HJ-NP-Za-km-z]{32}")) { throw new IllegalArgumentException("Expected a 32-character Base58 Key ID"); } this.keyId = keyId; this.key = loadKey(privateKeyPEM); this.http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(15)) .followRedirects(HttpClient.Redirect.NEVER).build(); } // Caller checks both HTTP status and the response business code. public HttpResponse request(String method, String target, byte[] body) throws Exception { method = method.toUpperCase(java.util.Locale.ROOT); if (!method.matches("[A-Z]+") || !target.startsWith("/v2/") || !target.matches("[!-~]+") || target.contains("#")) { throw new IllegalArgumentException("Invalid method or escaped /v2/ request target"); } URI uri = URI.create(origin.toString() + target); String requestTarget = uri.getRawPath() + (uri.getRawQuery() == null ? "" : "?" + uri.getRawQuery()); if (!requestTarget.equals(target)) throw new IllegalArgumentException("Target changed"); byte[] random = new byte[16]; new SecureRandom().nextBytes(random); String nonce = Base64.getUrlEncoder().withoutPadding().encodeToString(random); Map headers = sign(key, keyId, method, requestTarget, body, Long.toString(Instant.now().getEpochSecond()), nonce); HttpRequest.Builder request = HttpRequest.newBuilder(uri).timeout(Duration.ofSeconds(15)) .method(method, HttpRequest.BodyPublishers.ofByteArray(body)); headers.forEach(request::header); return http.send(request.build(), HttpResponse.BodyHandlers.ofString()); } }