@maverick0351/odin-protocol-js
Version:
JavaScript/TypeScript SDK for ODIN v1.0 state capsule protocol
262 lines • 11.2 kB
JavaScript
;
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