All files / src/crypto/encryption sign.ts

100% Statements 15/15
77.77% Branches 7/9
100% Functions 2/2
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 9241x 41x 41x                                             41x 9x     9x           9x 9x 9x                     9x                                           41x 9x     9x           9x 9x                    
import { getPublicKey as getPublicKeyFromPrivate, verify, sign } from '@noble/secp256k1';
import { bytesToHex, arrayBufferToUint8, utf8ToBytes } from 'micro-stacks/common';
import { hashSha256 } from 'micro-stacks/crypto-sha';
 
/**
 * Sign content using ECDSA
 *
 * @param {String} privateKey - secp256k1 private key hex string
 * @param {Object} content - content to sign
 * @return {Object} contains:
 * signature - Hex encoded DER signature
 * public key - Hex encoded private string taken from privateKey
 * @private
 * @ignore
 */
interface SignECDSA {
  privateKey: string;
  contents: string | ArrayBuffer | Uint8Array;
}
 
interface SignedResponse {
  signature: string;
  publicKey: string;
}
 
export async function signECDSA(params: SignECDSA): Promise<SignedResponse> {
  const { contents, privateKey } = params;
 
  const contentBuffer =
    contents instanceof ArrayBuffer
      ? arrayBufferToUint8(contents)
      : typeof contents === 'string'
      ? utf8ToBytes(contents)
      : contents;
 
  const publicKey = bytesToHex(getPublicKeyFromPrivate(privateKey, true));
  const contentsHash = hashSha256(contentBuffer);
  const signature = await sign(contentsHash, privateKey, {
    // whether a signature s should be no more than 1/2 prime order.
    // true makes signatures compatible with libsecp256k1
    // false makes signatures compatible with openssl <-- stacks currently uses this
    canonical: false,
    // https://github.com/paulmillr/noble-secp256k1#signmsghash-privatekey
    // additional entropy k' for deterministic signature, follows section 3.6 of RFC6979. When true, it would automatically be filled with 32 bytes of cryptographically secure entropy
    // TODO: how can we make this default true?
    // extraEntropy: true,
  });
 
  return {
    signature: bytesToHex(signature),
    publicKey,
  };
}
 
/**
 * Verify content using ECDSA
 * @param {String | Buffer} content - Content to verify was signed
 * @param {String} publicKey - secp256k1 private key hex string
 * @param {String} signature - Hex encoded DER signature
 * @param {Boolean} strict - whether a signature s should be no more than 1/2 prime order. true makes signatures compatible with libsecp256k1, false makes signatures compatible with openssl
 * @return {Boolean} returns true when signature matches publickey + content, false if not
 * @private
 * @ignore
 */
interface VerifyESDSA {
  contents: string | ArrayBuffer | Uint8Array;
  publicKey: string;
  signature: string;
}
 
export function verifyECDSA(params: VerifyESDSA, strict = false): boolean {
  const { contents, publicKey, signature } = params;
 
  const contentBuffer =
    contents instanceof ArrayBuffer
      ? arrayBufferToUint8(contents)
      : typeof contents === 'string'
      ? utf8ToBytes(contents)
      : contents;
 
  const contentHash = hashSha256(contentBuffer);
  return verify(
    signature,
    contentHash,
    publicKey,
    // TODO: should this be true by default?
    // is not compat with legacy implementations.
    // change reflected here https://github.com/paulmillr/noble-secp256k1/releases/tag/1.4.0
    { strict }
  );
}