-
Notifications
You must be signed in to change notification settings - Fork 279
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(abstract-utxo): add descriptor support for offline vault
TICKET: BTC-1731
- Loading branch information
1 parent
ad33065
commit 7082893
Showing
8 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
modules/abstract-utxo/src/offlineVault/OfflineVaultHalfSigned.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as utxolib from '@bitgo/utxo-lib'; | ||
import { BIP32Interface } from '@bitgo/utxo-lib'; | ||
|
||
import { getNetworkFromChain } from '../names'; | ||
|
||
import { OfflineVaultUnsigned } from './OfflineVaultUnsigned'; | ||
import { DescriptorTransaction, getHalfSignedPsbt } from './descriptor'; | ||
|
||
export type OfflineVaultHalfSigned = { | ||
halfSigned: { txHex: string }; | ||
}; | ||
|
||
function createHalfSignedFromPsbt(psbt: utxolib.Psbt): OfflineVaultHalfSigned { | ||
return { halfSigned: { txHex: psbt.toHex() } }; | ||
} | ||
|
||
export function createHalfSigned(coin: string, prv: string | BIP32Interface, tx: unknown): OfflineVaultHalfSigned { | ||
const network = getNetworkFromChain(coin); | ||
if (typeof prv === 'string') { | ||
prv = utxolib.bip32.fromBase58(prv); | ||
} | ||
if (!OfflineVaultUnsigned.is(tx)) { | ||
throw new Error('unsupported transaction type'); | ||
} | ||
if (DescriptorTransaction.is(tx)) { | ||
return createHalfSignedFromPsbt(getHalfSignedPsbt(tx, prv, network)); | ||
} | ||
throw new Error('unsupported transaction type'); | ||
} |
35 changes: 35 additions & 0 deletions
35
modules/abstract-utxo/src/offlineVault/OfflineVaultUnsigned.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as utxolib from '@bitgo/utxo-lib'; | ||
import { Triple } from '@bitgo/sdk-core'; | ||
import * as t from 'io-ts'; | ||
|
||
export const XPubWithDerivationPath = t.intersection( | ||
[t.type({ xpub: t.string }), t.partial({ derivationPath: t.string })], | ||
'XPubWithDerivationPath' | ||
); | ||
|
||
export type XPubWithDerivationPath = t.TypeOf<typeof XPubWithDerivationPath>; | ||
|
||
/** | ||
* This is the transaction payload that is sent to the offline vault to sign. | ||
*/ | ||
export const OfflineVaultUnsigned = t.type( | ||
{ | ||
xpubsWithDerivationPath: t.type({ | ||
user: XPubWithDerivationPath, | ||
backup: XPubWithDerivationPath, | ||
bitgo: XPubWithDerivationPath, | ||
}), | ||
coinSpecific: t.type({ txHex: t.string }), | ||
}, | ||
'BaseTransaction' | ||
); | ||
|
||
export type OfflineVaultUnsigned = t.TypeOf<typeof OfflineVaultUnsigned>; | ||
|
||
type WithXpub = { xpub: string }; | ||
type NamedKeys = { user: WithXpub; backup: WithXpub; bitgo: WithXpub }; | ||
export function toKeyTriple(xpubs: NamedKeys): Triple<utxolib.BIP32Interface> { | ||
return [xpubs.user.xpub, xpubs.backup.xpub, xpubs.bitgo.xpub].map((xpub) => | ||
utxolib.bip32.fromBase58(xpub) | ||
) as Triple<utxolib.BIP32Interface>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './transaction'; |
43 changes: 43 additions & 0 deletions
43
modules/abstract-utxo/src/offlineVault/descriptor/transaction.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import * as utxolib from '@bitgo/utxo-lib'; | ||
import * as t from 'io-ts'; | ||
|
||
import { NamedDescriptor } from '../../descriptor'; | ||
import { OfflineVaultUnsigned, toKeyTriple } from '../OfflineVaultUnsigned'; | ||
import { | ||
getValidatorOneOfTemplates, | ||
getValidatorSignedByUserKey, | ||
getValidatorSome, | ||
toDescriptorMapValidate, | ||
} from '../../descriptor/validatePolicy'; | ||
import { DescriptorMap } from '../../core/descriptor'; | ||
import { signPsbt } from '../../transaction/descriptor'; | ||
|
||
export const DescriptorTransaction = t.intersection( | ||
[OfflineVaultUnsigned, t.type({ descriptors: t.array(NamedDescriptor) })], | ||
'DescriptorTransaction' | ||
); | ||
|
||
export type DescriptorTransaction = t.TypeOf<typeof DescriptorTransaction>; | ||
|
||
export function getDescriptorsFromDescriptorTransaction(tx: DescriptorTransaction): DescriptorMap { | ||
const { descriptors, xpubsWithDerivationPath } = tx; | ||
const pubkeys = toKeyTriple(xpubsWithDerivationPath); | ||
const policy = getValidatorSome([ | ||
// allow all 2-of-3-ish descriptors where the keys match the wallet keys | ||
getValidatorOneOfTemplates(['Wsh2Of3', 'Wsh2Of3CltvDrop', 'ShWsh2Of3CltvDrop']), | ||
// allow all descriptors signed by the user key | ||
getValidatorSignedByUserKey(), | ||
]); | ||
return toDescriptorMapValidate(descriptors, pubkeys, policy); | ||
} | ||
|
||
export function getHalfSignedPsbt( | ||
tx: DescriptorTransaction, | ||
prv: utxolib.BIP32Interface, | ||
network: utxolib.Network | ||
): utxolib.Psbt { | ||
const psbt = utxolib.bitgo.createPsbtDecode(tx.coinSpecific.txHex, network); | ||
const descriptorMap = getDescriptorsFromDescriptorTransaction(tx); | ||
signPsbt(psbt, descriptorMap, prv, { onUnknownInput: 'throw' }); | ||
return psbt; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * as descriptor from './descriptor'; | ||
export * from './OfflineVaultHalfSigned'; |
73 changes: 73 additions & 0 deletions
73
modules/abstract-utxo/test/offlineVault/fixtures/Wsh2Of3.buildAdmin.custodial.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
{ | ||
"walletKeys": [ | ||
"xprv9zXudaVgkhXsjTeXgpJ3K62R6bZDYQ5L5TVYfhGPLDDLNhfLtCYwHm4R4aMnMMeRpHSiM5Krxxbrux7iz99f7rSmZyddtBogiSch4PRVVXZ", | ||
"xpub6CYwP1gQmPLwzD67x2nBoxiKquYs2CQExaqyQ3KbcntynZCoUGKErxXNg6Mft3x7r5c1xtHTp827ZRQY7dwUa8XYjQ5RCPUwtTPAe6bSv8b", | ||
"xprv9s21ZrQH143K3Kh6W9VDkrpUSDrikEYKbbEKyB5Xn9bJeBPRSRSyWqQ5Fzoujj4eFmRKrxFPipYtfVqyu3aNYH4Lojrdhemi4aUdX8CjD8W" | ||
], | ||
"response": { | ||
"txBase64": "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", | ||
"descriptors": [ | ||
{ | ||
"name": "external", | ||
"value": "wsh(multi(2,xpub6DXG362ab56Awwiznqq3gDy9edPhwroBSgR9U5fztYkKFVzVRjsBqZNtusyuT3U841NogK9ByRFHz6Zhb5jTGWkiJyu3SVBqKiPFzw2GcyV/0/*,xpub6CYwP1gQmPLwzD67x2nBoxiKquYs2CQExaqyQ3KbcntynZCoUGKErxXNg6Mft3x7r5c1xtHTp827ZRQY7dwUa8XYjQ5RCPUwtTPAe6bSv8b/0/*,xpub661MyMwAqRbcFomZcB2E7zmCzFhD9hGAxp9vmZV9LV8HWyiZyxmE4diZ7EREPnU5XdHjx1w9Xp2o7qtP3mGmWM6SWYD1zGcA2VuU6VEjomd/0/*))#gw7wwcku", | ||
"signatures": [], | ||
"lastIndex": 1 | ||
}, | ||
{ | ||
"name": "internal", | ||
"value": "wsh(multi(2,xpub6DXG362ab56Awwiznqq3gDy9edPhwroBSgR9U5fztYkKFVzVRjsBqZNtusyuT3U841NogK9ByRFHz6Zhb5jTGWkiJyu3SVBqKiPFzw2GcyV/1/*,xpub6CYwP1gQmPLwzD67x2nBoxiKquYs2CQExaqyQ3KbcntynZCoUGKErxXNg6Mft3x7r5c1xtHTp827ZRQY7dwUa8XYjQ5RCPUwtTPAe6bSv8b/1/*,xpub661MyMwAqRbcFomZcB2E7zmCzFhD9hGAxp9vmZV9LV8HWyiZyxmE4diZ7EREPnU5XdHjx1w9Xp2o7qtP3mGmWM6SWYD1zGcA2VuU6VEjomd/1/*))#sw3k4yqr", | ||
"signatures": [], | ||
"lastIndex": 0 | ||
} | ||
], | ||
"formatVersion": 1, | ||
"coin": "btc", | ||
"pubs": [ | ||
"xpub6DXG362ab56Awwiznqq3gDy9edPhwroBSgR9U5fztYkKFVzVRjsBqZNtusyuT3U841NogK9ByRFHz6Zhb5jTGWkiJyu3SVBqKiPFzw2GcyV", | ||
"xpub6CYwP1gQmPLwzD67x2nBoxiKquYs2CQExaqyQ3KbcntynZCoUGKErxXNg6Mft3x7r5c1xtHTp827ZRQY7dwUa8XYjQ5RCPUwtTPAe6bSv8b", | ||
"xpub661MyMwAqRbcFomZcB2E7zmCzFhD9hGAxp9vmZV9LV8HWyiZyxmE4diZ7EREPnU5XdHjx1w9Xp2o7qtP3mGmWM6SWYD1zGcA2VuU6VEjomd" | ||
], | ||
"xpubsWithDerivationPath": { | ||
"user": { | ||
"xpub": "xpub6DXG362ab56Awwiznqq3gDy9edPhwroBSgR9U5fztYkKFVzVRjsBqZNtusyuT3U841NogK9ByRFHz6Zhb5jTGWkiJyu3SVBqKiPFzw2GcyV", | ||
"derivedFromParentWithSeed": "143700591154482" | ||
}, | ||
"backup": { | ||
"xpub": "xpub6CYwP1gQmPLwzD67x2nBoxiKquYs2CQExaqyQ3KbcntynZCoUGKErxXNg6Mft3x7r5c1xtHTp827ZRQY7dwUa8XYjQ5RCPUwtTPAe6bSv8b", | ||
"derivedFromParentWithSeed": "210593312354420" | ||
}, | ||
"bitgo": { | ||
"xpub": "xpub661MyMwAqRbcFomZcB2E7zmCzFhD9hGAxp9vmZV9LV8HWyiZyxmE4diZ7EREPnU5XdHjx1w9Xp2o7qtP3mGmWM6SWYD1zGcA2VuU6VEjomd" | ||
} | ||
}, | ||
"walletId": "6788d3cf8eaf7aa1db27d4575218103c", | ||
"walletLabel": "UtxoWalletClient", | ||
"amount": "199995579", | ||
"address": "bc1q9cp8uk0qpal0h4hzrp2gvjy5hmwune773pnp2qtfwap22ryrnuxsehzwgh", | ||
"pendingApprovalId": "6788d3cf8eaf7aa1db27d4f563e0e411", | ||
"creatorId": "6788d3ce8eaf7aa1db27d18bc5baa93d", | ||
"creatorEmail": "testuser-1737020360683@example.com", | ||
"createDate": "2025-01-16T09:39:27.667Z", | ||
"enterpriseId": "6788d3ce8eaf7aa1db27d2a664daec54", | ||
"enterpriseName": "Foo Enterprises", | ||
"enterpriseFeatureFlags": [ | ||
"enableMMI" | ||
], | ||
"videoId": { | ||
"approver": "", | ||
"date": "", | ||
"link": "", | ||
"exception": "", | ||
"waived": true | ||
}, | ||
"coinSpecific": { | ||
"txHex": "70736274ff0100dd020000000201010101010101010101010101010101010101010101010101010101010101010000000000fdffffff01010101010101010101010101010101010101010101010101010101010101010100000000fdffffff0340420f00000000002200202e027e59e00f7efbd6e21854864894beddc9e7de88661501697742a50c839f0d80841e00000000002200202e027e59e00f7efbd6e21854864894beddc9e7de88661501697742a50c839f0dfbe9bd0b0000000022002072cba07a4f95a936089851622eea3b63232de777a4a627b72a3c4958f06f33a1000000000001012b00e1f505000000002200202e027e59e00f7efbd6e21854864894beddc9e7de88661501697742a50c839f0d010569522102f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e2102d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df21023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b53ae2206023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b0c59e62a710000000000000000220602d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df0c1508470b0000000000000000220602f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e0c2a1493e800000000000000000001012b00e1f505000000002200202e027e59e00f7efbd6e21854864894beddc9e7de88661501697742a50c839f0d010569522102f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e2102d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df21023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b53ae2206023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b0c59e62a710000000000000000220602d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df0c1508470b0000000000000000220602f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e0c2a1493e8000000000000000000010169522102f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e2102d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df21023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b53ae2202023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b0c59e62a710000000000000000220202d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df0c1508470b0000000000000000220202f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e0c2a1493e8000000000000000000010169522102f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e2102d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df21023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b53ae2202023bdb693c4bf735870b5cbe279bf3e86b11dfbbdf2fd542945bbcef8d96dd177b0c59e62a710000000000000000220202d212e44dac0b4586cab689c566963ecc994d300508939180cbb707e13a8c32df0c1508470b0000000000000000220202f70923b37875101b1d49b0849befef4b7bb9f90f3f571f7a3187f98eb45a061e0c2a1493e800000000000000000001016952210344a042f622eb71a19f80ef8bfb864f2068ed2d0643d165ba6ce674134d9cb9462103fd3ced6d7adb7b6efb9e07a8e60425f73fb882a6a2eae41bf303aa110c0e85712102b4b094b52276da9686a9a037964d9b637bba5c2b3f2f7b8521fba94851029a1653ae220202b4b094b52276da9686a9a037964d9b637bba5c2b3f2f7b8521fba94851029a160c59e62a71000000000100000022020344a042f622eb71a19f80ef8bfb864f2068ed2d0643d165ba6ce674134d9cb9460c2a1493e80000000001000000220203fd3ced6d7adb7b6efb9e07a8e60425f73fb882a6a2eae41bf303aa110c0e85710c1508470b000000000100000000", | ||
"inputIds": [ | ||
"0101010101010101010101010101010101010101010101010101010101010101:0", | ||
"0101010101010101010101010101010101010101010101010101010101010101:1" | ||
] | ||
}, | ||
"recipientsInfo": [], | ||
"keyDerivationPath": "143700591154482" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import * as fs from 'fs'; | ||
import assert from 'assert'; | ||
import crypto from 'crypto'; | ||
|
||
import * as t from 'io-ts'; | ||
import * as utxolib from '@bitgo/utxo-lib'; | ||
|
||
import { createHalfSigned } from '../../src/offlineVault'; | ||
import { DescriptorTransaction } from '../../src/offlineVault/descriptor'; | ||
|
||
function getFixturesNames(): string[] { | ||
// I'm using sync here because mocha cannot do async setup | ||
// eslint-disable-next-line no-sync | ||
return fs.readdirSync(__dirname + '/fixtures').filter((f) => f.endsWith('.json')); | ||
} | ||
|
||
const Fixture = t.type({ | ||
walletKeys: t.array(t.string), | ||
response: t.unknown, | ||
}); | ||
|
||
type Fixture = t.TypeOf<typeof Fixture>; | ||
|
||
async function readFixture(name: string): Promise<Fixture> { | ||
const data = JSON.parse(await fs.promises.readFile(__dirname + '/fixtures/' + name, 'utf-8')); | ||
if (!Fixture.is(data)) { | ||
throw new Error(`Invalid fixture ${name}`); | ||
} | ||
return data; | ||
} | ||
|
||
function withRotatedXpubs(tx: DescriptorTransaction): DescriptorTransaction { | ||
const { user, backup, bitgo } = tx.xpubsWithDerivationPath; | ||
return { | ||
...tx, | ||
xpubsWithDerivationPath: { | ||
user: bitgo, | ||
backup: user, | ||
bitgo: backup, | ||
}, | ||
}; | ||
} | ||
|
||
function withRandomXpubs(tx: DescriptorTransaction) { | ||
function randomXpub() { | ||
const bytes = crypto.getRandomValues(new Uint8Array(32)); | ||
return utxolib.bip32.fromSeed(Buffer.from(bytes)).neutered().toBase58(); | ||
} | ||
return { | ||
...tx, | ||
xpubsWithDerivationPath: { | ||
user: randomXpub(), | ||
backup: randomXpub(), | ||
bitgo: randomXpub(), | ||
}, | ||
}; | ||
} | ||
|
||
function withoutDescriptors(tx: DescriptorTransaction): DescriptorTransaction { | ||
return { | ||
...tx, | ||
descriptors: [], | ||
}; | ||
} | ||
|
||
describe('OfflineVaultHalfSigned', function () { | ||
for (const fixtureName of getFixturesNames()) { | ||
it(`can sign fixture ${fixtureName}`, async function () { | ||
const { walletKeys, response } = await readFixture(fixtureName); | ||
const prv = utxolib.bip32.fromBase58(walletKeys[0]); | ||
createHalfSigned('btc', prv, response); | ||
|
||
assert(DescriptorTransaction.is(response)); | ||
const mutations = [withRotatedXpubs(response), withRandomXpubs(response), withoutDescriptors(response)]; | ||
for (const mutation of mutations) { | ||
assert.throws(() => createHalfSigned('btc', prv, mutation)); | ||
} | ||
}); | ||
} | ||
}); |