Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for checking an identity satisfies a statement #319

Merged
merged 7 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.concordium.sdk.crypto.wallet.identityobject;

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

import lombok.Getter;
import lombok.val;

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

public String getChosenAttribute(AttributeType attribute) throws MissingAttributeException {
val chosenAttributes = this.getAttributeList().getChosenAttributes();
orhoj marked this conversation as resolved.
Show resolved Hide resolved
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.parseLong(lower.getValue());
orhoj marked this conversation as resolved.
Show resolved Hide resolved
long upperVal = Long.parseLong(upper.getValue());
long val = Long.parseLong(this.getValue());
return lowerVal <= val && val < upperVal;
}
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 @@ -8,9 +8,24 @@

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

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
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 Exception {
orhoj marked this conversation as resolved.
Show resolved Hide resolved
AttributeType type = AttributeType.fromJSON(this.getAttributeTag());
String raw = identityObject.getAttributeList().getChosenAttributes().get(type);
return CredentialAttribute.builder().value(raw).type(CredentialAttribute.CredentialAttributeType.STRING).build();

orhoj marked this conversation as resolved.
Show resolved Hide resolved
}

// 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,6 +2,8 @@

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;

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

@Override
public boolean canBeProvedBy(IdentityObject identityObject) throws Exception {
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,6 +2,7 @@

import java.util.List;

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

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

@Override
public boolean canBeProvedBy(IdentityObject identityObject) {
try {
CredentialAttribute value = this.getAttributeValue(identityObject);
return !set.contains(value);
} catch (Exception e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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 lombok.Builder;
Expand All @@ -16,4 +17,15 @@ public class RangeStatement extends AtomicStatement {
private CredentialAttribute lower;
private CredentialAttribute upper;
private String attributeTag;
@Override
public boolean canBeProvedBy(IdentityObject identityObject) throws Exception {
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,9 @@
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 lombok.Builder;
import lombok.Getter;
Expand All @@ -13,4 +16,22 @@ public class RequestStatement {
private String id;
private List<String> type;
private List<AtomicStatement> statement;

public List<AtomicStatement> getUnsatisfiedStatements(IdentityObject identityObject) {
orhoj marked this conversation as resolved.
Show resolved Hide resolved
return statement.stream().filter(s -> {
try {
return !s.canBeProvedBy(identityObject);
orhoj marked this conversation as resolved.
Show resolved Hide resolved
} catch (Exception e) {
return false;
}
}).collect(Collectors.toList());
}

public boolean canBeProvedBy(IdentityObject identityObject) throws Exception {
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
@@ -1,5 +1,6 @@
package com.concordium.sdk.responses.accountinfo.credential;

import com.concordium.sdk.serializing.JsonMapper;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
Expand Down Expand Up @@ -36,5 +37,9 @@ public enum AttributeType {
@JsonProperty("taxIdNo")
TAX_ID_NO,
@JsonProperty("lei")
LEI
LEI;

public static AttributeType fromJSON(String jsonStr) throws Exception {
return JsonMapper.INSTANCE.readValue(String.format("\"%s\"", jsonStr), AttributeType.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,36 +32,6 @@

public class CredentialTest {

static String readFile(String path, Charset encoding)
throws IOException {
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}

private CryptographicParameters getCryptographicParameters() throws Exception {
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/global.json", Charset.forName("UTF-8")),
CryptographicParameters.class);
}

private IdentityProviderInfo getIdentityProviderInfo() throws Exception {
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/ip_info.json", Charset.forName("UTF-8")),
IdentityProviderInfo.class);
}

private Map<String, AnonymityRevokerInfo> getAnonymityRevokerInfos() throws Exception {
MapType mapType = TypeFactory.defaultInstance().constructMapType(Map.class, String.class,
AnonymityRevokerInfo.class);
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/ars_infos.json", Charset.forName("UTF-8")), mapType);
}

private IdentityObject getIdentityObject() throws Exception {
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/id_object.json", Charset.forName("UTF-8")),
IdentityObject.class); }

private static String TEST_SEED = "efa5e27326f8fa0902e647b52449bf335b7b605adc387015ec903f41d95080eb71361cbc7fb78721dcd4f3926a337340aa1406df83332c44c1cdcfe100603860";

@Test
Expand All @@ -76,10 +46,10 @@ public void createUnsignedCredential() throws Exception {
}

UnsignedCredentialInput input = UnsignedCredentialInput.builder()
.ipInfo(getIdentityProviderInfo())
.globalContext(getCryptographicParameters())
.arsInfos(getAnonymityRevokerInfos())
.idObject(getIdentityObject())
.ipInfo(FileHelpers.getIdentityProviderInfo())
.globalContext(FileHelpers.getCryptographicParameters())
.arsInfos(FileHelpers.getAnonymityRevokerInfos())
.idObject(FileHelpers.getIdentityObject())
.credNumber(credentialCounter)
.attributeRandomness(attributeRandomness)
.blindingRandomness(wallet.getSignatureBlindingRandomness(0, 0))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.concordium.sdk.crypto.wallet;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject;
import com.concordium.sdk.responses.blocksummary.updates.queues.AnonymityRevokerInfo;
import com.concordium.sdk.responses.blocksummary.updates.queues.IdentityProviderInfo;
import com.concordium.sdk.responses.cryptographicparameters.CryptographicParameters;
import com.concordium.sdk.serializing.JsonMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;

public class FileHelpers {
public static String readFile(String path, Charset encoding)
throws IOException {
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}

public static CryptographicParameters getCryptographicParameters() throws Exception {
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/global.json", Charset.forName("UTF-8")),
CryptographicParameters.class);
}

public static IdentityProviderInfo getIdentityProviderInfo() throws Exception {
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/ip_info.json", Charset.forName("UTF-8")),
IdentityProviderInfo.class);
}

public static Map<String, AnonymityRevokerInfo> getAnonymityRevokerInfos() throws Exception {
MapType mapType = TypeFactory.defaultInstance().constructMapType(Map.class, String.class,
AnonymityRevokerInfo.class);
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/ars_infos.json", Charset.forName("UTF-8")), mapType);
}

public static IdentityObject getIdentityObject() throws Exception {
return JsonMapper.INSTANCE.readValue(
readFile("./src/test/testresources/wallet/id_object.json", Charset.forName("UTF-8")),
IdentityObject.class); }

}
Loading
Loading