Last active
July 24, 2025 18:57
-
-
Save elmariachi111/3486fde2d4746d511eb2869ac51076e3 to your computer and use it in GitHub Desktop.
secp256k1 signature verification
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { secp256k1 } from "@noble/curves/secp256k1"; | |
| import { sha256 } from "@noble/hashes/sha2"; | |
| import { bytesToHex } from "@noble/hashes/utils"; | |
| import { toHex } from "viem"; | |
| // this is some public address, which deals as the message to be signed | |
| const ADDRESS = "0x70997970c51812dc3a010c7d01b50e0d17dc79c8"; | |
| /** | |
| * this is nillion's implemenation of the sign function | |
| * https://github.com/NillionNetwork/nuc-ts/blob/main/src/keypair.ts#L102 | |
| * | |
| * @param bytes msgBytes | |
| * @param bytes privateKey | |
| * @returns | |
| */ | |
| const sign = (msgString, privateKey) => { | |
| const msgBytes = new TextEncoder().encode(msgString); | |
| const signature = secp256k1.sign(msgBytes, privateKey, { | |
| prehash: true, | |
| }); | |
| //this is what the Nillion:Keypair class does, it doesn't contain the recovery bit: | |
| return signature.toCompactRawBytes(); | |
| }; | |
| async function main() { | |
| const privateKey = secp256k1.utils.randomSecretKey(); | |
| //const privateKey = hexToBytes(PK); | |
| const publicKey = secp256k1.getPublicKey(privateKey); | |
| console.log("Private", toHex(privateKey)); | |
| console.log("Public", toHex(publicKey)); | |
| const hashedAddress = sha256(ADDRESS); | |
| const signature = sign(ADDRESS, privateKey); | |
| //this correctly verifies the signature has been issued over the message | |
| const isValidSignatureForPublicKey = secp256k1.verify( | |
| signature, | |
| new TextEncoder().encode(ADDRESS), | |
| bytesToHex(publicKey), | |
| { | |
| prehash: true, | |
| } | |
| ); | |
| console.log( | |
| isValidSignatureForPublicKey ? "✅" : "❌", | |
| "isValidSignatureForPublicKey" | |
| ); | |
| // as we don't know the recovery bit we cannot recover the public however. | |
| // Using 0 here fails in 50% of the cases: | |
| const secpSignature = | |
| secp256k1.Signature.fromCompact(signature).addRecoveryBit(0); | |
| const finalRecoveredKey = secpSignature.recoverPublicKey(hashedAddress); | |
| const recoveredCorrectly = "0x" + finalRecoveredKey.toHex() === toHex(publicKey); | |
| console.log( | |
| recoveredCorrectly ? "✅" : "❌", | |
| " Final recovered key:", | |
| finalRecoveredKey.toHex() | |
| ); | |
| } | |
| main(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "test-secp", | |
| "version": "1.0.0", | |
| "description": "", | |
| "main": "index.js", | |
| "type": "module", | |
| "scripts": { | |
| "test": "echo \"Error: no test specified\" && exit 1" | |
| }, | |
| "keywords": [], | |
| "author": "", | |
| "license": "ISC", | |
| "dependencies": { | |
| "@noble/curves": "^1.9.2", | |
| "@noble/hashes": "^1.8.0", | |
| "viem": "^2.33.0" | |
| }, | |
| "packageManager": "[email protected]" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { secp256k1 } from "@noble/curves/secp256k1"; | |
| import { sha256 } from "@noble/hashes/sha2"; | |
| import { hexToBigInt, parseSignature, serializeSignature, toHex } from "viem"; | |
| // this is some public address, which deals as the message to be signed | |
| const ADDRESS = "0x70997970c51812dc3a010c7d01b50e0d17dc79c8"; | |
| /** | |
| * this is nillion's implemenation of the sign function | |
| * https://github.com/NillionNetwork/nuc-ts/blob/main/src/keypair.ts#L102 | |
| * | |
| * @param bytes msgBytes | |
| * @param bytes privateKey | |
| * @returns | |
| */ | |
| const sign = (msgString, privateKey) => { | |
| const msgBytes = new TextEncoder().encode(msgString); | |
| const signature = secp256k1.sign(msgBytes, privateKey, { | |
| prehash: true, | |
| }); | |
| //this is what the Nillion:Keypair class does, it doesn't contain the recovery bit: | |
| //return signature.toBytes("compact"); | |
| //return signature.toCompactRawBytes(); | |
| return signature; | |
| }; | |
| async function main() { | |
| const privateKey = secp256k1.utils.randomSecretKey(); | |
| //const privateKey = hexToBytes(PK); | |
| const publicKey = secp256k1.getPublicKey(privateKey); | |
| console.log("Private", toHex(privateKey)); | |
| console.log("Public", toHex(publicKey)); | |
| const hashedAddress = sha256(ADDRESS); | |
| const signature = sign(ADDRESS, privateKey); | |
| const recoveryBit = signature.recovery; | |
| //const secpSignature = secp256k1.Signature.fromBytes(signature).addRecoveryBit(1); | |
| const secpSignature = new secp256k1.Signature( | |
| signature.r, | |
| signature.s, | |
| signature.recovery | |
| ); | |
| const immediatelyRevoredPublicKey = | |
| secpSignature.recoverPublicKey(hashedAddress); | |
| console.log("immediately Recovered", immediatelyRevoredPublicKey.toHex()); | |
| // const signatureHex = signature.toHex("compact"); | |
| // console.log("Signature", signature, " | ", signatureHex); | |
| const encodedSignature = serializeSignature({ | |
| ...signature, | |
| yParity: signature.recovery, | |
| }); | |
| console.log("Encoded (65 bytes):", encodedSignature); | |
| //const decodedSignature = decodeSignatureWithRecoveryBit(encodedSignature); | |
| const decodedSignature = parseSignature(encodedSignature); | |
| const secpSignature2 = new secp256k1.Signature( | |
| hexToBigInt(decodedSignature.r), | |
| hexToBigInt(decodedSignature.s), | |
| decodedSignature.yParity | |
| ); | |
| const finalRecoveredKey = secpSignature2.recoverPublicKey(hashedAddress); | |
| const didItWork = "0x" + finalRecoveredKey.toHex() === toHex(publicKey); | |
| console.log( | |
| didItWork ? "✅" : "❌", | |
| " Final recovered key:", | |
| finalRecoveredKey.toHex() | |
| ); | |
| } | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment