Skip to content

Commit 100bd48

Browse files
authored
fix: revoke keyAgreement key removes dangling references (#246)
* chore: deprecate linea:goerli and artis networks, update tests and config * fix: simplify ERC-1056 event types and event processing logic * refactor event processing logic to use discriminated unions and reduce type assertions * test: add regression test for revoking keyAgreement
1 parent 3c0f171 commit 100bd48

9 files changed

Lines changed: 195 additions & 123 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
2222
with:
2323
version: 11.5.0
24-
- name: 'Setup Node.js with pnpm cache'
24+
- name: 'Setup Node.js'
2525
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
2626
with:
2727
node-version: 24

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,4 @@ $RECYCLE.BIN/
229229
# End of https://www.gitignore.io/api/node,linux,macos,windows,intellij
230230

231231
dist/
232+
/planning/

src/__tests__/config.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ describe('configuration', () => {
1111
expect(contracts['mainnet']).toBeDefined()
1212
expect(contracts['0x1']).toBeDefined()
1313
expect(contracts['dev']).toBeDefined()
14-
expect(contracts['linea:goerli']).toBeDefined()
15-
expect(contracts['0xe704']).toBeDefined()
14+
expect(contracts['aurora']).toBeDefined()
15+
expect(contracts['0x4e454152']).toBeDefined()
1616
})
1717

1818
it('works with infuraProjectId and overrides', () => {
@@ -26,19 +26,19 @@ describe('configuration', () => {
2626

2727
it('works with named network', async () => {
2828
const contracts = configureResolverWithNetworks({
29-
networks: [{ name: 'linea:goerli', provider: new JsonRpcProvider('some goerli JSONRPC URL') }],
29+
networks: [{ name: 'aurora', provider: new JsonRpcProvider('some JSONRPC URL') }],
3030
})
31-
expect(contracts['linea:goerli']).toBeDefined()
32-
expect(contracts['0xe704']).toBeDefined()
31+
expect(contracts['aurora']).toBeDefined()
32+
expect(contracts['0x4e454152']).toBeDefined()
3333
})
3434

3535
it('works with single network', async () => {
3636
const contracts = configureResolverWithNetworks({
37-
name: 'linea:goerli',
38-
provider: new JsonRpcProvider('some goerli JSONRPC URL'),
37+
name: 'aurora',
38+
provider: new JsonRpcProvider('some JSONRPC URL'),
3939
})
40-
expect(contracts['linea:goerli']).toBeDefined()
41-
expect(contracts['0xe704']).toBeDefined()
40+
expect(contracts['aurora']).toBeDefined()
41+
expect(contracts['0x4e454152']).toBeDefined()
4242
})
4343

4444
it('works with single provider', async () => {
@@ -68,24 +68,24 @@ describe('configuration', () => {
6868
it('throws when no configuration is provided', () => {
6969
expect(() => {
7070
configureResolverWithNetworks()
71-
}).toThrowError('invalid_config: Please make sure to have at least one network')
71+
}).toThrow('invalid_config: Please make sure to have at least one network')
7272
})
7373

7474
it('throws when no registry is known for a network', () => {
7575
expect(() => {
7676
configureResolverWithNetworks({ networks: [{ name: 'unknown-net', rpcUrl: 'http://localhost:8545' }] })
77-
}).toThrowError('invalid_config: No registry address known for network unknown-net')
77+
}).toThrow('invalid_config: No registry address known for network unknown-net')
7878
})
7979

8080
it('throws when no relevant configuration is provided for a network', () => {
8181
expect(() => {
8282
configureResolverWithNetworks({ networks: [{ chainId: '0xbad' }] })
83-
}).toThrowError('invalid_config: No web3 provider could be determined for network')
83+
}).toThrow('invalid_config: No web3 provider could be determined for network')
8484
})
8585

8686
it('throws when malformed configuration is provided for a network', () => {
8787
expect(() => {
8888
configureResolverWithNetworks({ networks: [{ web3: '0xbad' }] })
89-
}).toThrowError('invalid_config: No web3 provider could be determined for network')
89+
}).toThrow('invalid_config: No web3 provider could be determined for network')
9090
})
9191
})

src/__tests__/resolve.attribute.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,54 @@ describe('attributes', () => {
504504
})
505505
})
506506

507+
it('revokes X25519 keyAgreement key and removes keyAgreement section', async () => {
508+
expect.assertions(2)
509+
const { address: identity, shortDID: did, signer } = await randomAccount(provider)
510+
const pubKeyBase64 = 'MCowBQYDK2VuAyEAEYVXd3/7B4d0NxpSsA/tdVYdz5deYcR1U+ZkphdmEFI='
511+
const pubKeyHex = ethers.hexlify(ethers.decodeBase64(pubKeyBase64))
512+
await new EthrDidController(did, registryContract, signer).setAttribute('did/pub/X25519/enc', pubKeyHex, 86404)
513+
const { didDocument: didDocumentBefore } = await didResolver.resolve(did)
514+
expect(didDocumentBefore).toEqual({
515+
'@context': expect.anything(),
516+
id: did,
517+
verificationMethod: [
518+
{
519+
id: `${did}#controller`,
520+
type: 'EcdsaSecp256k1RecoveryMethod2020',
521+
controller: did,
522+
blockchainAccountId: `eip155:1337:${identity}`,
523+
},
524+
{
525+
id: `${did}#delegate-1`,
526+
type: 'X25519KeyAgreementKey2020',
527+
controller: did,
528+
publicKeyMultibase: toMultibase(pubKeyHex, new Uint8Array([0xec, 0x01])),
529+
},
530+
],
531+
authentication: [`${did}#controller`],
532+
assertionMethod: [`${did}#controller`],
533+
keyAgreement: [`${did}#delegate-1`],
534+
})
535+
536+
await new EthrDidController(did, registryContract, signer).revokeAttribute('did/pub/X25519/enc', pubKeyHex)
537+
538+
const { didDocument: didDocumentAfter } = await didResolver.resolve(did)
539+
expect(didDocumentAfter).toEqual({
540+
'@context': expect.anything(),
541+
id: did,
542+
verificationMethod: [
543+
{
544+
id: `${did}#controller`,
545+
type: 'EcdsaSecp256k1RecoveryMethod2020',
546+
controller: did,
547+
blockchainAccountId: `eip155:1337:${identity}`,
548+
},
549+
],
550+
authentication: [`${did}#controller`],
551+
assertionMethod: [`${did}#controller`],
552+
})
553+
})
554+
507555
it('expires key automatically', async () => {
508556
expect.assertions(2)
509557
const { address: identity, shortDID: did, signer } = await randomAccount(provider)

src/__tests__/resolve.unregistered.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BrowserProvider } from 'ethers'
33
import { Resolvable } from 'did-resolver'
44

55
import { deployRegistry, randomAccount } from './testUtils'
6-
import { compressedSecp256k1ToJwk } from '../helpers'
6+
import { secp256k1ToJwk } from '../helpers'
77

88
describe('unregistered DIDs', () => {
99
let didResolver: Resolvable, provider: BrowserProvider
@@ -58,7 +58,7 @@ describe('unregistered DIDs', () => {
5858
id: `${longDID}#controllerKey`,
5959
type: 'EcdsaSecp256k1VerificationKey2019',
6060
controller: longDID,
61-
publicKeyJwk: compressedSecp256k1ToJwk(pubKey),
61+
publicKeyJwk: secp256k1ToJwk(pubKey),
6262
},
6363
],
6464
authentication: [`${longDID}#controller`, `${longDID}#controllerKey`],
@@ -86,7 +86,7 @@ describe('unregistered DIDs', () => {
8686
id: `${pubdid}#controllerKey`,
8787
type: 'EcdsaSecp256k1VerificationKey2019',
8888
controller: pubdid,
89-
publicKeyJwk: compressedSecp256k1ToJwk(pubKey),
89+
publicKeyJwk: secp256k1ToJwk(pubKey),
9090
},
9191
],
9292
authentication: [`${pubdid}#controller`, `${pubdid}#controllerKey`],
@@ -114,7 +114,7 @@ describe('unregistered DIDs', () => {
114114
id: `${pubdid}#controllerKey`,
115115
type: 'EcdsaSecp256k1VerificationKey2019',
116116
controller: pubdid,
117-
publicKeyJwk: compressedSecp256k1ToJwk(pubKey),
117+
publicKeyJwk: secp256k1ToJwk(pubKey),
118118
},
119119
],
120120
authentication: [`${pubdid}#controller`, `${pubdid}#controllerKey`],

src/config/deployments.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ export type EthrDidRegistryDeployment = {
4141
*/
4242
export const deployments: EthrDidRegistryDeployment[] = [
4343
{ chainId: 1, registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b', name: 'mainnet', legacyNonce: true },
44+
// // deprecated networks
4445
// { chainId: 3, registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b', name: 'ropsten', legacyNonce: true },
4546
// { chainId: 4, registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b', name: 'rinkeby', legacyNonce: true },
4647
// { chainId: 5, registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b', name: 'goerli', legacyNonce: true },
4748
{ chainId: 11155111, registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', name: 'sepolia', legacyNonce: false },
4849
{ chainId: 100, registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', name: 'gno', legacyNonce: false },
4950
{ chainId: 17000, registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', name: 'holesky', legacyNonce: false },
51+
// deprecated networks
5052
// { chainId: 42, registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b', name: 'kovan', legacyNonce: true },
5153
// // rsk networks cause socket hang up
5254
// { chainId: 30, registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b', name: 'rsk', legacyNonce: true },
@@ -70,35 +72,38 @@ export const deployments: EthrDidRegistryDeployment[] = [
7072
description: 'energy web testnet',
7173
legacyNonce: false,
7274
},
73-
{
74-
chainId: 246785,
75-
registry: '0xdCa7EF03e98e0DC2B855bE647C39ABe984fcF21B',
76-
name: 'artis:tau1',
77-
legacyNonce: true,
78-
},
79-
{
80-
chainId: 246529,
81-
registry: '0xdCa7EF03e98e0DC2B855bE647C39ABe984fcF21B',
82-
name: 'artis:sigma1',
83-
legacyNonce: true,
84-
},
75+
// // deprecated networks
76+
// {
77+
// chainId: 246785,
78+
// registry: '0xdCa7EF03e98e0DC2B855bE647C39ABe984fcF21B',
79+
// name: 'artis:tau1',
80+
// legacyNonce: true,
81+
// },
82+
// {
83+
// chainId: 246529,
84+
// registry: '0xdCa7EF03e98e0DC2B855bE647C39ABe984fcF21B',
85+
// name: 'artis:sigma1',
86+
// legacyNonce: true,
87+
// },
8588
{ chainId: 137, registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b', name: 'polygon', legacyNonce: true },
86-
{
87-
chainId: 80001,
88-
registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b',
89-
name: 'polygon:test',
90-
legacyNonce: true,
91-
},
89+
// // deprecated
90+
// {
91+
// chainId: 80001,
92+
// registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b',
93+
// name: 'polygon:test',
94+
// legacyNonce: true,
95+
// },
9296
{
9397
chainId: 1313161554,
9498
registry: '0x63eD58B671EeD12Bc1652845ba5b2CDfBff198e0',
9599
name: 'aurora',
96100
legacyNonce: true,
97101
},
98-
{
99-
chainId: 59140,
100-
registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818',
101-
name: 'linea:goerli',
102-
legacyNonce: false,
103-
},
102+
// // deprecated
103+
// {
104+
// chainId: 59140,
105+
// registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818',
106+
// name: 'linea:goerli',
107+
// legacyNonce: false,
108+
// },
104109
]

src/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const infuraNames: Record<string, string> = {
88
aurora: 'aurora-mainnet',
99
}
1010

11-
const knownInfuraNames = ['mainnet', 'aurora', 'linea:goerli', 'sepolia']
11+
const knownInfuraNames = ['mainnet', 'aurora', 'sepolia']
1212

1313
/**
1414
* A configuration entry for an ethereum network

src/helpers.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,48 @@ export type uint256 = bigint
2020
export type bytes32 = string
2121
export type bytes = string
2222

23-
export interface ERC1056Event {
23+
/**
24+
* Common fields shared by all ERC-1056 events.
25+
*/
26+
type ERC1056Base = {
2427
identity: address
2528
previousChange: uint256
26-
validTo?: bigint
27-
_eventName: string
2829
blockNumber: number
2930
}
3031

31-
export interface DIDOwnerChanged extends ERC1056Event {
32+
/**
33+
* ERC-1056 owner change event — no `validTo`.
34+
*/
35+
export type DIDOwnerChanged = ERC1056Base & {
36+
_eventName: 'DIDOwnerChanged'
3237
owner: address
3338
}
3439

35-
export interface DIDAttributeChanged extends ERC1056Event {
40+
/**
41+
* ERC-1056 attribute change event.
42+
*/
43+
export type DIDAttributeChanged = ERC1056Base & {
44+
_eventName: 'DIDAttributeChanged'
3645
name: bytes32
3746
value: bytes
3847
validTo: uint256
3948
}
4049

41-
export interface DIDDelegateChanged extends ERC1056Event {
50+
/**
51+
* ERC-1056 delegate change event.
52+
*/
53+
export type DIDDelegateChanged = ERC1056Base & {
54+
_eventName: 'DIDDelegateChanged'
4255
delegateType: bytes32
4356
delegate: address
4457
validTo: uint256
4558
}
4659

60+
/**
61+
* Discriminated union of all ERC-1056 event types.
62+
*/
63+
export type ERC1056Event = DIDOwnerChanged | DIDAttributeChanged | DIDDelegateChanged
64+
4765
export const VMTypes = {
4866
EcdsaSecp256k1VerificationKey2019: 'EcdsaSecp256k1VerificationKey2019',
4967
EcdsaSecp256k1RecoveryMethod2020: 'EcdsaSecp256k1RecoveryMethod2020',
@@ -184,7 +202,7 @@ export function toMultibase(hexValue: string, prefix?: Uint8Array): string {
184202
* Decompresses a 33-byte secp256k1 public key (hex, with or without 0x prefix) and
185203
* returns a JWK object suitable for use as `publicKeyJwk` in a DID document.
186204
*/
187-
export function compressedSecp256k1ToJwk(hex: string): JsonWebKey {
205+
export function secp256k1ToJwk(hex: string): JsonWebKey {
188206
const uncompressed = SigningKey.computePublicKey(hex.startsWith('0x') ? hex : `0x${hex}`, false)
189207
// uncompressed is 0x04 || x (32 bytes) || y (32 bytes)
190208
const raw = getBytes(uncompressed)

0 commit comments

Comments
 (0)