Skip to content

Commit

Permalink
feat(did-provider-ethr): add signOnly flag and logic for add/remove k…
Browse files Browse the repository at this point in the history
…ey/service (#1389)
  • Loading branch information
radleylewis authored Jun 13, 2024
1 parent 83cc115 commit 2110590
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 24 deletions.
189 changes: 189 additions & 0 deletions packages/did-provider-ethr/src/__tests__/ethr-did-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { IDIDManager, IKeyManager, MinimalImportableKey } from '@veramo/core-types'
import { DIDManager, MemoryDIDStore } from '../../../did-manager/src'
import { EthrDIDProvider } from '../ethr-did-provider'
import { createAgent } from '../../../core/src'
import { KeyManager, MemoryKeyStore, MemoryPrivateKeyStore } from '../../../key-manager/src'
import { KeyManagementSystem } from '../../../kms-local/src'

const PROVIDER = 'did:ethr:sepolia'
const MOCK_DID = 'did:ethr:sepolia:0x76d331386cec35862a73aabdbfa5ef97cdac58cf'
const KMS = 'local'
const CONTROLLER_KEY = Object.freeze({
type: 'Secp256k1',
kid: 'controller-key',
publicKeyHex:
'3fdecd32358c5c3343f1096217baaf4ffffc68d64f5d82f3fb924e60191f6f3c0a46161f0f425d2c48ced2ec9a95f6af1993cdf0525250a4702407bce37f6269',
privateKeyHex: 'f090b28a3bd279049710908e913d1644df061a6316fe3e17792a18e5267c4bd5',
meta: {
algorithms: [
'ES256K',
'ES256K-R',
'eth_signTransaction',
'eth_signTypedData',
'eth_signMessage',
'eth_rawSign',
],
},
kms: KMS,
}) satisfies MinimalImportableKey

const INFURA_API_KEY = '3586660d179141e3801c3895de1c2eba'

const ethrDidProvider = new EthrDIDProvider({
defaultKms: KMS,
network: 'sepolia',
registry: '0x03d5003bf0e79c5f5223588f347eba39afbc3818',
rpcUrl: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,
})

const agent = createAgent<IKeyManager, IDIDManager>({
plugins: [
new KeyManager({
store: new MemoryKeyStore(),
kms: {
[KMS]: new KeyManagementSystem(new MemoryPrivateKeyStore()),
},
}),
new DIDManager({
providers: { [PROVIDER]: ethrDidProvider },
defaultProvider: PROVIDER,
store: new MemoryDIDStore(),
}),
],
})

describe('EthrDIDProvider', () => {
beforeAll(async () => {
await agent.keyManagerImport(CONTROLLER_KEY)
await agent.didManagerImport({
did: MOCK_DID,
provider: PROVIDER,
controllerKeyId: CONTROLLER_KEY.kid,
keys: [CONTROLLER_KEY],
services: [],
})
})

describe('adding keys', () => {
it('returns the signed addKey transaction parameters for an Ed25519 key type', async () => {
expect.assertions(11)
const ed25519Key: MinimalImportableKey = {
kms: KMS,
kid: 'test-ed25519-key',
type: 'Ed25519',
privateKeyHex: 'f88e9aa3dd651d1abdfb6770159d81d2564728eff8b683b0a3041cf277b3ded2',
publicKeyHex: 'cfdf62bdafc9fa7add58270ff29d499d649a85d0e906a1e1a92c877188d6b163',
meta: { algorithms: ['EdDSA', 'Ed25519'] },
}
const importedEd25519Key = await agent.keyManagerImport(ed25519Key)
const params = {
did: MOCK_DID,
key: importedEd25519Key,
options: {
signOnly: true,
},
}
const [attrName, attrValue, ttl, signature, options] = await agent.didManagerAddKey(params)
expect(attrName).toEqual('did/pub/Ed25519/veriKey/hex')
expect(attrValue).toContain(ed25519Key.publicKeyHex)
expect(attrValue.slice(0, 2)).toBe('0x')
expect(ttl).toBe(86_400)
expect(signature).toBeDefined()
expect(typeof signature.sigV).toBe('number')
expect(typeof signature.sigR).toBe('string')
expect(signature.sigR).toContain('0x')
expect(typeof signature.sigS).toBe('string')
expect(signature.sigS).toContain('0x')
expect(options).toEqual({ signOnly: true, gasLimit: 100_000 })
})

it('returns the signed removeKey transaction parameters for an Ed25519 key type', async () => {
expect.assertions(10)
const ed25519Key: MinimalImportableKey = {
kms: KMS,
kid: 'test-ed25519-key-remove',
type: 'Ed25519',
privateKeyHex: 'f88e9aa3dd651d1abdfb6770159d81d2564728eff8b683b0a3041cf277b3ded2',
publicKeyHex: 'cfdf62bdafc9fa7add58270ff29d499d649a85d0e906a1e1a92c877188d6b163',
meta: { algorithms: ['EdDSA', 'Ed25519'] },
}
const importedEd25519Key = await agent.keyManagerImport(ed25519Key)
await agent.didManagerImport({
did: MOCK_DID,
provider: PROVIDER,
controllerKeyId: CONTROLLER_KEY.kid,
keys: [CONTROLLER_KEY, ed25519Key],
})
const params = {
did: MOCK_DID,
kid: importedEd25519Key.kid,
options: {
signOnly: true,
},
}
const [attrName, attrValue, signature, options] = await agent.didManagerRemoveKey(params)
console.log('attrName', attrName)
console.log('attrValue', attrValue)
console.log('signature', signature)
console.log('options', options)
expect(attrName).toEqual('did/pub/Ed25519/veriKey/hex')
expect(attrValue).toContain(ed25519Key.publicKeyHex)
expect(attrValue.slice(0, 2)).toBe('0x')
expect(signature).toBeDefined()
expect(typeof signature.sigV).toBe('number')
expect(typeof signature.sigR).toBe('string')
expect(signature.sigR).toContain('0x')
expect(typeof signature.sigS).toBe('string')
expect(signature.sigS).toContain('0x')
expect(options).toEqual({ signOnly: true, gasLimit: 100_000 })
})
})

describe('adding services', () => {
it('returns the signed add service endpoint transaction parameters', async () => {
expect.assertions(10)
const type = 'DIDCommMessaging'
const params = {
did: MOCK_DID,
service: {
id: `${MOCK_DID}#${type}`,
type,
serviceEndpoint: 'this-is-a-new-service-endpoint',
},
options: { signOnly: true },
}

const [attrName, attrValue, ttl, signature, options] = await agent.didManagerAddService(params)
expect(attrName).toEqual('did/svc/DIDCommMessaging')
expect(attrValue).toBe('this-is-a-new-service-endpoint')
expect(ttl).toBe(86_400)
expect(signature).toBeDefined()
expect(typeof signature.sigV).toBe('number')
expect(typeof signature.sigR).toBe('string')
expect(signature.sigR).toContain('0x')
expect(typeof signature.sigS).toBe('string')
expect(signature.sigS).toContain('0x')
expect(options).toEqual({ signOnly: true, gasLimit: 100_000 })
})

it('returns the signed remove service endpoint transaction parameters', async () => {
expect.assertions(9)
const type = 'DIDCommMessaging'
const params = {
did: MOCK_DID,
id: `${MOCK_DID}#${type}`,
options: { signOnly: true },
}
const [attrName, attrValue, signature, options] = await agent.didManagerRemoveService(params)
expect(attrName).toEqual('did/svc/DIDCommMessaging')
expect(attrValue).toBe('this-is-a-new-service-endpoint')
expect(signature).toBeDefined()
expect(typeof signature.sigV).toBe('number')
expect(typeof signature.sigR).toBe('string')
expect(signature.sigR).toContain('0x')
expect(typeof signature.sigS).toBe('string')
expect(signature.sigS).toContain('0x')
expect(options).toEqual({ signOnly: true, gasLimit: 100_000 })
})
})
})
83 changes: 59 additions & 24 deletions packages/did-provider-ethr/src/ethr-did-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface TransactionOptions extends TransactionRequest {
ttl?: number
encoding?: string
metaIdentifierKeyId?: string
signOnly?: boolean
}

/**
Expand Down Expand Up @@ -182,17 +183,17 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {
{ kms, options }: { kms?: string; options?: CreateDidEthrOptions },
context: IRequiredContext,
): Promise<Omit<IIdentifier, 'provider'>> {
const key = await context.agent.keyManagerCreate({kms: kms || this.defaultKms, type: 'Secp256k1'})
const key = await context.agent.keyManagerCreate({ kms: kms || this.defaultKms, type: 'Secp256k1' })
const compressedPublicKey = SigningKey.computePublicKey(`0x${key.publicKeyHex}`, true)

let networkSpecifier
if(options?.network) {
if(typeof options.network === 'number') {
if (options?.network) {
if (typeof options.network === 'number') {
networkSpecifier = BigInt(options?.network)
} else {
networkSpecifier = options?.network
}
} else if(options?.providerName?.match(/^did:ethr:.+$/)) {
} else if (options?.providerName?.match(/^did:ethr:.+$/)) {
networkSpecifier = options?.providerName?.substring(9)
} else {
networkSpecifier = undefined
Expand All @@ -206,9 +207,7 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {
}
if (typeof networkSpecifier === 'bigint' || typeof networkSpecifier === 'number') {
networkSpecifier =
network.name && network.name.length > 0
? network.name
: BigInt(options?.network || 1).toString(16)
network.name && network.name.length > 0 ? network.name : BigInt(options?.network || 1).toString(16)
}
const networkString = networkSpecifier && networkSpecifier !== 'mainnet' ? `${networkSpecifier}:` : ''
const identifier: Omit<IIdentifier, 'provider'> = {
Expand Down Expand Up @@ -236,20 +235,20 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {
return true
}

private getNetworkFor(networkSpecifier: string | number | bigint | undefined): EthrNetworkConfiguration | undefined {
private getNetworkFor(
networkSpecifier: string | number | bigint | undefined,
): EthrNetworkConfiguration | undefined {
let networkNameOrId: string | number | bigint = networkSpecifier || 'mainnet'
let network = this.networks.find(
(n) => {
if(n.chainId) {
if(typeof networkSpecifier === 'bigint') {
if(BigInt(n.chainId) === networkNameOrId) return n
} else {
if(n.chainId === networkNameOrId) return n
}
let network = this.networks.find((n) => {
if (n.chainId) {
if (typeof networkSpecifier === 'bigint') {
if (BigInt(n.chainId) === networkNameOrId) return n
} else {
if (n.chainId === networkNameOrId) return n
}
if(n.name === networkNameOrId || n.description === networkNameOrId) return n
},
)
}
if (n.name === networkNameOrId || n.description === networkNameOrId) return n
})
if (!network && !networkSpecifier && this.networks.length === 1) {
network = this.networks[0]
}
Expand Down Expand Up @@ -278,7 +277,7 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {
throw new Error(`invalid_argument: cannot find network for ${identifier.did}`)
}

if(!network.provider) {
if (!network.provider) {
throw new Error(`Provider was not found for network ${identifier.did}`)
}

Expand Down Expand Up @@ -331,7 +330,14 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {
const attrValue = '0x' + key.publicKeyHex
const ttl = options?.ttl || this.ttl || 86400
const gasLimit = options?.gasLimit || this.gas || DEFAULT_GAS_LIMIT
if (options?.metaIdentifierKeyId) {
if (options?.signOnly) {
const metaHash = await ethrDid.createSetAttributeHash(attrName, attrValue, ttl)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, identifier, metaHash)
debug('ethrDid.addKeySigned %o', { attrName, attrValue, ttl, gasLimit })
delete options.metaIdentifierKeyId
const signature = { sigV: canonicalSignature.v, sigR: canonicalSignature.r, sigS: canonicalSignature.s }
return [attrName, attrValue, ttl, signature, { ...options, gasLimit }]
} else if (options?.metaIdentifierKeyId) {
const metaHash = await ethrDid.createSetAttributeHash(attrName, attrValue, ttl)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, identifier, metaHash)

Expand Down Expand Up @@ -381,7 +387,14 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {

debug('ethrDid.setAttribute %o', { attrName, attrValue, ttl, gasLimit })

if (options?.metaIdentifierKeyId) {
if (options?.signOnly) {
const metaHash = await ethrDid.createSetAttributeHash(attrName, attrValue, ttl)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, identifier, metaHash)
debug('ethrDid.addKeySigned %o', { attrName, attrValue, ttl, gasLimit })
delete options.metaIdentifierKeyId
const signature = { sigV: canonicalSignature.v, sigR: canonicalSignature.r, sigS: canonicalSignature.s }
return [attrName, attrValue, ttl, signature, { ...options, gasLimit }]
} else if (options?.metaIdentifierKeyId) {
const metaHash = await ethrDid.createSetAttributeHash(attrName, attrValue, ttl)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, identifier, metaHash)

Expand Down Expand Up @@ -424,8 +437,15 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {
const attrName = `did/pub/${key.type}/${usg}/${encoding}`
const attrValue = '0x' + key.publicKeyHex
const gasLimit = args.options?.gasLimit || this.gas || DEFAULT_GAS_LIMIT
if (args.options?.signOnly) {
const metaHash = await ethrDid.createRevokeAttributeHash(attrName, attrValue)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, args.identifier, metaHash)

if (args.options?.metaIdentifierKeyId) {
debug('ethrDid.revokeAttributeSigned %o', { attrName, attrValue, gasLimit })
delete args.options.metaIdentifierKeyId
const signature = { sigV: canonicalSignature.v, sigR: canonicalSignature.r, sigS: canonicalSignature.s }
return [attrName, attrValue, signature, { ...args.options, gasLimit }]
} else if (args.options?.metaIdentifierKeyId) {
const metaHash = await ethrDid.createRevokeAttributeHash(attrName, attrValue)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, args.identifier, metaHash)

Expand Down Expand Up @@ -474,7 +494,22 @@ export class EthrDIDProvider extends AbstractIdentifierProvider {
: JSON.stringify(service.serviceEndpoint)
const gasLimit = args.options?.gasLimit || this.gas || DEFAULT_GAS_LIMIT

if (args.options?.metaIdentifierKeyId) {
if (args.options?.signOnly) {
const metaHash = await ethrDid.createRevokeAttributeHash(attrName, attrValue)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, args.identifier, metaHash)

debug('ethrDid.revokeAttributeSigned %o', { attrName, attrValue, gasLimit })
delete args.options.metaIdentifierKeyId
return [
attrName,
attrValue,
{ sigV: canonicalSignature.v, sigR: canonicalSignature.r, sigS: canonicalSignature.s },
{
...args.options,
gasLimit,
},
]
} else if (args.options?.metaIdentifierKeyId) {
const metaHash = await ethrDid.createRevokeAttributeHash(attrName, attrValue)
const canonicalSignature = await EthrDIDProvider.createMetaSignature(context, args.identifier, metaHash)

Expand Down

0 comments on commit 2110590

Please sign in to comment.