UNPKG

@bsv/wallet-toolbox-mobile

Version:

React Native and mobile-safe BRC-100 wallet, signer, and remote storage components

1,536 lines 1.2 MB
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs"; import { AuthFetch, BEEF_V1, BEEF_V2, Beef, BeefParty, BigNumber, CachedKeyDeriver, Certificate, Curve, Hash, LocalKVStore, LockingScript, LookupResolver, MasterCertificate, MerklePath, P2PKH, PrivateKey, ProtoWallet, PublicKey, PushDrop, RPuzzle, Random, SHIPBroadcaster, Script, ScriptEvaluationError, Signature, Spend, SymmetricKey, Telemetry, Transaction, TransactionSignature, Utils, Validation, Validation as Validation$1, VerifiableCertificate, createNonce, defaultHttpClient, verifyNonce } from "@bsv/sdk"; import { AESGCM, AESGCMDecrypt } from "@bsv/sdk/primitives/AESGCM"; import argon2Api from "hash-wasm/dist/argon2.umd.min.js"; import pbkdf2Api from "hash-wasm/dist/pbkdf2.umd.min.js"; import sha256Api from "hash-wasm/dist/sha256.umd.min.js"; import sha512Api from "hash-wasm/dist/sha512.umd.min.js"; import { openDB } from "idb"; //#region ../src/sdk/WalletError.ts /** * Derived class constructors should use the derived class name as the value for `name`, * and an internationalizable constant string for `message`. * * If a derived class intends to wrap another WalletError, the public property should * be named `walletError` and will be recovered by `fromUnknown`. * * Optionaly, the derived class `message` can include template parameters passed in * to the constructor. See WERR_MISSING_PARAMETER for an example. * * To avoid derived class name colisions, packages should include a package specific * identifier after the 'WERR_' prefix. e.g. 'WERR_FOO_' as the prefix for Foo package error * classes. */ var WalletError = class WalletError extends Error { details; isError = true; constructor(name, message, stack, details) { super(message); this.details = details; this.name = name; if (stack != null && stack !== "") this.stack = stack; } /** * Error class compatible accessor for `code`. */ get code() { return this.name; } set code(v) { this.name = v; } /** * Error class compatible accessor for `description`. */ get description() { return this.message; } set description(v) { this.message = v; } /** * Recovers all public fields from WalletError derived error classes and relevant Error derived errors. * */ static nonEmptyString(value) { return typeof value === "string" && value !== "" ? value : void 0; } static objectErrorFields(error) { const stringValue = WalletError.nonEmptyString; const name = error.name === "Error" || error.name === "FetchError" ? stringValue(error.code) ?? stringValue(error.status) ?? "WERR_UNKNOWN" : stringValue(error.name) ?? stringValue(error.code) ?? stringValue(error.status) ?? "WERR_UNKNOWN"; const message = stringValue(error.message) ?? stringValue(error.description) ?? ""; const stack = typeof error.stack === "string" ? error.stack : void 0; const details = {}; if (typeof error.sql === "string") details.sql = error.sql; if (typeof error.sqlMessage === "string") details.sqlMessage = error.sqlMessage; return { name, message, stack, details: Object.keys(details).length > 0 ? details : void 0 }; } static copyPublicErrorFields(target, source) { const extensibleTarget = target; const baseFields = /* @__PURE__ */ new Set([ "status", "name", "code", "message", "description", "stack", "sql", "sqlMessage" ]); for (const [key, value] of Object.entries(source)) { const supportedValue = typeof value === "string" || typeof value === "number" || Array.isArray(value); if (key === "walletError") extensibleTarget[key] = WalletError.fromUnknown(value); else if (!baseFields.has(key) && supportedValue) extensibleTarget[key] = value; } } static fromUnknown(err) { if (err instanceof WalletError) return err; let message = ""; if (typeof err === "string") message = err; else if (typeof err === "number") message = err.toString(); let fields = { name: "WERR_UNKNOWN", message, stack: void 0, details: void 0 }; if (err !== null && typeof err === "object") fields = WalletError.objectErrorFields(err); const e = new WalletError(fields.name, fields.message, fields.stack, fields.details); if (err !== null && typeof err === "object") WalletError.copyPublicErrorFields(e, err); return e; } /** * @returns standard HTTP error status object with status property set to 'error'. */ asStatus() { return { status: "error", code: this.name, description: this.message }; } /** * Base class default JSON serialization. * Captures just the name and message properties. * * Override this method to safely (avoid deep, large, circular issues) serialize * derived class properties. * * @returns stringified JSON representation of the WalletError. */ toJson() { const e = new WalletError(this.name, this.message); return JSON.stringify({ isError: true, name: e.name, message: e.message }); } /** * Safely serializes a WalletError derived, WERR_REVIEW_ACTIONS (special case), Error or unknown error to JSON. * * Safely means avoiding deep, large, circular issues. * * @param error * @returns stringified JSON representation of the error such that it can be desirialized to a WalletError. */ static unknownToJson(error) { let json; let e; const t = typeof error; const ctorName = t === "object" && error !== null ? error.constructor : void 0; const ctor = ctorName != null && typeof ctorName.name === "string" ? ctorName.name : void 0; const name = t === "object" && error !== null && typeof error.name === "string" ? error.name : ""; const message = t === "object" && error !== null && typeof error.message === "string" ? error.message : ""; const hasToJson = t === "object" && typeof error?.toJson === "function"; if (ctor != null && ctor !== "" && ctor.startsWith("WERR_") && hasToJson) json = error.toJson(); else if (name !== "" && message !== "") { e = new WalletError(name, message); json = e.toJson(); } else { e = new WalletError("WERR_UNKNOWN", String(error)); json = e.toJson(); } return json; } }; //#endregion //#region ../src/sdk/WERR_errors.ts /** * Not implemented. */ var WERR_NOT_IMPLEMENTED = class extends WalletError { constructor(message) { super("WERR_NOT_IMPLEMENTED", message ?? "Not implemented."); } }; /** * An internal error has occurred. * * This is an example of an error with an optional custom `message`. */ var WERR_INTERNAL = class extends WalletError { constructor(message) { super("WERR_INTERNAL", message ?? "An internal error has occurred."); } }; /** * The ${parameter} parameter is invalid. * * This is an example of an error object with a custom property `parameter` and templated `message`. */ var WERR_INVALID_OPERATION = class extends WalletError { constructor(message) { super("WERR_INVALID_OPERATION", message ?? "An invalid operation was requested."); } }; /** * Unable to broadcast transaction at this time. */ var WERR_BROADCAST_UNAVAILABLE = class extends WalletError { constructor(_message) { super("WERR_BROADCAST_UNAVAILABLE", "Unable to broadcast transaction at this time."); } }; /** * The ${parameter} parameter is invalid. * * This is an example of an error object with a custom property `parameter` and templated `message`. */ var WERR_INVALID_PARAMETER = class extends WalletError { parameter; constructor(parameter, mustBe) { super("WERR_INVALID_PARAMETER", `The ${parameter} parameter must be ${mustBe ?? "valid."}`); this.parameter = parameter; } toJson() { const obj = JSON.parse(super.toJson()); obj.code = 6; obj.parameter = this.parameter; return JSON.stringify(obj); } }; /** * Invalid merkleRoot ${merkleRoot} for block ${blockHash} at height ${blockHeight}${txid ? ` for txid ${txid}` : ''}. * * Typically thrown when a chain tracker fails to validate a merkle root. */ var WERR_INVALID_MERKLE_ROOT = class extends WalletError { blockHash; blockHeight; merkleRoot; txid; constructor(blockHash, blockHeight, merkleRoot, txid) { super("WERR_INVALID_MERKLE_ROOT", `Invalid merkleRoot ${merkleRoot} for block ${blockHash} at height ${blockHeight}${txid != null ? " for txid " + txid : ""}.`); this.blockHash = blockHash; this.blockHeight = blockHeight; this.merkleRoot = merkleRoot; this.txid = txid; } toJson() { const obj = JSON.parse(super.toJson()); obj.code = 8; obj.blockHash = this.blockHash; obj.blockHeight = this.blockHeight; obj.merkleRoot = this.merkleRoot; obj.txid = this.txid; return JSON.stringify(obj); } }; /** * The required ${parameter} parameter is missing. * * This is an example of an error object with a custom property `parameter` */ var WERR_MISSING_PARAMETER = class extends WalletError { parameter; constructor(parameter) { super("WERR_MISSING_PARAMETER", `The required ${parameter} parameter is missing.`); this.parameter = parameter; } toJson() { const obj = JSON.parse(super.toJson()); obj.parameter = this.parameter; return JSON.stringify(obj); } }; /** * The request is invalid. */ var WERR_BAD_REQUEST = class extends WalletError { constructor(message) { super("WERR_BAD_REQUEST", message ?? "The request is invalid."); } }; /** * Configured network chain is invalid or does not match across services. */ var WERR_NETWORK_CHAIN = class extends WalletError { constructor(message) { super("WERR_NETWORK_CHAIN", message ?? "Configured network chain is invalid or does not match across services."); } }; /** * Access is denied due to an authorization error. */ var WERR_UNAUTHORIZED = class extends WalletError { constructor(message) { super("WERR_UNAUTHORIZED", message ?? "Access is denied due to an authorization error."); } }; /** * WalletStorageManager is not accessing user's active storage or there are conflicting active stores configured. */ var WERR_NOT_ACTIVE = class extends WalletError { constructor(message) { super("WERR_NOT_ACTIVE", message ?? "WalletStorageManager is not accessing user's active storage or there are conflicting active stores configured."); } }; /** * Insufficient funds in the available inputs to cover the cost of the required outputs * and the transaction fee (${moreSatoshisNeeded} more satoshis are needed, * for a total of ${totalSatoshisNeeded}), plus whatever would be required in order * to pay the fee to unlock and spend the outputs used to provide the additional satoshis. */ var WERR_INSUFFICIENT_FUNDS = class extends WalletError { totalSatoshisNeeded; moreSatoshisNeeded; /** * @param totalSatoshisNeeded Total satoshis required to fund transactions after net of required inputs and outputs. * @param moreSatoshisNeeded Shortfall on total satoshis required to fund transactions after net of required inputs and outputs. */ constructor(totalSatoshisNeeded, moreSatoshisNeeded) { super("WERR_INSUFFICIENT_FUNDS", `Insufficient funds in the available inputs to cover the cost of the required outputs and the transaction fee (${moreSatoshisNeeded} more satoshis are needed, for a total of ${totalSatoshisNeeded}), plus whatever would be required in order to pay the fee to unlock and spend the outputs used to provide the additional satoshis.`); this.totalSatoshisNeeded = totalSatoshisNeeded; this.moreSatoshisNeeded = moreSatoshisNeeded; } toJson() { const obj = JSON.parse(super.toJson()); obj.code = 7; obj.totalSatoshisNeeded = this.totalSatoshisNeeded; obj.moreSatoshisNeeded = this.moreSatoshisNeeded; return JSON.stringify(obj); } }; var WERR_INVALID_PUBLIC_KEY = class extends WalletError { key; /** * @param key The invalid public key that caused the error. * @param environment Optional environment flag to control whether the key is included in the message. */ constructor(key, network = "mainnet") { const message = network === "mainnet" ? `The provided public key "${key}" is invalid or malformed.` : "The provided public key is invalid or malformed."; super("WERR_INVALID_PUBLIC_KEY", message); this.key = key; } toJson() { const obj = JSON.parse(super.toJson()); obj.key = this.key; return JSON.stringify(obj); } }; /** * When a `createAction` or `signAction` is completed in undelayed mode (`acceptDelayedBroadcast`: false), * any unsuccessful result will return the results by way of this exception to ensure attention is * paid to processing errors. */ var WERR_REVIEW_ACTIONS = class extends WalletError { reviewActionResults; sendWithResults; txid; tx; noSendChange; /** * All parameters correspond to their comparable `createAction` or `signAction` results * with the exception of `reviewActionResults`; * which contains more details, particularly for double spend results. */ constructor(reviewActionResults, sendWithResults, txid, tx, noSendChange) { super("WERR_REVIEW_ACTIONS", "Undelayed createAction or signAction results require review."); this.reviewActionResults = reviewActionResults; this.sendWithResults = sendWithResults; this.txid = txid; this.tx = tx; this.noSendChange = noSendChange; } toJson() { const obj = JSON.parse(super.toJson()); obj.code = 5; obj.reviewActionResults = this.reviewActionResults; obj.sendWithResults = this.sendWithResults; obj.txid = this.txid; obj.tx = this.tx; obj.noSendChange = this.noSendChange; return JSON.stringify(obj); } }; /** * IF YOU ADD NEW ERRORS, ALSO UPDATE THE WalletError.fromJson METHOD IN src/sdk/WalletError.ts */ //#endregion //#region ../src/sdk/WalletErrorFromJson.ts /** * Reconstruct the correct derived WalletError from a JSON object created by `WalletError.unknownToJson`. * * This function is implemented as a separate function instead of a WalletError class static * to avoid circular dependencies. * * @param json * @returns a WalletError derived error object, typically for re-throw. */ function WalletErrorFromJson(json) { let e; const obj = json; switch (obj.name) { case "WERR_NOT_IMPLEMENTED": e = new WERR_NOT_IMPLEMENTED(obj.message); break; case "WERR_INTERNAL": e = new WERR_INTERNAL(obj.message); break; case "WERR_INVALID_OPERATION": e = new WERR_INVALID_OPERATION(obj.message); break; case "WERR_BROADCAST_UNAVAILABLE": e = new WERR_BROADCAST_UNAVAILABLE(obj.message); break; case "WERR_INVALID_PARAMETER": e = new WERR_INVALID_PARAMETER(obj.parameter); e.message = obj.message; break; case "WERR_MISSING_PARAMETER": e = new WERR_MISSING_PARAMETER(obj.parameter); e.message = obj.message; break; case "WERR_BAD_REQUEST": e = new WERR_BAD_REQUEST(obj.message); break; case "WERR_NETWORK_CHAIN": e = new WERR_NETWORK_CHAIN(obj.message); break; case "WERR_UNAUTHORIZED": e = new WERR_UNAUTHORIZED(obj.message); break; case "WERR_NOT_ACTIVE": e = new WERR_NOT_ACTIVE(obj.message); break; case "WERR_INSUFFICIENT_FUNDS": e = new WERR_INSUFFICIENT_FUNDS(obj.totalSatoshisNeeded, obj.moreSatoshisNeeded); break; case "WERR_INVALID_PUBLIC_KEY": e = new WERR_INVALID_PUBLIC_KEY(obj.key, "mainnet"); e.message = obj.message; break; case "WERR_REVIEW_ACTIONS": e = new WERR_REVIEW_ACTIONS(obj.reviewActionResults, obj.sendWithResults, obj.txid, obj.tx, obj.noSendChange); break; default: e = new WalletError(typeof obj.name === "string" && obj.name !== "" ? obj.name : "WERR_UNKNOWN", typeof obj.message === "string" ? obj.message : ""); break; } return e; } //#endregion //#region ../src/sdk/types.ts const ProvenTxReqTerminalStatus = [ "completed", "invalid", "doubleSpend" ]; const ProvenTxReqNonTerminalStatus = [ "sending", "unsent", "nosend", "unknown", "nonfinal", "unprocessed", "unmined", "callback", "unconfirmed" ]; /** * `listOutputs` special operation basket name value. * * Returns wallet's current change balance in the `totalOutputs` result property. * The `outputs` result property will always be an empty array. */ const specOpWalletBalance = "893b7646de0e1c9f741bd6e9169b76a8847ae34adef7bef1e6a285371206d2e8"; /** * `listOutputs` special operation basket name value. * * Lists only spendable wallet-managed BRC-29 change from the `default` * basket. Raw administrative `listOutputs({ basket: 'default' })` remains * intentionally unfiltered so legacy incompatible rows stay discoverable * for recovery instead of being hidden or silently mutated. */ const specOpWalletManagedUtxos = "284570a6213a74ba861c38b1cf790e1e400d9cf9324454b76ea98860b6031c1a"; /** * `listOutputs` special operation basket name value. * * Returns currently spendable wallet change outputs that fail to validate as unspent transaction outputs. * * Optional tag value 'release'. If present, updates invalid change outputs to not spendable. * * Optional tag value 'all'. If present, processes all spendable true outputs, independent of baskets, but basket must be defined. */ const specOpInvalidChange = "5a76fd430a311f8bc0553859061710a4475c19fed46e2ff95969aa918e612e57"; /** * `listOutputs` special operation basket name value. * * Updates the wallet's automatic change management parameters. * * Tag at index 0 is the new desired number of spendable change outputs to maintain. * * Tag at index 1 is the new target for minimum satoshis when creating new change outputs. */ const specOpSetWalletChangeParams = "a4979d28ced8581e9c1c92f1001cc7cb3aabf8ea32e10888ad898f0a509a3929"; /** * @param basket Output basket name value. * @returns true iff the `basket` name is a reserved `listOutputs` special operation identifier. */ function isListOutputsSpecOp(basket) { return [ specOpWalletBalance, specOpWalletManagedUtxos, specOpInvalidChange, specOpSetWalletChangeParams ].includes(basket); } /** * `listActions` special operation label name value. * * Processes only actions currently with status 'nosend' * * Optional label value 'abort'. If present, runs abortAction on all the actions returned. */ const specOpNoSendActions = "ac6b20a3bb320adafecd637b25c84b792ad828d3aa510d05dc841481f664277d"; /** * `listActions` special operation label name value. * * Processes only actions currently with status 'failed' * * Optional label value 'unfail'. If present, sets status to 'unfail', which queues them for attempted recovery by the Monitor. */ const specOpFailedActions = "97d4eb1e49215e3374cc2c1939a7c43a55e95c7427bf2d45ed63e3b4e0c88153"; /** * @param label Action / Transaction label name value. * @returns true iff the `label` name is a reserved `listActions` special operation identifier. */ function isListActionsSpecOp(label) { return [specOpNoSendActions, specOpFailedActions].includes(label); } /** * `createAction` special operation label name value. * * Causes WERR_REVIEW_ACTIONS throw with dummy properties. * */ const specOpThrowReviewActions = "a496e747fc3ad5fabdd4ae8f91184e71f87539bd3d962aa2548942faaaf0047a"; /** * @param label Action / Transaction label name value. * @returns true iff the `label` name is a reserved `createAction` special operation identifier. */ function isCreateActionSpecOp(label) { return [specOpThrowReviewActions].includes(label); } //#endregion //#region ../src/sdk/PrivilegedKeyManager.ts /** * PrivilegedKeyManager * * This class manages a privileged (i.e., very sensitive) private key, obtained from * an external function (`keyGetter`), which might be backed by HSMs, secure enclaves, * or other secure storage. The manager retains the key in memory only for a limited * duration (`retentionPeriod`), uses XOR-based chunk-splitting obfuscation, and * includes decoy data to raise the difficulty of discovering the real key in memory. * * IMPORTANT: While these measures raise the bar for attackers, JavaScript environments * do not provide perfect in-memory secrecy. */ var PrivilegedKeyManager = class { /** * Function that will retrieve the PrivateKey from a secure environment, * e.g., an HSM or secure enclave. The reason for key usage is passed in * to help with user consent, auditing, and access policy checks. */ keyGetter; /** * Time (in ms) for which the obfuscated key remains in memory * before being automatically destroyed. */ retentionPeriod; /** * A list of dynamically generated property names used to store * real key chunks (XORed with random pads). */ chunkPropNames = []; /** * A list of dynamically generated property names used to store * the random pads that correspond to the real key chunks. */ chunkPadPropNames = []; /** * A list of decoy property names that will be removed * when the real key is destroyed. */ decoyPropNamesDestroy = []; /** * A list of decoy property names that remain in memory * even after the real key is destroyed (just to cause confusion). */ decoyPropNamesRemain = []; /** * Handle to the timer that will remove the key from memory * after the retention period. If the key is refreshed again * within that period, the timer is cleared and re-set. */ destroyTimer; /** * Number of chunks to split the 32-byte key into. * Adjust to increase or decrease obfuscation complexity. */ CHUNK_COUNT = 4; /** * @param keyGetter - Asynchronous function that retrieves the PrivateKey from a secure environment. * @param retentionPeriod - Time in milliseconds to retain the obfuscated key in memory before zeroizing. */ constructor(keyGetter, retentionPeriod = 12e4) { this.keyGetter = keyGetter; this.retentionPeriod = retentionPeriod; for (let i = 0; i < 2; i++) { const propName = this.generateRandomPropName(); this[propName] = Uint8Array.from(Random(16)); this.decoyPropNamesRemain.push(propName); } } /** * Safely destroys the in-memory obfuscated key material by zeroizing * and deleting related fields. Also destroys some (but not all) decoy * properties to further confuse an attacker. */ destroyKey() { try { for (const name of this.chunkPropNames) { const data = this[name]; if (data != null) data.fill(0); delete this[name]; } for (const name of this.chunkPadPropNames) { const data = this[name]; if (data != null) data.fill(0); delete this[name]; } for (const name of this.decoyPropNamesDestroy) { const data = this[name]; if (data != null) data.fill(0); delete this[name]; } this.chunkPropNames = []; this.chunkPadPropNames = []; this.decoyPropNamesDestroy = []; } catch {} finally { if (this.destroyTimer != null) { clearTimeout(this.destroyTimer); this.destroyTimer = void 0; } } } /** * Re/sets the destruction timer that removes the key from memory * after `retentionPeriod` ms. If a timer is already running, it * is cleared and re-set. This ensures the key remains in memory * for exactly the desired window after its most recent acquisition. */ scheduleKeyDestruction() { if (this.destroyTimer != null) clearTimeout(this.destroyTimer); this.destroyTimer = setTimeout(() => { this.destroyKey(); }, this.retentionPeriod); } /** * XOR-based obfuscation on a per-chunk basis. * This function takes two equal-length byte arrays * and returns the XOR combination. */ xorBytes(a, b) { const out = new Uint8Array(a.length); for (let i = 0; i < a.length; i++) out[i] = a[i] ^ b[i]; return out; } /** * Splits the 32-byte key into `this.CHUNK_COUNT` smaller chunks * (mostly equal length; the last chunk picks up leftover bytes * if 32 is not evenly divisible). */ splitKeyIntoChunks(keyBytes) { const chunkSize = Math.floor(keyBytes.length / this.CHUNK_COUNT); const chunks = []; let offset = 0; for (let i = 0; i < this.CHUNK_COUNT; i++) { const size = i === this.CHUNK_COUNT - 1 ? keyBytes.length - offset : chunkSize; chunks.push(keyBytes.slice(offset, offset + size)); offset += size; } return chunks; } /** * Reassembles the chunks from the dynamic properties, XORs them * with their corresponding pads, and returns a single 32-byte * Uint8Array representing the raw key. */ reassembleKeyFromChunks() { try { const chunkArrays = []; for (let i = 0; i < this.chunkPropNames.length; i++) { const chunkEnc = this[this.chunkPropNames[i]]; const chunkPad = this[this.chunkPadPropNames[i]]; if (chunkEnc?.length == null || chunkEnc.length !== chunkPad?.length) return null; const rawChunk = this.xorBytes(chunkEnc, chunkPad); chunkArrays.push(rawChunk); } const totalLength = chunkArrays.reduce((sum, c) => sum + c.length, 0); if (totalLength !== 32) return null; const rawKey = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunkArrays) { rawKey.set(chunk, offset); offset += chunk.length; chunk.fill(0); } return rawKey; } catch { return null; } } /** * Generates a random property name to store key chunks or decoy data. */ generateRandomPropName() { const randomHex = Utils.toHex(Random(4)); const extraBytes = Random(3); return `_${randomHex}_${(extraBytes[0] << 16 | extraBytes[1] << 8 | extraBytes[2]) % 1e6}`; } /** * Forces a PrivateKey to be represented as exactly 32 bytes, left-padding * with zeros if its numeric value has fewer than 32 bytes. */ get32ByteRepresentation(privKey) { const buf = privKey.toArray(); if (buf.length > 32) throw new Error("PrivilegedKeyManager: Expected a 32-byte key, but got more."); const keyBytes = /* @__PURE__ */ new Uint8Array(32); keyBytes.set(buf, 32 - buf.length); return keyBytes; } /** * Returns the privileged key needed to perform cryptographic operations. * Uses in-memory chunk-based obfuscation if the key was already fetched. * Otherwise, it calls out to `keyGetter`, splits the 32-byte representation * of the key, XORs each chunk with a random pad, and stores them under * dynamic property names. Also populates new decoy properties. * * @param reason - The reason for why the key is needed, passed to keyGetter. * @returns The PrivateKey object needed for cryptographic operations. */ async getPrivilegedKey(reason) { if (this.chunkPropNames.length > 0 && this.chunkPadPropNames.length > 0) { const rawKeyBytes = this.reassembleKeyFromChunks(); if (rawKeyBytes?.length === 32) { const hexKey = Utils.toHex([...rawKeyBytes]); rawKeyBytes.fill(0); this.scheduleKeyDestruction(); return new PrivateKey(hexKey, "hex"); } } const fetchedKey = await this.keyGetter(reason); const keyBytes = this.get32ByteRepresentation(fetchedKey); this.destroyKey(); const chunks = this.splitKeyIntoChunks(keyBytes); for (const chunk of chunks) { const chunkProp = this.generateRandomPropName(); const padProp = this.generateRandomPropName(); this.chunkPropNames.push(chunkProp); this.chunkPadPropNames.push(padProp); const pad = Uint8Array.from(Random(chunk.length)); const obf = this.xorBytes(chunk, pad); this[chunkProp] = obf; this[padProp] = pad; } for (let i = 0; i < 2; i++) { const decoyProp = this.generateRandomPropName(); this[decoyProp] = Uint8Array.from(Random(32)); this.decoyPropNamesDestroy.push(decoyProp); } keyBytes.fill(0); this.scheduleKeyDestruction(); return fetchedKey; } async getPublicKey(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).getPublicKey(args); } async revealCounterpartyKeyLinkage(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).revealCounterpartyKeyLinkage(args); } async revealSpecificKeyLinkage(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).revealSpecificKeyLinkage(args); } async encrypt(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).encrypt(args); } async decrypt(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).decrypt(args); } async createHmac(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).createHmac(args); } async verifyHmac(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).verifyHmac(args); } async createSignature(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).createSignature(args); } async verifySignature(args) { return await new ProtoWallet(await this.getPrivilegedKey(args.privilegedReason)).verifySignature(args); } }; //#endregion //#region ../src/sdk/index.ts var sdk_exports = /* @__PURE__ */ __exportAll({ PrivilegedKeyManager: () => PrivilegedKeyManager, ProvenTxReqNonTerminalStatus: () => ProvenTxReqNonTerminalStatus, ProvenTxReqTerminalStatus: () => ProvenTxReqTerminalStatus, Validation: () => Validation$1, WERR_BAD_REQUEST: () => WERR_BAD_REQUEST, WERR_BROADCAST_UNAVAILABLE: () => WERR_BROADCAST_UNAVAILABLE, WERR_INSUFFICIENT_FUNDS: () => WERR_INSUFFICIENT_FUNDS, WERR_INTERNAL: () => WERR_INTERNAL, WERR_INVALID_MERKLE_ROOT: () => WERR_INVALID_MERKLE_ROOT, WERR_INVALID_OPERATION: () => WERR_INVALID_OPERATION, WERR_INVALID_PARAMETER: () => WERR_INVALID_PARAMETER, WERR_INVALID_PUBLIC_KEY: () => WERR_INVALID_PUBLIC_KEY, WERR_MISSING_PARAMETER: () => WERR_MISSING_PARAMETER, WERR_NETWORK_CHAIN: () => WERR_NETWORK_CHAIN, WERR_NOT_ACTIVE: () => WERR_NOT_ACTIVE, WERR_NOT_IMPLEMENTED: () => WERR_NOT_IMPLEMENTED, WERR_REVIEW_ACTIONS: () => WERR_REVIEW_ACTIONS, WERR_UNAUTHORIZED: () => WERR_UNAUTHORIZED, WalletError: () => WalletError, WalletErrorFromJson: () => WalletErrorFromJson, isCreateActionSpecOp: () => isCreateActionSpecOp, isListActionsSpecOp: () => isListActionsSpecOp, isListOutputsSpecOp: () => isListOutputsSpecOp, specOpFailedActions: () => specOpFailedActions, specOpInvalidChange: () => specOpInvalidChange, specOpNoSendActions: () => specOpNoSendActions, specOpSetWalletChangeParams: () => specOpSetWalletChangeParams, specOpThrowReviewActions: () => specOpThrowReviewActions, specOpWalletBalance: () => specOpWalletBalance, specOpWalletManagedUtxos: () => specOpWalletManagedUtxos }); //#endregion //#region ../src/utility/stampLog.ts /** * If a log is being kept, add a time stamped line. * @param log Optional time stamped log to extend, or an object with a log property to update * @param lineToAdd Content to add to line. * @returns undefined or log extended by time stamped `lineToAdd` and new line. */ function stampLog(log, lineToAdd) { const add = `${(/* @__PURE__ */ new Date()).toISOString()} ${lineToAdd}\n`; if (typeof log === "object" && typeof log.log === "string") { log.log = log.log + add; return log.log; } if (typeof log === "string") return log + add; } function parseStampLog(log) { const data = []; const newClocks = []; let last = 0; for (const line of log.split("\n")) { const spaceAt = line.indexOf(" "); if (spaceAt < 0) continue; const when = new Date(line.substring(0, spaceAt)).getTime(); const rest = line.substring(spaceAt + 1); const delta = when - (last !== 0 ? last : when); const newClock = rest.includes("**NETWORK**"); if (newClock) newClocks.push(data.length); data.push({ when, rest, delta, newClock }); last = when; } return { data, newClocks }; } function adjustNetworkDeltas(data, newClocks, total) { if (newClocks.length % 2 !== 0) return; let network = total; let lastNewClock = 0; for (const newClock of newClocks) { network -= data[newClock - 1].when - data[lastNewClock].when; lastNewClock = newClock; } network -= data.at(-1).when - data[lastNewClock].when; let networks = newClocks.length; for (const newClock of newClocks) { const delta = networks > 1 ? Math.floor(network / networks) : network; data[newClock].delta = delta; network -= delta; networks--; } } function formatStampLog(data, total) { let formatted = `${new Date(data[0].when).toISOString()} Total = ${total} msecs\n`; for (const entry of data) { const delta = entry.delta.toString(); formatted += `${" ".repeat(8 - delta.length)}${delta} ${entry.rest}\n`; } return formatted; } /** * Replaces individual timestamps with delta msecs. * Looks for two network crossings and adjusts clock for clock skew if found. * Assumes log built by repeated calls to `stampLog` * @param log Each logged event starts with ISO time stamp, space, rest of line, terminated by `\n`. * @returns reformated multi-line event log */ function stampLogFormat(log) { if (typeof log !== "string") return ""; const { data, newClocks } = parseStampLog(log); const total = data.at(-1).when - data[0].when; adjustNetworkDeltas(data, newClocks, total); return formatStampLog(data, total); } //#endregion //#region ../src/utility/utilityHelpers.noBuffer.ts /** * Convert a value to an encoded string if currently an encoded string or number[] or Uint8Array. * @param val string or number[] or Uint8Array. If string, encoding must be hex. If number[], each value must be 0..255. * @param enc optional encoding type if val is string, defaults to 'hex'. Can be 'hex', 'utf8', or 'base64'. * @param returnEnc optional encoding type for returned string if different from `enc`, defaults to 'hex'. Can be 'hex', 'utf8', or 'base64'. * @returns hex encoded string representation of val. * @publicbody */ function asString(val, enc, returnEnc) { enc ||= "hex"; returnEnc ||= enc; if (typeof val === "string") { if (enc === returnEnc) return val; val = asUint8Array(val, enc); } const v = Array.isArray(val) ? val : Array.from(val); switch (returnEnc) { case "utf8": return Utils.toUTF8(v); case "base64": return Utils.toBase64(v); } return Utils.toHex(v); } /** * Convert a value to number[] if currently an encoded string or number[] or Uint8Array. * @param val string or number[] or Uint8Array. If string, encoding must be hex. If number[], each value must be 0..255. * @param enc optional encoding type if val is string, defaults to 'hex'. Can be 'hex', 'utf8', or 'base64'. * @returns number[] array of byte values representation of val. * @publicbody */ function asArray(val, enc) { if (Array.isArray(val)) return val; if (typeof val !== "string") return Array.from(val); enc ||= "hex"; return Utils.toArray(val, enc); } /** * Convert a value to Uint8Array if currently an encoded string or number[] or Uint8Array. * @param val string or number[] or Uint8Array. If string, encoding must be hex. If number[], each value must be 0..255. * @param enc optional encoding type if val is string, defaults to 'hex'. Can be 'hex', 'utf8', or 'base64'. * @returns Uint8Array representation of val. * @publicbody */ function asUint8Array(val, enc) { if (Array.isArray(val)) return Uint8Array.from(val); if (typeof val !== "string") return val; enc ||= "hex"; return Utils.toUint8Array(val, enc); } //#endregion //#region ../src/utility/utilityHelpers.ts async function getIdentityKey(wallet) { return (await wallet.getPublicKey({ identityKey: true })).publicKey; } function toWalletNetwork(chain) { switch (chain) { case "main": return "mainnet"; case "test": case "stn": case "ttn": case "tstn": case "mock": return "testnet"; } } /** * Maps a Chain to a network preset suitable for LookupResolver / SHIPBroadcaster. * Unlike `toWalletNetwork`, this returns `'local'` for `mock` chain. */ function toLookupNetworkPreset(chain) { switch (chain) { case "main": return "mainnet"; case "test": return "testnet"; case "stn": case "ttn": case "tstn": case "mock": return "local"; } } function makeAtomicBeef(tx, beef) { if (Array.isArray(beef)) beef = Beef.fromBinary(beef); beef.mergeTransaction(tx); return beef.toBinaryAtomic(tx.id("hex")); } /** * Coerce a bsv transaction encoded as a hex string, serialized array, or Transaction to Transaction * If tx is already a Transaction, just return it. * @publicbody */ function asBsvSdkTx(tx) { if (Array.isArray(tx)) tx = Transaction.fromBinary(tx); else if (typeof tx === "string") tx = Transaction.fromHex(tx); return tx; } /** * Coerce a bsv script encoded as a hex string, serialized array, or Script to Script * If script is already a Script, just return it. * @publicbody */ function asBsvSdkScript(script) { if (Array.isArray(script)) script = Script.fromBinary(script); else if (typeof script === "string") script = Script.fromHex(script); return script; } /** * @param privKey bitcoin private key in 32 byte hex string form * @returns @bsv/sdk PrivateKey */ function asBsvSdkPrivateKey(privKey) { return PrivateKey.fromString(privKey, "hex"); } /** * @param pubKey bitcoin public key in standard compressed key hex string form * @returns @bsv/sdk PublicKey */ function asBsvSdkPublickKey(pubKey) { return PublicKey.fromString(pubKey); } /** * Helper function. * * Verifies that a possibly optional value has a value. */ function verifyTruthy(v, description) { if (v == null) throw new WERR_INTERNAL(description ?? "A truthy value is required."); return v; } /** * Helper function. * * Verifies that a hex string is trimmed and lower case. */ function verifyHexString(v) { if (typeof v !== "string") throw new WERR_INTERNAL("A string is required."); v = v.trim().toLowerCase(); return v; } /** * Helper function. * * Verifies that an optional or null hex string is undefined or a trimmed lowercase string. */ function verifyOptionalHexString(v) { if (v == null || v === "") return void 0; return verifyHexString(v); } /** * Helper function. * * Verifies that an optional or null number has a numeric value. */ function verifyNumber(v) { if (typeof v !== "number") throw new WERR_INTERNAL("A number is required."); return v; } /** * Helper function. * * Verifies that an optional or null number has a numeric value. */ function verifyInteger(v) { if (typeof v !== "number" || !Number.isInteger(v)) throw new WERR_INTERNAL("An integer is required."); return v; } /** * Helper function. * * Verifies that a database record identifier is an integer greater than zero. */ function verifyId(id) { id = verifyInteger(id); if (id < 1) throw new WERR_INTERNAL("id must be valid integer greater than zero."); return id; } /** * Helper function. * * @throws WERR_BAD_REQUEST if results has length greater than one. * * @returns results[0] or undefined if length is zero. */ function verifyOneOrNone(results) { if (results.length > 1) throw new WERR_BAD_REQUEST("Result must be unique."); return results[0]; } /** * Helper function. * * @throws WERR_BAD_REQUEST if results has length other than one. * * @returns results[0]. */ function verifyOne(results, errorDescrition) { if (results.length !== 1) throw new WERR_BAD_REQUEST(errorDescrition ?? "Result must exist and be unique."); return results[0]; } /** * Returns an await'able Promise that resolves in the given number of msecs. * @param msecs number of milliseconds to wait before resolving the promise. * Must be greater than zero and less than 2 minutes (120,000 msecs) * @publicbody */ async function wait(msecs) { const MIN_WAIT = 0; const MAX_WAIT = 120 * 1e3; if (typeof msecs !== "number" || !Number.isFinite(msecs) || Number.isNaN(msecs) || msecs < MIN_WAIT || msecs > MAX_WAIT) throw new WERR_INVALID_PARAMETER("msecs", `a number between ${MIN_WAIT} and ${MAX_WAIT} msecs, not ${msecs}.`); return await new Promise((resolve) => setTimeout(resolve, msecs)); } /** * @returns count cryptographically secure random bytes as array of bytes */ function randomBytes(count) { return Random(count); } /** * @returns count cryptographically secure random bytes as hex encoded string */ function randomBytesHex(count) { return Utils.toHex(Random(count)); } /** * @returns count cryptographically secure random bytes as base64 encoded string */ function randomBytesBase64(count) { return Utils.toBase64(Random(count)); } function validateSecondsSinceEpoch(time) { const date = /* @__PURE__ */ new Date(time * 1e3); if (date.getTime() / 1e3 !== time || time < 16e8 || time > 1e11) throw new WERR_INVALID_PARAMETER("time", "valid \"since epoch\" unix time"); return date; } /** * Compares lengths and direct equality of values. * @param arr1 * @param arr2 * @returns */ function arraysEqual(arr1, arr2) { if (arr1.length !== arr2.length) return false; for (let i = 0; i < arr1.length; i++) if (arr1[i] !== arr2[i]) return false; return true; } function optionalArraysEqual(arr1, arr2) { if (arr1 == null && arr2 == null) return true; if (arr1 == null || arr2 == null) return false; return arraysEqual(arr1, arr2); } function maxDate(d1, d2) { if (d1 != null && d2 != null) { if (d1 > d2) return d1; return d2; } if (d1 != null) return d1; if (d2 != null) return d2; } /** * Calculate the SHA256 hash of an array of bytes * @returns sha256 hash of buffer contents. * @publicbody */ function sha256Hash(data) { if (!Array.isArray(data)) data = asArray(data); return new Hash.SHA256().update(data).digest(); } /** * Calculate the SHA256 hash of the SHA256 hash of an array of bytes. * @param data an array of bytes * @returns double sha256 hash of data, byte 0 of hash first. * @publicbody */ function doubleSha256LE(data) { if (!Array.isArray(data)) data = asArray(data); const first = new Hash.SHA256().update(data).digest(); return new Hash.SHA256().update(first).digest(); } /** * Calculate the SHA256 hash of the SHA256 hash of an array of bytes. * @param data is an array of bytes. * @returns reversed (big-endian) double sha256 hash of data, byte 31 of hash first. * @publicbody */ function doubleSha256BE(data) { return doubleSha256LE(data).reverse(); } /** * Logging function to handle logging based on running in jest "single test" mode, * * @param {string} message - The main message to log. * @param {...any} optionalParams - Additional parameters to log (optional). */ const logger = (message, ...optionalParams) => { if (process.argv.some((arg) => arg === "--testNamePattern" || arg === "-t")) console.log(message, ...optionalParams); }; //#endregion //#region ../src/utility/ScriptTemplateBRC29.ts const brc29ProtocolID = [2, "3241645161d8"]; /** * Simple Authenticated BSV P2PKH Payment Protocol * https://brc.dev/29 */ var ScriptTemplateBRC29 = class { params; p2pkh; constructor(params) { this.params = params; this.p2pkh = new P2PKH(); verifyTruthy(params.derivationPrefix); verifyTruthy(params.derivationSuffix); } getKeyID() { return `${this.params.derivationPrefix ?? ""} ${this.params.derivationSuffix ?? ""}`; } getKeyDeriver(privKey) { if (this.params.keyDeriver?.rootKey === privKey) return this.params.keyDeriver; if (typeof privKey === "string") { if (this.params.keyDeriver?.rootKey.toHex() === privKey) return this.params.keyDeriver; privKey = PrivateKey.fromHex(privKey); } if (this.params.keyDeriver == null || this.params.keyDeriver.rootKey.toHex() !== privKey.toHex()) return new CachedKeyDeriver(privKey); return this.params.keyDeriver; } lock(lockerPrivKey, unlockerPubKey) { const address = this.getKeyDeriver(lockerPrivKey).derivePublicKey(brc29ProtocolID, this.getKeyID(), unlockerPubKey, false).toAddress(); return this.p2pkh.lock(address); } unlock(unlockerPrivKey, lockerPubKey, sourceSatoshis, lockingScript) { const derivedPrivateKey = this.getKeyDeriver(unlockerPrivKey).derivePrivateKey(brc29ProtocolID, this.getKeyID(), lockerPubKey); return this.unlockWithDerivedPrivateKey(derivedPrivateKey, sourceSatoshis, lockingScript); } unlockWithDerivedPrivateKey(derivedPrivateKey, sourceSatoshis, lockingScript) { return this.p2pkh.unlock(derivedPrivateKey, "all", false, sourceSatoshis, lockingScript); } /** * P2PKH unlock estimateLength is a constant */ unlockLength = 108; }; //#endregion //#region ../src/utility/parseTxScriptOffsets.ts function parseTxScriptOffsets(rawTx) { const br = Utils.ReaderUint8Array.makeReader(rawTx); const inputs = []; const outputs = []; br.pos += 4; const inputsLength = br.readVarIntNum(); for (let i = 0; i < inputsLength; i++) { br.pos += 36; const scriptLength = br.readVarIntNum(); inputs.push({ vin: i, offset: br.pos, length: scriptLength }); br.pos += scriptLength + 4; } const outputsLength = br.readVarIntNum(); for (let i = 0; i < outputsLength; i++) { br.pos += 8; const scriptLength = br.readVarIntNum(); outputs.push({ vout: i, offset: br.pos, length: scriptLength }); br.pos += scriptLength; } return { inputs, outputs }; } //#endregion //#region ../src/utility/tscProofToMerklePath.ts function convertProofToMerklePath(txid, proof) { const blockHeight = proof.height; const treeHeight = proof.nodes.length; const path = Array.from({ length: treeHeight }).fill(0).map(() => []); let index = proof.index; for (let level = 0; level < treeHeight; level++) { const node = proof.nodes[level]; const isOdd = index % 2 === 1; const leaf = { offset: isOdd ? index - 1 : index + 1 }; if (node === "*" || level === 0 && node === txid) leaf.duplicate = true; else leaf.hash = node; path[level].push(leaf); if (level === 0) { const txidLeaf = { offset: proof.index, hash: txid, txid: true }; if (isOdd) path[0].push(txidLeaf); else path[0].unshift(txidLeaf); } index = index >> 1; } return new MerklePath(blockHeight, path); } //#endregion //#region ../src/utility/brc114ActionTimeLabels.ts const FROM_PREFIX = "action time from "; const TO_PREFIX = "action time to "; function parseActionTimeBound(label, prefix, bound) { const value = label.slice(prefix.length); const invalid = `valid. Invalid action time ${bound} timestamp value.`; if (!/^\d+$/.test(value)) throw new WERR_INVALID_PARAMETER("labels", invalid); const timestamp = Number(value); if (!Number.isSafeInteger(timestamp) || timestamp < 0) throw new WERR_INVALID_PARAMETER("labels", invalid); if (Number.isNaN(new Date(timestamp).getTime())) throw new WERR_INVALID_PARAMETER("labels", invalid); return timestamp; } function setActionTimeBound(current, label, prefix, bound) { if (current !== void 0) throw new WERR_INVALID_PARAMETER("labels", `valid. Duplicate action time ${bound} label.`); return parseActionTimeBound(label, prefix, bound); } function parseBrc114ActionTimeLabels(labels) { let from; let to; const remainingLabels = []; let timeFilterRequested = false; for (const label of labels ?? []) { if (label.startsWith(FROM_PREFIX)) { timeFilterRequested = true; from = setActionTimeBound(from, label, FROM_PREFIX, "from"); continue; } if (label.startsWith(TO_PREFIX)) { timeFilterRequested = true; to = setActionTimeBound(to, label, TO_PREFIX, "to"); continue; } remainingLabels.push(label); } if (from !== void 0 && to !== void 0 && from >= to) throw new WERR_INVALID_PARAMETER("labels", "valid. action time from must be less than action time to."); return { from, to, timeFilterRequested, remainingLabels }; } function makeBrc114ActionTimeLabel(unixMillis) { return `action time ${unixMillis}`; } //#endregion //#region ../src/storage/schema/entities/EntityBase.ts var EntityBase = class { api; constructor(api) { this.api = api; } /** * An entity may decode properties of the underlying Api object on construction. * * The `toApi` method forces an `updateApi` before returning the underlying, * now updated, Api object. * * @returns The underlying Api object with any entity decoded properties updated. */ toApi() { this.updateApi(); return this.api; } }; function createSyncMap() { return { provenTx: { entityName: "provenTx", idMap: {}, maxUpdated_at: void 0, count: 0 }, outputBasket: { entityName: "outputBasket", idMap: {}, maxUpdated_at: void 0, count: 0 }, transaction: { entityName: "transaction", idMap: {}, maxUpdated_at: void 0, count: 0 }, provenTxReq: { entityName: "provenTxReq", idMap: {}, maxUpdated_at: void 0, count: 0 }, txLabel: { entityName: "txLabel", idMap: {}, maxUpdated_at: void 0, count: 0 }, txLabelMap: { entityName: "txLabelMap", idMap: {}, maxUpdated_at: void 0, count: 0 }, output: { entityName: "output", idMap: {}, maxUpdated_at: void 0, count: 0 }, outputTag: { entityName: "outputTag", idMap: {}, maxUpdated_at: void 0, count: 0 }, outputTagMap: { entityName: "outputTagMap", idMap: {}, maxUpdated_at: void 0, count: 0 }, certificate: { entityName: "certificate", idMap: {}, maxUpdated_at: void 0, count: 0 }, certificateField: { entityName: "certificateField", idMap: {}, maxUpdated_at: void 0, count: 0 }, commission: { entityName: "commission", idMap: {}, maxUpdated_at: void 0, count: 0 } }; } //#endregion //#region ../src/storage/schema/entities/EntityCertificate.ts var EntityCertificate = class EntityCertificate extends EntityBase { constructor(api) { const now = /* @__PURE__ */ new Date(); super(api || { certificateId: 0, created_at: now, updated_at: now, userId: 0, type: "", subject: "", verifier: void 0, serialNumber: "", certifier: "", revocationOutpoint: "", signature: "", isDeleted: false }); } updateApi() {} get certificateId() { return this.api.certificateId; } set certificateId(v) { this.api.certificateId = v; } get created_at() { return this.api.created_at; } set created_at(v) { this.api.created_at = v; } get updated_at() { return this.api.updated_at; } set updated_at(v) { this.api.updated_at = v; } get userId() { return this.api.userId; } set userId(v) { this.api.userId = v; } get type() { return this.api.type; } set type(v) { this.api.type = v; } get subject() { return this.api.subject; } set subject(v) { this.api.subject = v; } get verifier() { return this.api.verifier; } set verifier(v) { this.api.verifier = v; } get serialNumber() { return this.api.serialNumber; } set serialNumber(v) { this.api.serialNumber = v; } get certifier() { return this.api.certifier; } set certifier(v) { this.api.certifier = v; } get revocationOutpoint() { return this.api.revocationOutpoint; } set revocationOutpoint(v) { this.api.revocationOutpoint = v; } get signature() { return this.api.signa