com.apicatalog
@@ -39,7 +39,7 @@ Android 12+ (API Level >=32)
```gradle
-implementation("com.apicatalog:carbon-did:0.4.0")
+implementation("com.apicatalog:carbon-did:0.5.0")
implementation("com.apicatalog:copper-multibase:0.5.0")
```
diff --git a/pom.xml b/pom.xml
index c0fc82c..26f19f2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
com.apicatalog
carbon-did
- 0.4.0
+ 0.5.0
jar
https://github.com/filip26/carbon-decentralized-identifiers
diff --git a/src/main/java/com/apicatalog/did/Did.java b/src/main/java/com/apicatalog/did/Did.java
index 4010f23..c1098cb 100644
--- a/src/main/java/com/apicatalog/did/Did.java
+++ b/src/main/java/com/apicatalog/did/Did.java
@@ -4,21 +4,25 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
+import java.util.function.IntPredicate;
public class Did implements Serializable {
private static final long serialVersionUID = -3127318269811273712L;
- public static final String SCHEME = "did";
+ /*
+ * method-char = %x61-7A / DIGIT
+ */
+ static final IntPredicate METHOD_CHAR = ch -> (0x61 <= ch && ch <= 0x7A) || ('0' <= ch && ch <= '9');
+ public static final String SCHEME = "did";
+
protected final String method;
- protected final String version;
- protected final String methodSpecificId;
+ protected final String specificId;
- protected Did(final String method, final String version, final String methodSpecificId) {
+ protected Did(final String method, final String specificId) {
this.method = method;
- this.version = version;
- this.methodSpecificId = methodSpecificId;
+ this.specificId = specificId;
}
public static boolean isDid(final URI uri) {
@@ -29,29 +33,32 @@ public static boolean isDid(final URI uri) {
|| isNotBlank(uri.getHost())
|| isNotBlank(uri.getPath())
|| isNotBlank(uri.getQuery())
- || isNotBlank(uri.getFragment())
- ) {
- return false;
- }
+ || isNotBlank(uri.getFragment())) {
+ return false;
+ }
- final String[] parts = uri.getSchemeSpecificPart().split(":");
+ final String[] parts = uri.getSchemeSpecificPart().split(":", 2);
- return parts.length == 2 || parts.length == 3;
+ return parts.length == 2
+ && parts[0].length() > 0
+ && parts[1].length() > 0
+ && parts[0].codePoints().allMatch(METHOD_CHAR)
+ ;
}
public static boolean isDid(final String uri) {
- if (isBlank(uri)) {
+ if (uri == null) {
return false;
}
- final String[] parts = uri.split(":");
+ final String[] parts = uri.split(":", 3);
- return (parts.length == 3 || parts.length == 4)
+ return parts.length == 3
&& Did.SCHEME.equalsIgnoreCase(parts[0])
- && !parts[parts.length - 1].contains("/") // path
- && !parts[parts.length - 1].contains("?") // query
- && !parts[parts.length - 1].contains("#") // fragment
+ && parts[1].length() > 0
+ && parts[2].length() > 0
+ && parts[1].codePoints().allMatch(METHOD_CHAR)
;
}
@@ -61,19 +68,21 @@ public static boolean isDid(final String uri) {
* @param uri The source URI to be transformed into DID
* @return The new DID
*
- * @throws NullPointerException
- * If {@code uri} is {@code null}
+ * @throws NullPointerException If {@code uri} is {@code null}
*
- * @throws IllegalArgumentException
- * If the given {@code uri} is not valid DID
+ * @throws IllegalArgumentException If the given {@code uri} is not valid DID
*/
public static Did from(final URI uri) {
- if (!isDid(uri)) {
- throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID key, does not start with 'did:'.");
+ if (uri == null) {
+ throw new IllegalArgumentException("The DID must not be null.");
}
- return from(uri, uri.getSchemeSpecificPart().split(":"), 3);
+ if (!Did.SCHEME.equalsIgnoreCase(uri.getScheme())) {
+ throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must start with 'did:' prefix.");
+ }
+
+ return from(uri, uri.getSchemeSpecificPart().split(":", 2));
}
/**
@@ -82,59 +91,58 @@ public static Did from(final URI uri) {
* @param uri The source URI to be transformed into DID
* @return The new DID
*
- * @throws NullPointerException
- * If {@code uri} is {@code null}
+ * @throws NullPointerException If {@code uri} is {@code null}
*
- * @throws IllegalArgumentException
- * If the given {@code uri} is not valid DID
+ * @throws IllegalArgumentException If the given {@code uri} is not valid DID
*/
public static Did from(final String uri) {
+ if (uri == null || uri.length() == 0) {
+ throw new IllegalArgumentException("The DID must not be null or blank string.");
+ }
- if (!isDid(uri)) {
- throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID key, does not start with 'did:'.");
+ final String[] parts = uri.split(":", 3);
+
+ if (parts.length != 3) {
+ throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
}
- return from(uri, uri.split(":"), 4);
- }
+ if (!Did.SCHEME.equalsIgnoreCase(parts[0])) {
+ throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must start with 'did:' prefix.");
+ }
- protected static Did from(final Object uri, final String[] parts, int max) {
+ return from(uri, new String[] { parts[1], parts[2] });
+ }
- if (parts.length < max - 1
- || parts.length > max
- || isBlank(parts[max - 3])
- || isBlank(parts[max - 2])
- ) {
+ protected static Did from(final Object uri, final String[] parts) {
+ if (parts.length != 2) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
}
- String methodSpecificId = parts[max - 2];
- String version = "1"; // default DID version
-
- if (parts.length == max) {
- if (isBlank(parts[max - 1])) {
- throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, must be in form 'did:method:method-specific-id'.");
- }
- version = parts[max - 2];
- methodSpecificId = parts[max - 1];
+ // check method
+ if (parts[0].length() == 0
+ || !parts[0].codePoints().allMatch(METHOD_CHAR)) {
+ throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method [" + parts[0] + "] syntax is blank or invalid.");
+ }
+
+ // check method specific id
+ if (parts[1].length() == 0) {
+ throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID, method specific id [" + parts[1] + "] is blank.");
}
- return new Did(parts[max - 3], version, methodSpecificId);
+ return new Did(parts[0], parts[1]);
}
+
public String getMethod() {
return method;
}
- public String getVersion() {
- return version;
- }
-
public String getMethodSpecificId() {
- return methodSpecificId;
+ return specificId;
}
public URI toUri() {
try {
- return new URI(SCHEME, method + ":" + methodSpecificId, null);
+ return new URI(SCHEME, method + ":" + specificId, null);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
@@ -150,23 +158,17 @@ public DidUrl asDidUrl() {
@Override
public String toString() {
- final StringBuilder builder = new StringBuilder()
- .append(SCHEME)
- .append(':')
- .append(method)
- .append(':');
-
- if (!"1".equals(version)) {
- builder
- .append(version)
- .append(':');
- }
- return builder.append(methodSpecificId).toString();
+ return new StringBuilder()
+ .append(SCHEME)
+ .append(':')
+ .append(method)
+ .append(':')
+ .append(specificId).toString();
}
@Override
public int hashCode() {
- return Objects.hash(method, methodSpecificId, version);
+ return Objects.hash(method, specificId);
}
@Override
@@ -181,10 +183,10 @@ public boolean equals(final Object obj) {
return false;
}
Did other = (Did) obj;
- return Objects.equals(method, other.method) && Objects.equals(methodSpecificId, other.methodSpecificId)
- && Objects.equals(version, other.version);
+ return Objects.equals(method, other.method) && Objects.equals(specificId, other.specificId);
+
}
-
+
static final boolean isNotBlank(String value) {
return value != null && !value.trim().isEmpty();
}
@@ -192,5 +194,4 @@ static final boolean isNotBlank(String value) {
static final boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
-
}
diff --git a/src/main/java/com/apicatalog/did/DidUrl.java b/src/main/java/com/apicatalog/did/DidUrl.java
index ef10683..ea31952 100644
--- a/src/main/java/com/apicatalog/did/DidUrl.java
+++ b/src/main/java/com/apicatalog/did/DidUrl.java
@@ -9,13 +9,13 @@
public class DidUrl extends Did {
private static final long serialVersionUID = 5752880077497569763L;
-
+
protected final String path;
protected final String query;
protected final String fragment;
protected DidUrl(Did did, String path, String query, String fragment) {
- super(did.method, did.version, did.methodSpecificId);
+ super(did.method, did.specificId);
this.path = path;
this.query = query;
this.fragment = fragment;
@@ -31,7 +31,7 @@ public static DidUrl from(final URI uri) {
throw new IllegalArgumentException("The URI [" + uri + "] is not valid DID URL, does not start with 'did:'.");
}
- Did did = from(uri, uri.getSchemeSpecificPart().split(":"), 3);
+ Did did = from(uri, uri.getSchemeSpecificPart().split(":", 2));
return new DidUrl(did, uri.getPath(), uri.getQuery(), uri.getFragment());
}
@@ -56,7 +56,7 @@ public static boolean isDidUrl(final String uri) {
@Override
public URI toUri() {
try {
- return new URI(SCHEME, method + ":" + methodSpecificId, path, query, fragment);
+ return new URI(SCHEME, method + ":" + specificId, path, query, fragment);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
diff --git a/src/main/java/com/apicatalog/did/key/DidKey.java b/src/main/java/com/apicatalog/did/key/DidKey.java
index a26fd7f..210ca26 100644
--- a/src/main/java/com/apicatalog/did/key/DidKey.java
+++ b/src/main/java/com/apicatalog/did/key/DidKey.java
@@ -9,8 +9,7 @@
/**
* Immutable DID Key
*
- * did-key-format := did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type,
- * raw-public-key-bytes))
+ * did-key-format := did:key:[version]:MULTIBASE(multiencodedKey)
*
*
* @see new IllegalArgumentException("Unsupported did:key base encoding. DID [" + did.toString() + "]."));
+ final String[] parts = did.getMethodSpecificId().split(":", 2);
+
+ String version = DEFAULT_VERSION;
+ String encoded = parts[0];
+
+ if (parts.length == 2) {
+ version = parts[0];
+ encoded = parts[1];
+ }
+
+ final Multibase base = bases.getBase(encoded).orElseThrow(() -> new IllegalArgumentException("Unsupported did:key base encoding. DID [" + did.toString() + "]."));
- final byte[] debased = base.decode(did.getMethodSpecificId());
+ final byte[] debased = base.decode(encoded);
- return new DidKey(did.getVersion(), did.getMethodSpecificId(), base, debased);
+ return new DidKey(version, encoded, base, debased);
}
public static final DidKey create(Multibase base, byte[] key) {
@@ -93,6 +107,10 @@ public Multibase getBase() {
}
public byte[] getKey() {
- return encodedKey;
+ return debased;
+ }
+
+ public String getVersion() {
+ return version;
}
}
diff --git a/src/test/java/com/apicatalog/did/DidTest.java b/src/test/java/com/apicatalog/did/DidTest.java
new file mode 100644
index 0000000..c9c83e7
--- /dev/null
+++ b/src/test/java/com/apicatalog/did/DidTest.java
@@ -0,0 +1,96 @@
+package com.apicatalog.did;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@DisplayName("DID")
+@TestMethodOrder(OrderAnnotation.class)
+class DidTest {
+
+ @DisplayName("Create DID from string")
+ @ParameterizedTest(name = "{0}")
+ @MethodSource({ "validVectors" })
+ void fromString(String uri, String method, String specificId) {
+ try {
+
+ final Did did = Did.from(uri);
+
+ assertNotNull(did);
+ assertEquals(method, did.getMethod());
+ assertEquals(specificId, did.getMethodSpecificId());
+
+ } catch (IllegalArgumentException | NullPointerException e) {
+ e.printStackTrace();
+ fail(e);
+ }
+ }
+
+ @DisplayName("Create DID from URI")
+ @ParameterizedTest(name = "{0}")
+ @MethodSource({ "validVectors" })
+ void fromUri(String input, String method, String specificId) {
+ try {
+
+ final Did did = Did.from(URI.create(input));
+
+ assertNotNull(did);
+ assertEquals(method, did.getMethod());
+ assertEquals(specificId, did.getMethodSpecificId());
+
+ } catch (IllegalArgumentException | NullPointerException e) {
+ e.printStackTrace();
+ fail(e);
+ }
+ }
+
+ @DisplayName("isDid(String)")
+ @ParameterizedTest(name = "{0}")
+ @MethodSource({ "validVectors" })
+ void stringIsDid(String uri) {
+ assertTrue(Did.isDid(uri));
+ }
+
+ @DisplayName("isDid(URI)")
+ @ParameterizedTest(name = "{0}")
+ @MethodSource({ "validVectors" })
+ void uriIsDid(String uri) {
+ assertTrue(Did.isDid(URI.create(uri)));
+ }
+
+ static Stream validVectors() {
+ return Arrays.stream(new String[][] {
+ {
+ "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
+ "key",
+ "z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
+ },
+ {
+ "did:example:z6MkicdicToW5HbxPP7zZV1H7RHvXgRMhoujWAF2n5WQkdd2",
+ "example",
+ "z6MkicdicToW5HbxPP7zZV1H7RHvXgRMhoujWAF2n5WQkdd2"
+ },
+ {
+ "did:key:1.1:z6MkicdicToW5HbxPP7zZV1H7RHvXgRMhoujWAF2n5WQkdd2",
+ "key",
+ "1.1:z6MkicdicToW5HbxPP7zZV1H7RHvXgRMhoujWAF2n5WQkdd2"
+ },
+ {
+ "did:web:method:specific:identifier",
+ "web",
+ "method:specific:identifier"
+ },
+ });
+ }
+}
diff --git a/src/test/java/com/apicatalog/did/DidKeyTest.java b/src/test/java/com/apicatalog/did/key/DidKeyTest.java
similarity index 98%
rename from src/test/java/com/apicatalog/did/DidKeyTest.java
rename to src/test/java/com/apicatalog/did/key/DidKeyTest.java
index 3d3ca79..52e142a 100644
--- a/src/test/java/com/apicatalog/did/DidKeyTest.java
+++ b/src/test/java/com/apicatalog/did/key/DidKeyTest.java
@@ -1,4 +1,4 @@
-package com.apicatalog.did;
+package com.apicatalog.did.key;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -13,7 +13,6 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
-import com.apicatalog.did.key.DidKey;
import com.apicatalog.multibase.MultibaseDecoder;
@DisplayName("DID Key")
diff --git a/src/test/java/com/apicatalog/did/DidKeyTestCase.java b/src/test/java/com/apicatalog/did/key/DidKeyTestCase.java
similarity index 96%
rename from src/test/java/com/apicatalog/did/DidKeyTestCase.java
rename to src/test/java/com/apicatalog/did/key/DidKeyTestCase.java
index db9805e..02bef10 100644
--- a/src/test/java/com/apicatalog/did/DidKeyTestCase.java
+++ b/src/test/java/com/apicatalog/did/key/DidKeyTestCase.java
@@ -1,4 +1,4 @@
-package com.apicatalog.did;
+package com.apicatalog.did.key;
import java.net.URI;