Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions THIRD-PARTY-LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ This is the list of all third-party dependencies grouped by their license type.
## Apache License, Version 2.0:

* **Jackson-annotations** (com.fasterxml.jackson.core:jackson-annotations:2.21 - https://github.com/FasterXML/jackson)
* **Jackson-core** (com.fasterxml.jackson.core:jackson-core:2.21.1 - https://github.com/FasterXML/jackson-core)
* **jackson-databind** (com.fasterxml.jackson.core:jackson-databind:2.21.1 - https://github.com/FasterXML/jackson)
* **Jackson-core** (com.fasterxml.jackson.core:jackson-core:2.21.2 - https://github.com/FasterXML/jackson-core)
* **jackson-databind** (com.fasterxml.jackson.core:jackson-databind:2.21.2 - https://github.com/FasterXML/jackson)
* **FindBugs-jsr305** (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
* **Gson** (com.google.code.gson:gson:2.13.2 - https://github.com/google/gson)
* **Tink Cryptography API** (com.google.crypto.tink:tink:1.20.0 - http://github.com/tink-crypto/tink-java)
Expand Down Expand Up @@ -49,6 +49,5 @@ This is the list of all third-party dependencies grouped by their license type.

## MIT License:

* **multibase** (com.github.multiformats:java-multibase:1.3.0 - https://github.com/multiformats/java-multibase)
* **DID Resolver** (ch.admin.swiyu:didresolver:2.6.0 - https://github.com/swiyu-admin-ch/didresolver-kotlin)
* **DID Resolver** (ch.admin.swiyu:didresolver:2.7.0 - https://github.com/swiyu-admin-ch/didresolver-kotlin)
* **Project Lombok** (org.projectlombok:lombok:1.18.44 - https://projectlombok.org)
18 changes: 1 addition & 17 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,9 @@
<!--project.dependencies.directory>lib/</project.dependencies.directory-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<didresolver.version>2.6.0</didresolver.version>
<didresolver.version>2.7.0</didresolver.version>
<jna.version>5.18.1</jna.version>
<gson.version>2.13.2</gson.version>
<java-multibase.version>1.3.0</java-multibase.version>
<lombok.version>1.18.44</lombok.version>
<jcommander.version>3.0</jcommander.version>
<bouncycastle.version>1.83</bouncycastle.version>
Expand Down Expand Up @@ -505,15 +504,6 @@
</snapshotRepository>
</distributionManagement-->

<repositories>
<!-- required for https://github.com/multiformats/java-multibase
as described by https://jitpack.io/#multiformats/java-multibase/ -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>

<dependencies>
<dependency>
<!-- CAUTION Until 2.0.1 (GitHub packages), the "groupId" was set to "ch.admin.eid".
Expand All @@ -535,12 +525,6 @@
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.multiformats/java-multibase -->
<dependency>
<groupId>com.github.multiformats</groupId>
<artifactId>java-multibase</artifactId>
<version>${java-multibase.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on -->
<!-- https://www.bouncycastle.org/download/bouncy-castle-java/?filter=java%3Drelease-1-81 -->
<dependency>
Expand Down
161 changes: 161 additions & 0 deletions src/main/java/ch/admin/bj/swiyu/didtoolbox/Base58.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package ch.admin.bj.swiyu.didtoolbox;

import java.util.Arrays;

/**
* Yet another Java implementation of <a href="https://bitcoinwiki.org/wiki/base58#Base58_Encode_and_Decode">base58btc</a> encoder/decoder.
* <p>
* Base58 is a way to encode Bitcoin addresses (or arbitrary data) as alphanumeric strings.
*
* <p>Note that this is different from the base58 as used by Flickr, which you may find referenced around
* the Internet.
*
* <p>Satoshi explains: why base-58 instead of standard base-64 encoding?
*
* <ul>
* <li>Don't want 0OIl characters that look the same in some fonts and could be used to create
* visually identical looking account numbers.
* <li>A string with non-alphanumeric characters is not as easily accepted as an account number.
* <li>E-mail usually won't line-break if there's no punctuation to break at.
* <li>Doubleclicking selects the whole number as one word if it's all alphanumeric.
* </ul>
*
* <p>However, note that the encoding/decoding runs in O(n&sup2;) time, so it is not useful for
* large data.
*
* <p>The basic idea of the encoding is to treat the data bytes as a large number represented using
* base-256 digits, convert the number to be represented using base-58 digits, preserve the exact
* number of leading zeros (which are otherwise lost during the mathematical operations on the
* numbers), and finally represent the resulting base-58 digits as alphanumeric ASCII characters.
*/
public final class Base58 {

public static final char[] ALPHABET =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private static final char ENCODED_ZERO = ALPHABET[0];
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() {
}

/**
* Encodes the given bytes as a base58 string (no checksum is appended).
*
* @param input the bytes to encode
* @return the base58-encoded string
*/
public static String encode(byte[] input) {
if (input.length == 0) {
return "";
}

// Count leading zeros.
int zeros = 0;
while (zeros < input.length && input[zeros] == 0) {
++zeros;
}

// Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
var in = Arrays.copyOf(input, input.length); // since we modify it in-place
char[] encoded = new char[in.length * 2]; // upper bound
int outputStart = encoded.length;
for (int inputStart = zeros; inputStart < in.length; ) {
encoded[--outputStart] = ALPHABET[divmod(in, inputStart, 256, 58)];

Check warning on line 70 in src/main/java/ch/admin/bj/swiyu/didtoolbox/Base58.java

View workflow job for this annotation

GitHub Actions / code-quality-check

Avoid assignment to outputStart in operand

Avoid assignments in operands; this can make code more complicated and harder to read. AssignmentInOperand (Priority: 3, Ruleset: Error Prone) https://docs.pmd-code.org/snapshot/pmd_rules_java_errorprone.html#assignmentinoperand

Check warning

Code scanning / PMD

Avoid assignment to zeros in operand Warning

Avoid assignment to outputStart in operand
if (in[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros

Check warning on line 72 in src/main/java/ch/admin/bj/swiyu/didtoolbox/Base58.java

View workflow job for this annotation

GitHub Actions / code-quality-check

Avoid reassigning the loop control variable 'inputStart'

Reassigning loop variables can lead to hard-to-find bugs. Prevent or limit how these variables can be changed. In foreach-loops, configured by the `foreachReassign` property: - `deny`: Report any reassignment of the loop variable in the loop body. _This is the default._ - `allow`: Don't check the loop variable. - `firstOnly`: Report any reassignments of the loop variable, except as the first statement in the loop body. _This is useful if some kind of normalization or clean-up of the value before using is permitted, but any other change of the variable is not._ In for-loops, configured by the `forReassign` property: - `deny`: Report any reassignment of the control variable in the loop body. _This is the default._ - `allow`: Don't check the control variable. - `skip`: Report any reassignments of the control variable, except conditional increments/decrements (`++`, `--`, `+=`, `-=`). _This prevents accidental reassignments or unconditional increments of the control variable._ AvoidReassigningLoopVariables (Priority: 3, Ruleset: Best Practices) https://docs.pmd-code.org/snapshot/pmd_rules_java_bestpractices.html#avoidreassigningloopvariables
Comment thread Fixed

Check warning

Code scanning / PMD

Avoid reassigning the loop control variable 'inputStart' Warning

Avoid reassigning the loop control variable 'inputStart'
}
}

// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
++outputStart;
}

while (--zeros >= 0) {

Check warning on line 81 in src/main/java/ch/admin/bj/swiyu/didtoolbox/Base58.java

View workflow job for this annotation

GitHub Actions / code-quality-check

Avoid assignment to zeros in operand

Avoid assignments in operands; this can make code more complicated and harder to read. AssignmentInOperand (Priority: 3, Ruleset: Error Prone) https://docs.pmd-code.org/snapshot/pmd_rules_java_errorprone.html#assignmentinoperand
Comment thread Fixed

Check warning

Code scanning / PMD

Avoid assignment to zeros in operand Warning

Avoid assignment to zeros in operand
encoded[--outputStart] = ENCODED_ZERO;

Check warning on line 82 in src/main/java/ch/admin/bj/swiyu/didtoolbox/Base58.java

View workflow job for this annotation

GitHub Actions / code-quality-check

Avoid assignment to outputStart in operand

Avoid assignments in operands; this can make code more complicated and harder to read. AssignmentInOperand (Priority: 3, Ruleset: Error Prone) https://docs.pmd-code.org/snapshot/pmd_rules_java_errorprone.html#assignmentinoperand
Comment thread Fixed

Check warning

Code scanning / PMD

Avoid assignment to zeros in operand Warning

Avoid assignment to outputStart in operand
}

// Return encoded string (including encoded leading zeros).
return new String(encoded, outputStart, encoded.length - outputStart);
}

/**
* Decodes the given base58 string into the original data bytes.
*
* @param input the base58-encoded string to decode
* @return the decoded data bytes
*/
@SuppressWarnings({"PMD.CyclomaticComplexity"})
public static byte[] decode(String input) {
Comment thread Fixed
if (input.isEmpty()) {
return new byte[0];
}

// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
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) {
throw new IllegalArgumentException(
String.format("Invalid character in Base58: 0x%04x", (int) c));
}
input58[i] = (byte) digit;
}

// Count leading zeros.
int zeros = 0;
while (zeros < input58.length && input58[zeros] == 0) {
++zeros;
}

// Convert base-58 digits to base-256 digits.
byte[] decoded = new byte[input.length()];
int outputStart = decoded.length;
for (int inputStart = zeros; inputStart < input58.length; ) {
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);

Check warning on line 123 in src/main/java/ch/admin/bj/swiyu/didtoolbox/Base58.java

View workflow job for this annotation

GitHub Actions / code-quality-check

Avoid assignment to outputStart in operand

Avoid assignments in operands; this can make code more complicated and harder to read. AssignmentInOperand (Priority: 3, Ruleset: Error Prone) https://docs.pmd-code.org/snapshot/pmd_rules_java_errorprone.html#assignmentinoperand

Check warning

Code scanning / PMD

Avoid assignment to zeros in operand Warning

Avoid assignment to outputStart in operand
if (input58[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros

Check warning on line 125 in src/main/java/ch/admin/bj/swiyu/didtoolbox/Base58.java

View workflow job for this annotation

GitHub Actions / code-quality-check

Avoid reassigning the loop control variable 'inputStart'

Reassigning loop variables can lead to hard-to-find bugs. Prevent or limit how these variables can be changed. In foreach-loops, configured by the `foreachReassign` property: - `deny`: Report any reassignment of the loop variable in the loop body. _This is the default._ - `allow`: Don't check the loop variable. - `firstOnly`: Report any reassignments of the loop variable, except as the first statement in the loop body. _This is useful if some kind of normalization or clean-up of the value before using is permitted, but any other change of the variable is not._ In for-loops, configured by the `forReassign` property: - `deny`: Report any reassignment of the control variable in the loop body. _This is the default._ - `allow`: Don't check the control variable. - `skip`: Report any reassignments of the control variable, except conditional increments/decrements (`++`, `--`, `+=`, `-=`). _This prevents accidental reassignments or unconditional increments of the control variable._ AvoidReassigningLoopVariables (Priority: 3, Ruleset: Best Practices) https://docs.pmd-code.org/snapshot/pmd_rules_java_bestpractices.html#avoidreassigningloopvariables
Comment thread Fixed

Check warning

Code scanning / PMD

Avoid reassigning the loop control variable 'inputStart' Warning

Avoid reassigning the loop control variable 'inputStart'
}
}

// Ignore extra leading zeroes that were added during the calculation.
while (outputStart < decoded.length && decoded[outputStart] == 0) {
++outputStart;
}

// Return decoded data (including original number of leading zeros).
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
}

/**
* Divides a number, represented as an array of bytes each containing a single digit in the
* specified base, by the given divisor. The given number is modified in-place to contain the
* quotient, and the return value is the remainder.
*
* @param number the number to divide
* @param firstDigit the index within the array of the first non-zero digit (this is used for
* optimization by skipping the leading zeros)
* @param base the base in which the number's digits are represented (up to 256)
* @param divisor the number to divide by (up to 256)
* @return the remainder of the division operation
*/
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
// this is just long division which accounts for the base of the input digits
int remainder = 0;
for (int i = firstDigit; i < number.length; i++) {
int digit = (int) number[i] & 0xFF;
int temp = remainder * base + digit;
number[i] = (byte) (temp / divisor);
remainder = temp % divisor;
}
return (byte) remainder;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package ch.admin.bj.swiyu.didtoolbox;

import io.ipfs.multibase.Base58;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import io.ipfs.multibase.Base58;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import ch.admin.eid.did_sidekicks.DidSidekicksException;
import ch.admin.eid.did_sidekicks.JcsSha256Hasher;
import com.google.gson.JsonObject;
import io.ipfs.multibase.Base58;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package ch.admin.bj.swiyu.didtoolbox.model;

import ch.admin.bj.swiyu.didtoolbox.Base58;
import ch.admin.bj.swiyu.didtoolbox.Ed25519Utils;
import ch.admin.bj.swiyu.didtoolbox.JCSHasher;
import ch.admin.bj.swiyu.didtoolbox.PemUtils;
import ch.admin.eid.did_sidekicks.DidSidekicksException;
import com.google.gson.JsonArray;
import com.google.gson.JsonPrimitive;
import io.ipfs.multibase.Base58;

import java.io.File;
import java.nio.file.Path;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.admin.bj.swiyu.didtoolbox.vc_data_integrity;

import ch.admin.bj.swiyu.didtoolbox.Base58;
import ch.admin.bj.swiyu.didtoolbox.Ed25519Utils;
import ch.admin.bj.swiyu.didtoolbox.VerificationMethodKeyProvider;
import ch.admin.bj.swiyu.didtoolbox.context.DidLogCreatorContext;
Expand All @@ -8,7 +9,6 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import io.ipfs.multibase.Base58;

import java.io.IOException;
import java.io.InputStream;
Expand Down
76 changes: 76 additions & 0 deletions src/test/java/ch/admin/bj/swiyu/didtoolbox/Base58Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package ch.admin.bj.swiyu.didtoolbox;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.Collection;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;

class Base58Test {

private static Collection<Object[]> data() {
return Arrays.asList(
new Object[][]{
{
hexToBytes("1220120F6AF601D46E10B2D2E11ED71C55D25F3042C22501E41D1246E7A1E9D3D8EC"),
"QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB" // w/out multibase ('z') prefix
},
{
hexToBytes("1220BA8632EF1A07986B171B3C8FAF0F79B3EE01B6C30BBE15A13261AD6CB0D02E3A"),
"QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy" // w/out multibase ('z') prefix
},
{new byte[1], "1"}, // w/out multibase ('z') prefix
{new byte[2], "11"}, // w/out multibase ('z') prefix
{new byte[4], "1111"}, // w/out multibase ('z') prefix
{new byte[8], "11111111"}, // w/out multibase ('z') prefix
{new byte[16], "1111111111111111"}, // w/out multibase ('z') prefix
{new byte[32], "11111111111111111111111111111111"}, // w/out multibase ('z') prefix
{
hexToBytes("446563656e7472616c697a652065766572797468696e67212121"),
"36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt" // w/out multibase ('z') prefix
},
});
}

@MethodSource("data")
@ParameterizedTest(name = "{index}: {0}, {2}")
void testEncode(byte[] raw, String encoded) {
String output = Base58.encode(raw);
assertEquals(encoded, output, String.format("Expected %s, but got %s", bytesToHex(raw), output));
}

@MethodSource("data")
@ParameterizedTest(name = "{index}: {0}, {2}")
void testDecode(byte[] raw, String encoded) {
byte[] output = Base58.decode(encoded);
assertArrayEquals(
raw, output, String.format("Expected %s, but got %s", bytesToHex(raw), bytesToHex(output)));
}

// Copied from https://stackoverflow.com/a/140861
private static byte[] hexToBytes(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;
}

// Copied from https://stackoverflow.com/a/9855338
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

private static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ch.admin.bj.swiyu.didtoolbox;

import io.ipfs.multibase.Base58;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand Down
Loading