Skip to content

Commit

Permalink
Support --passphrase with wallet-init (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
wizz-wallet-dev authored Nov 10, 2024
1 parent 2506c56 commit 2167fd5
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 56 deletions.
24 changes: 19 additions & 5 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { ElectrumApi } from './api/electrum-api';
import { validateCliInputs } from './utils/validate-cli-inputs';
import { IValidatedWalletInfo, IWalletRecord, validateWalletStorage } from './utils/validate-wallet-storage';
import * as qrcode from 'qrcode-terminal';
import { detectAddressTypeToScripthash, detectAddressTypeToScripthash2, detectScriptToAddressType, performAddressAliasReplacement } from './utils/address-helpers';
import {
defaultDerivedPath,
detectAddressTypeToScripthash,
detectAddressTypeToScripthash2,
detectScriptToAddressType,
performAddressAliasReplacement
} from './utils/address-helpers';
import { AtomicalsGetFetchType } from './commands/command.interface';
import { fileReader, jsonFileReader, jsonFileWriter } from './utils/file-utils';
import * as cbor from 'borc';
Expand Down Expand Up @@ -233,10 +239,12 @@ program.command('wallet-create')
program.command('wallet-decode')
.description('Decode secret mnemonic phrase to display derive address and key at provided path')
.argument('<phrase>', 'string')
.option('-p, --path <string>', 'Derivation path to use', `m/44'/0'/0'/0/0`)
.option('-p, --path <string>', 'Derivation path to use', defaultDerivedPath)
.option('--passphrase <string>', 'Passphrase for the wallet')
.action(async (phrase, options) => {
let path = options.path;
const result = await Atomicals.walletPhraseDecode(phrase, path);
let passphrase = options.passphrase;
const result = await Atomicals.walletPhraseDecode(phrase, path, passphrase);
console.log('Provided mnemonic phrase:');
console.log(`phrase: ${result.data.phrase}`);
console.log(`Requested Derivation Path: ${path}`);
Expand All @@ -250,11 +258,17 @@ program.command('wallet-decode')
program.command('wallet-init')
.description('Initializes a new wallet at wallet.json')
.option('--phrase <string>', 'Provide a wallet phrase')
.option('--path <string>', 'Provide a path base', `m/86'/0'/0'`)
.option('--path <string>', 'Provide a path base', defaultDerivedPath.substring(0, 11))
.option('--passphrase <string>', 'Provide a passphrase for the wallet')
.option('--n <number>', 'Provider number of alias')
.action(async (options) => {
try {
const result = await Atomicals.walletInit(options.phrase, options.path, options.n ? parseInt(options.n, 10) : undefined);
const result = await Atomicals.walletInit(
options.phrase,
options.path,
options.passphrase,
options.n ? parseInt(options.n, 10) : undefined
);
console.log('Wallet created at wallet.json');
console.log(`phrase: ${result.data.phrase}`);
console.log(`Primary address (P2TR): ${result.data.primary.address}`);
Expand Down
20 changes: 17 additions & 3 deletions lib/commands/wallet-init-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,35 @@ import * as fs from 'fs';
const walletPath = walletPathResolver();

export class WalletInitCommand implements CommandInterface {
constructor(private phrase: string | undefined, private path: string, private n?: number) {

constructor(
private phrase: string | undefined,
private path: string,
private passphrase?: string,
private n?: number
) {
}

async run(): Promise<CommandResultInterface> {
if (await this.walletExists()) {
throw "wallet.json exists, please remove it first to initialize another wallet. You may also use 'wallet-create' command to generate a new wallet."
}

const { wallet, imported } = await createPrimaryAndFundingImportedKeyPairs(this.phrase, this.path, this.n);
const {
wallet,
imported
} = await createPrimaryAndFundingImportedKeyPairs(
this.phrase,
this.path,
this.passphrase,
this.n
);
const walletDir = `wallets/`;
if (!fs.existsSync(walletDir)) {
fs.mkdirSync(walletDir);
}
const created = {
phrase: wallet.phrase,
passphrase: wallet.passphrase,
primary: {
address: wallet.primary.address,
path: wallet.primary.path,
Expand Down
6 changes: 3 additions & 3 deletions lib/commands/wallet-phrase-decode-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { CommandInterface } from "./command.interface";
import { decodeMnemonicPhrase } from "../utils/decode-mnemonic-phrase";

export class WalletPhraseDecodeCommand implements CommandInterface {
constructor(private phrase, private path) {
constructor(private phrase: string, private path: string, private passphrase?: string) {
}
async run(): Promise<CommandResultInterface> {
const wallet = await decodeMnemonicPhrase(this.phrase, this.path);
const wallet = await decodeMnemonicPhrase(this.phrase, this.path, this.passphrase);
return {
success: true,
data: wallet
}
}
}
}
10 changes: 5 additions & 5 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { APIInterface, BaseRequestOptions } from "./interfaces/api.interface";
const bitcoin = require('bitcoinjs-lib');
import * as ecc from 'tiny-secp256k1';
bitcoin.initEccLib(ecc);
import * as cbor from 'borc';

export { ElectrumApiMock } from "./api/electrum-api-mock";
import { ConfigurationInterface } from "./interfaces/configuration.interface";
import { ElectrumApiInterface } from "./api/electrum-api.interface";
Expand Down Expand Up @@ -248,9 +248,9 @@ export class Atomicals implements APIInterface {
}
}

static async walletPhraseDecode(phrase: string, path: string): Promise<any> {
static async walletPhraseDecode(phrase: string, path: string, passphrase?: string): Promise<any> {
try {
const command: CommandInterface = new WalletPhraseDecodeCommand(phrase, path);
const command: CommandInterface = new WalletPhraseDecodeCommand(phrase, path, passphrase);
return command.run();
} catch (error: any) {
return {
Expand All @@ -261,9 +261,9 @@ export class Atomicals implements APIInterface {
}
}

static async walletInit(phrase: string | undefined, path: string, n?: number): Promise<any> {
static async walletInit(phrase: string | undefined, path: string, passphrase?: string, n?: number): Promise<any> {
try {
const command: CommandInterface = new WalletInitCommand(phrase, path, n);
const command: CommandInterface = new WalletInitCommand(phrase, path, passphrase, n);
return command.run();
} catch (error: any) {
return {
Expand Down
6 changes: 4 additions & 2 deletions lib/utils/address-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { toXOnly } from "./create-key-pair";
import { Network } from "bitcoinjs-lib";
dotenv.config();

export const defaultDerivedPath = `m/86'/0'/0'/0/0`;

function convertAddressToScripthash(address, network) {
const output = bitcoin.address.toOutputScript(address, network);
return {
Expand Down Expand Up @@ -103,8 +105,8 @@ export function performAddressAliasReplacement(walletInfo: IValidatedWalletInfo,
/**
* Whether the atomical for the mint is owned by the provided wallet or not
* @param ownerRecord The proposed wallet that owns the atomical
* @param atomical
* @returns
* @param atomical
* @returns
*/
export function IsAtomicalOwnedByWalletRecord(address: string, atomical: AtomicalStatus): IInputUtxoPartial | null {
if (!(atomical.location_info_obj as any)) {
Expand Down
8 changes: 6 additions & 2 deletions lib/utils/address-keypair-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ export interface ExtendTaprootAddressScriptKeyPairInfo {
path: string;
}

export const getExtendTaprootAddressKeypairPath = async (phrase: string, path: string): Promise<ExtendTaprootAddressScriptKeyPairInfo> => {
const seed = await bip39.mnemonicToSeed(phrase);
export const getExtendTaprootAddressKeypairPath = async (
phrase: string,
path: string,
passphrase?: string,
): Promise<ExtendTaprootAddressScriptKeyPairInfo> => {
const seed = await bip39.mnemonicToSeed(phrase, passphrase);
const rootKey = bip32.fromSeed(seed);
const childNode = rootKey.derivePath(path);
const childNodeXOnlyPubkey = childNode.publicKey.slice(1, 33);
Expand Down
49 changes: 31 additions & 18 deletions lib/utils/create-key-pair.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@

const bitcoin = require('bitcoinjs-lib');
import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { createMnemonicPhrase } from './create-mnemonic-phrase';
bitcoin.initEccLib(ecc);

import ECPairFactory from 'ecpair';
import { defaultDerivedPath } from './address-helpers';
import { createMnemonicPhrase } from './create-mnemonic-phrase';

const ECPair = ECPairFactory(ecc);
import BIP32Factory from 'bip32';
import { NETWORK } from '../commands/command-helpers';
Expand All @@ -24,12 +25,16 @@ export interface KeyPair {
privateKey?: string
}

export const createKeyPair = async (phrase: string = '', path = `m/44'/0'/0'/0/0`) : Promise<KeyPair> => {
export const createKeyPair = async (
phrase: string = '',
path = defaultDerivedPath,
passphrase: string = ''
) : Promise<KeyPair> => {
if (!phrase || phrase === '') {
const phraseResult = await createMnemonicPhrase();
const phraseResult = createMnemonicPhrase();
phrase = phraseResult.phrase;
}
const seed = await bip39.mnemonicToSeed(phrase);
const seed = await bip39.mnemonicToSeed(phrase, passphrase);
const rootKey = bip32.fromSeed(seed);
const childNodePrimary = rootKey.derivePath(path);
// const p2pkh = bitcoin.payments.p2pkh({ pubkey: childNodePrimary.publicKey });
Expand Down Expand Up @@ -74,37 +79,45 @@ export interface WalletRequestDefinition {
path?: string | undefined
}

export const createPrimaryAndFundingImportedKeyPairs = async (phrase?: string | undefined, path?: string | undefined, n?: number) => {
let phraseResult: any = phrase;
if (!phraseResult) {
phraseResult = await createMnemonicPhrase();
phraseResult = phraseResult.phrase;
export const createPrimaryAndFundingImportedKeyPairs = async (
phrase?: string | undefined,
path?: string | undefined,
passphrase?: string | undefined,
n?: number
) => {
if (!phrase) {
phrase = createMnemonicPhrase().phrase;
}
let pathUsed = `m/44'/0'/0'`;
let pathUsed = defaultDerivedPath.substring(0, 11);
if (path) {
pathUsed = path;
}
const imported = {}

if (n) {
for (let i = 2; i < n + 2; i++) {
imported[i+''] = await createKeyPair(phraseResult, `${pathUsed}/0/` + i)
imported[i+''] = await createKeyPair(phrase, `${pathUsed}/0/` + i, passphrase)
}
}
return {
wallet: {
phrase: phraseResult,
primary: await createKeyPair(phraseResult, `${pathUsed}/0/0`),
funding: await createKeyPair(phraseResult, `${pathUsed}/1/0`)
phrase,
passphrase,
primary: await createKeyPair(phrase, `${pathUsed}/0/0`, passphrase),
funding: await createKeyPair(phrase, `${pathUsed}/1/0`, passphrase)
},
imported
}
}

export const createNKeyPairs = async (phrase, n = 1) => {
export const createNKeyPairs = async (
phrase: string | undefined,
passphrase: string | undefined,
n = 1
) => {
const keypairs: any = [];
for (let i = 0; i < n; i++) {
keypairs.push(await createKeyPair(phrase, `m/44'/0'/0'/0/${i}`));
keypairs.push(await createKeyPair(phrase, `${defaultDerivedPath.substring(0, 13)}/${i}`, passphrase));
}
return {
phrase,
Expand Down
6 changes: 3 additions & 3 deletions lib/utils/decode-mnemonic-phrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ const toXOnly = (publicKey) => {
}
const bip39 = require('bip39');

export const decodeMnemonicPhrase = async (phrase: string, path: string) => {
export const decodeMnemonicPhrase = async (phrase: string, path: string, passphrase?: string) => {
if (!bip39.validateMnemonic(phrase)) {
throw new Error("Invalid mnemonic phrase provided!");
}
const seed = await bip39.mnemonicToSeed(phrase);
const seed = await bip39.mnemonicToSeed(phrase, passphrase);
const rootKey = bip32.fromSeed(seed);
const childNode = rootKey.derivePath(path);
// const { address } = bitcoin.payments.p2pkh({ pubkey: childNode.publicKey });
Expand Down Expand Up @@ -46,4 +46,4 @@ export const decodeMnemonicPhrase = async (phrase: string, path: string) => {
privateKey: childNode.privateKey?.toString('hex'),
}

}
}
18 changes: 3 additions & 15 deletions lib/utils/validate-wallet-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,6 @@ export const validateWalletStorage = async (): Promise<IValidatedWalletInfo> =>
throw new Error(`Wallet needs a funding address`);
}

// Validate paths
/*if (wallet.primary.path !== `m/44'/0'/0'/0/0`) {
console.log(`Primary path must be m/44'/0'/0'/0/0`);
throw new Error(`Primary path must be m/44'/0'/0'/0/0`);
}
if (wallet.funding.path !== `m/44'/0'/0'/1/0`) {
console.log(`Funding path must be m/44'/0'/0'/1/0`);
throw new Error(`Funding path must be m/44'/0'/0'/1/0`);
}*/

// Validate WIF
if (!wallet.primary.WIF) {
console.log(`Primary WIF not set`);
Expand All @@ -83,9 +72,9 @@ export const validateWalletStorage = async (): Promise<IValidatedWalletInfo> =>
throw new Error(`Funding address not set`);
}

const seed = await bip39.mnemonicToSeed(wallet.phrase);
const seed = await bip39.mnemonicToSeed(wallet.phrase, wallet.passphrase);
const rootKey = bip32.fromSeed(seed);
const derivePathPrimary = wallet.primary.path; //`m/44'/0'/0'/0/0`;
const derivePathPrimary = wallet.primary.path;

const childNodePrimary = rootKey.derivePath(derivePathPrimary);

Expand All @@ -97,7 +86,7 @@ export const validateWalletStorage = async (): Promise<IValidatedWalletInfo> =>
if (!p2trPrimary.address || !p2trPrimary.output) {
throw "error creating p2tr primary"
}
const derivePathFunding = wallet.funding.path; //`m/44'/0'/0'/1/0`;
const derivePathFunding = wallet.funding.path;
const childNodeFunding = rootKey.derivePath(derivePathFunding);
const childNodeXOnlyPubkeyFunding = toXOnly(childNodeFunding.publicKey);
const p2trFunding = bitcoin.payments.p2tr({
Expand All @@ -107,7 +96,6 @@ export const validateWalletStorage = async (): Promise<IValidatedWalletInfo> =>
if (!p2trFunding.address || !p2trFunding.output) {
throw "error creating p2tr funding"
}
// const derivePathFunding = `m/44'/0'/0'/1/0`;
//const childNodeFunding = rootKey.derivePath(derivePathFunding);
// const { address } = bitcoin.payments.p2pkh({ pubkey: childNode.publicKey });
// const wif = childNodePrimary.toWIF();
Expand Down

0 comments on commit 2167fd5

Please sign in to comment.