Search code examples
node.jsnode-crypto

How to extract r s values from Node.js Sign object?


I follow some online code snippet practicing to do ECDSA sign operation as below.

const { privKey, pubKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' });
const sign = crypto.createSign('SHA256');
sign.update('message to be signed');
sign.end();
const signature = sign.sign(privKey);

const verify = crypto.createVerify('SHA256');
verify.update('message to be signed');
verify.end();
console.log(verify.verify(pubKey, signature));

But I have a question. How do I extract R S values from signature object? From the node.js doc[1], it mentions the default format is DER encoded ASN1. But checking online, some info like [2] talks about how to extract R S values, but it's neither node.js, nor talking about which position to slice the bytes array. So I am confused, how should I extract R S values from node.js Sign.sign() returned object.

Any advice? Many thanks.

[1]. https://nodejs.org/api/crypto.html#signsignprivatekey-outputencoding

[2]. ECDSA Signature - extract R|S from ASN 1. format


Solution

  • In this post you can find a description of the ASN.1/DER and IEEE P1363 format for ECDSA signatures. This can be used to manually convert between the two formats.

    However, a manual conversion is not required at all, as the crypto module also supports the IEEE P1363 format (r|s) since v13.2.0, s. sign.sign(). For this, the specifier dsaEncoding is used and the IEEE P1363 format is selected with 'ieee-p1363'.

    The following shows the modification of your code so that it uses the IEEE P1363 format:

    const crypto = require('crypto')
    
    const { privateKey: privKey, publicKey: pubKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' });
    const sign = crypto.createSign('SHA256');
    sign.update('message to be signed');
    sign.end();
    const signature = sign.sign({key: privKey, dsaEncoding: 'ieee-p1363'});
    console.log(signature.toString('hex'));
    
    const verify = crypto.createVerify('SHA256');
    verify.update('message to be signed');
    verify.end();
    console.log(verify.verify({key: pubKey, dsaEncoding: 'ieee-p1363'}, signature));
    

    signature now contains the signature as a concatenation of r and s: r|s. r and s each have the length of the order of the generator point, padded with leading 0x00 values if required. For secp256k1, both values are 32 bytes each.