From f87e74cb01b17f2ecb3745300bac79ba0cc69007 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 26 Sep 2018 16:00:52 +0300 Subject: [PATCH 01/75] disable gradle daemon for CI --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b733ff3..d6ea77c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: # fallback to using the latest cache if no exact match is found - v1-dependencies- - - run: ./gradlew dependencies androidDependencies + - run: ./gradlew dependencies androidDependencies --no-daemon - save_cache: paths: @@ -40,6 +40,6 @@ jobs: key: v1-dependencies-{{ checksum "build.gradle" }} # run tests! - - run: ./gradlew test --no-parallel + - run: ./gradlew test --no-parallel --no-daemon From 061592c704cbf0a341d13a38b772019d3a7ed365 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 26 Sep 2018 16:04:02 +0300 Subject: [PATCH 02/75] change cache key for CI --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d6ea77c6..193f90bb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,9 +27,9 @@ jobs: # Download and cache dependencies - restore_cache: keys: - - v1-dependencies-{{ checksum "build.gradle" }} + - v2-dependencies-{{ checksum "build.gradle" }} # fallback to using the latest cache if no exact match is found - - v1-dependencies- + - v2-dependencies- - run: ./gradlew dependencies androidDependencies --no-daemon @@ -37,7 +37,7 @@ jobs: paths: - ~/.gradle - ./gradle - key: v1-dependencies-{{ checksum "build.gradle" }} + key: v2-dependencies-{{ checksum "build.gradle" }} # run tests! - run: ./gradlew test --no-parallel --no-daemon From 3dc5cecdb178f0506e14fd7487061e86d9a7f2e1 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Thu, 27 Sep 2018 16:43:10 +0300 Subject: [PATCH 03/75] add signJWT method to Signer interface and a few tests for the blank signer --- .../src/main/java/me/uport/sdk/core/Signer.kt | 34 ++++++++++----- .../me/uport/sdk/core/BlankSignerTests.kt | 42 +++++++++++++++++++ .../sdk/identity/UportHDSignerWrapper.kt | 13 +++++- .../me/uport/sdk/signer/MetaIdentitySigner.kt | 8 +++- .../java/me/uport/sdk/signer/SimpleSigner.kt | 19 ++++++++- .../java/me/uport/sdk/signer/TxRelaySigner.kt | 12 ++++-- 6 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 core/src/test/java/me/uport/sdk/core/BlankSignerTests.kt diff --git a/core/src/main/java/me/uport/sdk/core/Signer.kt b/core/src/main/java/me/uport/sdk/core/Signer.kt index 6be3d0d9..77b3c11c 100644 --- a/core/src/main/java/me/uport/sdk/core/Signer.kt +++ b/core/src/main/java/me/uport/sdk/core/Signer.kt @@ -5,24 +5,30 @@ import org.kethereum.model.SignatureData import org.kethereum.model.Transaction import kotlin.coroutines.experimental.suspendCoroutine +/** + * Callback type for signature results. + */ +typealias SignatureCallback = (err: Exception?, sigData: SignatureData) -> Unit + /** * An interface used to sign transactions or messages for uport specific operations */ interface Signer { - fun signMessage(rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) + fun signETH(rawMessage: ByteArray, callback: SignatureCallback) + fun signJWT(rawPayload: ByteArray, callback: SignatureCallback) fun getAddress() : String fun signRawTx( unsignedTx: Transaction, callback: (err: Exception?, - signedEncodedTransaction: ByteArray) -> Unit) = signMessage(unsignedTx.encodeRLP()) + signedEncodedTransaction: ByteArray) -> Unit) = signETH(unsignedTx.encodeRLP()) { err, sig -> if (err != null) { - return@signMessage callback(err, byteArrayOf()) + return@signETH callback(err, byteArrayOf()) } - return@signMessage callback(null, unsignedTx.encodeRLP(sig)) + return@signETH callback(null, unsignedTx.encodeRLP(sig)) } companion object { @@ -30,10 +36,8 @@ interface Signer { * A useless signer that calls back with empty signature and has no associated address */ val blank = object : Signer { - override fun signMessage(rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { - callback(null, SignatureData()) - } - + override fun signETH(rawMessage: ByteArray, callback: SignatureCallback) = callback(null, SignatureData()) + override fun signJWT(rawPayload: ByteArray, callback: SignatureCallback) = callback(null, SignatureData()) override fun getAddress(): String = "" } } @@ -54,8 +58,18 @@ suspend fun Signer.signRawTx(unsignedTx: Transaction): ByteArray = suspendCorout } } -suspend fun Signer.signMessage(rawMessage: ByteArray): SignatureData = suspendCoroutine { continuation -> - this.signMessage(rawMessage) { err, sigData -> +suspend fun Signer.signETH(rawMessage: ByteArray): SignatureData = suspendCoroutine { continuation -> + this.signETH(rawMessage) { err, sigData -> + if (err != null) { + continuation.resumeWithException(err) + } else { + continuation.resume(sigData) + } + } +} + +suspend fun Signer.signJWT(rawMessage: ByteArray): SignatureData = suspendCoroutine { continuation -> + this.signJWT(rawMessage) { err, sigData -> if (err != null) { continuation.resumeWithException(err) } else { diff --git a/core/src/test/java/me/uport/sdk/core/BlankSignerTests.kt b/core/src/test/java/me/uport/sdk/core/BlankSignerTests.kt new file mode 100644 index 00000000..e57611fe --- /dev/null +++ b/core/src/test/java/me/uport/sdk/core/BlankSignerTests.kt @@ -0,0 +1,42 @@ +package me.uport.sdk.core + +import kotlinx.coroutines.experimental.runBlocking +import org.junit.Assert +import org.junit.Assert.assertNotNull +import org.junit.Test + +class BlankSignerTests { + + private val tested = Signer.blank + + @Test + fun `sign ETH calls back without error`() { + tested.signETH("hello".toByteArray()) { err, _ -> + Assert.assertNull(err) + } + } + + @Test + fun `signJWT calls back without error`() { + tested.signJWT("hello".toByteArray()) { err, _ -> + Assert.assertNull(err) + } + } + + @Test + fun `signETH coroutine passes`() = runBlocking { + val sig = tested.signETH("hello".toByteArray()) + assertNotNull(sig) + } + + @Test + fun `signJWT coroutine passes`() = runBlocking { + val sig = tested.signJWT("hello".toByteArray()) + assertNotNull(sig) + } + + @Test + fun `returned address is non null`() { + assertNotNull(tested.getAddress()) + } +} \ No newline at end of file diff --git a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt index 02dbeed2..ffe1e48d 100644 --- a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt +++ b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt @@ -16,7 +16,7 @@ class UportHDSignerWrapper( private val deviceAddress: String ) : Signer { - override fun signMessage( + override fun signETH( rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { @@ -29,6 +29,17 @@ class UportHDSignerWrapper( callback) } + override fun signJWT(rawPayload: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { + + return uportHDSigner.signJwtBundle( + context, //FIXME: Not cool hiding the context like this... may lead to leaks + rootAddress, + Account.GENERIC_DEVICE_KEY_DERIVATION_PATH, + Base64.encodeToString(rawPayload, Base64.DEFAULT), + "", + callback) + } + /** * returns the address that corresponds to the device keypair */ diff --git a/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt b/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt index 16b4ea49..c5723d3b 100644 --- a/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt +++ b/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt @@ -18,9 +18,13 @@ class MetaIdentitySigner( /** * Signs a buffer by forwarding to the [wrappedSigner] */ - override fun signMessage( + override fun signETH( rawMessage: ByteArray, - callback: (err: Exception?, sigData: SignatureData) -> Unit) = wrappedSigner.signMessage(rawMessage, callback) + callback: (err: Exception?, sigData: SignatureData) -> Unit) = wrappedSigner.signETH(rawMessage, callback) + + override fun signJWT( + rawPayload: ByteArray, + callback: (err: Exception?, sigData: SignatureData) -> Unit) = wrappedSigner.signJWT(rawPayload, callback) /** diff --git a/sdk/src/main/java/me/uport/sdk/signer/SimpleSigner.kt b/sdk/src/main/java/me/uport/sdk/signer/SimpleSigner.kt index f6cd4512..ac21baa0 100644 --- a/sdk/src/main/java/me/uport/sdk/signer/SimpleSigner.kt +++ b/sdk/src/main/java/me/uport/sdk/signer/SimpleSigner.kt @@ -4,14 +4,31 @@ import me.uport.sdk.core.Signer import org.kethereum.crypto.ECKeyPair import org.kethereum.crypto.getAddress import org.kethereum.crypto.signMessage +import org.kethereum.crypto.signMessageHash import org.kethereum.extensions.hexToBigInteger +import org.kethereum.hashes.sha256 import org.kethereum.model.SignatureData +/** + * Simple [Signer] implementation that holds its keys in memory. + * + * There is no special handling of threads for callbacks. + */ class SimpleSigner(private val privateKey: String) : Signer { + override fun signJWT(rawPayload: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { + try { + val keyPair = ECKeyPair.create(privateKey.hexToBigInteger()) + val sigData = signMessageHash(rawPayload.sha256(), keyPair, false) + callback(null, sigData) + } catch (err: Exception) { + callback(err, SignatureData()) + } + } + override fun getAddress() = ECKeyPair.create(privateKey.hexToBigInteger()).getAddress() - override fun signMessage(rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { + override fun signETH(rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { try { val keyPair = ECKeyPair.create(privateKey.hexToBigInteger()) diff --git a/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt b/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt index 375262fc..6e904eae 100644 --- a/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt +++ b/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt @@ -28,7 +28,11 @@ class TxRelaySigner(private val wrappedSigner: Signer, /** * signs a buffer using the [wrappedSigner] */ - override fun signMessage(rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) = wrappedSigner.signMessage(rawMessage, callback) + override fun signETH(rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) = wrappedSigner.signETH(rawMessage, callback) + + override fun signJWT( + rawPayload: ByteArray, + callback: (err: Exception?, sigData: SignatureData) -> Unit) = wrappedSigner.signJWT(rawPayload, callback) /** * Takes in an [unsignedTx], wraps it as a call to `relayMetaTx` and signs it using the [wrappedSigner] @@ -51,10 +55,10 @@ class TxRelaySigner(private val wrappedSigner: Signer, to.cleanHex + data.toNoPrefixHexString() - signMessage(hashInput.hexToByteArray()) { err, signature -> + signETH(hashInput.hexToByteArray()) { err, signature -> if (err != null) { - return@signMessage callback(err, byteArrayOf()) + return@signETH callback(err, byteArrayOf()) } val rawMetaTxData = TxRelayHelper(network) @@ -71,7 +75,7 @@ class TxRelaySigner(private val wrappedSigner: Signer, input = rawMetaTxData.toList() ) - return@signMessage callback(null, wrapperTx.encodeRLP()) + return@signETH callback(null, wrapperTx.encodeRLP()) } From 69d837e0ce0d06358c06d85d2ef6cbc842fee209 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 28 Sep 2018 10:29:13 +0300 Subject: [PATCH 04/75] move Signer interface definition and SimpleSigner to the `signer` module --- build.gradle | 2 +- .../src/main/java/me/uport/sdk/ethrdid/EthrDID.kt | 4 ++-- .../java/me/uport/sdk/ethrdid/EthrDIDResolver.kt | 1 + .../test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- .../src/main/java/me/uport/sdk/identity/Account.kt | 2 +- .../me/uport/sdk/identity/UportHDSignerWrapper.kt | 2 +- sdk/src/main/java/me/uport/sdk/Transactions.kt | 4 ++-- .../java/me/uport/sdk/signer/MetaIdentitySigner.kt | 2 +- .../main/java/me/uport/sdk/signer/TxRelaySigner.kt | 2 +- signer/build.gradle | 12 ++++++++++-- .../src/main/java/com/uport/sdk/signer}/Signer.kt | 2 +- .../main/java/com}/uport/sdk/signer/SimpleSigner.kt | 4 ++-- .../java/com/uport/sdk/signer/storage/CryptoUtil.kt | 2 +- .../java/com/uport/sdk/signer}/BlankSignerTests.kt | 2 +- 15 files changed, 28 insertions(+), 19 deletions(-) rename {core/src/main/java/me/uport/sdk/core => signer/src/main/java/com/uport/sdk/signer}/Signer.kt (98%) rename {sdk/src/main/java/me => signer/src/main/java/com}/uport/sdk/signer/SimpleSigner.kt (95%) rename {core/src/test/java/me/uport/sdk/core => signer/src/test/java/com/uport/sdk/signer}/BlankSignerTests.kt (97%) diff --git a/build.gradle b/build.gradle index 43155f69..549cfcb4 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { kotlin_serialization_version = '0.6.1' coroutines_version = "0.26.1" - android_tools_version = '3.3.0-alpha11' + android_tools_version = '3.3.0-alpha12' build_tools_version = "28.0.2" diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt index fdf7c465..5e8ee810 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt @@ -1,7 +1,7 @@ package me.uport.sdk.ethrdid -import me.uport.sdk.core.Signer -import me.uport.sdk.core.signRawTx +import com.uport.sdk.signer.Signer +import com.uport.sdk.signer.signRawTx import me.uport.sdk.ethrdid.DelegateType.Secp256k1VerificationKey2018 import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jsonrpc.JsonRpcBaseResponse diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt index 14e7b1c9..d896c354 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt @@ -2,6 +2,7 @@ package me.uport.sdk.ethrdid import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PRIVATE +import com.uport.sdk.signer.Signer import kotlinx.coroutines.experimental.GlobalScope import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.withContext diff --git a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt index 44807caa..ae19e620 100644 --- a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt +++ b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt @@ -6,7 +6,7 @@ import com.nhaarman.mockitokotlin2.* import kotlinx.coroutines.experimental.runBlocking import me.uport.sdk.DEFAULT_GAS_PRICE import me.uport.sdk.jsonrpc.JsonRPC -import me.uport.sdk.signer.SimpleSigner +import com.uport.sdk.signer.SimpleSigner import org.junit.Assert.assertEquals import org.junit.Test import org.mockito.ArgumentMatchers.anyString diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index acafbea8..b971f986 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Sep 24 12:51:56 EEST 2018 +#Thu Sep 27 17:26:34 EEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/identity/src/main/java/me/uport/sdk/identity/Account.kt b/identity/src/main/java/me/uport/sdk/identity/Account.kt index d3d06c89..65404cb2 100644 --- a/identity/src/main/java/me/uport/sdk/identity/Account.kt +++ b/identity/src/main/java/me/uport/sdk/identity/Account.kt @@ -6,7 +6,7 @@ import android.support.annotation.VisibleForTesting.PACKAGE_PRIVATE import com.squareup.moshi.Json import com.uport.sdk.signer.UportHDSigner import me.uport.mnid.MNID -import me.uport.sdk.core.Signer +import com.uport.sdk.signer.Signer import me.uport.sdk.identity.endpoints.moshi data class Account( diff --git a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt index ffe1e48d..01db728d 100644 --- a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt +++ b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt @@ -3,7 +3,7 @@ package me.uport.sdk.identity import android.content.Context import android.util.Base64 import com.uport.sdk.signer.UportHDSigner -import me.uport.sdk.core.Signer +import com.uport.sdk.signer.Signer import org.kethereum.model.SignatureData /** diff --git a/sdk/src/main/java/me/uport/sdk/Transactions.kt b/sdk/src/main/java/me/uport/sdk/Transactions.kt index 93d41d1a..e10ad47c 100644 --- a/sdk/src/main/java/me/uport/sdk/Transactions.kt +++ b/sdk/src/main/java/me/uport/sdk/Transactions.kt @@ -2,8 +2,8 @@ package me.uport.sdk import android.content.Context import me.uport.sdk.core.Networks -import me.uport.sdk.core.Signer -import me.uport.sdk.core.signRawTx +import com.uport.sdk.signer.Signer +import com.uport.sdk.signer.signRawTx import me.uport.sdk.endpoints.Sensui import me.uport.sdk.extensions.waitForTransactionToMine import me.uport.sdk.identity.Account diff --git a/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt b/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt index c5723d3b..92273a6f 100644 --- a/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt +++ b/sdk/src/main/java/me/uport/sdk/signer/MetaIdentitySigner.kt @@ -1,7 +1,7 @@ package me.uport.sdk.signer import me.uport.sdk.MetaIdentityManager -import me.uport.sdk.core.Signer +import com.uport.sdk.signer.Signer import org.kethereum.extensions.hexToBigInteger import org.kethereum.model.Address import org.kethereum.model.SignatureData diff --git a/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt b/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt index 6e904eae..fdd1ade6 100644 --- a/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt +++ b/sdk/src/main/java/me/uport/sdk/signer/TxRelaySigner.kt @@ -1,7 +1,7 @@ package me.uport.sdk.signer import me.uport.sdk.core.EthNetwork -import me.uport.sdk.core.Signer +import com.uport.sdk.signer.Signer import me.uport.sdk.signer.TxRelayHelper.Companion.ZERO_ADDRESS import org.kethereum.extensions.toBytesPadded import org.kethereum.functions.encodeRLP diff --git a/signer/build.gradle b/signer/build.gradle index 05e20ea2..985473f9 100644 --- a/signer/build.gradle +++ b/signer/build.gradle @@ -39,6 +39,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "com.android.support:appcompat-v7:$support_lib_version" api "com.github.walleth.kethereum:crypto:$kethereum_version" @@ -51,10 +52,17 @@ dependencies { } -repositories { - mavenCentral() + +sourceCompatibility = "1.7" +targetCompatibility = "1.7" + +kotlin { + experimental { + coroutines "enable" + } } + // //task androidJavadocs(type: Javadoc) { // source = android.sourceSets.main.java.srcDirs diff --git a/core/src/main/java/me/uport/sdk/core/Signer.kt b/signer/src/main/java/com/uport/sdk/signer/Signer.kt similarity index 98% rename from core/src/main/java/me/uport/sdk/core/Signer.kt rename to signer/src/main/java/com/uport/sdk/signer/Signer.kt index 77b3c11c..8f6bd046 100644 --- a/core/src/main/java/me/uport/sdk/core/Signer.kt +++ b/signer/src/main/java/com/uport/sdk/signer/Signer.kt @@ -1,4 +1,4 @@ -package me.uport.sdk.core +package com.uport.sdk.signer import org.kethereum.functions.encodeRLP import org.kethereum.model.SignatureData diff --git a/sdk/src/main/java/me/uport/sdk/signer/SimpleSigner.kt b/signer/src/main/java/com/uport/sdk/signer/SimpleSigner.kt similarity index 95% rename from sdk/src/main/java/me/uport/sdk/signer/SimpleSigner.kt rename to signer/src/main/java/com/uport/sdk/signer/SimpleSigner.kt index ac21baa0..04fc6538 100644 --- a/sdk/src/main/java/me/uport/sdk/signer/SimpleSigner.kt +++ b/signer/src/main/java/com/uport/sdk/signer/SimpleSigner.kt @@ -1,6 +1,6 @@ -package me.uport.sdk.signer +package com.uport.sdk.signer -import me.uport.sdk.core.Signer +import com.uport.sdk.signer.Signer import org.kethereum.crypto.ECKeyPair import org.kethereum.crypto.getAddress import org.kethereum.crypto.signMessage diff --git a/signer/src/main/java/com/uport/sdk/signer/storage/CryptoUtil.kt b/signer/src/main/java/com/uport/sdk/signer/storage/CryptoUtil.kt index c0dffa12..5886eec0 100644 --- a/signer/src/main/java/com/uport/sdk/signer/storage/CryptoUtil.kt +++ b/signer/src/main/java/com/uport/sdk/signer/storage/CryptoUtil.kt @@ -58,7 +58,7 @@ class CryptoUtil(context: Context, private val alias: String = DEFAULT_ALIAS) { val secretKey = keyStore.getKey(alias, null) ?: genEncryptionKey() cipher.init(Cipher.ENCRYPT_MODE, secretKey) - //FIXME: On some devices (like emulator with API 24) this throws IllegalBlockSizeException for large blobs (ex 4096 bytes) + //FIXME: On some devices (like emulator with API 24 & 26) this throws IllegalBlockSizeException for large blobs (ex 4096 bytes) val encryptedBytes = cipher.doFinal(blob) return packCiphertext(cipher.iv, encryptedBytes) diff --git a/core/src/test/java/me/uport/sdk/core/BlankSignerTests.kt b/signer/src/test/java/com/uport/sdk/signer/BlankSignerTests.kt similarity index 97% rename from core/src/test/java/me/uport/sdk/core/BlankSignerTests.kt rename to signer/src/test/java/com/uport/sdk/signer/BlankSignerTests.kt index e57611fe..32a176ff 100644 --- a/core/src/test/java/me/uport/sdk/core/BlankSignerTests.kt +++ b/signer/src/test/java/com/uport/sdk/signer/BlankSignerTests.kt @@ -1,4 +1,4 @@ -package me.uport.sdk.core +package com.uport.sdk.signer import kotlinx.coroutines.experimental.runBlocking import org.junit.Assert From d9871d93d2c6f5192c53798321fdfb8fb5f1129b Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 28 Sep 2018 23:10:28 +0300 Subject: [PATCH 05/75] add tests for SimpleSigner --- .../sdk/identity/UportHDSignerWrapper.kt | 2 +- .../com/uport/sdk/signer/SimpleSignerTests.kt | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt diff --git a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt index 01db728d..e78b9451 100644 --- a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt +++ b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt @@ -7,7 +7,7 @@ import com.uport.sdk.signer.Signer import org.kethereum.model.SignatureData /** - * Wrapps the [uportHDSigner] into a [Signer] interface + * Wraps the [uportHDSigner] into a [Signer] interface */ class UportHDSignerWrapper( private val context: Context, diff --git a/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt b/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt new file mode 100644 index 00000000..b0d9cd48 --- /dev/null +++ b/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt @@ -0,0 +1,86 @@ +package com.uport.sdk.signer + +import kotlinx.coroutines.experimental.runBlocking +import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.kethereum.extensions.hexToBigInteger +import org.kethereum.model.SignatureData +import org.walleth.khex.hexToByteArray +import org.walleth.khex.prepend0xPrefix + +class SimpleSignerTests { + + private val tested = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + + @Test + fun `sign ETH calls back without error`() { + + val referencePrivateKey = "3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158" + + val expectedSignature = SignatureData( + r = "809e3b5ef25f4a3b039139e2fb70f70b636eba89c77a3b01e0c71c1a36d84126".hexToBigInteger(), + s = "38524dfcd3e412cb6bc37f4594bbad104b6764bb14c64e42c699730106d1885a".hexToBigInteger(), + v = 28.toByte()) + + val rawTransactionBytes = "f380850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef01234567890".hexToByteArray() + + val signer = SimpleSigner(referencePrivateKey) + + signer.signETH(rawTransactionBytes) { err, sigData -> + Assert.assertNull(err) + assertEquals(expectedSignature, sigData) + } + } + + @Test + fun `signJWT calls back with correct signature`() { + val referencePayload = "Hello, world!".toByteArray() + + val referenceSignature = SignatureData( + r = "6bcd81446183af193ca4a172d5c5c26345903b24770d90b5d790f74a9dec1f68".hexToBigInteger(), + s = "e2b85b3c92c9b4f3cf58de46e7997d8efb6e14b2e532d13dfa22ee02f3a43d5d".hexToBigInteger(), + v = 28.toByte()) + + tested.signJWT(referencePayload) { err, sigData -> + assertNull(err) + Assert.assertEquals(referenceSignature, sigData) + } + } + + @Test + fun `signETH coroutine passes`() = runBlocking { + val referencePrivateKey = "3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158" + + val expectedSignature = SignatureData( + r = "809e3b5ef25f4a3b039139e2fb70f70b636eba89c77a3b01e0c71c1a36d84126".hexToBigInteger(), + s = "38524dfcd3e412cb6bc37f4594bbad104b6764bb14c64e42c699730106d1885a".hexToBigInteger(), + v = 28.toByte()) + + val rawTransactionBytes = "f380850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef01234567890".hexToByteArray() + + val signer = SimpleSigner(referencePrivateKey) + val sigData = signer.signETH(rawTransactionBytes) + + assertEquals(expectedSignature, sigData) + } + + @Test + fun `signJWT coroutine passes`() = runBlocking { + val referencePayload = "Hello, world!".toByteArray() + + val referenceSignature = SignatureData( + r = "6bcd81446183af193ca4a172d5c5c26345903b24770d90b5d790f74a9dec1f68".hexToBigInteger(), + s = "e2b85b3c92c9b4f3cf58de46e7997d8efb6e14b2e532d13dfa22ee02f3a43d5d".hexToBigInteger(), + v = 28.toByte()) + + val sigData = tested.signJWT(referencePayload) + Assert.assertEquals(referenceSignature, sigData) + } + + @Test + fun `returned address is non null`() { + assertEquals("0x794adde0672914159c1b77dd06d047904fe96ac8", tested.getAddress().prepend0xPrefix()) + } +} \ No newline at end of file From 5c309ba7bb1fa2456cd9a93b158091c23e22ec05 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 2 Oct 2018 17:46:16 +0200 Subject: [PATCH 06/75] refactor simple signer tests for uniformity --- .../com/uport/sdk/signer/SimpleSignerTests.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt b/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt index b0d9cd48..3f912222 100644 --- a/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt +++ b/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt @@ -12,13 +12,9 @@ import org.walleth.khex.prepend0xPrefix class SimpleSignerTests { - private val tested = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") - @Test fun `sign ETH calls back without error`() { - val referencePrivateKey = "3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158" - val expectedSignature = SignatureData( r = "809e3b5ef25f4a3b039139e2fb70f70b636eba89c77a3b01e0c71c1a36d84126".hexToBigInteger(), s = "38524dfcd3e412cb6bc37f4594bbad104b6764bb14c64e42c699730106d1885a".hexToBigInteger(), @@ -26,7 +22,7 @@ class SimpleSignerTests { val rawTransactionBytes = "f380850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef01234567890".hexToByteArray() - val signer = SimpleSigner(referencePrivateKey) + val signer = SimpleSigner("3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158") signer.signETH(rawTransactionBytes) { err, sigData -> Assert.assertNull(err) @@ -43,7 +39,9 @@ class SimpleSignerTests { s = "e2b85b3c92c9b4f3cf58de46e7997d8efb6e14b2e532d13dfa22ee02f3a43d5d".hexToBigInteger(), v = 28.toByte()) - tested.signJWT(referencePayload) { err, sigData -> + val signer = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + + signer.signJWT(referencePayload) { err, sigData -> assertNull(err) Assert.assertEquals(referenceSignature, sigData) } @@ -51,7 +49,6 @@ class SimpleSignerTests { @Test fun `signETH coroutine passes`() = runBlocking { - val referencePrivateKey = "3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158" val expectedSignature = SignatureData( r = "809e3b5ef25f4a3b039139e2fb70f70b636eba89c77a3b01e0c71c1a36d84126".hexToBigInteger(), @@ -60,7 +57,7 @@ class SimpleSignerTests { val rawTransactionBytes = "f380850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef01234567890".hexToByteArray() - val signer = SimpleSigner(referencePrivateKey) + val signer = SimpleSigner("3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158") val sigData = signer.signETH(rawTransactionBytes) assertEquals(expectedSignature, sigData) @@ -75,12 +72,14 @@ class SimpleSignerTests { s = "e2b85b3c92c9b4f3cf58de46e7997d8efb6e14b2e532d13dfa22ee02f3a43d5d".hexToBigInteger(), v = 28.toByte()) - val sigData = tested.signJWT(referencePayload) + val signer = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + val sigData = signer.signJWT(referencePayload) Assert.assertEquals(referenceSignature, sigData) } @Test fun `returned address is non null`() { - assertEquals("0x794adde0672914159c1b77dd06d047904fe96ac8", tested.getAddress().prepend0xPrefix()) + val signer = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + assertEquals("0x794adde0672914159c1b77dd06d047904fe96ac8", signer.getAddress().prepend0xPrefix()) } } \ No newline at end of file From 7e643e862cc5f1f00e4ddfefe768886f1a140b1f Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 2 Oct 2018 17:49:27 +0200 Subject: [PATCH 07/75] rename hd signer wrapper --- identity/src/main/java/me/uport/sdk/identity/Account.kt | 2 +- .../{UportHDSignerWrapper.kt => UportHDSignerImpl.kt} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename identity/src/main/java/me/uport/sdk/identity/{UportHDSignerWrapper.kt => UportHDSignerImpl.kt} (94%) diff --git a/identity/src/main/java/me/uport/sdk/identity/Account.kt b/identity/src/main/java/me/uport/sdk/identity/Account.kt index 65404cb2..d9a7ba2c 100644 --- a/identity/src/main/java/me/uport/sdk/identity/Account.kt +++ b/identity/src/main/java/me/uport/sdk/identity/Account.kt @@ -47,7 +47,7 @@ data class Account( fun toJson(pretty: Boolean = false): String = adapter.indent(if (pretty) " " else "").toJson(this) - fun getSigner(context: Context): Signer = UportHDSignerWrapper(context, UportHDSigner(), rootAddress = handle, deviceAddress = deviceAddress) + fun getSigner(context: Context): Signer = UportHDSignerImpl(context, UportHDSigner(), rootAddress = handle, deviceAddress = deviceAddress) companion object { diff --git a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerImpl.kt similarity index 94% rename from identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt rename to identity/src/main/java/me/uport/sdk/identity/UportHDSignerImpl.kt index e78b9451..3568bfb7 100644 --- a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerWrapper.kt +++ b/identity/src/main/java/me/uport/sdk/identity/UportHDSignerImpl.kt @@ -7,9 +7,9 @@ import com.uport.sdk.signer.Signer import org.kethereum.model.SignatureData /** - * Wraps the [uportHDSigner] into a [Signer] interface + * Wraps a [UportHDSigner] into a [Signer] interface */ -class UportHDSignerWrapper( +class UportHDSignerImpl( private val context: Context, private val uportHDSigner: UportHDSigner, private val rootAddress: String, From 46d9c06d3733545cfef17d8c2e69cd70bf1c153d Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 2 Oct 2018 18:09:19 +0200 Subject: [PATCH 08/75] move the HD wrappers for Signer to the signer module --- .../src/main/java/me/uport/sdk/identity/Account.kt | 11 +++-------- .../java/me/uport/sdk/identity/KPAccountCreator.kt | 6 +++++- .../src/main/java/com/uport/sdk/signer/Signer.kt | 13 +++++++++++++ .../java/com/uport/sdk/signer/UportHDSigner.kt | 6 +++++- .../uport/sdk/signer}/UportHDSignerExtensions.kt | 8 ++++++-- .../com/uport/sdk/signer}/UportHDSignerImpl.kt | 14 ++++++++------ 6 files changed, 40 insertions(+), 18 deletions(-) rename {identity/src/main/java/me/uport/sdk/identity => signer/src/main/java/com/uport/sdk/signer}/UportHDSignerExtensions.kt (94%) rename {identity/src/main/java/me/uport/sdk/identity => signer/src/main/java/com/uport/sdk/signer}/UportHDSignerImpl.kt (75%) diff --git a/identity/src/main/java/me/uport/sdk/identity/Account.kt b/identity/src/main/java/me/uport/sdk/identity/Account.kt index d9a7ba2c..cf3158de 100644 --- a/identity/src/main/java/me/uport/sdk/identity/Account.kt +++ b/identity/src/main/java/me/uport/sdk/identity/Account.kt @@ -4,9 +4,10 @@ import android.content.Context import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PACKAGE_PRIVATE import com.squareup.moshi.Json +import com.uport.sdk.signer.Signer import com.uport.sdk.signer.UportHDSigner +import com.uport.sdk.signer.UportHDSignerImpl import me.uport.mnid.MNID -import com.uport.sdk.signer.Signer import me.uport.sdk.identity.endpoints.moshi data class Account( @@ -40,7 +41,7 @@ data class Account( val isDefault: Boolean? = false ) { - val address : String + val address: String get() = getMnid() fun getMnid() = MNID.encode(network, publicAddress) @@ -51,12 +52,6 @@ data class Account( companion object { - /** - * TODO: should be used to derive SDK KeyPairs instead of the UPORT_ROOT - */ - const val GENERIC_DEVICE_KEY_DERIVATION_PATH = "m/44'/60'/0'/0" - const val GENERIC_RECOVERY_DERIVATION_PATH = "m/44'/60'/0'/1" - val blank = Account("", "", "", "", "", "", "", SignerType.KeyPair) private val adapter = moshi.adapter(Account::class.java) diff --git a/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt b/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt index c8698dd8..c9b039ee 100644 --- a/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt +++ b/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt @@ -2,7 +2,11 @@ package me.uport.sdk.identity import android.content.Context import com.uport.sdk.signer.UportHDSigner +import com.uport.sdk.signer.UportHDSigner.Companion.GENERIC_DEVICE_KEY_DERIVATION_PATH +import com.uport.sdk.signer.computeAddressForPath +import com.uport.sdk.signer.createHDSeed import com.uport.sdk.signer.encryption.KeyProtection +import com.uport.sdk.signer.importHDSeed import kotlinx.coroutines.experimental.GlobalScope import kotlinx.coroutines.experimental.launch import me.uport.sdk.core.UI @@ -21,7 +25,7 @@ class KPAccountCreator(private val appContext: Context) : AccountCreator { } val (deviceAddress, _) = signer.computeAddressForPath(appContext, handle, - Account.GENERIC_DEVICE_KEY_DERIVATION_PATH, + GENERIC_DEVICE_KEY_DERIVATION_PATH, "") val account = Account( handle, diff --git a/signer/src/main/java/com/uport/sdk/signer/Signer.kt b/signer/src/main/java/com/uport/sdk/signer/Signer.kt index 8f6bd046..d34d7873 100644 --- a/signer/src/main/java/com/uport/sdk/signer/Signer.kt +++ b/signer/src/main/java/com/uport/sdk/signer/Signer.kt @@ -15,11 +15,24 @@ typealias SignatureCallback = (err: Exception?, sigData: SignatureData) -> Unit */ interface Signer { + /** + * Signs a blob of bytes that represent a RLP encoded transaction. + */ fun signETH(rawMessage: ByteArray, callback: SignatureCallback) + + /** + * Signs a blob of bytes that represent the encoded header and payload parts of a JWT + */ fun signJWT(rawPayload: ByteArray, callback: SignatureCallback) + /** + * returns the ethereum address corresponding to the key that does the signing + */ fun getAddress() : String + /** + * + */ fun signRawTx( unsignedTx: Transaction, callback: (err: Exception?, diff --git a/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt b/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt index 7f7b6347..f1f5b89b 100644 --- a/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt +++ b/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt @@ -292,12 +292,16 @@ class UportHDSigner : UportSigner() { val prefs = context.getSharedPreferences(ETH_ENCRYPTED_STORAGE, MODE_PRIVATE) //list all stored keys, keep a list off what looks like uport root addresses return prefs.all.keys + .asSequence() .filter { label -> label.startsWith(SEED_PREFIX) } .filter { hasCorrespondingLevelKey(prefs, it) } .map { label: String -> label.substring(SEED_PREFIX.length) } + .toList() } companion object { - const val UPORT_ROOT_DERIVATION_PATH: String = "m/7696500'/0'/0'/0'" + const val UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'" + const val GENERIC_DEVICE_KEY_DERIVATION_PATH = "m/44'/60'/0'/0" + const val GENERIC_RECOVERY_DERIVATION_PATH = "m/44'/60'/0'/1" } } \ No newline at end of file diff --git a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerExtensions.kt b/signer/src/main/java/com/uport/sdk/signer/UportHDSignerExtensions.kt similarity index 94% rename from identity/src/main/java/me/uport/sdk/identity/UportHDSignerExtensions.kt rename to signer/src/main/java/com/uport/sdk/signer/UportHDSignerExtensions.kt index a1f15ae3..2313d81e 100644 --- a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerExtensions.kt +++ b/signer/src/main/java/com/uport/sdk/signer/UportHDSignerExtensions.kt @@ -1,10 +1,14 @@ -package me.uport.sdk.identity +package com.uport.sdk.signer import android.content.Context -import com.uport.sdk.signer.UportHDSigner import com.uport.sdk.signer.encryption.KeyProtection import kotlin.coroutines.experimental.suspendCoroutine +/** + * + * Exposes some HD key provider async methods as coroutines + */ + suspend fun UportHDSigner.createHDSeed( context: Context, level: KeyProtection.Level): Pair = suspendCoroutine { diff --git a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerImpl.kt b/signer/src/main/java/com/uport/sdk/signer/UportHDSignerImpl.kt similarity index 75% rename from identity/src/main/java/me/uport/sdk/identity/UportHDSignerImpl.kt rename to signer/src/main/java/com/uport/sdk/signer/UportHDSignerImpl.kt index 3568bfb7..e1098fa4 100644 --- a/identity/src/main/java/me/uport/sdk/identity/UportHDSignerImpl.kt +++ b/signer/src/main/java/com/uport/sdk/signer/UportHDSignerImpl.kt @@ -1,13 +1,15 @@ -package me.uport.sdk.identity +package com.uport.sdk.signer import android.content.Context import android.util.Base64 -import com.uport.sdk.signer.UportHDSigner -import com.uport.sdk.signer.Signer +import com.uport.sdk.signer.UportHDSigner.Companion.GENERIC_DEVICE_KEY_DERIVATION_PATH import org.kethereum.model.SignatureData /** - * Wraps a [UportHDSigner] into a [Signer] interface + * Wraps a [UportHDSigner] into a [Signer] interface. + * + * The HD key provider it wraps needs an activity context for keys that are linked to user-auth. + * This object should not be long-lived */ class UportHDSignerImpl( private val context: Context, @@ -23,7 +25,7 @@ class UportHDSignerImpl( return uportHDSigner.signTransaction( context, //FIXME: Not cool hiding the context like this... may lead to leaks rootAddress, - Account.GENERIC_DEVICE_KEY_DERIVATION_PATH, + GENERIC_DEVICE_KEY_DERIVATION_PATH, Base64.encodeToString(rawMessage, Base64.DEFAULT), "", callback) @@ -34,7 +36,7 @@ class UportHDSignerImpl( return uportHDSigner.signJwtBundle( context, //FIXME: Not cool hiding the context like this... may lead to leaks rootAddress, - Account.GENERIC_DEVICE_KEY_DERIVATION_PATH, + GENERIC_DEVICE_KEY_DERIVATION_PATH, Base64.encodeToString(rawPayload, Base64.DEFAULT), "", callback) From 65b38d77d2d0ddfaab52e21e16bd548ccc450e2b Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 2 Oct 2018 18:09:37 +0200 Subject: [PATCH 09/75] rename SimpleSigner to KPSigner --- .../src/main/java/me/uport/sdk/ethrdid/EthrDID.kt | 2 +- .../test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt | 6 +++--- .../sdk/signer/{SimpleSigner.kt => KPSigner.kt} | 5 ++--- .../{SimpleSignerTests.kt => KPSignerTests.kt} | 12 ++++++------ 4 files changed, 12 insertions(+), 13 deletions(-) rename signer/src/main/java/com/uport/sdk/signer/{SimpleSigner.kt => KPSigner.kt} (88%) rename signer/src/test/java/com/uport/sdk/signer/{SimpleSignerTests.kt => KPSignerTests.kt} (84%) diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt index 5e8ee810..e10de37f 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt @@ -94,7 +94,7 @@ class EthrDID( // // Create a temporary signing delegate able to sign JWT on behalf of identity // suspend fun createSigningDelegate(delegateType: String = "Secp256k1VerificationKey2018", expiresIn: Long = 86400L) { // val kp = createKeyPair() -// this.signer = SimpleSigner(kp.privateKey) +// this.signer = KPSigner(kp.privateKey) // const txHash = await this.addDelegate(kp.address, { delegateType, expiresIn }) // return { kp, txHash } // } diff --git a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt index ae19e620..7d940c76 100644 --- a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt +++ b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt @@ -6,7 +6,7 @@ import com.nhaarman.mockitokotlin2.* import kotlinx.coroutines.experimental.runBlocking import me.uport.sdk.DEFAULT_GAS_PRICE import me.uport.sdk.jsonrpc.JsonRPC -import com.uport.sdk.signer.SimpleSigner +import com.uport.sdk.signer.KPSigner import org.junit.Assert.assertEquals import org.junit.Test import org.mockito.ArgumentMatchers.anyString @@ -38,7 +38,7 @@ class EthrDIDTest { (it.arguments.last() as CBI).invoke(null, "{\"result\":\"0x0000000000000000000000001122334455667788990011223344556677889900\"}") } - val ethrDid = EthrDID("0x11", rpc, rinkebyRegistry, SimpleSigner(originalPrivKey)) + val ethrDid = EthrDID("0x11", rpc, rinkebyRegistry, KPSigner(originalPrivKey)) val owner = runBlocking { ethrDid.lookupOwner() } @@ -50,7 +50,7 @@ class EthrDIDTest { runBlocking { - val signer = SimpleSigner(originalPrivKey) + val signer = KPSigner(originalPrivKey) val address = signer.getAddress().prepend0xPrefix() val rpc = mock() diff --git a/signer/src/main/java/com/uport/sdk/signer/SimpleSigner.kt b/signer/src/main/java/com/uport/sdk/signer/KPSigner.kt similarity index 88% rename from signer/src/main/java/com/uport/sdk/signer/SimpleSigner.kt rename to signer/src/main/java/com/uport/sdk/signer/KPSigner.kt index 04fc6538..c054ff05 100644 --- a/signer/src/main/java/com/uport/sdk/signer/SimpleSigner.kt +++ b/signer/src/main/java/com/uport/sdk/signer/KPSigner.kt @@ -1,6 +1,5 @@ package com.uport.sdk.signer -import com.uport.sdk.signer.Signer import org.kethereum.crypto.ECKeyPair import org.kethereum.crypto.getAddress import org.kethereum.crypto.signMessage @@ -10,11 +9,11 @@ import org.kethereum.hashes.sha256 import org.kethereum.model.SignatureData /** - * Simple [Signer] implementation that holds its keys in memory. + * Simple [Signer] implementation that holds the KeyPair in memory. * * There is no special handling of threads for callbacks. */ -class SimpleSigner(private val privateKey: String) : Signer { +class KPSigner(private val privateKey: String) : Signer { override fun signJWT(rawPayload: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { try { diff --git a/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt b/signer/src/test/java/com/uport/sdk/signer/KPSignerTests.kt similarity index 84% rename from signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt rename to signer/src/test/java/com/uport/sdk/signer/KPSignerTests.kt index 3f912222..278b336b 100644 --- a/signer/src/test/java/com/uport/sdk/signer/SimpleSignerTests.kt +++ b/signer/src/test/java/com/uport/sdk/signer/KPSignerTests.kt @@ -10,7 +10,7 @@ import org.kethereum.model.SignatureData import org.walleth.khex.hexToByteArray import org.walleth.khex.prepend0xPrefix -class SimpleSignerTests { +class KPSignerTests { @Test fun `sign ETH calls back without error`() { @@ -22,7 +22,7 @@ class SimpleSignerTests { val rawTransactionBytes = "f380850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef01234567890".hexToByteArray() - val signer = SimpleSigner("3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158") + val signer = KPSigner("3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158") signer.signETH(rawTransactionBytes) { err, sigData -> Assert.assertNull(err) @@ -39,7 +39,7 @@ class SimpleSignerTests { s = "e2b85b3c92c9b4f3cf58de46e7997d8efb6e14b2e532d13dfa22ee02f3a43d5d".hexToBigInteger(), v = 28.toByte()) - val signer = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + val signer = KPSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") signer.signJWT(referencePayload) { err, sigData -> assertNull(err) @@ -57,7 +57,7 @@ class SimpleSignerTests { val rawTransactionBytes = "f380850ba43b7400832fefd8949e2068cce22de4e1e80f15cb71ef435a20a3b37c880de0b6b3a7640000890abcdef01234567890".hexToByteArray() - val signer = SimpleSigner("3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158") + val signer = KPSigner("3686e245890c7f997766b73a21d8e59f6385e1208831af3862574790cbc3d158") val sigData = signer.signETH(rawTransactionBytes) assertEquals(expectedSignature, sigData) @@ -72,14 +72,14 @@ class SimpleSignerTests { s = "e2b85b3c92c9b4f3cf58de46e7997d8efb6e14b2e532d13dfa22ee02f3a43d5d".hexToBigInteger(), v = 28.toByte()) - val signer = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + val signer = KPSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") val sigData = signer.signJWT(referencePayload) Assert.assertEquals(referenceSignature, sigData) } @Test fun `returned address is non null`() { - val signer = SimpleSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + val signer = KPSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") assertEquals("0x794adde0672914159c1b77dd06d047904fe96ac8", signer.getAddress().prepend0xPrefix()) } } \ No newline at end of file From 5c41d50306b3934f1a814124a814c5d05933388d Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 2 Oct 2018 18:13:36 +0200 Subject: [PATCH 10/75] rename SignerType to AccountType --- identity/src/androidTest/java/KPAccountCreatorTest.kt | 4 ++-- .../src/main/java/me/uport/sdk/identity/Account.kt | 4 ++-- .../sdk/identity/{SignerType.kt => AccountType.kt} | 2 +- .../java/me/uport/sdk/identity/KPAccountCreator.kt | 2 +- .../uport/sdk/identity/MetaIdentityAccountCreator.kt | 2 +- .../test/java/me/uport/sdk/identity/AccountsTests.kt | 2 +- sdk/src/main/java/me/uport/sdk/Transactions.kt | 10 +++++----- .../me/uport/sdk/extensions/TransactionExtensions.kt | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) rename identity/src/main/java/me/uport/sdk/identity/{SignerType.kt => AccountType.kt} (86%) diff --git a/identity/src/androidTest/java/KPAccountCreatorTest.kt b/identity/src/androidTest/java/KPAccountCreatorTest.kt index c5ef5ac3..bc96e3a2 100644 --- a/identity/src/androidTest/java/KPAccountCreatorTest.kt +++ b/identity/src/androidTest/java/KPAccountCreatorTest.kt @@ -23,7 +23,7 @@ class KPAccountCreatorTest { val account = KPAccountCreator(appContext).createAccount(Networks.rinkeby.network_id) assertNotNull(account) assertNotEquals(Account.blank, account) - assertTrue(account.signerType == SignerType.KeyPair) + assertTrue(account.type == AccountType.KeyPair) assertTrue(account.address.isNotEmpty()) assertTrue(account.publicAddress.isNotEmpty()) assertTrue(account.deviceAddress.isNotEmpty()) @@ -39,7 +39,7 @@ class KPAccountCreatorTest { val account = KPAccountCreator(appContext).importAccount(Networks.rinkeby.network_id, referenceSeedPhrase) assertNotNull(account) assertNotEquals(Account.blank, account) - assertTrue(account.signerType == SignerType.KeyPair) + assertTrue(account.type == AccountType.KeyPair) assertEquals("2opxPamUQoLarQHAoVDKo2nDNmfQLNCZif4", account.address) assertEquals("0x847e5e3e8b2961c2225cb4a2f719d5409c7488c6", account.publicAddress) assertEquals("0x847e5e3e8b2961c2225cb4a2f719d5409c7488c6", account.deviceAddress) diff --git a/identity/src/main/java/me/uport/sdk/identity/Account.kt b/identity/src/main/java/me/uport/sdk/identity/Account.kt index cf3158de..81650149 100644 --- a/identity/src/main/java/me/uport/sdk/identity/Account.kt +++ b/identity/src/main/java/me/uport/sdk/identity/Account.kt @@ -35,7 +35,7 @@ data class Account( val fuelToken: String, @Json(name = "signerType") - val signerType: SignerType = SignerType.KeyPair, + val type: AccountType = AccountType.KeyPair, @Json(name = "isDefault") val isDefault: Boolean? = false @@ -52,7 +52,7 @@ data class Account( companion object { - val blank = Account("", "", "", "", "", "", "", SignerType.KeyPair) + val blank = Account("", "", "", "", "", "", "", AccountType.KeyPair) private val adapter = moshi.adapter(Account::class.java) diff --git a/identity/src/main/java/me/uport/sdk/identity/SignerType.kt b/identity/src/main/java/me/uport/sdk/identity/AccountType.kt similarity index 86% rename from identity/src/main/java/me/uport/sdk/identity/SignerType.kt rename to identity/src/main/java/me/uport/sdk/identity/AccountType.kt index 770d03b5..2c25a853 100644 --- a/identity/src/main/java/me/uport/sdk/identity/SignerType.kt +++ b/identity/src/main/java/me/uport/sdk/identity/AccountType.kt @@ -3,7 +3,7 @@ package me.uport.sdk.identity import android.support.annotation.Keep @Keep -enum class SignerType { +enum class AccountType { KeyPair, MetaIdentityManager, Proxy, diff --git a/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt b/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt index c9b039ee..35e439ff 100644 --- a/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt +++ b/identity/src/main/java/me/uport/sdk/identity/KPAccountCreator.kt @@ -35,7 +35,7 @@ class KPAccountCreator(private val appContext: Context) : AccountCreator { "", "", "", - SignerType.KeyPair + AccountType.KeyPair ) launch(UI) { callback(null, account) } diff --git a/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt b/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt index 786de14f..0d50080f 100644 --- a/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt +++ b/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt @@ -136,7 +136,7 @@ class MetaIdentityAccountCreator( identityInfo.managerAddress, Networks.get(networkId).txRelayAddress, oldBundle.fuelToken, - SignerType.MetaIdentityManager + AccountType.MetaIdentityManager ) state = AccountCreationState.COMPLETE progress.save(state, oldBundle.copy(partialAccount = acc)) diff --git a/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt b/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt index f90bcc38..e9429eac 100644 --- a/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt +++ b/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt @@ -37,7 +37,7 @@ class AccountsTests { "manager":"0xidentityManagerAddress", "txRelay":"0xtxRelayAddress", "fuelToken":"base64FuelToken", - "signerType":"KeyPair" + "type":"KeyPair" }""".trimIndent() val account = Account.fromJson(serializedAccount) diff --git a/sdk/src/main/java/me/uport/sdk/Transactions.kt b/sdk/src/main/java/me/uport/sdk/Transactions.kt index e10ad47c..b2bdab5e 100644 --- a/sdk/src/main/java/me/uport/sdk/Transactions.kt +++ b/sdk/src/main/java/me/uport/sdk/Transactions.kt @@ -7,9 +7,9 @@ import com.uport.sdk.signer.signRawTx import me.uport.sdk.endpoints.Sensui import me.uport.sdk.extensions.waitForTransactionToMine import me.uport.sdk.identity.Account -import me.uport.sdk.identity.SignerType -import me.uport.sdk.identity.SignerType.* -import me.uport.sdk.identity.SignerType.MetaIdentityManager +import me.uport.sdk.identity.AccountType +import me.uport.sdk.identity.AccountType.* +import me.uport.sdk.identity.AccountType.MetaIdentityManager import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jsonrpc.experimental.getGasPrice import me.uport.sdk.jsonrpc.experimental.getTransactionCount @@ -41,7 +41,7 @@ class Transactions( * * Returns a modified [Transaction] object, ready to be signed and sent. */ - private suspend fun buildTransaction(request: Transaction, signerType: SignerType = Proxy): Transaction { + private suspend fun buildTransaction(request: Transaction, signerType: AccountType = Proxy): Transaction { var from = request.from val rpcRelayUrl = network.rpcUrl val rpcRelay = JsonRPC(rpcRelayUrl) @@ -83,7 +83,7 @@ class Transactions( gasLimit = gasLimit) } - suspend fun sendTransaction(signer: Signer, request: Transaction, signerType: SignerType = Proxy): String { + suspend fun sendTransaction(signer: Signer, request: Transaction, signerType: AccountType = Proxy): String { val txLabel = request.encodeRLP().toHexString() var (state, oldBundle) = if(progress.contains(txLabel)) { diff --git a/sdk/src/main/java/me/uport/sdk/extensions/TransactionExtensions.kt b/sdk/src/main/java/me/uport/sdk/extensions/TransactionExtensions.kt index 3d94c13a..088cd83f 100644 --- a/sdk/src/main/java/me/uport/sdk/extensions/TransactionExtensions.kt +++ b/sdk/src/main/java/me/uport/sdk/extensions/TransactionExtensions.kt @@ -48,7 +48,7 @@ suspend fun Account.send(context: Context, destinationAddress: String, value: Bi .sendTransaction( this.getSigner(context), rawTransaction, - this.signerType) + this.type) } /** @@ -66,7 +66,7 @@ suspend fun Account.send(context: Context, contractAddress: String, data: ByteAr .sendTransaction( this.getSigner(context), rawTransaction, - this.signerType) + this.type) } /** From 9fb5f5f0d143a03d35d063a6f6052ce2a2eea212 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 2 Oct 2018 18:18:40 +0200 Subject: [PATCH 11/75] fixed typo in class name --- sdk/src/main/java/me/uport/sdk/AccountStorage.kt | 2 +- sdk/src/main/java/me/uport/sdk/Uport.kt | 4 +--- sdk/src/test/java/me/uport/sdk/AccountStorageTest.kt | 10 +++++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/sdk/src/main/java/me/uport/sdk/AccountStorage.kt b/sdk/src/main/java/me/uport/sdk/AccountStorage.kt index 751a0e51..8cf3195c 100644 --- a/sdk/src/main/java/me/uport/sdk/AccountStorage.kt +++ b/sdk/src/main/java/me/uport/sdk/AccountStorage.kt @@ -21,7 +21,7 @@ interface AccountStorage { * * Accounts are loaded during construction and then relayed from memory */ -class SharedPrefsAcountStorage( +class SharedPrefsAccountStorage( private val prefs: SharedPreferences ) : AccountStorage { diff --git a/sdk/src/main/java/me/uport/sdk/Uport.kt b/sdk/src/main/java/me/uport/sdk/Uport.kt index 64799294..2ee8dfea 100644 --- a/sdk/src/main/java/me/uport/sdk/Uport.kt +++ b/sdk/src/main/java/me/uport/sdk/Uport.kt @@ -4,8 +4,6 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences -import android.support.annotation.VisibleForTesting -import android.support.annotation.VisibleForTesting.PRIVATE import kotlinx.coroutines.experimental.GlobalScope import kotlinx.coroutines.experimental.launch import me.uport.sdk.core.EthNetwork @@ -67,7 +65,7 @@ object Uport { prefs = context.getSharedPreferences(UPORT_CONFIG, MODE_PRIVATE) - accountStorage = SharedPrefsAcountStorage(prefs).apply { + accountStorage = SharedPrefsAccountStorage(prefs).apply { this.all().forEach { if (it.isDefault == true) { defaultAccountHandle = it.handle diff --git a/sdk/src/test/java/me/uport/sdk/AccountStorageTest.kt b/sdk/src/test/java/me/uport/sdk/AccountStorageTest.kt index 557bc6ef..d75eb603 100644 --- a/sdk/src/test/java/me/uport/sdk/AccountStorageTest.kt +++ b/sdk/src/test/java/me/uport/sdk/AccountStorageTest.kt @@ -9,7 +9,7 @@ class AccountStorageTest { @Test fun `can add and retrieve new account`() { - val storage: AccountStorage = SharedPrefsAcountStorage(InMemorySharedPrefs()) + val storage: AccountStorage = SharedPrefsAccountStorage(InMemorySharedPrefs()) val newAcc = Account("0xnewaccount", "", "", "", "", "", "") storage.upsert(newAcc) assertEquals(newAcc, storage.get("0xnewaccount")) @@ -17,7 +17,7 @@ class AccountStorageTest { @Test fun `can show all accounts`() { - val storage: AccountStorage = SharedPrefsAcountStorage(InMemorySharedPrefs()) + val storage: AccountStorage = SharedPrefsAccountStorage(InMemorySharedPrefs()) val accounts = (0..10).map { Account("0x$it", "", "", "", "", "", "") @@ -33,7 +33,7 @@ class AccountStorageTest { @Test fun `can delete account`() { - val storage: AccountStorage = SharedPrefsAcountStorage(InMemorySharedPrefs()) + val storage: AccountStorage = SharedPrefsAccountStorage(InMemorySharedPrefs()) val account = Account( "0xmyAccount", @@ -58,7 +58,7 @@ class AccountStorageTest { @Test fun `can overwrite account`() { - val storage: AccountStorage = SharedPrefsAcountStorage(InMemorySharedPrefs()) + val storage: AccountStorage = SharedPrefsAccountStorage(InMemorySharedPrefs()) val account = Account( "0xmyAccount", @@ -85,7 +85,7 @@ class AccountStorageTest { @Test fun `can upsert all`() { - val storage: AccountStorage = SharedPrefsAcountStorage(InMemorySharedPrefs()) + val storage: AccountStorage = SharedPrefsAccountStorage(InMemorySharedPrefs()) val accounts = (0..10).map { Account("0x$it", "", "", "", "", "", "") From f54b1ba60dee8c3beefa587adb78c2a2f0a896b0 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 2 Oct 2018 18:25:53 +0200 Subject: [PATCH 12/75] add time provider interface and default implementation --- .../java/me/uport/sdk/core/ITimeProvider.kt | 22 +++++++++++++++++++ .../me/uport/sdk/core/TimeProviderTest.kt | 15 +++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 core/src/main/java/me/uport/sdk/core/ITimeProvider.kt create mode 100644 core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt diff --git a/core/src/main/java/me/uport/sdk/core/ITimeProvider.kt b/core/src/main/java/me/uport/sdk/core/ITimeProvider.kt new file mode 100644 index 00000000..58691a9d --- /dev/null +++ b/core/src/main/java/me/uport/sdk/core/ITimeProvider.kt @@ -0,0 +1,22 @@ +package me.uport.sdk.core + +/** + * An interface for getting "current" timestamp. + * + * The default implementation is [SystemTimeProvider] but other implementations may be used during testing and for "was valid at" scenarios. + */ +interface ITimeProvider { + /** + * Returns the current timestamp in milliseconds + * @return the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC. + */ + fun now() : Long +} + + +/** + * Default time provider + */ +class SystemTimeProvider : ITimeProvider { + override fun now() = System.currentTimeMillis() +} \ No newline at end of file diff --git a/core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt b/core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt new file mode 100644 index 00000000..3e542d55 --- /dev/null +++ b/core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt @@ -0,0 +1,15 @@ +package me.uport.sdk.core + +import org.junit.Assert.assertTrue +import org.junit.Test + +class TimeProviderTest { + + @Test + fun `default provider is close to current time`() { + val systemTime = System.currentTimeMillis() + val defaultProvider = SystemTimeProvider() + //some systems have tens of milliseconds as the lowest granularity + assertTrue(defaultProvider.now() - systemTime < 100L) + } +} \ No newline at end of file From 0de91765bb793f8aade5a980911b0463879f9a56 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Mon, 8 Oct 2018 16:38:34 +0300 Subject: [PATCH 13/75] add some defaults for JWT header values --- .../uport/sdk/ethrdid/EthrDIDResolverTest.kt | 12 ++++--- .../identity/MetaIdentityAccountCreator.kt | 6 ++-- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 32 ++++++++----------- .../java/me/uport/sdk/jwt/model/JwtHeader.kt | 4 +-- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt index 13dfb348..4365ac61 100644 --- a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt +++ b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt @@ -56,15 +56,17 @@ class EthrDIDResolverTest { val resolver = EthrDIDResolver(rpc) val lastChanged = 2784036L.toBigInteger() val logs = rpc.getLogs(resolver.registryAddress, listOf(null, realAddress.hexToBytes32()), lastChanged, lastChanged) - println(logs) + assertTrue(logs.isNotEmpty()) + + //topics should be 0x prefixed hex strings val topics: List = logs[0].topics val data: String = logs[0].data val args: DIDOwnerChanged.Arguments = DIDOwnerChanged.decode(topics, data) - println(args) + //no assertion about args but it should not crash val previousBlock = args.previouschange.value - println(previousBlock) + assertTrue(previousBlock > BigInteger.ZERO) } @Test @@ -135,6 +137,8 @@ class EthrDIDResolverTest { val ddo = EthrDIDResolver(rpc).wrapDidDocument("did:ethr:$identity", owner, listOf(event)) println(ddo) + //did not crash + assertTrue(true) } @Test @@ -142,7 +146,7 @@ class EthrDIDResolverTest { val str = "did/pub/Secp256k1/veriKey/hex" val sol = Solidity.Bytes32(str.toByteArray()) -// //this fails. for some reason, it is resolving to Object.toString() instead of ByteArray.toString() +// //this fails. for some reason, the default is resolving to Object.toString() instead of ByteArray.toString() // val decodedStr = sol.bytes.toString() //this should work no matter what diff --git a/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt b/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt index 0d50080f..968866b0 100644 --- a/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt +++ b/identity/src/main/java/me/uport/sdk/identity/MetaIdentityAccountCreator.kt @@ -2,6 +2,8 @@ package me.uport.sdk.identity import android.content.Context import com.uport.sdk.signer.UportHDSigner +import com.uport.sdk.signer.UportHDSigner.Companion.GENERIC_DEVICE_KEY_DERIVATION_PATH +import com.uport.sdk.signer.UportHDSigner.Companion.GENERIC_RECOVERY_DERIVATION_PATH import com.uport.sdk.signer.encryption.KeyProtection import me.uport.sdk.core.Networks import me.uport.sdk.identity.ProgressPersistence.AccountCreationState @@ -62,7 +64,7 @@ class MetaIdentityAccountCreator( } AccountCreationState.ROOT_KEY_CREATED -> { - signer.computeAddressForPath(context, oldBundle.rootAddress, Account.GENERIC_DEVICE_KEY_DERIVATION_PATH, "") { err, deviceAddress, _ -> + signer.computeAddressForPath(context, oldBundle.rootAddress, GENERIC_DEVICE_KEY_DERIVATION_PATH, "") { err, deviceAddress, _ -> if (err != null) { return@computeAddressForPath fail(err, callback) } @@ -73,7 +75,7 @@ class MetaIdentityAccountCreator( } AccountCreationState.DEVICE_KEY_CREATED -> { - signer.computeAddressForPath(context, oldBundle.rootAddress, Account.GENERIC_RECOVERY_DERIVATION_PATH, "") { err, recoveryAddress, _ -> + signer.computeAddressForPath(context, oldBundle.rootAddress, GENERIC_RECOVERY_DERIVATION_PATH, "") { err, recoveryAddress, _ -> if (err != null) { return@computeAddressForPath fail(err, callback) } diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index 6e13c10f..2506026d 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -2,6 +2,7 @@ package me.uport.sdk.jwt //import org.kethereum.crypto.signedMessageToKey import android.content.Context +import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.uport.sdk.signer.UportHDSigner @@ -41,17 +42,8 @@ class JWTTools { private val notEmpty: (String) -> Boolean = { !it.isEmpty() } fun create(context: Context, payload: JwtPayload, rootHandle: String, derivationPath: String, prompt: String = "", recoverable: Boolean = false, callback: (err: Exception?, encodedJWT: String?) -> Unit) { - //JSON Parser - val moshi = Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - - //Create adapters with each object - val jwtHeaderAdapter = moshi.adapter(JwtHeader::class.java) - val jwtPayloadAdapter = moshi.adapter(JwtPayload::class.java) - //create header and convert the parts to json strings - val header = if (recoverable) { + val header = if (!recoverable) { JwtHeader("JWT", "ES256K") } else { JwtHeader("JWT", "ES256K-R") @@ -62,7 +54,7 @@ class JWTTools { val headerEncodedString = headerJsonString.toBase64UrlSafe() val payloadEncodedString = payloadJsonString.toBase64UrlSafe() - //XXX: This is the crux of the bad behavior. signJwtBundle expects a Base64 string as payload and it was receiving plain text + //FIXME: UportHDSigner should not be expecting base64 payloads val messageToSign = "$headerEncodedString.$payloadEncodedString".toBase64() UportHDSigner().signJwtBundle(context, rootHandle, derivationPath, messageToSign, prompt) { err, signature -> @@ -88,14 +80,6 @@ class JWTTools { val headerString = String(encodedHeader.decodeBase64()) val payloadString = String(encodedPayload.decodeBase64()) val signatureBytes = encodedSignature.decodeBase64() - //JSON Parser - val moshi = Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - - //Create adapters with each object - val jwtHeaderAdapter = moshi.adapter(JwtHeader::class.java) - val jwtPayloadAdapter = moshi.adapter(JwtPayload::class.java) //Parse Json if (headerString[0] != '{' || payloadString[0] != '{') @@ -276,6 +260,16 @@ class JWTTools { ?: throw SignatureException("Could not recover public key from signature") } + companion object { + private val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + + //Create adapters with each object + val jwtHeaderAdapter: JsonAdapter = moshi.adapter(JwtHeader::class.java) + val jwtPayloadAdapter: JsonAdapter = moshi.adapter(JwtPayload::class.java) + } + } private data class ECDSASignature internal constructor(val r: BigInteger, val s: BigInteger) \ No newline at end of file diff --git a/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt b/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt index 694a975f..5ed253ac 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt @@ -8,7 +8,7 @@ class JwtHeader( /** * Standard JWT headeer */ - val typ: String, + val typ: String = "JWT", - val alg: String + val alg: String = "ES256K-R" ) \ No newline at end of file From 08921a3321f528aa15336c936fd21ccebfbd95a1 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Mon, 8 Oct 2018 17:45:19 +0300 Subject: [PATCH 14/75] fix typo in test --- identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt b/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt index e9429eac..4a15b091 100644 --- a/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt +++ b/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt @@ -37,7 +37,7 @@ class AccountsTests { "manager":"0xidentityManagerAddress", "txRelay":"0xtxRelayAddress", "fuelToken":"base64FuelToken", - "type":"KeyPair" + "signerType":"KeyPair" }""".trimIndent() val account = Account.fromJson(serializedAccount) @@ -51,4 +51,4 @@ class AccountsTests { assertFalse(account.isDefault!!) } -} \ No newline at end of file +} From 214bc50c141f0e42e033109d14b793e47aed051e Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 12:33:05 +0300 Subject: [PATCH 15/75] bump dev tools versions --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 549cfcb4..c32e0e67 100644 --- a/build.gradle +++ b/build.gradle @@ -8,12 +8,12 @@ buildscript { kotlin_serialization_version = '0.6.1' coroutines_version = "0.26.1" - android_tools_version = '3.3.0-alpha12' + android_tools_version = '3.3.0-alpha13' - build_tools_version = "28.0.2" + build_tools_version = "28.0.3" min_sdk_version = 19 - compile_sdk_version = 27 + compile_sdk_version = 28 target_sdk_version = compile_sdk_version test_runner_version = "1.0.2" From e8d4ccabcf95d10ed69093ee51c805fc0ad1e2af Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 12:33:52 +0300 Subject: [PATCH 16/75] adapt CI cache keys to also consider gradle version when choosing the cache bundle to restore --- .circleci/config.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 193f90bb..75094b71 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,12 +7,11 @@ jobs: build: docker: # specify the version you desire here - - image: circleci/android:api-27-alpha + - image: circleci/android:api-28-alpha # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/postgres:9.4 working_directory: ~/repo @@ -27,17 +26,17 @@ jobs: # Download and cache dependencies - restore_cache: keys: - - v2-dependencies-{{ checksum "build.gradle" }} + - v3-dependencies-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "build.gradle" }} # fallback to using the latest cache if no exact match is found - - v2-dependencies- + - v3-dependencies-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} - run: ./gradlew dependencies androidDependencies --no-daemon - save_cache: + key: v3-dependencies-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "build.gradle" }} paths: - ~/.gradle - ./gradle - key: v2-dependencies-{{ checksum "build.gradle" }} # run tests! - run: ./gradlew test --no-parallel --no-daemon From 89b66b7d0e8eec4792bbbfd1954db77f6bb4bf7a Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 14:07:05 +0300 Subject: [PATCH 17/75] add a serialization module that should take care of JSON conversions --- jwt/build.gradle | 2 +- serialization/build.gradle | 43 +++++++++++++++++++ serialization/src/main/AndroidManifest.xml | 1 + .../sdk/serialization/MoshiExtensions.kt | 29 +++++++++++++ .../uport/sdk/serialization/MoshiProvider.kt | 13 ++++++ settings.gradle | 1 + 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 serialization/build.gradle create mode 100644 serialization/src/main/AndroidManifest.xml create mode 100644 serialization/src/main/java/me/uport/sdk/serialization/MoshiExtensions.kt create mode 100644 serialization/src/main/java/me/uport/sdk/serialization/MoshiProvider.kt diff --git a/jwt/build.gradle b/jwt/build.gradle index b9de88dd..64470a00 100644 --- a/jwt/build.gradle +++ b/jwt/build.gradle @@ -29,7 +29,6 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "com.android.support:support-annotations:$support_lib_version" - implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" api "com.github.walleth.kethereum:model:$kethereum_version" api "com.github.walleth.kethereum:crypto:$kethereum_version" @@ -40,6 +39,7 @@ dependencies { api project(":ethr-did") api project(":jsonrpc") api project(":core") + api project(":serialization") testImplementation "junit:junit:$junit_version" testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" diff --git a/serialization/build.gradle b/serialization/build.gradle new file mode 100644 index 00000000..053a8d7e --- /dev/null +++ b/serialization/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'com.android.library' +apply plugin: "kotlin-android" +apply plugin: "maven" + +android { + compileSdkVersion compile_sdk_version + + defaultConfig { + minSdkVersion min_sdk_version + targetSdkVersion target_sdk_version + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + api "com.squareup.moshi:moshi-kotlin:$moshi_version" + api "com.squareup.moshi:moshi-adapters:$moshi_version" + + testImplementation "junit:junit:$junit_version" +} + +sourceCompatibility = "1.7" +targetCompatibility = "1.7" + +kotlin { + experimental { + coroutines "enable" + } +} diff --git a/serialization/src/main/AndroidManifest.xml b/serialization/src/main/AndroidManifest.xml new file mode 100644 index 00000000..890aa64c --- /dev/null +++ b/serialization/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/serialization/src/main/java/me/uport/sdk/serialization/MoshiExtensions.kt b/serialization/src/main/java/me/uport/sdk/serialization/MoshiExtensions.kt new file mode 100644 index 00000000..d3b15be8 --- /dev/null +++ b/serialization/src/main/java/me/uport/sdk/serialization/MoshiExtensions.kt @@ -0,0 +1,29 @@ +package me.uport.sdk.serialization + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import java.lang.reflect.Type + +val moshi : Moshi + get() = MoshiProvider.default + +inline fun Moshi.listAdapter(elementType: Type = E::class.java): JsonAdapter> { + return adapter(listType(elementType)) +} + +inline fun Moshi.mapAdapter( + keyType: Type = K::class.java, + valueType: Type = V::class.java): JsonAdapter> { + return adapter(mapType(keyType, valueType)) +} + +inline fun listType(elementType: Type = E::class.java): Type { + return Types.newParameterizedType(List::class.java, elementType) +} + +inline fun mapType( + keyType: Type = K::class.java, + valueType: Type = V::class.java): Type { + return Types.newParameterizedType(Map::class.java, keyType, valueType) +} \ No newline at end of file diff --git a/serialization/src/main/java/me/uport/sdk/serialization/MoshiProvider.kt b/serialization/src/main/java/me/uport/sdk/serialization/MoshiProvider.kt new file mode 100644 index 00000000..66387e10 --- /dev/null +++ b/serialization/src/main/java/me/uport/sdk/serialization/MoshiProvider.kt @@ -0,0 +1,13 @@ +package me.uport.sdk.serialization + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory + +class MoshiProvider { + + companion object { + val default: Moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7e5d0059..d50d9865 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,5 +6,6 @@ include ':identity' include ':sdk' include ':jsonrpc' include ':core' +include ':serialization' include ':jwt' include ':fuelingservice' From fab991b3377bc67983a03f7af36bb7dfc43c8462 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 14:56:24 +0300 Subject: [PATCH 18/75] add JSON conversion to JwtHeader class --- jwt/build.gradle | 1 + .../java/me/uport/sdk/jwt/model/JwtHeader.kt | 25 +++++++++++++---- .../uport/sdk/jwt/model/JWTAlgorithmTest.kt | 28 +++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 jwt/src/test/java/me/uport/sdk/jwt/model/JWTAlgorithmTest.kt diff --git a/jwt/build.gradle b/jwt/build.gradle index 64470a00..a7965c7e 100644 --- a/jwt/build.gradle +++ b/jwt/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'maven' android { compileSdkVersion compile_sdk_version diff --git a/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt b/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt index 5ed253ac..ec88bc90 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/model/JwtHeader.kt @@ -1,14 +1,27 @@ package me.uport.sdk.jwt.model import android.support.annotation.Keep -import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import me.uport.sdk.serialization.moshi +/** + * Standard JWT header + */ @Keep class JwtHeader( - /** - * Standard JWT headeer - */ val typ: String = "JWT", - val alg: String = "ES256K-R" -) \ No newline at end of file + val alg: String = ES256K +) { + + fun toJson(): String = jsonAdapter.toJson(this) + + companion object { + const val ES256K = "ES256K" + const val ES256K_R = "ES256K-R" + + fun fromJson(headerString: String): JwtHeader? = jsonAdapter.lenient().fromJson(headerString) + + private val jsonAdapter: JsonAdapter by lazy { moshi.adapter(JwtHeader::class.java) } + } +} \ No newline at end of file diff --git a/jwt/src/test/java/me/uport/sdk/jwt/model/JWTAlgorithmTest.kt b/jwt/src/test/java/me/uport/sdk/jwt/model/JWTAlgorithmTest.kt new file mode 100644 index 00000000..6c3b1745 --- /dev/null +++ b/jwt/src/test/java/me/uport/sdk/jwt/model/JWTAlgorithmTest.kt @@ -0,0 +1,28 @@ +package me.uport.sdk.jwt.model + +import org.junit.Test + +import org.junit.Assert.* + +class JWTAlgorithmTest { + + @Test + fun `can encode to json`() { + val result = JwtHeader("hello", "world").toJson() + //language=JSON + assertEquals("""{"typ":"hello","alg":"world"}""", result) + } + + @Test + fun `can decode json`() { + //language=JSON + val result = JwtHeader.fromJson("""{"typ":"hello","alg":"world"}""") + assertNotNull(result) + } + + @Test + fun `can decode lenient json`() { + val result = JwtHeader.fromJson("""{typ:"hello",alg:"world"}""") + assertNotNull(result) + } +} \ No newline at end of file From e6c0c167c4f31d7d153b3195b406220205de2201 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 15:43:12 +0300 Subject: [PATCH 19/75] add a JWTSignerAlgorithm class that abstracts encoding of signature data in a recoverable/non-recoverable way --- jwt/build.gradle | 12 +++- .../sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt | 67 +++++++++++++++++++ .../java/me/uport/sdk/jwt/JWTExceptions.kt | 5 +- .../me/uport/sdk/jwt/JWTSignerAlgorithm.kt | 24 +++++++ .../uport/sdk/jwt/JWTSignerAlgorithmTest.kt | 38 +++++++++++ .../com/uport/sdk/signer/UportHDSignerImpl.kt | 12 ++-- 6 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt create mode 100644 jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt create mode 100644 jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt diff --git a/jwt/build.gradle b/jwt/build.gradle index a7965c7e..9663f788 100644 --- a/jwt/build.gradle +++ b/jwt/build.gradle @@ -29,6 +29,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "com.android.support:support-annotations:$support_lib_version" api "com.github.walleth.kethereum:model:$kethereum_version" @@ -54,6 +55,11 @@ dependencies { androidTestImplementation "com.android.support:appcompat-v7:$support_lib_version" } -repositories { - mavenCentral() -} +sourceCompatibility = "1.7" +targetCompatibility = "1.7" + +kotlin { + experimental { + coroutines "enable" + } +} \ No newline at end of file diff --git a/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt new file mode 100644 index 00000000..41485898 --- /dev/null +++ b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt @@ -0,0 +1,67 @@ +package me.uport.sdk.jwt + +import android.support.test.InstrumentationRegistry +import com.uport.sdk.signer.UportHDSigner +import com.uport.sdk.signer.UportHDSignerImpl +import com.uport.sdk.signer.encryption.KeyProtection +import com.uport.sdk.signer.importHDSeed +import kotlinx.coroutines.experimental.runBlocking +import me.uport.sdk.core.decodeBase64 +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K_R +import org.junit.Assert.assertEquals +import org.junit.Test + +class JWTSignerAlgorithmRuntimeTest { + + @Test + fun can_sign_using_non_recoverable_alg() = runBlocking { + + val targetContext = InstrumentationRegistry.getTargetContext() + + val referenceSeed = "vessel ladder alter error federal sibling chat ability sun glass valve picture" + val referencePayload = "Hello, world!" + + val baseSigner = UportHDSigner() + val (rootAddress, _) = baseSigner.importHDSeed(targetContext, KeyProtection.Level.SIMPLE, referenceSeed) + + val testedSigner = UportHDSignerImpl( + context = targetContext, + uportHDSigner = baseSigner, + rootAddress = rootAddress, + deviceAddress = rootAddress + ) + + val signature = JWTSignerAlgorithm(ES256K).sign(referencePayload, testedSigner) + + val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XQ" + assertEquals(expectedSignature, signature) + assertEquals(64, signature.decodeBase64().size) + } + + + @Test + fun can_sign_using_recoverable_alg() = runBlocking { + + val targetContext = InstrumentationRegistry.getTargetContext() + + val referenceSeed = "vessel ladder alter error federal sibling chat ability sun glass valve picture" + val referencePayload = "Hello, world!" + + val baseSigner = UportHDSigner() + val (rootAddress, _) = baseSigner.importHDSeed(targetContext, KeyProtection.Level.SIMPLE, referenceSeed) + + val testedSigner = UportHDSignerImpl( + context = targetContext, + uportHDSigner = baseSigner, + rootAddress = rootAddress, + deviceAddress = rootAddress + ) + + val signature = JWTSignerAlgorithm(ES256K_R).sign(referencePayload, testedSigner) + + val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XRw" + assertEquals(expectedSignature, signature) + assertEquals(65, signature.decodeBase64().size) + } +} \ No newline at end of file diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTExceptions.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTExceptions.kt index 5be199e4..7a1c6f39 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTExceptions.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTExceptions.kt @@ -2,5 +2,6 @@ package me.uport.sdk.jwt import java.security.SignatureException -class InvalidJWTException(message: String): IllegalStateException(message) -class InvalidSignatureException(message: String): SignatureException(message) +class JWTEncodingException(message: String) : IllegalArgumentException(message) +class InvalidJWTException(message: String) : IllegalStateException(message) +class InvalidSignatureException(message: String) : SignatureException(message) diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt new file mode 100644 index 00000000..02f7b1be --- /dev/null +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt @@ -0,0 +1,24 @@ +package me.uport.sdk.jwt + +import com.uport.sdk.signer.Signer +import com.uport.sdk.signer.getJoseEncoded +import com.uport.sdk.signer.signJWT +import me.uport.sdk.core.utf8 +import me.uport.sdk.jwt.model.JwtHeader + +class JWTSignerAlgorithm(private val jwtHeader: JwtHeader) { + + @Suppress("unused") + constructor(alg:String) : this(JwtHeader(alg = alg)) + + suspend fun sign(payload: String, signer: Signer): String { + + val signatureData = signer.signJWT(payload.toByteArray(utf8)) + + return when (jwtHeader.alg) { + JwtHeader.ES256K -> signatureData.getJoseEncoded(false) + JwtHeader.ES256K_R -> signatureData.getJoseEncoded(true) + else -> throw JWTEncodingException("Unknown algorithm ${jwtHeader.alg} requested for signing") + } + } +} \ No newline at end of file diff --git a/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt b/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt new file mode 100644 index 00000000..5c832e7a --- /dev/null +++ b/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt @@ -0,0 +1,38 @@ +package me.uport.sdk.jwt + +import com.uport.sdk.signer.KPSigner +import kotlinx.coroutines.experimental.runBlocking +import me.uport.sdk.core.decodeBase64 +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K_R +import org.junit.Assert.assertEquals +import org.junit.Test + +class JWTSignerAlgorithmTest { + + @Test + fun `can sign using non recoverable key algorithm`() = runBlocking { + + val signer = KPSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + + val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XQ" + val signature = JWTSignerAlgorithm(ES256K).sign("Hello, world!", signer) + + assertEquals(expectedSignature, signature) + assertEquals(64, signature.decodeBase64().size) + + } + + @Test + fun `can sign using recoverable key algorithm`() = runBlocking { + + val signer = KPSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") + + val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XRw" + val signature = JWTSignerAlgorithm(ES256K_R).sign("Hello, world!", signer) + + assertEquals(expectedSignature, signature) + assertEquals(65, signature.decodeBase64().size) + + } +} \ No newline at end of file diff --git a/signer/src/main/java/com/uport/sdk/signer/UportHDSignerImpl.kt b/signer/src/main/java/com/uport/sdk/signer/UportHDSignerImpl.kt index e1098fa4..986a8b0f 100644 --- a/signer/src/main/java/com/uport/sdk/signer/UportHDSignerImpl.kt +++ b/signer/src/main/java/com/uport/sdk/signer/UportHDSignerImpl.kt @@ -1,8 +1,8 @@ package com.uport.sdk.signer import android.content.Context -import android.util.Base64 -import com.uport.sdk.signer.UportHDSigner.Companion.GENERIC_DEVICE_KEY_DERIVATION_PATH +import com.uport.sdk.signer.UportHDSigner.Companion.UPORT_ROOT_DERIVATION_PATH +import me.uport.sdk.core.toBase64 import org.kethereum.model.SignatureData /** @@ -25,8 +25,8 @@ class UportHDSignerImpl( return uportHDSigner.signTransaction( context, //FIXME: Not cool hiding the context like this... may lead to leaks rootAddress, - GENERIC_DEVICE_KEY_DERIVATION_PATH, - Base64.encodeToString(rawMessage, Base64.DEFAULT), + UPORT_ROOT_DERIVATION_PATH, //FIXME: path should be configurable + rawMessage.toBase64(), "", callback) } @@ -36,8 +36,8 @@ class UportHDSignerImpl( return uportHDSigner.signJwtBundle( context, //FIXME: Not cool hiding the context like this... may lead to leaks rootAddress, - GENERIC_DEVICE_KEY_DERIVATION_PATH, - Base64.encodeToString(rawPayload, Base64.DEFAULT), + UPORT_ROOT_DERIVATION_PATH, //FIXME: path should be configurable + rawPayload.toBase64(), "", callback) } From a084c67b15f88e789f1b491fb4a9f1c3176872d1 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 16:45:47 +0300 Subject: [PATCH 20/75] add a method to createJWT from a payload Map and an abstracted signer. this fixes #160664026 --- build.gradle | 2 +- .../java/me/uport/sdk/jwt/JWTToolsTests.kt | 48 ++++++++++++--- .../me/uport/sdk/jwt/JWTSignerAlgorithm.kt | 10 ++- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 61 +++++++++++++------ .../{JWTToolsTest.kt => JWTToolsJVMTest.kt} | 21 ++++++- 5 files changed, 107 insertions(+), 35 deletions(-) rename jwt/src/test/java/{JWTToolsTest.kt => JWTToolsJVMTest.kt} (85%) diff --git a/build.gradle b/build.gradle index c32e0e67..e8bd8e3e 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ subprojects { subproject -> } - if (subproject.plugins.hasPlugin("com.android.application") || subproject.plugins.hasPlugin("com.android.library") ) { + if (subproject.plugins.hasPlugin("com.android.application") || subproject.plugins.hasPlugin("com.android.library")) { subproject.android.packagingOptions.exclude("META-INF/main.kotlin_module") } } diff --git a/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt index 5854fe2f..fd0d3b26 100644 --- a/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt +++ b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt @@ -6,14 +6,16 @@ package me.uport.sdk.jwt import android.support.test.rule.ActivityTestRule import android.util.Log import com.uport.sdk.signer.UportHDSigner +import com.uport.sdk.signer.UportHDSignerImpl import com.uport.sdk.signer.encryption.KeyProtection +import com.uport.sdk.signer.importHDSeed +import kotlinx.coroutines.experimental.runBlocking +import me.uport.sdk.core.ITimeProvider import me.uport.sdk.jwt.model.JwtPayload -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull +import org.junit.Assert.* import org.junit.Rule import org.junit.Test import java.util.concurrent.CountDownLatch -import kotlin.test.assertNotNull class JWTToolsTests { @@ -98,15 +100,41 @@ class JWTToolsTests { latch.await() } - private fun ensureSeedIsImported(phrase: String) { + @Test + fun create_token_from_payload() = runBlocking { + val timeProvider = TestTimeProvider(12345678000L) + val tested = JWTTools(timeProvider) + + val payload = mapOf( + "claims" to mapOf("name" to "R Daneel Olivaw") + ) + val baseSigner = UportHDSigner() + val (rootHandle, _) = baseSigner.importHDSeed(mActivityRule.activity, KeyProtection.Level.SIMPLE, "notice suffer eagle style exclude burst write mechanic junior crater crystal seek") + val signer = UportHDSignerImpl( + mActivityRule.activity, + baseSigner, + rootHandle, + rootHandle + ) + val issuerDID = "did:ethr:${signer.getAddress()}" + + val jwt = tested.createJWT(payload, issuerDID, signer) + val expected = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJjbGFpbXMiOnsibmFtZSI6IlIgRGFuZWVsIE9saXZhdyJ9LCJpYXQiOjEyMzQ1Njc4LCJleHAiOjEyMzQ1OTc4LCJpc3MiOiJkaWQ6ZXRocjoweDQxMjNjYmQxNDNiNTVjMDZlNDUxZmYyNTNhZjA5Mjg2YjY4N2E5NTAifQ.o6eDKYjHJnak1ylkpe9g8krxvK9UEhKf-1T0EYhH8pGyb8MjOEepRJi8DYlVEnZno0DkVYXQCf3u1i_HThBKtBs" + assertEquals(expected, jwt) + val tt = tested.decode(expected) + assertEquals(12345678L, tt.second.iat) + } + + private fun ensureSeedIsImported(phrase: String) = runBlocking { //ensure seed is imported - val latch = CountDownLatch(1) - UportHDSigner().importHDSeed(mActivityRule.activity, KeyProtection.Level.SIMPLE, phrase) { err, _, _ -> - assertNull(err) - latch.countDown() - } - latch.await() + UportHDSigner().importHDSeed(mActivityRule.activity, KeyProtection.Level.SIMPLE, phrase) } + +} + +class TestTimeProvider(val currentTime: Long) : ITimeProvider { + override fun now(): Long = currentTime + } diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt index 02f7b1be..85d7a3ac 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTSignerAlgorithm.kt @@ -6,10 +6,14 @@ import com.uport.sdk.signer.signJWT import me.uport.sdk.core.utf8 import me.uport.sdk.jwt.model.JwtHeader +/** + * Abstracts the signature to a recoverable/non-recoverable JOSE encoding based on the jwt header or algorithm provided during construction + * + * It supports "ES256K" and "ES256K-R" signing methods + */ class JWTSignerAlgorithm(private val jwtHeader: JwtHeader) { - @Suppress("unused") - constructor(alg:String) : this(JwtHeader(alg = alg)) + constructor(algorithm: String) : this(JwtHeader(alg = algorithm)) suspend fun sign(payload: String, signer: Signer): String { @@ -18,7 +22,7 @@ class JWTSignerAlgorithm(private val jwtHeader: JwtHeader) { return when (jwtHeader.alg) { JwtHeader.ES256K -> signatureData.getJoseEncoded(false) JwtHeader.ES256K_R -> signatureData.getJoseEncoded(true) - else -> throw JWTEncodingException("Unknown algorithm ${jwtHeader.alg} requested for signing") + else -> throw JWTEncodingException("Unknown algorithm (${jwtHeader.alg}) requested for signing") } } } \ No newline at end of file diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index 2506026d..94421f7c 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -1,22 +1,21 @@ package me.uport.sdk.jwt -//import org.kethereum.crypto.signedMessageToKey import android.content.Context import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.uport.sdk.signer.Signer import com.uport.sdk.signer.UportHDSigner import com.uport.sdk.signer.decodeJose import com.uport.sdk.signer.getJoseEncoded -import me.uport.sdk.core.Networks -import me.uport.sdk.core.decodeBase64 -import me.uport.sdk.core.toBase64 -import me.uport.sdk.core.toBase64UrlSafe +import me.uport.sdk.core.* import me.uport.sdk.did.DIDResolver import me.uport.sdk.ethrdid.EthrDIDResolver import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jwt.model.JwtHeader +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K_R import me.uport.sdk.jwt.model.JwtPayload +import me.uport.sdk.serialization.mapAdapter +import me.uport.sdk.serialization.moshi import org.kethereum.crypto.CURVE import org.kethereum.crypto.getAddress import org.kethereum.encodings.decodeBase58 @@ -38,17 +37,46 @@ import kotlin.experimental.and */ -class JWTTools { +class JWTTools( + private val timeProvider: ITimeProvider = SystemTimeProvider() +) { private val notEmpty: (String) -> Boolean = { !it.isEmpty() } + suspend fun createJWT(payload: Map, issuerDID: String, signer: Signer, expiresInSeconds: Long = 300, algorithm: String = ES256K_R): String { + val mapAdapter = moshi.mapAdapter(String::class.java, Any::class.java) + + val mutablePayload = payload.toMutableMap() + + val header = JwtHeader(alg = algorithm) + val headerJson = header.toJson() + + val iatSeconds = Math.floor(timeProvider.now() / 1000.0).toLong() + val expSeconds = iatSeconds + expiresInSeconds + + mutablePayload["iat"] = iatSeconds + mutablePayload["exp"] = expSeconds + mutablePayload["iss"] = issuerDID + + val payloadJson = mapAdapter.toJson(mutablePayload) + + @Suppress("SimplifiableCallChain", "ConvertCallChainIntoSequence") + val signingInput = listOf(headerJson, payloadJson) + .map { it.toBase64UrlSafe() } + .joinToString(".") + + val jwtSigner = JWTSignerAlgorithm(header) + val signature: String = jwtSigner.sign(signingInput, signer) + return listOf(signingInput, signature).joinToString(".") + } + fun create(context: Context, payload: JwtPayload, rootHandle: String, derivationPath: String, prompt: String = "", recoverable: Boolean = false, callback: (err: Exception?, encodedJWT: String?) -> Unit) { //create header and convert the parts to json strings val header = if (!recoverable) { - JwtHeader("JWT", "ES256K") + JwtHeader(alg = ES256K) } else { - JwtHeader("JWT", "ES256K-R") + JwtHeader(alg = ES256K_R) } - val headerJsonString = jwtHeaderAdapter.toJson(header) + val headerJsonString = header.toJson() val payloadJsonString = jwtPayloadAdapter.toJson(payload) //base 64 encode the jwt parts val headerEncodedString = headerJsonString.toBase64UrlSafe() @@ -85,7 +113,7 @@ class JWTTools { if (headerString[0] != '{' || payloadString[0] != '{') throw InvalidJWTException("Invalid JSON format, should start with {") else { - val header = jwtHeaderAdapter.fromJson(headerString) // JSONObject(headerString) + val header = JwtHeader.fromJson(headerString) // JSONObject(headerString) val payload = jwtPayloadAdapter.fromJson(payloadString) //JSONObject(payloadString return Triple(header!!, payload!!, signatureBytes) } @@ -102,7 +130,7 @@ class JWTTools { if (err !== null) return@resolve callback(err, null) - val sigData= signatureBytes.decodeJose() + val sigData = signatureBytes.decodeJose() val signingInputBytes = token.substringBeforeLast('.').toByteArray() @@ -261,13 +289,8 @@ class JWTTools { } companion object { - private val moshi = Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - //Create adapters with each object - val jwtHeaderAdapter: JsonAdapter = moshi.adapter(JwtHeader::class.java) - val jwtPayloadAdapter: JsonAdapter = moshi.adapter(JwtPayload::class.java) + val jwtPayloadAdapter: JsonAdapter by lazy { moshi.adapter(JwtPayload::class.java) } } } diff --git a/jwt/src/test/java/JWTToolsTest.kt b/jwt/src/test/java/JWTToolsJVMTest.kt similarity index 85% rename from jwt/src/test/java/JWTToolsTest.kt rename to jwt/src/test/java/JWTToolsJVMTest.kt index 5ffba7fe..7619552b 100644 --- a/jwt/src/test/java/JWTToolsTest.kt +++ b/jwt/src/test/java/JWTToolsJVMTest.kt @@ -1,12 +1,14 @@ package me.uport.sdk.jwt +import com.uport.sdk.signer.KPSigner +import kotlinx.coroutines.experimental.runBlocking import me.uport.sdk.core.stubUiContext import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import java.util.concurrent.CountDownLatch -class JWTToolsTest { +class JWTToolsJVMTest { @Before fun `run before every test`() { @@ -32,4 +34,19 @@ class JWTToolsTest { latch.await() } -} \ No newline at end of file + + @Suppress("UNUSED_VARIABLE") + @Test(expected = JWTEncodingException::class) + fun throws_when_algorithm_is_wrong() = runBlocking { + val tested = JWTTools() + + val payload = emptyMap() + val signer = KPSigner("0x1234") + val issuerDID = "did:ethr:${signer.getAddress()}" + + //should throw a JWTEncodingException + val unused = tested.createJWT(payload, issuerDID, signer, algorithm = "some fancy but unknown algorithm") + + } +} + From b03c12ecb75e1d374bec3c90e97068c8de7134cc Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 17:14:23 +0300 Subject: [PATCH 21/75] add comments to the JWT creation method --- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index 94421f7c..ffba6512 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -33,22 +33,27 @@ import java.security.SignatureException import kotlin.experimental.and /** - * Tools for Verifying, Creating, and Decoding uport JWT's + * Tools for Verifying, Creating, and Decoding uport JWTs + * + * the [timeProvider] defaults to [SystemTimeProvider] but you can configure it for testing or for "was valid at" scenarios */ - - class JWTTools( private val timeProvider: ITimeProvider = SystemTimeProvider() ) { private val notEmpty: (String) -> Boolean = { !it.isEmpty() } + /** + * This coroutine method creates a signed JWT from a [payload] Map and an abstracted [Signer] + * You're also supposed to pass the [issuerDID] and can configure the algorithm used and expiry time + * + * The issuerDID is NOT checked for format, nor for a match with the signer. + */ suspend fun createJWT(payload: Map, issuerDID: String, signer: Signer, expiresInSeconds: Long = 300, algorithm: String = ES256K_R): String { val mapAdapter = moshi.mapAdapter(String::class.java, Any::class.java) val mutablePayload = payload.toMutableMap() val header = JwtHeader(alg = algorithm) - val headerJson = header.toJson() val iatSeconds = Math.floor(timeProvider.now() / 1000.0).toLong() val expSeconds = iatSeconds + expiresInSeconds @@ -57,10 +62,8 @@ class JWTTools( mutablePayload["exp"] = expSeconds mutablePayload["iss"] = issuerDID - val payloadJson = mapAdapter.toJson(mutablePayload) - @Suppress("SimplifiableCallChain", "ConvertCallChainIntoSequence") - val signingInput = listOf(headerJson, payloadJson) + val signingInput = listOf(header.toJson(), mapAdapter.toJson(mutablePayload)) .map { it.toBase64UrlSafe() } .joinToString(".") @@ -166,7 +169,7 @@ class JWTTools( val signingInputBytes = signingInput.toByteArray() val recoveryBytes = if (signatureBytes.size > 64) - signatureBytes.sliceArray(64..64) // just the recovery byte + signatureBytes.sliceArray(64..64) // an array of just the recovery byte else byteArrayOf(27, 28) //try all recovery options From 62a5e888ebf38a208d0bf9bd40b973635b3844e8 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 9 Oct 2018 19:09:04 +0300 Subject: [PATCH 22/75] link to pivotal tracker --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6db74eee..74d1ac95 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Identity for your Android dApps. Many intended features are still missing, and the ones already present are under heavy development. Expect breaking changes!** +Issues are being tracket at https://www.pivotaltracker.com/n/projects/2198688 + ### Installation This SDK is available through [jitpack](https://jitpack.io/) From 22d7ab346953e39f0372f2c2e7f96fe5f4c8aafa Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 10 Oct 2018 18:33:13 +0300 Subject: [PATCH 23/75] rename did module to uport-did --- identity/build.gradle | 2 +- jwt/build.gradle | 2 +- settings.gradle | 2 +- {did => uport-did}/Readme.md | 0 {did => uport-did}/abi/UportRegistry.json | 0 {did => uport-did}/build.gradle | 0 {did => uport-did}/src/main/AndroidManifest.xml | 2 +- {did => uport-did}/src/main/java/me/uport/sdk/did/DDO.kt | 0 .../src/main/java/me/uport/sdk/did/DIDResolver.kt | 0 .../src/test/java/me/uport/sdk/did/DIDResolverTest.kt | 0 10 files changed, 4 insertions(+), 4 deletions(-) rename {did => uport-did}/Readme.md (100%) rename {did => uport-did}/abi/UportRegistry.json (100%) rename {did => uport-did}/build.gradle (100%) rename {did => uport-did}/src/main/AndroidManifest.xml (80%) rename {did => uport-did}/src/main/java/me/uport/sdk/did/DDO.kt (100%) rename {did => uport-did}/src/main/java/me/uport/sdk/did/DIDResolver.kt (100%) rename {did => uport-did}/src/test/java/me/uport/sdk/did/DIDResolverTest.kt (100%) diff --git a/identity/build.gradle b/identity/build.gradle index 4b996076..2b938af6 100644 --- a/identity/build.gradle +++ b/identity/build.gradle @@ -35,7 +35,7 @@ dependencies { implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" api "com.github.walleth.kethereum:model:$kethereum_version" - api project(":did") + api project(':uport-did') api project(":core") api project(":signer") diff --git a/jwt/build.gradle b/jwt/build.gradle index 9663f788..78bcb3a0 100644 --- a/jwt/build.gradle +++ b/jwt/build.gradle @@ -37,7 +37,7 @@ dependencies { api "com.github.walleth.kethereum:extensions:$kethereum_version" api project(":signer") - api project(":did") + api project(':uport-did') api project(":ethr-did") api project(":jsonrpc") api project(":core") diff --git a/settings.gradle b/settings.gradle index d50d9865..754726c3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ include ':demoapp' include ':signer' -include ':did' +include ':uport-did' include ':ethr-did' include ':identity' include ':sdk' diff --git a/did/Readme.md b/uport-did/Readme.md similarity index 100% rename from did/Readme.md rename to uport-did/Readme.md diff --git a/did/abi/UportRegistry.json b/uport-did/abi/UportRegistry.json similarity index 100% rename from did/abi/UportRegistry.json rename to uport-did/abi/UportRegistry.json diff --git a/did/build.gradle b/uport-did/build.gradle similarity index 100% rename from did/build.gradle rename to uport-did/build.gradle diff --git a/did/src/main/AndroidManifest.xml b/uport-did/src/main/AndroidManifest.xml similarity index 80% rename from did/src/main/AndroidManifest.xml rename to uport-did/src/main/AndroidManifest.xml index 9a447b0e..0dcd13da 100644 --- a/did/src/main/AndroidManifest.xml +++ b/uport-did/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="me.uport.sdk.uportdid"> diff --git a/did/src/main/java/me/uport/sdk/did/DDO.kt b/uport-did/src/main/java/me/uport/sdk/did/DDO.kt similarity index 100% rename from did/src/main/java/me/uport/sdk/did/DDO.kt rename to uport-did/src/main/java/me/uport/sdk/did/DDO.kt diff --git a/did/src/main/java/me/uport/sdk/did/DIDResolver.kt b/uport-did/src/main/java/me/uport/sdk/did/DIDResolver.kt similarity index 100% rename from did/src/main/java/me/uport/sdk/did/DIDResolver.kt rename to uport-did/src/main/java/me/uport/sdk/did/DIDResolver.kt diff --git a/did/src/test/java/me/uport/sdk/did/DIDResolverTest.kt b/uport-did/src/test/java/me/uport/sdk/did/DIDResolverTest.kt similarity index 100% rename from did/src/test/java/me/uport/sdk/did/DIDResolverTest.kt rename to uport-did/src/test/java/me/uport/sdk/did/DIDResolverTest.kt From 0d3e300ea4fbc257f2a813877a506d0622b3cc6a Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 10 Oct 2018 18:35:09 +0300 Subject: [PATCH 24/75] rename package did to uportdid --- jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt | 2 +- uport-did/src/main/java/me/uport/sdk/{did => uportdid}/DDO.kt | 2 +- .../src/main/java/me/uport/sdk/{did => uportdid}/DIDResolver.kt | 2 +- .../test/java/me/uport/sdk/{did => uportdid}/DIDResolverTest.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename uport-did/src/main/java/me/uport/sdk/{did => uportdid}/DDO.kt (98%) rename uport-did/src/main/java/me/uport/sdk/{did => uportdid}/DIDResolver.kt (99%) rename uport-did/src/test/java/me/uport/sdk/{did => uportdid}/DIDResolverTest.kt (98%) diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index ffba6512..f25dbd15 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -7,7 +7,7 @@ import com.uport.sdk.signer.UportHDSigner import com.uport.sdk.signer.decodeJose import com.uport.sdk.signer.getJoseEncoded import me.uport.sdk.core.* -import me.uport.sdk.did.DIDResolver +import me.uport.sdk.uportdid.DIDResolver import me.uport.sdk.ethrdid.EthrDIDResolver import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jwt.model.JwtHeader diff --git a/uport-did/src/main/java/me/uport/sdk/did/DDO.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/DDO.kt similarity index 98% rename from uport-did/src/main/java/me/uport/sdk/did/DDO.kt rename to uport-did/src/main/java/me/uport/sdk/uportdid/DDO.kt index e8aec386..d863adcb 100644 --- a/uport-did/src/main/java/me/uport/sdk/did/DDO.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/DDO.kt @@ -1,4 +1,4 @@ -package me.uport.sdk.did +package me.uport.sdk.uportdid import com.squareup.moshi.Json import com.squareup.moshi.Moshi diff --git a/uport-did/src/main/java/me/uport/sdk/did/DIDResolver.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/DIDResolver.kt similarity index 99% rename from uport-did/src/main/java/me/uport/sdk/did/DIDResolver.kt rename to uport-did/src/main/java/me/uport/sdk/uportdid/DIDResolver.kt index e1c46e01..5f847e86 100644 --- a/uport-did/src/main/java/me/uport/sdk/did/DIDResolver.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/DIDResolver.kt @@ -1,4 +1,4 @@ -package me.uport.sdk.did +package me.uport.sdk.uportdid import android.os.Handler import android.os.Looper diff --git a/uport-did/src/test/java/me/uport/sdk/did/DIDResolverTest.kt b/uport-did/src/test/java/me/uport/sdk/uportdid/DIDResolverTest.kt similarity index 98% rename from uport-did/src/test/java/me/uport/sdk/did/DIDResolverTest.kt rename to uport-did/src/test/java/me/uport/sdk/uportdid/DIDResolverTest.kt index f63d6f5f..f2abd075 100644 --- a/uport-did/src/test/java/me/uport/sdk/did/DIDResolverTest.kt +++ b/uport-did/src/test/java/me/uport/sdk/uportdid/DIDResolverTest.kt @@ -1,4 +1,4 @@ -package me.uport.sdk.did +package me.uport.sdk.uportdid import me.uport.mnid.Account import me.uport.sdk.jsonrpc.EthCall From 630c145d8008ee4744f0539f3990aa1c3ad0282f Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 12:24:50 +0300 Subject: [PATCH 25/75] replace moshi with kotlinx serialization in identity module --- build.gradle | 6 +-- ethr-did/build.gradle | 2 +- identity/build.gradle | 4 +- .../java/me/uport/sdk/identity/Account.kt | 34 ++++++------ .../uport/sdk/identity/ProgressPersistence.kt | 27 +++++----- .../me/uport/sdk/identity/endpoints/Unnu.kt | 54 ++++++++----------- .../me/uport/sdk/identity/endpoints/Utils.kt | 10 ---- .../me/uport/sdk/identity/AccountsTests.kt | 2 + .../java/me/uport/sdk/jwt/model/JwtPayload.kt | 4 +- serialization/build.gradle | 2 + 10 files changed, 66 insertions(+), 79 deletions(-) delete mode 100644 identity/src/main/java/me/uport/sdk/identity/endpoints/Utils.kt diff --git a/build.gradle b/build.gradle index e8bd8e3e..853f3daf 100644 --- a/build.gradle +++ b/build.gradle @@ -4,8 +4,8 @@ buildscript { ext { - kotlin_version = '1.2.61' - kotlin_serialization_version = '0.6.1' + kotlin_version = '1.2.71' + kotlin_serialization_version = '0.6.2' coroutines_version = "0.26.1" android_tools_version = '3.3.0-alpha13' @@ -25,7 +25,7 @@ buildscript { mockito_version = "2.19.0" mockito_kotlin_version = "2.0.0-RC1" - moshi_version = "1.6.0" + moshi_version = "1.7.0" okhttp_version = "3.10.0" bivrost_version = "1c0efeb7f3"//"v0.6.2" diff --git a/ethr-did/build.gradle b/ethr-did/build.gradle index 8beb02d0..da772264 100644 --- a/ethr-did/build.gradle +++ b/ethr-did/build.gradle @@ -38,7 +38,6 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$kotlin_serialization_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "com.android.support:support-annotations:$support_lib_version" @@ -51,6 +50,7 @@ dependencies { api project(":jsonrpc") api project(":core") api project(":signer") + api project(":serialization") testImplementation "junit:junit:$junit_version" testImplementation "org.mockito:mockito-inline:$mockito_version" diff --git a/identity/build.gradle b/identity/build.gradle index 2b938af6..1dcd043e 100644 --- a/identity/build.gradle +++ b/identity/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.library' apply plugin: "kotlin-android" +apply plugin: "kotlinx-serialization" apply plugin: "maven" android { @@ -32,12 +33,11 @@ dependencies { implementation "com.android.support:support-annotations:$support_lib_version" - implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" - api "com.github.walleth.kethereum:model:$kethereum_version" api project(':uport-did') api project(":core") api project(":signer") + api project(":serialization") androidTestImplementation "com.android.support.test:runner:$test_runner_version" androidTestImplementation "com.android.support.test:rules:$test_runner_version" diff --git a/identity/src/main/java/me/uport/sdk/identity/Account.kt b/identity/src/main/java/me/uport/sdk/identity/Account.kt index 81650149..cd650d83 100644 --- a/identity/src/main/java/me/uport/sdk/identity/Account.kt +++ b/identity/src/main/java/me/uport/sdk/identity/Account.kt @@ -3,50 +3,56 @@ package me.uport.sdk.identity import android.content.Context import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PACKAGE_PRIVATE -import com.squareup.moshi.Json import com.uport.sdk.signer.Signer import com.uport.sdk.signer.UportHDSigner import com.uport.sdk.signer.UportHDSignerImpl +import kotlinx.serialization.Optional +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.json.JSON import me.uport.mnid.MNID -import me.uport.sdk.identity.endpoints.moshi +@Serializable data class Account( @VisibleForTesting(otherwise = PACKAGE_PRIVATE) - @Json(name = "uportRoot") + @SerialName("uportRoot") val handle: String, - @Json(name = "devKey") + @SerialName("devKey") val deviceAddress: String, - @Json(name = "network") + @SerialName("network") val network: String, - @Json(name = "proxy") + @SerialName("proxy") val publicAddress: String, - @Json(name = "manager") + @SerialName("manager") val identityManagerAddress: String, - @Json(name = "txRelay") + @SerialName("txRelay") val txRelayAddress: String, - @Json(name = "fuelToken") + @SerialName("fuelToken") val fuelToken: String, - @Json(name = "signerType") + @SerialName("signerType") val type: AccountType = AccountType.KeyPair, - @Json(name = "isDefault") + @Optional + @SerialName("isDefault") val isDefault: Boolean? = false ) { + @Transient val address: String get() = getMnid() fun getMnid() = MNID.encode(network, publicAddress) - fun toJson(pretty: Boolean = false): String = adapter.indent(if (pretty) " " else "").toJson(this) + fun toJson(pretty: Boolean = false): String = if (pretty) JSON.indented.stringify(this) else JSON.stringify(this) fun getSigner(context: Context): Signer = UportHDSignerImpl(context, UportHDSigner(), rootAddress = handle, deviceAddress = deviceAddress) @@ -54,14 +60,12 @@ data class Account( val blank = Account("", "", "", "", "", "", "", AccountType.KeyPair) - private val adapter = moshi.adapter(Account::class.java) - fun fromJson(serializedAccount: String?): Account? { if (serializedAccount == null || serializedAccount.isEmpty()) { return null } - return adapter.fromJson(serializedAccount) + return JSON.parse(serializedAccount) } } diff --git a/identity/src/main/java/me/uport/sdk/identity/ProgressPersistence.kt b/identity/src/main/java/me/uport/sdk/identity/ProgressPersistence.kt index 8309b354..6f68b887 100644 --- a/identity/src/main/java/me/uport/sdk/identity/ProgressPersistence.kt +++ b/identity/src/main/java/me/uport/sdk/identity/ProgressPersistence.kt @@ -2,8 +2,9 @@ package me.uport.sdk.identity import android.content.Context import android.content.SharedPreferences -import com.squareup.moshi.Json -import me.uport.sdk.identity.endpoints.moshi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JSON class ProgressPersistence(context: Context) { @@ -30,36 +31,34 @@ class ProgressPersistence(context: Context) { /** * Wrapper for intermediate states of account creation */ + @Serializable internal data class PersistentBundle( - @Json(name = "rootAddress") + @SerialName("rootAddress") val rootAddress: String = "", - @Json(name = "devKey") + @SerialName("devKey") val deviceAddress: String = "", - @Json(name = "recoveryKey") + @SerialName("recoveryKey") val recoveryAddress: String = "", - @Json(name = "fuelToken") + @SerialName("fuelToken") val fuelToken: String = "", - @Json(name = "txHash") + @SerialName("txHash") val txHash: String = "", - @Json(name = "partialAccount") + @SerialName("partialAccount") val partialAccount: Account = Account.blank ) { - fun toJson() = jsonAdapter.toJson(this) ?: "" + fun toJson() = JSON.stringify(this) companion object { fun fromJson(json: String): PersistentBundle = try { - jsonAdapter.fromJson(json) - ?: PersistentBundle() + JSON.nonstrict.parse(json) } catch (err: Exception) { PersistentBundle() } - - private val jsonAdapter = moshi.adapter(PersistentBundle::class.java) } } @@ -76,7 +75,7 @@ class ProgressPersistence(context: Context) { internal fun restore(): Pair { val persistedOrdinal = prefs.getInt(ACCOUNT_CREATION_PROGRESS, AccountCreationState.NONE.ordinal) val state = AccountCreationState.values()[persistedOrdinal] - val serialized = prefs.getString(ACCOUNT_CREATION_DETAIL, "") + val serialized = prefs.getString(ACCOUNT_CREATION_DETAIL, "") ?: "" return (state to PersistentBundle.fromJson(serialized)) } diff --git a/identity/src/main/java/me/uport/sdk/identity/endpoints/Unnu.kt b/identity/src/main/java/me/uport/sdk/identity/endpoints/Unnu.kt index 3fe530ce..227042eb 100644 --- a/identity/src/main/java/me/uport/sdk/identity/endpoints/Unnu.kt +++ b/identity/src/main/java/me/uport/sdk/identity/endpoints/Unnu.kt @@ -1,7 +1,8 @@ package me.uport.sdk.identity.endpoints -import com.squareup.moshi.Json -import com.squareup.moshi.JsonAdapter +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JSON import me.uport.sdk.core.Networks import me.uport.sdk.core.urlPost import java.io.IOException @@ -16,58 +17,51 @@ typealias IdentityInfoCallback = (err: Exception?, identityInfo: UnnuIdentityInf /** * Encapsulates the payload data for an "Identity creation request" made to Unnu */ +@Serializable data class UnnuCreationRequest( - @Json(name = "deviceKey") + @SerialName("deviceKey") val deviceKey: String, - @Json(name = "recoveryKey") + @SerialName("recoveryKey") val recoveryKey: String, - @Json(name = "blockchain") + @SerialName("blockchain") val blockchain: String, - @Json(name = "managerType") + @SerialName("managerType") val managerType: String = "MetaIdentityManager") { - fun toJson() = unnuCreationRequestAdapter?.toJson(this) ?: "" - - companion object { - /** - * Adapter used to serialize unnu request object - */ - private val unnuCreationRequestAdapter = moshi.adapter(UnnuCreationRequest::class.java) - } + fun toJson() = JSON.stringify(this) } /** * Wraps the data needed for a Unnu lookup request */ +@Serializable data class UnnuLookupRequest(val deviceKey: String) { - fun toJson() = jsonAdapter?.toJson(this) ?: "" + fun toJson() = JSON.stringify(this) - companion object { - private val jsonAdapter: JsonAdapter? = moshi.adapter(UnnuLookupRequest::class.java) - } } /** * Encapsulates the response data for identity creation or lookup */ +@Serializable data class UnnuIdentityInfo( - @Json(name = "managerType") + @SerialName("managerType") val managerType: String = "MetaIdentityManager", - @Json(name = "managerAddress") + @SerialName("managerAddress") val managerAddress: String = "", - @Json(name = "txHash") + @SerialName("txHash") val txHash: String? = null, - @Json(name = "identity") + @SerialName("identity") val proxyAddress: String? = null, - @Json(name = "blockchain") + @SerialName("blockchain") val blockchain: String? = null) { companion object { @@ -78,24 +72,20 @@ data class UnnuIdentityInfo( /** * Wraps the [UnnuIdentityInfo] response in an object suitable for receiving JsonRPC responses */ +@Serializable data class UnnuJRPCResponse( - @Json(name = "status") + @SerialName("status") val status: String = "failure", - @Json(name = "message") + @SerialName("message") val message: String? = null, - @Json(name = "data") + @SerialName("data") val data: UnnuIdentityInfo = UnnuIdentityInfo()) { companion object { - /** - * Adapter used to de-serialize unnu response object - */ - private val jsonAdapter = moshi.adapter(UnnuJRPCResponse::class.java) - - fun fromJson(json: String) = jsonAdapter.fromJson(json) ?: UnnuJRPCResponse() + fun fromJson(json: String) : UnnuJRPCResponse = JSON.nonstrict.parse(json) } } diff --git a/identity/src/main/java/me/uport/sdk/identity/endpoints/Utils.kt b/identity/src/main/java/me/uport/sdk/identity/endpoints/Utils.kt deleted file mode 100644 index 5c39d55b..00000000 --- a/identity/src/main/java/me/uport/sdk/identity/endpoints/Utils.kt +++ /dev/null @@ -1,10 +0,0 @@ -package me.uport.sdk.identity.endpoints - -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory - -/** - * Moshi instance used to (de)serialize objects in requests/responses - */ -internal val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - diff --git a/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt b/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt index 4a15b091..ab350f47 100644 --- a/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt +++ b/identity/src/test/java/me/uport/sdk/identity/AccountsTests.kt @@ -19,6 +19,8 @@ class AccountsTests { val serialized = acc.toJson() + println(serialized) + val other = Account.fromJson(serialized) assertEquals(acc, other) diff --git a/jwt/src/main/java/me/uport/sdk/jwt/model/JwtPayload.kt b/jwt/src/main/java/me/uport/sdk/jwt/model/JwtPayload.kt index ff5b6f4d..d6815180 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/model/JwtPayload.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/model/JwtPayload.kt @@ -10,10 +10,10 @@ data class JwtPayload( * General */ val iss: String = "", //Cannot be null for signature verification - val iat: Int? = null, + val iat: Long? = null, val sub: String? = null, val aud: String? = null, - val exp: Int? = null, + val exp: Long? = null, val callback: String? = null, val type: String? = null, diff --git a/serialization/build.gradle b/serialization/build.gradle index 053a8d7e..a87f68ac 100644 --- a/serialization/build.gradle +++ b/serialization/build.gradle @@ -28,6 +28,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" api "com.squareup.moshi:moshi-kotlin:$moshi_version" +// kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" + api "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$kotlin_serialization_version" api "com.squareup.moshi:moshi-adapters:$moshi_version" testImplementation "junit:junit:$junit_version" From 3e6820e8c19fba10d633f5a81929160c0d324b49 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 13:53:25 +0300 Subject: [PATCH 26/75] add basic interfaces for UniversalDID --- settings.gradle | 1 + universal-did/build.gradle | 65 +++++++++++++++++++ universal-did/src/main/AndroidManifest.xml | 5 ++ .../me/uport/sdk/universaldid/UniversalDID.kt | 48 ++++++++++++++ .../me/uport/sdk/ethrdid/UniversalDIDTest.kt | 17 +++++ 5 files changed, 136 insertions(+) create mode 100644 universal-did/build.gradle create mode 100644 universal-did/src/main/AndroidManifest.xml create mode 100644 universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt create mode 100644 universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt diff --git a/settings.gradle b/settings.gradle index 754726c3..7a8f55ef 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ include ':demoapp' include ':signer' include ':uport-did' include ':ethr-did' +include ':universal-did' include ':identity' include ':sdk' include ':jsonrpc' diff --git a/universal-did/build.gradle b/universal-did/build.gradle new file mode 100644 index 00000000..41d21bc6 --- /dev/null +++ b/universal-did/build.gradle @@ -0,0 +1,65 @@ +apply plugin: "com.android.library" +apply plugin: "kotlin-android" +apply plugin: "kotlinx-serialization" +apply plugin: "maven" + +//apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle' + +android { + compileSdkVersion compile_sdk_version + buildToolsVersion build_tools_version + + + defaultConfig { + minSdkVersion min_sdk_version + targetSdkVersion target_sdk_version + versionCode 1 + versionName "1.0" + + multiDexEnabled true + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + dexOptions { + jumboMode true + } + +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + + implementation "com.android.support:support-annotations:$support_lib_version" + + api "com.github.walleth.kethereum:extensions:$kethereum_version" + api "com.github.walleth.kethereum:model:$kethereum_version" + api "com.github.walleth.kethereum:base58:$kethereum_version" + + api project(":jsonrpc") + api project(":core") + api project(":signer") + api project(":serialization") + + testImplementation "junit:junit:$junit_version" + testImplementation "org.mockito:mockito-inline:$mockito_version" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin_version" + + androidTestImplementation "com.android.support.test:runner:$test_runner_version" + androidTestImplementation "com.android.support.test:rules:$test_runner_version" +} + +kotlin { + experimental { + coroutines "enable" + } +} \ No newline at end of file diff --git a/universal-did/src/main/AndroidManifest.xml b/universal-did/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ac732f86 --- /dev/null +++ b/universal-did/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt new file mode 100644 index 00000000..c494a4b6 --- /dev/null +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -0,0 +1,48 @@ +package me.uport.sdk.universaldid + +import android.support.annotation.VisibleForTesting +import android.support.annotation.VisibleForTesting.PRIVATE + +object UniversalDID : DIDResolver { + + private val resolvers = mapOf().toMutableMap() + fun registerResolver(resolver: DIDResolver) { + if (resolver.method.isBlank()) { + return + } + resolvers[resolver.method] = resolver + } + + @VisibleForTesting(otherwise = PRIVATE) + internal fun clearResolvers() = resolvers.clear() + + override val method: String = "" + + override fun resolve(did: String): DIDDocument { + val (method, _) = parse(did) + if (method.isBlank()) return DIDDocument.blank + return resolvers[method]?.resolve(did) ?: return DIDDocument.blank + } + + private fun parse(did: String): Pair { + val matchResult = didPattern.find(did) ?: return ("" to "") + val (method, identifier) = matchResult.destructured + return (method to identifier) + } + + //language=RegExp + private val didPattern = "^did:(.*):(.*)".toRegex() +} + +interface DIDResolver { + val method: String + fun resolve(did: String): DIDDocument + +} + +interface DIDDocument { + + companion object { + val blank = object : DIDDocument {} + } +} \ No newline at end of file diff --git a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt new file mode 100644 index 00000000..f0787954 --- /dev/null +++ b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt @@ -0,0 +1,17 @@ +package me.uport.sdk.ethrdid + +import me.uport.sdk.universaldid.DIDDocument +import me.uport.sdk.universaldid.UniversalDID +import org.junit.Assert.assertEquals +import org.junit.Test + +class UniversalDIDTest { + + @Test + fun `blank resolves to blank`() { + UniversalDID.clearResolvers() + + assertEquals(DIDDocument.blank, UniversalDID.resolve("")) + } + +} \ No newline at end of file From 34fd6cd9eabcd44aec3137a60e9edc181a78c1c5 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 14:35:31 +0300 Subject: [PATCH 27/75] add tests to the universal resolver --- universal-did/build.gradle | 12 ---- .../me/uport/sdk/universaldid/UniversalDID.kt | 9 +-- .../me/uport/sdk/ethrdid/UniversalDIDTest.kt | 64 ++++++++++++++++++- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/universal-did/build.gradle b/universal-did/build.gradle index 41d21bc6..29212abf 100644 --- a/universal-did/build.gradle +++ b/universal-did/build.gradle @@ -41,21 +41,9 @@ dependencies { implementation "com.android.support:support-annotations:$support_lib_version" - api "com.github.walleth.kethereum:extensions:$kethereum_version" - api "com.github.walleth.kethereum:model:$kethereum_version" - api "com.github.walleth.kethereum:base58:$kethereum_version" - - api project(":jsonrpc") - api project(":core") - api project(":signer") - api project(":serialization") - testImplementation "junit:junit:$junit_version" testImplementation "org.mockito:mockito-inline:$mockito_version" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin_version" - - androidTestImplementation "com.android.support.test:runner:$test_runner_version" - androidTestImplementation "com.android.support.test:rules:$test_runner_version" } kotlin { diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt index c494a4b6..3aa05383 100644 --- a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -18,25 +18,26 @@ object UniversalDID : DIDResolver { override val method: String = "" - override fun resolve(did: String): DIDDocument { + override suspend fun resolve(did: String): DIDDocument { val (method, _) = parse(did) if (method.isBlank()) return DIDDocument.blank return resolvers[method]?.resolve(did) ?: return DIDDocument.blank } - private fun parse(did: String): Pair { + @VisibleForTesting(otherwise = PRIVATE) + internal fun parse(did: String): Pair { val matchResult = didPattern.find(did) ?: return ("" to "") val (method, identifier) = matchResult.destructured return (method to identifier) } //language=RegExp - private val didPattern = "^did:(.*):(.*)".toRegex() + private val didPattern = "^did:(.*?):(.+)".toRegex() } interface DIDResolver { val method: String - fun resolve(did: String): DIDDocument + suspend fun resolve(did: String): DIDDocument } diff --git a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt index f0787954..dbc5a801 100644 --- a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt +++ b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt @@ -1,17 +1,77 @@ package me.uport.sdk.ethrdid +import kotlinx.coroutines.experimental.runBlocking import me.uport.sdk.universaldid.DIDDocument +import me.uport.sdk.universaldid.DIDResolver import me.uport.sdk.universaldid.UniversalDID -import org.junit.Assert.assertEquals +import org.junit.Assert.* import org.junit.Test class UniversalDIDTest { @Test - fun `blank resolves to blank`() { + fun `blank resolves to blank`() = runBlocking { UniversalDID.clearResolvers() assertEquals(DIDDocument.blank, UniversalDID.resolve("")) } + private val validDIDs = listOf( + "did:generic:0x0011223344556677889900112233445566778899", + "did:generic:01234", + "did:generic:has spaces", + "did:generic:more:colons", + "did:generic:01234#fragment-attached", + "did:generic:01234?key=value", + "did:generic:01234?key=value&other-key=other-value" + ) + + private val invalidDIDs = listOf( + "", + "0x0011223344556677889900112233445566778899", + "ethr:0x0011223344556677889900112233445566778899", + "did:ethr", + "did::something", + "did:ethr:" + ) + + @Test + fun `parses dids correctly`() { + validDIDs.forEach { + val (method, identifier) = UniversalDID.parse(it) + assertTrue("parsing $it failed, got (method=$method, identifier=$identifier)", method.isNotBlank()) + assertEquals("generic", method) + } + + invalidDIDs.forEach { + val (method, identifier) = UniversalDID.parse(it) + assertTrue("parsing $it should have failed, got (method=$method, identifier=$identifier)", method.isBlank()) + } + } + + private val testDDO = object : DIDDocument { + val unusedField = "test document" + } + + private val testResolver = object : DIDResolver { + override val method: String = "test" + + override suspend fun resolve(did: String): DIDDocument { + return testDDO + } + + } + + @Test + fun `can register and find resolver`() { + UniversalDID.clearResolvers() + + UniversalDID.registerResolver(resolver = testResolver) + + runBlocking { + val ddo = UniversalDID.resolve("did:test:can find resolver for this") + assertNotEquals(ddo, DIDDocument.blank) + } + } + } \ No newline at end of file From 6ead2dd64fb280544c6594b5756db5d19f656d1c Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 14:51:49 +0300 Subject: [PATCH 28/75] add minimal documentation to Universal DID methods and classes --- .../me/uport/sdk/universaldid/UniversalDID.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt index 3aa05383..98a65553 100644 --- a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -3,9 +3,21 @@ package me.uport.sdk.universaldid import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PRIVATE +/** + * A class to abstract resolving Decentralized Identity (DID) documents + * from specific implementations based on the [method] component of a DID [String] + * + * [DIDResolver] implementations need to be registered using [registerResolver] + * + * Known implementations of [DIDResolver] are [ethr-did] and [uport-did] + */ object UniversalDID : DIDResolver { private val resolvers = mapOf().toMutableMap() + + /** + * Register a resolver for a particular DID [method] + */ fun registerResolver(resolver: DIDResolver) { if (resolver.method.isBlank()) { return @@ -35,12 +47,21 @@ object UniversalDID : DIDResolver { private val didPattern = "^did:(.*?):(.+)".toRegex() } +/** + * Abstraction of various methods of resolving DIDs + * + * Each resolver should know the [method] it is supposed to resolve + * and implement a [resolve] coroutine to eventually return a [DIDDocument] or throw an error + */ interface DIDResolver { val method: String suspend fun resolve(did: String): DIDDocument } +/** + * Abstraction for DID documents + */ interface DIDDocument { companion object { From 3a004ee3ef83a65fdf2e627f921434c5fb92b42a Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 15:31:27 +0300 Subject: [PATCH 29/75] add inheritance of DID interfaces --- ethr-did/build.gradle | 1 + .../ethrdid/{DDO.kt => EthrDIDDocument.kt} | 7 ++++--- .../me/uport/sdk/ethrdid/EthrDIDResolver.kt | 21 +++++++++++-------- .../test/java/me/uport/sdk/ethrdid/DDOTest.kt | 4 ++-- .../uport/sdk/ethrdid/EthrDIDResolverTest.kt | 2 +- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 4 ++-- uport-did/build.gradle | 1 + .../uportdid/{DDO.kt => UportDIDDocument.kt} | 9 ++++---- .../{DIDResolver.kt => UportDIDResolver.kt} | 18 ++++++++-------- ...esolverTest.kt => UportDIDResolverTest.kt} | 10 ++++----- 10 files changed, 42 insertions(+), 35 deletions(-) rename ethr-did/src/main/java/me/uport/sdk/ethrdid/{DDO.kt => EthrDIDDocument.kt} (93%) rename uport-did/src/main/java/me/uport/sdk/uportdid/{DDO.kt => UportDIDDocument.kt} (88%) rename uport-did/src/main/java/me/uport/sdk/uportdid/{DIDResolver.kt => UportDIDResolver.kt} (85%) rename uport-did/src/test/java/me/uport/sdk/uportdid/{DIDResolverTest.kt => UportDIDResolverTest.kt} (82%) diff --git a/ethr-did/build.gradle b/ethr-did/build.gradle index da772264..b74687e2 100644 --- a/ethr-did/build.gradle +++ b/ethr-did/build.gradle @@ -51,6 +51,7 @@ dependencies { api project(":core") api project(":signer") api project(":serialization") + api project(":universal-did") testImplementation "junit:junit:$junit_version" testImplementation "org.mockito:mockito-inline:$mockito_version" diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/DDO.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDDocument.kt similarity index 93% rename from ethr-did/src/main/java/me/uport/sdk/ethrdid/DDO.kt rename to ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDDocument.kt index 37a98eb3..2678c71b 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/DDO.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDDocument.kt @@ -5,13 +5,14 @@ import kotlinx.serialization.Optional import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JSON +import me.uport.sdk.universaldid.DIDDocument /** * Classes to describe the DID document corresponding to a particular ethr-did */ @Serializable -data class DDO( +data class EthrDIDDocument( @SerialName("id") val id: String, @@ -27,11 +28,11 @@ data class DDO( @SerialName("@context") val context: String = "https://w3id.org/did/v1" -) { +) : DIDDocument { override fun toString(): String = JSON.indented.stringify(this) companion object { - val blank = DDO("") + val blank = EthrDIDDocument("") } } diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt index d896c354..3e4d0576 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt @@ -15,6 +15,7 @@ import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jsonrpc.JsonRpcBaseResponse import me.uport.sdk.jsonrpc.experimental.ethCall import me.uport.sdk.jsonrpc.experimental.getLogs +import me.uport.sdk.universaldid.DIDResolver import org.kethereum.encodings.encodeToBase58String import org.kethereum.extensions.hexToBigInteger import org.kethereum.extensions.toHexStringNoPrefix @@ -29,12 +30,14 @@ class EthrDIDResolver( private val rpc: JsonRPC, //TODO: replace hardcoded coordinates with configuration val registryAddress: String = DEFAULT_REGISTRY_ADDRESS -) { +) : DIDResolver { + + override val method = "ethr" /** - * Resolves a given ethereum address or DID string into a corresponding DDO + * Resolves a given ethereum address or DID string into a corresponding [EthrDIDDocument] */ - suspend fun resolve(did: String): DDO { + override suspend fun resolve(did: String): EthrDIDDocument { val normalizedDid = normalizeDid(did) val identity = parseIdentity(normalizedDid) val ethrdidContract = EthrDID(identity, rpc, registryAddress, Signer.blank) @@ -44,16 +47,16 @@ class EthrDIDResolver( } /** - * Resolves a given ethereum address or DID string into a corresponding DDO + * Resolves a given ethereum address or DID string into a corresponding [EthrDIDDocument] * Calls back on the main thread with the result or an exception */ - fun resolve(did: String, callback: (err: Exception?, ddo: DDO) -> Unit) { + fun resolve(did: String, callback: (err: Exception?, ddo: EthrDIDDocument) -> Unit) { GlobalScope.launch { try { val ddo = resolve(did) withContext(UI) { callback(null, ddo) } } catch (ex: Exception) { - withContext(UI) { callback(ex, DDO.blank) } + withContext(UI) { callback(ex, EthrDIDDocument.blank) } } } } @@ -119,10 +122,10 @@ class EthrDIDResolver( } /** - * Wraps previously gathered info into a DDO + * Wraps previously gathered info into a [EthrDIDDocument] */ @VisibleForTesting(otherwise = PRIVATE) - fun wrapDidDocument(normalizedDid: String, owner: String, history: List): DDO { + fun wrapDidDocument(normalizedDid: String, owner: String, history: List): EthrDIDDocument { val now = System.currentTimeMillis() / 1000 val pkEntries = mapOf().toMutableMap().apply { @@ -222,7 +225,7 @@ class EthrDIDResolver( } } - return DDO( + return EthrDIDDocument( id = normalizedDid, publicKey = pkEntries.values.toList(), authentication = authEntries.values.toList(), diff --git a/ethr-did/src/test/java/me/uport/sdk/ethrdid/DDOTest.kt b/ethr-did/src/test/java/me/uport/sdk/ethrdid/DDOTest.kt index 2509a936..2c6669fa 100644 --- a/ethr-did/src/test/java/me/uport/sdk/ethrdid/DDOTest.kt +++ b/ethr-did/src/test/java/me/uport/sdk/ethrdid/DDOTest.kt @@ -8,7 +8,7 @@ class DDOTest { @Test fun `can serialize minimal doc`() { - val doc = DDO("hello") + val doc = EthrDIDDocument("hello") val docText = JSON.stringify(doc) assertEquals(""" {"id":"hello","publicKey":[],"authentication":[],"service":[],"@context":"https://w3id.org/did/v1"} @@ -35,7 +35,7 @@ class DDOTest { } """.trimIndent() - val obj: DDO = JSON.parse(docText) + val obj: EthrDIDDocument = JSON.parse(docText) println(obj) } diff --git a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt index 4365ac61..86e15a68 100644 --- a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt +++ b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt @@ -172,7 +172,7 @@ class EthrDIDResolverTest { "publicKey": "did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a#owner"}] } """.trimIndent() - val referenceDDO = JSON.nonstrict.parse(referenceDDOString) + val referenceDDO = JSON.nonstrict.parse(referenceDDOString) val realAddress = "0xb9c5714089478a327f09197987f16f9e5d936e8a" diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index f25dbd15..cdd8da64 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -7,7 +7,7 @@ import com.uport.sdk.signer.UportHDSigner import com.uport.sdk.signer.decodeJose import com.uport.sdk.signer.getJoseEncoded import me.uport.sdk.core.* -import me.uport.sdk.uportdid.DIDResolver +import me.uport.sdk.uportdid.UportDIDResolver import me.uport.sdk.ethrdid.EthrDIDResolver import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jwt.model.JwtHeader @@ -160,7 +160,7 @@ class JWTTools( } } } else { - DIDResolver().getProfileDocument(payload.iss) { err, ddo -> + UportDIDResolver().getProfileDocument(payload.iss) { err, ddo -> if (err !== null) return@getProfileDocument callback(err, null) diff --git a/uport-did/build.gradle b/uport-did/build.gradle index 6f505784..ea996f6d 100644 --- a/uport-did/build.gradle +++ b/uport-did/build.gradle @@ -36,6 +36,7 @@ dependencies { api project(":jsonrpc") api project(":core") + api project(":universal-did") implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/DDO.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt similarity index 88% rename from uport-did/src/main/java/me/uport/sdk/uportdid/DDO.kt rename to uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt index d863adcb..9f0f9d40 100644 --- a/uport-did/src/main/java/me/uport/sdk/uportdid/DDO.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt @@ -3,6 +3,7 @@ package me.uport.sdk.uportdid import com.squareup.moshi.Json import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import me.uport.sdk.universaldid.DIDDocument /** * A class that encapsulates the DID document @@ -11,7 +12,7 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory * */ @Deprecated("This class definition is prone to change soon so it's marked Deprecated as opposed to stable") -data class DDO( +data class UportDIDDocument( @Json(name = "@context") val context: String?, //ex: "http://schema.org" @@ -32,11 +33,11 @@ data class DDO( @Json(name = "description") val description: String? // ex: "uPort Attestation" -) { +) : DIDDocument { companion object { - fun fromJson(json: String): DDO? { + fun fromJson(json: String): UportDIDDocument? { val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - val adapter = moshi.adapter(DDO::class.java) + val adapter = moshi.adapter(UportDIDDocument::class.java) return adapter.fromJson(json) } diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/DIDResolver.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt similarity index 85% rename from uport-did/src/main/java/me/uport/sdk/uportdid/DIDResolver.kt rename to uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt index 5f847e86..963f79eb 100644 --- a/uport-did/src/main/java/me/uport/sdk/uportdid/DIDResolver.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt @@ -16,7 +16,7 @@ import org.walleth.khex.hexToByteArray import pm.gnosis.model.Solidity -class DIDResolver { +class UportDIDResolver { /** * Given an MNID, calls the uport registry and returns the raw json @@ -53,10 +53,10 @@ class DIDResolver { } /** - * Given an MNID, obtains the IPFS hash of the DIDResolver document by eth_call to the uport registry + * Given an MNID, obtains the IPFS hash of the UportDIDResolver document by eth_call to the uport registry */ internal fun getIpfsHashSync(mnid: String): String { - val docAddressHex = DIDResolver().callRegistrySync(mnid) + val docAddressHex = UportDIDResolver().callRegistrySync(mnid) return if (docAddressHex.isBlank()) { return "" } else { @@ -65,7 +65,7 @@ class DIDResolver { } /** - * Obtains the JSON encoded DIDResolver doc given an mnid + * Obtains the JSON encoded UportDIDResolver doc given an mnid */ private fun getJsonProfileSync(mnid: String): String { @@ -77,22 +77,22 @@ class DIDResolver { } /** - * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [DDO] object + * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [UportDIDDocument] object * * Should return `null` if anything goes wrong */ - internal fun getProfileDocumentSync(mnid: String): DDO? { + internal fun getProfileDocumentSync(mnid: String): UportDIDDocument? { val rawJsonDDO = getJsonProfileSync(mnid) - return DDO.fromJson(rawJsonDDO) + return UportDIDDocument.fromJson(rawJsonDDO) } /** - * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [DDO] object + * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [UportDIDDocument] object * * TODO: Should [callback] with non-`null` error if anything goes wrong */ - fun getProfileDocument(mnid: String, callback: (err: Exception?, ddo: DDO) -> Unit) { + fun getProfileDocument(mnid: String, callback: (err: Exception?, ddo: UportDIDDocument) -> Unit) { Thread { //safe to call networks diff --git a/uport-did/src/test/java/me/uport/sdk/uportdid/DIDResolverTest.kt b/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt similarity index 82% rename from uport-did/src/test/java/me/uport/sdk/uportdid/DIDResolverTest.kt rename to uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt index f2abd075..92fd4130 100644 --- a/uport-did/src/test/java/me/uport/sdk/uportdid/DIDResolverTest.kt +++ b/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt @@ -5,7 +5,7 @@ import me.uport.sdk.jsonrpc.EthCall import org.junit.Assert.assertEquals import org.junit.Test -class DIDResolverTest { +class UportDIDResolverTest { //TODO: add tests that call mock server instead of actually calling IPFS and ETH nets @@ -24,7 +24,7 @@ class DIDResolverTest { fun encodes_eth_call() { val expectedEncoding = "0x447885f075506f727450726f66696c654950465331323230000000000000000000000000000000000000000000000000f12c30cd32b4a027710c150ae742f50db0749213000000000000000000000000f12c30cd32b4a027710c150ae742f50db0749213" val acc = Account.from(network = "0x04", address = "0xf12c30cd32b4a027710c150ae742f50db0749213") - val encoding = DIDResolver().encodeRegistryFunctionCall("uPortProfileIPFS1220", acc, acc) + val encoding = UportDIDResolver().encodeRegistryFunctionCall("uPortProfileIPFS1220", acc, acc) assertEquals(expectedEncoding, encoding) } @@ -33,7 +33,7 @@ class DIDResolverTest { fun calls_registry() { val expectedDocAddress = "QmWzBDtv8m21ph1aM57yVDWxdG7LdQd3rNf5xrRiiV2D2E" - val docAddressHex = DIDResolver().getIpfsHashSync("2ozs2ntCXceKkAQKX4c9xp2zPS8pvkJhVqC") + val docAddressHex = UportDIDResolver().getIpfsHashSync("2ozs2ntCXceKkAQKX4c9xp2zPS8pvkJhVqC") assertEquals(expectedDocAddress, docAddressHex) } @@ -41,7 +41,7 @@ class DIDResolverTest { @Test fun getJsonDIDSync() { - val expectedDDO = DDO( + val expectedDDO = UportDIDDocument( context = "http://schema.org", type = "Person", publicKey = "0x04e8989d1826cd6258906cfaa71126e2db675eaef47ddeb9310ee10db69b339ab960649e1934dc1e1eac1a193a94bd7dc5542befc5f7339845265ea839b9cbe56f", @@ -51,7 +51,7 @@ class DIDResolverTest { name = null ) - val ddo = DIDResolver().getProfileDocumentSync("2ozs2ntCXceKkAQKX4c9xp2zPS8pvkJhVqC") + val ddo = UportDIDResolver().getProfileDocumentSync("2ozs2ntCXceKkAQKX4c9xp2zPS8pvkJhVqC") assertEquals(expectedDDO, ddo) } From 089c4e191802cb85de197cd044fd37abd8c8d778 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 15:41:06 +0300 Subject: [PATCH 30/75] implement DIDResolver methods in UportDIDResolver --- uport-did/build.gradle | 6 ++++-- .../me/uport/sdk/uportdid/UportDIDDocument.kt | 1 - .../me/uport/sdk/uportdid/UportDIDResolver.kt | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/uport-did/build.gradle b/uport-did/build.gradle index ea996f6d..c5792682 100644 --- a/uport-did/build.gradle +++ b/uport-did/build.gradle @@ -45,6 +45,8 @@ dependencies { } -repositories { - mavenCentral() +kotlin { + experimental { + coroutines "enable" + } } \ No newline at end of file diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt index 9f0f9d40..b728f4bf 100644 --- a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt @@ -11,7 +11,6 @@ import me.uport.sdk.universaldid.DIDDocument * See [identity_document spec](https://github.com/uport-project/specs/blob/develop/pki/identitydocument.md) * */ -@Deprecated("This class definition is prone to change soon so it's marked Deprecated as opposed to stable") data class UportDIDDocument( @Json(name = "@context") val context: String?, //ex: "http://schema.org" diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt index 963f79eb..14f376c2 100644 --- a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt @@ -9,14 +9,28 @@ import me.uport.sdk.core.urlGetSync import me.uport.sdk.core.urlPostSync import me.uport.sdk.jsonrpc.EthCall import me.uport.sdk.jsonrpc.JsonRpcBaseResponse +import me.uport.sdk.universaldid.DIDDocument +import me.uport.sdk.universaldid.DIDResolver import org.kethereum.encodings.encodeToBase58String import org.kethereum.extensions.hexToBigInteger import org.walleth.khex.clean0xPrefix import org.walleth.khex.hexToByteArray import pm.gnosis.model.Solidity +import kotlin.coroutines.experimental.suspendCoroutine -class UportDIDResolver { +class UportDIDResolver : DIDResolver { + override val method: String = "uport" + + override suspend fun resolve(did: String): DIDDocument = suspendCoroutine { continuation -> + getProfileDocument(did) { err, ddo -> + if(err != null) { + continuation.resumeWithException(err) + } else { + continuation.resume(ddo) + } + } + } /** * Given an MNID, calls the uport registry and returns the raw json From 714b749a32c52da463a4806cd84fea6fb7de35a3 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 18:29:27 +0300 Subject: [PATCH 31/75] add `canResolve` method to did resolvers, which is supposed to check the input for validity but be able to accept some invalid but normalizable input --- .../me/uport/sdk/ethrdid/EthrDIDResolver.kt | 3 ++ .../me/uport/sdk/universaldid/UniversalDID.kt | 7 ++++ .../me/uport/sdk/ethrdid/UniversalDIDTest.kt | 2 + uport-did/build.gradle | 1 + .../me/uport/sdk/uportdid/UportDIDResolver.kt | 39 +++++++++++++++++-- .../sdk/uportdid/UportDIDResolverTest.kt | 33 +++++++++++++++- 6 files changed, 80 insertions(+), 5 deletions(-) diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt index 3e4d0576..6247e672 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt @@ -34,6 +34,9 @@ class EthrDIDResolver( override val method = "ethr" + override fun canResolve(potentialDID: String): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } /** * Resolves a given ethereum address or DID string into a corresponding [EthrDIDDocument] */ diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt index 98a65553..1f2c8f85 100644 --- a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -13,6 +13,7 @@ import android.support.annotation.VisibleForTesting.PRIVATE */ object UniversalDID : DIDResolver { + private val resolvers = mapOf().toMutableMap() /** @@ -30,6 +31,11 @@ object UniversalDID : DIDResolver { override val method: String = "" + override fun canResolve(potentialDID: String): Boolean { + //FIXME: check if any of the registered resolvers can resolve + return true + } + override suspend fun resolve(did: String): DIDDocument { val (method, _) = parse(did) if (method.isBlank()) return DIDDocument.blank @@ -57,6 +63,7 @@ interface DIDResolver { val method: String suspend fun resolve(did: String): DIDDocument + fun canResolve(potentialDID: String): Boolean } /** diff --git a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt index dbc5a801..5dd34809 100644 --- a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt +++ b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt @@ -54,6 +54,8 @@ class UniversalDIDTest { } private val testResolver = object : DIDResolver { + override fun canResolve(potentialDID: String): Boolean = true + override val method: String = "test" override suspend fun resolve(did: String): DIDDocument { diff --git a/uport-did/build.gradle b/uport-did/build.gradle index c5792682..ab79442f 100644 --- a/uport-did/build.gradle +++ b/uport-did/build.gradle @@ -31,6 +31,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "com.github.gnosis.bivrost-kotlin:bivrost-solidity-types:$bivrost_version" + implementation "com.android.support:support-annotations:$support_lib_version" api "com.github.walleth.kethereum:extensions:$kethereum_version" diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt index 14f376c2..cf3049f3 100644 --- a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt @@ -2,6 +2,8 @@ package me.uport.sdk.uportdid import android.os.Handler import android.os.Looper +import android.support.annotation.VisibleForTesting +import android.support.annotation.VisibleForTesting.PRIVATE import me.uport.mnid.Account import me.uport.mnid.MNID import me.uport.sdk.core.Networks @@ -18,13 +20,21 @@ import org.walleth.khex.hexToByteArray import pm.gnosis.model.Solidity import kotlin.coroutines.experimental.suspendCoroutine - +/** + * This is a DID resolver implementation that supports the "uport" DID method. + * It accepts uport dids or simple mnids and produces a document described at: + * https://github.com/uport-project/specs/blob/develop/pki/identitydocument.md + * + * Example uport did: "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX#owner" + * Example mnid: "2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX" + */ class UportDIDResolver : DIDResolver { override val method: String = "uport" override suspend fun resolve(did: String): DIDDocument = suspendCoroutine { continuation -> - getProfileDocument(did) { err, ddo -> - if(err != null) { + val (_, mnid) = parse(did) + getProfileDocument(mnid) { err, ddo -> + if (err != null) { continuation.resumeWithException(err) } else { continuation.resume(ddo) @@ -32,6 +42,22 @@ class UportDIDResolver : DIDResolver { } } + override fun canResolve(potentialDID: String): Boolean { + val (method, mnid) = parse(potentialDID) + return if (method == this.method) { + MNID.isMNID(mnid) + } else { + MNID.isMNID(potentialDID) + } + } + + @VisibleForTesting(otherwise = PRIVATE) + internal fun parse(did: String): Pair { + val matchResult = uportDIDPattern.find(did) ?: return ("" to did) + val (_, method, mnid) = matchResult.destructured + return (method to mnid) + } + /** * Given an MNID, calls the uport registry and returns the raw json */ @@ -70,7 +96,7 @@ class UportDIDResolver : DIDResolver { * Given an MNID, obtains the IPFS hash of the UportDIDResolver document by eth_call to the uport registry */ internal fun getIpfsHashSync(mnid: String): String { - val docAddressHex = UportDIDResolver().callRegistrySync(mnid) + val docAddressHex = callRegistrySync(mnid) return if (docAddressHex.isBlank()) { return "" } else { @@ -120,4 +146,9 @@ class UportDIDResolver : DIDResolver { } + companion object { + //language=RegExp + private val uportDIDPattern = "^(did:(uport):)?([1-9A-HJ-NP-Za-km-z]{34,38})(.*)".toRegex() + } + } diff --git a/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt b/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt index 92fd4130..b9a4158e 100644 --- a/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt +++ b/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt @@ -2,7 +2,7 @@ package me.uport.sdk.uportdid import me.uport.mnid.Account import me.uport.sdk.jsonrpc.EthCall -import org.junit.Assert.assertEquals +import org.junit.Assert.* import org.junit.Test class UportDIDResolverTest { @@ -57,4 +57,35 @@ class UportDIDResolverTest { } + @Test + fun can_resolve_valid_dids() { + listOf( + "2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "5A8bRWU3F7j3REx3vkJWxdjQPp4tqmxFPmab1Tr", + "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX#owner" + ).forEach { + assertTrue("fails to resolve resolve '$it'", UportDIDResolver().canResolve(it)) + } + + } + + @Test + fun fails_on_invalid_dids() { + listOf( + "did:something:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", //different method + "QmXuNqXmrkxs4WhTDC2GCnXEep4LUD87bu97LQMn1rkxmQ", //not mnid + "1GbVUSW5WJmRCpaCJ4hanUny77oDaWW4to", //not mnid + "0x00521965e7bd230323c423d96c657db5b79d099f", //not mnid + "did:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", //missing method + "did:uport:", //missing mnid + "did:uport" //missing mnid and colon + ).forEach { + assertFalse("claims to be able to resolve '$it", UportDIDResolver().canResolve(it)) + } + + + } + + } \ No newline at end of file From e168119b95449f547951ae608f1ade68d08a48be Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 18:54:19 +0300 Subject: [PATCH 32/75] add `canResolve` implementation for EthrDIDResolver and UniversalDID resolver --- .../me/uport/sdk/ethrdid/EthrDIDResolver.kt | 3 +- .../uport/sdk/ethrdid/EthrDIDResolverTest.kt | 3 +- .../me/uport/sdk/universaldid/UniversalDID.kt | 37 +++++++++++++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt index 6247e672..01a5c3bf 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt @@ -35,7 +35,8 @@ class EthrDIDResolver( override val method = "ethr" override fun canResolve(potentialDID: String): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + //if it can be normalized, then it matches either an ethereum address or a full ethr-did + return normalizeDid(potentialDID).isNotBlank() } /** * Resolves a given ethereum address or DID string into a corresponding [EthrDIDDocument] diff --git a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt index 86e15a68..3fe645a5 100644 --- a/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt +++ b/ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDResolverTest.kt @@ -190,7 +190,8 @@ class EthrDIDResolverTest { "0xb9c5714089478a327f09197987f16f9e5d936e8a", "0xB9C5714089478a327F09197987f16f9E5d936E8a", "did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a", - "did:ethr:0xB9C5714089478a327F09197987f16f9E5d936E8a" + "did:ethr:0xB9C5714089478a327F09197987f16f9E5d936E8a", + "did:ethr:0xB9C5714089478a327F09197987f16f9E5d936E8a#owner" ) val invalidDids = listOf( diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt index 1f2c8f85..72b272bd 100644 --- a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -2,6 +2,8 @@ package me.uport.sdk.universaldid import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PRIVATE +import java.lang.IllegalArgumentException +import java.lang.IllegalStateException /** * A class to abstract resolving Decentralized Identity (DID) documents @@ -31,15 +33,32 @@ object UniversalDID : DIDResolver { override val method: String = "" + /** + * checks if any of the registered resolvers can resolve + */ override fun canResolve(potentialDID: String): Boolean { - //FIXME: check if any of the registered resolvers can resolve - return true + val resolver = resolvers.values.find { + it.canResolve(potentialDID) + } + return (resolver != null) } override suspend fun resolve(did: String): DIDDocument { val (method, _) = parse(did) - if (method.isBlank()) return DIDDocument.blank - return resolvers[method]?.resolve(did) ?: return DIDDocument.blank + + if (method.isBlank()) { + val resolver = resolvers.values.find { + it.canResolve(did) + } + return resolver?.resolve(did) + ?: throw IllegalArgumentException("The provided did ($did) could not be resolved by any of the ${resolvers.size} registered resolvers") + } //no else clause, carry on + + if (resolvers.containsKey(method)) { + return resolvers[method]?.resolve(did) ?: throw IllegalStateException("There DIDResolver for '$method' failed to resolve '$did' for an unknown reason.") + } else { + throw IllegalStateException("There is no DIDResolver registered to resolve '$method' DIDs and none of the other ${resolvers.size} registered ones can do it.") + } } @VisibleForTesting(otherwise = PRIVATE) @@ -60,9 +79,19 @@ object UniversalDID : DIDResolver { * and implement a [resolve] coroutine to eventually return a [DIDDocument] or throw an error */ interface DIDResolver { + /** + * The DID method that a particular implementation can resolve + */ val method: String + + /** + * Resolve a given [did] in a coroutine and return the [DIDDocument] or throw an error + */ suspend fun resolve(did: String): DIDDocument + /** + * Check if the [potentialDID] can be resolved by this resolver. + */ fun canResolve(potentialDID: String): Boolean } From 88c38f18e3d4bcdad868f8c0e1d7c87150868234 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 19:01:34 +0300 Subject: [PATCH 33/75] add some more documentation for universal resolver methods --- .../java/me/uport/sdk/universaldid/UniversalDID.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt index 72b272bd..e79720a5 100644 --- a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -31,10 +31,14 @@ object UniversalDID : DIDResolver { @VisibleForTesting(otherwise = PRIVATE) internal fun clearResolvers() = resolvers.clear() + /** + * This universal resolver can't be used for any one particular did but for all [DIDResolver]s + * that have been added using [registerResolver] + */ override val method: String = "" /** - * checks if any of the registered resolvers can resolve + * Checks if any of the registered resolvers can resolve */ override fun canResolve(potentialDID: String): Boolean { val resolver = resolvers.values.find { @@ -43,6 +47,12 @@ object UniversalDID : DIDResolver { return (resolver != null) } + /** + * Looks for a [DIDResolver] that can resolve the provided [did] either by method if the did contains one or by trial + * + * @throws IllegalStateException if the proper resolver is not registered or produces `null` + * @throws [IllegalArgumentException] if the given [did] has no `method` but could be resolved by one of the registered resolvers and that one fails with `null` + */ override suspend fun resolve(did: String): DIDDocument { val (method, _) = parse(did) From 8edd4d6a355d056d7e53d7d033530d648e373e07 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 19:12:29 +0300 Subject: [PATCH 34/75] reorg some tests --- .../me/uport/sdk/universaldid/UniversalDID.kt | 3 - .../me/uport/sdk/ethrdid/UniversalDIDTest.kt | 64 ++++++++++--------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt index e79720a5..195e1123 100644 --- a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -110,7 +110,4 @@ interface DIDResolver { */ interface DIDDocument { - companion object { - val blank = object : DIDDocument {} - } } \ No newline at end of file diff --git a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt index 5dd34809..3b200a8c 100644 --- a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt +++ b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt @@ -6,14 +6,47 @@ import me.uport.sdk.universaldid.DIDResolver import me.uport.sdk.universaldid.UniversalDID import org.junit.Assert.* import org.junit.Test +import java.lang.IllegalArgumentException class UniversalDIDTest { + private val testDDO = object : DIDDocument { + val unusedField = "test document" + } + + private val testResolver = object : DIDResolver { + override fun canResolve(potentialDID: String): Boolean = true + + override val method: String = "test" + + override suspend fun resolve(did: String): DIDDocument { + return if (did.contains("test")) testDDO else throw IllegalArgumentException("can't use test resolver") + } + + } + + @Test(expected = IllegalArgumentException::class) + fun `blank resolves to error`() = runBlocking { + UniversalDID.clearResolvers() + + val unusedDdo = UniversalDID.resolve("") + } + + @Test(expected = Exception::class) + fun `testResolver resolves to error with blank`() = runBlocking { + UniversalDID.clearResolvers() + UniversalDID.registerResolver(testResolver) + + val unusedDdo = UniversalDID.resolve("") + } + @Test - fun `blank resolves to blank`() = runBlocking { + fun `can register and find resolver`() = runBlocking { UniversalDID.clearResolvers() + UniversalDID.registerResolver(testResolver) - assertEquals(DIDDocument.blank, UniversalDID.resolve("")) + val ddo = UniversalDID.resolve("did:test:this is a test did") + assertEquals(testDDO, ddo) } private val validDIDs = listOf( @@ -49,31 +82,4 @@ class UniversalDIDTest { } } - private val testDDO = object : DIDDocument { - val unusedField = "test document" - } - - private val testResolver = object : DIDResolver { - override fun canResolve(potentialDID: String): Boolean = true - - override val method: String = "test" - - override suspend fun resolve(did: String): DIDDocument { - return testDDO - } - - } - - @Test - fun `can register and find resolver`() { - UniversalDID.clearResolvers() - - UniversalDID.registerResolver(resolver = testResolver) - - runBlocking { - val ddo = UniversalDID.resolve("did:test:can find resolver for this") - assertNotEquals(ddo, DIDDocument.blank) - } - } - } \ No newline at end of file From 8322af09edb4ac37de88dcece6c8b68c1d8de3fb Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 12 Oct 2018 19:36:32 +0300 Subject: [PATCH 35/75] initialize did resolvers during SDK init --- sdk/build.gradle | 3 +++ sdk/src/main/java/me/uport/sdk/Uport.kt | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/sdk/build.gradle b/sdk/build.gradle index 1bc5a961..1d7db62f 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -36,6 +36,9 @@ dependencies { api project(":identity") api project(":core") api project(":jsonrpc") + api project(":universal-did") + api project(":ethr-did") + api project(":uport-did") androidTestImplementation "com.android.support.test:runner:$test_runner_version" androidTestImplementation "com.android.support.test:rules:$test_runner_version" diff --git a/sdk/src/main/java/me/uport/sdk/Uport.kt b/sdk/src/main/java/me/uport/sdk/Uport.kt index 2ee8dfea..5a335a0b 100644 --- a/sdk/src/main/java/me/uport/sdk/Uport.kt +++ b/sdk/src/main/java/me/uport/sdk/Uport.kt @@ -7,8 +7,13 @@ import android.content.SharedPreferences import kotlinx.coroutines.experimental.GlobalScope import kotlinx.coroutines.experimental.launch import me.uport.sdk.core.EthNetwork +import me.uport.sdk.core.Networks import me.uport.sdk.core.UI +import me.uport.sdk.ethrdid.EthrDIDResolver import me.uport.sdk.identity.* +import me.uport.sdk.jsonrpc.JsonRPC +import me.uport.sdk.universaldid.UniversalDID +import me.uport.sdk.uportdid.UportDIDResolver import kotlin.coroutines.experimental.suspendCoroutine @SuppressLint("StaticFieldLeak") @@ -80,6 +85,10 @@ object Uport { prefs.edit().remove(OLD_DEFAULT_ACCOUNT).apply() } + UniversalDID.registerResolver(UportDIDResolver()) + val defaultRPC = JsonRPC(Networks.mainnet.rpcUrl) + UniversalDID.registerResolver(EthrDIDResolver(defaultRPC)) + //TODO: weak, make Configuration into a builder and actually make methods fail when not configured initialized = true } @@ -177,5 +186,6 @@ object Uport { this.applicationContext = context.applicationContext return this } + } } From 71ab35bc88842fdb78fa634451a35bba5207010b Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 16 Oct 2018 17:05:52 +0300 Subject: [PATCH 36/75] add standard fields to DIDDocument interface and implement a conversion mechanism for UportDID --- .../main/java/me/uport/sdk/ethrdid/EthrDID.kt | 7 +- .../me/uport/sdk/ethrdid/EthrDIDDocument.kt | 65 ++-------- .../me/uport/sdk/ethrdid/EthrDIDResolver.kt | 17 ++- universal-did/build.gradle | 2 + .../me/uport/sdk/universaldid/DIDDocument.kt | 72 +++++++++++ .../me/uport/sdk/universaldid/UniversalDID.kt | 15 +-- .../me/uport/sdk/ethrdid/UniversalDIDTest.kt | 20 ++- .../me/uport/sdk/uportdid/UportDIDDocument.kt | 55 --------- .../me/uport/sdk/uportdid/UportDIDResolver.kt | 43 +++---- .../sdk/uportdid/UportIdentityDocument.kt | 116 ++++++++++++++++++ .../sdk/uportdid/UportDIDResolverTest.kt | 37 +++++- 11 files changed, 285 insertions(+), 164 deletions(-) create mode 100644 universal-did/src/main/java/me/uport/sdk/universaldid/DIDDocument.kt delete mode 100644 uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt create mode 100644 uport-did/src/main/java/me/uport/sdk/uportdid/UportIdentityDocument.kt diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt index e10de37f..3569931c 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt @@ -2,13 +2,13 @@ package me.uport.sdk.ethrdid import com.uport.sdk.signer.Signer import com.uport.sdk.signer.signRawTx -import me.uport.sdk.ethrdid.DelegateType.Secp256k1VerificationKey2018 import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jsonrpc.JsonRpcBaseResponse import me.uport.sdk.jsonrpc.experimental.ethCall import me.uport.sdk.jsonrpc.experimental.getGasPrice import me.uport.sdk.jsonrpc.experimental.getTransactionCount import me.uport.sdk.jsonrpc.experimental.sendRawTransaction +import me.uport.sdk.universaldid.DelegateType import org.kethereum.extensions.hexToBigInteger import org.kethereum.model.Address import org.kethereum.model.createTransactionWithDefaults @@ -29,11 +29,10 @@ class EthrDID( class DelegateOptions( - val delegateType: DelegateType = Secp256k1VerificationKey2018, + val delegateType: DelegateType = DelegateType.Secp256k1VerificationKey2018, val expiresIn: Long = 86400L ) - suspend fun lookupOwner(cache: Boolean = true): String { if (cache && this.owner != null) return this.owner val encodedCall = EthereumDIDRegistry.IdentityOwner.encode(Solidity.Address(address.hexToBigInteger())) @@ -69,7 +68,7 @@ class EthrDID( return signAndSendContractCall(owner, encodedCall) } - suspend fun revokeDelegate(delegate: String, delegateType: DelegateType = Secp256k1VerificationKey2018): String { + suspend fun revokeDelegate(delegate: String, delegateType: DelegateType = DelegateType.Secp256k1VerificationKey2018): String { val owner = this.lookupOwner() val encodedCall = EthereumDIDRegistry.RevokeDelegate.encode( Solidity.Address(this.address.hexToBigInteger()), diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDDocument.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDDocument.kt index 2678c71b..e845bc00 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDDocument.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDDocument.kt @@ -1,11 +1,13 @@ package me.uport.sdk.ethrdid -import android.support.annotation.Keep import kotlinx.serialization.Optional import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JSON +import me.uport.sdk.universaldid.AuthenticationEntry import me.uport.sdk.universaldid.DIDDocument +import me.uport.sdk.universaldid.PublicKeyEntry +import me.uport.sdk.universaldid.ServiceEntry /** * Classes to describe the DID document corresponding to a particular ethr-did @@ -14,20 +16,20 @@ import me.uport.sdk.universaldid.DIDDocument @Serializable data class EthrDIDDocument( @SerialName("id") - val id: String, + override val id: String, @SerialName("publicKey") - val publicKey: List = emptyList(), + override val publicKey: List = emptyList(), @SerialName("authentication") - val authentication: List = emptyList(), + override val authentication: List = emptyList(), @Optional @SerialName("service") - val service: List = emptyList(), + override val service: List = emptyList(), @SerialName("@context") - val context: String = "https://w3id.org/did/v1" + override val context: String = "https://w3id.org/did/v1" ) : DIDDocument { override fun toString(): String = JSON.indented.stringify(this) @@ -36,55 +38,4 @@ data class EthrDIDDocument( } } -@Serializable -data class PublicKeyEntry( - @SerialName("id") - val id: String, - - @SerialName("type") - val type: DelegateType, - - @SerialName("owner") - val owner: String, - - @Optional - @SerialName("ethereumAddress") - val ethereumAddress: String? = null, - - @Optional - @SerialName("publicKeyHex") - val publicKeyHex: String? = null, - - @Optional - @SerialName("publicKeyBase64") - val publicKeyBase64: String? = null, - - @Optional - @SerialName("publicKeyBase58") - val publicKeyBase58: String? = null, - - @Optional - @SerialName("value") - val value: String? = null -) - -@Serializable -data class AuthenticationEntry( - val type: DelegateType, - val publicKey: String -) - -@Serializable -data class ServiceEntry( - val type : String, - val serviceEndpoint: String -) - -@Keep -enum class DelegateType { - Secp256k1VerificationKey2018, - Secp256k1SignatureAuthentication2018, - Ed25519VerificationKey2018, - RsaVerificationKey2018, -} diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt index 01a5c3bf..babbf132 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt @@ -7,15 +7,13 @@ import kotlinx.coroutines.experimental.GlobalScope import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.withContext import me.uport.sdk.core.* -import me.uport.sdk.ethrdid.DelegateType.Secp256k1SignatureAuthentication2018 -import me.uport.sdk.ethrdid.DelegateType.Secp256k1VerificationKey2018 import me.uport.sdk.ethrdid.EthereumDIDRegistry.Events.DIDAttributeChanged import me.uport.sdk.ethrdid.EthereumDIDRegistry.Events.DIDDelegateChanged import me.uport.sdk.jsonrpc.JsonRPC import me.uport.sdk.jsonrpc.JsonRpcBaseResponse import me.uport.sdk.jsonrpc.experimental.ethCall import me.uport.sdk.jsonrpc.experimental.getLogs -import me.uport.sdk.universaldid.DIDResolver +import me.uport.sdk.universaldid.* import org.kethereum.encodings.encodeToBase58String import org.kethereum.extensions.hexToBigInteger import org.kethereum.extensions.toHexStringNoPrefix @@ -38,6 +36,7 @@ class EthrDIDResolver( //if it can be normalized, then it matches either an ethereum address or a full ethr-did return normalizeDid(potentialDID).isNotBlank() } + /** * Resolves a given ethereum address or DID string into a corresponding [EthrDIDDocument] */ @@ -135,7 +134,7 @@ class EthrDIDResolver( val pkEntries = mapOf().toMutableMap().apply { put("owner", PublicKeyEntry( id = "$normalizedDid#owner", - type = Secp256k1VerificationKey2018, + type = DelegateType.Secp256k1VerificationKey2018, owner = normalizedDid, ethereumAddress = owner )) @@ -143,7 +142,7 @@ class EthrDIDResolver( } val authEntries = mapOf().toMutableMap().apply { put("owner", AuthenticationEntry( - type = Secp256k1SignatureAuthentication2018, + type = DelegateType.Secp256k1SignatureAuthentication2018, publicKey = "$normalizedDid#owner" )) } @@ -163,15 +162,15 @@ class EthrDIDResolver( delegateCount++ when (delegateType) { - Secp256k1SignatureAuthentication2018.name, + DelegateType.Secp256k1SignatureAuthentication2018.name, sigAuth -> authEntries[key] = AuthenticationEntry( - type = Secp256k1SignatureAuthentication2018, + type = DelegateType.Secp256k1SignatureAuthentication2018, publicKey = "$normalizedDid#delegate-$delegateCount") - Secp256k1VerificationKey2018.name, + DelegateType.Secp256k1VerificationKey2018.name, veriKey -> pkEntries[key] = PublicKeyEntry( id = "$normalizedDid#delegate-$delegateCount", - type = Secp256k1VerificationKey2018, + type = DelegateType.Secp256k1VerificationKey2018, owner = normalizedDid, ethereumAddress = delegate) } diff --git a/universal-did/build.gradle b/universal-did/build.gradle index 29212abf..4ef38c82 100644 --- a/universal-did/build.gradle +++ b/universal-did/build.gradle @@ -39,6 +39,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + api project(":serialization") + implementation "com.android.support:support-annotations:$support_lib_version" testImplementation "junit:junit:$junit_version" diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/DIDDocument.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/DIDDocument.kt new file mode 100644 index 00000000..ed91d448 --- /dev/null +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/DIDDocument.kt @@ -0,0 +1,72 @@ +package me.uport.sdk.universaldid + +import android.support.annotation.Keep +import kotlinx.serialization.Optional +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +/** + * Abstraction for DID documents + */ +interface DIDDocument { + val context: String? + val id: String? + val publicKey: List + val authentication: List + val service: List +} + + +@Serializable +data class PublicKeyEntry( + @SerialName("id") + val id: String, + + @SerialName("type") + val type: DelegateType, + + @SerialName("owner") + val owner: String, + + @Optional + @SerialName("ethereumAddress") + val ethereumAddress: String? = null, + + @Optional + @SerialName("publicKeyHex") + val publicKeyHex: String? = null, + + @Optional + @SerialName("publicKeyBase64") + val publicKeyBase64: String? = null, + + @Optional + @SerialName("publicKeyBase58") + val publicKeyBase58: String? = null, + + @Optional + @SerialName("value") + val value: String? = null +) + +@Serializable +data class AuthenticationEntry( + val type: DelegateType, + val publicKey: String +) + +@Serializable +data class ServiceEntry( + val type: String, + val serviceEndpoint: String +) + +@Keep +enum class DelegateType { + Secp256k1VerificationKey2018, + Secp256k1SignatureAuthentication2018, + Ed25519VerificationKey2018, + RsaVerificationKey2018, + Curve25519EncryptionPublicKey, // encryption key. Usage described here: https://github.com/uport-project/specs/blob/develop/pki/diddocument.md +} \ No newline at end of file diff --git a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt index 195e1123..95c830ac 100644 --- a/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt +++ b/universal-did/src/main/java/me/uport/sdk/universaldid/UniversalDID.kt @@ -2,8 +2,8 @@ package me.uport.sdk.universaldid import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PRIVATE -import java.lang.IllegalArgumentException -import java.lang.IllegalStateException +import me.uport.sdk.universaldid.UniversalDID.method +import me.uport.sdk.universaldid.UniversalDID.registerResolver /** * A class to abstract resolving Decentralized Identity (DID) documents @@ -15,7 +15,6 @@ import java.lang.IllegalStateException */ object UniversalDID : DIDResolver { - private val resolvers = mapOf().toMutableMap() /** @@ -65,7 +64,8 @@ object UniversalDID : DIDResolver { } //no else clause, carry on if (resolvers.containsKey(method)) { - return resolvers[method]?.resolve(did) ?: throw IllegalStateException("There DIDResolver for '$method' failed to resolve '$did' for an unknown reason.") + return resolvers[method]?.resolve(did) + ?: throw IllegalStateException("There DIDResolver for '$method' failed to resolve '$did' for an unknown reason.") } else { throw IllegalStateException("There is no DIDResolver registered to resolve '$method' DIDs and none of the other ${resolvers.size} registered ones can do it.") } @@ -103,11 +103,4 @@ interface DIDResolver { * Check if the [potentialDID] can be resolved by this resolver. */ fun canResolve(potentialDID: String): Boolean -} - -/** - * Abstraction for DID documents - */ -interface DIDDocument { - } \ No newline at end of file diff --git a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt index 3b200a8c..1648f801 100644 --- a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt +++ b/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt @@ -1,9 +1,7 @@ package me.uport.sdk.ethrdid import kotlinx.coroutines.experimental.runBlocking -import me.uport.sdk.universaldid.DIDDocument -import me.uport.sdk.universaldid.DIDResolver -import me.uport.sdk.universaldid.UniversalDID +import me.uport.sdk.universaldid.* import org.junit.Assert.* import org.junit.Test import java.lang.IllegalArgumentException @@ -11,7 +9,11 @@ import java.lang.IllegalArgumentException class UniversalDIDTest { private val testDDO = object : DIDDocument { - val unusedField = "test document" + override val context: String = "test context" + override val id: String = "1234" + override val publicKey: List = emptyList() + override val authentication: List = emptyList() + override val service: List = emptyList() } private val testResolver = object : DIDResolver { @@ -29,7 +31,10 @@ class UniversalDIDTest { fun `blank resolves to error`() = runBlocking { UniversalDID.clearResolvers() - val unusedDdo = UniversalDID.resolve("") + val ddo = UniversalDID.resolve("") + + //should never be reached, expecting exception above + assertNull(ddo) } @Test(expected = Exception::class) @@ -37,7 +42,10 @@ class UniversalDIDTest { UniversalDID.clearResolvers() UniversalDID.registerResolver(testResolver) - val unusedDdo = UniversalDID.resolve("") + val ddo = UniversalDID.resolve("") + + //should never be reached, expecting exception above + assertNull(ddo) } @Test diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt deleted file mode 100644 index b728f4bf..00000000 --- a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDDocument.kt +++ /dev/null @@ -1,55 +0,0 @@ -package me.uport.sdk.uportdid - -import com.squareup.moshi.Json -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import me.uport.sdk.universaldid.DIDDocument - -/** - * A class that encapsulates the DID document - * - * See [identity_document spec](https://github.com/uport-project/specs/blob/develop/pki/identitydocument.md) - * - */ -data class UportDIDDocument( - @Json(name = "@context") - val context: String?, //ex: "http://schema.org" - - @Json(name = "@type") - val type: String, //ex: "Organization", "Person" - - @Json(name = "publicKey") - val publicKey: String, //ex: "0x04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab062" - - @Json(name = "publicEncKey") - val publicEncKey: String?, //ex: "0x04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab062" - - @Json(name = "image") - val image: ProfilePicture?, //ex: {"@type":"ImageObject","name":"avatar","contentUrl":"/ipfs/QmSCnmXC91Arz2gj934Ce4DeR7d9fULWRepjzGMX6SSazB"} - - @Json(name = "name") - val name: String?, //ex: "uPort @ Devcon3" , "Vitalik Buterout" - - @Json(name = "description") - val description: String? // ex: "uPort Attestation" -) : DIDDocument { - companion object { - fun fromJson(json: String): UportDIDDocument? { - val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() - val adapter = moshi.adapter(UportDIDDocument::class.java) - - return adapter.fromJson(json) - } - } -} - -class ProfilePicture( - @Json(name = "@type") - val type: String? = "ImageObject", - - @Json(name = "name") - val name: String? = "avatar", - - @Json(name = "contentUrl") - val contentUrl: String? = "" -) \ No newline at end of file diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt index cf3049f3..54409d5d 100644 --- a/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/UportDIDResolver.kt @@ -2,8 +2,6 @@ package me.uport.sdk.uportdid import android.os.Handler import android.os.Looper -import android.support.annotation.VisibleForTesting -import android.support.annotation.VisibleForTesting.PRIVATE import me.uport.mnid.Account import me.uport.mnid.MNID import me.uport.sdk.core.Networks @@ -32,18 +30,22 @@ class UportDIDResolver : DIDResolver { override val method: String = "uport" override suspend fun resolve(did: String): DIDDocument = suspendCoroutine { continuation -> - val (_, mnid) = parse(did) - getProfileDocument(mnid) { err, ddo -> - if (err != null) { - continuation.resumeWithException(err) - } else { - continuation.resume(ddo) + if (canResolve(did)) { + val (_, mnid) = parseDIDString(did) + getProfileDocument(mnid) { err, ddo -> + if (err != null) { + continuation.resumeWithException(err) + } else { + continuation.resume(ddo.convertToDIDDocument(did)) + } } + } else { + continuation.resumeWithException(java.lang.IllegalArgumentException("The DID('$did') cannot be resolved by the uPort DID resolver")) } } override fun canResolve(potentialDID: String): Boolean { - val (method, mnid) = parse(potentialDID) + val (method, mnid) = parseDIDString(potentialDID) return if (method == this.method) { MNID.isMNID(mnid) } else { @@ -51,13 +53,6 @@ class UportDIDResolver : DIDResolver { } } - @VisibleForTesting(otherwise = PRIVATE) - internal fun parse(did: String): Pair { - val matchResult = uportDIDPattern.find(did) ?: return ("" to did) - val (_, method, mnid) = matchResult.destructured - return (method to mnid) - } - /** * Given an MNID, calls the uport registry and returns the raw json */ @@ -117,22 +112,22 @@ class UportDIDResolver : DIDResolver { } /** - * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [UportDIDDocument] object + * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [UportIdentityDocument] object * * Should return `null` if anything goes wrong */ - internal fun getProfileDocumentSync(mnid: String): UportDIDDocument? { + internal fun getProfileDocumentSync(mnid: String): UportIdentityDocument? { val rawJsonDDO = getJsonProfileSync(mnid) - return UportDIDDocument.fromJson(rawJsonDDO) + return UportIdentityDocument.fromJson(rawJsonDDO) } /** - * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [UportDIDDocument] object + * Given an [mnid], obtains the JSON encoded DID doc then tries to convert it to a [UportIdentityDocument] object * * TODO: Should [callback] with non-`null` error if anything goes wrong */ - fun getProfileDocument(mnid: String, callback: (err: Exception?, ddo: UportDIDDocument) -> Unit) { + fun getProfileDocument(mnid: String, callback: (err: Exception?, ddo: UportIdentityDocument) -> Unit) { Thread { //safe to call networks @@ -149,6 +144,12 @@ class UportDIDResolver : DIDResolver { companion object { //language=RegExp private val uportDIDPattern = "^(did:(uport):)?([1-9A-HJ-NP-Za-km-z]{34,38})(.*)".toRegex() + + internal fun parseDIDString(did: String): Pair { + val matchResult = uportDIDPattern.find(did) ?: return ("" to did) + val (_, method, mnid) = matchResult.destructured + return (method to mnid) + } } } diff --git a/uport-did/src/main/java/me/uport/sdk/uportdid/UportIdentityDocument.kt b/uport-did/src/main/java/me/uport/sdk/uportdid/UportIdentityDocument.kt new file mode 100644 index 00000000..762bf8b2 --- /dev/null +++ b/uport-did/src/main/java/me/uport/sdk/uportdid/UportIdentityDocument.kt @@ -0,0 +1,116 @@ +package me.uport.sdk.uportdid + +import android.support.annotation.Keep +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import me.uport.sdk.serialization.moshi +import me.uport.sdk.universaldid.* +import me.uport.sdk.uportdid.UportDIDResolver.Companion.parseDIDString +import org.walleth.khex.clean0xPrefix + +/** + * A class that encapsulates the DID document + * + * See [identity_document spec](https://github.com/uport-project/specs/blob/develop/pki/identitydocument.md) + * + */ +data class UportIdentityDocument( + @Json(name = "@context") + val context: String?, //ex: "http://schema.org" + + @Json(name = "@type") + val type: String, //ex: "Organization", "Person" + + @Json(name = "publicKey") + val publicKey: String?, //ex: "0x04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab062" + + @Json(name = "publicEncKey") + val publicEncKey: String?, //ex: "0x04613bb3a4874d27032618f020614c21cbe4c4e4781687525f6674089f9bd3d6c7f6eb13569053d31715a3ba32e0b791b97922af6387f087d6b5548c06944ab062" + + @Json(name = "image") + val image: ProfilePicture?, //ex: {"@type":"ImageObject","name":"avatar","contentUrl":"/ipfs/QmSCnmXC91Arz2gj934Ce4DeR7d9fULWRepjzGMX6SSazB"} + + @Json(name = "name") + val name: String?, //ex: "uPort @ Devcon3" , "Vitalik Buterout" + + @Json(name = "description") + val description: String? // ex: "uPort Attestation" +) { + + fun convertToDIDDocument(did: String): DIDDocument { + + val normalizedDid = normalizeDID(did) + + val publicVerificationKey = PublicKeyEntry( + id = "$normalizedDid#keys-1", + type = DelegateType.Secp256k1VerificationKey2018, + owner = normalizedDid, + publicKeyHex = this.publicKey?.clean0xPrefix() + ) + val authEntries = listOf(AuthenticationEntry( + type = DelegateType.Secp256k1SignatureAuthentication2018, + publicKey = "$normalizedDid#keys-1") + ) + + val pkEntries = listOf(publicVerificationKey).toMutableList() + + if (publicEncKey != null) { + pkEntries.add(PublicKeyEntry( + id = "$normalizedDid#keys-2", + type = DelegateType.Curve25519EncryptionPublicKey, + owner = normalizedDid, + publicKeyBase64 = publicEncKey) + ) + } + + return UportDIDDocument( + context = "https://w3id.org/did/v1", + id = normalizedDid, + publicKey = pkEntries, + authentication = authEntries, + uportProfile = copy( + context = null, + publicEncKey = null, + publicKey = null + )) + } + + private fun normalizeDID(did: String): String { + val (_, mnid) = parseDIDString(did) + return "did:uport:$mnid" + } + + fun toJson(): String = jsonAdapter.toJson(this) + + companion object { + private val jsonAdapter: JsonAdapter by lazy { + moshi.adapter(UportIdentityDocument::class.java) + } + + fun fromJson(json: String): UportIdentityDocument? = jsonAdapter.fromJson(json) + } +} + +class ProfilePicture( + @Json(name = "@type") + val type: String? = "ImageObject", + + @Json(name = "name") + val name: String? = "avatar", + + @Json(name = "contentUrl") + val contentUrl: String? = "" +) + +@Keep +data class UportDIDDocument( + override val id: String, + override val publicKey: List, + override val authentication: List, + override val service: List = emptyList(), + + @Json(name = "@context") + override val context: String = "https://w3id.org/did/v1", + + val uportProfile: UportIdentityDocument +) : DIDDocument \ No newline at end of file diff --git a/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt b/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt index b9a4158e..34b6b4b9 100644 --- a/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt +++ b/uport-did/src/test/java/me/uport/sdk/uportdid/UportDIDResolverTest.kt @@ -2,8 +2,11 @@ package me.uport.sdk.uportdid import me.uport.mnid.Account import me.uport.sdk.jsonrpc.EthCall +import me.uport.sdk.universaldid.DelegateType +import me.uport.sdk.universaldid.PublicKeyEntry import org.junit.Assert.* import org.junit.Test +import org.walleth.khex.clean0xPrefix class UportDIDResolverTest { @@ -38,10 +41,42 @@ class UportDIDResolverTest { assertEquals(expectedDocAddress, docAddressHex) } + @Test + fun `can convert legacy DDO`() { + val publicKeyHex = "0x04e8989d1826cd6258906cfaa71126e2db675eaef47ddeb9310ee10db69b339ab960649e1934dc1e1eac1a193a94bd7dc5542befc5f7339845265ea839b9cbe56f" + val publicEncKey = "k8q5G4YoIMP7zvqMC9q84i7xUBins6dXGt8g5H007F0=" + val legacyDDO = UportIdentityDocument( + context = "http://schema.org", + type = "Person", + publicKey = publicKeyHex, + publicEncKey = publicEncKey, + description = null, + image = ProfilePicture(), + name = null + ) + + val convertedDDO = legacyDDO.convertToDIDDocument("2ozs2ntCXceKkAQKX4c9xp2zPS8pvkJhVqC") + + val expectedOwner = "did:uport:2ozs2ntCXceKkAQKX4c9xp2zPS8pvkJhVqC" + assertTrue(convertedDDO.id == expectedOwner) + assertNotNull(convertedDDO.publicKey.find { it -> + it.id.startsWith(expectedOwner) && + it.owner == expectedOwner && + it.type == DelegateType.Secp256k1VerificationKey2018 && + it.publicKeyHex == publicKeyHex.clean0xPrefix() + }) + assertNotNull(convertedDDO.publicKey.find { it -> + it.id.startsWith(expectedOwner) && + it.owner == expectedOwner && + it.type == DelegateType.Curve25519EncryptionPublicKey && + it.publicKeyBase64 == publicEncKey + }) + } + @Test fun getJsonDIDSync() { - val expectedDDO = UportDIDDocument( + val expectedDDO = UportIdentityDocument( context = "http://schema.org", type = "Person", publicKey = "0x04e8989d1826cd6258906cfaa71126e2db675eaef47ddeb9310ee10db69b339ab960649e1934dc1e1eac1a193a94bd7dc5542befc5f7339845265ea839b9cbe56f", From f84e9d4572d42885db0ba467cf89b11495862d76 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 16 Oct 2018 17:44:35 +0300 Subject: [PATCH 37/75] update the readme with some info regarding universal did resolving --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 74d1ac95..4c3fe677 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Identity for your Android dApps. Many intended features are still missing, and the ones already present are under heavy development. Expect breaking changes!** -Issues are being tracket at https://www.pivotaltracker.com/n/projects/2198688 +Issues are being tracked at https://www.pivotaltracker.com/n/projects/2198688 ### Installation @@ -129,6 +129,22 @@ val receipt = Networks.rinkeby.awaitConfirmation(txHash) ``` +#### off-chain interaction + +Off-chain interaction is essentially signing and verifying JWTs using uport-specific JWT algorithms. +Verification of such tokens implies resolving a +[Decentralized Identity (DID) document](https://github.com/uport-project/specs/blob/develop/pki/diddocument.md) +that will contain the keys or address that should match a JWT signature. + +To obtain a `DIDDocument` one needs to use a `DIDResolver`. +`UniversalDID` is a global registry of `DIDResolver`s for apps using the SDK. +During SDK initialization this registry gets populated with resolvers for +[uport-did](https://github.com/uport-project/uport-did-resolver) and [ethr-did](https://github.com/uport-project/ethr-did-resolver) + +``` +//TODO: Details about how to use these docs for verification of JWTs to be added when +JWT verification functionality is finalized +``` ### Dependencies @@ -142,6 +158,10 @@ but that may be removed when pure kotlin implementations of the required cryptog ### Changelog +* 0.3.x - upcoming release with breaking changes + * add universal DID resolver + * add cleaner way of creating JWTs with abstracted signer + * 0.2.2 * update of dependencies for coroutines and build tools From 83c1c581d6338b758179a07ee5b806a39ab8a0a8 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 17 Oct 2018 10:53:09 +0300 Subject: [PATCH 38/75] create scaffold for `credentials` module --- credentials/build.gradle | 58 +++++++++++++++++++ credentials/src/main/AndroidManifest.xml | 5 ++ .../me/uport/sdk/credentials/Credentials.kt | 10 ++++ .../uport/sdk/credentials/CredentialsTest.kt | 13 +++++ settings.gradle | 9 +-- 5 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 credentials/build.gradle create mode 100644 credentials/src/main/AndroidManifest.xml create mode 100644 credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt create mode 100644 credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt diff --git a/credentials/build.gradle b/credentials/build.gradle new file mode 100644 index 00000000..5daa9b41 --- /dev/null +++ b/credentials/build.gradle @@ -0,0 +1,58 @@ +apply plugin: "com.android.library" +apply plugin: "kotlin-android" +apply plugin: "kotlinx-serialization" +apply plugin: "maven" + +//apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle' + +android { + compileSdkVersion compile_sdk_version + buildToolsVersion build_tools_version + + + defaultConfig { + minSdkVersion min_sdk_version + targetSdkVersion target_sdk_version + versionCode 1 + versionName "1.0" + + multiDexEnabled true + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + dexOptions { + jumboMode true + } + +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + + api project(":serialization") + api project(":jwt") + api project(":universal-did") + api project(":signer") + + implementation "com.android.support:support-annotations:$support_lib_version" + + testImplementation "junit:junit:$junit_version" + testImplementation "org.mockito:mockito-inline:$mockito_version" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin_version" +} + +kotlin { + experimental { + coroutines "enable" + } +} \ No newline at end of file diff --git a/credentials/src/main/AndroidManifest.xml b/credentials/src/main/AndroidManifest.xml new file mode 100644 index 00000000..75552dbe --- /dev/null +++ b/credentials/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt new file mode 100644 index 00000000..6eb057ed --- /dev/null +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -0,0 +1,10 @@ +package me.uport.sdk.credentials + +import com.uport.sdk.signer.Signer + +class Credentials( + private val did: String, + private val signer: Signer +) { + +} \ No newline at end of file diff --git a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt new file mode 100644 index 00000000..caeac679 --- /dev/null +++ b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt @@ -0,0 +1,13 @@ +package me.uport.sdk.credentials + +import org.junit.Assert.assertTrue +import org.junit.Test + +class CredentialsTest { + + @Test + fun `setup works`() { + assertTrue(true) + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7a8f55ef..8f3f53f2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,12 +1,13 @@ include ':demoapp' +include ':serialization' +include ':core' include ':signer' +include ':jsonrpc' +include ':jwt' include ':uport-did' include ':ethr-did' include ':universal-did' +include ':credentials' include ':identity' include ':sdk' -include ':jsonrpc' -include ':core' -include ':serialization' -include ':jwt' include ':fuelingservice' From 9eba4fb7dc8bf730d9161c81bb55e21850ceb406 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 17 Oct 2018 11:02:56 +0300 Subject: [PATCH 39/75] supply a clock to the Credentials object --- .../java/me/uport/sdk/core/ITimeProvider.kt | 2 +- .../java/me/uport/sdk/core/TimeProviderTest.kt | 2 +- .../me/uport/sdk/credentials/Credentials.kt | 18 +++++++++++++++++- jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/me/uport/sdk/core/ITimeProvider.kt b/core/src/main/java/me/uport/sdk/core/ITimeProvider.kt index 58691a9d..a629750d 100644 --- a/core/src/main/java/me/uport/sdk/core/ITimeProvider.kt +++ b/core/src/main/java/me/uport/sdk/core/ITimeProvider.kt @@ -17,6 +17,6 @@ interface ITimeProvider { /** * Default time provider */ -class SystemTimeProvider : ITimeProvider { +object SystemTimeProvider : ITimeProvider { override fun now() = System.currentTimeMillis() } \ No newline at end of file diff --git a/core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt b/core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt index 3e542d55..bcb60aa0 100644 --- a/core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt +++ b/core/src/test/java/me/uport/sdk/core/TimeProviderTest.kt @@ -8,7 +8,7 @@ class TimeProviderTest { @Test fun `default provider is close to current time`() { val systemTime = System.currentTimeMillis() - val defaultProvider = SystemTimeProvider() + val defaultProvider = SystemTimeProvider //some systems have tens of milliseconds as the lowest granularity assertTrue(defaultProvider.now() - systemTime < 100L) } diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt index 6eb057ed..09281502 100644 --- a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -1,10 +1,26 @@ package me.uport.sdk.credentials import com.uport.sdk.signer.Signer +import me.uport.sdk.core.ITimeProvider +import me.uport.sdk.core.SystemTimeProvider +/** + * The [Credentials] class should allow you to create the signed payloads used in uPort including + * verifiable claims and signed mobile app requests (ex. selective disclosure requests + * for user data). It should also provide signature verification over signed payloads. + */ class Credentials( private val did: String, - private val signer: Signer + private val signer: Signer, + private val clock: ITimeProvider = SystemTimeProvider ) { + @Suppress("EnumEntryName") + enum class RequestType { + shareReq, + shareResp, + verReq, + ethtx + } + } \ No newline at end of file diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index cdd8da64..1f297e00 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -38,7 +38,7 @@ import kotlin.experimental.and * the [timeProvider] defaults to [SystemTimeProvider] but you can configure it for testing or for "was valid at" scenarios */ class JWTTools( - private val timeProvider: ITimeProvider = SystemTimeProvider() + private val timeProvider: ITimeProvider = SystemTimeProvider ) { private val notEmpty: (String) -> Boolean = { !it.isEmpty() } From 4ce823f6cd8aa0f546c66593018abba7e4e9ef6e Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 17 Oct 2018 14:04:29 +0300 Subject: [PATCH 40/75] add a method to normalize a potential DID string to a known format (ethr-did or uport-did) --- .../me/uport/sdk/credentials/Credentials.kt | 34 +++++++++++++++++++ .../uport/sdk/credentials/CredentialsTest.kt | 30 ++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt index 09281502..cb46cb81 100644 --- a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -1,8 +1,13 @@ package me.uport.sdk.credentials +import android.support.annotation.VisibleForTesting +import android.support.annotation.VisibleForTesting.PRIVATE import com.uport.sdk.signer.Signer +import me.uport.mnid.MNID import me.uport.sdk.core.ITimeProvider import me.uport.sdk.core.SystemTimeProvider +import me.uport.sdk.jwt.JWTTools +import me.uport.sdk.jwt.model.JwtHeader /** * The [Credentials] class should allow you to create the signed payloads used in uPort including @@ -23,4 +28,33 @@ class Credentials( ethtx } + companion object { + + /** + * Attempts to normalize a [potentialDID] to a known format. + * + * This will transform an ethereum address into an ethr-did and an MNID string into a uport-did + */ + @VisibleForTesting(otherwise = PRIVATE) + internal fun normalizeKnownDID(potentialDID: String): String { + + //ignore if it's already a did + if (potentialDID.matches("^did:(.*)?:.*".toRegex())) + return potentialDID + + //match an ethereum address + "^(0[xX])*([0-9a-fA-F]{40})".toRegex().find(potentialDID)?.let { + val (_, hexDigits) = it.destructured + return "did:ethr:0x$hexDigits" + } + + //match an MNID + if (MNID.isMNID(potentialDID)) { + return "did:uport:$potentialDID" + } + + return potentialDID + } + } + } \ No newline at end of file diff --git a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt index caeac679..eaf82b9d 100644 --- a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt +++ b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt @@ -1,5 +1,6 @@ package me.uport.sdk.credentials +import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -10,4 +11,33 @@ class CredentialsTest { assertTrue(true) } + @Test + fun `can normalize a known string format to a DID format`() { + val transformations = mapOf( + //already did + "did:example:something something" to "did:example:something something", + "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74" to "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", + "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74#keys-1" to "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74#keys-1", + "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX" to "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX#owner" to "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX#owner", + + //eth addr to ethrdid + "0xf3beac30c498d9e26865f34fcaa57dbb935b0d74" to "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", + "0XF3BEAC30c498d9e26865f34fcaa57dbb935b0d74" to "did:ethr:0xF3BEAC30c498d9e26865f34fcaa57dbb935b0d74", + "f3beac30c498d9e26865f34fcaa57dbb935b0d74" to "did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", + + //mnid to uport did + "2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX" to "did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", + "5A8bRWU3F7j3REx3vkJWxdjQPp4tqmxFPmab1Tr" to "did:uport:5A8bRWU3F7j3REx3vkJWxdjQPp4tqmxFPmab1Tr", + + //unknown is left intact + "0x1234" to "0x1234", + "2nQtiQG6Cgm1GYTBaaK" to "2nQtiQG6Cgm1GYTBaaK" + ) + + transformations.forEach {(orig, expected) -> + assertEquals(expected, Credentials.normalizeKnownDID(orig)) + } + } + } \ No newline at end of file From 1209d69278ac5d4ad4a5ec93cc17b2628eead75b Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 17 Oct 2018 18:31:28 +0300 Subject: [PATCH 41/75] add a method to wrap a payload into a signed JWT using an algorithm based on the DID type --- .../me/uport/sdk/credentials/Credentials.kt | 10 +++++++ .../uport/sdk/credentials/CredentialsTest.kt | 30 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt index cb46cb81..a8cdfd90 100644 --- a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -20,6 +20,7 @@ class Credentials( private val clock: ITimeProvider = SystemTimeProvider ) { + @Suppress("EnumEntryName") enum class RequestType { shareReq, @@ -28,6 +29,15 @@ class Credentials( ethtx } + /** + * Creates a JWT using the given [payload] + */ + suspend fun signJWT(payload: Map, expiresInSeconds: Long = 300L): String { + val normDID = normalizeKnownDID(this.did) + val alg = if (normDID.startsWith("did:uport:")) JwtHeader.ES256K else JwtHeader.ES256K_R + return JWTTools(clock).createJWT(payload, normDID, this.signer, expiresInSeconds = expiresInSeconds, algorithm = alg) + } + companion object { /** diff --git a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt index eaf82b9d..7b436228 100644 --- a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt +++ b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt @@ -1,5 +1,10 @@ package me.uport.sdk.credentials +import com.uport.sdk.signer.KPSigner +import kotlinx.coroutines.experimental.runBlocking +import me.uport.sdk.jwt.JWTTools +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K +import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K_R import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -35,9 +40,32 @@ class CredentialsTest { "2nQtiQG6Cgm1GYTBaaK" to "2nQtiQG6Cgm1GYTBaaK" ) - transformations.forEach {(orig, expected) -> + transformations.forEach { (orig, expected) -> assertEquals(expected, Credentials.normalizeKnownDID(orig)) } } + @Test + fun `signJWT uses the correct algorithm for uport did`() = runBlocking { + + val cred = Credentials("did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", KPSigner("0x1234")) + val jwt = cred.signJWT(emptyMap()) + + val (header, payload, signature) = JWTTools().decode(jwt) + assertEquals(ES256K, header.alg) + + } + + @Test + fun `signJWT uses the correct algorithm for non-uport did`() = runBlocking { + + val cred = Credentials("0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", KPSigner("0x1234")) + val jwt = cred.signJWT(emptyMap()) + + val (header, payload, signature) = JWTTools().decode(jwt) + assertEquals(ES256K_R, header.alg) + + } + + } \ No newline at end of file From 91a6ea4aeb413559f5a4e1386d3a4c794aa474a4 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 17 Oct 2018 19:58:37 +0300 Subject: [PATCH 42/75] add enum with known types of JWT that the `credentials` module should be able to handle --- .../me/uport/sdk/credentials/Credentials.kt | 28 ++++++++++++++++++- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 3 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt index a8cdfd90..ce60a101 100644 --- a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -1,5 +1,6 @@ package me.uport.sdk.credentials +import android.support.annotation.Keep import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PRIVATE import com.uport.sdk.signer.Signer @@ -20,12 +21,37 @@ class Credentials( private val clock: ITimeProvider = SystemTimeProvider ) { - + /** + * Supported (known) types of requests/responses + */ + @Keep @Suppress("EnumEntryName") enum class RequestType { + /** + * a selective disclosure request + * See also: https://github.com/uport-project/specs/blob/develop/messages/sharereq.md + */ shareReq, + + /** + * a selective disclosure response + * See also: https://github.com/uport-project/specs/blob/develop/messages/shareresp.md + */ shareResp, + + /** + * See also: https://github.com/uport-project/specs/blob/develop/messages/verificationreq.md + */ verReq, + + /** + * See also: https://github.com/uport-project/specs/blob/develop/messages/signtypeddata.md + */ + eip712Req, + + /** + * See also: https://github.com/uport-project/specs/blob/develop/messages/tx.md + */ ethtx } diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index 1f297e00..f640869a 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -48,7 +48,7 @@ class JWTTools( * * The issuerDID is NOT checked for format, nor for a match with the signer. */ - suspend fun createJWT(payload: Map, issuerDID: String, signer: Signer, expiresInSeconds: Long = 300, algorithm: String = ES256K_R): String { + suspend fun createJWT(payload: Map, issuerDID: String, signer: Signer, expiresInSeconds: Long = DEFAULT_JWT_VALIDITY_SECONDS, algorithm: String = ES256K_R): String { val mapAdapter = moshi.mapAdapter(String::class.java, Any::class.java) val mutablePayload = payload.toMutableMap() @@ -294,6 +294,7 @@ class JWTTools( companion object { //Create adapters with each object val jwtPayloadAdapter: JsonAdapter by lazy { moshi.adapter(JwtPayload::class.java) } + const val DEFAULT_JWT_VALIDITY_SECONDS = 300L } } From 0b3fbd38a1d48dd40b796f72bf327c53216160b3 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 17 Oct 2018 20:00:34 +0300 Subject: [PATCH 43/75] add failing tests for scenarios regarding selective disclosure --- .../java/me/uport/sdk/credentials/CredentialsTest.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt index 7b436228..3e750cc9 100644 --- a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt +++ b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt @@ -67,5 +67,14 @@ class CredentialsTest { } + @Test + fun `selective disclosure payload is filtered to specific fields`() { + TODO("implement this scenario. the payload that actually gets signed should include the following fields if they are present in the parameters: requested, networkId(net), accountType(act), expiresIn(exp=iat+expiresIn), verified, callbackUrl(callback), notifications") + } + + @Test + fun `selective disclosure request contains required fields`() { + TODO("implement this scenario. The processed payload has to have: iss, iat, type=shareReq") + } } \ No newline at end of file From 601b512a554378290edab3fac6248448e187ae0c Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Thu, 18 Oct 2018 11:46:06 +0300 Subject: [PATCH 44/75] describe parameters used for creating a selective disclosure request. --- .../me/uport/sdk/credentials/Credentials.kt | 98 ++++++++++++++++++- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 21 +++- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt index ce60a101..4ec81fce 100644 --- a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -8,6 +8,7 @@ import me.uport.mnid.MNID import me.uport.sdk.core.ITimeProvider import me.uport.sdk.core.SystemTimeProvider import me.uport.sdk.jwt.JWTTools +import me.uport.sdk.jwt.JWTTools.Companion.DEFAULT_JWT_VALIDITY_SECONDS import me.uport.sdk.jwt.model.JwtHeader /** @@ -55,10 +56,101 @@ class Credentials( ethtx } + @Keep + @Suppress("EnumEntryName") + enum class RequestAccountType { + general, + segregated, + keypair, + devicekey, + none + } + /** - * Creates a JWT using the given [payload] + * A class that encapsulates the supported parameter types for creating a SelectiveDisclosureRequest. + * + * A selective disclosure request is just a JWT with some required fields + * (described [here](https://github.com/uport-project/specs/blob/develop/messages/sharereq.md) ) + * The parameters in this class get encoded in the JWT that represents such a request. + * + * While the JWT can be manually constructed, this class is provided for discoverability + * and ease of use of frequently used params. */ - suspend fun signJWT(payload: Map, expiresInSeconds: Long = 300L): String { + class SelectiveDisclosureRequestParams( + /** + * [**required**] + * a list of attributes for which you are requesting credentials. + * Ex. [ 'name', 'country' ] + */ + val requested: List, + + /** + * [**optional**] + * A list of signed claims being requested. + * This is semantically similar to the [requested] field + * but the response should contain signatures as well. + */ + val verified: List?, + + /** + * [**required**] + * the url that can receive the response to this request. + * TODO: detail how that URL should be handled by the APP implementing this SDK + * + * This gets encoded as `callback` in the JWT payload + */ + val callbackUrl: String, + + /** + * [**optional**] + * The Ethereum network ID if it is relevant for this request. + * + * This gets encoded as `net` in the JWT payload + */ + val networkId: String?, + + + /** + * [**optional**] + * If this request implies a particular kind of account. This defaults to [RequestAccountType.none] + * + * This gets encoded as `act` in the JWT payload + * + * @see [RequestAccountType] + */ + val accountType: RequestAccountType? = RequestAccountType.none, + + /** + * [**optional**] defaults to [DEFAULT_SHARE_REQ_VALIDITY_SECONDS] + * The validity interval of this request, measured in seconds since the moment it is issued. + */ + val expiresInSeconds: Long? = DEFAULT_SHARE_REQ_VALIDITY_SECONDS, + + + //omitting the "notifications" permission because it has no relevance on android. + // It may be worth adding for direct interop with iOS but that is unclear now + + /** + * [**optional**] + * This can hold extra fields for the JWT payload representing the request. + * Use this to provide any of the extra fields described in the + * [specs](https://github.com/uport-project/specs/blob/develop/messages/sharereq.md) + * + * The fields contained in [extras] will get overwritten by the named parameters + * in this class in case of a name collision. + */ + val extras: Map? + ) + + /** + * Creates a JWT using the given [payload], issued and signed using the [did] and [signer] + * fields of this [Credentials] instance. + * + * @param payload a map detailing the payload of the resulting JWT + * @param expiresInSeconds _optional_ number of seconds of validity of the JWT. This parameter + * is ignored if the [payload] already contains an `exp` field + */ + suspend fun signJWT(payload: Map, expiresInSeconds: Long = DEFAULT_JWT_VALIDITY_SECONDS): String { val normDID = normalizeKnownDID(this.did) val alg = if (normDID.startsWith("did:uport:")) JwtHeader.ES256K else JwtHeader.ES256K_R return JWTTools(clock).createJWT(payload, normDID, this.signer, expiresInSeconds = expiresInSeconds, algorithm = alg) @@ -91,6 +183,8 @@ class Credentials( return potentialDID } + + private const val DEFAULT_SHARE_REQ_VALIDITY_SECONDS = 600L } } \ No newline at end of file diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index f640869a..fc2a9a79 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -46,7 +46,20 @@ class JWTTools( * This coroutine method creates a signed JWT from a [payload] Map and an abstracted [Signer] * You're also supposed to pass the [issuerDID] and can configure the algorithm used and expiry time * - * The issuerDID is NOT checked for format, nor for a match with the signer. + * @param payload a map containing the fields forming the payload of this JWT + * @param issuerDID a DID string that will be set as the `iss` field in the JWT payload. + * The signature produced by the signer should correspond to this DID. + * If the `iss` field is already part of the [payload], that will get overwritten. + * **The [issuerDID] is NOT checked for format, nor for a match with the signer.** + * @param signer a [Signer] that will produce the signature section of this JWT. + * The signature should correspond to the [issuerDID]. + * @param expiresInSeconds number of seconds of validity of this JWT. You may omit this param if + * an `exp` timestamp is already part of the [payload]. + * If there is no `exp` field in the payload and the param is not specified, + * it defaults to [DEFAULT_JWT_VALIDITY_SECONDS] + * @param algorithm defaults to `ES256K-R`. The signing algorithm for this JWT. + * Supported types are `ES256K` for uport DID and `ES256K-R` for ethr-did and the rest + * */ suspend fun createJWT(payload: Map, issuerDID: String, signer: Signer, expiresInSeconds: Long = DEFAULT_JWT_VALIDITY_SECONDS, algorithm: String = ES256K_R): String { val mapAdapter = moshi.mapAdapter(String::class.java, Any::class.java) @@ -59,7 +72,7 @@ class JWTTools( val expSeconds = iatSeconds + expiresInSeconds mutablePayload["iat"] = iatSeconds - mutablePayload["exp"] = expSeconds + mutablePayload["exp"] = payload["exp"] ?: expSeconds mutablePayload["iss"] = issuerDID @Suppress("SimplifiableCallChain", "ConvertCallChainIntoSequence") @@ -294,6 +307,10 @@ class JWTTools( companion object { //Create adapters with each object val jwtPayloadAdapter: JsonAdapter by lazy { moshi.adapter(JwtPayload::class.java) } + + /** + * 5 minutes. The default number of seconds of validity of a JWT, in case no other interval is specified. + */ const val DEFAULT_JWT_VALIDITY_SECONDS = 300L } From 61f173116c38922113534487a68a8677f13f06c0 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Thu, 18 Oct 2018 12:49:39 +0300 Subject: [PATCH 45/75] move SelectiveDisclosureRequestParams and RequestType to separate files --- .../me/uport/sdk/credentials/Credentials.kt | 123 ----------------- .../me/uport/sdk/credentials/RequestType.kt | 37 +++++ .../SelectiveDisclosureRequestParams.kt | 126 ++++++++++++++++++ .../uport/sdk/credentials/CredentialsTest.kt | 29 +++- 4 files changed, 190 insertions(+), 125 deletions(-) create mode 100644 credentials/src/main/java/me/uport/sdk/credentials/RequestType.kt create mode 100644 credentials/src/main/java/me/uport/sdk/credentials/SelectiveDisclosureRequestParams.kt diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt index 4ec81fce..cb26d538 100644 --- a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -1,6 +1,5 @@ package me.uport.sdk.credentials -import android.support.annotation.Keep import android.support.annotation.VisibleForTesting import android.support.annotation.VisibleForTesting.PRIVATE import com.uport.sdk.signer.Signer @@ -22,126 +21,6 @@ class Credentials( private val clock: ITimeProvider = SystemTimeProvider ) { - /** - * Supported (known) types of requests/responses - */ - @Keep - @Suppress("EnumEntryName") - enum class RequestType { - /** - * a selective disclosure request - * See also: https://github.com/uport-project/specs/blob/develop/messages/sharereq.md - */ - shareReq, - - /** - * a selective disclosure response - * See also: https://github.com/uport-project/specs/blob/develop/messages/shareresp.md - */ - shareResp, - - /** - * See also: https://github.com/uport-project/specs/blob/develop/messages/verificationreq.md - */ - verReq, - - /** - * See also: https://github.com/uport-project/specs/blob/develop/messages/signtypeddata.md - */ - eip712Req, - - /** - * See also: https://github.com/uport-project/specs/blob/develop/messages/tx.md - */ - ethtx - } - - @Keep - @Suppress("EnumEntryName") - enum class RequestAccountType { - general, - segregated, - keypair, - devicekey, - none - } - - /** - * A class that encapsulates the supported parameter types for creating a SelectiveDisclosureRequest. - * - * A selective disclosure request is just a JWT with some required fields - * (described [here](https://github.com/uport-project/specs/blob/develop/messages/sharereq.md) ) - * The parameters in this class get encoded in the JWT that represents such a request. - * - * While the JWT can be manually constructed, this class is provided for discoverability - * and ease of use of frequently used params. - */ - class SelectiveDisclosureRequestParams( - /** - * [**required**] - * a list of attributes for which you are requesting credentials. - * Ex. [ 'name', 'country' ] - */ - val requested: List, - - /** - * [**optional**] - * A list of signed claims being requested. - * This is semantically similar to the [requested] field - * but the response should contain signatures as well. - */ - val verified: List?, - - /** - * [**required**] - * the url that can receive the response to this request. - * TODO: detail how that URL should be handled by the APP implementing this SDK - * - * This gets encoded as `callback` in the JWT payload - */ - val callbackUrl: String, - - /** - * [**optional**] - * The Ethereum network ID if it is relevant for this request. - * - * This gets encoded as `net` in the JWT payload - */ - val networkId: String?, - - - /** - * [**optional**] - * If this request implies a particular kind of account. This defaults to [RequestAccountType.none] - * - * This gets encoded as `act` in the JWT payload - * - * @see [RequestAccountType] - */ - val accountType: RequestAccountType? = RequestAccountType.none, - - /** - * [**optional**] defaults to [DEFAULT_SHARE_REQ_VALIDITY_SECONDS] - * The validity interval of this request, measured in seconds since the moment it is issued. - */ - val expiresInSeconds: Long? = DEFAULT_SHARE_REQ_VALIDITY_SECONDS, - - - //omitting the "notifications" permission because it has no relevance on android. - // It may be worth adding for direct interop with iOS but that is unclear now - - /** - * [**optional**] - * This can hold extra fields for the JWT payload representing the request. - * Use this to provide any of the extra fields described in the - * [specs](https://github.com/uport-project/specs/blob/develop/messages/sharereq.md) - * - * The fields contained in [extras] will get overwritten by the named parameters - * in this class in case of a name collision. - */ - val extras: Map? - ) - /** * Creates a JWT using the given [payload], issued and signed using the [did] and [signer] * fields of this [Credentials] instance. @@ -183,8 +62,6 @@ class Credentials( return potentialDID } - - private const val DEFAULT_SHARE_REQ_VALIDITY_SECONDS = 600L } } \ No newline at end of file diff --git a/credentials/src/main/java/me/uport/sdk/credentials/RequestType.kt b/credentials/src/main/java/me/uport/sdk/credentials/RequestType.kt new file mode 100644 index 00000000..d3f5522e --- /dev/null +++ b/credentials/src/main/java/me/uport/sdk/credentials/RequestType.kt @@ -0,0 +1,37 @@ +package me.uport.sdk.credentials + +import android.support.annotation.Keep + +/** + * Supported (known) types of JWT requests/responses + */ +@Keep +@Suppress("EnumEntryName") +enum class RequestType { + /** + * a selective disclosure request + * See also: https://github.com/uport-project/specs/blob/develop/messages/sharereq.md + */ + shareReq, + + /** + * a selective disclosure response + * See also: https://github.com/uport-project/specs/blob/develop/messages/shareresp.md + */ + shareResp, + + /** + * See also: https://github.com/uport-project/specs/blob/develop/messages/verificationreq.md + */ + verReq, + + /** + * See also: https://github.com/uport-project/specs/blob/develop/messages/signtypeddata.md + */ + eip712Req, + + /** + * See also: https://github.com/uport-project/specs/blob/develop/messages/tx.md + */ + ethtx +} \ No newline at end of file diff --git a/credentials/src/main/java/me/uport/sdk/credentials/SelectiveDisclosureRequestParams.kt b/credentials/src/main/java/me/uport/sdk/credentials/SelectiveDisclosureRequestParams.kt new file mode 100644 index 00000000..34289c3a --- /dev/null +++ b/credentials/src/main/java/me/uport/sdk/credentials/SelectiveDisclosureRequestParams.kt @@ -0,0 +1,126 @@ +package me.uport.sdk.credentials + +import android.support.annotation.Keep + +/** + * A class that encapsulates the supported parameter types for creating a SelectiveDisclosureRequest. + * + * A selective disclosure request is just a JWT with some required fields + * (described [here](https://github.com/uport-project/specs/blob/develop/messages/sharereq.md) ) + * The parameters in this class get encoded in the JWT that represents such a request. + * + * While the JWT can be manually constructed, this class is provided for discoverability + * and ease of use of frequently used params. + */ +class SelectiveDisclosureRequestParams( + /** + * [**required**] + * a list of attributes for which you are requesting credentials. + * Ex. [ 'name', 'country' ] + */ + val requested: List, + + /** + * [**required**] + * the url that can receive the response to this request. + * TODO: detail how that URL should be handled by the APP implementing this SDK + * + * This gets encoded as `callback` in the JWT payload + */ + val callbackUrl: String, + + /** + * [**optional**] + * A list of signed claims being requested. + * This is semantically similar to the [requested] field + * but the response should contain signatures as well. + */ + val verified: List? = null, + + /** + * [**optional**] + * The Ethereum network ID if it is relevant for this request. + * + * This gets encoded as `net` in the JWT payload + */ + val networkId: String? = null, + + + /** + * [**optional**] + * If this request implies a particular kind of account. + * This defaults to [RequestAccountType.general] (user choice) + * + * This gets encoded as `act` in the JWT payload + * + * @see [RequestAccountType] + */ + val accountType: RequestAccountType? = RequestAccountType.general, + + /** + * [**optional**] + * A list of signed claims about the issuer, usually signed by 3rd parties. + */ + val vc: List? = null, + + /** + * [**optional**] defaults to [DEFAULT_SHARE_REQ_VALIDITY_SECONDS] + * The validity interval of this request, measured in seconds since the moment it is issued. + */ + val expiresInSeconds: Long? = DEFAULT_SHARE_REQ_VALIDITY_SECONDS, + + + //omitting the "notifications" permission because it has no relevance on android. + // It may be worth adding for direct interop with iOS but that is unclear now + + /** + * [**optional**] + * This can hold extra fields for the JWT payload representing the request. + * Use this to provide any of the extra fields described in the + * [specs](https://github.com/uport-project/specs/blob/develop/messages/sharereq.md) + * + * The fields contained in [extras] will get overwritten by the named parameters + * in this class in case of a name collision. + */ + val extras: Map? = null +) + +/** + * Ethereum account type that can be requested by a dApp during selective disclosure: + * + * * [general] users choice (default) + * * [segregated] a unique smart contract based account will be created for requesting app + * * [keypair] a unique keypair based account will be created for requesting app + * * [devicekey] request a new device key for a + * [Private Chain Account](https://github.com/uport-project/specs/blob/develop/messages/privatechain.md) + * * [none] no account is returned + */ +@Keep +@Suppress("EnumEntryName") +enum class RequestAccountType { + general, + segregated, + keypair, + devicekey, + none +} + +const val DEFAULT_SHARE_REQ_VALIDITY_SECONDS = 600L + +/** + * Converts a [SelectiveDisclosureRequestParams] object into a map with the required fields for JWT payload + */ +internal fun buildPayloadForShareReq(params: SelectiveDisclosureRequestParams): MutableMap { + val payload = params.extras.orEmpty().toMutableMap() + + payload["callback"] = params.callbackUrl + payload["requested"] = params.requested + params.verified?.let { payload["verified"] = it } + params.vc?.let { payload["vc"] = it } + params.networkId?.let { payload["net"] = it } + params.accountType?.let { payload["act"] = it.name } + + payload["type"] = RequestType.shareReq.name + + return payload +} \ No newline at end of file diff --git a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt index 3e750cc9..61185678 100644 --- a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt +++ b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt @@ -2,6 +2,7 @@ package me.uport.sdk.credentials import com.uport.sdk.signer.KPSigner import kotlinx.coroutines.experimental.runBlocking +import me.uport.sdk.core.SystemTimeProvider import me.uport.sdk.jwt.JWTTools import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K import me.uport.sdk.jwt.model.JwtHeader.Companion.ES256K_R @@ -73,8 +74,32 @@ class CredentialsTest { } @Test - fun `selective disclosure request contains required fields`() { - TODO("implement this scenario. The processed payload has to have: iss, iat, type=shareReq") + fun `selective disclosure payload contains relevant fields`() = runBlocking { + + val params = SelectiveDisclosureRequestParams( + requested = listOf("name", "country"), + callbackUrl = "myapp://get-back-to-me-with-response.url", + verified = listOf("email"), + networkId = "0x4", + accountType = RequestAccountType.keypair, + vc = emptyList(), + expiresInSeconds = 1234L, + extras = mapOf( + "hello" to "world", + "type" to "expect this to be overwritten" + ) + ) + + val load = buildPayloadForShareReq(params) + + assertTrue((load["requested"] as List<*>).containsAll(listOf("name", "country"))) + assertTrue((load["verified"] as List<*>).containsAll(listOf("email"))) + assertEquals("myapp://get-back-to-me-with-response.url", load["callback"]) + assertEquals("0x4", load["net"]) + assertEquals("keypair", load["act"]) + assertTrue((load["vc"] as List<*>).isEmpty()) + assertEquals("world", load["hello"]) + assertEquals("shareReq", load["type"]) } } \ No newline at end of file From 2ddda0ea4cf09f0ca2fd179d5e66ee75b22d14c1 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Thu, 18 Oct 2018 12:51:21 +0300 Subject: [PATCH 46/75] implement createDisclosureRequest() method --- .../me/uport/sdk/credentials/Credentials.kt | 20 +++++++++++++++++++ .../uport/sdk/credentials/CredentialsTest.kt | 12 +++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt index cb26d538..b0982973 100644 --- a/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt +++ b/credentials/src/main/java/me/uport/sdk/credentials/Credentials.kt @@ -21,6 +21,26 @@ class Credentials( private val clock: ITimeProvider = SystemTimeProvider ) { + /** + * Creates a [Selective Disclosure Request JWT](https://github.com/uport-project/specs/blob/develop/messages/sharereq.md) + * + * Example: + * ``` + * val reqParams = SelectiveDisclosureRequestParams( + * requested = listOf("name", "country"), + * callbackUrl = "https://myserver.com" + * ) + * val jwt = credentials.createDisclosureRequest(reqParams) + * + * // ... send jwt to the relevant party and expect a callback with the response at https://myserver.com + * + * ``` + */ + suspend fun createDisclosureRequest(params: SelectiveDisclosureRequestParams): String { + val payload = buildPayloadForShareReq(params) + return this.signJWT(payload, params.expiresInSeconds ?: DEFAULT_SHARE_REQ_VALIDITY_SECONDS) + } + /** * Creates a JWT using the given [payload], issued and signed using the [did] and [signer] * fields of this [Credentials] instance. diff --git a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt index 61185678..7ad06314 100644 --- a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt +++ b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt @@ -69,8 +69,16 @@ class CredentialsTest { } @Test - fun `selective disclosure payload is filtered to specific fields`() { - TODO("implement this scenario. the payload that actually gets signed should include the following fields if they are present in the parameters: requested, networkId(net), accountType(act), expiresIn(exp=iat+expiresIn), verified, callbackUrl(callback), notifications") + fun `selective disclosure request contains required fields`() = runBlocking { + val now = Math.floor(SystemTimeProvider.now() / 1000.0).toLong() + val cred = Credentials("did:example:issuer", KPSigner("0x1234")) + + val jwt = cred.createDisclosureRequest(SelectiveDisclosureRequestParams(emptyList(), "")) + val (_, payload, _) = JWTTools().decode(jwt) + + assertEquals("did:example:issuer", payload.iss) + assertTrue("payload.iat(${payload.iat}) should be greater than currentTime ($now)", payload.iat!! >= now) + assertEquals(RequestType.shareReq.name, payload.type) } @Test From 08055571eb4d521d7bb3568e755de0329df7d17a Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Fri, 19 Oct 2018 14:31:10 +0300 Subject: [PATCH 47/75] add transport module --- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 1 + settings.gradle | 1 + transport/build.gradle | 53 +++++++++++++++++++ transport/src/main/AndroidManifest.xml | 5 ++ .../me/uport/sdk/transport/IntentSender.kt | 8 +++ .../uport/sdk/transport/IntentSenderTest.kt | 14 +++++ 6 files changed, 82 insertions(+) create mode 100644 transport/build.gradle create mode 100644 transport/src/main/AndroidManifest.xml create mode 100644 transport/src/main/java/me/uport/sdk/transport/IntentSender.kt create mode 100644 transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index 453905e3..0b1573aa 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -86,6 +86,7 @@ class JWTTools( return listOf(signingInput, signature).joinToString(".") } + @Deprecated("This method has been deprecated in favor of `createJWT` because it is too coupled to the UportHDSigner mechanics", ReplaceWith("createJWT()")) fun create(context: Context, payload: JwtPayload, rootHandle: String, derivationPath: String, prompt: String = "", recoverable: Boolean = false, callback: (err: Exception?, encodedJWT: String?) -> Unit) { //create header and convert the parts to json strings val header = if (!recoverable) { diff --git a/settings.gradle b/settings.gradle index 8f3f53f2..0eef6209 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,7 @@ include ':uport-did' include ':ethr-did' include ':universal-did' include ':credentials' +include ':transport' include ':identity' include ':sdk' include ':fuelingservice' diff --git a/transport/build.gradle b/transport/build.gradle new file mode 100644 index 00000000..29212abf --- /dev/null +++ b/transport/build.gradle @@ -0,0 +1,53 @@ +apply plugin: "com.android.library" +apply plugin: "kotlin-android" +apply plugin: "kotlinx-serialization" +apply plugin: "maven" + +//apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle' + +android { + compileSdkVersion compile_sdk_version + buildToolsVersion build_tools_version + + + defaultConfig { + minSdkVersion min_sdk_version + targetSdkVersion target_sdk_version + versionCode 1 + versionName "1.0" + + multiDexEnabled true + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + dexOptions { + jumboMode true + } + +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + + implementation "com.android.support:support-annotations:$support_lib_version" + + testImplementation "junit:junit:$junit_version" + testImplementation "org.mockito:mockito-inline:$mockito_version" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin_version" +} + +kotlin { + experimental { + coroutines "enable" + } +} \ No newline at end of file diff --git a/transport/src/main/AndroidManifest.xml b/transport/src/main/AndroidManifest.xml new file mode 100644 index 00000000..24d32e8e --- /dev/null +++ b/transport/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt b/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt new file mode 100644 index 00000000..49fab65e --- /dev/null +++ b/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt @@ -0,0 +1,8 @@ +package me.uport.sdk.transport + +import android.support.annotation.VisibleForTesting +import android.support.annotation.VisibleForTesting.PRIVATE + +class IntentSender { + +} \ No newline at end of file diff --git a/transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt b/transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt new file mode 100644 index 00000000..0310a768 --- /dev/null +++ b/transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt @@ -0,0 +1,14 @@ +package me.uport.sdk.transport + +import kotlinx.coroutines.experimental.runBlocking +import org.junit.Assert.* +import org.junit.Test + +class IntentSenderTest { + + @Test + fun `setup is ok`() = runBlocking { + assertTrue(true) + } + +} \ No newline at end of file From 7e16f412b5a891799292fd949220bf440bc99026 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Mon, 22 Oct 2018 14:46:48 +0300 Subject: [PATCH 48/75] add scaffold for intentSender testing --- transport/build.gradle | 11 +++----- transport/src/androidTest/AndroidManifest.xml | 8 ++++++ .../sdk/transport/IntentSenderAndroidTest.kt | 25 +++++++++++++++++++ .../UniversalDIDTest.kt | 2 +- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 transport/src/androidTest/AndroidManifest.xml create mode 100644 transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt rename universal-did/src/test/java/me/uport/sdk/{ethrdid => universaldid}/UniversalDIDTest.kt (98%) diff --git a/transport/build.gradle b/transport/build.gradle index 29212abf..27552c7f 100644 --- a/transport/build.gradle +++ b/transport/build.gradle @@ -16,8 +16,6 @@ android { versionCode 1 versionName "1.0" - multiDexEnabled true - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -29,21 +27,20 @@ android { } } - dexOptions { - jumboMode true - } - } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "com.android.support:support-annotations:$support_lib_version" testImplementation "junit:junit:$junit_version" testImplementation "org.mockito:mockito-inline:$mockito_version" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin_version" + + androidTestImplementation "com.android.support.test:runner:$test_runner_version" + androidTestImplementation "com.android.support.test:rules:$test_rules_version" + androidTestImplementation "com.android.support.test.espresso:espresso-intents:$espresso_version" } kotlin { diff --git a/transport/src/androidTest/AndroidManifest.xml b/transport/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..6705e3bc --- /dev/null +++ b/transport/src/androidTest/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt b/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt new file mode 100644 index 00000000..1cbad611 --- /dev/null +++ b/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt @@ -0,0 +1,25 @@ +package me.uport.sdk.transport + +import android.app.Activity +import android.support.test.espresso.intent.rule.IntentsTestRule +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test + + +class IntentSenderAndroidTest { + + //only used for testing + class DummyActivity : Activity() + + + @JvmField + @Rule + var intentsTestRule = IntentsTestRule(DummyActivity::class.java) + + @Test + fun setup_is_ok() { + assertTrue(true) + } + +} \ No newline at end of file diff --git a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt b/universal-did/src/test/java/me/uport/sdk/universaldid/UniversalDIDTest.kt similarity index 98% rename from universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt rename to universal-did/src/test/java/me/uport/sdk/universaldid/UniversalDIDTest.kt index 1648f801..7f3159ca 100644 --- a/universal-did/src/test/java/me/uport/sdk/ethrdid/UniversalDIDTest.kt +++ b/universal-did/src/test/java/me/uport/sdk/universaldid/UniversalDIDTest.kt @@ -1,4 +1,4 @@ -package me.uport.sdk.ethrdid +package me.uport.sdk.universaldid import kotlinx.coroutines.experimental.runBlocking import me.uport.sdk.universaldid.* From 549385778f09b999a764853783b448777cd14ebf Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Mon, 22 Oct 2018 15:22:49 +0300 Subject: [PATCH 49/75] call a generic `send` method to broadcast URL intent --- .../java/me/uport/sdk/transport/IntentSenderAndroidTest.kt | 6 ++++++ .../src/main/java/me/uport/sdk/transport/IntentSender.kt | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt b/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt index 1cbad611..94b6224b 100644 --- a/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt +++ b/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt @@ -22,4 +22,10 @@ class IntentSenderAndroidTest { assertTrue(true) } + @Test + fun can_call_share_req() { + val jwt = "dummy jwt bundle" + IntentSender().send(jwt) + } + } \ No newline at end of file diff --git a/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt b/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt index 49fab65e..a2b306bd 100644 --- a/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt +++ b/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt @@ -5,4 +5,8 @@ import android.support.annotation.VisibleForTesting.PRIVATE class IntentSender { + fun send(jwt: String) { + TODO("unimplemented. this should broadcast a URL intent with the JWT") + } + } \ No newline at end of file From 29a839228a6ebce781574c3111b0b2c1f793247d Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Mon, 22 Oct 2018 20:24:52 +0300 Subject: [PATCH 50/75] bump dependencies and adapt methods to use the new shape of the kethereum API --- build.gradle | 12 ++--- .../uport/sdk/credentials/CredentialsTest.kt | 4 +- .../main/java/me/uport/sdk/jwt/JWTTools.kt | 7 +-- jwt/src/test/java/JWTToolsJVMTest.kt | 11 ++-- signer/build.gradle | 1 + .../com/uport/sdk/signer/HDSignerTests.kt | 11 ++-- .../java/com/uport/sdk/signer/SignerTests.kt | 15 +++--- .../sdk/signer/UserInteractionContextTests.kt | 5 +- .../java/com/uport/sdk/signer/Extensions.kt | 6 +-- .../java/com/uport/sdk/signer/KPSigner.kt | 14 +++--- .../com/uport/sdk/signer/UportHDSigner.kt | 42 ++++++++-------- .../java/com/uport/sdk/signer/UportSigner.kt | 26 ++++++---- .../sdk/signer/crypto/bip32/DerivationTest.kt | 12 +++-- .../signer/crypto/bip32/ExtendedKeyTest.kt | 42 ++++++++-------- .../sdk/signer/crypto/bip39/MnemonicTest.kt | 50 ++++++++++--------- 15 files changed, 136 insertions(+), 122 deletions(-) diff --git a/build.gradle b/build.gradle index 853f3daf..03de766a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,9 @@ buildscript { kotlin_version = '1.2.71' kotlin_serialization_version = '0.6.2' - coroutines_version = "0.26.1" + coroutines_version = "0.30.2" - android_tools_version = '3.3.0-alpha13' + android_tools_version = '3.3.0-beta01' build_tools_version = "28.0.3" @@ -18,19 +18,19 @@ buildscript { test_runner_version = "1.0.2" test_rules_version = test_runner_version - support_lib_version = "27.1.1" + support_lib_version = "28.0.0" play_services_version = "15.0.0" espresso_version = "3.0.2" junit_version = "4.12" - mockito_version = "2.19.0" - mockito_kotlin_version = "2.0.0-RC1" + mockito_version = "2.23.0" + mockito_kotlin_version = "2.0.0-RC3" moshi_version = "1.7.0" okhttp_version = "3.10.0" bivrost_version = "1c0efeb7f3"//"v0.6.2" kmnid_version = "0.2.1" - kethereum_version = "0.53" + kethereum_version = "0.63" khex_version = "0.5" uport_sdk_version = "0.2.2" diff --git a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt index 7ad06314..66e82e44 100644 --- a/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt +++ b/credentials/src/test/java/me/uport/sdk/credentials/CredentialsTest.kt @@ -52,7 +52,7 @@ class CredentialsTest { val cred = Credentials("did:uport:2nQtiQG6Cgm1GYTBaaKAgr76uY7iSexUkqX", KPSigner("0x1234")) val jwt = cred.signJWT(emptyMap()) - val (header, payload, signature) = JWTTools().decode(jwt) + val (header, _, _) = JWTTools().decode(jwt) assertEquals(ES256K, header.alg) } @@ -63,7 +63,7 @@ class CredentialsTest { val cred = Credentials("0xf3beac30c498d9e26865f34fcaa57dbb935b0d74", KPSigner("0x1234")) val jwt = cred.signJWT(emptyMap()) - val (header, payload, signature) = JWTTools().decode(jwt) + val (header, _, _) = JWTTools().decode(jwt) assertEquals(ES256K_R, header.alg) } diff --git a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt index fc2a9a79..453905e3 100644 --- a/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt +++ b/jwt/src/main/java/me/uport/sdk/jwt/JWTTools.kt @@ -17,7 +17,8 @@ import me.uport.sdk.jwt.model.JwtPayload import me.uport.sdk.serialization.mapAdapter import me.uport.sdk.serialization.moshi import org.kethereum.crypto.CURVE -import org.kethereum.crypto.getAddress +import org.kethereum.crypto.model.PublicKey +import org.kethereum.crypto.toAddress import org.kethereum.encodings.decodeBase58 import org.kethereum.extensions.toBytesPadded import org.kethereum.extensions.toHexStringZeroPadded @@ -154,14 +155,14 @@ class JWTTools( val pubKeyNoPrefix = recoveredPubKey .toBytesPadded(65) .copyOfRange(1, 65) - val recoveredAddress = getAddress(pubKeyNoPrefix).toNoPrefixHexString() + val recoveredAddress = PublicKey(pubKeyNoPrefix).toAddress().cleanHex val numMatches = ddo.publicKey.map { val pk = it.publicKeyHex?.hexToByteArray() ?: it.publicKeyBase64?.decodeBase64() ?: it.publicKeyBase58?.decodeBase58() ?: byteArrayOf() - (it.ethereumAddress ?: getAddress(pk).toHexString()).clean0xPrefix() + (it.ethereumAddress?.clean0xPrefix() ?: PublicKey(pk).toAddress().cleanHex) }.filter { it == recoveredAddress }.size diff --git a/jwt/src/test/java/JWTToolsJVMTest.kt b/jwt/src/test/java/JWTToolsJVMTest.kt index 7619552b..5e370fe2 100644 --- a/jwt/src/test/java/JWTToolsJVMTest.kt +++ b/jwt/src/test/java/JWTToolsJVMTest.kt @@ -7,6 +7,7 @@ import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit class JWTToolsJVMTest { @@ -23,16 +24,16 @@ class JWTToolsJVMTest { @Test fun verify() { - val latch = CountDownLatch(tokens.size) - tokens.forEach { - JWTTools().verify(it) { err, payload -> - assertNull(err) + tokens.forEachIndexed { index, token -> + val latch = CountDownLatch(tokens.size) + JWTTools().verify(token) { err, payload -> + assertNull("token $index failed verification", err) println(payload) latch.countDown() } + latch.await(15, TimeUnit.SECONDS) } - latch.await() } @Suppress("UNUSED_VARIABLE") diff --git a/signer/build.gradle b/signer/build.gradle index 985473f9..40e9b8a4 100644 --- a/signer/build.gradle +++ b/signer/build.gradle @@ -44,6 +44,7 @@ dependencies { api "com.github.walleth.kethereum:crypto:$kethereum_version" api "com.github.walleth.kethereum:bip39:$kethereum_version" + api "com.github.walleth.kethereum:bip39_wordlist_en:$kethereum_version" api project(":core") androidTestImplementation "com.android.support.test:runner:$test_runner_version" diff --git a/signer/src/androidTest/java/com/uport/sdk/signer/HDSignerTests.kt b/signer/src/androidTest/java/com/uport/sdk/signer/HDSignerTests.kt index cd311edd..63d26517 100644 --- a/signer/src/androidTest/java/com/uport/sdk/signer/HDSignerTests.kt +++ b/signer/src/androidTest/java/com/uport/sdk/signer/HDSignerTests.kt @@ -15,8 +15,9 @@ import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.kethereum.bip32.generateKey -import org.kethereum.bip39.Mnemonic +import org.kethereum.bip32.toKey +import org.kethereum.bip39.model.MnemonicWords +import org.kethereum.bip39.toSeed import org.kethereum.extensions.hexToBigInteger import org.spongycastle.jce.provider.BouncyCastleProvider import java.security.Security @@ -87,7 +88,7 @@ class HDSignerTests { @Test fun testJwtComponents() { - val referenceSeed = Mnemonic.mnemonicToSeed("vessel ladder alter error federal sibling chat ability sun glass valve picture") + val referenceSeed = MnemonicWords("vessel ladder alter error federal sibling chat ability sun glass valve picture").toSeed() val referencePayload = "Hello, world!".toByteArray() val referencePrivateKey = "65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960".hexToBigInteger() @@ -95,9 +96,9 @@ class HDSignerTests { val referenceR = "6bcd81446183af193ca4a172d5c5c26345903b24770d90b5d790f74a9dec1f68".hexToBigInteger() val referenceS = "e2b85b3c92c9b4f3cf58de46e7997d8efb6e14b2e532d13dfa22ee02f3a43d5d".hexToBigInteger() - val derivedRootExtendedKey = generateKey(referenceSeed, UportHDSigner.UPORT_ROOT_DERIVATION_PATH) + val derivedRootExtendedKey = referenceSeed.toKey(UportHDSigner.UPORT_ROOT_DERIVATION_PATH) - assertEquals(referencePrivateKey, derivedRootExtendedKey.keyPair.privateKey) + assertEquals(referencePrivateKey, derivedRootExtendedKey.keyPair.privateKey.key) val keyPair = derivedRootExtendedKey.keyPair diff --git a/signer/src/androidTest/java/com/uport/sdk/signer/SignerTests.kt b/signer/src/androidTest/java/com/uport/sdk/signer/SignerTests.kt index 21b7ee0b..eec8ad2a 100644 --- a/signer/src/androidTest/java/com/uport/sdk/signer/SignerTests.kt +++ b/signer/src/androidTest/java/com/uport/sdk/signer/SignerTests.kt @@ -11,8 +11,9 @@ import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.kethereum.crypto.ECKeyPair +import org.kethereum.crypto.model.PrivateKey import org.kethereum.crypto.signMessage +import org.kethereum.crypto.toECKeyPair import org.kethereum.extensions.hexToBigInteger import org.spongycastle.util.encoders.Hex import org.walleth.khex.hexToByteArray @@ -125,7 +126,7 @@ class SignerTests { fun testPublicKey1() { val referencePrivateKey = "5047c789919e943c559d8c134091d47b4642122ba0111dfa842ef6edefb48f38" val referencePublicKey = "04bf42759e6d2a684ef64a8210c55bf2308e4101f78959ffa335ff045ef1e4252b1c09710281f8971b39efed7bfb61ae381ed73b9faa5a96f17e00c1a4c32796b1" - val keyPair = ECKeyPair.create(Hex.decode(referencePrivateKey)) + val keyPair = PrivateKey(referencePrivateKey.hexToBigInteger()).toECKeyPair() val pubKeyBytes = keyPair.getUncompressedPublicKeyWithPrefix() val pubKeyHex = pubKeyBytes.toNoPrefixHexString() @@ -159,7 +160,7 @@ class SignerTests { @Test fun testPublicKey2() { - val keypair = ECKeyPair.create(Hex.decode("278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f")) + val keypair = PrivateKey("278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f".hexToBigInteger()).toECKeyPair() val pubKeyBytes = keypair.getUncompressedPublicKeyWithPrefix() val pubKeyEnc = Hex.toHexString(pubKeyBytes) assertEquals("04fdd57adec3d438ea237fe46b33ee1e016eda6b585c3e27ea66686c2ea535847946393f8145252eea68afe67e287b3ed9b31685ba6c3b00060a73b9b1242d68f7", pubKeyEnc) @@ -172,7 +173,7 @@ class SignerTests { val msg = "Hello, world!".toByteArray() - val keyPair = ECKeyPair.create(Hex.decode(referencePrivateKey)) + val keyPair = PrivateKey(referencePrivateKey.hexToBigInteger()).toECKeyPair() val sigData = UportSigner().signJwt(msg, keyPair).getJoseEncoded() @@ -186,7 +187,7 @@ class SignerTests { val msg = "Hello, world!".toByteArray() - val keyPair = ECKeyPair.create(Hex.decode(referencePrivateKey)) + val keyPair = PrivateKey(referencePrivateKey.hexToBigInteger()).toECKeyPair() val sigData = UportSigner().signJwt(msg, keyPair).getDerEncoded() @@ -202,7 +203,7 @@ class SignerTests { val msg = "Hello, world!".toByteArray() - val keyPair = ECKeyPair.create(referencePrivateKey) + val keyPair = PrivateKey(referencePrivateKey).toECKeyPair() val sigData = UportSigner().signJwt(msg, keyPair) @@ -219,7 +220,7 @@ class SignerTests { val rawTransaction = "84CFC6Q7dACDL+/YlJ4gaMziLeTh6A8Vy3HvQ1ogo7N8iA3gtrOnZAAAiQq83vASNFZ4kA==".decodeBase64() - val keyPair = ECKeyPair.create(referencePrivKeyBytes) + val keyPair = PrivateKey(referencePrivKeyBytes).toECKeyPair() val sigData = keyPair.signMessage(rawTransaction) diff --git a/signer/src/androidTest/java/com/uport/sdk/signer/UserInteractionContextTests.kt b/signer/src/androidTest/java/com/uport/sdk/signer/UserInteractionContextTests.kt index 2c5a2693..9f940c2e 100644 --- a/signer/src/androidTest/java/com/uport/sdk/signer/UserInteractionContextTests.kt +++ b/signer/src/androidTest/java/com/uport/sdk/signer/UserInteractionContextTests.kt @@ -10,13 +10,14 @@ import me.uport.sdk.core.toBase64 import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Test -import org.kethereum.bip39.Mnemonic +import org.kethereum.bip39.generateMnemonic +import org.kethereum.bip39.wordlists.WORDLIST_ENGLISH import java.security.SecureRandom import java.util.concurrent.CountDownLatch class UserInteractionContextTests { - private val phrase = Mnemonic.generateMnemonic() + private val phrase = generateMnemonic(wordList = WORDLIST_ENGLISH) private val key = ByteArray(32).apply { SecureRandom().nextBytes(this) } private val context = InstrumentationRegistry.getTargetContext() diff --git a/signer/src/main/java/com/uport/sdk/signer/Extensions.kt b/signer/src/main/java/com/uport/sdk/signer/Extensions.kt index a13d4d45..a99c48fd 100644 --- a/signer/src/main/java/com/uport/sdk/signer/Extensions.kt +++ b/signer/src/main/java/com/uport/sdk/signer/Extensions.kt @@ -5,8 +5,8 @@ import me.uport.sdk.core.decodeBase64 import me.uport.sdk.core.padBase64 import me.uport.sdk.core.toBase64 import me.uport.sdk.core.toBase64UrlSafe -import org.kethereum.crypto.ECKeyPair -import org.kethereum.crypto.PRIVATE_KEY_SIZE +import org.kethereum.crypto.model.ECKeyPair +import org.kethereum.crypto.model.PRIVATE_KEY_SIZE import org.kethereum.extensions.toBytesPadded import org.kethereum.model.SignatureData import org.spongycastle.asn1.ASN1EncodableVector @@ -78,7 +78,7 @@ fun unpackCiphertext(ciphertext: String): List = .map { it.decodeBase64() } fun ECKeyPair.getUncompressedPublicKeyWithPrefix(): ByteArray { - val pubBytes = this.publicKey.toBytesPadded(UportSigner.UNCOMPRESSED_PUBLIC_KEY_SIZE) + val pubBytes = this.publicKey.key.toBytesPadded(UportSigner.UNCOMPRESSED_PUBLIC_KEY_SIZE) pubBytes[0] = 0x04 return pubBytes } diff --git a/signer/src/main/java/com/uport/sdk/signer/KPSigner.kt b/signer/src/main/java/com/uport/sdk/signer/KPSigner.kt index c054ff05..3a7e8ca6 100644 --- a/signer/src/main/java/com/uport/sdk/signer/KPSigner.kt +++ b/signer/src/main/java/com/uport/sdk/signer/KPSigner.kt @@ -1,9 +1,7 @@ package com.uport.sdk.signer -import org.kethereum.crypto.ECKeyPair -import org.kethereum.crypto.getAddress -import org.kethereum.crypto.signMessage -import org.kethereum.crypto.signMessageHash +import org.kethereum.crypto.* +import org.kethereum.crypto.model.PrivateKey import org.kethereum.extensions.hexToBigInteger import org.kethereum.hashes.sha256 import org.kethereum.model.SignatureData @@ -13,11 +11,12 @@ import org.kethereum.model.SignatureData * * There is no special handling of threads for callbacks. */ -class KPSigner(private val privateKey: String) : Signer { +class KPSigner(privateKey: String) : Signer { + + private val keyPair = PrivateKey(privateKey.hexToBigInteger()).toECKeyPair() override fun signJWT(rawPayload: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { try { - val keyPair = ECKeyPair.create(privateKey.hexToBigInteger()) val sigData = signMessageHash(rawPayload.sha256(), keyPair, false) callback(null, sigData) } catch (err: Exception) { @@ -25,12 +24,11 @@ class KPSigner(private val privateKey: String) : Signer { } } - override fun getAddress() = ECKeyPair.create(privateKey.hexToBigInteger()).getAddress() + override fun getAddress() = keyPair.toAddress().hex override fun signETH(rawMessage: ByteArray, callback: (err: Exception?, sigData: SignatureData) -> Unit) { try { - val keyPair = ECKeyPair.create(privateKey.hexToBigInteger()) val sigData = keyPair.signMessage(rawMessage) callback(null, sigData) } catch (ex: Exception) { diff --git a/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt b/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt index f1f5b89b..4b7f8d6e 100644 --- a/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt +++ b/signer/src/main/java/com/uport/sdk/signer/UportHDSigner.kt @@ -6,12 +6,15 @@ import com.uport.sdk.signer.encryption.KeyProtection import me.uport.sdk.core.decodeBase64 import me.uport.sdk.core.padBase64 import me.uport.sdk.core.toBase64 -import org.kethereum.bip32.generateKey -import org.kethereum.bip39.Mnemonic -import org.kethereum.crypto.getAddress +import org.kethereum.bip39.entropyToMnemonic +import org.kethereum.bip39.mnemonicToEntropy +import org.kethereum.bip39.model.MnemonicWords +import org.kethereum.bip39.toKey +import org.kethereum.bip39.validate +import org.kethereum.bip39.wordlists.WORDLIST_ENGLISH import org.kethereum.crypto.signMessage +import org.kethereum.crypto.toAddress import org.kethereum.model.SignatureData -import org.walleth.khex.prepend0xPrefix import java.security.SecureRandom @Suppress("unused", "KDocUnresolvedReference") @@ -41,7 +44,7 @@ class UportHDSigner : UportSigner() { val entropyBuffer = ByteArray(128 / 8) SecureRandom().nextBytes(entropyBuffer) - val seedPhrase = Mnemonic.entropyToMnemonic(entropyBuffer) + val seedPhrase = entropyToMnemonic(entropyBuffer, WORDLIST_ENGLISH) return importHDSeed(context, level, seedPhrase, callback) @@ -60,17 +63,17 @@ class UportHDSigner : UportSigner() { fun importHDSeed(context: Context, level: KeyProtection.Level, phrase: String, callback: (err: Exception?, address: String, pubKey: String) -> Unit) { try { - val seedBuffer = Mnemonic.mnemonicToSeed(phrase) +// val seedBuffer = mnemonicToSeed(phrase) - val entropyBuffer = Mnemonic.mnemonicToEntropy(phrase) + val entropyBuffer = mnemonicToEntropy(phrase, WORDLIST_ENGLISH) - val extendedRootKey = generateKey(seedBuffer, UPORT_ROOT_DERIVATION_PATH) + val extendedRootKey = MnemonicWords(phrase).toKey(UPORT_ROOT_DERIVATION_PATH) val keyPair = extendedRootKey.keyPair val publicKeyBytes = keyPair.getUncompressedPublicKeyWithPrefix() val publicKeyString = publicKeyBytes.toBase64().padBase64() - val address: String = keyPair.getAddress().prepend0xPrefix() + val address: String = keyPair.toAddress().hex val label = asSeedLabel(address) @@ -142,9 +145,8 @@ class UportHDSigner : UportSigner() { try { - val phrase = Mnemonic.entropyToMnemonic(entropyBuff) - val seed = Mnemonic.mnemonicToSeed(phrase) - val extendedKey = generateKey(seed, derivationPath) + val phrase = entropyToMnemonic(entropyBuff, WORDLIST_ENGLISH) + val extendedKey = MnemonicWords(phrase).toKey(derivationPath) val keyPair = extendedKey.keyPair @@ -192,9 +194,8 @@ class UportHDSigner : UportSigner() { } try { - val phrase = Mnemonic.entropyToMnemonic(entropyBuff) - val seed = Mnemonic.mnemonicToSeed(phrase) - val extendedKey = generateKey(seed, derivationPath) + val phrase = entropyToMnemonic(entropyBuff, WORDLIST_ENGLISH) + val extendedKey = MnemonicWords(phrase).toKey(derivationPath) val keyPair = extendedKey.keyPair @@ -230,15 +231,14 @@ class UportHDSigner : UportSigner() { } try { - val phrase = Mnemonic.entropyToMnemonic(entropyBuff) - val seed = Mnemonic.mnemonicToSeed(phrase) - val extendedKey = generateKey(seed, derivationPath) + val phrase = entropyToMnemonic(entropyBuff, WORDLIST_ENGLISH) + val extendedKey = MnemonicWords(phrase).toKey(derivationPath) val keyPair = extendedKey.keyPair val publicKeyBytes = keyPair.getUncompressedPublicKeyWithPrefix() val publicKeyString = publicKeyBytes.toBase64().padBase64() - val address: String = keyPair.getAddress().prepend0xPrefix() + val address: String = keyPair.toAddress().hex return@decrypt callback(null, address, publicKeyString) @@ -271,7 +271,7 @@ class UportHDSigner : UportSigner() { } try { - val phrase = Mnemonic.entropyToMnemonic(entropyBuff) + val phrase = entropyToMnemonic(entropyBuff, WORDLIST_ENGLISH) return@decrypt callback(null, phrase) } catch (exception: Exception) { return@decrypt callback(err, "") @@ -282,7 +282,7 @@ class UportHDSigner : UportSigner() { /** * Verifies if a given phrase is a valid mnemonic phrase usable in seed generation */ - fun validateMnemonic(phrase: String): Boolean = Mnemonic.validateMnemonic(phrase) + fun validateMnemonic(phrase: String): Boolean = MnemonicWords(phrase).validate(WORDLIST_ENGLISH) /** * Returns a list of addresses representing the uport roots used as handles for seeds diff --git a/signer/src/main/java/com/uport/sdk/signer/UportSigner.kt b/signer/src/main/java/com/uport/sdk/signer/UportSigner.kt index 93c93ec5..abf36880 100644 --- a/signer/src/main/java/com/uport/sdk/signer/UportSigner.kt +++ b/signer/src/main/java/com/uport/sdk/signer/UportSigner.kt @@ -12,10 +12,14 @@ import me.uport.sdk.core.decodeBase64 import me.uport.sdk.core.padBase64 import me.uport.sdk.core.toBase64 import org.kethereum.crypto.* +import org.kethereum.crypto.model.ECKeyPair +import org.kethereum.crypto.model.PRIVATE_KEY_SIZE +import org.kethereum.crypto.model.PUBLIC_KEY_SIZE +import org.kethereum.crypto.model.PrivateKey +import org.kethereum.extensions.toBytesPadded import org.kethereum.hashes.sha256 import org.kethereum.model.SignatureData import org.spongycastle.jce.provider.BouncyCastleProvider -import org.walleth.khex.prepend0xPrefix import java.math.BigInteger import java.security.InvalidKeyException import java.security.KeyException @@ -59,8 +63,8 @@ open class UportSigner { fun createKey(context: Context, level: KeyProtection.Level, callback: (err: Exception?, address: String, pubKey: String) -> Unit) { val privateKeyBytes = try { - val (privKey, _) = createEcKeyPair() - privKey.toByteArray() + val (privKey, _) = createEthereumKeyPair() + privKey.key.toBytesPadded(PRIVATE_KEY_SIZE) } catch (exception: Exception) { return callback(KeyException("ERR_CREATING_KEYPAIR", exception), "", "") } @@ -76,11 +80,11 @@ open class UportSigner { */ fun saveKey(context: Context, level: KeyProtection.Level, privateKeyBytes: ByteArray, callback: (err: Exception?, address: String, pubKey: String) -> Unit) { - val keyPair = ECKeyPair.create(privateKeyBytes) + val keyPair = PrivateKey(privateKeyBytes).toECKeyPair() val publicKeyBytes = keyPair.getUncompressedPublicKeyWithPrefix() val publicKeyString = publicKeyBytes.toBase64().padBase64() - val address: String = keyPair.getAddress().prepend0xPrefix() + val address: String = keyPair.toAddress().hex val label = asAddressLabel(address) @@ -137,15 +141,15 @@ open class UportSigner { return callback(storageError, EMPTY_SIGNATURE_DATA) } - encryptionLayer.decrypt(context, prompt, encryptedPrivateKey) { err, privateKey -> + encryptionLayer.decrypt(context, prompt, encryptedPrivateKey) { err, privateKeyBytes -> if (err != null) { return@decrypt callback(err, EMPTY_SIGNATURE_DATA) } try { - val keyPair = ECKeyPair.create(privateKey) - privateKey.fill(0) + val keyPair = PrivateKey(privateKeyBytes).toECKeyPair() + privateKeyBytes.fill(0) val txBytes = txPayload.decodeBase64() @@ -216,16 +220,16 @@ open class UportSigner { return callback(storageError, SignatureData()) } - encryptionLayer.decrypt(context, prompt, encryptedPrivateKey) { err, privateKey -> + encryptionLayer.decrypt(context, prompt, encryptedPrivateKey) { err, privateKeyBytes -> if (err != null) { return@decrypt callback(err, SignatureData()) } try { - val keyPair = ECKeyPair.create(privateKey) + val keyPair = PrivateKey(privateKeyBytes).toECKeyPair() val payloadBytes = data.decodeBase64() val sig = signJwt(payloadBytes, keyPair) - privateKey.fill(0) + privateKeyBytes.fill(0) return@decrypt callback(null, sig) } catch (exception: Exception) { diff --git a/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/DerivationTest.kt b/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/DerivationTest.kt index 403ada7d..dbcac0c6 100644 --- a/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/DerivationTest.kt +++ b/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/DerivationTest.kt @@ -2,8 +2,10 @@ package com.uport.sdk.signer.crypto.bip32 import org.junit.Assert.assertEquals import org.junit.Test -import org.kethereum.bip32.ExtendedKey -import org.kethereum.bip32.generateKey +import org.kethereum.bip32.model.Seed +import org.kethereum.bip32.model.XPriv +import org.kethereum.bip32.toExtendedKey +import org.kethereum.bip32.toKey import org.walleth.khex.hexToByteArray class DerivationTest { @@ -14,7 +16,7 @@ class DerivationTest { testData.forEach { - val derivedKey = generateKey(it.seed.hexToByteArray(), it.path) + val derivedKey = Seed(it.seed.hexToByteArray()).toKey(it.path) val obtainedPub = derivedKey.asPublicOnly().serialize() assertEquals(it.expectedPublicKey, obtainedPub) @@ -22,8 +24,8 @@ class DerivationTest { val obtainedPrv = derivedKey.serialize() assertEquals(it.expectedPrivateKey, obtainedPrv) - assertEquals(ExtendedKey.parse(it.expectedPublicKey), derivedKey.asPublicOnly()) - assertEquals(ExtendedKey.parse(it.expectedPrivateKey), derivedKey) + assertEquals(XPriv(it.expectedPublicKey).toExtendedKey(), derivedKey.asPublicOnly()) + assertEquals(XPriv(it.expectedPrivateKey).toExtendedKey(), derivedKey) counter++ diff --git a/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/ExtendedKeyTest.kt b/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/ExtendedKeyTest.kt index ae21a8cc..c53f81a6 100644 --- a/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/ExtendedKeyTest.kt +++ b/signer/src/test/java/com/uport/sdk/signer/crypto/bip32/ExtendedKeyTest.kt @@ -4,9 +4,13 @@ import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import org.kethereum.bip32.ExtendedKey -import org.kethereum.bip44.BIP44 -import org.kethereum.crypto.ECKeyPair +import org.kethereum.bip32.generateChildKey +import org.kethereum.bip32.model.ExtendedKey +import org.kethereum.bip32.model.Seed +import org.kethereum.bip32.toExtendedKey +import org.kethereum.bip44.BIP44Element +import org.kethereum.crypto.model.ECKeyPair +import org.kethereum.crypto.model.PrivateKey import org.spongycastle.jce.provider.BouncyCastleProvider import org.walleth.khex.hexToByteArray import java.math.BigInteger @@ -25,7 +29,7 @@ class ExtendedKeyTest { fun createPrivateFromSeed() { val seed = "000102030405060708090a0b0c0d0e0f".hexToByteArray() val expectedExtendedPrivateKey = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" - val key = ExtendedKey.createFromSeed(seed) + val key = Seed(seed).toExtendedKey() val encoded = key.serialize() assertEquals(expectedExtendedPrivateKey, encoded) } @@ -34,7 +38,7 @@ class ExtendedKeyTest { fun createPublicFromSeed() { val seed = "000102030405060708090a0b0c0d0e0f".hexToByteArray() val expectedExtendedPublicKey = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" - val key = ExtendedKey.createFromSeed(seed, true) + val key = Seed(seed).toExtendedKey(true) val encoded = key.serialize() assertEquals(expectedExtendedPublicKey, encoded) } @@ -43,9 +47,9 @@ class ExtendedKeyTest { fun createNonHardenedChildPrivateKey() { val seed = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542".hexToByteArray() val expectedExtendedPrivateKey = "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" - val key = ExtendedKey.createFromSeed(seed) + val key = Seed(seed).toExtendedKey() - val child = key.generateChildKey(0) + val child = key.generateChildKey(BIP44Element(false, 0)) val encoded = child.serialize() assertEquals(expectedExtendedPrivateKey, encoded) } @@ -55,9 +59,9 @@ class ExtendedKeyTest { //Chain m/0 val seed = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542".hexToByteArray() val expectedExtendedPublicKey = "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH" - val key = ExtendedKey.createFromSeed(seed, true) + val key = Seed(seed).toExtendedKey(true) - val child = key.generateChildKey(0) + val child = key.generateChildKey(BIP44Element(false, 0)) val encoded = child.serialize() assertEquals(expectedExtendedPublicKey, encoded) } @@ -69,18 +73,16 @@ class ExtendedKeyTest { val seed = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542".hexToByteArray() val expectedExtendedPrivateKey = "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" - val master = ExtendedKey.createFromSeed(seed) + val master = Seed(seed).toExtendedKey() - val child1 = master.generateChildKey(0) - val child2 = child1.generateChildKey(hardenSequence(2147483647)) + val child1 = master.generateChildKey(BIP44Element(false, 0)) + val child2 = child1.generateChildKey(BIP44Element(true, 2147483647)) val result = child2.serialize() assertEquals(expectedExtendedPrivateKey, result) } - private fun hardenSequence(i: Int): Int = i or BIP44.HARDENING_FLAG - @Test fun createPublicChildFromHardenedPath() { //Chain m/0/2147483647' @@ -90,10 +92,10 @@ class ExtendedKeyTest { val seed = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542".hexToByteArray() val expectedExtendedPublicKey = "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a" - val master = ExtendedKey.createFromSeed(seed) + val master = Seed(seed).toExtendedKey() - val child1 = master.generateChildKey(0) - val child2 = child1.generateChildKey(hardenSequence(2147483647)) + val child1 = master.generateChildKey(BIP44Element(false, 0)) + val child2 = child1.generateChildKey(BIP44Element(true, 2147483647)) .asPublicOnly() val result = child2.serialize() @@ -106,10 +108,10 @@ class ExtendedKeyTest { //Chain m/2147483647' // hardened paths don't allow derivation starting from public keys - val masterPub = ExtendedKey.createFromSeed("whatever".toByteArray(), true) + val masterPub = Seed("whatever".toByteArray()).toExtendedKey(true) //expect crash - masterPub.generateChildKey(hardenSequence(2147483647)) + masterPub.generateChildKey(BIP44Element(true, 2147483647)) } @Test @@ -138,4 +140,4 @@ class ExtendedKeyTest { } -internal fun ExtendedKey.asPublicOnly(): ExtendedKey = this.copy(keyPair = ECKeyPair(BigInteger.ZERO, this.keyPair.publicKey)) +internal fun ExtendedKey.asPublicOnly(): ExtendedKey = this.copy(keyPair = ECKeyPair(PrivateKey(BigInteger.ZERO), this.keyPair.publicKey)) diff --git a/signer/src/test/java/com/uport/sdk/signer/crypto/bip39/MnemonicTest.kt b/signer/src/test/java/com/uport/sdk/signer/crypto/bip39/MnemonicTest.kt index cfb91d01..6bf1417c 100644 --- a/signer/src/test/java/com/uport/sdk/signer/crypto/bip39/MnemonicTest.kt +++ b/signer/src/test/java/com/uport/sdk/signer/crypto/bip39/MnemonicTest.kt @@ -1,13 +1,12 @@ package com.uport.sdk.signer.crypto.bip39 -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import org.junit.After +import org.junit.Assert.* import org.junit.Before import org.junit.Test -import org.kethereum.bip32.generateKey -import org.kethereum.bip39.Mnemonic +import org.kethereum.bip39.* +import org.kethereum.bip39.model.MnemonicWords +import org.kethereum.bip39.wordlists.WORDLIST_ENGLISH import org.spongycastle.jce.provider.BouncyCastleProvider import org.walleth.khex.hexToByteArray import java.security.Security @@ -24,12 +23,17 @@ class MnemonicTest { Security.addProvider(BouncyCastleProvider()) } + @After + fun `run after every test`() { + Security.removeProvider("SC") + } + @Test - fun mnemonicToSeed() { + fun `can hash mnemonic phrase to seed buffer`() { testData.forEach { val expectedSeed = it.seed.hexToByteArray() - val actualSeed = Mnemonic.mnemonicToSeed(it.phrase, "TREZOR") + val actualSeed = MnemonicWords(it.phrase).toSeed("TREZOR").seed assertArrayEquals(expectedSeed, actualSeed) } @@ -37,62 +41,60 @@ class MnemonicTest { } @Test - fun mnemonicToEntropy() { + fun `can convert mnemonic phrase to entropy buffer`() { testData.forEach { val expectedEntropy = it.entropy.hexToByteArray() - val actualEntropy = Mnemonic.mnemonicToEntropy(it.phrase) + val actualEntropy = MnemonicWords(it.phrase).mnemonicToEntropy(WORDLIST_ENGLISH) assertArrayEquals(expectedEntropy, actualEntropy) } } @Test - fun entropyToMnemonic() { + fun `can convert entropy to mnemonic phrase `() { testData.forEach { val entropy = it.entropy.hexToByteArray() - val actualPhrase = Mnemonic.entropyToMnemonic(entropy) + val actualPhrase = entropyToMnemonic(entropy, WORDLIST_ENGLISH) assertEquals(it.phrase, actualPhrase) } } @Test - fun mnemonicToMasterKey() { + fun `can convert mnemonic phrase to extended key`() { testData.forEach { - val generatedSeed = Mnemonic.mnemonicToSeed(it.phrase, "TREZOR") - - val generatedMaster = generateKey(generatedSeed, "m/") + val generatedMaster = MnemonicWords(it.phrase).toKey("m/", "TREZOR") assertEquals(it.masterKey, generatedMaster.serialize()) //XXX: be advised, the roots generated here use the string "TREZOR" for salting. // The actual roots in the app will probably use something else - val generatedUportRoot = generateKey(generatedSeed, "m/7696500'/0'/0'/0'") + val generatedUportRoot = MnemonicWords(it.phrase).toKey("m/7696500'/0'/0'/0'", "TREZOR") assertEquals(it.uportRoot, generatedUportRoot.serialize()) } } @Test - fun mnemonicVerify() { + fun `can (in)validate mnemonic phrase`() { val phraseGood = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" //bad checksum val phraseBad1 = "about about about about about about about about about about about about" // missing from dictionary val phraseBad2 = "hello world" - assertTrue(Mnemonic.validateMnemonic(phraseGood)) - assertFalse(Mnemonic.validateMnemonic(phraseBad1)) - assertFalse(Mnemonic.validateMnemonic(phraseBad2)) + assertTrue(MnemonicWords(phraseGood).validate(WORDLIST_ENGLISH)) + assertFalse(MnemonicWords(phraseBad1).validate(WORDLIST_ENGLISH)) + assertFalse(MnemonicWords(phraseBad2).validate(WORDLIST_ENGLISH)) } @Test - fun batchTest() { + fun `can convert entropy buffers to mnemonic phrases`() { testData.forEach { val entropy = it.entropy.hexToByteArray() val expectedPhrase = it.phrase - val actualPhrase = Mnemonic.entropyToMnemonic(entropy) + val actualPhrase = entropyToMnemonic(entropy, WORDLIST_ENGLISH) assertEquals(expectedPhrase, actualPhrase) } @@ -100,7 +102,7 @@ class MnemonicTest { data class MnemonicTestData(val entropy: String, val phrase: String, val seed: String, val masterKey: String, val uportRoot: String) - val testData = arrayOf( + private val testData = arrayOf( MnemonicTestData("00000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", From 632365ed3b0a89ca809f75d11ec11261dc9e56fa Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Mon, 22 Oct 2018 20:27:36 +0300 Subject: [PATCH 51/75] mention the breaking kethereum API in readme for devs that are using it in parallel --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4c3fe677..f293061d 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,8 @@ but that may be removed when pure kotlin implementations of the required cryptog * 0.3.x - upcoming release with breaking changes * add universal DID resolver * add cleaner way of creating JWTs with abstracted signer + * updated to kethereum 0.63 which has a different key derivation and mnemonic API. + If you're using an older version in parallel, you need to update as well. * 0.2.2 * update of dependencies for coroutines and build tools From 0a87d9e671f1ee052b85b974a49b54f1f4927443 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 23 Oct 2018 16:30:11 +0300 Subject: [PATCH 52/75] implement simple intent sender method --- .../sdk/transport/IntentSenderAndroidTest.kt | 31 --------- .../sdk/transport/TransportsAndroidTests.kt | 67 +++++++++++++++++++ .../me/uport/sdk/transport/IntentSender.kt | 12 ---- .../java/me/uport/sdk/transport/Transports.kt | 26 +++++++ .../uport/sdk/transport/IntentSenderTest.kt | 14 ---- .../uport/sdk/transport/TransportsJVMTest.kt | 13 ++++ 6 files changed, 106 insertions(+), 57 deletions(-) delete mode 100644 transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt create mode 100644 transport/src/androidTest/java/me/uport/sdk/transport/TransportsAndroidTests.kt delete mode 100644 transport/src/main/java/me/uport/sdk/transport/IntentSender.kt create mode 100644 transport/src/main/java/me/uport/sdk/transport/Transports.kt delete mode 100644 transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt create mode 100644 transport/src/test/java/me/uport/sdk/transport/TransportsJVMTest.kt diff --git a/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt b/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt deleted file mode 100644 index 94b6224b..00000000 --- a/transport/src/androidTest/java/me/uport/sdk/transport/IntentSenderAndroidTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package me.uport.sdk.transport - -import android.app.Activity -import android.support.test.espresso.intent.rule.IntentsTestRule -import org.junit.Assert.assertTrue -import org.junit.Rule -import org.junit.Test - - -class IntentSenderAndroidTest { - - //only used for testing - class DummyActivity : Activity() - - - @JvmField - @Rule - var intentsTestRule = IntentsTestRule(DummyActivity::class.java) - - @Test - fun setup_is_ok() { - assertTrue(true) - } - - @Test - fun can_call_share_req() { - val jwt = "dummy jwt bundle" - IntentSender().send(jwt) - } - -} \ No newline at end of file diff --git a/transport/src/androidTest/java/me/uport/sdk/transport/TransportsAndroidTests.kt b/transport/src/androidTest/java/me/uport/sdk/transport/TransportsAndroidTests.kt new file mode 100644 index 00000000..b697fe80 --- /dev/null +++ b/transport/src/androidTest/java/me/uport/sdk/transport/TransportsAndroidTests.kt @@ -0,0 +1,67 @@ +package me.uport.sdk.transport + +import android.app.Activity +import android.app.Instrumentation +import android.content.Intent +import android.content.IntentFilter +import android.support.test.InstrumentationRegistry +import android.support.test.espresso.intent.Intents.intended +import android.support.test.espresso.intent.matcher.IntentMatchers.* +import android.support.test.espresso.intent.matcher.UriMatchers.hasHost +import android.support.test.espresso.intent.matcher.UriMatchers.hasPath +import android.support.test.espresso.intent.rule.IntentsTestRule +import org.hamcrest.Matchers.* +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test + + +class TransportsAndroidTests { + + //only used for testing + class DummyActivity : Activity() + + @JvmField + @Rule + var intentsTestRule = IntentsTestRule(DummyActivity::class.java, true, true) + + private val filter: IntentFilter? = null + // used to block intent from actual broadcast + private var instrumentation: Instrumentation? = null + private var monitor: Instrumentation.ActivityMonitor? = null + + @Before + fun run_before_every_test() { + instrumentation = InstrumentationRegistry.getInstrumentation() + monitor = instrumentation?.addMonitor(filter, null, true) + } + + @After + fun run_after_every_test() { + instrumentation?.removeMonitor(monitor) + } + + @Test + fun setup_is_ok() { + assertTrue(true) + } + + @Test + fun share_req_sends_intent_with_proper_data() { + + val jwt = "dummy.jwt.bundle" + Transports().send(intentsTestRule.activity, jwt) + + intended(allOf( + hasAction(equalTo(Intent.ACTION_VIEW)), + hasCategories(hasItem(equalTo(Intent.CATEGORY_BROWSABLE))), + hasData(allOf( + hasHost(equalTo("id.uport.me")), + hasPath(containsString("/req/$jwt")) + ) + ))) + } + +} \ No newline at end of file diff --git a/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt b/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt deleted file mode 100644 index a2b306bd..00000000 --- a/transport/src/main/java/me/uport/sdk/transport/IntentSender.kt +++ /dev/null @@ -1,12 +0,0 @@ -package me.uport.sdk.transport - -import android.support.annotation.VisibleForTesting -import android.support.annotation.VisibleForTesting.PRIVATE - -class IntentSender { - - fun send(jwt: String) { - TODO("unimplemented. this should broadcast a URL intent with the JWT") - } - -} \ No newline at end of file diff --git a/transport/src/main/java/me/uport/sdk/transport/Transports.kt b/transport/src/main/java/me/uport/sdk/transport/Transports.kt new file mode 100644 index 00000000..f225726a --- /dev/null +++ b/transport/src/main/java/me/uport/sdk/transport/Transports.kt @@ -0,0 +1,26 @@ +package me.uport.sdk.transport + +import android.content.Context +import android.content.Intent +import android.net.Uri +import java.net.URLEncoder + +class Transports { + + /** + * Sends a selective disclosure request to the uPort app. + * If the uPort app is not present this will open a browser + * + * __API-maturity: new__ + * + * This method does not ensure any type of response. + */ + fun send(context: Context, jwt: String) { + val encodedQuery = URLEncoder.encode(jwt, "UTF-8") + val uri = Uri.parse("https://id.uport.me/req/$encodedQuery") + val intent = Intent(Intent.ACTION_VIEW, uri) + .addCategory(Intent.CATEGORY_BROWSABLE) + context.startActivity(intent) + } + +} \ No newline at end of file diff --git a/transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt b/transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt deleted file mode 100644 index 0310a768..00000000 --- a/transport/src/test/java/me/uport/sdk/transport/IntentSenderTest.kt +++ /dev/null @@ -1,14 +0,0 @@ -package me.uport.sdk.transport - -import kotlinx.coroutines.experimental.runBlocking -import org.junit.Assert.* -import org.junit.Test - -class IntentSenderTest { - - @Test - fun `setup is ok`() = runBlocking { - assertTrue(true) - } - -} \ No newline at end of file diff --git a/transport/src/test/java/me/uport/sdk/transport/TransportsJVMTest.kt b/transport/src/test/java/me/uport/sdk/transport/TransportsJVMTest.kt new file mode 100644 index 00000000..dbb29131 --- /dev/null +++ b/transport/src/test/java/me/uport/sdk/transport/TransportsJVMTest.kt @@ -0,0 +1,13 @@ +package me.uport.sdk.transport + +import org.junit.Assert.assertTrue +import org.junit.Test + +class TransportsJVMTest { + + @Test + fun `setup is ok`() { + assertTrue(true) + } + +} \ No newline at end of file From 12120396607ac4053fb46e0de47c90c90c95d28e Mon Sep 17 00:00:00 2001 From: Ugo Amanoh Date: Thu, 25 Oct 2018 19:31:54 +0100 Subject: [PATCH 53/75] Add contributions.md file to project --- CONTRIBUTING.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..618c74f6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +#### Guidlines for writing User Stories and Pull Requests +This is the first draft of this documents which be continuous revised. + +# User Stories: +#### Goal + +This should be 1 - 2 sentences max and should clearly state what the User Story ie expected to achieve. A user story should have just one goal + +#### Description + +This should specify any important steps which needed to achieve the above goal. Important technical information such as specific steps, sample data, user experience considerations, etc should go into the description + +#### Acceptance Criteria + +Each story must have a minimum 2 and a maximum of 5 acceptance criteria. These are conditions which can be verified by the test engineer and must be met for the task to be seen as completed. + +#### Test Cases + +A User Story should have at least one test case. This is a scenario or example which can validates the acceptance criteria above + + +# Pull Requests: + +Every PR should have: + +1. Brief explanation of what was actually done +2. Screenshots if applicable +3. Unit tests for new code +4. Testing instructions in PR description +5. Comments (dokka, javadoc) for new methods From b69eb9c7df27ab1ac852f39e64ce27c572b7d2bd Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 30 Oct 2018 08:24:40 +0200 Subject: [PATCH 54/75] add test that validates the scenario of #42 --- .../src/main/java/me/uport/sdk/ethrdid/EthrDID.kt | 2 +- .../me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt | 14 ++++++++++++++ sdk/build.gradle | 2 ++ transport/src/androidTest/AndroidManifest.xml | 2 +- .../main/java/me/uport/sdk/transport/Transports.kt | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt index 3569931c..68a6aca2 100644 --- a/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt +++ b/ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt @@ -22,7 +22,7 @@ class EthrDID( private val address: String, private val rpc: JsonRPC, private val registry: String, - var signer: Signer + val signer: Signer ) { private val owner: String? = null diff --git a/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt b/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt index 5c832e7a..05066d02 100644 --- a/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt +++ b/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt @@ -35,4 +35,18 @@ class JWTSignerAlgorithmTest { assertEquals(65, signature.decodeBase64().size) } + + @Test + fun `can sign using vector from did-jwt`() = runBlocking { + + val signer = KPSigner("278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f") + + //the signature data from https://github.com/uport-project/did-jwt/blob/develop/src/__tests__/__snapshots__/SimpleSigner-test.js.snap + // JOSE encoded with recovery param + val expectedSignature = "jsvdLwqr-O206hkegoq6pbo7LJjCaflEKHCvfohBP9XJ4C7mG2TPL9YjyKEpYSXqqkUrfRoCxQecHR11Uh7POwA" + + val signature = JWTSignerAlgorithm(ES256K_R).sign("thequickbrownfoxjumpedoverthelazyprogrammer", signer) + + assertEquals(expectedSignature, signature) + } } \ No newline at end of file diff --git a/sdk/build.gradle b/sdk/build.gradle index 1d7db62f..1939035c 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -39,6 +39,8 @@ dependencies { api project(":universal-did") api project(":ethr-did") api project(":uport-did") + api project(":credentials") + api project(":transport") androidTestImplementation "com.android.support.test:runner:$test_runner_version" androidTestImplementation "com.android.support.test:rules:$test_runner_version" diff --git a/transport/src/androidTest/AndroidManifest.xml b/transport/src/androidTest/AndroidManifest.xml index 6705e3bc..139ade25 100644 --- a/transport/src/androidTest/AndroidManifest.xml +++ b/transport/src/androidTest/AndroidManifest.xml @@ -3,6 +3,6 @@ - + diff --git a/transport/src/main/java/me/uport/sdk/transport/Transports.kt b/transport/src/main/java/me/uport/sdk/transport/Transports.kt index f225726a..27626d18 100644 --- a/transport/src/main/java/me/uport/sdk/transport/Transports.kt +++ b/transport/src/main/java/me/uport/sdk/transport/Transports.kt @@ -20,6 +20,7 @@ class Transports { val uri = Uri.parse("https://id.uport.me/req/$encodedQuery") val intent = Intent(Intent.ACTION_VIEW, uri) .addCategory(Intent.CATEGORY_BROWSABLE) + println(uri.toString()) context.startActivity(intent) } From 6369f15da50f2ffc9c7eafed15cf1848a5feead8 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 30 Oct 2018 08:26:23 +0200 Subject: [PATCH 55/75] change encoding of recovery param to 0-1 instead of 27-28 and adapt other tests this fixes #42 --- .../java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt | 2 +- jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt | 2 +- jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt | 2 +- signer/src/main/java/com/uport/sdk/signer/Extensions.kt | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt index 41485898..39a4f8c1 100644 --- a/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt +++ b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTSignerAlgorithmRuntimeTest.kt @@ -60,7 +60,7 @@ class JWTSignerAlgorithmRuntimeTest { val signature = JWTSignerAlgorithm(ES256K_R).sign(referencePayload, testedSigner) - val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XRw" + val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XQE" assertEquals(expectedSignature, signature) assertEquals(65, signature.decodeBase64().size) } diff --git a/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt index fd0d3b26..dc2b68fc 100644 --- a/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt +++ b/jwt/src/androidTest/java/me/uport/sdk/jwt/JWTToolsTests.kt @@ -119,7 +119,7 @@ class JWTToolsTests { val issuerDID = "did:ethr:${signer.getAddress()}" val jwt = tested.createJWT(payload, issuerDID, signer) - val expected = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJjbGFpbXMiOnsibmFtZSI6IlIgRGFuZWVsIE9saXZhdyJ9LCJpYXQiOjEyMzQ1Njc4LCJleHAiOjEyMzQ1OTc4LCJpc3MiOiJkaWQ6ZXRocjoweDQxMjNjYmQxNDNiNTVjMDZlNDUxZmYyNTNhZjA5Mjg2YjY4N2E5NTAifQ.o6eDKYjHJnak1ylkpe9g8krxvK9UEhKf-1T0EYhH8pGyb8MjOEepRJi8DYlVEnZno0DkVYXQCf3u1i_HThBKtBs" + val expected = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJjbGFpbXMiOnsibmFtZSI6IlIgRGFuZWVsIE9saXZhdyJ9LCJpYXQiOjEyMzQ1Njc4LCJleHAiOjEyMzQ1OTc4LCJpc3MiOiJkaWQ6ZXRocjoweDQxMjNjYmQxNDNiNTVjMDZlNDUxZmYyNTNhZjA5Mjg2YjY4N2E5NTAifQ.o6eDKYjHJnak1ylkpe9g8krxvK9UEhKf-1T0EYhH8pGyb8MjOEepRJi8DYlVEnZno0DkVYXQCf3u1i_HThBKtAA" assertEquals(expected, jwt) val tt = tested.decode(expected) assertEquals(12345678L, tt.second.iat) diff --git a/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt b/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt index 05066d02..4a731f1f 100644 --- a/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt +++ b/jwt/src/test/java/me/uport/sdk/jwt/JWTSignerAlgorithmTest.kt @@ -28,7 +28,7 @@ class JWTSignerAlgorithmTest { val signer = KPSigner("65fc670d9351cb87d1f56702fb56a7832ae2aab3427be944ab8c9f2a0ab87960") - val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XRw" + val expectedSignature = "a82BRGGDrxk8pKFy1cXCY0WQOyR3DZC115D3Sp3sH2jiuFs8ksm0889Y3kbnmX2O-24UsuUy0T36Iu4C86Q9XQE" val signature = JWTSignerAlgorithm(ES256K_R).sign("Hello, world!", signer) assertEquals(expectedSignature, signature) diff --git a/signer/src/main/java/com/uport/sdk/signer/Extensions.kt b/signer/src/main/java/com/uport/sdk/signer/Extensions.kt index a99c48fd..0e9cc913 100644 --- a/signer/src/main/java/com/uport/sdk/signer/Extensions.kt +++ b/signer/src/main/java/com/uport/sdk/signer/Extensions.kt @@ -24,6 +24,9 @@ const val SIG_RECOVERABLE_SIZE = SIG_SIZE + 1 /** * Returns the JOSE encoding of the standard signature components (joined by empty string) + * + * @param recoverable If this is true then the buffer returned gets an extra byte with the + * recovery param shifted back to [0, 1] ( as opposed to [27,28] ) */ fun SignatureData.getJoseEncoded(recoverable: Boolean = false): String { val size = if (recoverable) @@ -35,7 +38,7 @@ fun SignatureData.getJoseEncoded(recoverable: Boolean = false): String { bos.write(this.r.toBytesPadded(SIG_COMPONENT_SIZE)) bos.write(this.s.toBytesPadded(SIG_COMPONENT_SIZE)) if (recoverable) { - bos.write(byteArrayOf(this.v)) + bos.write(byteArrayOf((this.v - 27).toByte())) } return bos.toByteArray().toBase64UrlSafe() } From 9b840aa60b1323b8a2f92181a6650361ba3c159f Mon Sep 17 00:00:00 2001 From: Ugo Amanoh Date: Thu, 1 Nov 2018 16:51:34 +0100 Subject: [PATCH 56/75] Added a new activity showing the list of features to demo --- build.gradle | 2 +- demoapp/src/main/AndroidManifest.xml | 2 +- .../java/me/uport/sdk/demoapp/MainActivity.kt | 1 + .../me/uport/sdk/demoapp/MainListActivity.kt | 29 +++++++++++++++++++ .../main/res/layout/activity_list_main.xml | 11 +++++++ demoapp/src/main/res/layout/activity_main.xml | 1 + 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt create mode 100644 demoapp/src/main/res/layout/activity_list_main.xml diff --git a/build.gradle b/build.gradle index 03de766a..0b701dc7 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { kotlin_serialization_version = '0.6.2' coroutines_version = "0.30.2" - android_tools_version = '3.3.0-beta01' + android_tools_version = '3.3.0-beta02' build_tools_version = "28.0.3" diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index 29b63606..e841c81f 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:theme="@style/AppTheme" android:name=".DemoApplication" > - + diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt index 64c36fb4..3b1bfe9e 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt @@ -6,6 +6,7 @@ import android.view.View import me.uport.sdk.Uport import me.uport.sdk.core.Networks import kotlinx.android.synthetic.main.activity_main.* +import me.uport.sdk.extensions.send class MainActivity : AppCompatActivity() { diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt new file mode 100644 index 00000000..24e263f2 --- /dev/null +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt @@ -0,0 +1,29 @@ +package me.uport.sdk.demoapp + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.widget.ArrayAdapter +import android.widget.ListView + +/** + * + * Main screen for the demo app + * This lists all the features to be demonstrated within the app + * Clicking on an item opens up a new activity for the specific feature + * + **/ + +class MainListActivity : AppCompatActivity() { + + val features = arrayOf("Create an Account", "Manage Keys", "Create a JWT", "Verify a JWT", "Selective Disclosure") + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_list_main) + + var listView = findViewById(R.id.feature_list) + val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, features) + + listView.adapter = adapter + } +} \ No newline at end of file diff --git a/demoapp/src/main/res/layout/activity_list_main.xml b/demoapp/src/main/res/layout/activity_list_main.xml new file mode 100644 index 00000000..88b2bdb5 --- /dev/null +++ b/demoapp/src/main/res/layout/activity_list_main.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/demoapp/src/main/res/layout/activity_main.xml b/demoapp/src/main/res/layout/activity_main.xml index 341e53b8..6585ea90 100644 --- a/demoapp/src/main/res/layout/activity_main.xml +++ b/demoapp/src/main/res/layout/activity_main.xml @@ -47,4 +47,5 @@ app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + From d9eebc4344308fbfe156b57ef39763f9ec9610f0 Mon Sep 17 00:00:00 2001 From: Ugo Amanoh Date: Thu, 1 Nov 2018 17:39:24 +0100 Subject: [PATCH 57/75] Launch new activity when list item is clicked --- demoapp/src/main/AndroidManifest.xml | 2 ++ .../main/java/me/uport/sdk/demoapp/MainActivity.kt | 3 +-- .../java/me/uport/sdk/demoapp/MainListActivity.kt | 11 +++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index e841c81f..691aaecb 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -17,6 +17,8 @@ + + \ No newline at end of file diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt index 3b1bfe9e..087f8193 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt @@ -3,10 +3,9 @@ package me.uport.sdk.demoapp import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.View +import kotlinx.android.synthetic.main.activity_main.* import me.uport.sdk.Uport import me.uport.sdk.core.Networks -import kotlinx.android.synthetic.main.activity_main.* -import me.uport.sdk.extensions.send class MainActivity : AppCompatActivity() { diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt index 24e263f2..6dcf992c 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt @@ -1,5 +1,6 @@ package me.uport.sdk.demoapp +import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.ArrayAdapter @@ -25,5 +26,15 @@ class MainListActivity : AppCompatActivity() { val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, features) listView.adapter = adapter + + listView.setOnItemClickListener { _, _, position, _ -> + itemSelected(position) + } + } + + private fun itemSelected(position: Int) { + when(position) { + 0 -> startActivity(Intent(this, MainActivity::class.java)) + } } } \ No newline at end of file From 16ae1a5d5f1f5e8335d85df2879de4634928864b Mon Sep 17 00:00:00 2001 From: Ugo Amanoh Date: Thu, 1 Nov 2018 17:40:16 +0100 Subject: [PATCH 58/75] Rename MainActivity to CreateAccountActivity --- demoapp/src/main/AndroidManifest.xml | 2 +- .../sdk/demoapp/{MainActivity.kt => CreateAccountActivity.kt} | 2 +- demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt | 2 +- demoapp/src/main/res/layout/activity_main.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename demoapp/src/main/java/me/uport/sdk/demoapp/{MainActivity.kt => CreateAccountActivity.kt} (95%) diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index 691aaecb..ee78634a 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -18,7 +18,7 @@ - + \ No newline at end of file diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/CreateAccountActivity.kt similarity index 95% rename from demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt rename to demoapp/src/main/java/me/uport/sdk/demoapp/CreateAccountActivity.kt index 087f8193..e76dbab2 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/CreateAccountActivity.kt @@ -8,7 +8,7 @@ import me.uport.sdk.Uport import me.uport.sdk.core.Networks -class MainActivity : AppCompatActivity() { +class CreateAccountActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt index 6dcf992c..107728df 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt @@ -34,7 +34,7 @@ class MainListActivity : AppCompatActivity() { private fun itemSelected(position: Int) { when(position) { - 0 -> startActivity(Intent(this, MainActivity::class.java)) + 0 -> startActivity(Intent(this, CreateAccountActivity::class.java)) } } } \ No newline at end of file diff --git a/demoapp/src/main/res/layout/activity_main.xml b/demoapp/src/main/res/layout/activity_main.xml index 6585ea90..5bffbd4f 100644 --- a/demoapp/src/main/res/layout/activity_main.xml +++ b/demoapp/src/main/res/layout/activity_main.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="me.uport.sdk.demoapp.MainActivity"> + tools:context="me.uport.sdk.demoapp.CreateAccountActivity"> Date: Wed, 7 Nov 2018 16:02:26 +0100 Subject: [PATCH 59/75] Replaced findViewById with kotlin extension implementation --- .../java/me/uport/sdk/demoapp/MainListActivity.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt index 107728df..d6748ebc 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt @@ -4,7 +4,8 @@ import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.ArrayAdapter -import android.widget.ListView +import android.widget.Toast +import kotlinx.android.synthetic.main.activity_list_main.* /** * @@ -22,19 +23,19 @@ class MainListActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_list_main) - var listView = findViewById(R.id.feature_list) val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, features) - listView.adapter = adapter + feature_list.adapter = adapter - listView.setOnItemClickListener { _, _, position, _ -> + feature_list.setOnItemClickListener { _, _, position, _ -> itemSelected(position) } } private fun itemSelected(position: Int) { - when(position) { + when (position) { 0 -> startActivity(Intent(this, CreateAccountActivity::class.java)) + else -> Toast.makeText(this, "Not yet Implemented", Toast.LENGTH_LONG).show() } } } \ No newline at end of file From 2400a3ab7952bd5cfd446e2a3b702518bd121d8c Mon Sep 17 00:00:00 2001 From: Ugo Amanoh Date: Wed, 7 Nov 2018 16:04:21 +0100 Subject: [PATCH 60/75] Added 2 more features to the list --- demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt index d6748ebc..4dd4b433 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt @@ -17,7 +17,7 @@ import kotlinx.android.synthetic.main.activity_list_main.* class MainListActivity : AppCompatActivity() { - val features = arrayOf("Create an Account", "Manage Keys", "Create a JWT", "Verify a JWT", "Selective Disclosure") + val features = arrayOf("Create an Account", "Create a Key", "Import a Key", "Manage Keys","Create a JWT", "Verify a JWT", "Selective Disclosure") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) From 4a6ee7d10ddf202c6f2f87a9f4cc2ac43f6c5bf3 Mon Sep 17 00:00:00 2001 From: Ugo Amanoh Date: Wed, 7 Nov 2018 16:25:20 +0100 Subject: [PATCH 61/75] Setup Activity for creating keys --- demoapp/src/main/AndroidManifest.xml | 1 + .../me/uport/sdk/demoapp/CreateKeyActivity.kt | 13 ++++++++++ .../me/uport/sdk/demoapp/MainListActivity.kt | 6 +++-- demoapp/src/main/res/layout/create_key.xml | 24 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 demoapp/src/main/java/me/uport/sdk/demoapp/CreateKeyActivity.kt create mode 100644 demoapp/src/main/res/layout/create_key.xml diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index ee78634a..e227cd07 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + \ No newline at end of file diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/CreateKeyActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/CreateKeyActivity.kt new file mode 100644 index 00000000..c0c8e858 --- /dev/null +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/CreateKeyActivity.kt @@ -0,0 +1,13 @@ +package me.uport.sdk.demoapp + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import kotlinx.android.synthetic.main.create_key.* + +class CreateKeyActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.create_key) + } +} \ No newline at end of file diff --git a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt index 4dd4b433..18cdd906 100644 --- a/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt +++ b/demoapp/src/main/java/me/uport/sdk/demoapp/MainListActivity.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.ArrayAdapter +import android.widget.ListAdapter import android.widget.Toast import kotlinx.android.synthetic.main.activity_list_main.* @@ -17,7 +18,7 @@ import kotlinx.android.synthetic.main.activity_list_main.* class MainListActivity : AppCompatActivity() { - val features = arrayOf("Create an Account", "Create a Key", "Import a Key", "Manage Keys","Create a JWT", "Verify a JWT", "Selective Disclosure") + private val features = arrayOf("Create an Account", "Create a Key", "Import a Key", "Manage Keys","Create a JWT", "Verify a JWT", "Selective Disclosure") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -25,7 +26,7 @@ class MainListActivity : AppCompatActivity() { val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, features) - feature_list.adapter = adapter + feature_list.adapter = adapter as ListAdapter? feature_list.setOnItemClickListener { _, _, position, _ -> itemSelected(position) @@ -35,6 +36,7 @@ class MainListActivity : AppCompatActivity() { private fun itemSelected(position: Int) { when (position) { 0 -> startActivity(Intent(this, CreateAccountActivity::class.java)) + 1 -> startActivity(Intent(this, CreateKeyActivity::class.java)) else -> Toast.makeText(this, "Not yet Implemented", Toast.LENGTH_LONG).show() } } diff --git a/demoapp/src/main/res/layout/create_key.xml b/demoapp/src/main/res/layout/create_key.xml new file mode 100644 index 00000000..deacbcbf --- /dev/null +++ b/demoapp/src/main/res/layout/create_key.xml @@ -0,0 +1,24 @@ + + + + + + +