Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/crypto/keys.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, it, expect } from 'vitest';
import { generateDefaultIdentityKeysHD, generateDefaultIdentityKeys } from './keys.js';
import { generateNewMnemonic } from './hd.js';

/**
* Tests for identity key generation in the create flow.
*
* The create-identity path calls generateDefaultIdentityKeysHD() which must
* produce all required keys including both ENCRYPTION and DECRYPTION.
*/
describe('generateDefaultIdentityKeysHD', () => {
const network = 'testnet' as const;
const mnemonic = generateNewMnemonic(128);

it('produces the correct key layout for identity creation', () => {
const keys = generateDefaultIdentityKeysHD(network, mnemonic);
const layout = keys.map((k) => ({
id: k.id,
name: k.name,
purpose: k.purpose,
securityLevel: k.securityLevel,
keyType: k.keyType,
}));

expect(layout).toEqual([
{ id: 0, name: 'Master', purpose: 'AUTHENTICATION', securityLevel: 'MASTER', keyType: 'ECDSA_SECP256K1' },
{ id: 1, name: 'High Auth', purpose: 'AUTHENTICATION', securityLevel: 'HIGH', keyType: 'ECDSA_SECP256K1' },
{ id: 2, name: 'Critical Auth', purpose: 'AUTHENTICATION', securityLevel: 'CRITICAL', keyType: 'ECDSA_SECP256K1' },
{ id: 3, name: 'Transfer', purpose: 'TRANSFER', securityLevel: 'CRITICAL', keyType: 'ECDSA_SECP256K1' },
{ id: 4, name: 'Encryption', purpose: 'ENCRYPTION', securityLevel: 'MEDIUM', keyType: 'ECDSA_SECP256K1' },
{ id: 5, name: 'Decryption', purpose: 'DECRYPTION', securityLevel: 'MEDIUM', keyType: 'ECDSA_SECP256K1' },
]);
});

});

describe('generateDefaultIdentityKeys (deprecated)', () => {
it('produces the same key layout as the HD variant', () => {
const keys = generateDefaultIdentityKeys('testnet');
expect(keys).toHaveLength(6);
const purposes = keys.map((k) => k.purpose);
expect(purposes).toContain('ENCRYPTION');
expect(purposes).toContain('DECRYPTION');
});
});
4 changes: 3 additions & 1 deletion src/crypto/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function updateKeyType(
}

/**
* Generate default identity keys (5 keys)
* Generate default identity keys (6 keys)
* @deprecated Use generateDefaultIdentityKeysHD for HD derivation
*/
export function generateDefaultIdentityKeys(
Expand All @@ -137,6 +137,7 @@ export function generateDefaultIdentityKeys(
generateIdentityKey(2, 'Critical Auth', 'ECDSA_SECP256K1', 'AUTHENTICATION', 'CRITICAL', network),
generateIdentityKey(3, 'Transfer', 'ECDSA_SECP256K1', 'TRANSFER', 'CRITICAL', network),
generateIdentityKey(4, 'Encryption', 'ECDSA_SECP256K1', 'ENCRYPTION', 'MEDIUM', network),
generateIdentityKey(5, 'Decryption', 'ECDSA_SECP256K1', 'DECRYPTION', 'MEDIUM', network),
];
}

Expand Down Expand Up @@ -214,6 +215,7 @@ export function generateDefaultIdentityKeysHD(
generateIdentityKeyFromMnemonic(2, 'Critical Auth', 'ECDSA_SECP256K1', 'AUTHENTICATION', 'CRITICAL', network, mnemonic, 2),
generateIdentityKeyFromMnemonic(3, 'Transfer', 'ECDSA_SECP256K1', 'TRANSFER', 'CRITICAL', network, mnemonic, 3),
generateIdentityKeyFromMnemonic(4, 'Encryption', 'ECDSA_SECP256K1', 'ENCRYPTION', 'MEDIUM', network, mnemonic, 4),
generateIdentityKeyFromMnemonic(5, 'Decryption', 'ECDSA_SECP256K1', 'DECRYPTION', 'MEDIUM', network, mnemonic, 5),
];
}

Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type KeyType = 'ECDSA_SECP256K1' | 'ECDSA_HASH160';
/**
* Key purposes supported by Dash Platform
*/
export type KeyPurpose = 'AUTHENTICATION' | 'ENCRYPTION' | 'TRANSFER' | 'VOTING' | 'OWNER';
export type KeyPurpose = 'AUTHENTICATION' | 'ENCRYPTION' | 'DECRYPTION' | 'TRANSFER' | 'VOTING' | 'OWNER';

/**
* Security levels supported by Dash Platform
Expand Down
5 changes: 4 additions & 1 deletion src/ui/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getAssetLockDerivationPath } from '../crypto/hd.js';

// Available options for key configuration
const KEY_TYPES: KeyType[] = ['ECDSA_SECP256K1', 'ECDSA_HASH160'];
const KEY_PURPOSES: KeyPurpose[] = ['AUTHENTICATION', 'ENCRYPTION', 'TRANSFER', 'VOTING', 'OWNER'];
const KEY_PURPOSES: KeyPurpose[] = ['AUTHENTICATION', 'ENCRYPTION', 'DECRYPTION', 'TRANSFER', 'VOTING', 'OWNER'];
const SECURITY_LEVELS: SecurityLevel[] = ['MASTER', 'CRITICAL', 'HIGH', 'MEDIUM'];

/**
Expand All @@ -20,6 +20,9 @@ function getAllowedSecurityLevels(purpose: KeyPurpose, includeMaster = true): Se
if (purpose === 'TRANSFER') {
return ['CRITICAL'];
}
if (purpose === 'ENCRYPTION' || purpose === 'DECRYPTION') {
return ['MEDIUM'];
}
return includeMaster ? SECURITY_LEVELS : SECURITY_LEVELS.filter(s => s !== 'MASTER');
}

Expand Down
75 changes: 75 additions & 0 deletions src/ui/state.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, it, expect } from 'vitest';
import { createInitialState, setMode, updateIdentityKey, updateManageNewKey } from './state.js';

describe('setMode("create") state path', () => {
const initial = createInitialState('testnet');

it('transitions to configure_keys with mnemonic and all 6 identity keys including ENCRYPTION and DECRYPTION', () => {
const state = setMode(initial, 'create');

expect(state.step).toBe('configure_keys');
expect(state.mode).toBe('create');
expect(state.mnemonic).toBeTruthy();
expect(state.identityKeys).toHaveLength(6);

const purposes = state.identityKeys.map((k) => k.purpose);
expect(purposes).toContain('ENCRYPTION');
expect(purposes).toContain('DECRYPTION');
});
});

describe('updateIdentityKey security level coercion', () => {
const state = setMode(createInitialState('testnet'), 'create');

it('coerces DECRYPTION purpose to MEDIUM security level', () => {
const decKey = state.identityKeys.find(k => k.purpose === 'DECRYPTION')!;
const updated = updateIdentityKey(state, decKey.id, { securityLevel: 'CRITICAL' });
const key = updated.identityKeys.find(k => k.id === decKey.id)!;
expect(key.securityLevel).toBe('MEDIUM');
});

it('coerces ENCRYPTION purpose to MEDIUM security level', () => {
const encKey = state.identityKeys.find(k => k.purpose === 'ENCRYPTION')!;
const updated = updateIdentityKey(state, encKey.id, { securityLevel: 'HIGH' });
const key = updated.identityKeys.find(k => k.id === encKey.id)!;
expect(key.securityLevel).toBe('MEDIUM');
});

it('coerces security level when purpose is changed to DECRYPTION', () => {
const authKey = state.identityKeys.find(k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'HIGH')!;
const updated = updateIdentityKey(state, authKey.id, { purpose: 'DECRYPTION' });
const key = updated.identityKeys.find(k => k.id === authKey.id)!;
expect(key.purpose).toBe('DECRYPTION');
expect(key.securityLevel).toBe('MEDIUM');
});
});

describe('updateManageNewKey security level coercion', () => {
it('coerces DECRYPTION purpose to MEDIUM security level', () => {
const state: any = {
manageKeysToAdd: [{
tempId: 'test-1',
purpose: 'DECRYPTION',
securityLevel: 'CRITICAL',
keyType: 'ECDSA_SECP256K1',
name: 'Test',
}],
};
const updated = updateManageNewKey(state, 'test-1', { securityLevel: 'HIGH' });
expect(updated.manageKeysToAdd![0].securityLevel).toBe('MEDIUM');
});

it('coerces security level when purpose is changed to ENCRYPTION', () => {
const state: any = {
manageKeysToAdd: [{
tempId: 'test-1',
purpose: 'AUTHENTICATION',
securityLevel: 'HIGH',
keyType: 'ECDSA_SECP256K1',
name: 'Test',
}],
};
const updated = updateManageNewKey(state, 'test-1', { purpose: 'ENCRYPTION' });
expect(updated.manageKeysToAdd![0].securityLevel).toBe('MEDIUM');
});
});
10 changes: 10 additions & 0 deletions src/ui/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ export function updateIdentityKey(
effectiveSecurityLevel = 'CRITICAL';
}

// ENCRYPTION and DECRYPTION purposes only allow MEDIUM security level
if ((effectivePurpose === 'ENCRYPTION' || effectivePurpose === 'DECRYPTION') && effectiveSecurityLevel !== 'MEDIUM') {
effectiveSecurityLevel = 'MEDIUM';
}

// If keyType changed, regenerate with new type using HD derivation
if (updates.keyType && updates.keyType !== key.keyType) {
return generateIdentityKeyFromMnemonic(
Expand Down Expand Up @@ -939,6 +944,11 @@ export function updateManageNewKey(
effectiveSecurityLevel = 'CRITICAL';
}

// ENCRYPTION and DECRYPTION purposes only allow MEDIUM security level
if ((effectivePurpose === 'ENCRYPTION' || effectivePurpose === 'DECRYPTION') && effectiveSecurityLevel !== 'MEDIUM') {
effectiveSecurityLevel = 'MEDIUM';
}

return {
...k,
...updates,
Expand Down
Loading