diff --git a/.changeset/bright-dogs-divide.md b/.changeset/bright-dogs-divide.md new file mode 100644 index 00000000..3c8ea4f3 --- /dev/null +++ b/.changeset/bright-dogs-divide.md @@ -0,0 +1,5 @@ +--- +"@caravan/psbt": minor +--- + +export of new utils for psbt v0 handling. Primarily adds support for taproot outputs by upgrading to bitcoinjs-lib v6 depedency. Upgrades to a new API from legacy utils from caravan/bitcoin and includes some utilities and types for handling conversions. diff --git a/.changeset/calm-adults-own.md b/.changeset/calm-adults-own.md new file mode 100644 index 00000000..906adc70 --- /dev/null +++ b/.changeset/calm-adults-own.md @@ -0,0 +1,5 @@ +--- +"@caravan/bitcoin": minor +--- + +export signature utilities from caravan/bitcoin to support new psbt tooling diff --git a/.changeset/cyan-queens-enjoy.md b/.changeset/cyan-queens-enjoy.md new file mode 100644 index 00000000..b0bea8e4 --- /dev/null +++ b/.changeset/cyan-queens-enjoy.md @@ -0,0 +1,5 @@ +--- +"@caravan/wallets": minor +--- + +upgrading psbt generation to support taproot outputs and new caravan/psbt utils diff --git a/.changeset/happy-rivers-pump.md b/.changeset/happy-rivers-pump.md new file mode 100644 index 00000000..2ed4e7d4 --- /dev/null +++ b/.changeset/happy-rivers-pump.md @@ -0,0 +1,5 @@ +--- +"@caravan/multisig": major +--- + +New package for multisig wallet utilities and types to share across other packages diff --git a/.changeset/light-plants-remember.md b/.changeset/light-plants-remember.md new file mode 100644 index 00000000..054f5401 --- /dev/null +++ b/.changeset/light-plants-remember.md @@ -0,0 +1,5 @@ +--- +"@caravan/bitcoin": minor +--- + +transaction parser was stripping out network information from global xpubs being added to psbt. global xpubs will now respect the network and include appropriate prefix diff --git a/.changeset/perfect-panthers-chew.md b/.changeset/perfect-panthers-chew.md new file mode 100644 index 00000000..1275c8b8 --- /dev/null +++ b/.changeset/perfect-panthers-chew.md @@ -0,0 +1,5 @@ +--- +"caravan-coordinator": minor +--- + +upgrade tx processing utils to use new psbt utils for taproot output support diff --git a/.changeset/twelve-kids-provide.md b/.changeset/twelve-kids-provide.md new file mode 100644 index 00000000..4e4a3fd6 --- /dev/null +++ b/.changeset/twelve-kids-provide.md @@ -0,0 +1,6 @@ +--- +"@caravan/wallets": patch +"caravan-coordinator": patch +--- + +fixes an issue where esbuild was polyfilling process.env which breaks the ability to override trezor connect settings and pass other env vars at run time in dependent applications diff --git a/.changeset/wet-cougars-sin.md b/.changeset/wet-cougars-sin.md new file mode 100644 index 00000000..067bddf7 --- /dev/null +++ b/.changeset/wet-cougars-sin.md @@ -0,0 +1,5 @@ +--- +"@caravan/eslint-config": patch +--- + +initiliazing package, upgrading deps. need to improve shared config before major release diff --git a/apps/coordinator/README.md b/apps/coordinator/README.md index b78a650d..03312c52 100644 --- a/apps/coordinator/README.md +++ b/apps/coordinator/README.md @@ -139,6 +139,10 @@ By default, Caravan uses a free API provided by information about the bitcoin blockchain or to broadcast transactions. Blockstream.info is also available as a fallback option for a public API. +Mainnet and Testnet are available options for connecting to any of the available +consensus client options. Regtest can be available through an uploaded wallet +configuration file, but only for the private client backend. + ### Bitcoind client You can also ask Caravan to use your own private [bitcoind full diff --git a/apps/coordinator/jest.config.json b/apps/coordinator/jest.config.json deleted file mode 100644 index d15cd5b8..00000000 --- a/apps/coordinator/jest.config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "testEnvironment": "jsdom", - "transform": { - "\\.[jt]sx?$": "babel-jest" - }, - "transformIgnorePatterns": [ - "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$", - "^.+\\.module\\.(css|sass|scss)$" - ], - "moduleNameMapper": { - "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy" - }, - "moduleFileExtensions": [ - "web.cjs", - "js", - "web.ts", - "ts", - "web.tsx", - "tsx", - "json", - "web.cjsx", - "jsx", - "node" - ] -} diff --git a/apps/coordinator/jest.config.ts b/apps/coordinator/jest.config.ts new file mode 100644 index 00000000..e901df97 --- /dev/null +++ b/apps/coordinator/jest.config.ts @@ -0,0 +1,27 @@ +import type { JestConfigWithTsJest } from "ts-jest"; + +const config: JestConfigWithTsJest = { + testEnvironment: "jsdom", + transform: { + "\\.[jt]sx?$": "babel-jest", + }, + transformIgnorePatterns: ["^.+\\.module\\.(css|sass|scss)$"], + moduleNameMapper: { + "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy", + }, + moduleFileExtensions: [ + "web.cjs", + "js", + "web.ts", + "ts", + "web.tsx", + "tsx", + "json", + "web.cjsx", + "jsx", + "node", + ], + setupFilesAfterEnv: ["/jest.setup.ts"], +}; + +export default config; diff --git a/apps/coordinator/jest.setup.ts b/apps/coordinator/jest.setup.ts new file mode 100644 index 00000000..b0bd26f6 --- /dev/null +++ b/apps/coordinator/jest.setup.ts @@ -0,0 +1 @@ +import "@inrupt/jest-jsdom-polyfills"; diff --git a/apps/coordinator/package.json b/apps/coordinator/package.json index 9552a431..d251b65b 100644 --- a/apps/coordinator/package.json +++ b/apps/coordinator/package.json @@ -31,6 +31,7 @@ "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.0", + "@inrupt/jest-jsdom-polyfills": "^3.2.1", "@testing-library/jest-dom": "^5.6.0", "@testing-library/react": "^10.0.4", "@types/history": "^5.0.0", @@ -95,8 +96,9 @@ "dependencies": { "@caravan/bitcoin": "*", "@caravan/clients": "*", - "@caravan/descriptors": "^0.0.6", + "@caravan/descriptors": "^0.1.1", "@caravan/eslint-config": "*", + "@caravan/psbt": "*", "@caravan/typescript-config": "*", "@caravan/wallets": "*", "@emotion/react": "^11.10.6", diff --git a/apps/coordinator/src/components/ScriptExplorer/SignatureImporter.jsx b/apps/coordinator/src/components/ScriptExplorer/SignatureImporter.jsx index fca52669..7a0f3266 100644 --- a/apps/coordinator/src/components/ScriptExplorer/SignatureImporter.jsx +++ b/apps/coordinator/src/components/ScriptExplorer/SignatureImporter.jsx @@ -3,7 +3,6 @@ import PropTypes from "prop-types"; import { connect } from "react-redux"; import { validateHex, - validateMultisigSignature, multisigBIP32Path, multisigBIP32Root, validateBIP32Path, @@ -38,6 +37,12 @@ import { } from "../../actions/signatureImporterActions"; import { setSigningKey as setSigningKeyAction } from "../../actions/transactionActions"; import { downloadFile } from "../../utils"; +import { + convertLegacyInput, + convertLegacyOutput, + getUnsignedMultisigPsbtV0, + validateMultisigPsbtSignature, +} from "@caravan/psbt"; const TEXT = "text"; const UNKNOWN = "unknown"; @@ -389,12 +394,17 @@ class SignatureImporter extends React.Component { let publicKey; try { - publicKey = validateMultisigSignature( + const args = { network, - inputs, - outputs, + inputs: inputs.map(convertLegacyInput), + outputs: outputs.map(convertLegacyOutput), + }; + const psbt = getUnsignedMultisigPsbtV0(args); + publicKey = validateMultisigPsbtSignature( + psbt.toBase64(), inputIndex, inputSignature, + inputs[inputIndex].amountSats, ); } catch (e) { errback(`Signature for input ${inputNumber} is invalid.`); @@ -482,13 +492,17 @@ class SignatureImporter extends React.Component { return; } try { - // This returns false if it completes with no error - publicKey = validateMultisigSignature( + const args = { network, - inputs, - outputs, + inputs: inputs.map(convertLegacyInput), + outputs: outputs.map(convertLegacyOutput), + }; + const psbt = getUnsignedMultisigPsbtV0(args); + publicKey = validateMultisigPsbtSignature( + psbt.toBase64(), inputIndex, inputSignature, + inputs[inputIndex].amountSats, ); } catch (e) { // eslint-disable-next-line no-console @@ -579,7 +593,8 @@ SignatureImporter.propTypes = { }), ).isRequired, fee: PropTypes.string.isRequired, - inputs: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + inputs: PropTypes.arrayOf(PropTypes.shape({ amountSats: PropTypes.string })) + .isRequired, inputsTotalSats: PropTypes.shape({}).isRequired, isWallet: PropTypes.bool.isRequired, network: PropTypes.string.isRequired, diff --git a/apps/coordinator/src/components/ScriptExplorer/Transaction.jsx b/apps/coordinator/src/components/ScriptExplorer/Transaction.jsx index 5c8fe36d..c9d67590 100644 --- a/apps/coordinator/src/components/ScriptExplorer/Transaction.jsx +++ b/apps/coordinator/src/components/ScriptExplorer/Transaction.jsx @@ -2,8 +2,8 @@ import React from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; import { - signedMultisigTransaction, blockExplorerTransactionURL, + addSignaturesToPSBT, } from "@caravan/bitcoin"; import { @@ -21,6 +21,13 @@ import { updateBlockchainClient } from "../../actions/clientActions"; import Copyable from "../Copyable"; import { externalLink } from "utils/ExternalLink"; import { setTXID } from "../../actions/transactionActions"; +import { + convertLegacyInput, + convertLegacyOutput, + getUnsignedMultisigPsbtV0, +} from "@caravan/psbt"; +import { Psbt } from "bitcoinjs-lib"; +import { Buffer } from "buffer"; class Transaction extends React.Component { constructor(props) { @@ -34,14 +41,30 @@ class Transaction extends React.Component { buildSignedTransaction = () => { const { network, inputs, outputs, signatureImporters } = this.props; - return signedMultisigTransaction( + const args = { network, - inputs, - outputs, - Object.values(signatureImporters).map( - (signatureImporter) => signatureImporter.signature, - ), - ); + inputs: inputs.map(convertLegacyInput), + outputs: outputs.map(convertLegacyOutput), + }; + const psbt = getUnsignedMultisigPsbtV0(args); + let partiallySignedTransaction = psbt.toBase64(); + for (const signatureImporter of Object.values(signatureImporters)) { + partiallySignedTransaction = addSignaturesToPSBT( + network, + partiallySignedTransaction, + signatureImporter.publicKeys.map((pubkey) => + Buffer.from(pubkey, "hex"), + ), + signatureImporter.signature.map((signature) => + Buffer.from(signature, "hex"), + ), + ); + } + + return Psbt.fromBase64(partiallySignedTransaction) + .finalizeAllInputs() + .extractTransaction() + .toHex(); }; handleBroadcast = async () => { @@ -52,7 +75,7 @@ class Transaction extends React.Component { let txid = ""; this.setState({ broadcasting: true }); try { - txid = await client.broadcastTransaction(signedTransaction.toHex()); + txid = await client.broadcastTransaction(signedTransaction); } catch (e) { // eslint-disable-next-line no-console console.error(e); @@ -71,14 +94,13 @@ class Transaction extends React.Component { render() { const { error, broadcasting, txid } = this.state; - const signedTransaction = this.buildSignedTransaction(); - const signedTransactionHex = signedTransaction.toHex(); + const signedTransactionHex = this.buildSignedTransaction(); return (
- {signedTransaction && ( + {signedTransactionHex && ( Signed Transaction @@ -89,7 +111,7 @@ class Transaction extends React.Component {