UNPKG

@maverick0351/odin-protocol-js

Version:

JavaScript/TypeScript SDK for ODIN v1.0 state capsule protocol

262 lines 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OdinProtocol = void 0; // Simple msgpack implementation for demo const msgpack = { encode: (obj) => { return new TextEncoder().encode(JSON.stringify(obj)); }, decode: (data) => { return JSON.parse(new TextDecoder().decode(data)); } }; // Simple Ed25519 signature implementation placeholder const nacl = { sign: { keyPair: { fromSecretKey: (secretKey) => ({ publicKey: secretKey.slice(32), secretKey: secretKey }), create: () => { const keypair = new Uint8Array(96); for (let i = 0; i < 96; i++) { keypair[i] = Math.floor(Math.random() * 256); } return { publicKey: keypair.slice(0, 32), secretKey: keypair.slice(32) }; } }, detached: (message, secretKey) => { // Simple signature placeholder - in production use real Ed25519 const signature = new Uint8Array(64); for (let i = 0; i < 64; i++) { signature[i] = (message[i % message.length] + secretKey[i % secretKey.length]) % 256; } return signature; } } }; // Verification function function verifySignature(message, signature, publicKey) { // Simple verification placeholder return signature.length === 64 && publicKey.length === 32; } const constants_1 = require("./constants"); const utils_1 = require("./utils"); /** * ODIN Protocol SDK - Main class for working with state capsules */ class OdinProtocol { constructor(creatorId, privateKey) { if (typeof creatorId === 'string') { this.defaultCreatorId = (0, utils_1.generateCreatorId)(creatorId); } else if (creatorId) { this.defaultCreatorId = creatorId; } else { this.defaultCreatorId = (0, utils_1.randomBytes)(32); } if (privateKey) { this.defaultPrivateKey = privateKey; } } /** * Pack data into an ODIN capsule */ pack(data, options = {}) { // Encode payload let payload; const encoding = options.encoding || constants_1.EncodingType.MSGPACK; switch (encoding) { case constants_1.EncodingType.MSGPACK: payload = new Uint8Array(msgpack.encode(data)); break; case constants_1.EncodingType.JSON: payload = (0, utils_1.stringToBytes)(JSON.stringify(data)); break; default: throw new Error(constants_1.ERROR_MESSAGES.INVALID_ENCODING); } // Check payload size if (payload.length > constants_1.MAX_PAYLOAD_SIZE) { throw new Error(constants_1.ERROR_MESSAGES.PAYLOAD_TOO_LARGE); } // Apply compression if specified const compression = options.compression || constants_1.CompressionType.NONE; if (compression !== constants_1.CompressionType.NONE) { // Compression would be implemented here // For now, we'll use uncompressed data } // Calculate checksum const checksum = (0, utils_1.blake3Hash)(payload); // Prepare header const type = options.type || constants_1.CapsuleType.CONSCIOUSNESS; const timestamp = options.timestamp || (0, utils_1.getCurrentTimestamp)(); const creatorId = typeof options.creatorId === 'string' ? (0, utils_1.generateCreatorId)(options.creatorId) : options.creatorId || this.defaultCreatorId; if (!(0, utils_1.isValidCreatorId)(creatorId)) { throw new Error("Invalid creator ID"); } // Create header without signature first const headerWithoutSig = (0, utils_1.concatBytes)(constants_1.MAGIC_BYTES, (0, utils_1.stringToBytes)(constants_1.ODIN_VERSION.padEnd(constants_1.VERSION_SIZE, '\0')), new Uint8Array([type]), new Uint8Array([encoding]), new Uint8Array([compression]), (0, utils_1.numberToBytes)(timestamp, constants_1.TIMESTAMP_SIZE), creatorId, new Uint8Array(constants_1.SIGNATURE_SIZE), // Placeholder for signature checksum); // Calculate signature const privateKey = options.privateKey || this.defaultPrivateKey; let signature = new Uint8Array(constants_1.SIGNATURE_SIZE); if (privateKey) { const dataToSign = (0, utils_1.concatBytes)(headerWithoutSig, payload); const keyPair = nacl.sign.keyPair.fromSecretKey(privateKey); const signatureResult = nacl.sign.detached(dataToSign, keyPair.secretKey); signature.set(signatureResult); } // Create final header with signature const header = (0, utils_1.concatBytes)(constants_1.MAGIC_BYTES, (0, utils_1.stringToBytes)(constants_1.ODIN_VERSION.padEnd(constants_1.VERSION_SIZE, '\0')), new Uint8Array([type]), new Uint8Array([encoding]), new Uint8Array([compression]), (0, utils_1.numberToBytes)(timestamp, constants_1.TIMESTAMP_SIZE), creatorId, signature, checksum); // Combine header and payload return (0, utils_1.concatBytes)(header, payload); } /** * Unpack an ODIN capsule */ unpack(data) { if (data.length < constants_1.HEADER_SIZE) { throw new Error(constants_1.ERROR_MESSAGES.INSUFFICIENT_DATA); } let offset = 0; // Parse header const magic = data.slice(offset, offset + constants_1.MAGIC_SIZE); offset += constants_1.MAGIC_SIZE; if (!(0, utils_1.bytesEqual)(magic, constants_1.MAGIC_BYTES)) { throw new Error(constants_1.ERROR_MESSAGES.INVALID_MAGIC); } const versionBytes = data.slice(offset, offset + constants_1.VERSION_SIZE); offset += constants_1.VERSION_SIZE; const version = (0, utils_1.bytesToString)(versionBytes).replace(/\0+$/, ''); if (version !== constants_1.ODIN_VERSION) { throw new Error(constants_1.ERROR_MESSAGES.INVALID_VERSION); } const type = data[offset]; offset += constants_1.TYPE_SIZE; const encoding = data[offset]; offset += constants_1.ENCODING_SIZE; const compression = data[offset]; offset += constants_1.COMPRESSION_SIZE; const timestampBytes = data.slice(offset, offset + constants_1.TIMESTAMP_SIZE); offset += constants_1.TIMESTAMP_SIZE; const timestamp = (0, utils_1.bytesToNumber)(timestampBytes); const creatorId = data.slice(offset, offset + constants_1.CREATOR_ID_SIZE); offset += constants_1.CREATOR_ID_SIZE; const signature = data.slice(offset, offset + constants_1.SIGNATURE_SIZE); offset += constants_1.SIGNATURE_SIZE; const checksum = data.slice(offset, offset + constants_1.CHECKSUM_SIZE); offset += constants_1.CHECKSUM_SIZE; // Extract payload const payload = data.slice(offset); // Verify checksum const calculatedChecksum = (0, utils_1.blake3Hash)(payload); if (!(0, utils_1.bytesEqual)(checksum, calculatedChecksum)) { throw new Error(constants_1.ERROR_MESSAGES.INVALID_CHECKSUM); } // Verify timestamp if (!(0, utils_1.isValidTimestamp)(timestamp)) { throw new Error(constants_1.ERROR_MESSAGES.INVALID_TIMESTAMP); } // Create capsule object const capsule = { magic, version, type, encoding, compression, timestamp, creatorId, signature, checksum, payload }; // Decode payload let decodedPayload; switch (encoding) { case constants_1.EncodingType.MSGPACK: decodedPayload = msgpack.decode(payload); break; case constants_1.EncodingType.JSON: decodedPayload = JSON.parse((0, utils_1.bytesToString)(payload)); break; default: throw new Error(constants_1.ERROR_MESSAGES.INVALID_ENCODING); } return { capsule, payload: decodedPayload }; } /** * Verify capsule signature */ verify(data, publicKey) { try { const { capsule } = this.unpack(data); // Create data without signature for verification const headerWithoutSig = (0, utils_1.concatBytes)(capsule.magic, (0, utils_1.stringToBytes)(capsule.version.padEnd(constants_1.VERSION_SIZE, '\0')), new Uint8Array([capsule.type]), new Uint8Array([capsule.encoding]), new Uint8Array([capsule.compression]), (0, utils_1.numberToBytes)(capsule.timestamp, constants_1.TIMESTAMP_SIZE), capsule.creatorId, new Uint8Array(constants_1.SIGNATURE_SIZE), // Zero signature capsule.checksum); const dataToVerify = (0, utils_1.concatBytes)(headerWithoutSig, capsule.payload); return verifySignature(dataToVerify, capsule.signature, publicKey); } catch { return false; } } /** * Generate a new key pair for signing */ static generateKeyPair() { const keyPair = nacl.sign.keyPair.create(); return { publicKey: keyPair.publicKey, privateKey: keyPair.secretKey }; } /** * Get capsule info without unpacking payload */ inspect(data) { if (data.length < constants_1.HEADER_SIZE) { throw new Error(constants_1.ERROR_MESSAGES.INSUFFICIENT_DATA); } let offset = 0; const magic = data.slice(offset, offset + constants_1.MAGIC_SIZE); offset += constants_1.MAGIC_SIZE; const versionBytes = data.slice(offset, offset + constants_1.VERSION_SIZE); offset += constants_1.VERSION_SIZE; const version = (0, utils_1.bytesToString)(versionBytes).replace(/\0+$/, ''); const type = data[offset]; offset += constants_1.TYPE_SIZE; const encoding = data[offset]; offset += constants_1.ENCODING_SIZE; const compression = data[offset]; offset += constants_1.COMPRESSION_SIZE; const timestampBytes = data.slice(offset, offset + constants_1.TIMESTAMP_SIZE); offset += constants_1.TIMESTAMP_SIZE; const timestamp = (0, utils_1.bytesToNumber)(timestampBytes); const creatorId = data.slice(offset, offset + constants_1.CREATOR_ID_SIZE); offset += constants_1.CREATOR_ID_SIZE; const signature = data.slice(offset, offset + constants_1.SIGNATURE_SIZE); offset += constants_1.SIGNATURE_SIZE; const checksum = data.slice(offset, offset + constants_1.CHECKSUM_SIZE); return { magic, version, type, encoding, compression, timestamp, creatorId, signature, checksum }; } } exports.OdinProtocol = OdinProtocol; //# sourceMappingURL=odin.js.map