Skip to content

Commit

Permalink
feat: add support for meta transactions (uport-project#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
strumswell authored Sep 8, 2022
1 parent dee836b commit 0ee5d98
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 9 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@
"@ethersproject/wallet": "^5.6.2",
"did-jwt": "^6.3.0",
"did-resolver": "^4.0.0",
"ethr-did-resolver": "^6.2.1"
"ethr-did-resolver": "^6.2.2"
}
}
196 changes: 194 additions & 2 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Resolver, Resolvable } from 'did-resolver'
import { Resolvable, Resolver } from 'did-resolver'
import { Contract, ContractFactory } from '@ethersproject/contracts'
import { JsonRpcProvider } from '@ethersproject/providers'
import { getResolver } from 'ethr-did-resolver'
import { EthrDID, DelegateTypes, KeyPair } from '../index'
import { DelegateTypes, EthrDID, KeyPair } from '../index'
import { createProvider, sleep } from './testUtils'
import DidRegistryContract from 'ethr-did-registry'
import { verifyJWT } from 'did-jwt'
import { arrayify } from '@ethersproject/bytes'
import { SigningKey } from '@ethersproject/signing-key'

jest.setTimeout(30000)

Expand Down Expand Up @@ -763,3 +765,193 @@ describe('EthrDID', () => {
})
})
})

describe('EthrDID (Meta Transactions)', () => {
let ethrDid: EthrDID,
walletSigner: EthrDID,
registry: string,
registryContract: Contract,
accounts: string[],
did: string,
identity: string,
owner: string,
delegate1: string,
delegate2: string,
walletIdentity: string,
resolver: Resolvable

const provider: JsonRpcProvider = createProvider()

beforeAll(async () => {
const factory = ContractFactory.fromSolidity(DidRegistryContract).connect(provider.getSigner(0))

registryContract = await factory.deploy()
registryContract = await registryContract.deployed()

await registryContract.deployTransaction.wait()

registry = registryContract.address

accounts = await provider.listAccounts()

identity = accounts[1]
owner = accounts[2]
delegate1 = accounts[3]
delegate2 = accounts[4]
walletIdentity = accounts[5]
did = `did:ethr:dev:${identity}`

resolver = new Resolver(getResolver({ name: 'dev', provider, registry, chainId: 1337 }))
ethrDid = new EthrDID({
provider,
registry,
identifier: identity,
chainNameOrId: 'dev',
})
walletSigner = new EthrDID({
provider,
registry,
identifier: identity,
txSigner: provider.getSigner(walletIdentity),
chainNameOrId: 'dev',
})
})

const currentOwnerPrivateKey = arrayify('0x0000000000000000000000000000000000000000000000000000000000000001')

it('add delegates via meta transaction', async () => {
// Add first delegate
const delegateType = DelegateTypes.sigAuth
const exp = 86400
const hash1 = await ethrDid.createAddDelegateHash(delegateType, delegate1, exp)
const signature1 = new SigningKey(currentOwnerPrivateKey).signDigest(hash1)

await walletSigner.addDelegateSigned(
delegate1,
{
sigV: signature1.v,
sigR: signature1.r,
sigS: signature1.s,
},
{ delegateType: DelegateTypes.sigAuth, expiresIn: exp }
)

// Add second delegate
const hash2 = await ethrDid.createAddDelegateHash(delegateType, delegate2, exp)
const signature2 = new SigningKey(currentOwnerPrivateKey).signDigest(hash2)

await walletSigner.addDelegateSigned(
delegate2,
{
sigV: signature2.v,
sigR: signature2.r,
sigS: signature2.s,
},
{ delegateType: DelegateTypes.sigAuth, expiresIn: exp }
)
})

it('remove delegate1 via meta transaction', async () => {
const delegateType = DelegateTypes.sigAuth
const hash = await ethrDid.createRevokeDelegateHash(delegateType, delegate1)
const signature = new SigningKey(currentOwnerPrivateKey).signDigest(hash)

await walletSigner.revokeDelegateSigned(delegate1, DelegateTypes.sigAuth, {
sigV: signature.v,
sigR: signature.r,
sigS: signature.s,
})
})

it('add attributes via meta transaction', async () => {
// Add first attribute
const attributeName = 'did/svc/testService'
const serviceEndpointParams = { uri: 'https://didcomm.example.com', transportType: 'http' }
const attributeValue = JSON.stringify(serviceEndpointParams)
const attributeExpiration = 86400
const hash1 = await ethrDid.createSetAttributeHash(attributeName, attributeValue, attributeExpiration)
const signature1 = new SigningKey(currentOwnerPrivateKey).signDigest(hash1)

await walletSigner.setAttributeSigned(attributeName, attributeValue, attributeExpiration, {
sigV: signature1.v,
sigR: signature1.r,
sigS: signature1.s,
})

// Add second attribute
const attributeName2 = 'did/svc/test2Service'
const hash2 = await ethrDid.createSetAttributeHash(attributeName2, attributeValue, attributeExpiration)
const signature2 = new SigningKey(currentOwnerPrivateKey).signDigest(hash2)

await walletSigner.setAttributeSigned(attributeName2, attributeValue, attributeExpiration, {
sigV: signature2.v,
sigR: signature2.r,
sigS: signature2.s,
})
})

it('revoke attribute for testService via meta transaction', async () => {
const attributeName = 'did/svc/testService'
const serviceEndpointParams = { uri: 'https://didcomm.example.com', transportType: 'http' }
const attributeValue = JSON.stringify(serviceEndpointParams)
const hash = await ethrDid.createRevokeAttributeHash(attributeName, attributeValue)
const signature = new SigningKey(currentOwnerPrivateKey).signDigest(hash)

await walletSigner.revokeAttributeSigned(attributeName, attributeValue, {
sigV: signature.v,
sigR: signature.r,
sigS: signature.s,
})
})

it('change owner via meta transaction', async () => {
const nextOwner = accounts[2]
const hash = await ethrDid.createChangeOwnerHash(nextOwner)
const signature = new SigningKey(currentOwnerPrivateKey).signDigest(hash)

await walletSigner.changeOwnerSigned(nextOwner, {
sigV: signature.v,
sigR: signature.r,
sigS: signature.s,
})
})

it('resolves document and verify changes', async () => {
const nextOwner = accounts[2]
const resolved = await resolver.resolve(did)
expect(resolved.didDocumentMetadata).toEqual({
versionId: '8',
updated: expect.anything(),
})
expect(resolved.didDocument).toEqual({
'@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/secp256k1recovery-2020/v2'],
id: did,
verificationMethod: [
{
id: `${did}#controller`,
type: 'EcdsaSecp256k1RecoveryMethod2020',
controller: did,
blockchainAccountId: `eip155:1337:${nextOwner}`,
},
{
id: `${did}#delegate-2`,
type: 'EcdsaSecp256k1RecoveryMethod2020',
controller: did,
blockchainAccountId: `eip155:1337:${delegate2}`,
},
],
authentication: [`${did}#controller`, `${did}#delegate-2`],
assertionMethod: [`${did}#controller`, `${did}#delegate-2`],
service: [
{
id: `${did}#service-2`,
type: 'test2Service',
serviceEndpoint: {
uri: 'https://didcomm.example.com',
transportType: 'http',
},
},
],
})
})
})
113 changes: 111 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as base64 from '@ethersproject/base64'
import { hexlify, hexValue, isBytes } from '@ethersproject/bytes'
import { Base58 } from '@ethersproject/basex'
import { toUtf8Bytes } from '@ethersproject/strings'
import { EthrDidController, interpretIdentifier, REGISTRY } from 'ethr-did-resolver'
import { EthrDidController, interpretIdentifier, MetaSignature, REGISTRY } from 'ethr-did-resolver'
import { Resolvable } from 'did-resolver'

export enum DelegateTypes {
Expand Down Expand Up @@ -128,6 +128,22 @@ export class EthrDID {
return receipt.transactionHash
}

async createChangeOwnerHash(newOwner: string): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
return this.controller.createChangeOwnerHash(newOwner)
}

async changeOwnerSigned(newOwner: string, signature: MetaSignature, txOptions: CallOverrides): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
const receipt = await this.controller.changeOwnerSigned(newOwner, signature, txOptions)
this.owner = newOwner
return receipt.transactionHash
}

async addDelegate(
delegate: string,
delegateOptions?: DelegateOptions,
Expand All @@ -146,6 +162,32 @@ export class EthrDID {
return receipt.transactionHash
}

async createAddDelegateHash(delegateType: string, delegateAddress: string, exp: number): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
return this.controller.createAddDelegateHash(delegateType, delegateAddress, exp)
}

async addDelegateSigned(
delegate: string,
signature: MetaSignature,
delegateOptions?: DelegateOptions,
txOptions: CallOverrides = {}
): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
const receipt = await this.controller.addDelegateSigned(
delegateOptions?.delegateType || DelegateTypes.veriKey,
delegate,
delegateOptions?.expiresIn || 86400,
signature,
txOptions
)
return receipt.transactionHash
}

async revokeDelegate(
delegate: string,
delegateType = DelegateTypes.veriKey,
Expand All @@ -159,11 +201,31 @@ export class EthrDID {
return receipt.transactionHash
}

async createRevokeDelegateHash(delegateType: string, delegateAddress: string): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
return this.controller.createRevokeDelegateHash(delegateType, delegateAddress)
}

async revokeDelegateSigned(
delegate: string,
delegateType = DelegateTypes.veriKey,
signature: MetaSignature,
txOptions: CallOverrides = {}
): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
const receipt = await this.controller.revokeDelegateSigned(delegateType, delegate, signature, txOptions)
return receipt.transactionHash
}

async setAttribute(
key: string,
value: string | Uint8Array,
expiresIn = 86400,
/** @deprecated, please use txOptions.gasLimit */
/** @deprecated please use `txOptions.gasLimit` */
gasLimit?: number,
txOptions: CallOverrides = {}
): Promise<string> {
Expand All @@ -179,6 +241,33 @@ export class EthrDID {
return receipt.transactionHash
}

async createSetAttributeHash(attrName: string, attrValue: string, exp: number) {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
return this.controller.createSetAttributeHash(attrName, attrValue, exp)
}

async setAttributeSigned(
key: string,
value: string | Uint8Array,
expiresIn = 86400,
signature: MetaSignature,
txOptions: CallOverrides = {}
): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
const receipt = await this.controller.setAttributeSigned(
key,
attributeToHex(key, value),
expiresIn,
signature,
txOptions
)
return receipt.transactionHash
}

async revokeAttribute(
key: string,
value: string | Uint8Array,
Expand All @@ -198,6 +287,26 @@ export class EthrDID {
return receipt.transactionHash
}

async createRevokeAttributeHash(attrName: string, attrValue: string) {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
return this.controller.createRevokeAttributeHash(attrName, attrValue)
}

async revokeAttributeSigned(
key: string,
value: string | Uint8Array,
signature: MetaSignature,
txOptions: CallOverrides = {}
): Promise<string> {
if (typeof this.controller === 'undefined') {
throw new Error('a web3 provider configuration is needed for network operations')
}
const receipt = await this.controller.revokeAttributeSigned(key, attributeToHex(key, value), signature, txOptions)
return receipt.transactionHash
}

// Create a temporary signing delegate able to sign JWT on behalf of identity
async createSigningDelegate(
delegateType = DelegateTypes.veriKey,
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4826,10 +4826,10 @@ [email protected]:
resolved "https://registry.yarnpkg.com/ethr-did-registry/-/ethr-did-registry-0.0.3.tgz#f363d2c73cb9572b57bd7a5c9c90c88485feceb5"
integrity sha512-4BPvMGkxAK9vTduCq6D5b8ZqjteD2cvDIPPriXP6nnmPhWKFSxypo+AFvyQ0omJGa0cGTR+dkdI/8jiF7U/qaw==

ethr-did-resolver@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ethr-did-resolver/-/ethr-did-resolver-6.2.1.tgz#2edced98b2c165a29a01362cc1d9685edbdfa063"
integrity sha512-vQCMWfNvkNSEPHpJby4sUIOVdf4pLN6WCrXFNNjfcrQHcZK0TON1/fLOe74E8GOleaRcai4MkAeJrZjU9rd+Zw==
ethr-did-resolver@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/ethr-did-resolver/-/ethr-did-resolver-6.2.2.tgz#80b3f3358beba0f973474f03d9841556705afc7e"
integrity sha512-J77kz4YTdvpkxNVXMeMP1HNi6+UDKEgiCvZkao4vVlvYIySlYsp/GUwUNLoh/SVtodHwyRG9eqqa3gb5R1ZsGA==
dependencies:
"@ethersproject/abi" "^5.6.3"
"@ethersproject/abstract-signer" "^5.6.2"
Expand Down

0 comments on commit 0ee5d98

Please sign in to comment.