Skip to content

Commit

Permalink
feat(Bitgo): support bitgo vaults
Browse files Browse the repository at this point in the history
  • Loading branch information
KayBeSee committed Jul 19, 2023
1 parent 0da0fc1 commit baece25
Show file tree
Hide file tree
Showing 15 changed files with 138 additions and 60 deletions.
2 changes: 1 addition & 1 deletion apps/electron/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ ipcMain.handle('/xpub', async (event, args: HwiXpubRequest) => {
ipcMain.handle('/sign', async (event, args) => {
const { deviceType, devicePath, psbt } = args;
const resp = JSON.parse(await signtx(deviceType, devicePath, psbt, isTestnet));
if (resp.error) {
if (!resp.signed) {
return Promise.reject(new Error('Error signing transaction'));
}
return Promise.resolve(resp);
Expand Down
2 changes: 1 addition & 1 deletion apps/express/src/utils/setInitialConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export const setInitialConfig = async () => {
const lndHost = `${process.env.LND_IP}:${process.env.LND_GRPC_PORT}`;

const emptyConfig = EMPTY_CONFIG;
emptyConfig.isEmpty = true;
emptyConfig.lightning[0] = {
id: uuidv4(),
type: 'lightning',
Expand All @@ -46,6 +45,7 @@ export const setInitialConfig = async () => {
};

if (!configExists && process.env.APP_PASSWORD) {
emptyConfig.isEmpty = false;
const encryptedConfigObject = AES.encrypt(
JSON.stringify(emptyConfig),
process.env.APP_PASSWORD
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": "Lily Technologies, Inc. <[email protected]> (https://lily-wallet.com)",
"description": "Lily is the best way to secure your Bitcoin",
"license": "Custom",
"version": "1.3.0",
"version": "1.4.0",
"private": true,
"main": "./dist/main.js",
"homepage": "./",
Expand All @@ -24,7 +24,7 @@
"@heroicons/react": "^1.0.3",
"@lily-technologies/ecpair": "^2.0.0",
"@lily-technologies/lnrpc": "^0.14.1-beta.14",
"@lily/types": "1.3.0",
"@lily/types": "1.4.0",
"@styled-icons/bootstrap": "^10.34.0",
"@styled-icons/boxicons-logos": "^10.38.0",
"@styled-icons/boxicons-regular": "^10.38.0",
Expand Down
30 changes: 15 additions & 15 deletions apps/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,21 @@ const App = () => {
// return null;
// };

const RedirectHandler = () => {
const history = useHistory();
const { search } = useLocation();
const { redirect, ...rest } = queryString.parse(search);
useEffect(() => {
if (!config.isEmpty && !!redirect && typeof redirect === 'string') {
history.push({
pathname: redirect,
search: queryString.stringify(rest)
});
}
}, [redirect]);
// const RedirectHandler = () => {
// const history = useHistory();
// const { search } = useLocation();
// const { redirect, ...rest } = queryString.parse(search);
// useEffect(() => {
// if (!config.isEmpty && !!redirect && typeof redirect === 'string') {
// history.push({
// pathname: redirect,
// search: queryString.stringify(rest)
// });
// }
// }, [redirect]);

return null;
};
// return null;
// };

const Overlay = () => {
const { pathname } = useLocation();
Expand Down Expand Up @@ -265,7 +265,7 @@ const App = () => {
<TitleBar nodeConfig={nodeConfig} config={config} />
) : null}
<ConfigRequired />
<RedirectHandler />
{/* <RedirectHandler /> */}
<Overlay />
<Sidebar currentBitcoinNetwork={currentBitcoinNetwork} />
<Switch>
Expand Down
Binary file added apps/frontend/src/assets/bitgo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/src/assets/kingdom-trust.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/src/assets/onramp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/frontend/src/components/DeviceImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import Cobo from 'src/assets/cobo.png';
import Bitbox from 'src/assets/bitbox02.png';
import LilyLogo from 'src/assets/flower.svg';
import Unchained from 'src/assets/unchained.png';
import Bitgo from 'src/assets/bitgo.png';
import Onramp from 'src/assets/onramp.png';
import KingdomTrust from 'src/assets/kingdom-trust.png';

import { Device } from '@lily/types';

Expand Down Expand Up @@ -48,6 +51,12 @@ export const DeviceImage = ({ device, className }: Props) => {
? Bitbox
: device.type === 'unchained'
? Unchained
: device.type === 'onramp'
? Onramp
: device.type === 'kingdom-trust'
? KingdomTrust
: device.type === 'bitgo'
? Bitgo
: LilyLogo
}
/>
Expand Down
55 changes: 38 additions & 17 deletions apps/frontend/src/frontend-middleware/ElectronPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,19 @@ export class ElectronPlatform extends BasePlatform {
}

async signTransaction({ deviceType, devicePath, psbt }: HwiSignTransactionRequest) {
const response = await window.ipcRenderer.invoke('/sign', {
deviceType,
devicePath,
psbt
});
return Promise.resolve(response);
try {
const response = await window.ipcRenderer.invoke('/sign', {
deviceType,
devicePath,
psbt
});
return Promise.resolve(response);
} catch (e) {
console.log('e: ', e);
// @ts-ignore
console.log('e.message: ', e.message);
return Promise.reject(e);
}
}

async enumerate(): Promise<HwiEnumerateResponse[]> {
Expand All @@ -146,20 +153,34 @@ export class ElectronPlatform extends BasePlatform {
}

async promptPin({ deviceType, devicePath }: HwiPromptPinRequest): Promise<HwiPromptPinResponse> {
const response: HwiPromptPinResponse = await window.ipcRenderer.invoke('/promptpin', {
deviceType,
devicePath
});
return Promise.resolve(response);
try {
const response: HwiPromptPinResponse = await window.ipcRenderer.invoke('/promptpin', {
deviceType,
devicePath
});
return Promise.resolve(response);
} catch (e) {
console.log('e: ', e);
// @ts-ignore
console.log('e.message: ', e.message);
return Promise.reject(e);
}
}

async sendPin({ deviceType, devicePath, pin }: HwiSendPinRequest): Promise<HwiSendPinResponse> {
const response: HwiSendPinResponse = await window.ipcRenderer.invoke('/sendpin', {
deviceType,
devicePath,
pin
});
return Promise.resolve(response);
try {
const response: HwiSendPinResponse = await window.ipcRenderer.invoke('/sendpin', {
deviceType,
devicePath,
pin
});
return Promise.resolve(response);
} catch (e) {
console.log('e: ', e);
// @ts-ignore
console.log('e.message: ', e.message);
return Promise.reject(e);
}
}

async estimateFee(): Promise<FeeRates> {
Expand Down
17 changes: 9 additions & 8 deletions apps/frontend/src/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function clone<T>(a: T): T {
export const createAccountId = (config: Omit<OnChainConfig, 'id'>): AccountId => {
const preHashId = `${config.addressType}:${config.quorum.requiredSigners}:${
config.quorum.totalSigners
}:${config.extendedPublicKeys
}:${[...config.extendedPublicKeys]
// sort xpubs alphabetically to catch case where imported in different order
.sort((a, b) => (a.xpub > b.xpub ? -1 : a.xpub < b.xpub ? 1 : 0))
.map((xpub) => xpub.xpub)
Expand Down Expand Up @@ -392,7 +392,7 @@ export const createMultisigConfigFile = (
created_at: Date.now(),
parentFingerprint: device.fingerprint,
network: getUnchainedNetworkFromBjslibNetwork(currentBitcoinNetwork),
bip32Path: getMultisigDeriationPathForNetwork(currentBitcoinNetwork),
bip32Path: extendedPublicKey.bip32Path,
xpub: xpub,
device: {
type: device.type,
Expand All @@ -405,19 +405,20 @@ export const createMultisigConfigFile = (
);

const newAccountWithoutId: Omit<VaultConfig, 'id'> = {
...newAccountInputs,
type: 'onchain',
created_at: Date.now(),
name: newAccountInputs.name,
// name: newAccountInputs.name,
license: {
license: `trial:${currentBlockHeight + 4320}`, // one month free trial (6 * 24 * 30)
signature: ''
},
network: getUnchainedNetworkFromBjslibNetwork(currentBitcoinNetwork),
addressType: newAccountInputs.addressType,
quorum: {
requiredSigners: newAccountInputs.quorum.requiredSigners,
totalSigners: newAccountInputs.extendedPublicKeys.length
},
// addressType: newAccountInputs.addressType,
// quorum: {
// requiredSigners: newAccountInputs.quorum.requiredSigners,
// totalSigners: newAccountInputs.extendedPublicKeys.length
// },
extendedPublicKeys: newKeys
};

Expand Down
27 changes: 25 additions & 2 deletions apps/frontend/src/utils/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import BigNumber from 'bignumber.js';
import coinSelect from 'coinselect';
import coinSelectAll from 'coinselect/split';
import { Buffer } from 'buffer';
import { decode } from 'bs58check';

import { cloneBuffer, bufferToHex } from './other';

Expand All @@ -17,7 +18,8 @@ import {
FeeRates,
ExtendedPublicKey,
LilyOnchainAccount,
Device
Device,
PsbtInput
} from '@lily/types';

import { BasePlatform } from 'src/frontend-middleware';
Expand Down Expand Up @@ -207,8 +209,19 @@ export const createTransaction = async (
psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0); // These are defaults. This line is not needed.

// add global xpubs for security. see https://github.com/bitcoin-core/HWI/issues/671
psbt.updateGlobal({
globalXpub: currentAccount.config.extendedPublicKeys.map((key) => {
return {
extendedPubkey: decode(key.xpub), // have to decode to use 78 bytes pubkey, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/1504
masterFingerprint: Buffer.from(key.parentFingerprint, 'hex'),
path: key.bip32Path
};
})
});

inputs.forEach((input) => {
const currentInput = {
const currentInput: PsbtInput = {
hash: input.txid,
index: input.vout,
sequence: 0xfffffffd, // always enable RBF
Expand All @@ -235,8 +248,18 @@ export const createTransaction = async (
} else if (config.addressType === 'p2sh') {
// @ts-ignore-line
currentInput.redeemScript = getBuffer(input.address.redeem.output);
} else if (config.addressType === 'P2SH-P2WSH') {
currentInput.redeemScript = getBuffer(input.address.redeem.output);
currentInput.witnessScript = getBuffer(input.address.redeem.redeem.output);
// @ts-ignore-line
currentInput.witnessUtxo = {
value: input.value,
// @ts-ignore-line
script: getBuffer(input.address.output)
};
}

// @ts-ignore
psbt.addInput(currentInput);
});

Expand Down
Binary file modified packages/HWIs/HWI_MAC/HWI_MAC
Binary file not shown.
16 changes: 14 additions & 2 deletions packages/shared-server/src/OnchainProviders/Electrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,25 @@ export class ElectrumProvider extends OnchainBaseProvider {
const pos = cycles * BATCH_SIZE;
// derive a batch of receive/change addresses
for (let i = pos; i < pos + BATCH_SIZE; i++) {
const receiveAddress = getAddressFromAccount(account, `m/0/${i}`, this.network);
// we have different postfixes for the derivation path depending if standard or bitgo (https://bitcoin.stackexchange.com/a/105468/102518)
const recieveDerivationPostPath = account.bitgo ? `m/0/0/10/${i}` : `m/0/${i}`;
const receiveAddress = getAddressFromAccount(
account,
recieveDerivationPostPath,
this.network
);
receiveAddress.tags = await getAllLabelsForAddress(db, receiveAddress.address);
receiveAddress.isChange = false;
receiveAddress.isMine = true;
currentReceiveAddressBatch.push(receiveAddress);

const changeAddress = getAddressFromAccount(account, `m/1/${i}`, this.network);
// we have different postfixes for the derivation path depending if standard or bitgo (https://bitcoin.stackexchange.com/a/105468/102518)
const changeDerivationPostPath = account.bitgo ? `m/0/0/11/${i}` : `m/1/${i}`;
const changeAddress = getAddressFromAccount(
account,
changeDerivationPostPath,
this.network
);
changeAddress.tags = await getAllLabelsForAddress(db, changeAddress.address);
changeAddress.isChange = true;
changeAddress.isMine = true;
Expand Down
7 changes: 6 additions & 1 deletion packages/shared-server/src/utils/accountMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,19 @@ const getMultisigAddressFromPubKeys = (
currentBitcoinNetwork: Network
): Address => {
const rawPubkeys = pubkeys.map((publicKey) => publicKey.childPubKey);
rawPubkeys.sort();
if (config.bitgo) {
// if bitgo, skip sorting
} else {
rawPubkeys.sort();
}

const address = generateMultisigFromPublicKeys(
getUnchainedNetworkFromBjslibNetwork(currentBitcoinNetwork),
config.addressType,
config.quorum.requiredSigners,
...rawPubkeys
);

const decoratedAddress = {
...address,
bip32derivation: pubkeys.map((publicKey) => publicKey.bip32derivation)
Expand Down
29 changes: 18 additions & 11 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,17 +383,22 @@ export interface LilyZeroDotOneConfig {
exchanges: any[]; // TODO: change
}

type DeviceType =
| 'coldcard'
| 'trezor'
| 'ledger'
| 'phone'
| 'lily'
| 'cobo'
| 'bitbox02'
| 'unchained'
| 'bitgo'
| 'onramp'
| 'kingdom-trust'
| 'unknown';

export interface Device {
type:
| 'coldcard'
| 'trezor'
| 'ledger'
| 'phone'
| 'lily'
| 'cobo'
| 'bitbox02'
| 'unchained'
| 'unknown';
type: DeviceType;
fingerprint: string;
model: string; // KBC-TODO: get more specific with this
owner?: {
Expand Down Expand Up @@ -468,12 +473,14 @@ export enum AddressType {
P2WSH = 'P2WSH',
P2WPKH = 'P2WPKH',
p2sh = 'p2sh',
multisig = 'multisig'
multisig = 'multisig',
'P2SH-P2WSH' = 'P2SH-P2WSH'
}

export interface OnChainConfig {
id: AccountId;
type: 'onchain';
bitgo?: boolean;
created_at: number;
name: string;
network: 'mainnet' | 'testnet';
Expand Down

0 comments on commit baece25

Please sign in to comment.