Skip to content

Commit

Permalink
feat: add ledger multisig wallet creation for native segwit
Browse files Browse the repository at this point in the history
  • Loading branch information
Polybius93 committed May 2, 2024
1 parent b8746b6 commit c41ca61
Show file tree
Hide file tree
Showing 6 changed files with 665 additions and 150 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
"@bitgo/sdk-api": "^1.45.1",
"@bitgo/sdk-coin-btc": "^2.0.6",
"@bitgo/sdk-core": "^26.8.0",
"@ledgerhq/hw-transport-node-hid": "^6.28.6",
"@noble/hashes": "^1.4.0",
"@scure/base": "^1.1.6",
"@scure/btc-signer": "^1.3.1",
"bip32": "^4.0.0",
"bitcoinjs-lib": "^6.1.5",
"decimal.js": "^10.4.3",
"dotenv": "^16.4.5",
"ledger-bitcoin": "^0.2.3",
"lint": "^0.8.19",
"lodash": "^4.17.21",
"ls-lint": "^0.1.2",
Expand Down
4 changes: 2 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/** @format */

// test values for the integration tests
export const TEST_BITCOIN_AMOUNT = 0.01;
export const TEST_FEE_AMOUNT = 0.01;
export const TEST_BITCOIN_AMOUNT = 0.001;
export const TEST_FEE_AMOUNT = 0.1;
export const TEST_ATTESTOR_PUBLIC_KEY = '4caaf4bb366239b0a8b7a5e5a44d043b5f66ae7364895317af8847ac6fadbd2b';
export const TEST_FEE_PUBLIC_KEY = '03c9fc819e3c26ec4a58639add07f6372e810513f5d3d7374c25c65fdf1aefe4c5';
export const TEST_VAULT_UUID = '0xcf5f227dd384a590362b417153876d9d22b31b2ed1e22065e270b82437cf1880';
Expand Down
203 changes: 69 additions & 134 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import { BitGoAddress } from './models.js';
import { getNativeSegwitPublicKeys, getTaprootMultisigScript, getTaprootPublicKeys } from './payment-functions.js';
import { bitcoinToSats } from './utilities.js';
import { createScripts, testLedger } from './ledger.js';

dotenv.config();

Expand Down Expand Up @@ -63,6 +64,69 @@ async function createNativeSegwitAddress(bitGoWallet: Wallet, label: string) {
}
}

async function createMultisigWallet() {
const {
BITCOIN_NETWORK,
BITGO_ACCESS_TOKEN,
BITGO_WALLET_PASSPHRASE,
BITGO_WALLET_ID,
BITGO_NATIVE_SEGWIT_ADDRESS,
BITGO_TAPROOT_ADDRESS,
USER_XPUB,
BACKUP_XPUB,
BITGO_XPUB,
} = process.env;

if (
!BITCOIN_NETWORK ||
!BITGO_ACCESS_TOKEN ||
!BITGO_WALLET_PASSPHRASE ||
!BITGO_WALLET_ID ||
!BITGO_NATIVE_SEGWIT_ADDRESS ||
!BITGO_TAPROOT_ADDRESS ||
!USER_XPUB ||
!BACKUP_XPUB ||
!BITGO_XPUB
) {
throw new Error('Please provide all the required Environment Variables.');
}

let environmentName: EnvironmentName;
let coinType: string;
let coinInstance: CoinConstructor;
let bitcoinNetwork: Network;

switch (BITCOIN_NETWORK) {
case 'bitcoin':
environmentName = 'prod';
coinType = 'btc';
coinInstance = Btc.createInstance;
bitcoinNetwork = bitcoin;
break;
case 'testnet':
environmentName = 'test';
coinType = 'tbtc';
coinInstance = Tbtc.createInstance;
bitcoinNetwork = testnet;
break;
default:
throw new Error('Invalid BITCOIN_NETWORK Value. Please provide either "bitcoin" or "testnet".');
}

const attestorGroupXPublicKey = 'xpub43f9a14c790c0b86ce78bec919e96725e56aee8e0a0fdd8138aa7b351930b3c1';

let bitGoAPI: BitGoAPI;
try {
bitGoAPI = new BitGoAPI({ accessToken: BITGO_ACCESS_TOKEN, env: environmentName });
} catch (error) {
throw new Error(`Error while initializing BitGo API: ${error}`);
}

bitGoAPI.coin(coinType).wallets().generateWallet({ label: 'Test Wallet' });

bitGoAPI.register(coinType, coinInstance);
}

async function getBitGoDetails() {
const {
BITCOIN_NETWORK,
Expand Down Expand Up @@ -119,6 +183,8 @@ async function getBitGoDetails() {
throw new Error(`Error while initializing BitGo API: ${error}`);
}

bitGoAPI.coin(coinType).wallets().generateWallet({ label: 'Test Wallet' });

bitGoAPI.register(coinType, coinInstance);

let bitGoWallet: Wallet;
Expand Down Expand Up @@ -146,141 +212,10 @@ async function getBitGoDetails() {

async function main() {
try {
const {
bitGoAPI,
bitGoWallet,
bitGoKeyChain,
bitGoWalletPassphrase,
nativeSegwitAddress,
taprootAddress,
userXPUB,
backupXPUB,
bitGoXPUB,
bitcoinNetwork,
} = await getBitGoDetails();

console.log(`Current Balance: ${bitGoWallet.balance()}`);

const { addresses: bitGoAddresses } = await bitGoWallet.addresses();

const bitGoNativeSegwitAddress = findBitGoAddress(bitGoAddresses, nativeSegwitAddress);
const bitGoTaprootAddress = findBitGoAddress(bitGoAddresses, taprootAddress);

// this returns the public keys of the user, backup and bitgo for the native segwit multisig address
const nativeSegwitPublicKeys = getNativeSegwitPublicKeys(
bitGoNativeSegwitAddress,
userXPUB,
backupXPUB,
bitGoXPUB,
bitcoinNetwork
);

// this returns the public keys of the user, backup and bitgo for a new taproot multisig address
const taprootPublicKeys = getTaprootPublicKeys(bitGoTaprootAddress, userXPUB, backupXPUB, bitGoXPUB);

// not sure if this is the correct way to create a taproot multisig address from the public keys
const taprootMultisig = getTaprootMultisigScript(taprootPublicKeys, bitcoinNetwork);

// this creates a multisig transaction using the user's taproot public key and the attestor group's public key
const multisigTransaction = await createMultisigTransaction(
taprootMultisig.tweakedPubkey, // this is the user's taproot public key
Buffer.from(TEST_ATTESTOR_PUBLIC_KEY, 'hex'),
TEST_VAULT_UUID,
bitcoinNetwork
);

if (!multisigTransaction.address) throw new Error('Error while creating Multisig Transaction.');

// ### ORIGINAL FUNDING TRANSACTION FLOW ###

// #1 Create a Funding Transaction to fund the Multisig Transaction
// const fundingTransaction = await createFundingTransaction(
// TEST_BITCOIN_AMOUNT,
// bitcoinNetwork,
// multisigTransaction.address,
// bitGoNativeSegwitAddress.address,
// nativeSegwitPublicKeys,
// TEST_FEE_RATE,
// TEST_FEE_PUBLIC_KEY,
// TEST_FEE_AMOUNT
// );

// #2 Sign the Funding Transaction

// #3 Finalize the Funding Transaction
// const transaction = Transaction.fromPSBT(fundingTransaction);
// transaction.finalize();

// #4 Broadcast the Funding Transaction
// const broadcastResponse = await broadcastTransaction(bytesToHex(fundingTransaction));

// ### BITGO API FLOW ###

// #1 Create an Array of Recipients for the Transaction
const feeRecipientAddress = getFeeRecipientAddress(TEST_FEE_PUBLIC_KEY, bitcoinNetwork);
const recipients = [
{ amount: bitcoinToSats(TEST_BITCOIN_AMOUNT), address: multisigTransaction.address },
{ amount: bitcoinToSats(TEST_BITCOIN_AMOUNT * TEST_FEE_AMOUNT), address: feeRecipientAddress },
];

// #2 Prebuild the Transaction
const transactionPrebuild = await bitGoWallet.prebuildTransaction({
recipients: recipients,
walletPassphrase: bitGoWalletPassphrase,
});

// #3 Sign the Transaction
const signedTransaction = await bitGoWallet.signTransaction({
txPrebuild: transactionPrebuild,
keychain: bitGoKeyChain[0],
walletPassphrase: bitGoWalletPassphrase,
});

const FullySignedTransaction = signedTransaction as FullySignedTransaction;

// #4 Submit the Transaction
const submitResponse = await bitGoWallet.submitTransaction({ txHex: FullySignedTransaction.txHex });

// ### ORIGINAL CLOSING TRANSACTION FLOW ###

// #1 Create a Closing Transaction
const closingTransaction = await createClosingTransaction(
TEST_BITCOIN_AMOUNT,
bitcoinNetwork,
'fundingTransactionID', // this is the ID of the funding transaction
multisigTransaction,
bitGoNativeSegwitAddress.address,
TEST_FEE_RATE,
TEST_FEE_PUBLIC_KEY,
TEST_FEE_AMOUNT
);

// #2 Sign the Closing Transaction

// #3 Send the Closing Transaction PSBT to the Attestor Group

// ### BITGO API FLOW ###

// #1 Prebuild the Transaction
const closingTransactionPreBuild = await bitGoWallet.prebuildTransaction({
recipients: [
{ amount: bitcoinToSats(TEST_BITCOIN_AMOUNT), address: bitGoNativeSegwitAddress.address },
{ amount: bitcoinToSats(TEST_BITCOIN_AMOUNT * TEST_FEE_AMOUNT), address: feeRecipientAddress },
],
unspents: ['fundingTransactionID:index'], // this is how the unspent can be referenced in the BitGo API
walletPassphrase: bitGoWalletPassphrase,
});

// #2 Sign the Transaction
const signedClosingTransaction = await bitGoWallet.signTransaction({
txPrebuild: transactionPrebuild,
keychain: bitGoKeyChain[0],
walletPassphrase: bitGoWalletPassphrase,
});

// #3 Send the Closing Transaction PSBT to the Attestor Group
await testLedger();
// createScripts();
} catch (error) {
console.error(`Error while running BitGoAPI flow: ${error}`);
throw new Error(`Error running: ${error}`);
}
}

Expand Down
Loading

0 comments on commit c41ca61

Please sign in to comment.