UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

1,163 lines 59.4 kB
import { SecurityLevels } from '../Wallet.interfaces.js'; import Certificate from '../../auth/certificates/Certificate.js'; import * as Utils from '../../primitives/utils.js'; import calls from './WalletWireCalls.js'; import { WalletError } from '../WalletError.js'; /** * A way to make remote calls to a wallet over a wallet wire. */ export default class WalletWireTransceiver { wire; constructor(wire) { this.wire = wire; } async transmit(call, originator = '', params = []) { const frameWriter = new Utils.Writer(); frameWriter.writeUInt8(calls[call]); const originatorArray = Utils.toArray(originator, 'utf8'); frameWriter.writeUInt8(originatorArray.length); frameWriter.write(originatorArray); if (params.length > 0) { frameWriter.write(params); } const frame = frameWriter.toArray(); const result = await this.wire.transmitToWallet(frame); const resultReader = new Utils.Reader(result); const errorByte = resultReader.readUInt8(); if (errorByte === 0) { const resultFrame = resultReader.read(); return resultFrame; } else { // Deserialize the error message length const errorMessageLength = resultReader.readVarIntNum(); const errorMessageBytes = resultReader.read(errorMessageLength); const errorMessage = Utils.toUTF8(errorMessageBytes); // Deserialize the stack trace length const stackTraceLength = resultReader.readVarIntNum(); const stackTraceBytes = resultReader.read(stackTraceLength); const stackTrace = Utils.toUTF8(stackTraceBytes); // Construct a custom wallet error const e = new WalletError(errorMessage, errorByte, stackTrace); throw e; } } async createAction(args, originator) { const paramWriter = new Utils.Writer(); // Serialize description const descriptionBytes = Utils.toArray(args.description, 'utf8'); paramWriter.writeVarIntNum(descriptionBytes.length); paramWriter.write(descriptionBytes); // input BEEF if (args.inputBEEF != null) { paramWriter.writeVarIntNum(args.inputBEEF.length); paramWriter.write(args.inputBEEF); } else { paramWriter.writeVarIntNum(-1); } // Serialize inputs if (args.inputs != null) { paramWriter.writeVarIntNum(args.inputs.length); for (const input of args.inputs) { // outpoint paramWriter.write(this.encodeOutpoint(input.outpoint)); // unlockingScript / unlockingScriptLength if (input.unlockingScript != null && input.unlockingScript !== '') { const unlockingScriptBytes = Utils.toArray(input.unlockingScript, 'hex'); paramWriter.writeVarIntNum(unlockingScriptBytes.length); paramWriter.write(unlockingScriptBytes); } else { paramWriter.writeVarIntNum(-1); paramWriter.writeVarIntNum(input.unlockingScriptLength ?? 0); } // inputDescription const inputDescriptionBytes = Utils.toArray(input.inputDescription, 'utf8'); paramWriter.writeVarIntNum(inputDescriptionBytes.length); paramWriter.write(inputDescriptionBytes); // sequenceNumber if (typeof input.sequenceNumber === 'number') { paramWriter.writeVarIntNum(input.sequenceNumber); } else { paramWriter.writeVarIntNum(-1); } } } else { paramWriter.writeVarIntNum(-1); } // Serialize outputs if (args.outputs != null) { paramWriter.writeVarIntNum(args.outputs.length); for (const output of args.outputs) { // lockingScript const lockingScriptBytes = Utils.toArray(output.lockingScript, 'hex'); paramWriter.writeVarIntNum(lockingScriptBytes.length); paramWriter.write(lockingScriptBytes); // satoshis paramWriter.writeVarIntNum(output.satoshis); // outputDescription const outputDescriptionBytes = Utils.toArray(output.outputDescription, 'utf8'); paramWriter.writeVarIntNum(outputDescriptionBytes.length); paramWriter.write(outputDescriptionBytes); // basket if (output.basket != null && output.basket !== '') { const basketBytes = Utils.toArray(output.basket, 'utf8'); paramWriter.writeVarIntNum(basketBytes.length); paramWriter.write(basketBytes); } else { paramWriter.writeVarIntNum(-1); } // customInstructions if (output.customInstructions != null && output.customInstructions !== '') { const customInstructionsBytes = Utils.toArray(output.customInstructions, 'utf8'); paramWriter.writeVarIntNum(customInstructionsBytes.length); paramWriter.write(customInstructionsBytes); } else { paramWriter.writeVarIntNum(-1); } // tags if (output.tags != null) { paramWriter.writeVarIntNum(output.tags.length); for (const tag of output.tags) { const tagBytes = Utils.toArray(tag, 'utf8'); paramWriter.writeVarIntNum(tagBytes.length); paramWriter.write(tagBytes); } } else { paramWriter.writeVarIntNum(-1); } } } else { paramWriter.writeVarIntNum(-1); } // Serialize lockTime if (typeof args.lockTime === 'number') { paramWriter.writeVarIntNum(args.lockTime); } else { paramWriter.writeVarIntNum(-1); } // Serialize version if (typeof args.version === 'number') { paramWriter.writeVarIntNum(args.version); } else { paramWriter.writeVarIntNum(-1); } // Serialize labels if (args.labels != null) { paramWriter.writeVarIntNum(args.labels.length); for (const label of args.labels) { const labelBytes = Utils.toArray(label, 'utf8'); paramWriter.writeVarIntNum(labelBytes.length); paramWriter.write(labelBytes); } } else { paramWriter.writeVarIntNum(-1); } // Serialize options if (args.options != null) { paramWriter.writeInt8(1); // options present // signAndProcess if (typeof args.options.signAndProcess === 'boolean') { paramWriter.writeInt8(args.options.signAndProcess ? 1 : 0); } else { paramWriter.writeInt8(-1); } // acceptDelayedBroadcast if (typeof args.options.acceptDelayedBroadcast === 'boolean') { paramWriter.writeInt8(args.options.acceptDelayedBroadcast ? 1 : 0); } else { paramWriter.writeInt8(-1); } // trustSelf if (args.options.trustSelf === 'known') { paramWriter.writeInt8(1); } else { paramWriter.writeInt8(-1); } // knownTxids if (args.options.knownTxids != null) { paramWriter.writeVarIntNum(args.options.knownTxids.length); for (const txid of args.options.knownTxids) { const txidBytes = Utils.toArray(txid, 'hex'); paramWriter.write(txidBytes); } } else { paramWriter.writeVarIntNum(-1); } // returnTXIDOnly if (typeof args.options.returnTXIDOnly === 'boolean') { paramWriter.writeInt8(args.options.returnTXIDOnly ? 1 : 0); } else { paramWriter.writeInt8(-1); } // noSend if (typeof args.options.noSend === 'boolean') { paramWriter.writeInt8(args.options.noSend ? 1 : 0); } else { paramWriter.writeInt8(-1); } // noSendChange if (args.options.noSendChange != null) { paramWriter.writeVarIntNum(args.options.noSendChange.length); for (const outpoint of args.options.noSendChange) { paramWriter.write(this.encodeOutpoint(outpoint)); } } else { paramWriter.writeVarIntNum(-1); } // sendWith if (args.options.sendWith != null) { paramWriter.writeVarIntNum(args.options.sendWith.length); for (const txid of args.options.sendWith) { const txidBytes = Utils.toArray(txid, 'hex'); paramWriter.write(txidBytes); } } else { paramWriter.writeVarIntNum(-1); } // randomizeOutputs if (typeof args.options.randomizeOutputs === 'boolean') { paramWriter.writeInt8(args.options.randomizeOutputs ? 1 : 0); } else { paramWriter.writeInt8(-1); } } else { paramWriter.writeInt8(0); // options not present } // Transmit and parse response const result = await this.transmit('createAction', originator, paramWriter.toArray()); const resultReader = new Utils.Reader(result); const response = {}; // Parse txid const txidFlag = resultReader.readInt8(); if (txidFlag === 1) { const txidBytes = resultReader.read(32); response.txid = Utils.toHex(txidBytes); } // Parse tx const txFlag = resultReader.readInt8(); if (txFlag === 1) { const txLength = resultReader.readVarIntNum(); response.tx = resultReader.read(txLength); } // Parse noSendChange const noSendChangeLength = resultReader.readVarIntNum(); if (noSendChangeLength >= 0) { response.noSendChange = []; for (let i = 0; i < noSendChangeLength; i++) { const outpoint = this.readOutpoint(resultReader); response.noSendChange.push(outpoint); } } // Parse sendWithResults const sendWithResultsLength = resultReader.readVarIntNum(); if (sendWithResultsLength >= 0) { response.sendWithResults = []; for (let i = 0; i < sendWithResultsLength; i++) { const txidBytes = resultReader.read(32); const txid = Utils.toHex(txidBytes); const statusCode = resultReader.readInt8(); let status = 'unproven'; if (statusCode === 1) status = 'unproven'; else if (statusCode === 2) status = 'sending'; else if (statusCode === 3) status = 'failed'; response.sendWithResults.push({ txid, status }); } } // Parse signableTransaction const signableTransactionFlag = resultReader.readInt8(); if (signableTransactionFlag === 1) { const txLength = resultReader.readVarIntNum(); const tx = resultReader.read(txLength); const referenceLength = resultReader.readVarIntNum(); const referenceBytes = resultReader.read(referenceLength); response.signableTransaction = { tx, reference: Utils.toBase64(referenceBytes) }; } return response; } async signAction(args, originator) { const paramWriter = new Utils.Writer(); // Serialize spends const spendIndexes = Object.keys(args.spends); paramWriter.writeVarIntNum(spendIndexes.length); for (const index of spendIndexes) { paramWriter.writeVarIntNum(Number(index)); const spend = args.spends[Number(index)]; // unlockingScript const unlockingScriptBytes = Utils.toArray(spend.unlockingScript, 'hex'); paramWriter.writeVarIntNum(unlockingScriptBytes.length); paramWriter.write(unlockingScriptBytes); // sequenceNumber if (typeof spend.sequenceNumber === 'number') { paramWriter.writeVarIntNum(spend.sequenceNumber); } else { paramWriter.writeVarIntNum(-1); } } // Serialize reference const referenceBytes = Utils.toArray(args.reference, 'base64'); paramWriter.writeVarIntNum(referenceBytes.length); paramWriter.write(referenceBytes); // Serialize options if (args.options != null) { paramWriter.writeInt8(1); // options present // acceptDelayedBroadcast if (typeof args.options.acceptDelayedBroadcast === 'boolean') { paramWriter.writeInt8(args.options.acceptDelayedBroadcast ? 1 : 0); } else { paramWriter.writeInt8(-1); } // returnTXIDOnly if (typeof args.options.returnTXIDOnly === 'boolean') { paramWriter.writeInt8(args.options.returnTXIDOnly ? 1 : 0); } else { paramWriter.writeInt8(-1); } // noSend if (typeof args.options.noSend === 'boolean') { paramWriter.writeInt8(args.options.noSend ? 1 : 0); } else { paramWriter.writeInt8(-1); } // sendWith if (args.options.sendWith != null) { paramWriter.writeVarIntNum(args.options.sendWith.length); for (const txid of args.options.sendWith) { const txidBytes = Utils.toArray(txid, 'hex'); paramWriter.write(txidBytes); } } else { paramWriter.writeVarIntNum(-1); } } else { paramWriter.writeInt8(0); // options not present } // Transmit and parse response const result = await this.transmit('signAction', originator, paramWriter.toArray()); const resultReader = new Utils.Reader(result); const response = {}; // Parse txid const txidFlag = resultReader.readInt8(); if (txidFlag === 1) { const txidBytes = resultReader.read(32); response.txid = Utils.toHex(txidBytes); } // Parse tx const txFlag = resultReader.readInt8(); if (txFlag === 1) { const txLength = resultReader.readVarIntNum(); response.tx = resultReader.read(txLength); } // Parse sendWithResults const sendWithResultsLength = resultReader.readVarIntNum(); if (sendWithResultsLength >= 0) { response.sendWithResults = []; for (let i = 0; i < sendWithResultsLength; i++) { const txidBytes = resultReader.read(32); const txid = Utils.toHex(txidBytes); const statusCode = resultReader.readInt8(); let status = 'unproven'; if (statusCode === 1) status = 'unproven'; else if (statusCode === 2) status = 'sending'; else if (statusCode === 3) status = 'failed'; response.sendWithResults.push({ txid, status }); } } return response; } async abortAction(args, originator) { await this.transmit('abortAction', originator, Utils.toArray(args.reference, 'base64')); return { aborted: true }; } async listActions(args, originator) { const paramWriter = new Utils.Writer(); // Serialize labels paramWriter.writeVarIntNum(args.labels.length); for (const label of args.labels) { const labelBytes = Utils.toArray(label, 'utf8'); paramWriter.writeVarIntNum(labelBytes.length); paramWriter.write(labelBytes); } // Serialize labelQueryMode if (args.labelQueryMode === 'any') { paramWriter.writeInt8(1); } else if (args.labelQueryMode === 'all') { paramWriter.writeInt8(2); } else { paramWriter.writeInt8(-1); } // Serialize include options const includeOptions = [ args.includeLabels, args.includeInputs, args.includeInputSourceLockingScripts, args.includeInputUnlockingScripts, args.includeOutputs, args.includeOutputLockingScripts ]; for (const option of includeOptions) { if (typeof option === 'boolean') { paramWriter.writeInt8(option ? 1 : 0); } else { paramWriter.writeInt8(-1); } } // Serialize limit and offset if (typeof args.limit === 'number') { paramWriter.writeVarIntNum(args.limit); } else { paramWriter.writeVarIntNum(-1); } if (typeof args.offset === 'number') { paramWriter.writeVarIntNum(args.offset); } else { paramWriter.writeVarIntNum(-1); } // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); // Transmit and parse response const result = await this.transmit('listActions', originator, paramWriter.toArray()); const resultReader = new Utils.Reader(result); const totalActions = resultReader.readVarIntNum(); const actions = []; for (let i = 0; i < totalActions; i++) { // Parse action fields const txidBytes = resultReader.read(32); const txid = Utils.toHex(txidBytes); const satoshis = resultReader.readVarIntNum(); const statusCode = resultReader.readInt8(); let status; switch (statusCode) { case 1: status = 'completed'; break; case 2: status = 'unprocessed'; break; case 3: status = 'sending'; break; case 4: status = 'unproven'; break; case 5: status = 'unsigned'; break; case 6: status = 'nosend'; break; case 7: status = 'nonfinal'; break; case 8: status = 'failed'; break; default: throw new Error(`Unknown status code: ${statusCode}`); } const isOutgoing = resultReader.readInt8() === 1; const descriptionLength = resultReader.readVarIntNum(); const descriptionBytes = resultReader.read(descriptionLength); const description = Utils.toUTF8(descriptionBytes); const action = { txid, satoshis, status, isOutgoing, description, version: 0, lockTime: 0 }; // Parse labels const labelsLength = resultReader.readVarIntNum(); if (labelsLength >= 0) { action.labels = []; for (let j = 0; j < labelsLength; j++) { const labelLength = resultReader.readVarIntNum(); const labelBytes = resultReader.read(labelLength); action.labels.push(Utils.toUTF8(labelBytes)); } } // Parse version and lockTime action.version = resultReader.readVarIntNum(); action.lockTime = resultReader.readVarIntNum(); // Parse inputs const inputsLength = resultReader.readVarIntNum(); if (inputsLength >= 0) { action.inputs = []; for (let k = 0; k < inputsLength; k++) { const sourceOutpoint = this.readOutpoint(resultReader); const sourceSatoshis = resultReader.readVarIntNum(); // sourceLockingScript const sourceLockingScriptLength = resultReader.readVarIntNum(); let sourceLockingScript; if (sourceLockingScriptLength >= 0) { const sourceLockingScriptBytes = resultReader.read(sourceLockingScriptLength); sourceLockingScript = Utils.toHex(sourceLockingScriptBytes); } // unlockingScript const unlockingScriptLength = resultReader.readVarIntNum(); let unlockingScript; if (unlockingScriptLength >= 0) { const unlockingScriptBytes = resultReader.read(unlockingScriptLength); unlockingScript = Utils.toHex(unlockingScriptBytes); } // inputDescription const inputDescriptionLength = resultReader.readVarIntNum(); const inputDescriptionBytes = resultReader.read(inputDescriptionLength); const inputDescription = Utils.toUTF8(inputDescriptionBytes); // sequenceNumber const sequenceNumber = resultReader.readVarIntNum(); action.inputs.push({ sourceOutpoint, sourceSatoshis, sourceLockingScript, unlockingScript, inputDescription, sequenceNumber }); } } // Parse outputs const outputsLength = resultReader.readVarIntNum(); if (outputsLength >= 0) { action.outputs = []; for (let l = 0; l < outputsLength; l++) { const outputIndex = resultReader.readVarIntNum(); const satoshis = resultReader.readVarIntNum(); // lockingScript const lockingScriptLength = resultReader.readVarIntNum(); let lockingScript; if (lockingScriptLength >= 0) { const lockingScriptBytes = resultReader.read(lockingScriptLength); lockingScript = Utils.toHex(lockingScriptBytes); } const spendable = resultReader.readInt8() === 1; // outputDescription const outputDescriptionLength = resultReader.readVarIntNum(); const outputDescriptionBytes = resultReader.read(outputDescriptionLength); const outputDescription = Utils.toUTF8(outputDescriptionBytes); // basket const basketLength = resultReader.readVarIntNum(); let basket; if (basketLength >= 0) { const basketBytes = resultReader.read(basketLength); basket = Utils.toUTF8(basketBytes); } // tags const tagsLength = resultReader.readVarIntNum(); const tags = []; if (tagsLength >= 0) { for (let m = 0; m < tagsLength; m++) { const tagLength = resultReader.readVarIntNum(); const tagBytes = resultReader.read(tagLength); tags.push(Utils.toUTF8(tagBytes)); } } // customInstructions const customInstructionsLength = resultReader.readVarIntNum(); let customInstructions; if (customInstructionsLength >= 0) { const customInstructionsBytes = resultReader.read(customInstructionsLength); customInstructions = Utils.toUTF8(customInstructionsBytes); } action.outputs.push({ outputIndex, satoshis, lockingScript, spendable, outputDescription, basket, tags, customInstructions }); } } actions.push(action); } return { totalActions, actions }; } async internalizeAction(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.writeVarIntNum(args.tx.length); paramWriter.write(args.tx); paramWriter.writeVarIntNum(args.outputs.length); for (const out of args.outputs) { paramWriter.writeVarIntNum(out.outputIndex); if (out.protocol === 'wallet payment') { if (out.paymentRemittance == null) { throw new Error('Payment remittance is required for wallet payment'); } paramWriter.writeUInt8(1); paramWriter.write(Utils.toArray(out.paymentRemittance.senderIdentityKey, 'hex')); const derivationPrefixAsArray = Utils.toArray(out.paymentRemittance.derivationPrefix, 'base64'); paramWriter.writeVarIntNum(derivationPrefixAsArray.length); paramWriter.write(derivationPrefixAsArray); const derivationSuffixAsArray = Utils.toArray(out.paymentRemittance.derivationSuffix, 'base64'); paramWriter.writeVarIntNum(derivationSuffixAsArray.length); paramWriter.write(derivationSuffixAsArray); } else { paramWriter.writeUInt8(2); const basketAsArray = Utils.toArray(out.insertionRemittance?.basket, 'utf8'); paramWriter.writeVarIntNum(basketAsArray.length); paramWriter.write(basketAsArray); if (typeof out.insertionRemittance?.customInstructions === 'string' && out.insertionRemittance.customInstructions !== '') { const customInstructionsAsArray = Utils.toArray(out.insertionRemittance.customInstructions, 'utf8'); paramWriter.writeVarIntNum(customInstructionsAsArray.length); paramWriter.write(customInstructionsAsArray); } else { paramWriter.writeVarIntNum(-1); } if (typeof out.insertionRemittance?.tags === 'object') { paramWriter.writeVarIntNum(out.insertionRemittance.tags.length); for (const tag of out.insertionRemittance.tags) { const tagAsArray = Utils.toArray(tag, 'utf8'); paramWriter.writeVarIntNum(tagAsArray.length); paramWriter.write(tagAsArray); } } else { paramWriter.writeVarIntNum(0); } } } if (typeof args.labels === 'object') { paramWriter.writeVarIntNum(args.labels.length); for (const l of args.labels) { const labelAsArray = Utils.toArray(l, 'utf8'); paramWriter.writeVarIntNum(labelAsArray.length); paramWriter.write(labelAsArray); } } else { paramWriter.writeVarIntNum(-1); } const descriptionAsArray = Utils.toArray(args.description); paramWriter.writeVarIntNum(descriptionAsArray.length); paramWriter.write(descriptionAsArray); // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); await this.transmit('internalizeAction', originator, paramWriter.toArray()); return { accepted: true }; } async listOutputs(args, originator) { const paramWriter = new Utils.Writer(); const basketAsArray = Utils.toArray(args.basket, 'utf8'); paramWriter.writeVarIntNum(basketAsArray.length); paramWriter.write(basketAsArray); if (typeof args.tags === 'object') { paramWriter.writeVarIntNum(args.tags.length); for (const tag of args.tags) { const tagAsArray = Utils.toArray(tag, 'utf8'); paramWriter.writeVarIntNum(tagAsArray.length); paramWriter.write(tagAsArray); } } else { paramWriter.writeVarIntNum(0); } if (args.tagQueryMode === 'all') { paramWriter.writeInt8(1); } else if (args.tagQueryMode === 'any') { paramWriter.writeInt8(2); } else { paramWriter.writeInt8(-1); } if (args.include === 'locking scripts') { paramWriter.writeInt8(1); } else if (args.include === 'entire transactions') { paramWriter.writeInt8(2); } else { paramWriter.writeInt8(-1); } if (typeof args.includeCustomInstructions === 'boolean') { paramWriter.writeInt8(args.includeCustomInstructions ? 1 : 0); } else { paramWriter.writeInt8(-1); } if (typeof args.includeTags === 'boolean') { paramWriter.writeInt8(args.includeTags ? 1 : 0); } else { paramWriter.writeInt8(-1); } if (typeof args.includeLabels === 'boolean') { paramWriter.writeInt8(args.includeLabels ? 1 : 0); } else { paramWriter.writeInt8(-1); } if (typeof args.limit === 'number') { paramWriter.writeVarIntNum(args.limit); } else { paramWriter.writeVarIntNum(-1); } if (typeof args.offset === 'number') { paramWriter.writeVarIntNum(args.offset); } else { paramWriter.writeVarIntNum(-1); } // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); const result = await this.transmit('listOutputs', originator, paramWriter.toArray()); const resultReader = new Utils.Reader(result); const totalOutputs = resultReader.readVarIntNum(); const beefLength = resultReader.readVarIntNum(); let BEEF; if (beefLength >= 0) { BEEF = resultReader.read(beefLength); } const outputs = []; for (let i = 0; i < totalOutputs; i++) { const outpoint = this.readOutpoint(resultReader); const satoshis = resultReader.readVarIntNum(); const output = { spendable: true, outpoint, satoshis }; const scriptLength = resultReader.readVarIntNum(); if (scriptLength >= 0) { output.lockingScript = Utils.toHex(resultReader.read(scriptLength)); } const customInstructionsLength = resultReader.readVarIntNum(); if (customInstructionsLength >= 0) { output.customInstructions = Utils.toUTF8(resultReader.read(customInstructionsLength)); } const tagsLength = resultReader.readVarIntNum(); if (tagsLength !== -1) { const tags = []; for (let i = 0; i < tagsLength; i++) { const tagLength = resultReader.readVarIntNum(); tags.push(Utils.toUTF8(resultReader.read(tagLength))); } output.tags = tags; } const labelsLength = resultReader.readVarIntNum(); if (labelsLength !== -1) { const labels = []; for (let i = 0; i < labelsLength; i++) { const labelLength = resultReader.readVarIntNum(); labels.push(Utils.toUTF8(resultReader.read(labelLength))); } output.labels = labels; } outputs.push(output); } return { totalOutputs, BEEF, outputs }; } async relinquishOutput(args, originator) { const paramWriter = new Utils.Writer(); const basketAsArray = Utils.toArray(args.basket, 'utf8'); paramWriter.writeVarIntNum(basketAsArray.length); paramWriter.write(basketAsArray); paramWriter.write(this.encodeOutpoint(args.output)); await this.transmit('relinquishOutput', originator, paramWriter.toArray()); return { relinquished: true }; } encodeOutpoint(outpoint) { const writer = new Utils.Writer(); const [txid, index] = outpoint.split('.'); writer.write(Utils.toArray(txid, 'hex')); writer.writeVarIntNum(Number(index)); return writer.toArray(); } readOutpoint(reader) { const txid = Utils.toHex(reader.read(32)); const index = reader.readVarIntNum(); return `${txid}.${index}`; } async getPublicKey(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.writeUInt8(args.identityKey ? 1 : 0); if (!args.identityKey) { paramWriter.write(this.encodeKeyRelatedParams(args.protocolID ??= [SecurityLevels.Silent, 'default'], args.keyID ??= '', args.counterparty, args.privileged, args.privilegedReason)); if (typeof args.forSelf === 'boolean') { paramWriter.writeInt8(args.forSelf ? 1 : 0); } else { paramWriter.writeInt8(-1); } } else { paramWriter.write(this.encodePrivilegedParams(args.privileged, args.privilegedReason)); } // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); const result = await this.transmit('getPublicKey', originator, paramWriter.toArray()); return { publicKey: Utils.toHex(result) }; } async revealCounterpartyKeyLinkage(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodePrivilegedParams(args.privileged, args.privilegedReason)); paramWriter.write(Utils.toArray(args.counterparty, 'hex')); paramWriter.write(Utils.toArray(args.verifier, 'hex')); const result = await this.transmit('revealCounterpartyKeyLinkage', originator, paramWriter.toArray()); const resultReader = new Utils.Reader(result); const prover = Utils.toHex(resultReader.read(33)); const verifier = Utils.toHex(resultReader.read(33)); const counterparty = Utils.toHex(resultReader.read(33)); const revelationTimeLength = resultReader.readVarIntNum(); const revelationTime = Utils.toUTF8(resultReader.read(revelationTimeLength)); const encryptedLinkageLength = resultReader.readVarIntNum(); const encryptedLinkage = resultReader.read(encryptedLinkageLength); const encryptedLinkageProofLength = resultReader.readVarIntNum(); const encryptedLinkageProof = resultReader.read(encryptedLinkageProofLength); return { prover, verifier, counterparty, revelationTime, encryptedLinkage, encryptedLinkageProof }; } async revealSpecificKeyLinkage(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodeKeyRelatedParams(args.protocolID, args.keyID, args.counterparty, args.privileged, args.privilegedReason)); paramWriter.write(Utils.toArray(args.verifier, 'hex')); const result = await this.transmit('revealSpecificKeyLinkage', originator, paramWriter.toArray()); const resultReader = new Utils.Reader(result); const prover = Utils.toHex(resultReader.read(33)); const verifier = Utils.toHex(resultReader.read(33)); const counterparty = Utils.toHex(resultReader.read(33)); const securityLevel = resultReader.readUInt8(); const protocolLength = resultReader.readVarIntNum(); const protocol = Utils.toUTF8(resultReader.read(protocolLength)); const keyIDLength = resultReader.readVarIntNum(); const keyID = Utils.toUTF8(resultReader.read(keyIDLength)); const encryptedLinkageLength = resultReader.readVarIntNum(); const encryptedLinkage = resultReader.read(encryptedLinkageLength); const encryptedLinkageProofLength = resultReader.readVarIntNum(); const encryptedLinkageProof = resultReader.read(encryptedLinkageProofLength); const proofType = resultReader.readUInt8(); return { prover, verifier, counterparty, protocolID: [securityLevel, protocol], keyID, encryptedLinkage, encryptedLinkageProof, proofType }; } async encrypt(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodeKeyRelatedParams(args.protocolID, args.keyID, args.counterparty, args.privileged, args.privilegedReason)); paramWriter.writeVarIntNum(args.plaintext.length); paramWriter.write(args.plaintext); // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); return { ciphertext: await this.transmit('encrypt', originator, paramWriter.toArray()) }; } async decrypt(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodeKeyRelatedParams(args.protocolID, args.keyID, args.counterparty, args.privileged, args.privilegedReason)); paramWriter.writeVarIntNum(args.ciphertext.length); paramWriter.write(args.ciphertext); // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); return { plaintext: await this.transmit('decrypt', originator, paramWriter.toArray()) }; } async createHmac(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodeKeyRelatedParams(args.protocolID, args.keyID, args.counterparty, args.privileged, args.privilegedReason)); paramWriter.writeVarIntNum(args.data.length); paramWriter.write(args.data); // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); return { hmac: await this.transmit('createHmac', originator, paramWriter.toArray()) }; } async verifyHmac(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodeKeyRelatedParams(args.protocolID, args.keyID, args.counterparty, args.privileged, args.privilegedReason)); paramWriter.write(args.hmac); paramWriter.writeVarIntNum(args.data.length); paramWriter.write(args.data); // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); await this.transmit('verifyHmac', originator, paramWriter.toArray()); return { valid: true }; } async createSignature(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodeKeyRelatedParams(args.protocolID, args.keyID, args.counterparty, args.privileged, args.privilegedReason)); if (typeof args.data === 'object') { paramWriter.writeUInt8(1); paramWriter.writeVarIntNum(args.data.length); paramWriter.write(args.data); } else { paramWriter.writeUInt8(2); paramWriter.write(args.hashToDirectlySign ??= []); } // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); return { signature: await this.transmit('createSignature', originator, paramWriter.toArray()) }; } async verifySignature(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(this.encodeKeyRelatedParams(args.protocolID, args.keyID, args.counterparty, args.privileged, args.privilegedReason)); if (typeof args.forSelf === 'boolean') { paramWriter.writeInt8(args.forSelf ? 1 : 0); } else { paramWriter.writeInt8(-1); } paramWriter.writeVarIntNum(args.signature.length); paramWriter.write(args.signature); if (typeof args.data === 'object') { paramWriter.writeUInt8(1); paramWriter.writeVarIntNum(args.data.length); paramWriter.write(args.data); } else { paramWriter.writeUInt8(2); paramWriter.write(args.hashToDirectlyVerify ?? []); } // Serialize seekPermission paramWriter.writeInt8(typeof args.seekPermission === 'boolean' ? args.seekPermission ? 1 : 0 : -1); await this.transmit('verifySignature', originator, paramWriter.toArray()); return { valid: true }; } encodeKeyRelatedParams(protocolID, keyID, counterparty, privileged, privilegedReason) { const paramWriter = new Utils.Writer(); paramWriter.writeUInt8(protocolID[0]); const protocolAsArray = Utils.toArray(protocolID[1], 'utf8'); paramWriter.writeVarIntNum(protocolAsArray.length); paramWriter.write(protocolAsArray); const keyIDAsArray = Utils.toArray(keyID, 'utf8'); paramWriter.writeVarIntNum(keyIDAsArray.length); paramWriter.write(keyIDAsArray); if (typeof counterparty !== 'string') { paramWriter.writeUInt8(0); } else if (counterparty === 'self') { paramWriter.writeUInt8(11); } else if (counterparty === 'anyone') { paramWriter.writeUInt8(12); } else { paramWriter.write(Utils.toArray(counterparty, 'hex')); } paramWriter.write(this.encodePrivilegedParams(privileged, privilegedReason)); return paramWriter.toArray(); } async acquireCertificate(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.write(Utils.toArray(args.type, 'base64')); paramWriter.write(Utils.toArray(args.certifier, 'hex')); const fieldEntries = Object.entries(args.fields); paramWriter.writeVarIntNum(fieldEntries.length); for (const [key, value] of fieldEntries) { const keyAsArray = Utils.toArray(key, 'utf8'); const valueAsArray = Utils.toArray(value, 'utf8'); paramWriter.writeVarIntNum(keyAsArray.length); paramWriter.write(keyAsArray); paramWriter.writeVarIntNum(valueAsArray.length); paramWriter.write(valueAsArray); } paramWriter.write(this.encodePrivilegedParams(args.privileged, args.privilegedReason)); paramWriter.writeUInt8(args.acquisitionProtocol === 'direct' ? 1 : 2); if (args.acquisitionProtocol === 'direct') { paramWriter.write(Utils.toArray(args.serialNumber, 'base64')); paramWriter.write(this.encodeOutpoint(args.revocationOutpoint ?? '')); const signatureAsArray = Utils.toArray(args.signature, 'hex'); paramWriter.writeVarIntNum(signatureAsArray.length); paramWriter.write(signatureAsArray); const keyringRevealerAsArray = args.keyringRevealer !== 'certifier' ? Utils.toArray(args.keyringRevealer, 'hex') : [11]; paramWriter.write(keyringRevealerAsArray); const keyringKeys = Object.keys(args.keyringForSubject ?? {}); paramWriter.writeVarIntNum(keyringKeys.length); for (let i = 0; i < keyringKeys.length; i++) { const keyringKeysAsArray = Utils.toArray(keyringKeys[i], 'utf8'); paramWriter.writeVarIntNum(keyringKeysAsArray.length); paramWriter.write(keyringKeysAsArray); const keyringForSubjectAsArray = Utils.toArray(args.keyringForSubject?.[keyringKeys[i]], 'base64'); paramWriter.writeVarIntNum(keyringForSubjectAsArray.length); paramWriter.write(keyringForSubjectAsArray); } } else { const certifierUrlAsArray = Utils.toArray(args.certifierUrl, 'utf8'); paramWriter.writeVarIntNum(certifierUrlAsArray.length); paramWriter.write(certifierUrlAsArray); } const result = await this.transmit('acquireCertificate', originator, paramWriter.toArray()); const cert = Certificate.fromBinary(result); return { ...cert, signature: cert.signature }; } encodePrivilegedParams(privileged, privilegedReason) { const paramWriter = new Utils.Writer(); if (typeof privileged === 'boolean') { paramWriter.writeInt8(privileged ? 1 : 0); } else { paramWriter.writeInt8(-1); } if (typeof privilegedReason === 'string') { const privilegedReasonAsArray = Utils.toArray(privilegedReason, 'utf8'); paramWriter.writeInt8(privilegedReasonAsArray.length); paramWriter.write(privilegedReasonAsArray); } else { paramWriter.writeInt8(-1); } return paramWriter.toArray(); } async listCertificates(args, originator) { const paramWriter = new Utils.Writer(); paramWriter.writeVarIntNum(args.certifiers.length); for (let i = 0; i < args.certifiers.length; i++) { paramWriter.write(Utils.toArray(args.certifiers[i], 'hex')); } paramWriter.writeVarIntNum(args.types.length); for (let i = 0; i < args.types.length; i++) { paramWriter.write(Utils.toArray(args.types[i], 'base64')); } if (typeof args.limit === 'number') { paramWriter.writeVarIntNum(args.limit); } else { paramWriter.writeVarIntNum(-1); } if (typeof args.offset === 'number') { paramWriter.writeVarIntNum(args.offset); } else { paramWriter.writeVarIntNum(-1); } paramWriter.write(this.encodePrivilegedParams(args.privileged, args.privilegedReason)); const result = await this.transmit('listCertificates', originator, paramWriter.toArray()); const resultReader = new Utils.Reader(result); const totalCertificates = resultReader.readVarIntNum(); const certificates = []; for (let i = 0; i < totalCertificates; i++) { const certificateLength = resultReader.readVarIntNum(); const certificateBin = resultReader.read(certificateLength); const cert = Certificate.fromBinary(certificateBin); const keyringForVerifier = {}; if (resultReader.readInt8() === 1) { const numFields = resultReader.readVarIntNum(); for (let i = 0; i < numFields; i++) { const fieldKeyLength = resultReader.readVarIntNum(); const fieldKey = Utils.toUTF8(resultReader.read(fieldKeyLength)); const fieldValueLength = resultReader.readVarIntNum(); keyringForVerifier[fieldKey] = Utils.toBase64(resultReader.read(fieldValueLength)); } } const verifierLength = resultReader.readVarIntNum(); let v