Cannot generate ECDSA keys from seed due to “Cannot create a key using the specified key usages” error

I’m trying to generate ECDSA keys using seed and salt as inputs. My aim is to get it working with the pure browser javascript (SubtleCrypto) without the use of any external libs, it’s an important consideration.

The below sample code expectedly fails over a “SyntaxErrors: Cannot create a key using the specified key usages” due to a mismatch between the Eliptic Curve key type and Raw key data format when importing a private key. According to MDN private ECDSA keys can only be imported using PCSS8 or JWT formats. Then to generate a PCSS8 (for instance) a I ought to have an existing key so I can call its exportKey(), which I don’t have at first place, so it’s pretty much a chicken an egg situation for me.

Does anyone have any idea of how to I get the below code working using the exact inputs I have?

// Step 1: PBKDF2 to derive seed from mnemonic and password
async function mnemonicToSeed(mnemonic, password = '') {
    const mnemonicBuffer = new TextEncoder().encode(mnemonic.normalize('NFKD'));
    const saltBuffer = new TextEncoder().encode('mnemonic' + password.normalize('NFKD'));

    const keyMaterial = await window.crypto.subtle.importKey(
        'raw',
        mnemonicBuffer,
        { name: 'PBKDF2' },
        false,
        ['deriveBits']
    );

    const seed = await window.crypto.subtle.deriveBits(
        {
            name: 'PBKDF2',
            salt: saltBuffer,
            iterations: 2048,
            hash: 'SHA-512'
        },
        keyMaterial,
        512 // 512 bits = 64 bytes
    );

    return new Uint8Array(seed); // Return the seed as a Uint8Array
}

// Helper function to convert Uint8Array to hex string
function toHex(buffer) {
    return Array.from(buffer).map(b => b.toString(16).padStart(2, '0')).join('');
}

// Step 2: Generate an ECDSA key pair using WebCrypto and the P-384 curve
async function generateECDSAKeys(mnemonic, password) {
    // Step 2.1: Convert mnemonic to seed
    const seed = await mnemonicToSeed(mnemonic, password);

    // Step 2.2: Use part of the seed as the private key
    const privateKeyBytes = seed.slice(0, 48); // First 48 bytes as private key material (384 bits)

    // Step 2.3: Import the private key into WebCrypto (P-384 curve)
    const privateKey = await window.crypto.subtle.importKey(
        'raw',
        privateKeyBytes,
        { name: 'ECDSA', namedCurve: 'P-384' },
        true,
        ['sign']
    );

    // Step 2.4: Export the public key derived from the private key
    const publicKey = await window.crypto.subtle.exportKey('spki', privateKey);

    // Step 2.5: Export the private key in raw format (for display)
    const exportedPrivateKey = await window.crypto.subtle.exportKey('raw', privateKey);

    return {
        privateKey: toHex(new Uint8Array(exportedPrivateKey)),
        publicKey: toHex(new Uint8Array(publicKey))
    };
}





// Example usage
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const password = 'your_password';

generateECDSAKeys(mnemonic, password).then(keys => {
    console.log('Private Key:', keys.privateKey);
    console.log('Public Key:', keys.publicKey);
}).catch(console.error);