@originvault/ov-id-sdk
Version:
A TypeScript SDK for managing decentralized identities (DIDs) and verifiable credentials (VCs)
291 lines • 10.8 kB
JavaScript
import { Keyring } from '@polkadot/keyring';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import os from 'os';
import path from 'path';
import * as ed25519 from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha2'; // Ensure correct import
import dotenv from 'dotenv';
import fs from 'fs';
import { convertPrivateKeyToRecovery, decryptPrivateKey } from './encryption.js';
import inquirer from 'inquirer';
dotenv.config();
ed25519.etc.sha512Sync = sha512;
// Initialize keyring
let keyring;
export const KEYRING_FILE = path.join(os.homedir(), '.originvault-cheqd-did-keyring.json');
// Define the path for the encryption key file
const keyStore = {
encryptionKeyFilePath: path.join(os.homedir(), '.originvault-encryption-key'),
privateEncryptionKey: process.env.ENCRYPTION_KEY || 'admin-key',
};
async function initializeEncryptionKey() {
try {
const keyPath = path.join(os.homedir(), '.originvault-encryption-key');
if (!fs.existsSync(keyPath)) {
if (process.env.ENCRYPTION_KEY) {
keyStore.privateEncryptionKey = process.env.ENCRYPTION_KEY;
keyStore.encryptionKeyFilePath = keyPath;
return;
}
const { encryptionKey: inputKey } = await inquirer.prompt([
{
type: 'password',
name: 'encryptionKey',
message: 'Enter an encryption key to encrypt the password:',
mask: '*',
},
]);
// Store the encryption key in the file
fs.writeFileSync(keyPath, JSON.stringify({ key: inputKey }), 'utf8');
keyStore.privateEncryptionKey = inputKey;
keyStore.encryptionKeyFilePath = keyPath;
}
else {
const { key } = JSON.parse(fs.readFileSync(keyPath, 'utf8'));
keyStore.privateEncryptionKey = key;
}
}
catch (error) {
console.error("❌ Error initializing encryption key:", error);
throw error;
}
}
export async function getEncryptionKey() {
await initializeEncryptionKey();
return keyStore.privateEncryptionKey;
}
export async function storeEncryptionKey(key) {
await initializeEncryptionKey();
keyStore.privateEncryptionKey = key;
fs.writeFileSync(keyStore.encryptionKeyFilePath, JSON.stringify({ key }));
}
// Ensure the keyring is initialized
export async function ensureKeyring() {
await initializeEncryptionKey();
if (!keyring) {
await cryptoWaitReady();
keyring = new Keyring({ type: 'ed25519' });
if (fs.existsSync(KEYRING_FILE)) {
const keys = JSON.parse(fs.readFileSync(KEYRING_FILE, 'utf8'));
keys?.forEach((key) => {
keyring?.addFromJson(key);
});
}
}
return keyring;
}
// Exported functions
export const getVerifiedAuthentication = async (did, agent, kid) => {
if (!agent) {
throw new Error("Agent not found");
}
let resolvedDid = await agent?.resolveDid({ didUrl: did });
if (!resolvedDid || resolvedDid.didResolutionMetadata?.error) {
console.error("❌ DID could not be resolved", did, resolvedDid);
return null;
}
const didDoc = resolvedDid.didDocument;
const authentication = kid ? didDoc?.authentication?.find(auth => {
if (typeof auth === 'string') {
return auth === kid;
}
return auth.id === kid;
}) : didDoc?.authentication?.[0];
if (!authentication) {
console.error("❌ No authentication found for DID", did);
return null;
}
const verificationMethods = didDoc?.verificationMethod;
if (!verificationMethods) {
console.error("❌ No verification method found for DID", did);
return null;
}
const verifiedAuthentication = verificationMethods.find(method => method.id === authentication);
if (!verifiedAuthentication) {
console.error("❌ Could not find verification method for standard did authentication", did);
return null;
}
return verifiedAuthentication;
};
export const getPublicKeyMultibase = async (did, agent) => {
const verifiedAuthentication = await getVerifiedAuthentication(did, agent);
if (!verifiedAuthentication) {
return undefined;
}
const publicKeyMultibase = verifiedAuthentication.publicKeyMultibase;
return publicKeyMultibase;
};
export async function getPrivateKeyForPrimaryDID(password) {
await ensureKeyring();
const storedData = fs.readFileSync(KEYRING_FILE, 'utf8');
const { encryptedPrivateKey } = JSON.parse(storedData);
if (!encryptedPrivateKey)
return false;
const privateKey = decryptPrivateKey(encryptedPrivateKey, password);
if (!privateKey) {
console.error("❌ Failed to decrypt private key");
return false;
}
return privateKey;
}
export async function storePrivateKey(keyName, privateKey, kid) {
try {
// Check the length of the private key
if (privateKey.length === 64) {
privateKey = privateKey.slice(0, 32); // Use only the first 32 bytes
}
else if (privateKey.length !== 32) {
throw new Error("Invalid private key length. Expected 32 bytes or 64 bytes.");
}
const kr = await ensureKeyring();
const pair = kr.addFromSeed(privateKey, { keyName, isPrimary: false, kid });
kr.addPair(pair);
fs.writeFileSync(KEYRING_FILE, JSON.stringify(kr.getPairs().map(pair => pair.toJson()), null, 2));
// Check if the private key is stored correctly
const storedData = JSON.parse(fs.readFileSync(KEYRING_FILE, 'utf8'));
const isKeyStored = storedData.some((pair) => pair.address === pair.address); // Adjust this condition as needed
if (!isKeyStored) {
console.error("❌ Private Key not found in the keyring file.");
}
}
catch (error) {
console.error("❌ Error storing private key:", error);
throw error;
}
}
export async function retrievePrivateKey(keyName) {
try {
const kr = await ensureKeyring();
const pairs = kr.getPairs().map(pair => pair.toJson());
const pair = pairs.find(p => p.meta.keyName === keyName);
return kr.decodeAddress(pair?.address);
}
catch (error) {
console.error("❌ Error retrieving private key:", error);
return undefined;
}
}
export async function retrieveKeys(keyName) {
const kr = await ensureKeyring();
const pairs = kr.getPairs().map(pair => pair.toJson());
const pair = pairs.find(p => p.meta.keyName === keyName);
return {
keyName: pair?.meta.keyName,
isPrimary: pair?.meta.isPrimary,
kid: pair?.meta.kid
};
}
export async function retrieveMnemonicForDID(did) {
const privateKey = await retrievePrivateKey(did);
if (!privateKey) {
return undefined;
}
const mnemonic = await convertPrivateKeyToRecovery(Buffer.from(privateKey).toString('hex'));
return mnemonic;
}
export async function listAllKeys() {
try {
const kr = await ensureKeyring();
const pairs = kr.getPairs().map(pair => pair.toJson());
return pairs.map(pair => ({
did: pair.meta.keyName,
privateKey: pair.address,
isPrimary: pair.meta.isPrimary,
kid: pair.meta.kid
}));
}
catch (error) {
console.error("❌ Error listing keys:", error);
return [];
}
}
export async function deleteKey(did) {
try {
const kr = await ensureKeyring();
const pairs = kr.getPairs().map(pair => pair.toJson());
const pair = pairs.find(p => p.meta.did === did);
if (pair) {
kr.removePair(pair.address);
return true;
}
return false;
}
catch (error) {
console.error("❌ Error deleting key:", error);
return false;
}
}
export function base64ToHex(base64) {
// Decode the Base64 string to a byte array
const binaryString = atob(base64); // atob decodes a Base64 string
const byteArray = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
byteArray[i] = binaryString.charCodeAt(i);
}
// Convert the byte array to a hexadecimal string
let hexString = '';
byteArray.forEach(byte => {
const hex = byte.toString(16).padStart(2, '0'); // Convert to hex and pad with zero if needed
hexString += hex;
});
return hexString;
}
export function setPrimaryVc(signedVC) {
// Log the signed VC
console.log("Setting primary VC:", signedVC);
// Example: Store the signed VC in a file
const vcFilePath = path.join(os.homedir(), 'primary-vc.json');
try {
fs.writeFileSync(vcFilePath, JSON.stringify(signedVC, null, 2));
console.log("✅ Primary VC stored successfully at", vcFilePath);
}
catch (error) {
console.error("❌ Error storing primary VC:", error);
}
}
export async function getPrimaryVC() {
try {
const storedData = fs.readFileSync(KEYRING_FILE, 'utf8');
const { meta } = JSON.parse(storedData);
if (meta && meta.credential) {
return meta.credential; // Return the signed VC
}
console.error("❌ No primary VC found in keyring.");
return null;
}
catch (error) {
console.error("❌ Error accessing keyring:", error);
return null;
}
}
export function getStoredPassword() {
ensurePasswordFileExists();
const passwordFilePath = path.join(os.homedir(), '.encrypted-password');
const encryptedPassword = JSON.parse(fs.readFileSync(passwordFilePath, 'utf8').trim());
if (!encryptedPassword.iv) {
return encryptedPassword;
}
try {
return decryptPrivateKey(encryptedPassword, process.env.ENCRYPTION_KEY || '');
}
catch (error) {
console.error(`❌ Error retrieving stored password: ${error}`);
return null;
}
}
export function hexToBase64(hex) {
// Convert hex string to byte array
const byteArray = new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
// Convert byte array to Base64 string
const binaryString = String.fromCharCode(...byteArray);
return btoa(binaryString); // btoa encodes a binary string to Base64
}
// ✅ Ensure the password file exists
function ensurePasswordFileExists() {
const passwordFilePath = path.join(os.homedir(), '.encrypted-password');
console.log("Getting stored password", passwordFilePath);
if (!fs.existsSync(passwordFilePath)) {
fs.writeFileSync(passwordFilePath, JSON.stringify("")); // Create an empty password file
}
}
//# sourceMappingURL=storePrivateKeys.js.map