Search code examples
node.jstypescriptbitcoin

Signing a Bitcoin transaction in Node


I have been implementing transactions in a NodeJS environment and have been using guides like https://learnmeabitcoin.com/technical/ecdsa#sign to understand the code behind it.

I have written a function that signs a transaction, but some part of the code is flawed as verifying the signature produced by the below function keeps returning false. The verify function I've written should be working correctly, as it returns true for signatures produced by a a thirty party library.

Imported functions such as bufferToBigInt, pointMultiply, pointAdd, modulo have been tested and should be working correctly.

I've added type annotations below to make it easier to understand variable's types.

export const sign = (hashOfMessage: Buffer, privateKey: Buffer) => {
  const randomNumber: bigint = bufferToBigInt(generatePrivateKey());
  const randomPoint: Buffer = pointMultiply(GENERATOR_POINT, randomNumber);
  const randomPointX: bigint = extractX(randomPoint);
  const privateKeyAsNumber: bigint = bufferToBigInt(privateKey);
  const r: bigint = modulo(randomPointX, ORDER);
  const s: bigint = modulo(
    modInv(randomNumber, ORDER) *
      (bufferToBigInt(hashOfMessage) + randomNumber * privateKeyAsNumber),
    ORDER
  );
  const sLow: bigint = s > ORDER / 2n ? ORDER - s : s;
  return Buffer.concat([
    Buffer.from(r.toString(16).padStart(64, "0"), "hex"),
    Buffer.from(sLow.toString(16).padStart(64, "0"), "hex"),
  ]);
};

export const verify = (
  hashOfMessage: Buffer,
  publicKey: Buffer,
  signature: Buffer
) => {
  const r: bigint = bufferToBigInt(signature, 0, 32);
  const s: bigint = bufferToBigInt(signature, 32);
  const sModInverse: bigint = modInv(s, ORDER);
  const pointOne: Buffer = pointMultiply(
    GENERATOR_POINT,
    bufferToBigInt(hashOfMessage) * sModInverse
  );
  const pointTwo: Buffer = pointMultiply(publicKey, r * sModInverse);
  const pointResult: Buffer = pointAdd(pointOne, pointTwo);
  return extractX(pointResult) === r;
};

Solution

  • I found my mistake.

    I miscalculated s because I used randomNumber instead of r.

    The calculation for s should have been as follows:

    const s: bigint = modulo(
        modInv(randomNumber, ORDER) *
          (bufferToBigInt(hashOfMessage) + r * privateKeyAsNumber),
        ORDER
      );