Skip to content

Commit

Permalink
Merge branch 'main' into extend-examples
Browse files Browse the repository at this point in the history
  • Loading branch information
magnusbechwind committed Mar 4, 2024
2 parents 65d76a7 + 7bab2cc commit 6361444
Show file tree
Hide file tree
Showing 22 changed files with 559 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,15 @@ public String getValue() {
default: throw new IllegalArgumentException("An unsupported network was provided");
}
}

static public Network fromLowerCase(String value) {
switch (value) {
case "mainnet":
return MAINNET;
case "testnet":
return TESTNET;
default:
throw new IllegalArgumentException("An unsupported network was provided");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.concordium.sdk.crypto.wallet.identityobject;

import java.util.Map;

import com.concordium.sdk.responses.accountinfo.credential.AttributeType;

import lombok.Getter;

@Getter
Expand All @@ -9,4 +13,11 @@ public class IdentityObject {
private PreIdentityObject preIdentityObject;
private String signature;

public String getChosenAttribute(AttributeType attribute) throws MissingAttributeException {
Map<AttributeType, String> chosenAttributes = this.getAttributeList().getChosenAttributes();
if (!chosenAttributes.containsKey(attribute)) {
throw new MissingAttributeException(attribute);
}
return chosenAttributes.get(attribute);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.concordium.sdk.crypto.wallet.identityobject;

import com.concordium.sdk.responses.accountinfo.credential.AttributeType;

import lombok.Getter;

@Getter
public class MissingAttributeException extends Exception {
private AttributeType attribute;

public MissingAttributeException(AttributeType attribute) {
this.attribute = attribute;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.concordium.sdk.crypto.wallet.web3Id;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
Expand All @@ -21,8 +23,8 @@
@JsonDeserialize(using = CredentialAttribute.CredentialAttributieTypeDeserializer.class)
@Builder
@Getter
public class CredentialAttribute {
enum CredentialAttributeType {
public final class CredentialAttribute {
public enum CredentialAttributeType {
INT,
STRING,
TIMESTAMP;
Expand All @@ -31,8 +33,59 @@ enum CredentialAttributeType {
private String value;
private CredentialAttributeType type;

static class CredentialAttributieTypeDeserializer extends JsonDeserializer<CredentialAttribute> {
private int compareStringAttributes(byte[] aBytes, byte[] bBytes) {
if (aBytes.length < bBytes.length) return -1;
if (aBytes.length > bBytes.length) return 1;

for (int i = 0; i < aBytes.length; i++) {
byte aByte = aBytes[i];
byte bByte = bBytes[i];

if (aByte == bByte) continue;
return aByte < bByte ? -1 : 1;
}

return 0;
}

public boolean isBetween(CredentialAttribute lower, CredentialAttribute upper) throws IllegalArgumentException {
if (!this.getType().equals(lower.getType()) || !this.getType().equals(upper.getType())) {
throw new IllegalArgumentException("Attribute types must match");
}
switch (this.type) {
case INT: {
long lowerVal = Long.parseUnsignedLong(lower.getValue());
long upperVal = Long.parseUnsignedLong(upper.getValue());
long val = Long.parseUnsignedLong(this.getValue());
return Long.compareUnsigned(lowerVal, val) <= 0 && Long.compareUnsigned(upperVal, val) > 0;
}
case TIMESTAMP: {
LocalDateTime lowerVal = LocalDateTime.parse(lower.getValue());
LocalDateTime upperVal = LocalDateTime.parse(upper.getValue());
LocalDateTime val = LocalDateTime.parse(this.getValue());
return !lowerVal.isAfter(val) && upperVal.isAfter(val);
}
case STRING: {
byte[] lowerVal = lower.getValue().getBytes(StandardCharsets.UTF_8);
byte[] upperVal = upper.getValue().getBytes(StandardCharsets.UTF_8);
byte[] val = this.getValue().getBytes(StandardCharsets.UTF_8);
return this.compareStringAttributes(val, lowerVal) >= 0 && this.compareStringAttributes(val, upperVal) < 0;
}
default:
throw new IllegalArgumentException("Unknown attribute type");
}
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof CredentialAttribute)) {
return false;
}
CredentialAttribute cred = (CredentialAttribute) obj;
return this.type.equals(cred.type) && this.value.equals(cred.value);
}

static class CredentialAttributieTypeDeserializer extends JsonDeserializer<CredentialAttribute> {
@Override
public CredentialAttribute deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,30 @@
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME;

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
import com.concordium.sdk.crypto.wallet.identityobject.MissingAttributeException;
import com.concordium.sdk.crypto.wallet.web3Id.CredentialAttribute;
import com.concordium.sdk.responses.accountinfo.credential.AttributeType;

@JsonTypeInfo(use = NAME, include = As.PROPERTY, property = "type", visible = true)
@JsonSubTypes ({@Type (value = RevealStatement.class), @Type (value = RangeStatement.class), @Type (value = MembershipStatement.class), @Type (value = NonMembershipStatement.class)})
public abstract class AtomicStatement {
@JsonProperty("attributeTag")
public abstract String getAttributeTag();

// TODO: add overload for web3Id credential
protected CredentialAttribute getAttributeValue(IdentityObject identityObject) throws JsonProcessingException, JsonParseException, MissingAttributeException {
AttributeType type = AttributeType.fromJSON(this.getAttributeTag());
String raw = identityObject.getChosenAttribute(type);
return CredentialAttribute.builder().value(raw).type(CredentialAttribute.CredentialAttributeType.STRING).build();
}

// TODO: add overload for web3Id credential
public abstract boolean canBeProvedBy(IdentityObject identityObject) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import java.util.List;

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
import com.concordium.sdk.crypto.wallet.identityobject.MissingAttributeException;
import com.concordium.sdk.crypto.wallet.web3Id.CredentialAttribute;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;

import lombok.Getter;

Expand All @@ -12,4 +16,15 @@
public class MembershipStatement extends AtomicStatement {
private String attributeTag;
private List<CredentialAttribute> set;

@Override
public boolean canBeProvedBy(IdentityObject identityObject) throws JsonParseException, JsonProcessingException {
try {
CredentialAttribute value = this.getAttributeValue(identityObject);
return set.contains(value);
} catch (MissingAttributeException e) {
// If the identityObject does not have the relevant attribute, it does not satisfy the statement
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import java.util.List;

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
import com.concordium.sdk.crypto.wallet.identityobject.MissingAttributeException;
import com.concordium.sdk.crypto.wallet.web3Id.CredentialAttribute;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;

import lombok.Getter;

Expand All @@ -12,4 +16,15 @@
public class NonMembershipStatement extends AtomicStatement {
private String attributeTag;
private List<CredentialAttribute> set;

@Override
public boolean canBeProvedBy(IdentityObject identityObject) throws JsonParseException, JsonProcessingException {
try {
CredentialAttribute value = this.getAttributeValue(identityObject);
return !set.contains(value);
} catch (MissingAttributeException e) {
// If the identityObject does not have the relevant attribute, it does not satisfy the statement
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.concordium.sdk.crypto.wallet.web3Id.Statement;

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
import com.concordium.sdk.crypto.wallet.identityobject.MissingAttributeException;
import com.concordium.sdk.crypto.wallet.web3Id.CredentialAttribute;
import com.concordium.sdk.responses.accountinfo.credential.AttributeType;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;

import lombok.Builder;
import lombok.Getter;
Expand All @@ -16,4 +19,15 @@ public class RangeStatement extends AtomicStatement {
private CredentialAttribute lower;
private CredentialAttribute upper;
private String attributeTag;
@Override
public boolean canBeProvedBy(IdentityObject identityObject) throws JsonParseException, JsonProcessingException {
try {
CredentialAttribute value = getAttributeValue(identityObject);
return value.isBetween(lower, upper);
} catch (MissingAttributeException e) {
// If the identityObject does not have the relevant attribute, it does not satisfy the statement
return false;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.concordium.sdk.crypto.wallet.web3Id.Statement;

import java.util.List;
import java.util.stream.Collectors;

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
import com.concordium.sdk.crypto.wallet.web3Id.Statement.did.RequestIdentifier;
import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.Builder;
import lombok.Getter;
Expand All @@ -10,7 +15,39 @@
@Builder
@Jacksonized
public class RequestStatement {
private String id;
private List<String> type;
private RequestIdentifier id;
@JsonProperty("type")
private List<String> verifiableCredentialTypes;
private List<AtomicStatement> statement;

public StatementType getStatementType() {
return this.id.getType();
}

public List<AtomicStatement> getUnsatisfiedStatements(IdentityObject identityObject) {
if (!this.getStatementType().equals(StatementType.Credential)) {
throw new IllegalArgumentException("Only an account statement can be satisfied by an identity");
}

return statement.stream().filter(s -> {
try {
return !s.canBeProvedBy(identityObject);
} catch (Exception e) {
return false;
}
}).collect(Collectors.toList());
}

public boolean canBeProvedBy(IdentityObject identityObject) throws Exception {
if (!this.getStatementType().equals(StatementType.Credential)) {
throw new IllegalArgumentException("Only an account statement can by proven using a identity");
}

for (AtomicStatement s : this.statement) {
if (s.canBeProvedBy(identityObject))
continue;
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.concordium.sdk.crypto.wallet.web3Id.Statement;

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
import com.concordium.sdk.crypto.wallet.identityobject.MissingAttributeException;
import com.fasterxml.jackson.annotation.JsonTypeName;

import lombok.Getter;
Expand All @@ -8,4 +10,17 @@
@JsonTypeName("RevealAttribute")
public class RevealStatement extends AtomicStatement {
private String attributeTag;

@Override
public boolean canBeProvedBy(IdentityObject identityObject) throws Exception {
try {
// Attempt to get the attribute
this.getAttributeValue(identityObject);
} catch (MissingAttributeException e) {
// If the identityObject does not have the relevant attribute, it does not satisfy the statement
return false;
}
// With a reveal statement, the only requirement is that the identity has the attribute.
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.concordium.sdk.crypto.wallet.web3Id.Statement;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum StatementType {
@JsonProperty("cred")
Credential,
@JsonProperty("sci")
SmartContract
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.concordium.sdk.crypto.wallet.web3Id.Statement.did;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import com.concordium.sdk.crypto.wallet.Network;
import com.concordium.sdk.crypto.wallet.web3Id.Statement.StatementType;
import com.concordium.sdk.transactions.CredentialRegistrationId;
import com.fasterxml.jackson.annotation.JsonValue;

import lombok.Builder;
import lombok.Getter;

@Getter
public class AccountRequestIdentifier extends RequestIdentifier {
private CredentialRegistrationId credId;
private static Pattern pattern = Pattern.compile( "^" + getDID("(mainnet|testnet)", "([a-zA-Z0-9]*)") + "$");

@Builder
public AccountRequestIdentifier(Network network, CredentialRegistrationId credId) {
super(network);
this.credId = credId;
}

private static String getDID(String network, String credId) {
return "did:ccd:" + network + ":cred:" + credId;
}

@JsonValue
@Override
public String toString() {
return getDID(network.getValue().toLowerCase(), credId.getEncoded());
}

@Nullable
public static AccountRequestIdentifier tryFromString(String did) {
Matcher matcher = pattern.matcher(did);
if (matcher.matches()) {
Network network = Network.fromLowerCase(matcher.group(1));
CredentialRegistrationId credId = CredentialRegistrationId.from(matcher.group(2));
return new AccountRequestIdentifier(network, credId);
}
return null;
}

@Override
public StatementType getType() {
return StatementType.Credential;
}
}
Loading

0 comments on commit 6361444

Please sign in to comment.