diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b97c99c..5616e44ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Added method `findAtLowestHeight` for finding the earliest block satisfying some condition. - Added method `findAccountCreation` for finding the block in which an account was created. - Fixed an issue where `ConcordiumHdWallet.fromSeedPhrase` always produced an invalid seed as hex. +- Added `AcceptableRequest` class with `acceptableRequest` and `acceptableAtomicStatement` to check if a request satisfies wallet rules. ## 7.0.0 - Make the `energy` parameter for invoking an instance `Optional`. diff --git a/concordium-base b/concordium-base index 23129b486..e6461db70 160000 --- a/concordium-base +++ b/concordium-base @@ -1 +1 @@ -Subproject commit 23129b48624f8ca9fed57b167c88b31e7b7170d8 +Subproject commit e6461db7032ea88157babc8ff09ea5bac4a39d68 diff --git a/concordium-sdk/src/main/java/com/concordium/sdk/crypto/CryptoJniNative.java b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/CryptoJniNative.java index 0e338625c..01ad74a37 100644 --- a/concordium-sdk/src/main/java/com/concordium/sdk/crypto/CryptoJniNative.java +++ b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/CryptoJniNative.java @@ -7,6 +7,8 @@ import com.concordium.sdk.crypto.wallet.UnsignedCredentialInput; import com.concordium.sdk.crypto.wallet.credential.CredentialDeploymentDetails; import com.concordium.sdk.crypto.wallet.credential.CredentialDeploymentSerializationContext; +import com.concordium.sdk.crypto.wallet.web3Id.AcceptableRequest; +import com.concordium.sdk.crypto.wallet.web3Id.AttributeCheck; import com.concordium.sdk.exceptions.JNIError; import com.concordium.sdk.transactions.InitName; import com.concordium.sdk.transactions.ReceiveName; @@ -316,4 +318,31 @@ public static String getVerifiableCredentialBackupEncryptionKey(String seedAsHex * {@link Web3IdProof} as JSON. */ public static native String createWeb3IdProof(String input); + + /** + * Check that a request is acceptable + * + * @param input {@link Request} serialized as JSON. + * @return JSON representing {@link VoidResult}. If successful the field + * 'result' is empty, + * but if not acceptable the reason will be contained in the error. + */ + public static native String isAcceptableRequest(String input); + + /** + * Check that an atomic is acceptable, with the given restrictions + * + * @param input {@link Request} serialized as JSON. + * @param rangeTags the list of tags, which may be used for range statements, + * serialized as JSON. + * @param setTags the list of tags, which may be used for membership + * statements, serialized as JSON. + * @param check provides the function to check whether the statement value + * is acceptable. + * @return JSON representing {@link VoidResult}. If successful the field + * 'result' is empty, + * but if not acceptable the reason will be contained in the error. + */ + public static native String isAcceptableAtomicStatement(String input, String rangeTags, String setTags, + AcceptableRequest.RawAttributeCheck check); } diff --git a/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/ErrorResult.java b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/ErrorResult.java new file mode 100644 index 000000000..dd68a3cc3 --- /dev/null +++ b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/ErrorResult.java @@ -0,0 +1,16 @@ +package com.concordium.sdk.crypto.wallet; + +import com.concordium.sdk.exceptions.JNIError; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ErrorResult extends StringResult { + + @JsonCreator + ErrorResult( + @JsonProperty("Ok") String ok, + @JsonProperty("Err") JNIError err + ) { + super(ok, err); + } +} diff --git a/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/web3Id/AcceptableRequest.java b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/web3Id/AcceptableRequest.java new file mode 100644 index 000000000..41703ee3e --- /dev/null +++ b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/web3Id/AcceptableRequest.java @@ -0,0 +1,101 @@ +package com.concordium.sdk.crypto.wallet.web3Id; + +import java.util.List; +import com.concordium.sdk.crypto.CryptoJniNative; +import com.concordium.sdk.crypto.NativeResolver; +import com.concordium.sdk.crypto.wallet.ErrorResult; +import com.concordium.sdk.crypto.wallet.StringResult; +import com.concordium.sdk.crypto.wallet.web3Id.Statement.AtomicStatement; +import com.concordium.sdk.exceptions.CryptoJniException; +import com.concordium.sdk.serializing.JsonMapper; +import com.fasterxml.jackson.core.JsonProcessingException; + +public class AcceptableRequest { + // Static block to load native library. + static { + NativeResolver.loadLib(); + } + + /** + * Check that a request is acceptable: + * That range statements are only on the attributes "dob", "idiDocIssuedAt" or + * "idDocExpiredAt" + * That membership statement are only on the attributes "Country of residence", + * "Nationality", "IdDocType" or "IdDocIssuer" + * That attribute tags are not reused within a credentialStatement + * + * @param input the request that should be checked + * @throws NotAcceptableException if the request is not acceptable + * @Throws CryptoJniException if there an error occurs duting the check + * @returns if the request is acceptable, otherwise throws a + * NotAcceptableException + */ + public static void acceptableRequest(QualifiedRequest request) throws NotAcceptableException { + ErrorResult result = null; + try { + String jsonStr = CryptoJniNative.isAcceptableRequest(JsonMapper.INSTANCE.writeValueAsString(request)); + result = JsonMapper.INSTANCE.readValue(jsonStr, ErrorResult.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + if (!result.isSuccess()) { + throw CryptoJniException.from(result.getErr()); + } + if (result.getResult() != null) { + throw new NotAcceptableException(result.getResult()); + } + } + + /** + * Check that a statement is acceptable + * + * @param statement the statement that should be checked + * @param rangeTag the attribute tags that may be used for range + * statements + * @param setTags the attribute tags that may be used for set statements + * @param attributeCheck custom check on the value of the statement. + * @throws NotAcceptableException if the request is not acceptable + * @Throws CryptoJniException if there an error occurs duting the check + * @returns if the request is acceptable, otherwise throws a + * NotAcceptableException + */ + public static void acceptableAtomicStatement(AtomicStatement statement, List rangeTags, + List setTags, AttributeCheck attributeCheck) throws NotAcceptableException { + StringResult result = null; + try { + + String jsonStr = CryptoJniNative.isAcceptableAtomicStatement( + JsonMapper.INSTANCE.writeValueAsString(statement), + JsonMapper.INSTANCE.writeValueAsString(rangeTags), + JsonMapper.INSTANCE.writeValueAsString(setTags), + new RawAttributeCheck(attributeCheck)); + result = JsonMapper.INSTANCE.readValue(jsonStr, StringResult.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + if (!result.isSuccess()) { + throw CryptoJniException.from(result.getErr()); + } + if (result.getResult() != null) { + throw new NotAcceptableException(result.getResult()); + } + } + + public static class RawAttributeCheck { + AttributeCheck checker; + + public RawAttributeCheck(AttributeCheck checker) { + this.checker = checker; + } + + public void check_attribute(String tag, String value) throws Exception { + checker.checkAttribute(tag, JsonMapper.INSTANCE.readValue(value, CredentialAttribute.class)); + } + } + + public static class NotAcceptableException extends Exception { + public NotAcceptableException(String errorMessage) { + super(errorMessage); + } + } +} diff --git a/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/web3Id/AttributeCheck.java b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/web3Id/AttributeCheck.java new file mode 100644 index 000000000..6926c8c2e --- /dev/null +++ b/concordium-sdk/src/main/java/com/concordium/sdk/crypto/wallet/web3Id/AttributeCheck.java @@ -0,0 +1,12 @@ +package com.concordium.sdk.crypto.wallet.web3Id; + + +public interface AttributeCheck { + /** + * This check should throw an exception if the given attribute value is not valid. + * @param tag the attribute tag for the statement + * @param value the attribute value used in the statement + * @throws Exception to indicate the attribute is not valid. + */ + public void checkAttribute(String tag, CredentialAttribute value) throws Exception; +} diff --git a/concordium-sdk/src/test/java/com/concordium/sdk/crypto/wallet/web3Id/RequestStatementTest.java b/concordium-sdk/src/test/java/com/concordium/sdk/crypto/wallet/web3Id/RequestStatementTest.java index 9826fb03f..8b6bf33e0 100644 --- a/concordium-sdk/src/test/java/com/concordium/sdk/crypto/wallet/web3Id/RequestStatementTest.java +++ b/concordium-sdk/src/test/java/com/concordium/sdk/crypto/wallet/web3Id/RequestStatementTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertTrue; import java.nio.charset.Charset; +import java.util.Collections; import java.util.List; import org.junit.Test; @@ -12,8 +13,11 @@ import com.concordium.sdk.crypto.wallet.FileHelpers; import com.concordium.sdk.crypto.wallet.Network; import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject; +import com.concordium.sdk.crypto.wallet.web3Id.AcceptableRequest.NotAcceptableException; +import com.concordium.sdk.crypto.wallet.web3Id.CredentialAttribute.CredentialAttributeType; import com.concordium.sdk.crypto.wallet.web3Id.Statement.AtomicStatement; import com.concordium.sdk.crypto.wallet.web3Id.Statement.QualifiedRequestStatement; +import com.concordium.sdk.crypto.wallet.web3Id.Statement.RangeStatement; import com.concordium.sdk.crypto.wallet.web3Id.Statement.RequestStatement; import com.concordium.sdk.crypto.wallet.web3Id.Statement.StatementType; import com.concordium.sdk.serializing.JsonMapper; @@ -51,6 +55,49 @@ public void testGetsUnsatisfiedStatement() throws Exception { assertEquals(requestStatement1.getStatement().get(0), unsatisfied.get(0)); } + @Test + public void testIsAcceptableRequest() throws Exception { + QualifiedRequest request = ProofTest.loadRequest("accountRequest.json"); + AcceptableRequest.acceptableRequest(request); + } + + @Test + public void testIsAcceptableAtomicStatement() throws NotAcceptableException { + AtomicStatement statement = RangeStatement.builder().attributeTag("dob") + .lower(CredentialAttribute.builder().type(CredentialAttributeType.STRING).value("20140112").build()) + .upper(CredentialAttribute.builder().type(CredentialAttributeType.STRING).value("20150112").build()) + .build(); + + AcceptableRequest.acceptableAtomicStatement(statement, Collections.singletonList("dob"), Collections.emptyList(), new AttributeCheck() { + @Override + public void checkAttribute(String tag, CredentialAttribute value) {}}); + } + + @Test(expected = AcceptableRequest.NotAcceptableException.class) + public void testIllegalTagStatementThrowsNotAcceptable() throws NotAcceptableException { + AtomicStatement statement = RangeStatement.builder().attributeTag("dob") + .lower(CredentialAttribute.builder().type(CredentialAttributeType.STRING).value("20140112").build()) + .upper(CredentialAttribute.builder().type(CredentialAttributeType.STRING).value("20150112").build()) + .build(); + + AcceptableRequest.acceptableAtomicStatement(statement, Collections.emptyList(), Collections.emptyList(), new AttributeCheck() { + @Override + public void checkAttribute(String tag, CredentialAttribute value) {}}); + } + + @Test(expected = AcceptableRequest.NotAcceptableException.class) + public void testAttributeCheckThrowingErrorReturnsNotAcceptable() throws NotAcceptableException { + AtomicStatement statement = RangeStatement.builder().attributeTag("dob") + .lower(CredentialAttribute.builder().type(CredentialAttributeType.STRING).value("20140112").build()) + .upper(CredentialAttribute.builder().type(CredentialAttributeType.STRING).value("20150112").build()) + .build(); + + AcceptableRequest.acceptableAtomicStatement(statement, Collections.emptyList(), Collections.emptyList(), new AttributeCheck() { + @Override + public void checkAttribute(String tag, CredentialAttribute value) throws Exception { + throw new Exception("Not okay"); + }}); + } @Test public void testCanQualifyStatement() throws Exception { // Arrange @@ -61,6 +108,7 @@ public void testCanQualifyStatement() throws Exception { "8a3a87f3f38a7a507d1e85dc02a92b8bcaa859f5cf56accb3c1bc7c40e1789b4933875a38dd4c0646ca3e940a02c42d8"); ContractAddress contractAddress = ContractAddress.from(1232, 3); ED25519PublicKey publicKey = ED25519PublicKey + .from("16afdb3cb3568b5ad8f9a0fa3c741b065642de8c53e58f7920bf449e63ff2bf9"); // Act diff --git a/crypto-jni/Cargo.lock b/crypto-jni/Cargo.lock index 87f9a9204..aa63090e4 100644 --- a/crypto-jni/Cargo.lock +++ b/crypto-jni/Cargo.lock @@ -197,6 +197,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "bitvec" version = "1.0.1" @@ -332,18 +344,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] @@ -368,7 +379,7 @@ dependencies = [ [[package]] name = "concordium-contracts-common" -version = "9.0.0" +version = "9.1.0" dependencies = [ "base64", "bs58", @@ -388,7 +399,7 @@ dependencies = [ [[package]] name = "concordium-contracts-common-derive" -version = "4.0.1" +version = "4.1.0" dependencies = [ "proc-macro2", "quote", @@ -397,7 +408,7 @@ dependencies = [ [[package]] name = "concordium_base" -version = "4.0.0" +version = "4.1.0" dependencies = [ "anyhow", "ark-bls12-381", @@ -540,6 +551,27 @@ dependencies = [ "wallet_library", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "curve25519-dalek" version = "4.1.1" @@ -703,6 +735,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -745,6 +798,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.1" @@ -808,7 +867,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -864,6 +923,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" + [[package]] name = "hex" version = "0.4.3" @@ -940,6 +1005,17 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi 0.3.6", + "libc", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1019,6 +1095,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "leb128" version = "0.2.5" @@ -1031,6 +1113,17 @@ version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + [[package]] name = "link-cplusplus" version = "1.0.8" @@ -1162,7 +1255,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -1201,6 +1294,48 @@ dependencies = [ "sha2", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -1223,6 +1358,20 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettytable-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" +dependencies = [ + "csv", + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1334,6 +1483,26 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.9.4" @@ -1415,6 +1584,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rust_iso3166" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc46f436f726b768364d35d099f43a94f22fd34857ff4f679b1f5cbcb03b9f71" +dependencies = [ + "js-sys", + "phf", + "prettytable-rs", + "wasm-bindgen", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1424,6 +1605,12 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.12" @@ -1508,7 +1695,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.28", + "time", ] [[package]] @@ -1553,6 +1740,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "spki" version = "0.7.3" @@ -1615,6 +1808,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1644,17 +1848,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.28" @@ -1750,25 +1943,21 @@ dependencies = [ [[package]] name = "wallet_library" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", + "chrono", "concordium_base", "ed25519-dalek", "either", "hex", "key_derivation", + "rust_iso3166", "serde", "serde_json", "thiserror", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1860,6 +2049,72 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" + [[package]] name = "wyz" version = "0.5.1" diff --git a/crypto-jni/src/lib.rs b/crypto-jni/src/lib.rs index 117695729..9563c52fd 100644 --- a/crypto-jni/src/lib.rs +++ b/crypto-jni/src/lib.rs @@ -4,26 +4,36 @@ use concordium_base::{ base, common::*, contracts_common::{schema::VersionedModuleSchema, Amount}, - encrypted_transfers, - encrypted_transfers::types::{ - AggregatedDecryptedAmount, EncryptedAmount, EncryptedAmountTransferData, - IndexedEncryptedAmount, SecToPubAmountTransferData, + encrypted_transfers::{ + self, + types::{ + AggregatedDecryptedAmount, EncryptedAmount, EncryptedAmountTransferData, + IndexedEncryptedAmount, SecToPubAmountTransferData, + }, + }, + id::{ + self, + constants::{self, ArCurve, AttributeKind}, + curve_arithmetic::Curve, + elgamal, + id_proof_types::AtomicStatement, + types::GlobalContext, }, - id, - id::{constants::ArCurve, curve_arithmetic::Curve, elgamal, types::GlobalContext}, transactions::{AddBakerKeysMarker, BakerKeysPayload, ConfigureBakerKeysPayload}, + web3id::{Request, Web3IdAttribute}, }; use core::slice; use ed25519_dalek::*; use jni::{ - objects::{JClass, JString}, + objects::{JClass, JObject, JString}, sys::{jboolean, jbyteArray, jint, jlong, jstring, JNI_FALSE}, JNIEnv, }; use rand::thread_rng; use serde_json::{from_str, to_string}; use std::{ + collections::HashSet, convert::{From, TryFrom, TryInto}, i8, str::Utf8Error, @@ -35,6 +45,9 @@ use wallet_library::{ }, identity::{create_identity_object_request_v1_aux, create_identity_recovery_request_aux}, proofs::Web3IdProofInput, + statement::{ + AcceptableAtomicStatement, AcceptableRequest, RequestCheckError, WalletConfigRules, + }, wallet::{ get_account_public_key_aux, get_account_signing_key_aux, get_attribute_commitment_randomness_aux, get_credential_id_aux, get_id_cred_sec_aux, @@ -1314,3 +1327,146 @@ pub extern "system" fn Java_com_concordium_sdk_crypto_CryptoJniNative_createWeb3 CryptoJniResult::Ok(presentation_string).to_jstring(&env) } + +impl From for CryptoJniResult { + fn from(e: wallet_library::statement::RequestCheckError) -> Self { + let error = JNIErrorResponse { + errorType: JNIErrorResponseType::NativeConversion, + errorMessage: e.to_string(), + }; + CryptoJniResult::Err(error) + } +} + +/// Encodes a potential error message inside the result, that differentiates from the JniError +type ErrorResult = CryptoJniResult>; + +/// The JNI wrapper for checking that a request is acceptable. +/// * `input` - the JSON string of [`concordium_base::web3id::Request`] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn Java_com_concordium_sdk_crypto_CryptoJniNative_isAcceptableRequest( + env: JNIEnv, + _: JClass, + input: JString, +) -> jstring { + let input_string = match get_string(env, input) { + Ok(s) => s, + Err(err) => return ErrorResult::Err(err).to_jstring(&env), + }; + + let request: Request = + match serde_json::from_str(&input_string) { + Ok(req) => req, + Err(err) => return ErrorResult::from(err).to_jstring(&env), + }; + + match request.acceptable_request(&wallet_library::default_wallet_config::default_wallet_config()) { + Ok(r) => r, + Err(err) => return ErrorResult::Ok(Some(err.to_string())).to_jstring(&env), + }; + + ErrorResult::Ok(None).to_jstring(&env) +} + +/// The JNI wrapper for checking that an atomic statement is acceptable, according to the given rules. +/// * `statement_input` - the JSON string of [`concordium_base::id::id_proof_types::AtomicStatement`] +/// * `range_tags_input` - the JSON string for a list of strings, that represent attribute tags allowed for range statements +/// * `set_tags_input` - the JSON string for a list of strings, that represent attribute tags allowed for membership statements +/// * `attribute_check` - an object implementing the "check_attribute" function, to do a custom check for the attribute +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn Java_com_concordium_sdk_crypto_CryptoJniNative_isAcceptableAtomicStatement< + 'a, +>( + env: JNIEnv<'a>, + _: JClass, + statement_input: JString, + range_tags_input: JString, + set_tags_input: JString, + attribute_check: JObject<'a>, +) -> jstring { + let input_string = match get_string(env, statement_input) { + Ok(s) => s, + Err(err) => return ErrorResult::Err(err).to_jstring(&env), + }; + + let statement: AtomicStatement = + match serde_json::from_str(&input_string) { + Ok(req) => req, + Err(err) => return ErrorResult::from(err).to_jstring(&env), + }; + + let input_string = match get_string(env, set_tags_input) { + Ok(s) => s, + Err(err) => return ErrorResult::Err(err).to_jstring(&env), + }; + + let set_tags: HashSet = match serde_json::from_str(&input_string) { + Ok(req) => req, + Err(err) => return ErrorResult::from(err).to_jstring(&env), + }; + + let input_string = match get_string(env, range_tags_input) { + Ok(s) => s, + Err(err) => return ErrorResult::Err(err).to_jstring(&env), + }; + + let range_tags: HashSet = match serde_json::from_str(&input_string) { + Ok(req) => req, + Err(err) => return ErrorResult::from(err).to_jstring(&env), + }; + + let check = |tag: &String, attribute: &Web3IdAttribute| { + let jtag: JString = match env.new_string(tag) { + Ok(a) => a, + Err(_) => { + return Err(RequestCheckError::InvalidValue( + "Unable to be stringified".to_owned(), + )) + } + }; + let attribute: String = match to_string(attribute) { + Ok(a) => a, + Err(_) => { + return Err(RequestCheckError::InvalidValue( + "Unable to be stringified".to_owned(), + )) + } + }; + + let jattribute: JString = match env.new_string(attribute) { + Ok(a) => a, + Err(_) => { + return Err(RequestCheckError::InvalidValue( + "Unable to be stringified".to_owned(), + )) + } + }; + + let args = &[jtag.into(), jattribute.into()]; + match env.call_method( + attribute_check, + "check_attribute", + "(Ljava/lang/String;Ljava/lang/String;)V", + args, + ) { + Ok(_) => Ok(()), + Err(e) => Err(RequestCheckError::InvalidValue(e.to_string())), + } + }; + + let rules = WalletConfigRules:: { + set_tags, + range_tags, + attribute_check: Box::new(check), + _marker: std::marker::PhantomData, + }; + + match statement.acceptable_atomic_statement(Some(rules).as_ref()) { + Ok(r) => r, + Err(err) => return ErrorResult::Ok(Some(err.to_string())).to_jstring(&env), + }; + + ErrorResult::Ok(None).to_jstring(&env) +}