UNPKG

@bsv/wallet-toolbox-client

Version:
315 lines 13.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SimpleWalletManager = void 0; const sdk_1 = require("@bsv/sdk"); /** * SimpleWalletManager is a slimmed-down wallet manager that only requires two things to authenticate: * 1. A primary key (32 bytes), which represents the core secret for the wallet. * 2. A privileged key manager (an instance of `PrivilegedKeyManager`), responsible for * more sensitive operations. * * Once both pieces are provided (or if a snapshot containing the primary key is loaded, * and the privileged key manager is provided separately), the wallet becomes authenticated. * * After authentication, calls to the standard wallet methods (`createAction`, `signAction`, etc.) * are proxied to an underlying `WalletInterface` instance returned by a user-supplied `walletBuilder`. * * **Important**: This manager does not handle user password flows, recovery, or on-chain * token management. It is a straightforward wrapper that ensures the user has provided * both their main secret (primary key) and a privileged key manager before allowing usage. * * It also prevents calls from the special "admin originator" from being used externally. * (Any call that tries to use the admin originator as its originator, other than the manager itself, * will result in an error, ensuring that only internal operations can use that originator.) * * The manager can also save and load snapshots of its state. In this simplified version, * the snapshot only contains the primary key. If you load a snapshot, you still need to * re-provide the privileged key manager to complete authentication. */ class SimpleWalletManager { /** * Constructs a new `SimpleWalletManager`. * * @param adminOriginator The domain name of the administrative originator. * @param walletBuilder A function that, given a primary key and privileged key manager, * returns a fully functional `WalletInterface`. * @param stateSnapshot If provided, a previously saved snapshot of the wallet's state. * If the snapshot contains a primary key, it will be loaded immediately * (though you will still need to provide a privileged key manager to authenticate). */ constructor(adminOriginator, walletBuilder, stateSnapshot) { this.authenticated = false; this.adminOriginator = adminOriginator; this.walletBuilder = walletBuilder; if (stateSnapshot) { this.loadSnapshot(stateSnapshot); } } /** * Provides the primary key (32 bytes) needed for authentication. * If a privileged key manager has already been provided, we attempt to build * the underlying wallet. Otherwise, we wait until the manager is also provided. * * @param key A 32-byte primary key. */ async providePrimaryKey(key) { this.primaryKey = key; await this.tryBuildUnderlying(); } /** * Provides the privileged key manager needed for sensitive tasks. * If a primary key has already been provided (or loaded from a snapshot), * we attempt to build the underlying wallet. Otherwise, we wait until the key is provided. * * @param manager An instance of `PrivilegedKeyManager`. */ async providePrivilegedKeyManager(manager) { this.underlyingPrivilegedKeyManager = manager; await this.tryBuildUnderlying(); } /** * Internal method that checks if we have both the primary key and privileged manager. * If so, we build the underlying wallet instance and become authenticated. */ async tryBuildUnderlying() { if (this.authenticated) { throw new Error('The user is already authenticated.'); } if (!this.primaryKey || !this.underlyingPrivilegedKeyManager) { return; } // Build the underlying wallet: this.underlying = await this.walletBuilder(this.primaryKey, this.underlyingPrivilegedKeyManager); this.authenticated = true; } /** * Destroys the underlying wallet, returning to a default (unauthenticated) state. * * This clears the primary key, the privileged key manager, and the `authenticated` flag. */ destroy() { this.underlying = undefined; this.underlyingPrivilegedKeyManager = undefined; this.authenticated = false; this.primaryKey = undefined; } /** * Saves the current wallet state (including just the primary key) * into an encrypted snapshot. This snapshot can be stored and later * passed to `loadSnapshot` to restore the primary key (and partially authenticate). * * **Note**: The snapshot does NOT include the privileged key manager. * You must still provide that separately after loading the snapshot * in order to complete authentication. * * @remarks * Storing the snapshot (which contains the primary key) provides a significant * portion of the wallet's secret material. It must be protected carefully. * * @returns A byte array representing the encrypted snapshot. * @throws {Error} if no primary key is currently set. */ saveSnapshot() { if (!this.primaryKey) { throw new Error('No primary key is set; cannot save snapshot.'); } // Generate a random snapshot encryption key: const snapshotKey = (0, sdk_1.Random)(32); // For this simple wallet manager, we only store the primary key. const writer = new sdk_1.Utils.Writer(); // Write a 1-byte version: writer.writeUInt8(1); // Write a varint length and then the primary key bytes: writer.writeVarIntNum(this.primaryKey.length); writer.write(this.primaryKey); const snapshotPreimage = writer.toArray(); // Encrypt the data with the snapshotKey: const encryptedPayload = new sdk_1.SymmetricKey(snapshotKey).encrypt(snapshotPreimage); // Build the final snapshot: [ snapshotKey (32 bytes) + encryptedPayload ] const snapshotWriter = new sdk_1.Utils.Writer(); snapshotWriter.write(snapshotKey); snapshotWriter.write(encryptedPayload); return snapshotWriter.toArray(); } /** * Loads a previously saved state snapshot (produced by `saveSnapshot`). * This will restore the primary key but will **not** restore the privileged key manager * (that must be provided separately to complete authentication). * * @param snapshot A byte array that was previously returned by `saveSnapshot`. * @throws {Error} If the snapshot format is invalid or decryption fails. */ async loadSnapshot(snapshot) { try { const reader = new sdk_1.Utils.Reader(snapshot); // First 32 bytes is the snapshotKey: const snapshotKey = reader.read(32); // The rest is the encrypted payload: const encryptedPayload = reader.read(); // Decrypt the payload with the snapshotKey: const decrypted = new sdk_1.SymmetricKey(snapshotKey).decrypt(encryptedPayload); const payloadReader = new sdk_1.Utils.Reader(decrypted); // Check version: const version = payloadReader.readUInt8(); if (version !== 1) { throw new Error(`Unsupported snapshot version: ${version}`); } // Read the varint length and the primary key: const pkLength = payloadReader.readVarIntNum(); const pk = payloadReader.read(pkLength); this.primaryKey = pk; // Attempt to build the underlying wallet if the privileged manager is already provided: await this.tryBuildUnderlying(); } catch (error) { throw new Error(`Failed to load snapshot: ${error.message}`); } } /** * Returns whether the user is currently authenticated (the wallet has a primary key * and a privileged key manager). If not authenticated, an error is thrown. * * @param _ Not used in this manager. * @param originator The originator domain, which must not be the admin originator. * @throws If not authenticated, or if the originator is the admin. */ async isAuthenticated(_, originator) { this.ensureCanCall(originator); return { authenticated: true }; } /** * Blocks until the user is authenticated (by providing primaryKey and privileged manager). * If not authenticated yet, it waits until that occurs. * * @param _ Not used in this manager. * @param originator The originator domain, which must not be the admin originator. * @throws If the originator is the admin. */ async waitForAuthentication(_, originator) { if (originator === this.adminOriginator) { throw new Error('External applications cannot use the admin originator.'); } while (!this.authenticated) { await new Promise(resolve => setTimeout(resolve, 100)); } return { authenticated: true }; } async getPublicKey(args, originator) { this.ensureCanCall(originator); return this.underlying.getPublicKey(args, originator); } async revealCounterpartyKeyLinkage(args, originator) { this.ensureCanCall(originator); return this.underlying.revealCounterpartyKeyLinkage(args, originator); } async revealSpecificKeyLinkage(args, originator) { this.ensureCanCall(originator); return this.underlying.revealSpecificKeyLinkage(args, originator); } async encrypt(args, originator) { this.ensureCanCall(originator); return this.underlying.encrypt(args, originator); } async decrypt(args, originator) { this.ensureCanCall(originator); return this.underlying.decrypt(args, originator); } async createHmac(args, originator) { this.ensureCanCall(originator); return this.underlying.createHmac(args, originator); } async verifyHmac(args, originator) { this.ensureCanCall(originator); return this.underlying.verifyHmac(args, originator); } async createSignature(args, originator) { this.ensureCanCall(originator); return this.underlying.createSignature(args, originator); } async verifySignature(args, originator) { this.ensureCanCall(originator); return this.underlying.verifySignature(args, originator); } async createAction(args, originator) { this.ensureCanCall(originator); return this.underlying.createAction(args, originator); } async signAction(args, originator) { this.ensureCanCall(originator); return this.underlying.signAction(args, originator); } async abortAction(args, originator) { this.ensureCanCall(originator); return this.underlying.abortAction(args, originator); } async listActions(args, originator) { this.ensureCanCall(originator); return this.underlying.listActions(args, originator); } async internalizeAction(args, originator) { this.ensureCanCall(originator); return this.underlying.internalizeAction(args, originator); } async listOutputs(args, originator) { this.ensureCanCall(originator); return this.underlying.listOutputs(args, originator); } async relinquishOutput(args, originator) { this.ensureCanCall(originator); return this.underlying.relinquishOutput(args, originator); } async acquireCertificate(args, originator) { this.ensureCanCall(originator); return this.underlying.acquireCertificate(args, originator); } async listCertificates(args, originator) { this.ensureCanCall(originator); return this.underlying.listCertificates(args, originator); } async proveCertificate(args, originator) { this.ensureCanCall(originator); return this.underlying.proveCertificate(args, originator); } async relinquishCertificate(args, originator) { this.ensureCanCall(originator); return this.underlying.relinquishCertificate(args, originator); } async discoverByIdentityKey(args, originator) { this.ensureCanCall(originator); return this.underlying.discoverByIdentityKey(args, originator); } async discoverByAttributes(args, originator) { this.ensureCanCall(originator); return this.underlying.discoverByAttributes(args, originator); } async getHeight(_, originator) { this.ensureCanCall(originator); return this.underlying.getHeight({}, originator); } async getHeaderForHeight(args, originator) { this.ensureCanCall(originator); return this.underlying.getHeaderForHeight(args, originator); } async getNetwork(_, originator) { this.ensureCanCall(originator); return this.underlying.getNetwork({}, originator); } async getVersion(_, originator) { this.ensureCanCall(originator); return this.underlying.getVersion({}, originator); } /** * A small helper that throws if the user is not authenticated or if the * provided originator is the admin (which is not permitted externally). */ ensureCanCall(originator) { if (originator === this.adminOriginator) { throw new Error('External applications cannot use the admin originator.'); } if (!this.authenticated) { throw new Error('User is not authenticated.'); } } } exports.SimpleWalletManager = SimpleWalletManager; //# sourceMappingURL=SimpleWalletManager.js.map