UNPKG

@atomiqlabs/sdk

Version:

atomiq labs SDK for cross-chain swaps between smart chains and bitcoin

70 lines (56 loc) 3.28 kB
import {secp256k1, schnorr} from "@noble/curves/secp256k1"; import {randomBytes} from "../../utils/Utils"; function parsePrivateKey(value: string, name: string) { if(!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${name} must be a 32-byte hex private key`); const bytes = Buffer.from(value, "hex"); if(!secp256k1.utils.isValidPrivateKey(bytes)) throw new Error(`${name} is not a valid secp256k1 private key`); return bytes; } function parsePublicKey(value: string, name: string) { if(!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${name} must be an X-only 32-byte hex public key`); if(!secp256k1.utils.isValidPublicKey(Buffer.from("02"+value, "hex"), true)) throw new Error(`${name} is not a valid X-only 32-byte secp256k1 public key`); return Buffer.from(value, "hex"); } function parseSignature(value: string, name: string) { if(!/^[0-9a-fA-F]{128}$/.test(value)) throw new Error(`${name} must be a valid 64-byte hex schnorr signature`); return Buffer.from(value, "hex"); } const CERTIFICATE_LENGTH = 64+128+64; /** * Adds a `x-atomiq-auth` header with the following binary format, that is hex-encoded: * - 4 bytes - timestamp (timestamp when the request was sent) * - 32 bytes - random bytes (entropy, to be signed) * * - 32 bytes - key of the signing authority * - 64 bytes - schnorr signature signing the request signing key by the signing authority * * - 32 bytes - request signing key (signed by the signing authority) * - 64 bytes - schnorr signature of the timestamp and random bytes * * Uses secp256k1 curve and schnorr signatures */ export function getSignedKeyBasedAuthHandler(certificate: string, privateKey: string): (type: "GET" | "POST", url: string, body?: any) => Record<string, string> { const _privateKey = parsePrivateKey(privateKey, "Private key"); if(certificate.length!==CERTIFICATE_LENGTH) throw new Error(`Certificate has invalid length, expected ${CERTIFICATE_LENGTH}!`); const authorityPublicKey = parsePublicKey(certificate.substring(0, 64), "Certificate: Authority Public key"); const authoritySignature = parseSignature(certificate.substring(64, 64+128), "Certificate: Authority Signature"); const signingPublicKey = parsePublicKey(certificate.substring(64+128), "Certificate: Signing Public key"); if(!schnorr.verify(authoritySignature, signingPublicKey, authorityPublicKey)) throw new Error("Invalid certificate, authority signature is not valid!"); if(!signingPublicKey.equals(schnorr.getPublicKey(privateKey))) throw new Error("Passed private key doesn't match the certificate!"); const _certificate = Buffer.from(certificate, "hex"); return () => { const timestamp = Math.floor(Date.now()/1000); const timestampBuffer = Buffer.alloc(4); timestampBuffer.writeUInt32BE(timestamp); const requestUid = randomBytes(32); const toSign = Buffer.concat([timestampBuffer, requestUid]); const signature = schnorr.sign(toSign, _privateKey); return { "x-atomiq-auth": Buffer.concat([ toSign, _certificate, signature ]).toString("hex") } }; }