Skip to content

Instantly share code, notes, and snippets.

@olegpetroveth
Created August 19, 2024 20:07
Show Gist options
  • Save olegpetroveth/8731711b95c1bac0dbe86941b332d3c5 to your computer and use it in GitHub Desktop.
Save olegpetroveth/8731711b95c1bac0dbe86941b332d3c5 to your computer and use it in GitHub Desktop.
Sample Ledger signature WIP
/* 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