Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(utxo-bin): add bip32 subcommand #4979

Merged
merged 3 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions modules/utxo-bin/bin/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#!/usr/bin/env node
import * as yargs from 'yargs';

import { cmdParseTx, cmdParseAddress, cmdParseScript, cmdGenerateAddress, cmdParseXpub } from '../src/commands';
import { cmdParseTx, cmdParseScript, cmdBip32, cmdGenerateAddress, cmdParseAddress } from '../src/commands';

yargs
.command(cmdParseTx)
.command(cmdParseAddress)
.command(cmdParseScript)
.command(cmdGenerateAddress)
.command(cmdParseXpub)
.command(cmdBip32)
.strict()
.demandCommand()
.help()
.parse();
13 changes: 13 additions & 0 deletions modules/utxo-bin/src/args/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type FormatTreeOrJson = 'tree' | 'json';

export const formatTreeOrJson = {
type: 'string',
choices: ['tree', 'json'] as const,
default: 'tree',
coerce(arg: string): 'tree' | 'json' {
if (arg !== 'tree' && arg !== 'json') {
throw new Error(`invalid format ${arg}`);
}
return arg;
},
} as const;
1 change: 1 addition & 0 deletions modules/utxo-bin/src/args/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './parseString';
export * from './walletKeys';
export * from './parseNetwork';
export * from './format';
17 changes: 9 additions & 8 deletions modules/utxo-bin/src/bip32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import * as utxolib from '@bitgo/utxo-lib';
import { Parser, ParserNode } from './Parser';
import { parseUnknown } from './parseUnknown';

export function parseXpub(xpub: string, params: { derive?: string }): ParserNode {
if (!xpub.startsWith('xpub')) {
throw new Error('expected xpub');
}
export function parseBip32(bip32Key: string, params: { derive?: string }): ParserNode {
const parser = new Parser();
let xpubObj = utxolib.bip32.fromBase58(xpub);
let bip32 = utxolib.bip32.fromBase58(bip32Key);
if (params.derive) {
xpubObj = xpubObj.derivePath(params.derive);
bip32 = bip32.derivePath(params.derive);
}
const node = parseUnknown(parser, 'xpub', xpubObj, {
const label = bip32.isNeutered() ? 'xpub' : 'xprv';
const node = parseUnknown(parser, label, bip32, {
omit: ['network', '__Q', '__D', '__DEPTH', '__INDEX', '__PARENT_FINGERPRINT'],
});
node.value = xpubObj.toBase58();
if (!bip32.isNeutered()) {
node.nodes?.unshift(parser.node('xpub', bip32.neutered().toBase58()));
}
node.value = bip32.toBase58();
return node;
}
60 changes: 60 additions & 0 deletions modules/utxo-bin/src/commands/cmdBip32.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as crypto from 'crypto';

import * as yargs from 'yargs';
import { CommandModule } from 'yargs';
import * as utxolib from '@bitgo/utxo-lib';

import { parseBip32 } from '../bip32';
import { formatTreeOrJson, FormatTreeOrJson } from '../args';

import { formatString } from './formatString';

type ArgsBip32Generate = {
format: FormatTreeOrJson;
bip32Key: string;
derive?: string;
all: boolean;
};

export const cmdBip32Parse: CommandModule<unknown, ArgsBip32Generate> = {
command: 'parse [xpub|xprv]',
describe: 'show xpub info',
builder(b: yargs.Argv<unknown>): yargs.Argv<ArgsBip32Generate> {
return b
.options({ format: formatTreeOrJson })
.positional('bip32Key', { type: 'string', demandOption: true })
.option('all', { type: 'boolean', default: false })
.option('derive', { type: 'string', description: 'show xpub derived with path' });
},
handler(argv): void {
console.log(formatString(parseBip32(argv.bip32Key, { derive: argv.derive }), argv));
},
};

type GenerateBip32Args = {
seed: string;
};

export const cmdBip32Generate: CommandModule<unknown, GenerateBip32Args> = {
command: 'generateFromSeed',
describe: 'generate keypair from seed - do not use for real keys!',
builder(b) {
return b.option('seed', { type: 'string', demandOption: true, default: 'setec astronomy' });
},
handler(argv) {
const key = utxolib.bip32.fromSeed(crypto.createHash('sha256').update(argv.seed).digest());
console.log(key.toBase58());
console.log(key.neutered().toBase58());
},
};

export const cmdBip32: CommandModule<unknown, unknown> = {
command: 'bip32 <command>',
describe: 'bip32 commands',
builder(b: yargs.Argv<unknown>): yargs.Argv<unknown> {
return b.strict().command(cmdBip32Parse).command(cmdBip32Generate).demandCommand();
},
handler(): void {
// do nothing
},
};
7 changes: 3 additions & 4 deletions modules/utxo-bin/src/commands/cmdParseAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import * as utxolib from '@bitgo/utxo-lib';
import * as yargs from 'yargs';

import { AddressParser } from '../AddressParser';
import { getNetworkOptions } from '../args';
import { formatTreeOrJson, getNetworkOptions, FormatTreeOrJson } from '../args';

import { OutputFormat } from './cmdParseTx';
import { formatString } from './formatString';

export type ArgsParseAddress = {
network?: utxolib.Network;
all: boolean;
format: OutputFormat;
format: FormatTreeOrJson;
convert: boolean;
address: string;
};
Expand All @@ -26,7 +25,7 @@ export const cmdParseAddress = {
builder(b: yargs.Argv<unknown>): yargs.Argv<ArgsParseAddress> {
return b
.options(getNetworkOptions())
.option('format', { choices: ['tree', 'json'], default: 'tree' } as const)
.option('format', formatTreeOrJson)
.option('convert', { type: 'boolean', default: false })
.option('all', { type: 'boolean', default: false })
.positional('address', { type: 'string', demandOption: true });
Expand Down
7 changes: 3 additions & 4 deletions modules/utxo-bin/src/commands/cmdParseScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import * as utxolib from '@bitgo/utxo-lib';
import * as yargs from 'yargs';

import { ScriptParser } from '../ScriptParser';
import { getNetworkOptions, stringToBuffer } from '../args';
import { formatTreeOrJson, getNetworkOptions, FormatTreeOrJson, stringToBuffer } from '../args';

import { OutputFormat } from './cmdParseTx';
import { formatString } from './formatString';

export type ArgsParseScript = {
network?: utxolib.Network;
format: OutputFormat;
format: FormatTreeOrJson;
all: boolean;
script: string;
};
Expand All @@ -24,7 +23,7 @@ export const cmdParseScript = {
builder(b: yargs.Argv<unknown>): yargs.Argv<ArgsParseScript> {
return b
.options(getNetworkOptions())
.option('format', { choices: ['tree', 'json'], default: 'tree' } as const)
.option('format', formatTreeOrJson)
.option('all', { type: 'boolean', default: false })
.positional('script', { type: 'string', demandOption: true });
},
Expand Down
15 changes: 11 additions & 4 deletions modules/utxo-bin/src/commands/cmdParseTx.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import * as utxolib from '@bitgo/utxo-lib';
import * as yargs from 'yargs';

import { argToString, getNetworkOptionsDemand, ReadStringOptions, readStringOptions, stringToBuffer } from '../args';
import {
argToString,
formatTreeOrJson,
getNetworkOptionsDemand,
FormatTreeOrJson,
ReadStringOptions,
readStringOptions,
stringToBuffer,
} from '../args';
import { TxParser, TxParserArgs } from '../TxParser';
import {
fetchOutputSpends,
Expand All @@ -17,15 +25,14 @@ import { Parser } from '../Parser';

import { formatString } from './formatString';

export type OutputFormat = 'tree' | 'json';
export type ArgsParseTransaction = ReadStringOptions & {
network: utxolib.Network;
txid?: string;
blockHeight?: number;
txIndex?: number;
all: boolean;
cache: boolean;
format: OutputFormat;
format: FormatTreeOrJson;
fetchAll: boolean;
fetchStatus: boolean;
fetchInputs: boolean;
Expand Down Expand Up @@ -95,7 +102,7 @@ export const cmdParseTx = {
default: false,
description: 'use local cache for http responses',
})
.option('format', { choices: ['tree', 'json'], default: 'tree' } as const)
.option('format', formatTreeOrJson)
.option('parseError', { choices: ['continue', 'throw'], default: 'continue' } as const);
},

Expand Down
32 changes: 0 additions & 32 deletions modules/utxo-bin/src/commands/cmdParseXpub.ts

This file was deleted.

5 changes: 2 additions & 3 deletions modules/utxo-bin/src/commands/formatString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import * as yargs from 'yargs';

import { ParserNode } from '../Parser';
import { formatTree } from '../format';

import { OutputFormat } from './cmdParseTx';
import { FormatTreeOrJson } from '../args';

export type FormatStringArgs = {
format: OutputFormat;
format: FormatTreeOrJson;
all: boolean;
};

Expand Down
2 changes: 1 addition & 1 deletion modules/utxo-bin/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './cmdParseTx';
export * from './cmdParseAddress';
export * from './cmdParseScript';
export * from './cmdGenerateAddress';
export * from './cmdParseXpub';
export * from './cmdBip32';
19 changes: 10 additions & 9 deletions modules/utxo-bin/test/bip32.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import * as assert from 'assert';

import { parseXpub } from '../src/bip32';
import { parseBip32 } from '../src/bip32';

import { formatTreeNoColor, getFixtureString } from './fixtures';
import { getKey } from './bip32.util';

function runTest(xpub: string, args: { derive?: string }) {
describe('parse xpub', function () {
it('parses xpub', async function () {
const formatted = formatTreeNoColor(parseXpub(xpub, args), { showAll: true });
const filename = [xpub];
function runTest(bip32Key: string, args: { derive?: string }) {
describe(`parse bip32 ${JSON.stringify(args)}`, function () {
it('has expected output', async function () {
const formatted = formatTreeNoColor(parseBip32(bip32Key, args), { showAll: true });
const filename = [bip32Key];
if (args.derive) {
filename.push(args.derive.replace(/\//g, '_'));
}
assert.strictEqual(await getFixtureString(`test/fixtures/xpub/${filename.join('_')}.txt`, formatted), formatted);
assert.strictEqual(await getFixtureString(`test/fixtures/bip32/${filename.join('_')}.txt`, formatted), formatted);
});
});
}

runTest(getKey('parseXpub').neutered().toBase58(), {});
runTest(getKey('parseXpub').neutered().toBase58(), { derive: 'm/0/0' });
runTest(getKey('bip32').toBase58(), {});
runTest(getKey('bip32').toBase58(), { derive: 'm/0/0' });
runTest(getKey('bip32').neutered().toBase58(), { derive: 'm/0/0' });
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
xprv: xprv9s21ZrQH143K2Fnv8HGStbw1ckdpUZkV5DLHGmw82uiQo1rY59ZiZYhpG2u5HGENgJ5eykfEBC9nuFibB2cjiKXYUwrZ7q8aMRbpWA7N2Pt
├── xpub: xpub661MyMwAqRbcEjsPEJoTFjskAnUJt2ULSSFt5ALjbFFPfpBgcgsy7M2J7Kz66TNWwfYUBSKbhqNp7YJTAeVRfQMkjFjyTw8GzJiNaPQifio
├── lowR: false
├── chainCode: 13cd969b6f5948b45fe7a5e3e22e9d94830c82e9c2b61448af16565131736757 (32 bytes)
├── depth: 0
├── index: 0
├── parentFingerprint: 0
├── identifier: c3b7cdefd973965dd7aef07784908ed11a3e13fb (20 bytes)
├── fingerprint: c3b7cdef (4 bytes)
├── compressed: true
├── publicKey: 03faab4c92cfdb174adb4df27dddbd984dcec499d57b308a6ae343876d8015d0da (33 bytes)
└── privateKey: ebe706fb107727a8af3a75ef3c96672c9ddf4ccd4377b047df7ec43a5020c1ab (32 bytes)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
xprv: xprv9wLeZuTuzBVPggSspWwPzWp9WDL2NTDx5KhyfS3MNGdMpoYME6dZzXVukBSj1sWAJttfUoXck2GrnFHV2sUpmcRiFv26YGSe373QvEaMKpc
├── xpub: xpub6AKzyQzopZ3guAXLvYUQMekt4FAWmuwoSYdaTpSxvcALhbsVmdwpYKpPbST69B5kuSSn5LJFoGSmkcohTCc2Q5kjfa5vcuJaSVEqBY7B2Qk
├── lowR: false
├── chainCode: 4161a1f543ed7c95cc138bd8bfca549a9ba304349e926c6a0bc612a0c5a4e293 (32 bytes)
├── depth: 2
├── index: 0
├── parentFingerprint: 1273784303
├── identifier: 7f8e7c282a46dbeb6b2634faea4d204eaad4eb85 (20 bytes)
├── fingerprint: 7f8e7c28 (4 bytes)
├── compressed: true
├── publicKey: 0224051902c8f2ccd8cf2f27bc5b25dfa12f84573b994007a0943acdd764d873f3 (33 bytes)
└── privateKey: 27340d788194503cf1fd5a1b453e3400537f0c26d40f317211a36d02435584f4 (32 bytes)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
xpub: xpub6AKzyQzopZ3guAXLvYUQMekt4FAWmuwoSYdaTpSxvcALhbsVmdwpYKpPbST69B5kuSSn5LJFoGSmkcohTCc2Q5kjfa5vcuJaSVEqBY7B2Qk
├── lowR: false
├── chainCode: 4161a1f543ed7c95cc138bd8bfca549a9ba304349e926c6a0bc612a0c5a4e293 (32 bytes)
├── depth: 2
├── index: 0
├── parentFingerprint: 1273784303
├── identifier: 7f8e7c282a46dbeb6b2634faea4d204eaad4eb85 (20 bytes)
├── fingerprint: 7f8e7c28 (4 bytes)
├── compressed: true
├── publicKey: 0224051902c8f2ccd8cf2f27bc5b25dfa12f84573b994007a0943acdd764d873f3 (33 bytes)
└── privateKey: undefined

This file was deleted.

This file was deleted.

Loading