From c20a024eebfe09244f81f572bd2e3c18c651cc0d Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 28 May 2026 21:17:06 +0900 Subject: [PATCH] Init --- .gitignore | 13 ++ README.md | 3 + gajumaru-core/bin/compile | 20 +++ .../qpq/gajumaru/core/encoding/Base58.java | 152 ++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 gajumaru-core/bin/compile create mode 100644 gajumaru-core/src/main/java/swiss/qpq/gajumaru/core/encoding/Base58.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fa8234 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +build/ +.DS_Store +Thumbs.db +*.swp +*.swo +.netrwhist +.nvimlog +.idea/ +*.iml +.gradle/ +local.properties +Captures/ +.externalNativeBuild/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f37d8b8 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Gajumaru Java Libraries + +Java libraries for the Gajumaru diff --git a/gajumaru-core/bin/compile b/gajumaru-core/bin/compile new file mode 100755 index 0000000..bc9f8f8 --- /dev/null +++ b/gajumaru-core/bin/compile @@ -0,0 +1,20 @@ +#! /usr/bin/env bash + +# Copyright (c) 2026 QPQ AG . All rights reserved. +# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial + +set -e + +abs_dir="$(cd -P $(dirname ${BASH_SOURCE}) && pwd)" +project_dir="$(dirname $abs_dir)" + +# Clean +rm -rf "$project_dir/build/classes/*" + +# Build every .java file +find "$project_dir/src/main/java" -name "*.java" | xargs javac \ + -source 21 \ + -target 21 \ + -d ../build/classes + +echo "Bytecode in $project_dir/build/classes/" diff --git a/gajumaru-core/src/main/java/swiss/qpq/gajumaru/core/encoding/Base58.java b/gajumaru-core/src/main/java/swiss/qpq/gajumaru/core/encoding/Base58.java new file mode 100644 index 0000000..a5a193e --- /dev/null +++ b/gajumaru-core/src/main/java/swiss/qpq/gajumaru/core/encoding/Base58.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2026 QPQ AG . All rights reserved. + * Project: Gajumaru Core Java Libraries + * + * 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 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. + * + * Authors: + * - Craig Everett + * + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial + */ + +package swiss.qpq.gajumaru.core.encoding; + +import java.util.Arrays; + +// Stateless Base58 implementation. +public final class Base58 { + + private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final int[] INDEXES = new int[128]; + + static { + Arrays.fill(INDEXES, -1); + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + private Base58() {} + + // Encode raw bytes directly to a Base58 string. + public static String encode(byte[] input) { + if (input == null || input.length == 0) { + return ""; + } + + int zeros = 0; + while (zeros < input.length && input[zeros] == 0) { + zeros++; + } + + byte[] temp = Arrays.copyOf(input, input.length); + char[] encoded = new char[temp.length * 2]; + int outputStart = encoded.length; + + // Treat 'temp' as one giant number. Process until the entire array is reduced to zeros. + // i indicates the position of the most significant leading zero. + int i = zeros; + while (i < temp.length) { + int remainder = 0; + + // Perform long division from left to right across the active bytes + for (int j = i; j < temp.length; j++) { + int currentByte = temp[j] & 0xFF; // Safe unsigned byte conversion + int totalValue = (remainder * 256) + currentByte; + + temp[j] = (byte) (totalValue / 58); // Mutate array with the quotient + remainder = totalValue % 58; // Carry the remainder to the next byte + } + + // The final remainder of this pass is our next Base58 digit character + encoded[--outputStart] = ALPHABET[remainder]; + + // Advance the pointer forward if the current head byte has been ground down to 0 + if (temp[i] == 0) { + i++; + } + } + + while (outputStart < encoded.length && encoded[outputStart] == ALPHABET[0]) { + outputStart++; + } + while (--zeros >= 0) { + encoded[--outputStart] = ALPHABET[0]; + } + + return new String(encoded, outputStart, encoded.length - outputStart); + } + + // Decodes a Base58 string back into its original byte payload. + // Throws an IllegalArgumentException if any illegal characters are encountered. + // `throw` remains slightly disgusting. + public static byte[] decode(String input) throws IllegalArgumentException { + if (input == null || input.isEmpty()) { + return new byte[0]; + } + + // Convert the string into numeric index offsets + byte[] input58 = new byte[input.length()]; + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + int digit = (c < 128) ? INDEXES[c] : -1; + if (digit < 0) { + // I picked the wrong week to stop drinking... + throw new IllegalArgumentException("Illegal Base58 character encountered: " + c); + } + input58[i] = (byte) digit; + } + + // Count leading zeros to reconstruct leading 0 bytes + int zeros = 0; + while (zeros < input58.length && input58[zeros] == 0) { + zeros++; + } + + byte[] decoded = new byte[input.length()]; + int outputStart = decoded.length; + + // Treat input58 as one yuuge base-58 number. Reduce it down to base-256 bytes. + int i = zeros; + while (i < input58.length) { + int remainder = 0; + + // Long division from left to right across the remaining numeric character indices + for (int j = i; j < input58.length; j++) { + int currentBase58Digit = input58[j] & 0xFF; + int totalValue = (remainder * 58) + currentBase58Digit; // Shift base by 58 + + input58[j] = (byte) (totalValue / 256); // Mutate array with the base-256 quotient + remainder = totalValue % 256; // Carry the byte remainder forward + } + + // The remainder of this pass is the next raw base-256 byte payload + decoded[--outputStart] = (byte) remainder; + + // Permanently advance past this leading index if its value has been exhausted + if (input58[i] == 0) { + i++; + } + } + + + // Strip extra leading zero allocations from the worst-case boundary buffer + while (outputStart < decoded.length && decoded[outputStart] == 0) { + outputStart++; + } + + // Re-inject the necessary leading padding zeros + byte[] result = new byte[decoded.length - outputStart + zeros]; + System.arraycopy(decoded, outputStart, result, zeros, decoded.length - outputStart); + return result; + } +}