UNPKG

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
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 }, }; }