Testing with an Isolated IPFS Node¶
For advanced debugging, it can be useful to inspect IPFS content directly from a standalone container that is separate from the running TruSpace stack — for example, to fetch a CID and manually decrypt a document outside of the application.
Advanced / manual procedure
This is a low-level diagnostic technique intended for developers and administrators comfortable working directly with IPFS and Node.js. It is not required for normal operation of TruSpace.
1. Run an Isolated Container¶
Start a plain Ubuntu container attached to the TruSpace Docker network, with its own set of ports so it does not collide with the running stack:
docker run -itd --network truspace_default --name singleipfs \
-p 5027:5001 -p 4027:4001 -p 8027:8080 -p 8028:8081 \
--platform linux/arm64 arm64v8/ubuntu
Platform flag
Adjust --platform (e.g. linux/amd64) to match your host architecture.
Attach a shell to the container:
2. Install an IPFS Node (Kubo)¶
Inside the container:
apt update -y && apt upgrade -y && apt install wget -y && apt install nodejs -y
wget https://dist.ipfs.tech/kubo/v0.33.2/kubo_v0.33.2_linux-arm64.tar.gz
tar xvfz kubo_v0.33.2_linux-arm64.tar.gz
cd kubo
./install.sh
ipfs init
ipfs daemon
Matching architecture
Download the Kubo release matching your platform (e.g. kubo_v0.33.2_linux-amd64.tar.gz) from the Kubo release list.
3. Fetch Content by CID¶
With the daemon running (in a second shell into the same container, or backgrounded):
4. Decrypt a Fetched Document¶
TruSpace encrypts document content before writing it to IPFS, using the workspace ID as part of the key derivation (see Security Architecture). To inspect a fetched file's plaintext outside of the application, use the helper script below with the workspace ID (CID) as the password.
Save as decrypt.js:
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");
const ALGORITHM = "aes-256-cbc";
const KEY_DERIVATION_ITERATIONS = 10000;
const KEY_LENGTH = 32;
const IV_LENGTH = 16;
const SALT_LENGTH = 64;
/**
* Encrypts data using AES-256-CBC and a user-provided key.
*
* @param {Buffer|string} data - The data to be encrypted.
* @param {string} password - The key used for encryption.
* @returns {Promise<Buffer>} - Encrypted data as a Buffer.
*/
async function encrypt(data, password) {
const iv = crypto.randomBytes(IV_LENGTH);
const salt = crypto.randomBytes(SALT_LENGTH);
const key = await new Promise((resolve, reject) => {
crypto.pbkdf2(
password,
salt,
KEY_DERIVATION_ITERATIONS,
KEY_LENGTH,
"sha512",
(err, derivedKey) => {
if (err) reject(err);
else resolve(derivedKey);
}
);
});
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
return Buffer.concat([salt, iv, encrypted]);
}
/**
* Decrypts data previously encrypted with the `encrypt` function.
*
* @param {Buffer} data - Encrypted Buffer.
* @param {string} password - The same key used during encryption.
* @returns {Promise<Buffer>} - The decrypted data.
*/
async function decrypt(data, password) {
const salt = data.subarray(0, SALT_LENGTH);
const iv = data.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
const text = data.subarray(SALT_LENGTH + IV_LENGTH);
const key = await new Promise((resolve, reject) => {
crypto.pbkdf2(
password,
salt,
KEY_DERIVATION_ITERATIONS,
KEY_LENGTH,
"sha512",
(err, derivedKey) => {
if (err) reject(err);
else resolve(derivedKey);
}
);
});
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
const decrypted = Buffer.concat([decipher.update(text), decipher.final()]);
return decrypted;
}
/**
* Reads file content.
* @param {string} filePath - Path to the file.
* @returns {Buffer} - File content as Buffer.
*/
function readFile(filePath) {
if (!fs.existsSync(filePath)) {
console.error(`Error: File '${filePath}' not found.`);
process.exit(1);
}
return fs.readFileSync(filePath);
}
/**
* Writes content to a file.
* @param {string} filePath - Path to the file.
* @param {Buffer} data - Data to write.
*/
function writeFile(filePath, data) {
fs.writeFileSync(filePath, data);
console.log(`Output written to: ${filePath}`);
}
// Command-line interface (CLI)
(async () => {
let [, , command, inputArg, inputFile, password] = process.argv;
if (!command || !inputArg || !password) {
console.error("Usage:");
console.error(' node script.js encrypt "your message" "your-password"');
console.error(' node script.js encrypt --file input.txt "your-password"');
console.error(' node script.js decrypt <hex-data> "your-password"');
console.error(
' node script.js decrypt --file encrypted.txt "your-password"'
);
process.exit(1);
}
let isFile = inputArg === "--file";
let inputData;
let outputFilePath;
if (isFile) {
if (!inputFile) {
console.error("Error: No file specified after '--file'.");
process.exit(1);
}
inputData = readFile(inputFile);
outputFilePath =
command === "encrypt" ? `${inputFile}.enc` : `${inputFile}.dec`;
} else {
inputData = inputArg;
}
try {
if (command === "encrypt") {
const encryptedData = await encrypt(inputData, password);
if (isFile) {
writeFile(outputFilePath, encryptedData);
} else {
console.log("Encrypted Data (Hex):", encryptedData.toString("hex"));
}
} else if (command === "decrypt") {
const encryptedBuffer = isFile
? readFile(inputFile)
: Buffer.from(inputArg, "hex");
const decryptedData = await decrypt(encryptedBuffer, password);
if (isFile) {
writeFile(outputFilePath, decryptedData);
} else {
console.log("Decrypted Data:", decryptedData.toString("utf8"));
}
} else {
console.error("Invalid command. Use 'encrypt' or 'decrypt'.");
process.exit(1);
}
} catch (error) {
console.error("Error:", error.message);
}
})();
Usage:
# Decrypt a file fetched from IPFS, using the workspace CID as the password
node decrypt.js decrypt --file ./<fetched-file> "<workspace-cid>"
# Encrypt arbitrary data the same way TruSpace would
node decrypt.js encrypt "some plaintext" "<workspace-cid>"