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;
};
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
);