diff --git a/packages/daf-cli/src/identity-manager.ts b/packages/daf-cli/src/identity-manager.ts index d09335c33..2280daa0d 100644 --- a/packages/daf-cli/src/identity-manager.ts +++ b/packages/daf-cli/src/identity-manager.ts @@ -63,7 +63,9 @@ program }, ]) - const result = await core.identityManager.deleteIdentity(answers.did) + const identity = await core.identityManager.getIdentity(answers.did) + + const result = await core.identityManager.deleteIdentity(identity.identityProviderType, identity.did) console.log('Success:', result) } catch (e) { console.error(e) diff --git a/packages/daf-cli/src/setup.ts b/packages/daf-cli/src/setup.ts index c25280090..a5a657c55 100644 --- a/packages/daf-cli/src/setup.ts +++ b/packages/daf-cli/src/setup.ts @@ -21,7 +21,7 @@ const debug = Debug('daf:cli') const defaultPath = process.env.HOME + '/.daf' -const identityStoreFilename = process.env.DAF_IDENTITY_STORE ?? defaultPath + '/identity-store.json' +const identityStoreFilename = process.env.DAF_IDENTITY_STORE ?? defaultPath + '/identity-store-jwk.json' const dataStoreFilename = process.env.DAF_DATA_STORE ?? defaultPath + '/data-store-cli.sqlite3' const infuraProjectId = process.env.DAF_INFURA_ID ?? '5ffc47f65c4042ce847ef66a3fa70d4c' @@ -47,7 +47,14 @@ if (process.env.DAF_TG_URI) TG.ServiceController.defaultUri = process.env.DAF_TG if (process.env.DAF_TG_WSURI) TG.ServiceController.defaultWsUri = process.env.DAF_TG_WSURI TG.ServiceController.webSocketImpl = ws -const identityProviders = [new EthrDidFs.IdentityProvider(identityStoreFilename)] +const identityProviders = [ + new EthrDidFs.IdentityProvider({ + fileName: identityStoreFilename, + network: 'rinkeby', + rpcUrl: 'https://rinkeby.infura.io/v3/' + infuraProjectId, + resolver: didResolver, + }), +] const serviceControllers = [TG.ServiceController] const messageValidator = new DBG.MessageValidator() diff --git a/packages/daf-ethr-did-fs/package.json b/packages/daf-ethr-did-fs/package.json index 915de454e..41def8f1e 100644 --- a/packages/daf-ethr-did-fs/package.json +++ b/packages/daf-ethr-did-fs/package.json @@ -8,12 +8,15 @@ "build": "tsc" }, "dependencies": { + "base64url": "^3.0.1", "daf-core": "^1.4.1", "daf-resolver": "^1.1.0", "debug": "^4.1.1", "ethjs-provider-signer": "^0.1.4", "ethjs-signer": "^0.1.1", - "ethr-did": "^1.1.0" + "ethr-did": "^1.1.0", + "jose": "^1.22.1", + "js-sha3": "^0.8.0" }, "devDependencies": { "@types/debug": "^4.1.5", diff --git a/packages/daf-ethr-did-fs/src/__tests__/identity-provider.test.ts b/packages/daf-ethr-did-fs/src/__tests__/identity-provider.test.ts index f8590560d..425585941 100644 --- a/packages/daf-ethr-did-fs/src/__tests__/identity-provider.test.ts +++ b/packages/daf-ethr-did-fs/src/__tests__/identity-provider.test.ts @@ -47,8 +47,18 @@ describe('daf-ethr-did-fs', () => { it('imported identity adds serviceEndpoint', async () => { const serialized = { did: 'did:ethr:rinkeby:0xf09b1640417a4270b3631306b42403fa8c45d63d', - address: '0xf09b1640417a4270b3631306b42403fa8c45d63d', - privateKey: '8a6e19d13d096514aa405c20c518f748693049393cac43a57a70bfea448852f3', + keySet: { + keys: [ + { + crv: 'secp256k1', + x: 'z93SgW9Dagt89Sts0NEIN7XMPpHli5Vr1n8nZ97-Ae4', + y: 'HracHTExUzEuhZm7auhpDgVJRGYumwhWZHdrNAvzogk', + d: '', //@TODO: figure out how to do this without revealing private keys + kty: 'EC', + kid: 'yAfcGZwuMFCvi_PZmCs7WVihA-ZAnyr0LCoafnORGk4', + }, + ], + }, } const identity = await identityProvider.importIdentity(JSON.stringify(serialized)) diff --git a/packages/daf-ethr-did-fs/src/daf-jose.ts b/packages/daf-ethr-did-fs/src/daf-jose.ts new file mode 100644 index 000000000..018a012ec --- /dev/null +++ b/packages/daf-ethr-did-fs/src/daf-jose.ts @@ -0,0 +1,25 @@ +import base64url from 'base64url' +import { keccak_256 } from 'js-sha3' +import * as jose from 'jose' + +function keccak(data: any): Buffer { + return Buffer.from(keccak_256.arrayBuffer(data)) +} +export function toEthereumAddress(hexPublicKey: string): string { + return `0x${keccak(Buffer.from(hexPublicKey.slice(2), 'hex')) + .slice(-20) + .toString('hex')}` +} + +export function convertECKeyToEthHexKeys(key: jose.JWK.ECKey) { + const bx = base64url.toBuffer(key.x) + const by = base64url.toBuffer(key.y) + const hexPrivateKey = key.d ? base64url.toBuffer(key.d).toString('hex') : '' + const hexPublicKey = '04' + Buffer.concat([bx, by], 64).toString('hex') + const address = toEthereumAddress(hexPublicKey) + return { + hexPrivateKey, + hexPublicKey, + address, + } +} diff --git a/packages/daf-ethr-did-fs/src/identity-provider.ts b/packages/daf-ethr-did-fs/src/identity-provider.ts index 49b6a4c8e..04bddbe9b 100644 --- a/packages/daf-ethr-did-fs/src/identity-provider.ts +++ b/packages/daf-ethr-did-fs/src/identity-provider.ts @@ -1,4 +1,7 @@ import { AbstractIdentityProvider, AbstractIdentity, Resolver } from 'daf-core' +import * as jose from 'jose' +import { convertECKeyToEthHexKeys } from './daf-jose' + import { EthrIdentity } from './ethr-identity' import { sign } from 'ethjs-signer' const SignerProvider = require('ethjs-provider-signer') @@ -9,13 +12,12 @@ import Debug from 'debug' const debug = Debug('daf:ethr-did-fs:identity-provider') interface SerializedIdentity { - address: string - privateKey: string did: string + keySet: jose.JSONWebKeySet } interface FileContents { - identities: SerializedIdentity[] + [did: string]: SerializedIdentity } export class IdentityProvider extends AbstractIdentityProvider { @@ -41,7 +43,7 @@ export class IdentityProvider extends AbstractIdentityProvider { const raw = fs.readFileSync(this.fileName) return JSON.parse(raw) as FileContents } catch (e) { - return { identities: [] } + return {} } } @@ -50,19 +52,27 @@ export class IdentityProvider extends AbstractIdentityProvider { } private async getSerializedIdentity(did: string): Promise { - const { identities } = this.readFromFile() - const identity = identities.find(identity => identity.did == did) + const identities = this.readFromFile() + const identity = identities[did] if (!identity) { return Promise.reject('Did not found: ' + did) } return identity } + private getEthKeysFromSerialized(serialized: SerializedIdentity) { + const keyStore = jose.JWKS.asKeyStore(serialized.keySet) + const key = keyStore.get({ kty: 'EC', crv: 'secp256k1' }) as jose.JWK.ECKey + if (!key) throw Error('Key not found') + return convertECKeyToEthHexKeys(key) + } + private identityFromSerialized(serialized: SerializedIdentity): AbstractIdentity { + const hexKeys = this.getEthKeysFromSerialized(serialized) return new EthrIdentity({ did: serialized.did, - privateKey: serialized.privateKey, - address: serialized.address, + privateKey: hexKeys.hexPrivateKey, + address: hexKeys.address, identityProviderType: this.type, rpcUrl: this.rpcUrl, resolver: this.resolver, @@ -70,17 +80,21 @@ export class IdentityProvider extends AbstractIdentityProvider { } async getIdentities() { - const { identities } = this.readFromFile() - return identities.map(this.identityFromSerialized.bind(this)) as EthrIdentity[] + const identities = this.readFromFile() + const result = [] + for (const did of Object.keys(identities)) { + result.push(this.identityFromSerialized(identities[did])) + } + return result } async createIdentity() { - const keyPair = EthrDID.createKeyPair() + const key = jose.JWK.generateSync('EC', 'secp256k1') + const hexKeys = convertECKeyToEthHexKeys(key) const serialized = { - did: 'did:ethr:' + this.network + ':' + keyPair.address, - address: keyPair.address, - privateKey: keyPair.privateKey, + did: 'did:ethr:' + this.network + ':' + hexKeys.address, + keySet: new jose.JWKS.KeyStore([key]).toJWKS(true), } this.saveIdentity(serialized) @@ -88,21 +102,16 @@ export class IdentityProvider extends AbstractIdentityProvider { } async saveIdentity(serialized: SerializedIdentity) { - const { identities } = this.readFromFile() - this.writeToFile({ - identities: [...identities, serialized], - }) - + const identities = this.readFromFile() + identities[serialized.did] = serialized + this.writeToFile(identities) debug('Saved', serialized.did) } async deleteIdentity(did: string) { - const { identities } = this.readFromFile() - if (!identities.find(identity => identity.did == did)) { - return false - } - const filteredIdentities = identities.filter(identity => identity.did != did) - this.writeToFile({ identities: filteredIdentities }) + const identities = this.readFromFile() + delete identities[did] + this.writeToFile(identities) debug('Deleted', did) return true } @@ -128,10 +137,11 @@ export class IdentityProvider extends AbstractIdentityProvider { service: { id: string; type: string; serviceEndpoint: string }, ): Promise { const serialized = await this.getSerializedIdentity(did) + const hexKeys = this.getEthKeysFromSerialized(serialized) const provider = new SignerProvider(this.rpcUrl, { - signTransaction: (rawTx: any, cb: any) => cb(null, sign(rawTx, '0x' + serialized.privateKey)), + signTransaction: (rawTx: any, cb: any) => cb(null, sign(rawTx, '0x' + hexKeys.hexPrivateKey)), }) - const ethrDid = new EthrDID({ address: serialized.address, provider }) + const ethrDid = new EthrDID({ address: hexKeys.address, provider }) return ethrDid.setAttribute('did/svc/' + service.type, service.serviceEndpoint, 86400, 100000) } } diff --git a/packages/daf-selective-disclosure/src/action-handler.ts b/packages/daf-selective-disclosure/src/action-handler.ts index a28c67965..4ec9cfc9e 100644 --- a/packages/daf-selective-disclosure/src/action-handler.ts +++ b/packages/daf-selective-disclosure/src/action-handler.ts @@ -49,7 +49,7 @@ export class ActionHandler extends AbstractActionHandler { ...data, }, { - signer: identity.sign, + signer: identity.signer(), alg: 'ES256K-R', issuer: identity.did, }, diff --git a/packages/daf-trust-graph/src/service-controller.ts b/packages/daf-trust-graph/src/service-controller.ts index 2e7a51419..40f5b54aa 100644 --- a/packages/daf-trust-graph/src/service-controller.ts +++ b/packages/daf-trust-graph/src/service-controller.ts @@ -101,7 +101,7 @@ export class ServiceController extends AbstractServiceController { exp: Math.floor(Date.now() / 1000) + 5000, // what is a reasonable value here? }, { - signer: this.identity.sign, + signer: this.identity.signer(), alg: 'ES256K-R', issuer: this.identity.did, }, diff --git a/packages/daf-w3c/src/action-handler.ts b/packages/daf-w3c/src/action-handler.ts index 9c50a1b0c..4127df37c 100644 --- a/packages/daf-w3c/src/action-handler.ts +++ b/packages/daf-w3c/src/action-handler.ts @@ -29,7 +29,7 @@ export class ActionHandler extends AbstractActionHandler { debug('Signing VP with', did) // Removing duplicate JWT data.vp.verifiableCredential = Array.from(new Set(data.vp.verifiableCredential)) - const jwt = await createPresentation(data, { did: identity.did, signer: identity.sign }) + const jwt = await createPresentation(data, { did: identity.did, signer: identity.signer() }) return jwt } catch (error) { debug(error) @@ -42,7 +42,7 @@ export class ActionHandler extends AbstractActionHandler { try { const identity = await core.identityManager.getIdentity(did) debug('Signing VC with', did) - const jwt = await createVerifiableCredential(data, { did: identity.did, signer: identity.sign }) + const jwt = await createVerifiableCredential(data, { did: identity.did, signer: identity.signer() }) return jwt } catch (error) { debug(error) diff --git a/yarn.lock b/yarn.lock index d43700fa4..1113d66e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3754,6 +3754,16 @@ asn1.js@^4.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" +asn1.js@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.3.0.tgz#439099fe9174e09cff5a54a9dda70260517e8689" + integrity sha512-WHnQJFcOrIWT1RLOkFFBQkFVvyt9BPOOrH+Dp152Zk4R993rSzXUGPmkybIcUFhHE2d/iHH+nCaOWVCDbO8fgA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -9567,6 +9577,13 @@ jest@24.9.0, jest@^24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" +jose@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/jose/-/jose-1.22.1.tgz#c8681ce92cf6dbe768b3e24f86049b652e3ca680" + integrity sha512-0TV4InJSlSFNXM3a9q3mwvk+hsp03G2/rHbxhR82wV1HbtJXt+5s0dazoYyfsNhLGDfxhN1htw8I0rBLLmtfLA== + dependencies: + asn1.js "^5.3.0" + js-levenshtein@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"