hyperledger-fabricdigital-signatureecdsasignatureprime256v1

Canonical Signature on Hyperledger Fabric Node.js Client


I'm trying to run the "offline signature" method for fabric-gateway SDK on Node.js, but I'm having trouble signing the digests that Fabric uses for the transaction.

When signing the digests with crypto.sign('sha256', proposalDigest, privateKey)the peer logs show an error saying that the signature is invalid because S. is too high, it should be half of N. I discovered that this is because fabric requires the signature to be canonical, which mine obviously isn't. The problem is that I didn't found anything that can "canonize an signature" or sign something with this setting. The closest thing I've found is the "libsecp256k1" SDK, but it is incompatible with fabric because it uses the prime256v1 curve instead of secp256k1.

I had no luck trying to manually change the value of S too, it only resulted in "invalid signature" errors. If someone knows any package that is able to do this "canonized signature" or have already written any code related to this in node, please give me some light.


Solution

  • If you want to use the Node crypto ECDSA signing implementation, you will need to modify the s value of any generated signature that is greater than n / 2 to ensure a (canonical) low-S value. This can be done either using the @noble/curves package:

    import { p256 } from '@noble/curves/p256';
    
    // For a DER-encoded signature:
    const canonicalSignature = p256.Signature.fromDER(signature).normalizeS().toDERRawBytes();
    
    // For a compact signature:
    const canonicalSignature = p256.Signature.fromCompact(signature).normalizeS().toDERRawBytes();
    

    or by:

    1. Unpacking the signature yourself: slicing a compact signature in half to obtain r and s values, or using ASN.1 libraries to unpack a DER format signature.

    2. Replacing s with n - s if s is greater than n / 2.

    3. Using ASN.1 libraries to re-pack the r and s values of the signature into DER format.

    Alternatively, you can use a pure JavaScript ECDSA implementation that can ensure canonical signatures, such as @noble/curves:

    import { p256 } from '@noble/curves/p256';
    
    const { d } = nodePrivateKeyObj.export({ format: 'jwk' });
    const privateKey = Buffer.from(d, 'base64url');
    
    const signature = p256.sign(digest, privateKey, { lowS: true });
    const signatureBytes = signature.toDERRawBytes();
    

    or elliptic:

    import { ec as EC } from 'elliptic';
    
    const p256 = new EC('p256');
    
    const { d } = nodePrivateKeyObj.export({ format: 'jwk' });
    const privateKey = Buffer.from(d, 'base64url');
    
    const signature = p256.sign(digest, privateKey, { canonical: true });
    const signatureBytes = new Uint8Array(signature.toDER());