Skip to content

Commit

Permalink
Merge branch 'release/0.2.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
mirceanis committed Jul 10, 2019
2 parents fcbf03e + 8c6d4c0 commit a7ec6ee
Show file tree
Hide file tree
Showing 13 changed files with 492 additions and 90 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ We currently support the following DID methods:

- [`ethr`](https://github.com/uport-project/ethr-did-resolver)
- [`uport`](https://github.com/uport-project/uport-did-resolver)
- [`https`](https://github.com/uport-project/https-did-resolver)
- [`web`](https://github.com/uport-project/https-did-resolver)
- [`https`](https://github.com/uport-project/https-did-resolver) *DEPRECATED*

Defaults are automatically installed but you can customize to fit your needs.

Expand Down Expand Up @@ -132,8 +133,13 @@ so that only tokens intended for your app are considered valid.

## CHANGELOG

* 0.2.1
- add support for web DID, deprecating https DID (#5)
- allow creation of JWTs with no expiry (#6)
- fallback to ES256K-R style verification if ES256K algorithm fails because of missing key encoding (#7)
- [bugfix] delegate keys in ethr-did documents were not being resolved properly (#9)
* 0.2.0
- [breaking] add audience checking for JWT verification
- [breaking] add audience checking for JWT verification (#2)
- add `jwt-test` module with helpers for testing
* 0.1.2
- fix crash when parsing legacy identity document
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ buildscript {
spongycastle_version = "1.58.0.0"
uport_kotlin_common_version = "0.1.1"

current_release_version = "0.2.0"
current_release_version = "0.2.1"
}

repositories {
Expand Down
69 changes: 50 additions & 19 deletions ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDID.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package me.uport.sdk.ethrdid
import me.uport.sdk.jsonrpc.JsonRPC
import me.uport.sdk.signer.Signer
import me.uport.sdk.signer.signRawTx
import me.uport.sdk.signer.utf8
import me.uport.sdk.universaldid.PublicKeyType
import org.kethereum.extensions.hexToBigInteger
import org.kethereum.model.Address
Expand Down Expand Up @@ -50,7 +51,7 @@ class EthrDID(


class DelegateOptions(
val delegateType: PublicKeyType = PublicKeyType.Secp256k1VerificationKey2018,
val delegateType: PublicKeyType = PublicKeyType.veriKey,
val expiresIn: Long = 86400L
)

Expand All @@ -61,70 +62,100 @@ class EthrDID(
return rawResult.substring(rawResult.length - 40).prepend0xPrefix()
}

suspend fun changeOwner(newOwner: String): String {
suspend fun changeOwner(newOwner: String, txOptions: TransactionOptions? = null): String {
val owner = lookupOwner()

val encodedCall = EthereumDIDRegistry.ChangeOwner.encode(
Solidity.Address(address.hexToBigInteger()),
Solidity.Address(newOwner.hexToBigInteger())
)

return signAndSendContractCall(owner, encodedCall)
return signAndSendContractCall(owner, encodedCall, txOptions)
}


suspend fun addDelegate(delegate: String, options: DelegateOptions = DelegateOptions()): String {
suspend fun addDelegate(delegate: String, options: DelegateOptions = DelegateOptions(), txOptions: TransactionOptions? = null): String {
val owner = lookupOwner()

val encodedCall = EthereumDIDRegistry.AddDelegate.encode(
Solidity.Address(this.address.hexToBigInteger()),
Solidity.Bytes32(options.delegateType.name.toByteArray()),
Solidity.Bytes32(options.delegateType.name.toByteArray(utf8)),
Solidity.Address(delegate.hexToBigInteger()),
Solidity.UInt256(BigInteger.valueOf(options.expiresIn))
)

return signAndSendContractCall(owner, encodedCall)
return signAndSendContractCall(owner, encodedCall, txOptions)
}

suspend fun revokeDelegate(
delegate: String,
delegateType: PublicKeyType = PublicKeyType.Secp256k1VerificationKey2018
delegateType: PublicKeyType = PublicKeyType.veriKey,
txOptions: TransactionOptions? = null
): String {
val owner = this.lookupOwner()
val encodedCall = EthereumDIDRegistry.RevokeDelegate.encode(
Solidity.Address(this.address.hexToBigInteger()),
Solidity.Bytes32(delegateType.name.toByteArray()),
Solidity.Bytes32(delegateType.name.toByteArray(utf8)),
Solidity.Address(delegate.hexToBigInteger())
)

return signAndSendContractCall(owner, encodedCall)
return signAndSendContractCall(owner, encodedCall, txOptions)
}

suspend fun setAttribute(key: String, value: String, expiresIn: Long = 86400L): String {
suspend fun setAttribute(key: String, value: String, expiresIn: Long = 86400L, txOptions: TransactionOptions? = null): String {
val owner = this.lookupOwner()
val encodedCall = EthereumDIDRegistry.SetAttribute.encode(
Solidity.Address(this.address.hexToBigInteger()),
Solidity.Bytes32(key.toByteArray()),
Solidity.Bytes(value.toByteArray()),
Solidity.Bytes32(key.toByteArray(utf8)),
Solidity.Bytes(value.toByteArray(utf8)),
Solidity.UInt256(BigInteger.valueOf(expiresIn))
)
return signAndSendContractCall(owner, encodedCall)
return signAndSendContractCall(owner, encodedCall, txOptions)
}

private suspend fun signAndSendContractCall(owner: String, encodedCall: String): String {
/**
* Encapsulates some overrides
*/
data class TransactionOptions(
/**
* overrides the gasLimit used in the transaction
*/
val gasLimit: BigInteger? = null,

/**
* overrides the gasPrice (measured in wei) set for the transaction
*/
val gasPrice: BigInteger? = null,

/**
* overrides the nonce used in the transaction
* (for rebroadcasting with different params while the current one is not yet mined)
*/
val nonce: BigInteger? = null,

/**
* overrides the ETH value (measured in wei).
*/
val value: BigInteger? = null
)

private suspend fun signAndSendContractCall(
owner: String,
encodedCall: String,
txOption: TransactionOptions? = null
): String {
//these requests can be done in parallel
val nonce = rpc.getTransactionCount(owner)
val networkPrice = rpc.getGasPrice()
val nonce = txOption?.nonce ?: rpc.getTransactionCount(owner)
val networkPrice = txOption?.gasPrice ?: rpc.getGasPrice()

val unsignedTx = createTransactionWithDefaults(
from = Address(owner),
to = Address(registry),
gasLimit = BigInteger.valueOf(70_000),
//FIXME: allow overriding the gas price
gasLimit = txOption?.gasLimit ?: BigInteger.valueOf(80_000),
gasPrice = networkPrice,
nonce = nonce,
input = encodedCall.hexToByteArray(),
value = BigInteger.ZERO
value = txOption?.value ?: BigInteger.ZERO
)

val signedEncodedTx = signer.signRawTx(unsignedTx)
Expand Down
19 changes: 9 additions & 10 deletions ethr-did/src/main/java/me/uport/sdk/ethrdid/EthrDIDResolver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import me.uport.sdk.signer.utf8
import me.uport.sdk.universaldid.*
import me.uport.sdk.universaldid.PublicKeyType.Companion.Secp256k1SignatureAuthentication2018
import me.uport.sdk.universaldid.PublicKeyType.Companion.Secp256k1VerificationKey2018
import me.uport.sdk.universaldid.PublicKeyType.Companion.sigAuth
import me.uport.sdk.universaldid.PublicKeyType.Companion.veriKey
import org.kethereum.encodings.encodeToBase58String
import org.kethereum.extensions.hexToBigInteger
import org.kethereum.extensions.toHexStringNoPrefix
Expand Down Expand Up @@ -89,7 +91,7 @@ open class EthrDIDResolver(
*/
@Suppress("TooGenericExceptionCaught")
internal suspend fun getHistory(identity: String): List<Any> {
val lastChangedQueue: Queue<BigInteger> = PriorityQueue<BigInteger>()
val lastChangedQueue: Queue<BigInteger> = PriorityQueue()
val events = emptyList<Any>().toMutableList()
lastChangedQueue.add(lastChanged(identity).hexToBigInteger())
do {
Expand Down Expand Up @@ -249,7 +251,7 @@ open class EthrDIDResolver(
val authEntries = mapOf<String, AuthenticationEntry>().toMutableMap()

var delegateIndex = delegateCount
val delegateType = event.delegatetype.bytes.toString(utf8)
val delegateType = event.delegatetype.bytes.toString(utf8).replace("\u0000","")
val delegate = event.delegate.value.toHexStringNoPrefix().prepend0xPrefix()
val key = "DIDDelegateChanged-$delegateType-$delegate"
val validTo = event.validto.value.toLong()
Expand All @@ -259,13 +261,13 @@ open class EthrDIDResolver(

when (delegateType) {
Secp256k1SignatureAuthentication2018.name,
sigAuth -> authEntries[key] = AuthenticationEntry(
sigAuth.name -> authEntries[key] = AuthenticationEntry(
type = Secp256k1SignatureAuthentication2018,
publicKey = "$ownerDID#delegate-$delegateIndex"
)

Secp256k1VerificationKey2018.name,
veriKey -> pkEntries[key] = PublicKeyEntry(
veriKey.name -> pkEntries[key] = PublicKeyEntry(
id = "$ownerDID#delegate-$delegateIndex",
type = Secp256k1VerificationKey2018,
owner = ownerDID,
Expand All @@ -279,16 +281,13 @@ open class EthrDIDResolver(
companion object {
const val DEFAULT_REGISTRY_ADDRESS = "0xdca7ef03e98e0dc2b855be647c39abe984fcf21b"

internal const val veriKey = "veriKey"
internal const val sigAuth = "sigAuth"

private val attrTypes = mapOf(
sigAuth to "SignatureAuthentication2018",
veriKey to "VerificationKey2018"
sigAuth.name to "SignatureAuthentication2018",
veriKey.name to "VerificationKey2018"
)

private fun parseType(algo: String, rawType: String): PublicKeyType {
var type = if (rawType.isBlank()) veriKey else rawType
var type = if (rawType.isBlank()) veriKey.name else rawType
type = attrTypes[type] ?: type
return PublicKeyType("$algo$type") //will throw exception if none found
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,79 @@ class EthrDIDResolverTest {
assertThat(events).hasSize(3)
}

@Test
fun `can resolve doc for address with delegate key added`() = runBlocking {

val rpc = mockk<JsonRPC>()

coEvery {
//mock the lookup owner call to return itself
rpc.ethCall(any(), eq("0x8733d4e800000000000000000000000062d283fe6939c01fc88f02c6d2c9a547cc3e2656"))
}.returns("0x00000000000000000000000062d283fe6939c01fc88f02c6d2c9a547cc3e2656")

coEvery {
//mock the lastChanged call to point to block number 4680310 (0x476A76)
rpc.ethCall(any(), eq("0xf96d0f9f00000000000000000000000062d283fe6939c01fc88f02c6d2c9a547cc3e2656"))
}.returns("0x0000000000000000000000000000000000000000000000000000000000476A76")

coEvery {
rpc.getLogs(address = any(), topics = any(), fromBlock = any(), toBlock = any())
}.returnsMany(
listOf(
JsonRpcLogItem(
address = "0xdca7ef03e98e0dc2b855be647c39abe984fcf21b",
topics = listOf(
"0x5a5084339536bcab65f20799fcc58724588145ca054bd2be626174b27ba156f7",
"0x00000000000000000000000062d283fe6939c01fc88f02c6d2c9a547cc3e2656"
),
//this is the important bit that states that a delegate key was added
data = "0x766572694b657900000000000000000000000000000000000000000000000000000000000000000000000000cf03dd0a894ef79cb5b601a43c4b25e3ae4c67ed000000000000000000000000000000000000000000000000000000006245b1050000000000000000000000000000000000000000000000000000000000000000",
blockNumber = BigInteger("4680310"),
transactionHash = "0x5b1749dd1eb4cee09f114e5b12d82d68c9099ba38482d602f2d939f9082f71e3",
transactionIndex = BigInteger("0"),
blockHash = "0x4f1acf82e4b2578cb5a5c0fe1c3806dc89d5b28ca4946219cf1a0f04ad654fb8",
logIndex = BigInteger("0"),
removed = false
)
),
emptyList()
)

val resolver = EthrDIDResolver(rpc)
val ddo = resolver.resolve("0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656")

val expectedDDO = EthrDIDDocument.fromJson(
//language=json
"""{
"id": "did:ethr:0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656",
"publicKey": [
{
"id": "did:ethr:0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656#owner",
"type": "Secp256k1VerificationKey2018",
"owner": "did:ethr:0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656",
"ethereumAddress": "0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656"
},
{
"id": "did:ethr:0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656#delegate-1",
"type": "Secp256k1VerificationKey2018",
"owner": "did:ethr:0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656",
"ethereumAddress": "0xcf03dd0a894ef79cb5b601a43c4b25e3ae4c67ed"
}
],
"authentication": [
{
"type": "Secp256k1SignatureAuthentication2018",
"publicKey": "did:ethr:0x62d283fe6939c01fc88f02c6d2c9a547cc3e2656#owner"
}
],
"service": [],
"@context": "https://w3id.org/did/v1"
}""".trimIndent()
)

assertThat(ddo).isEqualTo(expectedDDO)
}

// "did/pub/(Secp256k1|Rsa|Ed25519)/(veriKey|sigAuth)/(hex|base64)",
private val attributeRegexes = listOf(
"did/pub/Secp256k1/veriKey/hex",
Expand Down Expand Up @@ -219,7 +292,7 @@ class EthrDIDResolverTest {
}

@Test
fun `can parse sample attr change event`() {
fun `can parse sample attr change event in history`() {
val soon = System.currentTimeMillis() / 1000 + 600
val identity = "0xf3beac30c498d9e26865f34fcaa57dbb935b0d74"
val owner = identity
Expand Down
19 changes: 11 additions & 8 deletions ethr-did/src/test/java/me/uport/sdk/ethrdid/EthrDIDTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package me.uport.sdk.ethrdid

import assertk.assertThat
import assertk.assertions.isEqualTo
import io.mockk.CapturingSlot
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import me.uport.sdk.jsonrpc.JsonRPC
Expand Down Expand Up @@ -50,30 +50,33 @@ class EthrDIDTest {
val signer = KPSigner(originalPrivKey)
val address = signer.getAddress().prepend0xPrefix()

val rawTxSlot = CapturingSlot<String>()

val rpc = mockk<JsonRPC> {
coEvery { getTransactionCount(any()) } returns BigInteger.ZERO
coEvery { getGasPrice() } returns 20_000_000_000L.toBigInteger()
coEvery {
//mock the call to getOwner to return itself
ethCall(
any(),
any()
)
} returns "0x0000000000000000000000001122334455667788990011223344556677889900"
coEvery { sendRawTransaction(any()) } returns "mockedTxHash"
coEvery { sendRawTransaction(capture(rawTxSlot)) } returns "mockedTxHash"
}

val ethrDid = EthrDID(address, rpc, rinkebyRegistry, signer)

val txHash = ethrDid.changeOwner(newAddress)
val txHash = ethrDid.changeOwner(newAddress, EthrDID.TransactionOptions(
gasLimit = 70_000L.toBigInteger(),
gasPrice = 20_000_000_000L.toBigInteger(),
nonce = BigInteger.ZERO
))

assertThat(txHash).isEqualTo("mockedTxHash")

val expectedSignedTx =
"0xf8aa808504a817c8008301117094dca7ef03e98e0dc2b855be647c39abe984fcf21b80b844f00d4b5d000000000000000000000000f3beac30c498d9e26865f34fcaa57dbb935b0d7400000000000000000000000045c4ebd7ffb86891ba6f9f68452f9f0815aacd8b1ca0eb687cc4a323d4c3471d01d3a0d3d212754539fa9d2f6973acc0f1de275f53e9a0257684845b8d3d5e0c0838c5da007ddc7a0df08722fba53866601821f0aceff4"

coVerify {
rpc.sendRawTransaction(eq(expectedSignedTx))
}
assertThat(rawTxSlot.captured).isEqualTo(expectedSignedTx)

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import me.uport.sdk.universaldid.DidResolverError
*
* Example https did: "did:https:example.com"
*/
@Deprecated(
message = "the `https` method has been deprecated in favor of the `web` method. Use the `WebDIDResolver` to make the transition",
replaceWith = ReplaceWith("WebDIDResolver(httpClient)")
)
open class HttpsDIDResolver(private val httpClient: HttpClient = HttpClient()) : DIDResolver {
override val method: String = "https"

Expand Down
Loading

0 comments on commit a7ec6ee

Please sign in to comment.