Convert an ECDSA signature in P1363 format to ASN.1 and vice versa in Javascript

I’d like to be able to verify ECDSA signatures across the browser and OpenSSL. While I believed this should be trivial, it turns out both applications use different signature standards; OpenSSL requires signatures to be in ASN.1, while subtle crypto in the browser generates signatures that adher to IEEE P1363.

I have found many questions and discussions on SO regarding this topic, but no good solution so far. They either use various libraries to do the job, or were regarding languages that I can’t use in the browser. So I attempted to write my own implementation in vanilla JS.

There are a few special cases that needed to be handled, such as signatures with large curves, eg. P-521, or calculating the correct length of various components.

I have put together two functions which I believe work well, but I have not tested them thoroughly. They take and return an ArrayBuffer, which I think makes sense for interaction with subtle crypto. Can somebody who knows Javascript and ECDSA tell me if this will work correctly for all cases and signature lengths? (and maybe also if this can be written in a more efficient way)

function p1363_to_asn1 (p1363) {
    const input = new Uint8Array(p1363);

    let asn1; // ASN.1 contents.
    let len = 0; // Length of ASN.1 contents.
    const componentLength = Math.floor(input.byteLength / 2); // Length of each P1363 component.

    // Separate P1363 signature into its two equally sized components.
    const chunks = [];
    chunks.push([...input.slice(0, componentLength)]);
    chunks.push([...input.slice(componentLength)]);

    const prefix = parseInt('02', 16); // 0x02 prefix before each component.

    for (let i=0; i<chunks.length; i++) {
        // remove leading 0x00 bytes in R and S!
        // https://stackoverflow.com/questions/59904522/asn1-encoding-routines-errors-when-verifying-ecdsa-signature-type-with-openssl#comment105937557_59905274
        while (chunks[i][0] === 0) {
            chunks[i].shift();
        }

        // Add 0x00 because first byte of component > 0x7f.
        // Length of component = (componentLength + 1).
        if (chunks[i][0] > parseInt('7f', 16)) {
            chunks[i].unshift(parseInt('00', 16));
        }
    }

    len = 4 + chunks[0].length + chunks[1].length;
    // 4 is the combined length of 2 prefixes and 2 lengths (?)

    let finalLength;
    if (len > parseInt('7f', 16)) {
        // handle large lengths, like P-521
        // https://security.stackexchange.com/a/164906
        finalLength = [parseInt('81', 16), len];
    } else {
        finalLength = [len];
    }

    asn1 = [
        parseInt('30', 16),
        ...finalLength,

        prefix,
        chunks[0].length,
        ...chunks[0],

        prefix,
        chunks[1].length,
        ...chunks[1]
    ];

    return new Uint8Array(asn1).buffer;
}

function asn1_to_p1363 (asn1) {
    // https://stackoverflow.com/a/48727351/3625228
    const input = new Uint8Array(asn1);
    const inputArray = Array.from(input);

    inputArray.shift(1); // remove 0x30 sequence

    let totalLength = inputArray.shift(1);
    if (totalLength == parseInt('81', 16)) {
        // handle large length curves
        totalLength += inputArray.shift(1);
    }

    inputArray.shift(1); // remove 0x02 prefix
    const c1Len = inputArray.shift(1);
    const c1 = inputArray.splice(0, c1Len);

    inputArray.shift(1); // remove 0x02 prefix
    const c2Len = inputArray.shift(1);
    const c2 = inputArray;

    // remove leading 0x00 bytes (??)
    while (c1[0] === 0) {
        c1.shift();
    }
    while (c2[0] === 0) {
        c2.shift();
    }

    return new Uint8Array([...c1, ...c2]).buffer;
}

I have also put up an example on Github, where you can convert between the formats entirely on the client.