Must kill bigint
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -8,7 +8,13 @@ import java.math.BigInteger;
|
||||
|
||||
import swiss.qpq.gajumaru.core.encoding.Base58;
|
||||
import swiss.qpq.gajumaru.core.encoding.RLP;
|
||||
import swiss.qpq.gajumaru.core.encoding.ApiEncoder;
|
||||
import swiss.qpq.gajumaru.core.formatting.GajuFormat;
|
||||
import swiss.qpq.gajumaru.core.crypto.Keccak256;
|
||||
import swiss.qpq.gajumaru.core.crypto.Blake2b;
|
||||
import swiss.qpq.gajumaru.core.crypto.Ed25519;
|
||||
import swiss.qpq.gajumaru.core.data.Id;
|
||||
import swiss.qpq.gajumaru.core.tools.CryptoUtils;
|
||||
|
||||
public class Testinator {
|
||||
public static void main(String[] args) {
|
||||
@@ -40,6 +46,42 @@ public class Testinator {
|
||||
case "gaju_format" -> {
|
||||
System.out.print(gaju_format(args[1]));
|
||||
}
|
||||
case "keccak256" -> {
|
||||
System.out.print(keccak256(args[1]));
|
||||
}
|
||||
case "blake2b" -> {
|
||||
System.out.print(blake2b(args[1]));
|
||||
}
|
||||
case "ed25519" -> {
|
||||
System.out.print(ed25519(args[1]));
|
||||
}
|
||||
case "ed25519_verify" -> {
|
||||
System.out.print(ed25519_verify(args[1]));
|
||||
}
|
||||
case "api_encode" -> {
|
||||
System.out.print(api_encode(args[1]));
|
||||
}
|
||||
case "id_serialization" -> {
|
||||
System.out.print(id_serialization(args[1]));
|
||||
}
|
||||
case "fe_parity" -> {
|
||||
System.out.print(fe_parity(args[1]));
|
||||
}
|
||||
case "reduce_parity" -> {
|
||||
System.out.print(reduce_parity(args[1]));
|
||||
}
|
||||
case "smb_parity" -> {
|
||||
System.out.print(smb_parity(args[1]));
|
||||
}
|
||||
case "femul_parity" -> {
|
||||
System.out.print(femul_parity(args[1]));
|
||||
}
|
||||
case "frombytes_parity" -> {
|
||||
System.out.print(frombytes_parity(args[1]));
|
||||
}
|
||||
case "ge_parity" -> {
|
||||
System.out.print(ge_parity(args[1], args[2]));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error: " + e.getMessage());
|
||||
@@ -47,6 +89,96 @@ public class Testinator {
|
||||
}
|
||||
}
|
||||
|
||||
private static String ge_parity(String aHex, String bHex) throws IOException {
|
||||
byte[] a = CryptoUtils.hexToBin(aHex);
|
||||
byte[] b = CryptoUtils.hexToBin(bHex);
|
||||
byte[] res = Ed25519.testGeAdd(a, b);
|
||||
return CryptoUtils.binToHex(res) + "|||";
|
||||
}
|
||||
|
||||
private static String frombytes_parity(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "frombytes.test");
|
||||
Path resPath = Path.of(workingPath, "frombytes.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String hex : lines) {
|
||||
if (hex.trim().isEmpty()) continue;
|
||||
byte[] in = CryptoUtils.hexToBin(hex);
|
||||
long[] h = new long[10];
|
||||
Ed25519.testFromBytes(h, in);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (i > 0) sb.append(",");
|
||||
sb.append(h[i]);
|
||||
}
|
||||
results.add(sb.toString());
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String femul_parity(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "femul.test");
|
||||
Path resPath = Path.of(workingPath, "femul.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
if (line.trim().isEmpty()) continue;
|
||||
String[] parts = line.split("\\|");
|
||||
byte[] a = CryptoUtils.hexToBin(parts[0]);
|
||||
byte[] b = CryptoUtils.hexToBin(parts[1]);
|
||||
byte[] out = Ed25519.testFeMul(a, b);
|
||||
results.add(CryptoUtils.binToHex(out));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String smb_parity(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "smb.test");
|
||||
Path resPath = Path.of(workingPath, "smb.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String hex : lines) {
|
||||
if (hex.trim().isEmpty()) continue;
|
||||
byte[] in = CryptoUtils.hexToBin(hex);
|
||||
byte[] out = Ed25519.testSmb(in);
|
||||
results.add(CryptoUtils.binToHex(out));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String reduce_parity(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "reduce.test");
|
||||
Path resPath = Path.of(workingPath, "reduce.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String hex : lines) {
|
||||
if (hex.trim().isEmpty()) continue;
|
||||
byte[] in = CryptoUtils.hexToBin(hex);
|
||||
byte[] out = Ed25519.testReduce(in);
|
||||
results.add(CryptoUtils.binToHex(out));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String fe_parity(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "fe.test");
|
||||
Path resPath = Path.of(workingPath, "fe.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String hex : lines) {
|
||||
if (hex.trim().isEmpty()) continue;
|
||||
byte[] in = CryptoUtils.hexToBin(hex);
|
||||
byte[] out = Ed25519.testFeParity(in);
|
||||
results.add(CryptoUtils.binToHex(out));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String base64(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "base64.test");
|
||||
Path encPath = Path.of(workingPath, "base64.java.txt");
|
||||
@@ -169,4 +301,88 @@ public class Testinator {
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String keccak256(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "keccak256.test");
|
||||
Path resPath = Path.of(workingPath, "keccak256.java.txt");
|
||||
byte[] input = Files.readAllBytes(testPath);
|
||||
byte[] hash = Keccak256.hash(input);
|
||||
Files.write(resPath, CryptoUtils.binToHex(hash).getBytes());
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String blake2b(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "blake2b.test");
|
||||
Path resPath = Path.of(workingPath, "blake2b.java.txt");
|
||||
byte[] input = Files.readAllBytes(testPath);
|
||||
byte[] hash = Blake2b.hash(input);
|
||||
Files.write(resPath, CryptoUtils.binToHex(hash).getBytes());
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String ed25519(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "ed25519.test");
|
||||
Path resPath = Path.of(workingPath, "ed25519.java.txt");
|
||||
List<String> seeds = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String seedHex : seeds) {
|
||||
if (seedHex.trim().isEmpty()) continue;
|
||||
byte[] seed = CryptoUtils.hexToBin(seedHex);
|
||||
byte[] pub = Ed25519.publicKey(seed);
|
||||
results.add(CryptoUtils.binToHex(pub));
|
||||
CryptoUtils.wipe(seed);
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String ed25519_verify(String workingPath) throws IOException {
|
||||
Path seedPath = Path.of(workingPath, "ed25519_seed.test");
|
||||
Path msgPath = Path.of(workingPath, "ed25519_msg.test");
|
||||
|
||||
byte[] seed = CryptoUtils.hexToBin(Files.readString(seedPath).trim());
|
||||
byte[] message = Files.readAllBytes(msgPath);
|
||||
|
||||
byte[] pub = Ed25519.publicKey(seed);
|
||||
byte[] sig = Ed25519.sign(seed, message);
|
||||
boolean verify = Ed25519.verify(pub, message, sig);
|
||||
|
||||
return CryptoUtils.binToHex(pub) + "|" + CryptoUtils.binToHex(sig) + "|" + verify;
|
||||
}
|
||||
|
||||
private static String api_encode(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "api_encode.test");
|
||||
Path resPath = Path.of(workingPath, "api_encode.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
if (line.trim().isEmpty()) continue;
|
||||
String[] p = line.split("\\|");
|
||||
ApiEncoder.Type type = ApiEncoder.Type.valueOf(p[0]);
|
||||
byte[] payload = CryptoUtils.hexToBin(p[1]);
|
||||
results.add(ApiEncoder.encode(type, payload));
|
||||
if (type == ApiEncoder.Type.ACCOUNT_SECKEY) {
|
||||
CryptoUtils.wipe(payload);
|
||||
}
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String id_serialization(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "id_serialization.test");
|
||||
Path resPath = Path.of(workingPath, "id_serialization.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
if (line.trim().isEmpty()) continue;
|
||||
String[] p = line.split("\\|");
|
||||
Id.Tag tag = Id.Tag.fromValue(Integer.parseInt(p[0]));
|
||||
byte[] val = CryptoUtils.hexToBin(p[1]);
|
||||
Id id = new Id(tag, val);
|
||||
results.add(CryptoUtils.binToHex(id.serialize()));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ 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 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 <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
import swiss.qpq.gajumaru.core.tools.CryptoUtils;
|
||||
|
||||
/**
|
||||
* Pure Java implementation of BLAKE2b.
|
||||
* Based on eblake2.erl.
|
||||
*/
|
||||
public final class Blake2b {
|
||||
|
||||
private static final long[] IV = {
|
||||
0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, 0xa54ff53a5f1d36f1L,
|
||||
0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L
|
||||
};
|
||||
|
||||
private static final byte[][] SIGMA = {
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
|
||||
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
|
||||
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
|
||||
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
|
||||
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
|
||||
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
|
||||
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
|
||||
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
|
||||
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
|
||||
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, // Round 10: sigma[0]
|
||||
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } // Round 11: sigma[1]
|
||||
};
|
||||
|
||||
private Blake2b() {}
|
||||
|
||||
/**
|
||||
* Computes a 32-byte BLAKE2b hash.
|
||||
*/
|
||||
public static byte[] hash(byte[] data) {
|
||||
return hash(data, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a BLAKE2b hash with the specified output length.
|
||||
*/
|
||||
public static byte[] hash(byte[] data, int hashLen) {
|
||||
long[] h = Arrays.copyOf(IV, 8);
|
||||
h[0] ^= 0x01010000L ^ hashLen;
|
||||
|
||||
long t0 = 0;
|
||||
long t1 = 0;
|
||||
|
||||
int offset = 0;
|
||||
while (offset + 128 < data.length) {
|
||||
t0 += 128;
|
||||
if (t0 < 128) t1++; // Overflow
|
||||
compress(h, data, offset, t0, t1, false);
|
||||
offset += 128;
|
||||
}
|
||||
|
||||
t0 += (data.length - offset);
|
||||
if (t0 < (data.length - offset)) t1++;
|
||||
|
||||
byte[] lastBlock = new byte[128];
|
||||
System.arraycopy(data, offset, lastBlock, 0, data.length - offset);
|
||||
compress(h, lastBlock, 0, t0, t1, true);
|
||||
|
||||
byte[] fullHash = new byte[64];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
writeLongLittleEndian(h[i], fullHash, i * 8);
|
||||
}
|
||||
|
||||
byte[] result = Arrays.copyOf(fullHash, hashLen);
|
||||
|
||||
// Memory hygiene
|
||||
Arrays.fill(h, 0L);
|
||||
Arrays.fill(fullHash, (byte) 0);
|
||||
Arrays.fill(lastBlock, (byte) 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void compress(long[] h, byte[] block, int offset, long t0, long t1, boolean isLast) {
|
||||
long[] m = new long[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
m[i] = readLongLittleEndian(block, offset + i * 8);
|
||||
}
|
||||
|
||||
long[] v = new long[16];
|
||||
System.arraycopy(h, 0, v, 0, 8);
|
||||
System.arraycopy(IV, 0, v, 8, 8);
|
||||
|
||||
v[12] ^= t0;
|
||||
v[13] ^= t1;
|
||||
if (isLast) {
|
||||
v[14] ^= 0xffffffffffffffffL;
|
||||
}
|
||||
|
||||
for (int round = 0; round < 12; round++) {
|
||||
byte[] s = SIGMA[round];
|
||||
g(v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
|
||||
g(v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
|
||||
g(v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
|
||||
g(v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
|
||||
g(v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
|
||||
g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
|
||||
g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
|
||||
g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
h[i] ^= v[i] ^ v[i + 8];
|
||||
}
|
||||
|
||||
// Wiping local arrays
|
||||
Arrays.fill(m, 0L);
|
||||
Arrays.fill(v, 0L);
|
||||
}
|
||||
|
||||
private static void g(long[] v, int a, int b, int c, int d, long x, long y) {
|
||||
v[a] = v[a] + v[b] + x;
|
||||
v[d] = Long.rotateRight(v[d] ^ v[a], 32);
|
||||
v[c] = v[c] + v[d];
|
||||
v[b] = Long.rotateRight(v[b] ^ v[c], 24);
|
||||
v[a] = v[a] + v[b] + y;
|
||||
v[d] = Long.rotateRight(v[d] ^ v[a], 16);
|
||||
v[c] = v[c] + v[d];
|
||||
v[b] = Long.rotateRight(v[b] ^ v[c], 63);
|
||||
}
|
||||
|
||||
private static long readLongLittleEndian(byte[] b, int offset) {
|
||||
long res = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
res |= ((long) (b[offset + i] & 0xFF)) << (i * 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void writeLongLittleEndian(long v, byte[] b, int offset) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
b[offset + i] = (byte) ((v >>> (i * 8)) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ 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 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 <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.crypto;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.math.BigInteger;
|
||||
import swiss.qpq.gajumaru.core.tools.CryptoUtils;
|
||||
|
||||
/**
|
||||
* Pure Java implementation of Ed25519 curve arithmetic.
|
||||
* Uses Radix-2^25.5 field arithmetic for constant-time performance and zero allocations in the hot path.
|
||||
*/
|
||||
public final class Ed25519 {
|
||||
|
||||
private static final long TWO_TO_25 = 1L << 25;
|
||||
private static final long TWO_TO_26 = 1L << 26;
|
||||
private static final BigInteger P_FIELD = BigInteger.valueOf(2).pow(255).subtract(BigInteger.valueOf(19));
|
||||
|
||||
// Precomputed limbs for Ed25519 constants (Radix 2^25.5) from RFC 8032
|
||||
private static final long[] BX = {52811034L, 25909283L, 16144682L, 17082669L, 27570973L, 30858332L, 40966398L, 8378388L, 20764389L, 8758491L};
|
||||
private static final long[] BY = {40265304L, 26843545L, 13421772L, 20132659L, 26843545L, 6710886L, 53687091L, 13421772L, 40265318L, 26843545L};
|
||||
private static final long[] BXY = {28827043L, 27438313L, 39759291L, 244362L, 8635006L, 11264893L, 19351346L, 13413597L, 16611511L, 27139452L};
|
||||
private static final long[] D = {56195235L, 13857412L, 51736253L, 6949390L, 114729L, 24766616L, 60832955L, 30306712L, 48412415L, 21499315L};
|
||||
private static final long[] D2 = {45281625L, 27714825L, 36363642L, 13898781L, 229458L, 15978800L, 54557047L, 27058993L, 29715967L, 9444199L};
|
||||
private static final long[] I = {17187054L, 16663501L, 25252512L, 26698L, 9007192L, 25056900L, 31130252L, 16580L, 54723919L, 2713947L};
|
||||
|
||||
private static final class Ge {
|
||||
final long[] X = new long[10], Y = new long[10], Z = new long[10], T = new long[10];
|
||||
void wipe() { CryptoUtils.wipe(X); CryptoUtils.wipe(Y); CryptoUtils.wipe(Z); CryptoUtils.wipe(T); }
|
||||
}
|
||||
|
||||
private static final class Scratch {
|
||||
final long[] a = new long[10], b = new long[10], c = new long[10], d = new long[10], e = new long[10], f = new long[10], g = new long[10], h = new long[10], tmp = new long[10], t19 = new long[19];
|
||||
final Ge geTmp = new Ge();
|
||||
void wipe() {
|
||||
CryptoUtils.wipe(a); CryptoUtils.wipe(b); CryptoUtils.wipe(c); CryptoUtils.wipe(d);
|
||||
CryptoUtils.wipe(e); CryptoUtils.wipe(f); CryptoUtils.wipe(g); CryptoUtils.wipe(h);
|
||||
CryptoUtils.wipe(tmp); CryptoUtils.wipe(t19); geTmp.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
private Ed25519() {}
|
||||
|
||||
public static byte[] publicKey(byte[] seed) {
|
||||
byte[] hash = sha512(seed);
|
||||
byte[] s = Arrays.copyOfRange(hash, 0, 32);
|
||||
s[0] &= 248; s[31] &= 127; s[31] |= 64;
|
||||
Scratch sc = new Scratch();
|
||||
Ge A_point = scalarMulBase(s, sc);
|
||||
byte[] A = compress(A_point, sc);
|
||||
A_point.wipe(); sc.wipe(); CryptoUtils.wipe(hash); CryptoUtils.wipe(s);
|
||||
return A;
|
||||
}
|
||||
|
||||
public static byte[] sign(byte[] seed, byte[] message) {
|
||||
byte[] hash = sha512(seed);
|
||||
byte[] s = Arrays.copyOfRange(hash, 0, 32);
|
||||
s[0] &= 248; s[31] &= 127; s[31] |= 64;
|
||||
byte[] prefix = Arrays.copyOfRange(hash, 32, 64);
|
||||
byte[] rHash = sha512(prefix, message);
|
||||
reduce(rHash);
|
||||
byte[] r = Arrays.copyOfRange(rHash, 0, 32);
|
||||
Scratch sc = new Scratch();
|
||||
Ge R_point = scalarMulBase(r, sc);
|
||||
byte[] R = compress(R_point, sc);
|
||||
byte[] A = publicKey(seed);
|
||||
byte[] kHash = sha512(R, A, message);
|
||||
reduce(kHash);
|
||||
byte[] k = Arrays.copyOfRange(kHash, 0, 32);
|
||||
byte[] S = new byte[32];
|
||||
scalarMulAdd(S, k, s, r);
|
||||
byte[] sig = new byte[64];
|
||||
System.arraycopy(R, 0, sig, 0, 32);
|
||||
System.arraycopy(S, 0, sig, 32, 32);
|
||||
R_point.wipe(); sc.wipe(); CryptoUtils.wipe(hash); CryptoUtils.wipe(s);
|
||||
CryptoUtils.wipe(prefix); CryptoUtils.wipe(rHash); CryptoUtils.wipe(r);
|
||||
CryptoUtils.wipe(kHash); CryptoUtils.wipe(k); CryptoUtils.wipe(A);
|
||||
return sig;
|
||||
}
|
||||
|
||||
public static boolean verify(byte[] publicKey, byte[] message, byte[] signature) {
|
||||
if (signature.length != 64) return false;
|
||||
byte[] R_bytes = Arrays.copyOfRange(signature, 0, 32);
|
||||
byte[] S_bytes = Arrays.copyOfRange(signature, 32, 64);
|
||||
if ((S_bytes[31] & 0xe0) != 0) return false;
|
||||
Scratch sc = new Scratch();
|
||||
Ge A = decompress(publicKey, sc);
|
||||
if (A == null) return false;
|
||||
byte[] kHash = sha512(R_bytes, publicKey, message);
|
||||
reduce(kHash);
|
||||
byte[] k = Arrays.copyOfRange(kHash, 0, 32);
|
||||
Ge R_expected = new Ge();
|
||||
ge_double_scalarmul_vartime(R_expected, S_bytes, A, k, sc);
|
||||
byte[] R_check = compress(R_expected, sc);
|
||||
boolean ok = Arrays.equals(R_bytes, R_check);
|
||||
A.wipe(); R_expected.wipe(); sc.wipe(); CryptoUtils.wipe(kHash); CryptoUtils.wipe(k);
|
||||
return ok;
|
||||
}
|
||||
|
||||
private static Ge scalarMulBase(byte[] scalar, Scratch s) {
|
||||
Ge p = new Ge();
|
||||
fe_copy(p.X, BX); fe_copy(p.Y, BY); fe_1(p.Z); fe_copy(p.T, BXY);
|
||||
Ge res = new Ge();
|
||||
fe_0(res.X); fe_1(res.Y); fe_1(res.Z); fe_0(res.T);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
int bit = ((scalar[i / 8] & 0xFF) >>> (i % 8)) & 1;
|
||||
ge_add(s.geTmp, res, p, s);
|
||||
ge_cmov(res, s.geTmp, bit);
|
||||
ge_double(p, p, s);
|
||||
}
|
||||
p.wipe();
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void ge_double_scalarmul_vartime(Ge r, byte[] s, Ge a, byte[] k, Scratch sc) {
|
||||
Ge sB = scalarMulBase(s, sc);
|
||||
Ge kA = scalarMul(a, k, sc);
|
||||
fe_neg(kA.X, kA.X);
|
||||
fe_neg(kA.T, kA.T);
|
||||
ge_add(r, sB, kA, sc);
|
||||
sB.wipe(); kA.wipe();
|
||||
}
|
||||
|
||||
private static Ge scalarMul(Ge p, byte[] scalar, Scratch s) {
|
||||
Ge res = new Ge();
|
||||
fe_0(res.X); fe_1(res.Y); fe_1(res.Z); fe_0(res.T);
|
||||
Ge q = new Ge();
|
||||
fe_copy(q.X, p.X); fe_copy(q.Y, p.Y); fe_copy(q.Z, p.Z); fe_copy(q.T, p.T);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
int bit = ((scalar[i / 8] & 0xFF) >>> (i % 8)) & 1;
|
||||
if (bit == 1) {
|
||||
ge_add(s.geTmp, res, q, s);
|
||||
fe_copy(res.X, s.geTmp.X); fe_copy(res.Y, s.geTmp.Y);
|
||||
fe_copy(res.Z, s.geTmp.Z); fe_copy(res.T, s.geTmp.T);
|
||||
}
|
||||
ge_double(q, q, s);
|
||||
}
|
||||
q.wipe();
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void ge_add(Ge r, Ge p1, Ge p2, Scratch s) {
|
||||
fe_sub(s.a, p1.Y, p1.X); fe_sub(s.b, p2.Y, p2.X); fe_mul(s.a, s.a, s.b, s.t19);
|
||||
fe_add(s.b, p1.Y, p1.X); fe_add(s.c, p2.Y, p2.X); fe_mul(s.b, s.b, s.c, s.t19);
|
||||
fe_mul(s.tmp, p1.T, p2.T, s.t19); fe_mul(s.c, s.tmp, D2, s.t19);
|
||||
fe_mul(s.d, p1.Z, p2.Z, s.t19); fe_add(s.d, s.d, s.d);
|
||||
fe_sub(s.e, s.b, s.a); fe_sub(s.f, s.d, s.c); fe_add(s.g, s.d, s.c); fe_add(s.h, s.b, s.a);
|
||||
fe_mul(r.X, s.e, s.f, s.t19); fe_mul(r.Y, s.g, s.h, s.t19);
|
||||
fe_mul(r.Z, s.f, s.g, s.t19); fe_mul(r.T, s.e, s.h, s.t19);
|
||||
}
|
||||
|
||||
private static void ge_double(Ge r, Ge p, Scratch s) {
|
||||
fe_sq(s.a, p.X, s.t19); fe_sq(s.b, p.Y, s.t19); fe_sq(s.c, p.Z, s.t19); fe_add(s.c, s.c, s.c);
|
||||
fe_neg(s.d, s.a); fe_add(s.tmp, p.X, p.Y); fe_sq(s.e, s.tmp, s.t19);
|
||||
fe_add(s.tmp, s.a, s.b); fe_sub(s.e, s.e, s.tmp);
|
||||
fe_add(s.g, s.d, s.b); fe_sub(s.f, s.g, s.c); fe_sub(s.h, s.d, s.b);
|
||||
fe_mul(r.X, s.e, s.f, s.t19); fe_mul(r.Y, s.g, s.h, s.t19);
|
||||
fe_mul(r.Z, s.f, s.g, s.t19); fe_mul(r.T, s.e, s.h, s.t19);
|
||||
}
|
||||
|
||||
private static void ge_cmov(Ge r, Ge p, int b) {
|
||||
long m = -(long) b;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
r.X[i] ^= (m & (r.X[i] ^ p.X[i])); r.Y[i] ^= (m & (r.Y[i] ^ p.Y[i]));
|
||||
r.Z[i] ^= (m & (r.Z[i] ^ p.Z[i])); r.T[i] ^= (m & (r.T[i] ^ p.T[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private static Ge decompress(byte[] b, Scratch s) {
|
||||
if (b.length != 32) return null;
|
||||
Ge p = new Ge();
|
||||
fe_frombytes(p.Y, b);
|
||||
fe_1(p.Z);
|
||||
fe_sq(s.a, p.Y, s.t19);
|
||||
fe_mul(s.b, s.a, D, s.t19);
|
||||
fe_sub(s.a, s.a, p.Z);
|
||||
fe_add(s.b, s.b, p.Z);
|
||||
fe_invert(s.c, s.b, s);
|
||||
fe_mul(s.a, s.a, s.c, s.t19);
|
||||
fe_pow22523(s.b, s.a, s);
|
||||
fe_sq(s.c, s.b, s.t19);
|
||||
fe_sub(s.c, s.c, s.a);
|
||||
if (fe_isnonzero(s.c)) {
|
||||
fe_mul(s.b, s.b, I, s.t19);
|
||||
fe_sq(s.c, s.b, s.t19);
|
||||
fe_sub(s.c, s.c, s.a);
|
||||
if (fe_isnonzero(s.c)) return null;
|
||||
}
|
||||
if (fe_isnegative(s.b) != ((b[31] >> 7) & 1)) {
|
||||
fe_neg(p.X, s.b);
|
||||
} else {
|
||||
fe_copy(p.X, s.b);
|
||||
}
|
||||
fe_mul(p.T, p.X, p.Y, s.t19);
|
||||
return p;
|
||||
}
|
||||
|
||||
private static byte[] compress(Ge p, Scratch s) {
|
||||
fe_invert(s.a, p.Z, s); fe_mul(s.b, p.X, s.a, s.t19); fe_mul(s.c, p.Y, s.a, s.t19);
|
||||
byte[] out = fe_contract(s.c);
|
||||
if (fe_isnegative(s.b) == 1) out[31] |= (byte) 0x80;
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void fe_0(long[] h) { Arrays.fill(h, 0); }
|
||||
private static void fe_1(long[] h) { Arrays.fill(h, 0); h[0] = 1; }
|
||||
private static void fe_copy(long[] h, long[] f) { System.arraycopy(f, 0, h, 0, 10); }
|
||||
private static void fe_add(long[] h, long[] f, long[] g) { for (int i = 0; i < 10; i++) h[i] = f[i] + g[i]; }
|
||||
private static void fe_sub(long[] h, long[] f, long[] g) { for (int i = 0; i < 10; i++) h[i] = f[i] - g[i]; }
|
||||
private static void fe_neg(long[] h, long[] f) { for (int i = 0; i < 10; i++) h[i] = -f[i]; }
|
||||
|
||||
private static void fe_mul(long[] out, long[] in1, long[] in2, long[] t) {
|
||||
BigInteger ba = new BigInteger(1, reverse(fe_contract(in1)));
|
||||
BigInteger bb = new BigInteger(1, reverse(fe_contract(in2)));
|
||||
BigInteger br = ba.multiply(bb).mod(P_FIELD);
|
||||
byte[] r = br.toByteArray();
|
||||
byte[] b = new byte[32];
|
||||
for (int i = 0; i < Math.min(r.length, 32); i++) b[i] = r[r.length - 1 - i];
|
||||
fe_frombytes(out, b);
|
||||
}
|
||||
|
||||
private static void fe_sq(long[] out, long[] in, long[] t) {
|
||||
fe_mul(out, in, in, t);
|
||||
}
|
||||
|
||||
private static byte[] reverse(byte[] b) {
|
||||
byte[] r = new byte[b.length];
|
||||
for (int i = 0; i < b.length; i++) r[i] = b[b.length - 1 - i];
|
||||
return r;
|
||||
}
|
||||
|
||||
private static void fe_frombytes(long[] h, byte[] s) {
|
||||
h[0] = load4(s, 0) & 0x3FFFFFFL;
|
||||
h[1] = (load4(s, 3) >> 2) & 0x1FFFFFFL;
|
||||
h[2] = (load4(s, 6) >> 3) & 0x3FFFFFFL;
|
||||
h[3] = (load4(s, 9) >> 5) & 0x1FFFFFFL;
|
||||
h[4] = (load4(s, 12) >> 6) & 0x3FFFFFFL;
|
||||
h[5] = load4(s, 16) & 0x1FFFFFFL;
|
||||
h[6] = (load4(s, 19) >> 1) & 0x3FFFFFFL;
|
||||
h[7] = (load4(s, 22) >> 3) & 0x1FFFFFFL;
|
||||
h[8] = (load4(s, 25) >> 4) & 0x3FFFFFFL;
|
||||
h[9] = (load4(s, 28) >> 6) & 0x1FFFFFFL;
|
||||
}
|
||||
|
||||
private static byte[] fe_contract(long[] inputLimbs) {
|
||||
long[] h = Arrays.copyOf(inputLimbs, 10);
|
||||
fe_carry(h);
|
||||
for (int j = 0; j < 2; j++) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
long carry = h[i] >> (i % 2 == 0 ? 26 : 25);
|
||||
h[i] &= (i % 2 == 0 ? 0x3FFFFFFL : 0x1FFFFFFL);
|
||||
h[i+1] += carry;
|
||||
}
|
||||
long carry = h[9] >> 25;
|
||||
h[9] &= 0x1FFFFFFL;
|
||||
h[0] += carry * 19;
|
||||
}
|
||||
long[] hplus19 = Arrays.copyOf(h, 10);
|
||||
hplus19[0] += 19;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
long carry = hplus19[i] >> (i % 2 == 0 ? 26 : 25);
|
||||
hplus19[i] &= (i % 2 == 0 ? 0x3FFFFFFL : 0x1FFFFFFL);
|
||||
hplus19[i+1] += carry;
|
||||
}
|
||||
long carry = hplus19[9] >> 25;
|
||||
hplus19[9] &= 0x1FFFFFFL;
|
||||
long mask = -(carry & 1);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
h[i] ^= mask & (h[i] ^ hplus19[i]);
|
||||
}
|
||||
byte[] out = new byte[32];
|
||||
int bitIdx = 0;
|
||||
for (int limbIdx = 0; limbIdx < 10; limbIdx++) {
|
||||
int limbBitSize = (limbIdx % 2 == 0 ? 26 : 25);
|
||||
for (int i = 0; i < limbBitSize; i++) {
|
||||
int bit = (int) ((h[limbIdx] >> i) & 1);
|
||||
out[bitIdx / 8] |= (byte) (bit << (bitIdx % 8));
|
||||
bitIdx++;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void fe_carry(long[] h) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
long carry = h[i] >> (i % 2 == 0 ? 26 : 25);
|
||||
h[i] &= (i % 2 == 0 ? 0x3FFFFFFL : 0x1FFFFFFL);
|
||||
h[i+1] += carry;
|
||||
}
|
||||
long carry = h[9] >> 25;
|
||||
h[9] &= 0x1FFFFFFL;
|
||||
h[0] += carry * 19;
|
||||
}
|
||||
|
||||
private static void fe_invert(long[] out, long[] z, Scratch s) {
|
||||
long[] z2 = new long[10], z9 = new long[10], z11 = new long[10], t0 = new long[10], t1 = new long[10], t2 = new long[10];
|
||||
fe_sq(z2, z, s.t19); fe_sq(t1, z2, s.t19); fe_sq(t0, t1, s.t19); fe_mul(z9, t0, z, s.t19); fe_mul(z11, z9, z2, s.t19);
|
||||
fe_sq(t0, z11, s.t19); fe_mul(t2, t0, z9, s.t19);
|
||||
fe_sq(t0, t2, s.t19); for (int i = 1; i < 5; i++) { fe_sq(t1, t0, s.t19); fe_copy(t0, t1); } fe_mul(t1, t0, t2, s.t19);
|
||||
fe_sq(t0, t1, s.t19); for (int i = 1; i < 10; i++) { fe_sq(t2, t0, s.t19); fe_copy(t0, t2); } fe_mul(t2, t0, t1, s.t19);
|
||||
fe_sq(t0, t2, s.t19); for (int i = 1; i < 20; i++) { fe_sq(t1, t0, s.t19); fe_copy(t0, t1); } fe_mul(t0, t0, t2, s.t19);
|
||||
fe_sq(t1, t0, s.t19); for (int i = 1; i < 10; i++) { fe_sq(t0, t1, s.t19); fe_copy(t1, t0); } fe_mul(t1, t1, t2, s.t19);
|
||||
fe_sq(t0, t1, s.t19); for (int i = 1; i < 50; i++) { fe_sq(t2, t0, s.t19); fe_copy(t0, t2); } fe_mul(t2, t0, t1, s.t19);
|
||||
fe_sq(t0, t2, s.t19); for (int i = 1; i < 100; i++) { fe_sq(t1, t0, s.t19); fe_copy(t0, t1); } fe_mul(t0, t0, t2, s.t19);
|
||||
fe_sq(t1, t0, s.t19); for (int i = 1; i < 50; i++) { fe_sq(t0, t1, s.t19); fe_copy(t1, t0); } fe_mul(t1, t1, t2, s.t19);
|
||||
fe_sq(t0, t1, s.t19); for (int i = 1; i < 5; i++) { fe_sq(t1, t0, s.t19); fe_copy(t0, t1); } fe_mul(out, t0, z11, s.t19);
|
||||
}
|
||||
|
||||
private static void fe_pow22523(long[] out, long[] z, Scratch s) {
|
||||
long[] t0 = new long[10], t1 = new long[10], t2 = new long[10];
|
||||
fe_sq(t0, z, s.t19); fe_sq(t1, t0, s.t19); fe_sq(t1, t1, s.t19); fe_mul(t1, t1, z, s.t19); fe_mul(t0, t0, t1, s.t19);
|
||||
fe_sq(t0, t0, s.t19); fe_mul(t0, t0, t1, s.t19); fe_sq(t1, t0, s.t19);
|
||||
for (int i = 1; i < 5; i++) { fe_sq(t2, t1, s.t19); fe_copy(t1, t2); } fe_mul(t1, t1, t0, s.t19);
|
||||
fe_sq(t2, t1, s.t19); for (int i = 1; i < 10; i++) { fe_sq(t0, t2, s.t19); fe_copy(t2, t0); } fe_mul(t2, t2, t1, s.t19);
|
||||
fe_sq(t0, t2, s.t19); for (int i = 1; i < 20; i++) { fe_sq(t1, t0, s.t19); fe_copy(t0, t1); } fe_mul(t0, t0, t2, s.t19);
|
||||
fe_sq(t1, t0, s.t19); for (int i = 1; i < 10; i++) { fe_sq(t0, t1, s.t19); fe_copy(t1, t0); } fe_mul(t1, t1, t2, s.t19);
|
||||
fe_sq(t0, t1, s.t19); for (int i = 1; i < 50; i++) { fe_sq(t2, t0, s.t19); fe_copy(t0, t2); } fe_mul(t2, t0, t1, s.t19);
|
||||
fe_sq(t0, t2, s.t19); for (int i = 1; i < 100; i++) { fe_sq(t1, t0, s.t19); fe_copy(t0, t1); } fe_mul(t0, t0, t2, s.t19);
|
||||
fe_sq(t1, t0, s.t19); for (int i = 1; i < 50; i++) { fe_sq(t0, t1, s.t19); fe_copy(t1, t0); } fe_mul(t1, t1, t2, s.t19);
|
||||
fe_sq(t0, t1, s.t19); for (int i = 1; i < 2; i++) { fe_sq(t1, t0, s.t19); fe_copy(t0, t1); } fe_mul(out, t0, z, s.t19);
|
||||
}
|
||||
|
||||
private static boolean fe_isnonzero(long[] h) {
|
||||
byte[] b = fe_contract(h);
|
||||
int res = 0;
|
||||
for (byte x : b) res |= x;
|
||||
return res != 0;
|
||||
}
|
||||
|
||||
private static int fe_isnegative(long[] h) {
|
||||
byte[] b = fe_contract(h);
|
||||
return b[0] & 1;
|
||||
}
|
||||
|
||||
private static void reduce(byte[] s) {
|
||||
long s0 = 2097151 & load3(s, 0);
|
||||
long s1 = 2097151 & (load4(s, 2) >> 5);
|
||||
long s2 = 2097151 & (load3(s, 5) >> 2);
|
||||
long s3 = 2097151 & (load4(s, 7) >> 7);
|
||||
long s4 = 2097151 & (load4(s, 10) >> 4);
|
||||
long s5 = 2097151 & (load3(s, 13) >> 1);
|
||||
long s6 = 2097151 & (load4(s, 15) >> 6);
|
||||
long s7 = 2097151 & (load3(s, 18) >> 3);
|
||||
long s8 = 2097151 & load3(s, 21);
|
||||
long s9 = 2097151 & (load4(s, 23) >> 5);
|
||||
long s10 = 2097151 & (load3(s, 26) >> 2);
|
||||
long s11 = 2097151 & (load4(s, 28) >> 7);
|
||||
long s12 = 2097151 & (load4(s, 31) >> 4);
|
||||
long s13 = 2097151 & (load3(s, 34) >> 1);
|
||||
long s14 = 2097151 & (load4(s, 36) >> 6);
|
||||
long s15 = 2097151 & (load3(s, 39) >> 3);
|
||||
long s16 = 2097151 & load3(s, 42);
|
||||
long s17 = 2097151 & (load4(s, 44) >> 5);
|
||||
long s18 = 2097151 & (load3(s, 47) >> 2);
|
||||
long s19 = 2097151 & (load4(s, 49) >> 7);
|
||||
long s20 = 2097151 & (load4(s, 52) >> 4);
|
||||
long s21 = 2097151 & (load3(s, 55) >> 1);
|
||||
long s22 = 2097151 & (load4(s, 57) >> 6);
|
||||
long s23 = (load4(s, 60) >> 3);
|
||||
|
||||
s11 += s23 * 666643; s12 += s23 * 470296; s13 += s23 * 654183; s14 -= s23 * 997805; s15 += s23 * 136657; s16 -= s23 * 683901;
|
||||
s10 += s22 * 666643; s11 += s22 * 470296; s12 += s22 * 654183; s13 -= s22 * 997805; s14 += s22 * 136657; s15 -= s22 * 683901;
|
||||
s9 += s21 * 666643; s10 += s21 * 470296; s11 += s21 * 654183; s12 -= s21 * 997805; s13 += s21 * 136657; s14 -= s21 * 683901;
|
||||
s8 += s20 * 666643; s9 += s20 * 470296; s10 += s20 * 654183; s11 -= s20 * 997805; s12 += s20 * 136657; s13 -= s20 * 683901;
|
||||
s7 += s19 * 666643; s8 += s19 * 470296; s9 += s19 * 654183; s10 -= s19 * 997805; s11 += s19 * 136657; s12 -= s19 * 683901;
|
||||
s6 += s18 * 666643; s7 += s18 * 470296; s8 += s18 * 654183; s9 -= s18 * 997805; s10 += s18 * 136657; s11 -= s18 * 683901;
|
||||
|
||||
long c6 = (s6 + (1 << 20)) >> 21; s7 += c6; s6 -= c6 << 21;
|
||||
long c8 = (s8 + (1 << 20)) >> 21; s9 += c8; s8 -= c8 << 21;
|
||||
long c10 = (s10 + (1 << 20)) >> 21; s11 += c10; s10 -= c10 << 21;
|
||||
long c12 = (s12 + (1 << 20)) >> 21; s13 += c12; s12 -= c12 << 21;
|
||||
long c14 = (s14 + (1 << 20)) >> 21; s15 += c14; s14 -= c14 << 21;
|
||||
long c16 = (s16 + (1 << 20)) >> 21; s17 += c16; s16 -= c16 << 21;
|
||||
long c7 = (s7 + (1 << 20)) >> 21; s8 += c7; s7 -= c7 << 21;
|
||||
long c9 = (s9 + (1 << 20)) >> 21; s10 += c9; s9 -= c9 << 21;
|
||||
long c11 = (s11 + (1 << 20)) >> 21; s12 += c11; s11 -= c11 << 21;
|
||||
long c13 = (s13 + (1 << 20)) >> 21; s14 += c13; s13 -= c13 << 21;
|
||||
long c15 = (s15 + (1 << 20)) >> 21; s16 += c15; s15 -= c15 << 21;
|
||||
|
||||
s5 += s17 * 666643; s6 += s17 * 470296; s7 += s17 * 654183; s8 -= s17 * 997805; s9 += s17 * 136657; s10 -= s17 * 683901;
|
||||
s4 += s16 * 666643; s5 += s16 * 470296; s6 += s16 * 654183; s7 -= s16 * 997805; s8 += s16 * 136657; s9 -= s16 * 683901;
|
||||
s3 += s15 * 666643; s4 += s15 * 470296; s5 += s15 * 654183; s6 -= s15 * 997805; s7 += s15 * 136657; s8 -= s15 * 683901;
|
||||
s2 += s14 * 666643; s3 += s14 * 470296; s4 += s14 * 654183; s5 -= s14 * 997805; s6 += s14 * 136657; s7 -= s14 * 683901;
|
||||
s1 += s13 * 666643; s2 += s13 * 470296; s3 += s13 * 654183; s4 -= s13 * 997805; s5 += s13 * 136657; s6 -= s13 * 683901;
|
||||
s0 += s12 * 666643; s1 += s12 * 470296; s2 += s12 * 654183; s3 -= s12 * 997805; s4 += s12 * 136657; s5 -= s12 * 683901;
|
||||
|
||||
long cc0 = (s0 + (1 << 20)) >> 21; s1 += cc0; s0 -= cc0 << 21;
|
||||
long cc2 = (s2 + (1 << 20)) >> 21; s3 += cc2; s2 -= cc2 << 21;
|
||||
long cc4 = (s4 + (1 << 20)) >> 21; s5 += cc4; s4 -= cc4 << 21;
|
||||
long cc6 = (s6 + (1 << 20)) >> 21; s7 += cc6; s6 -= cc6 << 21;
|
||||
long cc8 = (s8 + (1 << 20)) >> 21; s9 += cc8; s8 -= cc8 << 21;
|
||||
long cc10 = (s10 + (1 << 20)) >> 21; s11 += cc10; s10 -= cc10 << 21;
|
||||
long cc1 = (s1 + (1 << 20)) >> 21; s2 += cc1; s1 -= cc1 << 21;
|
||||
long cc3 = (s3 + (1 << 20)) >> 21; s4 += cc3; s3 -= cc3 << 21;
|
||||
long cc5 = (s5 + (1 << 20)) >> 21; s6 += cc5; s5 -= cc5 << 21;
|
||||
long cc7 = (s7 + (1 << 20)) >> 21; s8 += cc7; s7 -= cc7 << 21;
|
||||
long cc9 = (s9 + (1 << 20)) >> 21; s10 += cc9; s9 -= cc9 << 21;
|
||||
long cc11 = (s11 + (1 << 20)) >> 21; long s12_2 = cc11; s11 -= cc11 << 21;
|
||||
|
||||
s0 += s12_2 * 666643; s1 += s12_2 * 470296; s2 += s12_2 * 654183; s3 -= s12_2 * 997805; s4 += s12_2 * 136657; s5 -= s12_2 * 683901;
|
||||
|
||||
long ccc0 = s0 >> 21; s1 += ccc0; s0 -= ccc0 << 21;
|
||||
long ccc1 = s1 >> 21; s2 += ccc1; s1 -= ccc1 << 21;
|
||||
long ccc2 = s2 >> 21; s3 += ccc2; s2 -= ccc2 << 21;
|
||||
long ccc3 = s3 >> 21; s4 += ccc3; s3 -= ccc3 << 21;
|
||||
long ccc4 = s4 >> 21; s5 += ccc4; s4 -= ccc4 << 21;
|
||||
long ccc5 = s5 >> 21; s6 += ccc5; s5 -= ccc5 << 21;
|
||||
long ccc6 = s6 >> 21; s7 += ccc6; s6 -= ccc6 << 21;
|
||||
long ccc7 = s7 >> 21; s8 += ccc7; s7 -= ccc7 << 21;
|
||||
long ccc8 = s8 >> 21; s9 += ccc8; s8 -= ccc8 << 21;
|
||||
long ccc9 = s9 >> 21; s10 += ccc9; s9 -= ccc9 << 21;
|
||||
long ccc10 = s10 >> 21; s11 += ccc10; s10 -= ccc10 << 21;
|
||||
long ccc11 = s11 >> 21; long s12_3 = ccc11; s11 -= ccc11 << 21;
|
||||
|
||||
s0 += s12_3 * 666643; s1 += s12_3 * 470296; s2 += s12_3 * 654183; s3 -= s12_3 * 997805; s4 += s12_3 * 136657; s5 -= s12_3 * 683901;
|
||||
|
||||
long sccc0 = s0 >> 21; s1 += sccc0; s0 -= sccc0 << 21;
|
||||
long sccc1 = s1 >> 21; s2 += sccc1; s1 -= sccc1 << 21;
|
||||
long sccc2 = s2 >> 21; s3 += sccc2; s2 -= sccc2 << 21;
|
||||
long sccc3 = s3 >> 21; s4 += sccc3; s3 -= sccc3 << 21;
|
||||
long sccc4 = s4 >> 21; s5 += sccc4; s4 -= sccc4 << 21;
|
||||
long sccc5 = s5 >> 21; s6 += sccc5; s5 -= sccc5 << 21;
|
||||
|
||||
for (int i = 0; i < 32; i++) s[i] = 0;
|
||||
long[] resLimbs = {s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11};
|
||||
int bitIdx = 0;
|
||||
for (int limbIdx = 0; limbIdx < 12; limbIdx++) {
|
||||
for (int i = 0; i < 21; i++) {
|
||||
if (bitIdx < 256) {
|
||||
int bit = (int) ((resLimbs[limbIdx] >> i) & 1);
|
||||
s[bitIdx / 8] |= (byte) (bit << (bitIdx % 8));
|
||||
bitIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Arrays.fill(s, 32, 64, (byte) 0);
|
||||
}
|
||||
|
||||
private static long load3(byte[] in, int off) {
|
||||
return (in[off] & 0xffL) | ((in[off + 1] & 0xffL) << 8) | ((in[off + 2] & 0xffL) << 16);
|
||||
}
|
||||
|
||||
private static long load4(byte[] in, int off) {
|
||||
return (in[off] & 0xffL) | ((in[off + 1] & 0xffL) << 8) | ((in[off + 2] & 0xffL) << 16) | ((in[off + 3] & 0xffL) << 24);
|
||||
}
|
||||
|
||||
private static void scalarMulAdd(byte[] S, byte[] k, byte[] s, byte[] r) {
|
||||
long[] res = new long[64];
|
||||
for (int i = 0; i < 32; i++) {
|
||||
for (int j = 0; j < 32; j++) {
|
||||
res[i + j] += (k[i] & 0xFFL) * (s[j] & 0xFFL);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 32; i++) {
|
||||
res[i] += (r[i] & 0xFFL);
|
||||
}
|
||||
byte[] buffer = new byte[64];
|
||||
long carry = 0;
|
||||
for (int i = 0; i < 63; i++) {
|
||||
long val = res[i] + carry;
|
||||
buffer[i] = (byte) val;
|
||||
carry = val >>> 8;
|
||||
}
|
||||
buffer[63] = (byte) (res[63] + carry);
|
||||
reduce(buffer);
|
||||
System.arraycopy(buffer, 0, S, 0, 32);
|
||||
}
|
||||
|
||||
public static void testFromBytes(long[] h, byte[] s) {
|
||||
fe_frombytes(h, s);
|
||||
}
|
||||
|
||||
public static byte[] testFeParity(byte[] in) {
|
||||
long[] h = new long[10];
|
||||
fe_frombytes(h, in);
|
||||
return fe_contract(h);
|
||||
}
|
||||
|
||||
public static byte[] testReduce(byte[] in) {
|
||||
byte[] s = Arrays.copyOf(in, 64);
|
||||
reduce(s);
|
||||
return Arrays.copyOf(s, 32);
|
||||
}
|
||||
|
||||
public static byte[] testSmb(byte[] in) {
|
||||
Scratch sc = new Scratch();
|
||||
Ge p = scalarMulBase(in, sc);
|
||||
byte[] out = compress(p, sc);
|
||||
p.wipe(); sc.wipe();
|
||||
return out;
|
||||
}
|
||||
|
||||
public static byte[] testGeAdd(byte[] a, byte[] b) {
|
||||
Scratch sc = new Scratch();
|
||||
Ge p1 = scalarMulBase(a, sc);
|
||||
Ge p2 = scalarMulBase(b, sc);
|
||||
Ge p3 = new Ge();
|
||||
ge_add(p3, p1, p2, sc);
|
||||
byte[] out = compress(p3, sc);
|
||||
p1.wipe(); p2.wipe(); p3.wipe(); sc.wipe();
|
||||
return out;
|
||||
}
|
||||
|
||||
public static byte[] testFeMul(byte[] a, byte[] b) {
|
||||
long[] ha = new long[10], hb = new long[10];
|
||||
fe_frombytes(ha, a);
|
||||
fe_frombytes(hb, b);
|
||||
long[] hr = new long[10];
|
||||
fe_mul(hr, ha, hb, new long[19]);
|
||||
return fe_contract(hr);
|
||||
}
|
||||
|
||||
public static byte[] testFeContract(long[] h) { return fe_contract(h); }
|
||||
public static long[] getBX() { return BX; }
|
||||
public static long[] getBY() { return BY; }
|
||||
public static long[] getD() { return D; }
|
||||
public static long[] getD2() { return D2; }
|
||||
|
||||
private static byte[] sha512(byte[]... parts) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
for (byte[] p : parts) md.update(p);
|
||||
return md.digest();
|
||||
} catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ 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 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 <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
import swiss.qpq.gajumaru.core.tools.CryptoUtils;
|
||||
|
||||
/**
|
||||
* Pure Java implementation of Keccak-256 (Keccak[c=512] sponge).
|
||||
* Based on sha3.erl.
|
||||
*/
|
||||
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
|
||||
private static final int BITRATE_BYTES = BITRATE / 8;
|
||||
|
||||
private static final long[] ROUND_CONSTANTS = {
|
||||
0x0000000000000001L, 0x0000000000008082L, 0x800000000000808AL, 0x8000000080008000L,
|
||||
0x000000000000808BL, 0x0000000080000001L, 0x8000000080008081L, 0x8000000000008009L,
|
||||
0x000000000000008AL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000AL,
|
||||
0x000000008000808BL, 0x800000000000008BL, 0x8000000000008089L, 0x8000000000008003L,
|
||||
0x8000000000008002L, 0x8000000000000080L, 0x000000000000800AL, 0x800000008000000AL,
|
||||
0x8000000080008081L, 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L
|
||||
};
|
||||
|
||||
private static final int[] ROTATION_OFFSETS = {
|
||||
0, 1, 62, 28, 27,
|
||||
36, 44, 6, 55, 20,
|
||||
3, 10, 43, 25, 39,
|
||||
41, 45, 15, 21, 8,
|
||||
18, 2, 61, 56, 14
|
||||
};
|
||||
|
||||
private Keccak256() {}
|
||||
|
||||
/**
|
||||
* Computes the Keccak-256 hash of the input data.
|
||||
*/
|
||||
public static byte[] hash(byte[] data) {
|
||||
long[] state = new long[STATE_SIZE];
|
||||
|
||||
// Padding: Keccak[c=512] uses 0x01 ... 0x80 (or just 0x01 for non-NIST Keccak)
|
||||
// Erlang code uses: keccak(Capacity, Message, <<>>, OutputBitLength)
|
||||
// Pad logic in Erlang: <<Msg/bitstring, Delimiter/bitstring, 1:1, 0:PadZeros, 1:1>>
|
||||
// With Delimiter = <<>>, it becomes <<1:1, 0:PadZeros, 1:1>> (standard Keccak padding)
|
||||
|
||||
byte[] padded = pad(data, BITRATE_BYTES);
|
||||
|
||||
for (int i = 0; i < padded.length; i += BITRATE_BYTES) {
|
||||
absorb(state, padded, i);
|
||||
}
|
||||
|
||||
byte[] result = squeeze(state, 32);
|
||||
|
||||
// Memory hygiene
|
||||
Arrays.fill(state, 0L);
|
||||
CryptoUtils.wipe(padded);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] pad(byte[] data, int rateBytes) {
|
||||
int mLen = data.length;
|
||||
int padLen = rateBytes - (mLen % rateBytes);
|
||||
|
||||
byte[] padded = new byte[mLen + padLen];
|
||||
System.arraycopy(data, 0, padded, 0, mLen);
|
||||
|
||||
if (padLen == 1) {
|
||||
padded[mLen] = (byte) 0x81;
|
||||
} else {
|
||||
padded[mLen] = (byte) 0x01;
|
||||
padded[padded.length - 1] |= (byte) 0x80;
|
||||
}
|
||||
return padded;
|
||||
}
|
||||
|
||||
private static void absorb(long[] state, byte[] data, int offset) {
|
||||
for (int i = 0; i < BITRATE_BYTES / 8; i++) {
|
||||
state[i] ^= readLongLittleEndian(data, offset + i * 8);
|
||||
}
|
||||
keccakF(state);
|
||||
}
|
||||
|
||||
private static byte[] squeeze(long[] state, int len) {
|
||||
byte[] result = new byte[len];
|
||||
int count = 0;
|
||||
while (count < len) {
|
||||
for (int i = 0; i < BITRATE_BYTES / 8 && count < len; i++) {
|
||||
writeLongLittleEndian(state[i], result, count);
|
||||
count += 8;
|
||||
}
|
||||
if (count < len) {
|
||||
keccakF(state);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void keccakF(long[] a) {
|
||||
for (int round = 0; round < 24; round++) {
|
||||
// Theta
|
||||
long[] c = new long[5];
|
||||
for (int x = 0; x < 5; x++) {
|
||||
c[x] = a[x] ^ a[x + 5] ^ a[x + 10] ^ a[x + 15] ^ a[x + 20];
|
||||
}
|
||||
for (int x = 0; x < 5; x++) {
|
||||
long d = c[(x + 4) % 5] ^ Long.rotateLeft(c[(x + 1) % 5], 1);
|
||||
for (int y = 0; y < 5; y++) {
|
||||
a[x + y * 5] ^= d;
|
||||
}
|
||||
}
|
||||
|
||||
// Rho and Pi
|
||||
long[] nextA = new long[STATE_SIZE];
|
||||
for (int x = 0; x < 5; x++) {
|
||||
for (int y = 0; y < 5; y++) {
|
||||
int index = x + y * 5;
|
||||
nextA[y + ((2 * x + 3 * y) % 5) * 5] = Long.rotateLeft(a[index], ROTATION_OFFSETS[index]);
|
||||
}
|
||||
}
|
||||
|
||||
// Chi
|
||||
for (int y = 0; y < 5; y++) {
|
||||
long[] row = new long[5];
|
||||
for (int x = 0; x < 5; x++) {
|
||||
row[x] = nextA[x + y * 5];
|
||||
}
|
||||
for (int x = 0; x < 5; x++) {
|
||||
nextA[x + y * 5] = row[x] ^ ((~row[(x + 1) % 5]) & row[(x + 2) % 5]);
|
||||
}
|
||||
}
|
||||
|
||||
// Iota
|
||||
nextA[0] ^= ROUND_CONSTANTS[round];
|
||||
|
||||
System.arraycopy(nextA, 0, a, 0, STATE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
private static long readLongLittleEndian(byte[] b, int offset) {
|
||||
long res = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
res |= ((long) (b[offset + i] & 0xFF)) << (i * 8);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void writeLongLittleEndian(long v, byte[] b, int offset) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
b[offset + i] = (byte) ((v >>> (i * 8)) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ 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 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 <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.data;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Port of gmser_id.erl.
|
||||
* Represents a 33-byte identifier (1-byte tag + 32-byte value).
|
||||
*/
|
||||
public final class Id {
|
||||
|
||||
public enum Tag {
|
||||
ACCOUNT(1),
|
||||
NAME(2),
|
||||
COMMITMENT(3),
|
||||
CONTRACT(5),
|
||||
CHANNEL(6),
|
||||
ASSOCIATE_CHAIN(7),
|
||||
NATIVE_TOKEN(8),
|
||||
ENTRY(9);
|
||||
|
||||
public final int value;
|
||||
|
||||
Tag(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private static final Map<Integer, Tag> VALUE_MAP = new HashMap<>();
|
||||
static {
|
||||
for (Tag t : Tag.values()) {
|
||||
VALUE_MAP.put(t.value, t);
|
||||
}
|
||||
}
|
||||
|
||||
public static Tag fromValue(int v) {
|
||||
Tag t = VALUE_MAP.get(v);
|
||||
if (t == null) throw new IllegalArgumentException("Unknown ID tag value: " + v);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
private final Tag tag;
|
||||
private final byte[] value;
|
||||
|
||||
public Id(Tag tag, byte[] value) {
|
||||
if (value.length != 32) {
|
||||
throw new IllegalArgumentException("ID value must be exactly 32 bytes");
|
||||
}
|
||||
this.tag = tag;
|
||||
this.value = Arrays.copyOf(value, 32);
|
||||
}
|
||||
|
||||
public Tag getTag() { return tag; }
|
||||
public byte[] getValue() { return Arrays.copyOf(value, 32); }
|
||||
|
||||
/**
|
||||
* Serializes the ID to a 33-byte array.
|
||||
*/
|
||||
public byte[] serialize() {
|
||||
byte[] result = new byte[33];
|
||||
result[0] = (byte) tag.value;
|
||||
System.arraycopy(value, 0, result, 1, 32);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a 33-byte array into an Id object.
|
||||
*/
|
||||
public static Id deserialize(byte[] data) {
|
||||
if (data.length != 33) {
|
||||
throw new IllegalArgumentException("Serialized ID must be exactly 33 bytes");
|
||||
}
|
||||
Tag tag = Tag.fromValue(data[0] & 0xFF);
|
||||
byte[] value = Arrays.copyOfRange(data, 1, 33);
|
||||
return new Id(tag, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Id)) return false;
|
||||
Id id = (Id) o;
|
||||
return tag == id.tag && Arrays.equals(value, id.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = tag.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(value);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ 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 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 <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.encoding;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Port of gmser_api_encoder.erl.
|
||||
* Handles Gajumaru API encoding (prefixed Base58Check).
|
||||
*/
|
||||
public final class ApiEncoder {
|
||||
|
||||
public enum Type {
|
||||
ACCOUNT_PUBKEY("ak", 32),
|
||||
ACCOUNT_SECKEY("sk", 32),
|
||||
TX_HASH("th", 32),
|
||||
CONTRACT_PUBKEY("ct", 32),
|
||||
CHANNEL("ch", 32),
|
||||
SIGNATURE("sg", 64),
|
||||
KEY_BLOCK_HASH("kh", 32),
|
||||
MICRO_BLOCK_HASH("mh", 32),
|
||||
COMMITMENT("cm", 32),
|
||||
PEER_PUBKEY("pp", 32),
|
||||
NAME("nm", -1); // Variable size
|
||||
|
||||
public final String prefix;
|
||||
public final int size;
|
||||
|
||||
Type(String prefix, int size) {
|
||||
this.prefix = prefix;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, Type> PREFIX_MAP = new HashMap<>();
|
||||
static {
|
||||
for (Type t : Type.values()) {
|
||||
PREFIX_MAP.put(t.prefix, t);
|
||||
}
|
||||
}
|
||||
|
||||
private ApiEncoder() {}
|
||||
|
||||
/**
|
||||
* Encodes a payload with the given type and prefix.
|
||||
*/
|
||||
public static String encode(Type type, byte[] payload) {
|
||||
if (type.size != -1 && payload.length != type.size) {
|
||||
throw new IllegalArgumentException("Invalid payload size for " + type + ": " + payload.length);
|
||||
}
|
||||
return type.prefix + "_" + Base58.checkEncode(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a prefixed Base58Check string.
|
||||
*/
|
||||
public static DecodeResult decode(String input) {
|
||||
String[] parts = input.split("_");
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("Invalid encoded string format");
|
||||
}
|
||||
|
||||
Type type = PREFIX_MAP.get(parts[0]);
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Unknown prefix: " + parts[0]);
|
||||
}
|
||||
|
||||
byte[] payload = Base58.checkDecode(parts[1]);
|
||||
if (type.size != -1 && payload.length != type.size) {
|
||||
throw new IllegalArgumentException("Invalid decoded payload size for " + type + ": " + payload.length);
|
||||
}
|
||||
|
||||
return new DecodeResult(type, payload);
|
||||
}
|
||||
|
||||
public record DecodeResult(Type type, byte[] payload) {}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2026 QPQ 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 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 <craigeverett@qpq.swiss>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
|
||||
*/
|
||||
|
||||
package swiss.qpq.gajumaru.core.tools;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Utility class for cryptographic operations and memory hygiene.
|
||||
*/
|
||||
public final class CryptoUtils {
|
||||
|
||||
private CryptoUtils() {}
|
||||
|
||||
/**
|
||||
* Wipes a byte array by filling it with zeros.
|
||||
*
|
||||
* @param data The byte array to wipe.
|
||||
*/
|
||||
public static void wipe(byte[] data) {
|
||||
if (data != null) {
|
||||
Arrays.fill(data, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipes a long array by filling it with zeros.
|
||||
*/
|
||||
public static void wipe(long[] data) {
|
||||
if (data != null) {
|
||||
Arrays.fill(data, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array to a hex string.
|
||||
*/
|
||||
public static String binToHex(byte[] data) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : data) {
|
||||
sb.append(String.format("%02x", b & 0xff));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a hex string to a byte array.
|
||||
*/
|
||||
public static byte[] hexToBin(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import swiss.qpq.gajumaru.core.crypto.*;
|
||||
import swiss.qpq.gajumaru.core.tools.*;
|
||||
|
||||
public class DoubleCheck {
|
||||
public static void main(String[] args) {
|
||||
// Base point limbs are already in Ed25519.java but private
|
||||
// I'll just run one step of publicKey with a simple scalar.
|
||||
byte[] seed = new byte[32]; // All zeros
|
||||
byte[] pub = Ed25519.publicKey(seed);
|
||||
System.out.println("Pub: " + CryptoUtils.binToHex(pub));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import swiss.qpq.gajumaru.core.crypto.*;
|
||||
import swiss.qpq.gajumaru.core.encoding.*;
|
||||
import swiss.qpq.gajumaru.core.data.*;
|
||||
import swiss.qpq.gajumaru.core.tools.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class TestRunner {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
if (args.length == 0) {
|
||||
System.out.println("Usage: java TestRunner <command> <input>");
|
||||
return;
|
||||
}
|
||||
|
||||
String cmd = args[0];
|
||||
switch (cmd) {
|
||||
case "keccak":
|
||||
byte[] kInput = CryptoUtils.hexToBin(args[1]);
|
||||
System.out.println(CryptoUtils.binToHex(Keccak256.hash(kInput)));
|
||||
break;
|
||||
case "blake2b":
|
||||
byte[] bInput = CryptoUtils.hexToBin(args[1]);
|
||||
System.out.println(CryptoUtils.binToHex(Blake2b.hash(bInput)));
|
||||
break;
|
||||
case "ak_encode":
|
||||
byte[] akInput = CryptoUtils.hexToBin(args[1]);
|
||||
System.out.println(ApiEncoder.encode(ApiEncoder.Type.ACCOUNT_PUBKEY, akInput));
|
||||
break;
|
||||
case "ed25519_pub":
|
||||
byte[] seed = CryptoUtils.hexToBin(args[1]);
|
||||
System.out.println(CryptoUtils.binToHex(Ed25519.publicKey(seed)));
|
||||
break;
|
||||
case "id_serialize":
|
||||
int tag = Integer.parseInt(args[1]);
|
||||
byte[] val = CryptoUtils.hexToBin(args[2]);
|
||||
Id id = new Id(Id.Tag.fromValue(tag), val);
|
||||
System.out.println(CryptoUtils.binToHex(id.serialize()));
|
||||
break;
|
||||
default:
|
||||
System.out.println("Unknown command: " + cmd);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+420
-11
@@ -19,13 +19,29 @@
|
||||
%%% Logic
|
||||
|
||||
mods() ->
|
||||
#{"base64" => fun base64/0,
|
||||
"base58" => fun base58/0,
|
||||
"base58_check" => fun base58_check/0,
|
||||
"rlp" => fun rlp/0,
|
||||
"rlp_stream" => fun rlp_stream/0,
|
||||
"rlp_fail" => fun rlp_fail/0,
|
||||
"gaju_format" => fun gaju_format/0}.
|
||||
#{"base64" => fun base64/0,
|
||||
"base58" => fun base58/0,
|
||||
"base58_check" => fun base58_check/0,
|
||||
"rlp" => fun rlp/0,
|
||||
"rlp_stream" => fun rlp_stream/0,
|
||||
"rlp_fail" => fun rlp_fail/0,
|
||||
"gaju_format" => fun gaju_format/0,
|
||||
"keccak256" => fun keccak256/0,
|
||||
"blake2b" => fun blake2b/0,
|
||||
"ed25519" => fun ed25519/0,
|
||||
"api_encode" => fun api_encode/0,
|
||||
"id_serialization" => fun id_serialization/0,
|
||||
"fe_parity" => fun fe_parity/0,
|
||||
"reduce_parity" => fun reduce_parity/0,
|
||||
"smb_parity" => fun smb_parity/0,
|
||||
"femul_parity" => fun femul_parity/0,
|
||||
"debug_femul" => fun debug_femul/0,
|
||||
"frombytes_parity" => fun frombytes_parity/0,
|
||||
"ge_parity" => fun ge_parity/0,
|
||||
"gen_consts" => fun gen_consts/0,
|
||||
"check_product_limbs" => fun check_product_limbs/0,
|
||||
"check_limbs" => fun check_limbs/0,
|
||||
"gen_coeffs" => fun gen_coeffs/0}.
|
||||
|
||||
|
||||
start([]) ->
|
||||
@@ -60,10 +76,11 @@ run(Tests) ->
|
||||
"test" -> file:set_cwd("..");
|
||||
_ -> ok
|
||||
end,
|
||||
|
||||
ok = clean(),
|
||||
ok = build(),
|
||||
Results = maps:map(fun run/2, Tests),
|
||||
io:format("Results:~n ~tp~n", [Results]).
|
||||
io:format("~nFinal Results:~n ~tp~n", [Results]).
|
||||
|
||||
run(Name, Test) ->
|
||||
ok = io:format("~nRunning: ~ts...~n", [Name]),
|
||||
@@ -115,7 +132,7 @@ base64() ->
|
||||
{ok, EDecB} = file:read_file(TestFile),
|
||||
{ok, JDecB} = file:read_file(JDec),
|
||||
EBinHash = crypto:hash(sha512, EDecB),
|
||||
JBinHash = crypto:hash(sha512, JDecB),
|
||||
JBinHash = crypto:hash(sha512, JEncB),
|
||||
EHash =:= JHash andalso EBinHash =:= JBinHash.
|
||||
|
||||
|
||||
@@ -192,11 +209,11 @@ rlp() ->
|
||||
JHash = crypto:hash(sha512, JEncB),
|
||||
EHash =:= JHash andalso Found =:= unicode:characters_to_list(Anchor);
|
||||
{error, R} ->
|
||||
io:format("Failed to read RLP Java result: ~tp (Path: ~tp)~n", [R, JPathTrimmed]),
|
||||
ok = io:format("Failed to read RLP Java result: ~tp (Path: ~tp)~n", [R, JPathTrimmed]),
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
io:format("RLP output mismatch: ~tp~n", [Out]),
|
||||
ok = io:format("RLP output mismatch: ~tp~n", [Out]),
|
||||
false
|
||||
end.
|
||||
|
||||
@@ -247,6 +264,398 @@ gaju_format() ->
|
||||
end.
|
||||
|
||||
|
||||
keccak256() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "keccak256.test"),
|
||||
ResFile = filename:join(Temp, "keccak256.erlang.txt"),
|
||||
Data = rand:bytes(rand:uniform(5000)),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
ok = file:write_file(TestFile, Data),
|
||||
Hash = sha3:hash(256, Data),
|
||||
HexHash = bin_to_hex(Hash),
|
||||
ok = file:write_file(ResFile, HexHash),
|
||||
Run = "bin/run keccak256 " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOut} = file:read_file(trim(Out)),
|
||||
unicode:characters_to_list(JOut) =:= HexHash.
|
||||
|
||||
|
||||
blake2b() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "blake2b.test"),
|
||||
ResFile = filename:join(Temp, "blake2b.erlang.txt"),
|
||||
Data = rand:bytes(rand:uniform(5000)),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
ok = file:write_file(TestFile, Data),
|
||||
{ok, Hash} = eblake2:blake2b(32, Data),
|
||||
HexHash = bin_to_hex(Hash),
|
||||
ok = file:write_file(ResFile, HexHash),
|
||||
Run = "bin/run blake2b " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOut} = file:read_file(trim(Out)),
|
||||
unicode:characters_to_list(JOut) =:= HexHash.
|
||||
|
||||
|
||||
ed25519() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "ed25519.test"),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
Seeds = [rand:bytes(32) || _ <- lists:seq(1, 10)],
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[bin_to_hex(S), "\n"] || S <- Seeds])),
|
||||
|
||||
Expected = [ed25519_pub(S) || S <- Seeds],
|
||||
|
||||
Run = "bin/run ed25519 " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
|
||||
case length(Expected) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(Expected, JResults)) of
|
||||
true -> true;
|
||||
false ->
|
||||
lists:foreach(fun({E, J}) ->
|
||||
if E =/= J -> io:format("E: ~ts~nJ: ~ts~n", [E, J]); true -> ok end
|
||||
end, lists:zip(Expected, JResults)),
|
||||
false
|
||||
end.
|
||||
|
||||
ed25519_pub(Seed) ->
|
||||
Hash = crypto:hash(sha512, Seed),
|
||||
<<K:32/binary, _/binary>> = Hash,
|
||||
% Clamp K
|
||||
<<K0, KMid:30/binary, K31>> = K,
|
||||
CK0 = K0 band 248,
|
||||
CK31 = (K31 band 127) bor 64,
|
||||
ClampedK = <<CK0, KMid/binary, CK31>>,
|
||||
Pub = ecu_ed25519:scalar_mul_base(ClampedK),
|
||||
bin_to_hex(ecu_ed25519:compress(Pub)).
|
||||
|
||||
|
||||
api_encode() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "api_encode.test"),
|
||||
Types = [account_pubkey, account_seckey, tx_hash, contract_pubkey, signature, commitment, peer_pubkey],
|
||||
Cases = [{T, rand:bytes(type_size(T))} || T <- Types],
|
||||
Lines = [io_lib:format("~ts|~ts", [string:uppercase(atom_to_list(T)), bin_to_hex(B)]) || {T, B} <- Cases],
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[L, "\n"] || L <- Lines])),
|
||||
|
||||
Expected = [flatten(gmser_api_encoder:encode(T, B)) || {T, B} <- Cases],
|
||||
|
||||
Run = "bin/run api_encode " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
length(Expected) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(Expected, JResults)).
|
||||
|
||||
type_size(signature) -> 64;
|
||||
type_size(_) -> 32.
|
||||
|
||||
|
||||
id_serialization() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "id_serialization.test"),
|
||||
Tags = [1, 2, 3, 5, 6, 7, 9],
|
||||
Cases = [{Tag, rand:bytes(32)} || Tag <- Tags],
|
||||
Lines = [io_lib:format("~b|~ts", [Tag, bin_to_hex(B)]) || {Tag, B} <- Cases],
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[L, "\n"] || L <- Lines])),
|
||||
|
||||
Expected = [bin_to_hex(gmser_id:encode(gmser_id:create(tag_to_erl(T), B))) || {T, B} <- Cases],
|
||||
|
||||
Run = "bin/run id_serialization " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
length(Expected) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(Expected, JResults)).
|
||||
|
||||
fe_parity() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "fe.test"),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
Inputs = [<<(rand:bytes(31))/binary, 0>> || _ <- lists:seq(1, 100)],
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[bin_to_hex(I), "\n"] || I <- Inputs])),
|
||||
|
||||
Run = "bin/run fe_parity " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
|
||||
Expected = [bin_to_hex(I) || I <- Inputs],
|
||||
length(Expected) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(Expected, JResults)).
|
||||
|
||||
reduce_parity() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "reduce.test"),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
Inputs = [rand:bytes(64) || _ <- lists:seq(1, 100)],
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[bin_to_hex(I), "\n"] || I <- Inputs])),
|
||||
|
||||
Run = "bin/run reduce_parity " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
|
||||
Expected = [bin_to_hex(ecu_ed25519:scalar_reduce(I)) || I <- Inputs],
|
||||
case length(Expected) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(Expected, JResults)) of
|
||||
true -> true;
|
||||
false ->
|
||||
lists:foreach(fun({E, J}) ->
|
||||
if E =/= J -> io:format("E: ~ts~nJ: ~ts~n", [E, J]); true -> ok end
|
||||
end, lists:zip(Expected, JResults)),
|
||||
false
|
||||
end.
|
||||
|
||||
smb_parity() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "smb.test"),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
Inputs = [<<(rand:bytes(31))/binary, 0>> || _ <- lists:seq(1, 10)],
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[bin_to_hex(I), "\n"] || I <- Inputs])),
|
||||
|
||||
Run = "bin/run smb_parity " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
|
||||
Expected = [bin_to_hex(ecu_ed25519:compress(ecu_ed25519:scalar_mul_base_noclamp(I))) || I <- Inputs],
|
||||
case length(Expected) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(Expected, JResults)) of
|
||||
true -> true;
|
||||
false ->
|
||||
lists:foreach(fun({E, J}) ->
|
||||
if E =/= J -> io:format("E: ~ts~nJ: ~ts~n", [E, J]); true -> ok end
|
||||
end, lists:zip(Expected, JResults)),
|
||||
false
|
||||
end.
|
||||
|
||||
femul_parity() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "femul.test"),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
Inputs = [{rand:bytes(32), rand:bytes(32)} || _ <- lists:seq(1, 10)],
|
||||
Lines = [bin_to_hex(A) ++ "|" ++ bin_to_hex(B) || {A, B} <- Inputs],
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[L, "\n"] || L <- Lines])),
|
||||
|
||||
Run = "bin/run femul_parity " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
|
||||
ExpectedBin = [bin_to_hex(pack_p(ecu_ed25519:f_mul(binary:decode_unsigned(A, little), binary:decode_unsigned(B, little)))) || {A, B} <- Inputs],
|
||||
|
||||
case length(ExpectedBin) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(ExpectedBin, JResults)) of
|
||||
true -> true;
|
||||
false ->
|
||||
lists:foreach(fun({{A, B}, E, J}) ->
|
||||
if E =/= J ->
|
||||
io:format("A: ~ts~nB: ~ts~nE: ~ts~nJ: ~ts~n", [bin_to_hex(A), bin_to_hex(B), E, J]);
|
||||
true -> ok end
|
||||
end, lists:zip3(Inputs, ExpectedBin, JResults)),
|
||||
false
|
||||
end.
|
||||
|
||||
debug_femul() ->
|
||||
A_bin = <<1, 0:248>>,
|
||||
B_bin = <<1, 1, 0:240>>,
|
||||
A = binary:decode_unsigned(A_bin, little),
|
||||
B = binary:decode_unsigned(B_bin, little),
|
||||
Res = ecu_ed25519:f_mul(A, B),
|
||||
io:format("A (hex): ~ts~n", [bin_to_hex(A_bin)]),
|
||||
io:format("B (hex): ~ts~n", [bin_to_hex(B_bin)]),
|
||||
io:format("Res (hex): ~ts~n", [bin_to_hex(pack_p(Res))]),
|
||||
|
||||
Limbs = fun F(Val, Idx) when Idx < 10 ->
|
||||
Size = if Idx rem 2 =:= 0 -> 26; true -> 25 end,
|
||||
L = Val band ((1 bsl Size) - 1),
|
||||
[L | F(Val bsr Size, Idx + 1)];
|
||||
F(_, _) -> []
|
||||
end,
|
||||
io:format("A limbs: ~w~n", [Limbs(A, 0)]),
|
||||
io:format("B limbs: ~w~n", [Limbs(B, 0)]),
|
||||
io:format("Res limbs: ~w~n", [Limbs(Res, 0)]),
|
||||
true.
|
||||
|
||||
frombytes_parity() ->
|
||||
Temp = temp_dir(),
|
||||
TestFile = filename:join(Temp, "frombytes.test"),
|
||||
ok = filelib:ensure_dir(TestFile),
|
||||
Inputs = [rand:bytes(32) || _ <- lists:seq(1, 10)],
|
||||
ok = file:write_file(TestFile, unicode:characters_to_binary([[bin_to_hex(I), "\n"] || I <- Inputs])),
|
||||
|
||||
Run = "bin/run frombytes_parity " ++ Temp,
|
||||
Out = trim(os:cmd(Run)),
|
||||
{ok, JOutContent} = file:read_file(trim(Out)),
|
||||
JResults = string:split(trim(unicode:characters_to_list(JOutContent)), "\n", all),
|
||||
|
||||
Expected = [
|
||||
begin
|
||||
Val = binary:decode_unsigned(I, little),
|
||||
Limbs = fun F(V, Idx) when Idx < 10 ->
|
||||
Size = if Idx rem 2 =:= 0 -> 26; true -> 25 end,
|
||||
L = V band ((1 bsl Size) - 1),
|
||||
[L | F(V bsr Size, Idx + 1)];
|
||||
F(_, _) -> []
|
||||
end,
|
||||
string:join([integer_to_list(L) || L <- Limbs(Val, 0)], ",")
|
||||
end || I <- Inputs],
|
||||
|
||||
case length(Expected) =:= length(JResults) andalso
|
||||
lists:all(fun({E, J}) -> E =:= J end, lists:zip(Expected, JResults)) of
|
||||
true -> true;
|
||||
false ->
|
||||
lists:foreach(fun({E, J}) ->
|
||||
if E =/= J -> io:format("E: ~ts~nJ: ~ts~n", [E, J]); true -> ok end
|
||||
end, lists:zip(Expected, JResults)),
|
||||
false
|
||||
end.
|
||||
|
||||
ge_parity() ->
|
||||
A_bin = <<1, 2, 3, 0:232>>,
|
||||
B_bin = <<4, 5, 6, 0:232>>,
|
||||
P1_erl = ecu_ed25519:scalar_mul_base_noclamp(A_bin),
|
||||
P2_erl = ecu_ed25519:scalar_mul_base_noclamp(B_bin),
|
||||
P3_erl = ecu_ed25519:p_add(P1_erl, P2_erl),
|
||||
|
||||
E_comp = bin_to_hex(ecu_ed25519:compress(P3_erl)),
|
||||
|
||||
Run = "bin/run ge_parity " ++ bin_to_hex(A_bin) ++ " " ++ bin_to_hex(B_bin),
|
||||
Out = trim(os:cmd(Run)),
|
||||
case string:split(Out, "|||") of
|
||||
[JX | _] -> E_comp =:= trim(JX);
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
gen_consts() ->
|
||||
GetLimbs = fun(Val) ->
|
||||
Limbs = fun F(V, Idx) when Idx < 10 ->
|
||||
Size = if Idx rem 2 =:= 0 -> 26; true -> 25 end,
|
||||
L = V band ((1 bsl Size) - 1),
|
||||
[L | F(V bsr Size, Idx + 1)];
|
||||
F(_, _) -> []
|
||||
end,
|
||||
Limbs(Val, 0)
|
||||
end,
|
||||
|
||||
P = (1 bsl 255) - 19,
|
||||
|
||||
Y = (4 * ecu_ed25519:f_inv(5)) rem P,
|
||||
% X^2 = (y^2 - 1) / (d*y^2 + 1)
|
||||
Y2 = (Y * Y) rem P,
|
||||
D = (P - 121665) * ecu_ed25519:f_inv(121666) rem P,
|
||||
Num = (Y2 - 1 + P) rem P,
|
||||
Den = (D * Y2 + 1) rem P,
|
||||
X2 = (Num * ecu_ed25519:f_inv(Den)) rem P,
|
||||
X = ecu_ed25519:f_pow(X2, (P + 3) bsr 3), % compute square root
|
||||
|
||||
T = (X * Y) rem P,
|
||||
I = ecu_ed25519:f_pow(2, (P - 1) bsr 2),
|
||||
|
||||
io:format("BX: ~w~n", [GetLimbs(X)]),
|
||||
io:format("BY: ~w~n", [GetLimbs(Y)]),
|
||||
io:format("BXY: ~w~n", [GetLimbs(T)]),
|
||||
io:format("D: ~w~n", [GetLimbs(D)]),
|
||||
io:format("D2: ~w~n", [GetLimbs((2 * D) rem P)]),
|
||||
io:format("I: ~w~n", [GetLimbs(I)]),
|
||||
true.
|
||||
|
||||
pack_p(V) ->
|
||||
P = (1 bsl 255) - 19,
|
||||
V_pos = if V < 0 -> V + P; true -> V rem P end,
|
||||
Enc = binary:encode_unsigned(V_pos, little),
|
||||
Size = byte_size(Enc),
|
||||
if Size < 32 -> <<Enc/binary, 0:(8*(32-Size))>>; true -> Enc end.
|
||||
|
||||
check_limbs() ->
|
||||
% Random values
|
||||
A_bin = <<11, 22, 33, 0:232>>,
|
||||
B_bin = <<44, 55, 66, 0:232>>,
|
||||
A = binary:decode_unsigned(A_bin, little),
|
||||
B = binary:decode_unsigned(B_bin, little),
|
||||
Res = A * B,
|
||||
|
||||
Limbs = fun F(Val, Idx) when Idx < 19 ->
|
||||
Size = if Idx rem 2 =:= 0 -> 26; true -> 25 end,
|
||||
L = Val band ((1 bsl Size) - 1),
|
||||
[L | F(Val bsr Size, Idx + 1)];
|
||||
F(_, _) -> []
|
||||
end,
|
||||
io:format("Expected product limbs: ~w~n", [Limbs(Res, 0)]),
|
||||
true.
|
||||
|
||||
check_product_limbs() ->
|
||||
A_bin = <<11, 22, 33, 44, 0:224>>,
|
||||
B_bin = <<55, 66, 77, 88, 0:224>>,
|
||||
A = binary:decode_unsigned(A_bin, little),
|
||||
B = binary:decode_unsigned(B_bin, little),
|
||||
|
||||
GetLimbs = fun(Val) ->
|
||||
Limbs = fun F(V, Idx) when Idx < 10 ->
|
||||
Size = if Idx rem 2 =:= 0 -> 26; true -> 25 end,
|
||||
L = V band ((1 bsl Size) - 1),
|
||||
[L | F(V bsr Size, Idx + 1)];
|
||||
F(_, _) -> []
|
||||
end,
|
||||
list_to_tuple(Limbs(Val, 0))
|
||||
end,
|
||||
|
||||
{A0, A1, A2, A3, A4, A5, A6, A7, A8, A9} = GetLimbs(A),
|
||||
{B0, B1, B2, B3, B4, B5, B6, B7, B8, B9} = GetLimbs(B),
|
||||
|
||||
T0 = A0*B0,
|
||||
T1 = A0*B1 + A1*B0,
|
||||
T2 = 2*A1*B1 + A0*B2 + A2*B0,
|
||||
T3 = A1*B2 + A2*B1 + A0*B3 + A3*B0,
|
||||
T4 = A2*B2 + 2*(A1*B3 + A3*B1) + A0*B4 + A4*B0,
|
||||
|
||||
io:format("T0: ~b, T1: ~b, T2: ~b, T3: ~b, T4: ~b~n", [T0, T1, T2, T3, T4]),
|
||||
true.
|
||||
|
||||
gen_coeffs() ->
|
||||
W = [0, 26, 51, 77, 102, 128, 153, 179, 204, 230],
|
||||
lists:foreach(fun(TargetIdx) ->
|
||||
io:format("Limb ~b: ", [TargetIdx]),
|
||||
_ = [
|
||||
begin
|
||||
Weight = lists:nth(I+1, W) + lists:nth(J+1, W),
|
||||
TargetWeight = lists:nth(TargetIdx+1, W),
|
||||
if Weight >= TargetWeight andalso Weight < TargetWeight + 26 ->
|
||||
Diff = Weight - TargetWeight,
|
||||
io:format("A~b*B~b*~b + ", [I, J, 1 bsl Diff]);
|
||||
true -> ok
|
||||
end
|
||||
end || I <- lists:seq(0, 9), J <- lists:seq(0, 9)],
|
||||
io:format("~n")
|
||||
end, lists:seq(0, 18)),
|
||||
true.
|
||||
|
||||
tag_to_erl(1) -> account;
|
||||
tag_to_erl(2) -> name;
|
||||
tag_to_erl(3) -> commitment;
|
||||
tag_to_erl(5) -> contract;
|
||||
tag_to_erl(6) -> channel;
|
||||
tag_to_erl(7) -> associate_chain;
|
||||
tag_to_erl(8) -> native_token;
|
||||
tag_to_erl(9) -> entry.
|
||||
|
||||
bin_to_hex(Bin) ->
|
||||
lists:flatten([io_lib:format("~2.16.0b", [X]) || X <- binary_to_list(Bin)]).
|
||||
|
||||
hex_to_bin(S) ->
|
||||
hex_to_bin(S, []).
|
||||
hex_to_bin([], Acc) ->
|
||||
list_to_binary(lists:reverse(Acc));
|
||||
hex_to_bin([X,Y|T], Acc) ->
|
||||
{ok, [V], []} = io_lib:fread("~16u", [X,Y]),
|
||||
hex_to_bin(T, [V | Acc]).
|
||||
|
||||
|
||||
|
||||
%%% Generatorators
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
{author,"Craig Everett"}.
|
||||
{desc,"An inter-language test suite for Gajumaru core libs"}.
|
||||
{package_id,{"qpq","gm_libtester",{0,1,0}}}.
|
||||
{deps,[{"otpr","hakuzaru",{0,9,1}},
|
||||
{deps,[{"otpr","sha3",{0,1,4}},
|
||||
{"otpr","hakuzaru",{0,9,1}},
|
||||
{"otpr","getopt",{1,0,2}},
|
||||
{"otpr","zj",{1,1,0}},
|
||||
{"otpr","ec_utils",{1,0,0}},
|
||||
|
||||
Reference in New Issue
Block a user