Created
August 19, 2024 20:07
-
-
Save olegpetroveth/8731711b95c1bac0dbe86941b332d3c5 to your computer and use it in GitHub Desktop.
Sample Ledger signature WIP
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
/* eslint-disable @typescript-eslint/no-unused-vars */ | |
import * as fs from "fs"; | |
import path from "path"; | |
import type { LedgerMessageProtobuf } from "@thorswap/models"; | |
import * as elliptic from "elliptic"; | |
import forge from "node-forge"; | |
import * as protobuf from "protobufjs"; | |
import { env } from "./env.mjs"; | |
const EC = elliptic.ec; | |
const ec = new EC("secp256k1"); | |
export const signLedgerMessage = async ( | |
message: LedgerMessageProtobuf, | |
): Promise<{ | |
payload: string; | |
signature: string; | |
}> => { | |
try { | |
const privateKey = loadPrivateKeyFromPEM(env.LEDGER_PRIVATE_KEY); | |
const root = await new protobuf.Root().load( | |
path.join(__dirname, "ledger_swap_response.proto"), | |
{ | |
keepCase: true, | |
}, | |
); | |
const protobufMessageType = root.lookupType("NewTransactionResponse"); | |
const errMsg = protobufMessageType.verify(message); | |
if (errMsg) { | |
throw new Error(`Failed to verify message: ${errMsg}`); | |
} | |
const protoMessage = protobufMessageType.fromObject(message); | |
const byteArray = protobufMessageType.encode(protoMessage).finish(); | |
const buffer = Buffer.from(byteArray); | |
// Base64URL encode the payload | |
const payload = buffer.toString("base64url"); | |
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access | |
const hash = ec.hash().update(buffer).digest() as Buffer; | |
// Sign the hash with the private key using ES256 (ECDSA with SHA-256) | |
const signature = privateKey.sign(hash); | |
// Ensure the signature is 64 bytes long: 32 bytes for `r` and 32 bytes for `s` | |
const rBuffer = signature.r.toArrayLike(Buffer, "be", 32); | |
const sBuffer = signature.s.toArrayLike(Buffer, "be", 32); | |
// @ts-expect-error buffer concatenation works | |
const signatureBuffer = Buffer.concat([signature.r.toBuffer(), signature.s.toBuffer()]); | |
// Base64URL encode the signature | |
const signatureBase64Url = signatureBuffer.toString("base64url"); | |
return { | |
payload, | |
signature: signatureBase64Url, | |
}; | |
} catch (e) { | |
console.error("Error signing message:", e); | |
throw e; | |
} | |
}; | |
function loadPrivateKeyFromPEM(pemKey: string) { | |
const privateKeyHex = Buffer.from( | |
pemKey | |
.replace("-----BEGIN PRIVATE KEY-----", "") | |
.replace("-----END PRIVATE KEY-----", "") | |
.replace(/\n/g, ""), | |
"base64", | |
).toString("hex"); | |
return ec.keyFromPrivate(privateKeyHex); | |
} | |
function pemToBinary(pemKey: string) { | |
// Remove the PEM headers and footers | |
const pemHeader = "-----BEGIN PRIVATE KEY-----"; | |
const pemFooter = "-----END PRIVATE KEY-----"; | |
const base64Key = pemKey.replace(pemHeader, "").replace(pemFooter, "").replace(/\n/g, ""); | |
// Convert base64 string to binary buffer | |
const binaryKey = Buffer.from(base64Key, "base64"); | |
return binaryKey; | |
} | |
// Function to extract raw private key from PEM format | |
function extractRawPrivateKey(pemKey: string): Buffer { | |
// Convert the PEM key to a Forge object | |
const privateKeyObject = forge.pki.privateKeyFromPem(pemKey); | |
// The raw private key is the d field in the Forge private key object | |
const rawPrivateKey = Buffer.from(privateKeyObject.d.toByteArray()); | |
return rawPrivateKey; | |
} | |
function generateECKeyPair() { | |
const keyPair = ec.genKeyPair(); | |
// Get the private and public keys in hex format | |
const privateKeyHex = keyPair.getPrivate("hex"); | |
const publicKeyHex = keyPair.getPublic("hex"); | |
return { privateKeyHex, publicKeyHex }; | |
} | |
// Save PEM formatted keys to disk | |
function saveKeysToPEM(privateKeyHex: string, publicKeyHex: string) { | |
const privateKeyPem = `-----BEGIN PRIVATE KEY-----\n${Buffer.from(privateKeyHex, "hex").toString("base64")}\n-----END PRIVATE KEY-----\n`; | |
const publicKeyPem = `-----BEGIN PUBLIC KEY-----\n${Buffer.from(publicKeyHex, "hex").toString("base64")}\n-----END PUBLIC KEY-----\n`; | |
fs.writeFileSync(path.join(__dirname, "ec_private_key.pem"), privateKeyPem); | |
fs.writeFileSync(path.join(__dirname, "ec_public_key.pem"), publicKeyPem); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment