Skip to content

Commit

Permalink
fix!: Compressing private key should ensure string format
Browse files Browse the repository at this point in the history
  • Loading branch information
janniks committed Sep 24, 2024
1 parent 8d061f1 commit 6503e90
Show file tree
Hide file tree
Showing 8 changed files with 24 additions and 27 deletions.
3 changes: 1 addition & 2 deletions packages/cli/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const c32check = require('c32check');

import { HDKey } from '@scure/bip32';
import * as scureBip39 from '@scure/bip39';
import { bytesToHex } from '@stacks/common';

import {
compressPrivateKey,
Expand Down Expand Up @@ -152,7 +151,7 @@ export async function getStacksWalletKeyInfo(
const child = master.derive(derivationPath);
const pubkey = Buffer.from(child.publicKey!);
const privkeyBuffer = Buffer.from(child.privateKey!);
const privkey = bytesToHex(compressPrivateKey(privkeyBuffer));
const privkey = compressPrivateKey(privkeyBuffer);
const wifVersion = network.isTestnet() ? BITCOIN_WIF_TESTNET : BITCOIN_WIF;
const walletImportFormat = wif.encode(wifVersion, privkeyBuffer, true);

Expand Down
5 changes: 2 additions & 3 deletions packages/common/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ export const GAIA_URL = 'https://hub.blockstack.org';
// todo: deduplicate magic variables

/** @ignore internal */
export const PRIVATE_KEY_COMPRESSED_LENGTH = 33;
// todo: `next` make length consts more consistent in naming
export const PRIVATE_KEY_BYTES_COMPRESSED = 33;

/** @ignore internal */
export const PRIVATE_KEY_UNCOMPRESSED_LENGTH = 32;
export const PRIVATE_KEY_BYTES_UNCOMPRESSED = 32;
13 changes: 7 additions & 6 deletions packages/encryption/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { hmac } from '@noble/hashes/hmac';
import { sha256 } from '@noble/hashes/sha256';
import { getPublicKey as nobleGetPublicKey, signSync, utils } from '@noble/secp256k1';
import {
PRIVATE_KEY_COMPRESSED_LENGTH,
PRIVATE_KEY_BYTES_COMPRESSED,
PrivateKey,
bytesToHex,
concatBytes,
Expand All @@ -13,6 +13,7 @@ import {
import base58 from 'bs58';
import { hashRipemd160 } from './hashRipemd160';
import { hashSha256Sync } from './sha2Hash';
import { privateKeyToHex } from '../../transactions/src';

const BITCOIN_PUBKEYHASH = 0x00;

Expand Down Expand Up @@ -122,10 +123,10 @@ export function isValidPrivateKey(privateKey: PrivateKey): boolean {
/**
* @ignore
*/
export function compressPrivateKey(privateKey: PrivateKey): Uint8Array {
const privateKeyBytes = privateKeyToBytes(privateKey);
export function compressPrivateKey(privateKey: PrivateKey): string {
privateKey = privateKeyToHex(privateKey);

return privateKeyBytes.length == PRIVATE_KEY_COMPRESSED_LENGTH
? privateKeyBytes // leave compressed
: concatBytes(privateKeyBytes, new Uint8Array([1])); // compress
return privateKey.length == PRIVATE_KEY_BYTES_COMPRESSED * 2
? privateKey // leave compressed
: `${privateKey}01`; // compress
}
8 changes: 4 additions & 4 deletions packages/encryption/tests/keys.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { utils } from '@noble/secp256k1';
import {
PRIVATE_KEY_BYTES_UNCOMPRESSED,
bytesToHex,
hexToBytes,
PRIVATE_KEY_UNCOMPRESSED_LENGTH,
utf8ToBytes,
} from '@stacks/common';
import { address, ECPair, networks } from 'bitcoinjs-lib';
Expand All @@ -24,7 +24,7 @@ test('makeECPrivateKey', () => {

expect(privateKey).toBeTruthy();
expect(typeof privateKey).toEqual('string');
expect(privateKey.length).toEqual(PRIVATE_KEY_UNCOMPRESSED_LENGTH * 2);
expect(privateKey.length).toEqual(PRIVATE_KEY_BYTES_UNCOMPRESSED * 2);
expect(utils.isValidPrivateKey(privateKey)).toBeTruthy();
});

Expand Down Expand Up @@ -115,14 +115,14 @@ describe(compressPrivateKey, () => {
const privateKeyCompressed =
'00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb01';

expect(compressPrivateKey(privateKeyCompressed)).toEqual(hexToBytes(privateKeyCompressed));
expect(compressPrivateKey(privateKeyCompressed)).toEqual(privateKeyCompressed);
});

it('compresses uncompressed key', () => {
const privateKey = '00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb';
const privateKeyCompressed =
'00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb01';

expect(compressPrivateKey(privateKey)).toEqual(hexToBytes(privateKeyCompressed));
expect(compressPrivateKey(privateKey)).toEqual(privateKeyCompressed);
});
});
4 changes: 2 additions & 2 deletions packages/transactions/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
hexToBytes,
intToHex,
parseRecoverableSignatureVrs,
PRIVATE_KEY_COMPRESSED_LENGTH,
PRIVATE_KEY_BYTES_COMPRESSED,
PrivateKey,
privateKeyToBytes,
PublicKey,
Expand Down Expand Up @@ -116,7 +116,7 @@ export const isPrivateKeyCompressed = privateKeyIsCompressed;
/** @deprecated Use {@link isPrivateKeyCompressed} instead */
export function privateKeyIsCompressed(privateKey: PrivateKey): boolean {
const length = typeof privateKey === 'string' ? privateKey.length / 2 : privateKey.byteLength;
return length === PRIVATE_KEY_COMPRESSED_LENGTH;
return length === PRIVATE_KEY_BYTES_COMPRESSED;
}

/**
Expand Down
3 changes: 1 addition & 2 deletions packages/transactions/src/wire/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ export interface TransactionAuthFieldWire {

export type TransactionAuthFieldContentsWire = PublicKeyWire | MessageSignatureWire;

// todo: `next` refactor to match wire format more precisely eg https://github.com/jbencin/sips/blob/sip-02x-non-sequential-multisig-transactions/sips/sip-02x/sip-02x-non-sequential-multisig-transactions.md
// "A spending authorization field is encoded as follows:" ...
/** @see {@link AuthFieldType} */
export interface TransactionAuthFieldWire {
type: StacksWireType.TransactionAuthField;
pubKeyEncoding: PubKeyEncoding;
Expand Down
11 changes: 5 additions & 6 deletions packages/transactions/tests/builder.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
HIRO_MAINNET_URL,
HIRO_TESTNET_URL,
PRIVATE_KEY_COMPRESSED_LENGTH,
PRIVATE_KEY_BYTES_COMPRESSED,
bytesToHex,
bytesToUtf8,
createApiKeyMiddleware,
Expand Down Expand Up @@ -98,6 +98,7 @@ import {
transactionToHex,
} from '../src/transaction';
import { cloneDeep, randomBytes } from '../src/utils';
import { compressPrivateKey } from '../../encryption/src';

function setSignature(
unsignedTransaction: StacksTransaction,
Expand Down Expand Up @@ -2351,10 +2352,8 @@ describe('multi-sig', () => {
describe('working non-sequential multi-sig', () => {
const pk1 = makeRandomPrivKey();
const pk2 = makeRandomPrivKey();
const pk3 = bytesToHex(randomBytes(32)) + '01';
const pk4 = bytesToHex(randomBytes(32)) + '01';

// todo: add compressPrivateKey helper `next`
const pk3 = compressPrivateKey(makeRandomPrivKey());
const pk4 = compressPrivateKey(makeRandomPrivKey());

const CASES = [
{ signers: [pk1, pk2, pk3], signing: [pk1, pk2], required: 2 },
Expand Down Expand Up @@ -2458,7 +2457,7 @@ describe('multi-sig', () => {
);
});
tx.auth.spendingCondition.fields[fieldIdx] = createTransactionAuthField(
hexToBytes(signerKey).byteLength === PRIVATE_KEY_COMPRESSED_LENGTH
hexToBytes(signerKey).byteLength === PRIVATE_KEY_BYTES_COMPRESSED
? PubKeyEncoding.Compressed
: PubKeyEncoding.Uncompressed,
createMessageSignature(nextSig)
Expand Down
4 changes: 2 additions & 2 deletions packages/wallet-sdk/src/derive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,13 @@ export const derivePrivateKeyByType = ({
export const deriveStxPrivateKey = ({ rootNode, index }: { rootNode: HDKey; index: number }) => {
const childKey = rootNode.derive(STX_DERIVATION_PATH).deriveChild(index);
assertIsTruthy(childKey.privateKey);
return bytesToHex(compressPrivateKey(childKey.privateKey));
return compressPrivateKey(childKey.privateKey);
};

export const deriveDataPrivateKey = ({ rootNode, index }: { rootNode: HDKey; index: number }) => {
const childKey = rootNode.derive(DATA_DERIVATION_PATH).deriveChild(index + HARDENED_OFFSET);
assertIsTruthy(childKey.privateKey);
return bytesToHex(compressPrivateKey(childKey.privateKey));
return compressPrivateKey(childKey.privateKey);
};

export const deriveAccount = ({
Expand Down

0 comments on commit 6503e90

Please sign in to comment.