@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
70 lines (56 loc) • 3.28 kB
text/typescript
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")
}
};
}