Compare commits
8
Commits
20e462ec03
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8443e9a321 | ||
|
|
381c8f1b9a | ||
|
|
6714353f8c | ||
|
|
45c6f9c730 | ||
|
|
7cd299d250 | ||
|
|
9287cbb4b7 | ||
|
|
64c592285a | ||
|
|
954d8760ac |
@@ -30,7 +30,7 @@ does not have floats, and they do not exist at all in the Sophia language.
|
||||
|
||||
## Terms
|
||||
|
||||
Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
Project: Gajumaru Core Java Libraries
|
||||
|
||||
This program is dual-licensed:
|
||||
@@ -38,10 +38,10 @@ This program is dual-licensed:
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version (AGPL-3.0-or-later). (See LICENSE file.)
|
||||
|
||||
2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
If you wish to use this software outside the strict constraints of the
|
||||
AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
purchase a commercial license from QPQ AG.
|
||||
purchase a commercial license from QPQ IaaS AG.
|
||||
|
||||
Authors:
|
||||
- Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
# Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
|
||||
set -e
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
# Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
|
||||
set -e
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -21,7 +21,6 @@
|
||||
package swiss.qpq.gajumaru.core.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
import swiss.qpq.gajumaru.core.tools.CryptoUtils;
|
||||
|
||||
|
||||
// Pure Java implementation of BLAKE2b.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -228,6 +228,20 @@ public final class Ed25519 {
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a message using a 64-byte secret key (seed | public_key).
|
||||
* This matches the libsodium behavior.
|
||||
*/
|
||||
public static byte[] signWithExpandedKey(byte[] message, byte[] sk64) {
|
||||
byte[] seed = new byte[32];
|
||||
System.arraycopy(sk64, 0, seed, 0, 32);
|
||||
try {
|
||||
return sign(seed, message);
|
||||
} finally {
|
||||
CryptoUtils.wipe(seed);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// Internal curve and field arithmetic machinery.
|
||||
// These are public only for parity testing against reference implementations.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -29,7 +29,6 @@ import swiss.qpq.gajumaru.core.tools.CryptoUtils;
|
||||
|
||||
public final class Keccak256 {
|
||||
|
||||
private static final int LANE_SIZE = 64;
|
||||
private static final int STATE_SIZE = 25;
|
||||
private static final int CAPACITY = 512;
|
||||
private static final int BITRATE = 1600 - CAPACITY; // 1088 bits = 136 bytes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
package swiss.qpq.gajumaru.core.crypto;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
@@ -32,54 +33,58 @@ public final class Vault {
|
||||
|
||||
private static final String AES_GCM = "AES/GCM/NoPadding";
|
||||
private static final int GCM_TAG_LENGTH = 128; // Bits
|
||||
private static final int IV_LENGTH = 12; // Bytes
|
||||
|
||||
private Vault() {}
|
||||
|
||||
/**
|
||||
* Holds the results of an encryption operation.
|
||||
*/
|
||||
public static final class Ciphertext {
|
||||
private final byte[] iv;
|
||||
private final byte[] data;
|
||||
|
||||
public Ciphertext(byte[] iv, byte[] data) {
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public byte[] getIv() { return iv; }
|
||||
public byte[] getData() { return data; }
|
||||
}
|
||||
public record Ciphertext(byte[] iv, byte[] data) {}
|
||||
|
||||
/**
|
||||
* Encrypts plaintext using AES/GCM.
|
||||
* Letting the provider generate the IV (required by Android Keystore).
|
||||
* Manually generates the IV to ensure deterministic tag length and cross-provider consistency.
|
||||
*/
|
||||
public static Ciphertext encrypt(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] iv = cipher.getIV();
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
return new Ciphertext(iv, ciphertext);
|
||||
try {
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
|
||||
byte[] ciphertext = encrypt(key, iv, plaintext);
|
||||
return new Ciphertext(iv, ciphertext);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Vault.encrypt failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts plaintext using AES/GCM with a specific IV.
|
||||
*/
|
||||
public static byte[] encrypt(SecretKey key, byte[] iv, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(plaintext);
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(plaintext);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Vault.encrypt(iv) failed: " + e.getClass().getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts ciphertext using AES/GCM.
|
||||
*/
|
||||
public static byte[] decrypt(SecretKey key, byte[] iv, byte[] ciphertext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(ciphertext);
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(ciphertext);
|
||||
} catch (Exception e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) msg = e.toString();
|
||||
throw new Exception("Vault.decrypt failed [" + e.getClass().getSimpleName() + "]: " + msg, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -99,7 +99,7 @@ public final class Mnemonic {
|
||||
"broken", "bronze", "broom", "browse", "bruise", "brunt", "brute", "bubble", "buckle", "buddy",
|
||||
"budget", "buffet", "builder", "bulb", "bulge", "bulimia", "bulky", "bulletin", "bummed", "bumpy",
|
||||
"bundle", "bungalow", "bunion", "bunker", "buoyant", "burden", "bureau", "burger", "burial",
|
||||
"burlap", "burnout", "blurp", "burrito", "burst", "bushel", "business", "bustle", "busy", "butane",
|
||||
"burlap", "burnout", "burp", "burrito", "burst", "bushel", "business", "bustle", "busy", "butane",
|
||||
"butcher", "butler", "button", "buxom", "buyer", "buyout", "buzz", "bylaw", "bypass", "cabaret",
|
||||
"cabbage", "cabin", "cactus", "cadaver", "cadet", "caffeine", "caftan", "cage", "cairn", "cajole",
|
||||
"calamity", "calcium", "calendar", "calico", "callous", "calm", "calorie", "calypso", "camera",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -29,7 +29,7 @@ import java.util.Map;
|
||||
|
||||
// GRIDS URL handling, ported from hz_grids.erl.
|
||||
|
||||
public final class Grids {
|
||||
public final class Grids {
|
||||
|
||||
private Grids() {}
|
||||
|
||||
@@ -93,21 +93,23 @@ public final class Grids {
|
||||
if (path.startsWith("/1/s/")) {
|
||||
String recipient = path.substring(5);
|
||||
Map<String, String> qargs = parseQuery(query);
|
||||
BigInteger amount = new BigInteger(qargs.getOrDefault("a", "0"));
|
||||
String a = qargs.get("a");
|
||||
BigInteger amount = (a == null || a.isEmpty()) ? BigInteger.ZERO : new BigInteger(a);
|
||||
byte[] payload = decodePayload(qargs.getOrDefault("p", ""));
|
||||
return ParseResult.spend(Context.CHAIN, host, recipient, amount, payload);
|
||||
} else if (path.startsWith("/1/t/")) {
|
||||
String recipient = path.substring(5);
|
||||
Map<String, String> qargs = parseQuery(query);
|
||||
BigInteger amount = new BigInteger(qargs.getOrDefault("a", "0"));
|
||||
String a = qargs.get("a");
|
||||
BigInteger amount = (a == null || a.isEmpty()) ? BigInteger.ZERO : new BigInteger(a);
|
||||
byte[] payload = decodePayload(qargs.getOrDefault("p", ""));
|
||||
String location = host + (uri.getPort() != -1 ? ":" + uri.getPort() : "");
|
||||
return ParseResult.transfer(Context.NODE, location, recipient, amount, payload);
|
||||
} else if (path.startsWith("/1/d/")) {
|
||||
String subPath = path.substring(5);
|
||||
String subPath = path.substring(4); // Includes the leading slash
|
||||
String httpScheme = "grids".equals(scheme) ? "https" : "http";
|
||||
String baseUrl = httpScheme + "://" + host + (uri.getPort() != -1 ? ":" + uri.getPort() : "") + subPath;
|
||||
String httpUrl = query != null ? baseUrl + "?" + query : baseUrl;
|
||||
String hostPart = host + (uri.getPort() != -1 ? ":" + uri.getPort() : "");
|
||||
String httpUrl = httpScheme + "://" + hostPart + subPath + (query != null ? "?" + query : "");
|
||||
return ParseResult.sign("grids".equals(scheme) ? Context.HTTPS : Context.HTTP, httpUrl);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown verb in path: " + path);
|
||||
@@ -118,8 +120,8 @@ public final class Grids {
|
||||
}
|
||||
|
||||
public static final String REQ_TYPE_MESSAGE = "message";
|
||||
public static final String REQ_TYPE_BINARY = "binary";
|
||||
public static final String REQ_TYPE_TX = "tx";
|
||||
public static final String REQ_TYPE_ACK = "ack";
|
||||
|
||||
public static Map<String, Object> makeRequest(String type, Object payload, String publicId, String networkId) {
|
||||
Map<String, Object> req = new LinkedHashMap<>();
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
* 1) Under the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.tools;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import swiss.qpq.gajumaru.core.encoding.ZJ;
|
||||
|
||||
// Minimal HTTP client for GRIDS and Node interaction.
|
||||
|
||||
public final class Http {
|
||||
|
||||
private Http() {}
|
||||
|
||||
public static Object get(String url) throws Exception {
|
||||
return request(url, "GET", null);
|
||||
}
|
||||
|
||||
public static Object post(String url, Object body) throws Exception {
|
||||
return request(url, "POST", body);
|
||||
}
|
||||
|
||||
private static Object request(String urlStr, String method, Object body) throws Exception {
|
||||
URL url = new java.net.URI(urlStr).toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(15000);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
|
||||
// Emulate a standard browser agent to avoid being blocked by strict servers
|
||||
conn.setRequestProperty("Accept", "application/json, text/plain, */*");
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 10) GajuMobile/1.0");
|
||||
|
||||
if (body != null) {
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input;
|
||||
if (body instanceof String) {
|
||||
input = ((String) body).getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
input = ZJ.encode(body).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
try (java.io.InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
response.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
String responseStr = response.toString().trim();
|
||||
|
||||
if (code < 200 || code >= 300) {
|
||||
String reason = "HTTP " + code;
|
||||
try {
|
||||
Object decoded = ZJ.decode(responseStr);
|
||||
if (decoded instanceof Map) {
|
||||
Object r = ((Map<?, ?>) decoded).get("reason");
|
||||
if (r != null) reason = r.toString();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
throw new Exception(reason + " (URL: " + urlStr + ")");
|
||||
}
|
||||
|
||||
if (responseStr.isEmpty()) return null;
|
||||
|
||||
try {
|
||||
return ZJ.decode(responseStr);
|
||||
} catch (Exception e) {
|
||||
if (responseStr.startsWith("<")) {
|
||||
throw new Exception("Server returned HTML instead of JSON (URL: " + urlStr + ")");
|
||||
}
|
||||
throw new Exception("JSON parse failed for " + urlStr + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
* 1) Under the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.tools;
|
||||
|
||||
import swiss.qpq.gajumaru.core.crypto.Ed25519;
|
||||
|
||||
/**
|
||||
* JavaSigningProvider implements SigningProvider using pure Java Ed25519 arithmetic.
|
||||
*/
|
||||
public final class JavaSigningProvider implements SigningProvider {
|
||||
|
||||
@Override
|
||||
public byte[] cryptoSignSeedKeypair(byte[] seed) {
|
||||
byte[] pub = Ed25519.publicKey(seed);
|
||||
// Pure Java Ed25519 needs a way to get the expanded secret key.
|
||||
// In Ed25519.sign it's: hash = sha512(seed); s = hash[0..31]; s[0] &= 248; s[31] &= 127; s[31] |= 64;
|
||||
// Prefix is hash[32..63].
|
||||
// Libsodium's 64-byte SK is often [32-byte seed | 32-byte public key].
|
||||
// However, Ed25519.sign implementation expects the 64-byte expanded key to work correctly.
|
||||
// Let's look at how Ed25519.sign works. It derives 's' and 'prefix' from the seed.
|
||||
|
||||
// For parity with Native implementation which returns [32pk | 64sk],
|
||||
// we need to be careful about what 'sk' means.
|
||||
// In libsodium, sk is [32-byte seed | 32-byte public key].
|
||||
// In Ed25519.java, it uses seed directly.
|
||||
|
||||
// I will implement a bridge in Ed25519.java to handle this.
|
||||
|
||||
// FOR NOW: Assume we follow libsodium: SK = [seed(32) | pub(32)]
|
||||
byte[] sk = new byte[64];
|
||||
System.arraycopy(seed, 0, sk, 0, 32);
|
||||
System.arraycopy(pub, 0, sk, 32, 32);
|
||||
|
||||
byte[] res = new byte[96];
|
||||
System.arraycopy(pub, 0, res, 0, 32);
|
||||
System.arraycopy(sk, 0, res, 32, 64);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] cryptoSignDetached(byte[] message, byte[] secretKey) {
|
||||
// Assume secretKey is [seed(32) | pub(32)]
|
||||
byte[] seed = new byte[32];
|
||||
System.arraycopy(secretKey, 0, seed, 0, 32);
|
||||
return Ed25519.sign(seed, message);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -20,32 +20,14 @@
|
||||
|
||||
package swiss.qpq.gajumaru.core.tools;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import swiss.qpq.gajumaru.core.encoding.ZJ;
|
||||
|
||||
|
||||
// NodeClient provides a simple interface to query Gajumaru nodes.
|
||||
// Ported from hz.erl
|
||||
//
|
||||
// Conceptually very similar to Hakuzaru's main interface, but (at least currently)
|
||||
// more limited (no contract deployment, call, etc.). The most obvious difference is that
|
||||
// instead of gm-java being a stateful service the way hz can be, this is a pure library
|
||||
// and the caller is expected to be the one that is keeping track of a list of possible
|
||||
// endpoints.
|
||||
//
|
||||
// This design will evolve in the future as the concept of network and inter-network
|
||||
// crawling evolved (and obviously as ACs are introduced). In this iteration, though,
|
||||
// my goal is to keep things as simple as possible, and that is why this is probably
|
||||
// somewhat unidiomatic for Java (I don't *really* know, but this works).
|
||||
|
||||
/**
|
||||
* NodeClient provides a simple interface to query Gajumaru nodes.
|
||||
* Ported from hz.erl
|
||||
*/
|
||||
public final class NodeClient {
|
||||
|
||||
public static final class Endpoint {
|
||||
@@ -65,7 +47,6 @@ public final class NodeClient {
|
||||
}
|
||||
|
||||
private final List<Endpoint> endpoints;
|
||||
private int timeout = 5000;
|
||||
|
||||
public NodeClient(List<Endpoint> endpoints) {
|
||||
if (endpoints == null || endpoints.isEmpty()) {
|
||||
@@ -74,23 +55,23 @@ public final class NodeClient {
|
||||
this.endpoints = endpoints;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> status() throws Exception {
|
||||
return (Map<String, Object>) request("/v3/status", "GET", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public long topHeight() throws Exception {
|
||||
Map<String, Object> res = (Map<String, Object>) request("/v3/headers/top", "GET", null);
|
||||
return ((Number) res.get("height")).longValue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> account(String accountId) throws Exception {
|
||||
return (Map<String, Object>) request("/v3/accounts/" + accountId, "GET", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public long nextNonce(String accountId) throws Exception {
|
||||
try {
|
||||
Map<String, Object> res = (Map<String, Object>) request("/v3/accounts/" + accountId + "/next-nonce", "GET", null);
|
||||
@@ -103,16 +84,19 @@ public final class NodeClient {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> postTx(String signedTx) throws Exception {
|
||||
Map<String, String> body = new HashMap<>();
|
||||
body.put("tx", signedTx);
|
||||
return (Map<String, Object>) request("/v3/transactions", "POST", body);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> tx(String txHash) throws Exception {
|
||||
return (Map<String, Object>) request("/v3/transactions/" + txHash, "GET", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> txInfo(String txHash) throws Exception {
|
||||
return (Map<String, Object>) request("/v3/transactions/" + txHash + "/info", "GET", null);
|
||||
}
|
||||
@@ -121,15 +105,19 @@ public final class NodeClient {
|
||||
Exception lastException = null;
|
||||
for (Endpoint endpoint : endpoints) {
|
||||
try {
|
||||
String protocol = endpoint.useTls ? "https://" : "http://";
|
||||
String url = protocol + endpoint.host + ":" + endpoint.port + path;
|
||||
|
||||
if (endpoint.useTls) {
|
||||
try {
|
||||
return performRequest(endpoint, path, method, body, true);
|
||||
return "GET".equals(method) ? Http.get(url) : Http.post(url, body);
|
||||
} catch (Exception e) {
|
||||
// Fallback to HTTP
|
||||
return performRequest(endpoint, path, method, body, false);
|
||||
String httpUrl = "http://" + endpoint.host + ":" + endpoint.port + path;
|
||||
return "GET".equals(method) ? Http.get(httpUrl) : Http.post(httpUrl, body);
|
||||
}
|
||||
} else {
|
||||
return performRequest(endpoint, path, method, body, false);
|
||||
return "GET".equals(method) ? Http.get(url) : Http.post(url, body);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
@@ -137,46 +125,4 @@ public final class NodeClient {
|
||||
}
|
||||
throw new Exception("All endpoints failed. Last error: " + (lastException != null ? lastException.getMessage() : "unknown"), lastException);
|
||||
}
|
||||
|
||||
private Object performRequest(Endpoint endpoint, String path, String method, Object body, boolean useTls) throws Exception {
|
||||
String protocol = useTls ? "https://" : "http://";
|
||||
String urlStr = protocol + endpoint.host + ":" + endpoint.port + path;
|
||||
URL url = new URL(urlStr);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(timeout);
|
||||
conn.setReadTimeout(timeout);
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
|
||||
if (body != null) {
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = ZJ.encode(body).getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
StringBuilder response = new StringBuilder();
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(
|
||||
code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream(),
|
||||
StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
response.append(line.trim());
|
||||
}
|
||||
}
|
||||
|
||||
Object decoded = ZJ.decode(response.toString());
|
||||
if (code < 200 || code >= 300) {
|
||||
String reason = "HTTP " + code;
|
||||
if (decoded instanceof Map) {
|
||||
Object r = ((Map<?, ?>) decoded).get("reason");
|
||||
if (r != null) reason = r.toString();
|
||||
}
|
||||
throw new Exception(reason);
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
* 1) Under the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.tools;
|
||||
|
||||
/**
|
||||
* SigningProvider abstracts Ed25519 operations to allow different implementations
|
||||
* (e.g., pure Java vs. Native libsodium) to be used at runtime.
|
||||
*/
|
||||
public interface SigningProvider {
|
||||
|
||||
/**
|
||||
* Expands a 32-byte seed into a 64-byte secret key and 32-byte public key.
|
||||
* @param seed 32-byte seed.
|
||||
* @return 96-byte array [public_key(32) | secret_key(64)].
|
||||
*/
|
||||
byte[] cryptoSignSeedKeypair(byte[] seed);
|
||||
|
||||
/**
|
||||
* Signs a message using a 64-byte expanded secret key.
|
||||
* @param message The data to sign.
|
||||
* @param secretKey 64-byte secret key.
|
||||
* @return 64-byte detached signature.
|
||||
*/
|
||||
byte[] cryptoSignDetached(byte[] message, byte[] secretKey);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ AG <info@qpq.swiss>. All rights reserved.
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
@@ -7,10 +7,10 @@
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ AG.
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ AG.
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
@@ -30,13 +30,28 @@ import swiss.qpq.gajumaru.core.data.SignedTx;
|
||||
import swiss.qpq.gajumaru.core.data.SpendTx;
|
||||
import swiss.qpq.gajumaru.core.encoding.ApiEncoder;
|
||||
|
||||
// This is where the gnarly mess used to generate transactions lives for now.
|
||||
// They might not stay here, but this is where they are for now.
|
||||
|
||||
/**
|
||||
* TransactionService provides high-level tools to build and sign transactions
|
||||
* and other chain data.
|
||||
*/
|
||||
public final class TransactionService {
|
||||
|
||||
private static SigningProvider provider = new JavaSigningProvider();
|
||||
|
||||
private TransactionService() {}
|
||||
|
||||
/**
|
||||
* Sets the signing provider to use for all operations.
|
||||
*/
|
||||
public static void setProvider(SigningProvider newProvider) {
|
||||
if (newProvider != null) {
|
||||
provider = newProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and signs a Spend transaction using a 32-byte seed.
|
||||
*/
|
||||
public static String buildSpendTx(
|
||||
String networkId,
|
||||
String senderId,
|
||||
@@ -47,12 +62,11 @@ public final class TransactionService {
|
||||
long ttl,
|
||||
long nonce,
|
||||
String payload,
|
||||
byte[] seckey
|
||||
byte[] seed
|
||||
) {
|
||||
// 1. Decode IDs
|
||||
Id sender = new Id(Id.Tag.ACCOUNT, ApiEncoder.decode(senderId).payload());
|
||||
|
||||
// Recipient can be ak_ (account) or ct_ (contract)
|
||||
ApiEncoder.DecodeResult recipientResult = ApiEncoder.decode(recipientId);
|
||||
Id.Tag recipientTag = recipientResult.type() == ApiEncoder.Type.CONTRACT_PUBKEY ? Id.Tag.CONTRACT : Id.Tag.ACCOUNT;
|
||||
Id recipient = new Id(recipientTag, recipientResult.payload());
|
||||
@@ -69,13 +83,91 @@ public final class TransactionService {
|
||||
System.arraycopy(networkIdBytes, 0, networkHash, 0, networkIdBytes.length);
|
||||
System.arraycopy(txHash, 0, networkHash, networkIdBytes.length, txHash.length);
|
||||
|
||||
// 4. Sign
|
||||
byte[] signature = Ed25519.sign(seckey, networkHash);
|
||||
// 4. Expand Seed and Sign
|
||||
byte[] keypair = provider.cryptoSignSeedKeypair(seed);
|
||||
if (keypair == null) throw new RuntimeException("Signing provider keypair generation failed");
|
||||
|
||||
byte[] secretKey = new byte[64];
|
||||
System.arraycopy(keypair, 32, secretKey, 0, 64);
|
||||
|
||||
byte[] signature = provider.cryptoSignDetached(networkHash, secretKey);
|
||||
|
||||
CryptoUtils.wipe(keypair);
|
||||
CryptoUtils.wipe(secretKey);
|
||||
|
||||
if (signature == null) throw new RuntimeException("Native signing failed");
|
||||
|
||||
// 5. Wrap in SignedTx
|
||||
SignedTx signedTx = new SignedTx(List.of(signature), serializedTx);
|
||||
|
||||
// 6. Encode for API
|
||||
return ApiEncoder.encode(ApiEncoder.Type.TRANSACTION, signedTx.serialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a message using the Gajumaru standard (prefixed and hashed).
|
||||
* @param secretKey 64-byte expanded secret key.
|
||||
*/
|
||||
public static byte[] signMessage(String message, byte[] secretKey) {
|
||||
byte[] msgBytes = message.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] prefix = "Gajumaru Signed Message:\n".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] pSize = VarInt.encode(prefix.length);
|
||||
byte[] mSize = VarInt.encode(msgBytes.length);
|
||||
|
||||
byte[] smashed = new byte[pSize.length + prefix.length + mSize.length + msgBytes.length];
|
||||
int pos = 0;
|
||||
System.arraycopy(pSize, 0, smashed, pos, pSize.length); pos += pSize.length;
|
||||
System.arraycopy(prefix, 0, smashed, pos, prefix.length); pos += prefix.length;
|
||||
System.arraycopy(mSize, 0, smashed, pos, mSize.length); pos += mSize.length;
|
||||
System.arraycopy(msgBytes, 0, smashed, pos, msgBytes.length);
|
||||
|
||||
byte[] hashed = Blake2b.hash(smashed, 32);
|
||||
return provider.cryptoSignDetached(hashed, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs binary data using the Gajumaru standard (prefixed and hashed).
|
||||
* @param secretKey 64-byte expanded secret key.
|
||||
*/
|
||||
public static byte[] signBinary(byte[] data, byte[] secretKey) {
|
||||
byte[] prefix = "Gajumaru Signed Binary:".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] target = new byte[prefix.length + data.length];
|
||||
System.arraycopy(prefix, 0, target, 0, prefix.length);
|
||||
System.arraycopy(data, 0, target, prefix.length, data.length);
|
||||
|
||||
byte[] hashed = Blake2b.hash(target, 32);
|
||||
return provider.cryptoSignDetached(hashed, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a binary signature.
|
||||
*/
|
||||
public static boolean verifyBinary(byte[] data, byte[] signature, byte[] publicKey) {
|
||||
byte[] prefix = "Gajumaru Signed Binary:".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] target = new byte[prefix.length + data.length];
|
||||
System.arraycopy(prefix, 0, target, 0, prefix.length);
|
||||
System.arraycopy(data, 0, target, prefix.length, data.length);
|
||||
|
||||
byte[] hashed = Blake2b.hash(target, 32);
|
||||
return Ed25519.verify(publicKey, hashed, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a transaction.
|
||||
* @param secretKey 64-byte expanded secret key.
|
||||
*/
|
||||
public static String signTx(byte[] txData, String networkId, byte[] secretKey) {
|
||||
byte[] nidBytes = networkId.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] hash = Blake2b.hash(txData, 32);
|
||||
|
||||
byte[] networkHash = new byte[nidBytes.length + hash.length];
|
||||
System.arraycopy(nidBytes, 0, networkHash, 0, nidBytes.length);
|
||||
System.arraycopy(hash, 0, networkHash, nidBytes.length, hash.length);
|
||||
|
||||
byte[] signature = provider.cryptoSignDetached(networkHash, secretKey);
|
||||
if (signature == null) throw new RuntimeException("Signing provider signing failed");
|
||||
|
||||
SignedTx signedTx = new SignedTx(List.of(signature), txData);
|
||||
return ApiEncoder.encode(ApiEncoder.Type.TRANSACTION, signedTx.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
|
||||
* Project: Gajumaru Core Java Libraries <gajumaru.io>
|
||||
*
|
||||
* This program is dual-licensed:
|
||||
* 1) Under the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version (AGPL-3.0-or-later).
|
||||
*
|
||||
* 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
|
||||
* If you wish to use this software outside the strict constraints of the
|
||||
* AGPLv3 (e.g. within a closed-source or proprietary product), you must
|
||||
* purchase a commercial license from QPQ IaaS AG.
|
||||
*
|
||||
* Authors:
|
||||
* - Craig Everett <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.tools;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// Bitcoin-style Variable-length integer encoding.
|
||||
// Port of vencode in hz.erl.
|
||||
|
||||
public final class VarInt {
|
||||
|
||||
private VarInt() {}
|
||||
|
||||
public static byte[] encode(long n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("Negative values not supported: " + n);
|
||||
}
|
||||
if (n < 0xfd) {
|
||||
return new byte[]{(byte) n};
|
||||
} else if (n <= 0xffff) {
|
||||
ByteBuffer bb = ByteBuffer.allocate(3);
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
bb.put((byte) 0xfd);
|
||||
bb.putShort((short) n);
|
||||
return bb.array();
|
||||
} else if (n <= 0xffffffffL) {
|
||||
ByteBuffer bb = ByteBuffer.allocate(5);
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
bb.put((byte) 0xfe);
|
||||
bb.putInt((int) n);
|
||||
return bb.array();
|
||||
} else {
|
||||
ByteBuffer bb = ByteBuffer.allocate(9);
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
bb.put((byte) 0xff);
|
||||
bb.putLong(n);
|
||||
return bb.array();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user