Skip to content

Commit

Permalink
fix: update make_keychain command
Browse files Browse the repository at this point in the history
  • Loading branch information
janniks committed Jul 15, 2022
1 parent 5ac9385 commit 62355bc
Show file tree
Hide file tree
Showing 14 changed files with 339 additions and 276 deletions.
338 changes: 186 additions & 152 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
"test:watch": "jest --watch --coverage=false"
},
"dependencies": {
"@scure/bip32": "^1.1.0",
"@scure/bip39": "^1.1.0",
"@stacks/auth": "^4.3.2",
"@stacks/blockchain-api-client": "^4.0.1",
"@stacks/bns": "^4.3.2",
"@stacks/common": "^4.3.2",
"@stacks/encryption": "^4.3.2",
"@stacks/network": "^4.3.2",
"@stacks/stacking": "^4.3.2",
"@stacks/storage": "^4.3.2",
Expand All @@ -41,6 +44,7 @@
"jsontokens": "^3.1.1",
"node-fetch": "^2.6.0",
"ripemd160": "^2.0.1",
"wif": "^2.0.6",
"winston": "^3.2.1",
"zone-file": "^2.0.0-beta.3"
},
Expand All @@ -52,6 +56,7 @@
"@types/jest": "^26.0.22",
"@types/node-fetch": "^2.5.0",
"@types/ripemd160": "^2.0.0",
"@types/wif": "^2.0.2",
"jest": "^26.6.3",
"jest-fetch-mock": "^3.0.3",
"jest-module-name-mapper": "^0.1.5",
Expand Down
20 changes: 7 additions & 13 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import * as fs from 'fs';
import * as winston from 'winston';
import cors from 'cors';

import * as crypto from 'crypto';
import * as bip39 from 'bip39';
import * as scureBip39 from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import express from 'express';
import * as path from 'path';
import { prompt } from 'inquirer';
Expand Down Expand Up @@ -322,21 +322,15 @@ async function getStacksWalletKey(network: CLINetworkAdapter, args: string[]): P
* @mnemonic (string) OPTIONAL; the 12-word phrase
*/
async function makeKeychain(network: CLINetworkAdapter, args: string[]): Promise<string> {
let mnemonic: string;
if (args[0]) {
mnemonic = await getBackupPhrase(args[0]);
} else {
// eslint-disable-next-line @typescript-eslint/await-thenable
mnemonic = await bip39.generateMnemonic(
STX_WALLET_COMPATIBLE_SEED_STRENGTH,
crypto.randomBytes
);
}
const mnemonic: string = args[0]
? await getBackupPhrase(args[0])
: scureBip39.generateMnemonic(wordlist, STX_WALLET_COMPATIBLE_SEED_STRENGTH);

const derivationPath: string | undefined = args[1] || undefined;
const stacksKeyInfo = await getStacksWalletKeyInfo(network, mnemonic, derivationPath);

return JSONStringify({
mnemonic: mnemonic,
mnemonic,
keyInfo: stacksKeyInfo,
});
}
Expand Down
18 changes: 9 additions & 9 deletions packages/cli/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { publicKeyToAddress } from '@stacks/encryption';
import { pubKeyfromPrivKey } from '@stacks/transactions';
import * as bitcoinjs from 'bitcoinjs-lib';
import { TransactionSigner } from 'blockstack';
import { DEFAULT_MAX_ID_SEARCH_INDEX } from './argparse';
import { CLINetworkAdapter } from './network';
import * as blockstack from 'blockstack';
import { TransactionSigner } from 'blockstack';
import * as bitcoinjs from 'bitcoinjs-lib';

let maxIDSearchIndex = DEFAULT_MAX_ID_SEARCH_INDEX;

Expand Down Expand Up @@ -46,13 +47,12 @@ export function getPrivateKeyAddress(
privateKey: string | CLITransactionSigner
): string {
if (isCLITransactionSigner(privateKey)) {
const pkts = privateKey;
return pkts.address;
} else {
const pk = privateKey;
const ecKeyPair = blockstack.hexStringToECPair(pk);
return network.coerceAddress(blockstack.ecPairToAddress(ecKeyPair));
return privateKey.address;
}

const pubKey = pubKeyfromPrivKey(privateKey);
const btcAddress = publicKeyToAddress(pubKey.data);
return network.coerceAddress(btcAddress);
}

export function isCLITransactionSigner(
Expand Down
109 changes: 60 additions & 49 deletions packages/cli/src/keys.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
// TODO: most of this code should be in blockstack.js
// Will remove most of this code once the wallet functionality is there instead.

import * as blockstack from 'blockstack';
import * as bitcoin from 'bitcoinjs-lib';
import * as bip39 from 'bip39';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const c32check = require('c32check');

import { getPrivateKeyAddress, getMaxIDSearchIndex } from './common';
import { HDKey } from '@scure/bip32';
import * as scureBip39 from '@scure/bip39';
import { Buffer } from '@stacks/common';
import {
compressPrivateKey,
getPublicKeyFromPrivate,
publicKeyToBtcAddress,
} from '@stacks/encryption';
import { createStacksPrivateKey, privateKeyToString } from '@stacks/transactions';
import { DerivationType, deriveAccount, generateWallet, getRootNode } from '@stacks/wallet-sdk';
import * as bip32 from 'bip32';
import * as bip39 from 'bip39';
import * as blockstack from 'blockstack';
import * as wif from 'wif';

import { getMaxIDSearchIndex, getPrivateKeyAddress } from './common';
import { CLINetworkAdapter } from './network';

import * as bip32 from 'bip32';
import { BIP32Interface } from 'bip32';
const BITCOIN_PUBKEYHASH = 0;
const BITCOIN_PUBKEYHASH_TESTNET = 111;
const BITCOIN_WIF = 128;
// @ts-ignore
const BITCOIN_WIF_TESTNET = 239;

export const STX_WALLET_COMPATIBLE_SEED_STRENGTH = 256;
export const DERIVATION_PATH = "m/44'/5757'/0'/0/0";
Expand Down Expand Up @@ -58,10 +72,6 @@ async function walletFromMnemonic(mnemonic: string): Promise<blockstack.Blocksta
return new blockstack.BlockstackWallet(bip32.fromSeed(seed));
}

function getNodePrivateKey(node: BIP32Interface): string {
return blockstack.ecPairToHexString(bitcoin.ECPair.fromPrivateKey(node.privateKey!));
}

/*
* Get the owner key information for a 12-word phrase, at a specific index.
* @network (object) the blockstack network
Expand All @@ -80,15 +90,20 @@ export async function getOwnerKeyInfo(
index: number,
version: string = 'v0.10-current'
): Promise<OwnerKeyInfoType> {
const wallet = await walletFromMnemonic(mnemonic);
const identity = wallet.getIdentityAddressNode(index);
const addr = network.coerceAddress(blockstack.BlockstackWallet.getAddressFromBIP32Node(identity));
const privkey = getNodePrivateKey(identity);
const wallet = await generateWallet({ secretKey: mnemonic, password: '' });
const account = deriveAccount({
rootNode: getRootNode(wallet),
salt: wallet.salt,
stxDerivationType: DerivationType.Wallet,
index,
});
const publicKey = getPublicKeyFromPrivate(account.dataPrivateKey);
const addr = network.coerceAddress(publicKeyToBtcAddress(publicKey));
return {
privateKey: privkey,
version: version,
index: index,
privateKey: account.dataPrivateKey + '01',
idAddress: `ID-${addr}`,
version,
index,
} as OwnerKeyInfoType;
}

Expand Down Expand Up @@ -133,37 +148,27 @@ export async function getStacksWalletKeyInfo(
mnemonic: string,
derivationPath = DERIVATION_PATH
): Promise<StacksKeyInfoType> {
const seed = await bip39.mnemonicToSeed(mnemonic);
const master = bip32.fromSeed(seed);
const child = master.derivePath(derivationPath); // taken from stacks-wallet. See https://github.com/blockstack/stacks-wallet
const ecPair = bitcoin.ECPair.fromPrivateKey(child.privateKey!);
const privkey = blockstack.ecPairToHexString(ecPair);
const wif = child.toWIF();
const seed = await scureBip39.mnemonicToSeed(mnemonic);
const master = HDKey.fromMasterSeed(seed);
const child = master.derive(derivationPath);
const pubkey = Buffer.from(child.publicKey!);
const privkeyBuffer = Buffer.from(child.privateKey!);
const privkey = privateKeyToString(createStacksPrivateKey(compressPrivateKey(privkeyBuffer)));
const walletImportFormat = wif.encode(BITCOIN_WIF, privkeyBuffer, true);

const addr = getPrivateKeyAddress(network, privkey);
let btcAddress: string;
if (network.isTestnet()) {
// btcAddress = const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey });
const { address } = bitcoin.payments.p2pkh({
pubkey: ecPair.publicKey,
network: bitcoin.networks.testnet,
});
btcAddress = address!;
} else {
const { address } = bitcoin.payments.p2pkh({
pubkey: ecPair.publicKey,
network: bitcoin.networks.bitcoin,
});
btcAddress = address!;
}
const result: StacksKeyInfoType = {
const btcAddress = publicKeyToBtcAddress(
pubkey,
network.isTestnet() ? BITCOIN_PUBKEYHASH_TESTNET : BITCOIN_PUBKEYHASH
);

return {
privateKey: privkey,
address: c32check.b58ToC32(addr),
btcAddress,
wif,
wif: walletImportFormat,
index: 0,
};
return result;
} as StacksKeyInfoType;
}

/*
Expand All @@ -185,14 +190,20 @@ export async function findIdentityIndex(
throw new Error('Not an identity address');
}

const wallet = await walletFromMnemonic(mnemonic);
const wallet = await generateWallet({ secretKey: mnemonic, password: '' });
const needle = network.coerceAddress(idAddress.slice(3));

for (let i = 0; i < maxIndex; i++) {
const identity = wallet.getIdentityAddressNode(i);
const addr = blockstack.BlockstackWallet.getAddressFromBIP32Node(identity);
const account = deriveAccount({
rootNode: getRootNode(wallet),
salt: wallet.salt,
stxDerivationType: DerivationType.Wallet,
index: i,
});
const publicKey = getPublicKeyFromPrivate(account.dataPrivateKey);
const address = network.coerceAddress(publicKeyToBtcAddress(publicKey));

if (network.coerceAddress(addr) === network.coerceAddress(idAddress.slice(3))) {
return i;
}
if (address === needle) return i;
}

return -1;
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/tests/fixtures/keys.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export const getOwnerKeyInfo: Array<[string, number, string, OwnerKeyInfoType]>
},
],
[
'oppose spider rich leader proud foil dish finger fee pipe ethics bundle', // mnemonic
'oppose spider rich leader proud foil dish finger fee pipe ethics cable', // mnemonic
0, // index
'v0.10-current', // version
{
privateKey: '7e96519d1202ad990a4d1d14c9e8891d9c53b52ce1fd4aa1b4549e5fbc8bfcda01',
idAddress: 'ID-1FwnbMXxFMxiAFxXV7fKcZn5dB4y6hSkxY',
privateKey: 'd98c5f09b826f7f7b543055bdd25197ca08a6cbedbb80f8f5060645274a0704201',
idAddress: 'ID-1D6kMXjiSDmNt6hwFPUkeHnFtdUgSoPE1e',
version: 'v0.10-current',
index: 0,
},
Expand All @@ -48,8 +48,8 @@ export const findIdentityIndex: Array<[string, string, number]> = [
1, // index
],
[
'oppose spider rich leader proud foil dish finger fee pipe ethics bundle', // mnemonic
'ID-1FwnbMXxFMxiAFxXV7fKcZn5dB4y6hSkxY',
'oppose spider rich leader proud foil dish finger fee pipe ethics cable', // mnemonic
'ID-1D6kMXjiSDmNt6hwFPUkeHnFtdUgSoPE1e',
0, // index
],
];
15 changes: 9 additions & 6 deletions packages/cli/tests/keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ test('getStacksWalletKeyInfo', async () => {
});

describe('getStacksWalletKeyInfo custom derivation path', () => {
test.each(keyInfoTests)('%#', async (derivationPath: string, keyInfoResult: WalletKeyInfoResult) => {
const mnemonic = 'apart spin rich leader siren foil dish sausage fee pipe ethics bundle';
const info = await getStacksWalletKeyInfo(mainnetNetwork, mnemonic, derivationPath);

expect(info).toEqual(keyInfoResult);
});
test.each(keyInfoTests)(
'%#',
async (derivationPath: string, keyInfoResult: WalletKeyInfoResult) => {
const mnemonic = 'apart spin rich leader siren foil dish sausage fee pipe ethics bundle';
const info = await getStacksWalletKeyInfo(mainnetNetwork, mnemonic, derivationPath);

expect(info).toEqual(keyInfoResult);
}
);
});

describe('getOwnerKeyInfo', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/encryption/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dependencies": {
"@noble/hashes": "^1.0.0",
"@noble/secp256k1": "^1.5.5",
"@scure/bip39": "^1.0.0",
"@scure/bip39": "^1.1.0",
"@stacks/common": "^4.3.2",
"@types/node": "^14.14.43",
"bs58": "^5.0.0",
Expand Down
23 changes: 11 additions & 12 deletions packages/encryption/src/hashRipemd160.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Buffer } from '@stacks/common';
import Ripemd160Polyfill from 'ripemd160-min';
import { isNodeCryptoAvailable } from './cryptoUtils';

type NodeCryptoCreateHash = typeof import('crypto').createHash;

Expand Down Expand Up @@ -47,17 +46,17 @@ export class NodeCryptoRipemd160Digest implements Ripemd160Digest {
}

export function createHashRipemd160() {
const nodeCryptoCreateHash = isNodeCryptoAvailable(nodeCrypto => {
if (typeof nodeCrypto.createHash === 'function') {
return nodeCrypto.createHash;
}
return false;
});
if (nodeCryptoCreateHash) {
return new NodeCryptoRipemd160Digest(nodeCryptoCreateHash);
} else {
return new Ripemd160PolyfillDigest();
}
// // disable node hashRipemd160, because it doesn't work with node 17,18 openSSL
// const nodeCryptoCreateHash = isNodeCryptoAvailable(nodeCrypto => {
// if (typeof nodeCrypto.createHash === 'function') {
// return nodeCrypto.createHash;
// }
// return false;
// });
// if (nodeCryptoCreateHash) {
// return new NodeCryptoRipemd160Digest(nodeCryptoCreateHash);
// } else {
return new Ripemd160PolyfillDigest();
}

export function hashRipemd160(data: Buffer) {
Expand Down
13 changes: 13 additions & 0 deletions packages/encryption/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,26 @@ export function base58CheckEncode(version: number, hash: Buffer) {

/**
* @ignore
* @deprecated Use {@link publicKeyToBtcAddress} instead
*/
export function publicKeyToAddress(publicKey: string | Buffer) {
const publicKeyBuffer = Buffer.isBuffer(publicKey) ? publicKey : Buffer.from(publicKey, 'hex');
const publicKeyHash160 = hashRipemd160(hashSha256Sync(publicKeyBuffer));
return base58CheckEncode(BITCOIN_PUBKEYHASH, publicKeyHash160);
}

/**
* @ignore
*/
export function publicKeyToBtcAddress(
publicKey: string | Buffer,
version: number = BITCOIN_PUBKEYHASH
) {
const publicKeyBuffer = Buffer.isBuffer(publicKey) ? publicKey : Buffer.from(publicKey, 'hex');
const publicKeyHash160 = hashRipemd160(hashSha256Sync(publicKeyBuffer));
return base58CheckEncode(version, publicKeyHash160);
}

/**
* @ignore
* @returns a compressed public key
Expand Down

1 comment on commit 62355bc

@vercel
Copy link

@vercel vercel bot commented on 62355bc Jul 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.