Skip to content

Commit

Permalink
feat: FS dids backed by jwk
Browse files Browse the repository at this point in the history
  • Loading branch information
simonas-notcat committed Feb 4, 2020
1 parent b6bd930 commit 7a93f7c
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 37 deletions.
4 changes: 3 additions & 1 deletion packages/daf-cli/src/identity-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions packages/daf-cli/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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()
Expand Down
5 changes: 4 additions & 1 deletion packages/daf-ethr-did-fs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 12 additions & 2 deletions packages/daf-ethr-did-fs/src/__tests__/identity-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
25 changes: 25 additions & 0 deletions packages/daf-ethr-did-fs/src/daf-jose.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
64 changes: 37 additions & 27 deletions packages/daf-ethr-did-fs/src/identity-provider.ts
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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 {
Expand All @@ -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 {}
}
}

Expand All @@ -50,59 +52,66 @@ export class IdentityProvider extends AbstractIdentityProvider {
}

private async getSerializedIdentity(did: string): Promise<SerializedIdentity> {
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,
})
}

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)
return this.identityFromSerialized(serialized)
}

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
}
Expand All @@ -128,10 +137,11 @@ export class IdentityProvider extends AbstractIdentityProvider {
service: { id: string; type: string; serviceEndpoint: string },
): Promise<any> {
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)
}
}
2 changes: 1 addition & 1 deletion packages/daf-selective-disclosure/src/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class ActionHandler extends AbstractActionHandler {
...data,
},
{
signer: identity.sign,
signer: identity.signer(),
alg: 'ES256K-R',
issuer: identity.did,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/daf-trust-graph/src/service-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
4 changes: 2 additions & 2 deletions packages/daf-w3c/src/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -9567,6 +9577,13 @@ [email protected], 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"
Expand Down

0 comments on commit 7a93f7c

Please sign in to comment.