diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index 8500778a4..f5bfd27a8 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -164,3 +164,29 @@ test('Delegation targets cannot exceed 1_000', () => { 'Delegation targets cannot exceed 1000', ); }); + +test('Delegation chains cannot repeat public keys', async () => { + const root = createIdentity(0); + const middle = createIdentity(1); + const bottom = createIdentity(2); + + const rootToMiddle = await DelegationChain.create( + root, + middle.getPublicKey(), + new Date(1609459200000), + ); + const middleToBottom = await DelegationChain.create( + middle, + bottom.getPublicKey(), + new Date(1609459200000), + { + previous: rootToMiddle, + }, + ); + + expect( + DelegationChain.create(bottom, root.getPublicKey(), new Date(1609459200000), { + previous: middleToBottom, + }), + ).rejects.toThrow('Delegation chain cannot repeat public keys'); +}); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 98db784bc..4339dc27c 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -195,6 +195,18 @@ export class DelegationChain { targets?: Principal[]; } = {}, ): Promise { + if (options.previous) { + const delegatedKeys: PublicKey[] = [ + ...options.previous.delegations.map(signedDelegation => signedDelegation.delegation.pubkey), + from.getPublicKey(), + to, + ]; + const delegatedKeysSet = new Set(delegatedKeys.map(key => toHexString(key.derKey))); + if (delegatedKeys.length !== delegatedKeysSet.size) { + throw new DelegationError('Delegation chain cannot repeat public keys'); + } + } + const delegation = await _createSingleDelegation(from, to, expiration, options.targets); return new DelegationChain( [...(options.previous?.delegations || []), delegation], @@ -255,6 +267,7 @@ export class DelegationChain { protected constructor( public readonly delegations: SignedDelegation[], public readonly publicKey: DerEncodedPublicKey, + public readonly previous?: DelegationChain, ) { if (delegations.length > MAXIMUM_DELEGATION_CHAIN_LENGTH) { throw new DelegationError(