lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
298 lines (297 loc) • 12.4 kB
JavaScript
import { Point } from '../../bitcore/crypto/point.js';
import { BN } from '../../bitcore/crypto/bn.js';
import { PublicKey } from '../../bitcore/publickey.js';
import { DeserializationError, SerializationError, ErrorCode, } from './errors.js';
export function serializePoint(point) {
try {
const compressed = Point.pointToCompressed(point);
return compressed.toString('hex');
}
catch (error) {
throw new SerializationError(error instanceof Error ? error.message : String(error), 'Point', error instanceof Error ? error.message : 'Unknown error');
}
}
export function deserializePoint(hex) {
try {
if (typeof hex !== 'string') {
throw new DeserializationError('Input must be a string', 'point', typeof hex);
}
if (!/^[0-9a-fA-F]+$/.test(hex)) {
throw new DeserializationError('Invalid hex string format', 'point', hex.substring(0, 20));
}
const buffer = Buffer.from(hex, 'hex');
if (buffer.length !== 33) {
throw new DeserializationError(`Expected 33 bytes, got ${buffer.length}`, 'point', buffer.length);
}
const prefix = buffer[0];
if (prefix !== 0x02 && prefix !== 0x03) {
throw new DeserializationError(`Invalid prefix: expected 0x02 or 0x03, got 0x${prefix.toString(16)}`, 'point', prefix);
}
const odd = prefix === 0x03;
const x = new BN(buffer.slice(1), 'be');
return Point.fromX(odd, x);
}
catch (error) {
if (error instanceof DeserializationError) {
throw error;
}
throw new DeserializationError(error instanceof Error ? error.message : String(error), 'point', hex?.substring(0, 20));
}
}
export function serializePublicNonces(nonces) {
try {
if (!Array.isArray(nonces)) {
throw new SerializationError('Nonces must be an array', 'Point[]', 'Invalid input type');
}
if (nonces.length < 2) {
throw new SerializationError('MuSig2 requires at least 2 nonces (ν ≥ 2)', 'Point[]', 'Insufficient nonce count');
}
const nonceMap = {};
nonces.forEach((nonce, index) => {
if (!(nonce instanceof Point)) {
throw new SerializationError(`Invalid nonce at index ${index}: expected Point`, 'Point', 'Invalid object type');
}
nonceMap[`r${index + 1}`] = serializePoint(nonce);
});
return nonceMap;
}
catch (error) {
if (error instanceof SerializationError) {
throw error;
}
throw new SerializationError(error instanceof Error ? error.message : String(error), 'Point[]', error instanceof Error ? error.message : 'Unknown error');
}
}
export function deserializePublicNonces(nonceMap) {
try {
if (!nonceMap || typeof nonceMap !== 'object') {
throw new DeserializationError('Input must be an object', 'publicNonces', typeof nonceMap);
}
const nonceKeys = Object.keys(nonceMap)
.filter(key => /^r\d+$/.test(key))
.sort((a, b) => {
const numA = parseInt(a.substring(1));
const numB = parseInt(b.substring(1));
return numA - numB;
});
if (nonceKeys.length < 2) {
throw new DeserializationError('MuSig2 requires at least 2 nonces (ν ≥ 2)', 'publicNonces', nonceKeys.length);
}
const nonces = [];
for (const key of nonceKeys) {
const hexValue = nonceMap[key];
if (!hexValue || typeof hexValue !== 'string') {
throw new DeserializationError(`Missing or invalid nonce for key: ${key}`, ErrorCode.MALFORMED_DATA, 'publicNonces');
}
try {
const point = deserializePoint(hexValue);
nonces.push(point);
}
catch (error) {
throw new DeserializationError(`Failed to deserialize nonce ${key}: ${error instanceof Error ? error.message : String(error)}`, ErrorCode.MALFORMED_DATA, 'publicNonces');
}
}
return nonces;
}
catch (error) {
if (error instanceof DeserializationError) {
throw error;
}
throw new DeserializationError(error instanceof Error ? error.message : String(error), 'publicNonces');
}
}
export function serializeBN(bn) {
try {
if (!(bn instanceof BN)) {
throw new SerializationError('Input must be a BN instance', 'BN', 'Invalid object type');
}
return bn.toBuffer({ endian: 'big', size: 32 }).toString('hex');
}
catch (error) {
throw new SerializationError(error instanceof Error ? error.message : String(error), 'BN', error instanceof Error ? error.message : 'Unknown error');
}
}
export function deserializeBN(hex) {
try {
if (typeof hex !== 'string') {
throw new DeserializationError('Input must be a string', 'bn', typeof hex);
}
if (!/^[0-9a-fA-F]+$/.test(hex)) {
throw new DeserializationError('Invalid hex string format', 'bn', hex.substring(0, 20));
}
const buffer = Buffer.from(hex, 'hex');
return new BN(buffer, 'be');
}
catch (error) {
if (error instanceof DeserializationError) {
throw error;
}
throw new DeserializationError(error instanceof Error ? error.message : String(error), 'bn', hex?.substring(0, 20));
}
}
export function serializePublicKey(publicKey) {
try {
if (!(publicKey instanceof PublicKey)) {
throw new SerializationError('Input must be a PublicKey instance', 'PublicKey', 'Invalid object type');
}
return publicKey.toBuffer().toString('hex');
}
catch (error) {
throw new SerializationError(error instanceof Error ? error.message : String(error), 'PublicKey', error instanceof Error ? error.message : 'Unknown error');
}
}
export function deserializePublicKey(hex) {
try {
if (typeof hex !== 'string') {
throw new DeserializationError('Input must be a string', 'publicKey', typeof hex);
}
if (!/^[0-9a-fA-F]+$/.test(hex)) {
throw new DeserializationError('Invalid hex string format', 'publicKey', hex.substring(0, 20));
}
const buffer = Buffer.from(hex, 'hex');
if (buffer.length !== 33 && buffer.length !== 65) {
throw new DeserializationError(`Invalid public key length: expected 33 or 65 bytes, got ${buffer.length}`, 'publicKey', buffer.length);
}
return new PublicKey(buffer);
}
catch (error) {
if (error instanceof DeserializationError) {
throw error;
}
throw new DeserializationError(error instanceof Error ? error.message : String(error), 'publicKey', hex?.substring(0, 20));
}
}
export function serializeMessage(message) {
try {
if (!Buffer.isBuffer(message)) {
throw new SerializationError('Input must be a Buffer', 'Buffer', 'Invalid object type');
}
return message.toString('hex');
}
catch (error) {
throw new SerializationError(error instanceof Error ? error.message : String(error), 'Buffer', error instanceof Error ? error.message : 'Unknown error');
}
}
export function deserializeMessage(hex) {
try {
if (typeof hex !== 'string') {
throw new DeserializationError('Input must be a string', 'message', typeof hex);
}
if (!/^[0-9a-fA-F]*$/.test(hex)) {
throw new DeserializationError('Invalid hex string format', 'message', hex.substring(0, 20));
}
return Buffer.from(hex, 'hex');
}
catch (error) {
if (error instanceof DeserializationError) {
throw error;
}
throw new DeserializationError(error instanceof Error ? error.message : String(error), 'message', hex?.substring(0, 20));
}
}
export function serializeSignature(signature) {
try {
if (!Buffer.isBuffer(signature)) {
throw new SerializationError('Input must be a Buffer', 'Signature', 'Invalid object type');
}
if (signature.length !== 64) {
throw new SerializationError(`Expected 64-byte signature, got ${signature.length} bytes`, 'Signature', 'Invalid length');
}
return {
r: signature.subarray(0, 32).toString('hex'),
s: signature.subarray(32, 64).toString('hex'),
};
}
catch (error) {
if (error instanceof SerializationError) {
throw error;
}
throw new SerializationError(error instanceof Error ? error.message : String(error), 'Signature', error instanceof Error ? error.message : 'Unknown error');
}
}
export function deserializeSignature(sig) {
try {
if (!sig || typeof sig !== 'object') {
throw new DeserializationError('Input must be an object with r and s properties', 'signature', typeof sig);
}
if (typeof sig.r !== 'string' || typeof sig.s !== 'string') {
throw new DeserializationError('Signature r and s must be strings', 'signature', { r: typeof sig.r, s: typeof sig.s });
}
if (!/^[0-9a-fA-F]+$/.test(sig.r) || !/^[0-9a-fA-F]+$/.test(sig.s)) {
throw new DeserializationError('Invalid hex string format in signature', 'signature', { r: sig.r.substring(0, 10), s: sig.s.substring(0, 10) });
}
const rBuffer = Buffer.from(sig.r, 'hex');
const sBuffer = Buffer.from(sig.s, 'hex');
if (rBuffer.length !== 32 || sBuffer.length !== 32) {
throw new DeserializationError(`Invalid signature component length: r=${rBuffer.length}, s=${sBuffer.length}`, 'signature', { rLen: rBuffer.length, sLen: sBuffer.length });
}
return Buffer.concat([rBuffer, sBuffer]);
}
catch (error) {
if (error instanceof DeserializationError) {
throw error;
}
throw new DeserializationError(error instanceof Error ? error.message : String(error), 'signature');
}
}
export function serializePublicKeys(publicKeys) {
try {
if (!Array.isArray(publicKeys)) {
throw new SerializationError('Input must be an array', 'PublicKey[]', 'Invalid input type');
}
return publicKeys.map((pk, index) => {
if (!(pk instanceof PublicKey)) {
throw new SerializationError(`Invalid public key at index ${index}: expected PublicKey`, 'PublicKey', 'Invalid object type');
}
return serializePublicKey(pk);
});
}
catch (error) {
if (error instanceof SerializationError) {
throw error;
}
throw new SerializationError(error instanceof Error ? error.message : String(error), 'PublicKey[]', error instanceof Error ? error.message : 'Unknown error');
}
}
export function deserializePublicKeys(hexStrings) {
try {
if (!Array.isArray(hexStrings)) {
throw new DeserializationError('Input must be an array', 'publicKeys', typeof hexStrings);
}
return hexStrings.map((hex, index) => {
try {
return deserializePublicKey(hex);
}
catch (error) {
throw new DeserializationError(`Failed to deserialize public key at index ${index}: ${error instanceof Error ? error.message : String(error)}`, 'publicKeys', hex?.substring(0, 20));
}
});
}
catch (error) {
if (error instanceof DeserializationError) {
throw error;
}
throw new DeserializationError(error instanceof Error ? error.message : String(error), 'publicKeys');
}
}
export function validateHexString(hex, expectedLength) {
if (typeof hex !== 'string') {
return false;
}
if (!/^[0-9a-fA-F]*$/.test(hex)) {
return false;
}
if (expectedLength !== undefined) {
const buffer = Buffer.from(hex, 'hex');
return buffer.length === expectedLength;
}
return true;
}
export function getSerializationInfo() {
return {
pointFormat: '33-byte compressed hex',
bnFormat: '32-byte big-endian hex',
publicKeyFormat: '33-byte compressed or 65-byte uncompressed hex',
supportedNonceCount: { min: 2, max: 10 },
};
}