diff --git a/package-lock.json b/package-lock.json index b0d844f..df0a2d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "cli-progress": "^3.12.0", "crypto-js": "^4.2.0", "datalayer-driver": "^0.1.17", + "datalayer-driver-linux-x64-gnu": "^0.1.17", "express": "^4.19.2", "fs-extra": "^11.2.0", "ignore": "^5.3.2", @@ -2079,6 +2080,21 @@ "datalayer-driver-win32-x64-msvc": "0.1.17" } }, + "node_modules/datalayer-driver-linux-x64-gnu": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/datalayer-driver-linux-x64-gnu/-/datalayer-driver-linux-x64-gnu-0.1.17.tgz", + "integrity": "sha512-52ztE1BA+VO13KQN5KP6h+4yclS4bCfQ0/OfrUsr+8vZmRSavnFP0DvKag5XHt2fW1R74sXI28X64uu6AbHHjg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/datalayer-driver-win32-x64-msvc": { "version": "0.1.17", "resolved": "https://registry.npmjs.org/datalayer-driver-win32-x64-msvc/-/datalayer-driver-win32-x64-msvc-0.1.17.tgz", diff --git a/package.json b/package.json index ab428c0..55b409a 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "cli-progress": "^3.12.0", "crypto-js": "^4.2.0", "datalayer-driver": "^0.1.17", + "datalayer-driver-linux-x64-gnu": "^0.1.17", "express": "^4.19.2", "fs-extra": "^11.2.0", "ignore": "^5.3.2", diff --git a/src/actions/index.ts b/src/actions/index.ts index e24523d..edca7ba 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -4,7 +4,9 @@ export * from './middleware'; export * from './push'; export * from './remote'; export * from './pull'; -export * from './commit' +export * from './commit'; export * from './validate'; +export * from './store'; export * from './login'; +export * from './logout'; export * from './generate'; \ No newline at end of file diff --git a/src/actions/store.ts b/src/actions/store.ts index c3acb64..8c04d45 100644 --- a/src/actions/store.ts +++ b/src/actions/store.ts @@ -1,17 +1,257 @@ - -/* -export const upsert = async ({ writerPublicAddress, adminPublicAddress, oracleFee }): Promise => { - -} - -export const remove = async ({ writerPublicAddress, adminPublicAddress, oracleFee }): Promise => { - -} - -export const transfer = async ({ receivePublicAddress }): Promise => { - -} - -export const melt = async (): Promise => { - -}*/ +import {getActiveStoreId, STORE_PATH} from "../utils/config"; +import {DataIntegrityTree, DataIntegrityTreeOptions} from "../DataIntegrityTree"; +import {Buffer} from "buffer"; +import {Readable} from "stream"; +import fs from "fs"; + +/* +export const remove = async ({ writerPublicAddress, adminPublicAddress, oracleFee }): Promise => { + +} + +export const transfer = async ({ receivePublicAddress }): Promise => { + +} + +export const melt = async (): Promise => { + +}*/ + +export const deleteKey = async (key: string) => { + try { + const storeIdResult = getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + datalayer.deleteKey(key); + } catch (error: any) { + console.error('Cannot delete key:', error.message); + } +} + +export const upsertData = async (key: string, data: string) => { + try { + const storeIdResult = getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const dataStream = new Readable(); + dataStream.push(data); + dataStream.push(null); + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + await datalayer.upsertKey(dataStream, key); + + console.log(`Upserted data to datastore ${storeId} with key ${key}`); + } catch (error: any) { + console.error('Cannot upsert data:', error.message); + } +} + +export const upsertFile = async (key: string, filePath: string) => { + try { + const storeIdResult = getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const fileStream = fs.createReadStream(filePath); + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + await datalayer.upsertKey(fileStream, key); + + } catch (error: any) { + console.error('Cannot upsert file:', error.message); + } +} + +export const getKey = async (key: string) => { + try { + const storeIdResult = await getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + const valueStream = datalayer.getValueStream(key); + let valueData: Buffer = Buffer.alloc(0); + const valueDataHexArr: string[] = []; + + valueStream.on('data', (chunk) => { + const hex = chunk.toString('hex'); + valueDataHexArr.push(hex); + valueData = Buffer.concat([valueData, chunk]); + }); + + valueStream.on('end', () => { + const dataUtf8: string = valueData.toString('utf8'); + let dataPrinted = false + console.log(`Data for key ${key}:\n\n`); + + // print as json + try { + JSON.parse(dataUtf8); + console.log(dataUtf8); + dataPrinted = true; + } catch {} + + // print as xml + if (!dataPrinted) { + try { + const parser = new DOMParser(); + parser.parseFromString(dataUtf8, 'text/xml'); + console.log(dataUtf8); + dataPrinted = true; + } catch {} + } + + // print as hex + if (!dataPrinted) { + valueDataHexArr.forEach(chunk => { + let printableHexString = '' + for (let j = 0; j < chunk.length; j++) { + printableHexString += chunk[j]; + if (j % 2 === 0){ + printableHexString += ' '; + } + if (j % 40 === 0){ + printableHexString += '\n' + } + } + console.log(printableHexString); + }) + } + }); + + valueStream.on('error', (error) => { + throw error; + }); + + } catch (error: any) { + console.error('Cannot get key value:', error.message); + } +} + +export const getProof = async (key: string, sha256: string) => { + try { + const storeIdResult = getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + const proof = datalayer.getProof(key, sha256); + + console.log(`Proof for key ${key}\nand sha256 hash ${sha256}:`); + console.log(proof); + } catch (error: any) { + console.error('Cannot get proof:', error.message); + } +} + +export const getRoot = async () => { + try { + const storeIdResult = await getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + const rootHash = datalayer.getRoot(); + console.log(`The root hash is ${rootHash}`); + + } catch (error: any) { + console.error('Failed to get root hash:', error.message); + } +} + +export const listKeys = async () => { + try { + const storeIdResult = await getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + const keysArr = datalayer.listKeys(); + + if (keysArr.length > 0){ + let printableList: string = ''; + keysArr.forEach(key => { + printableList += `${key}\n`; + }); + + console.log(`Keys for store ${storeId}:\n\n${printableList}`); + } else { + console.log(`Store ${storeId} has no keys`); + } + + } catch (error: any) { + console.error('Failed to get store keys:', error.message); + } +} + +export const verifyProof = async (proof: string, sha256: string) => { + try { + const storeIdResult = getActiveStoreId(); + const storeId = storeIdResult?.toString(); + if (!storeId){ + throw new Error('Failed to find datastore'); + } + + const options: DataIntegrityTreeOptions = { + storageMode: "local", + storeDir: STORE_PATH, + }; + const datalayer = new DataIntegrityTree(storeId, options); + const proofVerified = datalayer.verifyProof(proof, sha256); + + if (proofVerified) { + console.log('Proof has been verified'); + } else { + console.error('Proof verification failed with provided hash'); + } + } catch (error: any) { + console.error('Cannot process proof:', error.message); + } +} + + diff --git a/src/types.ts b/src/types.ts index 40bf128..8c80f0f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,3 +44,8 @@ export interface Credentials { username: string; password: string; } + +export interface GetProof { + key: string, + sha256: string, +} diff --git a/src/yargs/commands.ts b/src/yargs/commands.ts index 5aeeb28..bac2a66 100644 --- a/src/yargs/commands.ts +++ b/src/yargs/commands.ts @@ -1,6 +1,7 @@ import yargs, { Argv } from "yargs"; import { handlers } from "./handlers"; import { CreateStoreUserInputs } from "../types"; +import {isArrayBufferView} from "node:util/types"; export function initCommand(yargs: Argv<{}>) { return yargs.command( @@ -92,9 +93,30 @@ export function storeCommand(yargs: Argv<{}>) { (yargs: Argv<{ action: string }>) => { return yargs .positional("action", { - describe: "Action to perform on keys", + describe: "The store action to perform", + type: "string", + choices: ["validate", "upsert_file", "upsert_data", "remove", "get_root", "get_proof", "verify_proof", + "list", "get_key", "delete_key"], + }) + .option ("key", { + type: "string", + describe: "The store key on which to operate" + }) + .option ("data", { + type: "string", + describe: "The data to upsert" + }) + .option ("path", { + type: "string", + describe: "The path to the file to upsert" + }) + .option ("proof", { type: "string", - choices: ["validate", "update", "remove"], + describe: "The proof to verify" + }) + .option ("sha256", { + type: "string", + describe: "The sha256 hash of the data corresponding to a store key" }) .option("writer", { type: "string", @@ -108,10 +130,39 @@ export function storeCommand(yargs: Argv<{}>) { type: "string", describe: "Specify an admin for the store", }) - .strict(); // Ensures that only the defined options are accepted + .check((argv) => { + if (argv.action === "get_proof") { + if (!argv.key || !argv.sha256) { + throw new Error(`The --key and --sha256 options are required for the '${argv.action}' action.`); + } + } else if (argv.action === "verify_proof") { + if (!argv.proof || !argv.sha256) { + throw new Error(`The --proof and --sha256 options are required for the '${argv.action}' action.`); + } + } else if (argv.action === "get_key") { + if (!argv.key) { + throw new Error(`The --key option is required for the '${argv.action}' action.`); + } + } else if (argv.action === "upsert_data") { + if (!argv.key || !argv.data) { + throw new Error(`The --key and --data options are required for the '${argv.action}' action.`); + } + } else if (argv.action === "upsert_file") { + if (!argv.key || !argv.path) { + throw new Error(`The --key and --path options are required for the '${argv.action}' action.`); + } + } + else if (argv.action === "delete_key") { + if (!argv.key) { + throw new Error(`The --key option is required for the '${argv.action}' action.`); + } + } + return true; + }) + .strict(); }, - async (argv: { action: string }) => { - await handlers.manageStore(argv.action); + async (argv) => { + await handlers.manageStore(argv); } ); } diff --git a/src/yargs/handlers.ts b/src/yargs/handlers.ts index 535d04f..25df953 100644 --- a/src/yargs/handlers.ts +++ b/src/yargs/handlers.ts @@ -13,12 +13,20 @@ import { init, validate, login, + logout, + getProof, + verifyProof, + listKeys, + getRoot, + getKey, + upsertData, + upsertFile, + deleteKey, syncRemoteSeed as _syncRemoteSeed, setRemoteSeed as _setRemoteSeed, generateEntropyValue } from "../actions"; import { CreateStoreUserInputs } from "../types"; -import { logout } from "../actions/logout"; import { startContentServer } from "../content_server/server"; import { checkStoreWritePermissions } from "../actions"; import { getActiveStoreId } from "../utils/config"; @@ -73,19 +81,58 @@ export const handlers = { generateCreds: async () => { await generateEntropyValue(); }, - manageStore: async (action: string) => { - switch (action) { - case "validate": - await validate(); - break; - case "update": - // await upsertStore(); - break; - case "remove": - // await removeStore(); - break; - default: - console.error("Unknown store action"); + manageStore: async (argv: {action: string} & any) => { + try { + switch (argv.action) { + case "validate": + await validate(); + break; + case "upsert_data": { + const {key, data} = argv; + await upsertData(key, data); + break; + } + case "upsert_file": { + const {key, path} = argv; + await upsertFile(key, path); + break; + } + case "remove": + // await removeStore(); + break; + case "get_proof": { + const {key, sha256} = argv; + await getProof(key, sha256); + break; + } + case "verify_proof": { + const {proof, sha256} = argv; + await verifyProof(proof, sha256); + break; + } + case "list": { + await listKeys(); + break; + } + case "getRoot": { + await getRoot(); + break; + } + case "get_key": { + const {key} = argv; + await getKey(key); + break; + } + case "delete_key": { + const {key} = argv; + await deleteKey(key); + break; + } + default: + console.error(`Unknown action ${argv.action}`); + } + } catch { + console.error('Invalid command structure') } }, manageKeys: async (action: string, providedMnemonic?: string) => {