diff --git a/package-lock.json b/package-lock.json index 9b8b66551..c1633fd6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22226,6 +22226,7 @@ "version": "6.16.1", "license": "MIT", "dependencies": { + "@noble/secp256k1": "1.7.1", "@stacks/common": "^6.16.0", "@stacks/encryption": "^6.16.1", "@stacks/network": "^6.16.0", @@ -22437,6 +22438,7 @@ "@stacks/api": "^6.9.0", "@stacks/blockchain-api-client": "^7.12.0", "@stacks/network": "^6.16.0", + "@stacks/stacking": "^6.9.0", "jest-fetch-mock": "^3.0.3" } }, diff --git a/packages/auth/package.json b/packages/auth/package.json index 27d6eeea6..fa84a8cac 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -20,6 +20,7 @@ "typecheck:watch": "npm run typecheck -- --watch" }, "dependencies": { + "@noble/secp256k1": "1.7.1", "@stacks/common": "^6.16.0", "@stacks/encryption": "^6.16.1", "@stacks/network": "^6.16.0", diff --git a/packages/auth/src/userSession.ts b/packages/auth/src/userSession.ts index b4da5d88f..24cc2082b 100644 --- a/packages/auth/src/userSession.ts +++ b/packages/auth/src/userSession.ts @@ -6,12 +6,8 @@ import { InstanceDataStore, LocalStorageStore, SessionDataStore } from './sessio import { decodeToken } from 'jsontokens'; import { verifyAuthResponse } from './verification'; import * as authMessages from './messages'; -import { - decryptContent, - encryptContent, - EncryptContentOptions, - isValidPrivateKey, -} from '@stacks/encryption'; +import { utils } from '@noble/secp256k1'; +import { decryptContent, encryptContent, EncryptContentOptions } from '@stacks/encryption'; import { getAddressFromDID } from './dids'; import { createFetchFn, @@ -254,7 +250,7 @@ export class UserSession { )) as string; } catch (e) { Logger.warn('Failed decryption of appPrivateKey, will try to use as given'); - if (!isValidPrivateKey(tokenPayload.private_key as string)) { + if (!utils.isValidPrivateKey(tokenPayload.private_key as string)) { throw new LoginFailedError( 'Failed decrypting appPrivateKey. Usually means' + ' that the transit key has changed during login.' diff --git a/packages/cli/src/keys.ts b/packages/cli/src/keys.ts index 6a405d837..f20a3bfad 100644 --- a/packages/cli/src/keys.ts +++ b/packages/cli/src/keys.ts @@ -7,11 +7,7 @@ const c32check = require('c32check'); import { HDKey } from '@scure/bip32'; import * as scureBip39 from '@scure/bip39'; -import { - compressPrivateKey, - getPublicKeyFromPrivate, - publicKeyToBtcAddress, -} from '@stacks/encryption'; +import { getPublicKeyFromPrivate, publicKeyToBtcAddress } from '@stacks/encryption'; import { DerivationType, deriveAccount, generateWallet, getRootNode } from '@stacks/wallet-sdk'; import * as bip32 from 'bip32'; import * as bip39 from 'bip39'; @@ -20,6 +16,7 @@ import * as wif from 'wif'; import { getMaxIDSearchIndex, getPrivateKeyAddress } from './common'; import { CLINetworkAdapter } from './network'; +import { compressPrivateKey } from '@stacks/transactions'; const BITCOIN_PUBKEYHASH = 0; const BITCOIN_PUBKEYHASH_TESTNET = 111; diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 60d3b45de..75643f263 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -439,7 +439,7 @@ function nthBit(value: bigint, n: bigint) { return value & (BigInt(1) << n); } -/** @internal */ +/** @ignore */ export function bytesToTwosBigInt(bytes: Uint8Array): bigint { return fromTwos(BigInt(`0x${bytesToHex(bytes)}`), BigInt(bytes.byteLength * 8)); } diff --git a/packages/encryption/src/keys.ts b/packages/encryption/src/keys.ts index 00cf735b1..1c826c26e 100644 --- a/packages/encryption/src/keys.ts +++ b/packages/encryption/src/keys.ts @@ -2,7 +2,6 @@ import { hmac } from '@noble/hashes/hmac'; import { sha256 } from '@noble/hashes/sha256'; import { getPublicKey as nobleGetPublicKey, signSync, utils } from '@noble/secp256k1'; import { - PRIVATE_KEY_BYTES_COMPRESSED, PrivateKey, bytesToHex, concatBytes, @@ -13,7 +12,6 @@ import { import base58 from 'bs58'; import { hashRipemd160 } from './hashRipemd160'; import { hashSha256Sync } from './sha2Hash'; -import { privateKeyToHex } from '../../transactions/src'; const BITCOIN_PUBKEYHASH = 0x00; @@ -112,21 +110,3 @@ export function ecSign(messageHash: Uint8Array, privateKey: PrivateKey) { der: false, }); } - -/** - * @ignore - */ -export function isValidPrivateKey(privateKey: PrivateKey): boolean { - return utils.isValidPrivateKey(privateKeyToBytes(privateKey)); -} - -/** - * @ignore - */ -export function compressPrivateKey(privateKey: PrivateKey): string { - privateKey = privateKeyToHex(privateKey); - - return privateKey.length == PRIVATE_KEY_BYTES_COMPRESSED * 2 - ? privateKey // leave compressed - : `${privateKey}01`; // compress -} diff --git a/packages/encryption/tests/keys.test.ts b/packages/encryption/tests/keys.test.ts index da5f9f57e..261b7e75e 100644 --- a/packages/encryption/tests/keys.test.ts +++ b/packages/encryption/tests/keys.test.ts @@ -5,12 +5,11 @@ import { hexToBytes, utf8ToBytes, } from '@stacks/common'; -import { address, ECPair, networks } from 'bitcoinjs-lib'; +import { ECPair, address, networks } from 'bitcoinjs-lib'; import bs58check from 'bs58check'; import { SECP256K1Client } from 'jsontokens'; import { base58Encode, - compressPrivateKey, ecSign, getPublicKeyFromPrivate, hashSha256Sync, @@ -109,20 +108,3 @@ test('ecSign', () => { expect(bytesToHex(signature)).toEqual(signatureHex); }); - -describe(compressPrivateKey, () => { - it('does not change already compressed key', () => { - const privateKeyCompressed = - '00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb01'; - - expect(compressPrivateKey(privateKeyCompressed)).toEqual(privateKeyCompressed); - }); - - it('compresses uncompressed key', () => { - const privateKey = '00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb'; - const privateKeyCompressed = - '00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb01'; - - expect(compressPrivateKey(privateKey)).toEqual(privateKeyCompressed); - }); -}); diff --git a/packages/internal/package.json b/packages/internal/package.json index 91e221c40..2642bc5df 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -8,6 +8,7 @@ }, "devDependencies": { "@stacks/api": "^6.9.0", + "@stacks/stacking": "^6.9.0", "@stacks/blockchain-api-client": "^7.12.0", "@stacks/network": "^6.16.0", "jest-fetch-mock": "^3.0.3" diff --git a/packages/internal/src/apiMockingHelpers.ts b/packages/internal/src/apiMockingHelpers.ts index d922e965d..a9d761d03 100644 --- a/packages/internal/src/apiMockingHelpers.ts +++ b/packages/internal/src/apiMockingHelpers.ts @@ -1,7 +1,7 @@ import { Configuration, TransactionsApi } from '@stacks/blockchain-api-client'; import { STACKS_TESTNET } from '@stacks/network'; import { MockResponseInitFunction } from 'jest-fetch-mock'; -import { StackingClient } from '../../stacking/src'; +import { StackingClient } from '@stacks/stacking'; import { StacksNodeApi } from '@stacks/api'; // NOTES diff --git a/packages/internal/tsconfig.build.json b/packages/internal/tsconfig.build.json index c0ddf5612..e65f27fb7 100644 --- a/packages/internal/tsconfig.build.json +++ b/packages/internal/tsconfig.build.json @@ -7,5 +7,13 @@ "tsBuildInfoFile": "./tsconfig.build.tsbuildinfo", "composite": true }, + "references": [ + { + "path": "../api/tsconfig.build.json" + }, + { + "path": "../stacking/tsconfig.build.json" + } + ], "include": ["src/**/*"] } diff --git a/packages/stacking/tsconfig.build.json b/packages/stacking/tsconfig.build.json index 358f147c0..039ff6365 100644 --- a/packages/stacking/tsconfig.build.json +++ b/packages/stacking/tsconfig.build.json @@ -17,9 +17,6 @@ { "path": "../encryption/tsconfig.build.json" }, - { - "path": "../internal/tsconfig.build.json" - }, { "path": "../network/tsconfig.build.json" }, diff --git a/packages/storage/src/hub.ts b/packages/storage/src/hub.ts index 91a5f6989..9294b692b 100644 --- a/packages/storage/src/hub.ts +++ b/packages/storage/src/hub.ts @@ -9,11 +9,11 @@ import { NotEnoughProofError, PayloadTooLargeError, PreconditionFailedError, + PRIVATE_KEY_BYTES_COMPRESSED, utf8ToBytes, ValidationError, } from '@stacks/common'; import { - compressPrivateKey, ecSign, getPublicKeyFromPrivate, hashSha256Sync, @@ -151,7 +151,9 @@ function makeLegacyAuthToken(challengeText: string, signerKeyHex: string): strin } if (parsedChallenge[0] === 'gaiahub' && parsedChallenge[3] === 'blockstack_storage_please_sign') { const digest = hashSha256Sync(utf8ToBytes(challengeText)); - const signatureBytes = ecSign(digest, compressPrivateKey(signerKeyHex)); + const compressedPrivateKey = + signerKeyHex.length === PRIVATE_KEY_BYTES_COMPRESSED * 2 ? signerKeyHex : `${signerKeyHex}01`; + const signatureBytes = ecSign(digest, compressedPrivateKey); // We only want the DER encoding so use toDERHex provided by @noble/secp256k1 const signature = Signature.fromCompact(bytesToHex(signatureBytes)).toDERHex(); diff --git a/packages/transactions/src/keys.ts b/packages/transactions/src/keys.ts index 7a5daf80f..fae637479 100644 --- a/packages/transactions/src/keys.ts +++ b/packages/transactions/src/keys.ts @@ -209,6 +209,17 @@ export function signMessageHashRsv({ return signatureVrsToRsv(signWithKey(privateKey, messageHash)); } +/** + * @ignore + */ +export function compressPrivateKey(privateKey: PrivateKey): string { + privateKey = privateKeyToHex(privateKey); + + return privateKey.length == PRIVATE_KEY_BYTES_COMPRESSED * 2 + ? privateKey // leave compressed + : `${privateKey}01`; // compress +} + /** * Convert a private key to a single-sig address. * @returns A Stacks address string (encoded with c32check) diff --git a/packages/transactions/src/transaction.ts b/packages/transactions/src/transaction.ts index c7ec7596a..0eb1e45e6 100644 --- a/packages/transactions/src/transaction.ts +++ b/packages/transactions/src/transaction.ts @@ -2,12 +2,13 @@ import { Hex, IntegerType, PrivateKey, + PublicKey, bytesToHex, concatArray, hexToBytes, intToBigInt, writeUInt32BE, -} from '@stacks/common/src'; +} from '@stacks/common'; import { ChainId, DEFAULT_CHAIN_ID, @@ -59,7 +60,6 @@ import { deserializePayloadBytes, serializeLPListBytes, } from './wire'; -import { PublicKey } from '@stacks/common'; export class StacksTransaction { version: TransactionVersion; diff --git a/packages/transactions/tests/keys.test.ts b/packages/transactions/tests/keys.test.ts index ec8945a9f..782028f42 100644 --- a/packages/transactions/tests/keys.test.ts +++ b/packages/transactions/tests/keys.test.ts @@ -18,6 +18,7 @@ import { ec as EC } from 'elliptic'; import { PubKeyEncoding, StacksWireType, + compressPrivateKey, compressPublicKey, createStacksPublicKey, encodeStructuredData, @@ -343,3 +344,20 @@ describe(privateKeyToAddress.name, () => { expect(addressTestnet).toBe('ST10J81WVGVB3M4PHQN4Q4G0R8586TBJH94CGRESQ'); }); }); + +describe(compressPrivateKey, () => { + it('does not change already compressed key', () => { + const privateKeyCompressed = + '00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb01'; + + expect(compressPrivateKey(privateKeyCompressed)).toEqual(privateKeyCompressed); + }); + + it('compresses uncompressed key', () => { + const privateKey = '00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb'; + const privateKeyCompressed = + '00cdce6b5f87d38f2a830cae0da82162e1b487f07c5affa8130f01fe1a2a25fb01'; + + expect(compressPrivateKey(privateKey)).toEqual(privateKeyCompressed); + }); +}); diff --git a/packages/wallet-sdk/src/derive.ts b/packages/wallet-sdk/src/derive.ts index 21d183a99..0110c747d 100644 --- a/packages/wallet-sdk/src/derive.ts +++ b/packages/wallet-sdk/src/derive.ts @@ -3,7 +3,7 @@ import { HDKey } from '@scure/bip32'; import { getNameInfo } from '@stacks/auth'; import { ApiParam, bytesToHex, defaultApiLike, utf8ToBytes } from '@stacks/common'; -import { compressPrivateKey, createSha2Hash } from '@stacks/encryption'; +import { createSha2Hash } from '@stacks/encryption'; import { STACKS_MAINNET, StacksNetwork, @@ -11,7 +11,7 @@ import { deriveDefaultUrl, networkFrom, } from '@stacks/network'; -import { getAddressFromPrivateKey } from '@stacks/transactions'; +import { compressPrivateKey, getAddressFromPrivateKey } from '@stacks/transactions'; import { Account, HARDENED_OFFSET, WalletKeys } from './models/common'; import { fetchFirstName } from './usernames'; import { assertIsTruthy } from './utils';