Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0e5e6b4
add privateKeyToBuffer to IDeriver, implement method for concrete cla…
MichaelAJay Dec 11, 2025
b63ad0f
And encryptBuffer & decryptToBuffer pairs to Encryption class.
MichaelAJay Dec 11, 2025
01215fa
adds Storage.addKeysSafe method & implements private-key encrypted wr…
MichaelAJay Dec 11, 2025
4bf659c
fix Deriver.privateKeyToBuffer call
MichaelAJay Dec 11, 2025
2bb39e5
implement privateKeyBuffertoNativePrivateKey on IDeriver concrete cla…
MichaelAJay Dec 11, 2025
23ba0e3
add backwards-compatible attempt to decrypt key.privKey & serialize i…
MichaelAJay Dec 11, 2025
dfe9aca
fixed backwards compat issue and wrote tests to backwards compat
MichaelAJay Dec 11, 2025
bb01d24
bug fix
MichaelAJay Dec 11, 2025
5b8d5c3
fix BTCDeriver private key buffer to native private key
MichaelAJay Dec 11, 2025
e1b901e
fix Wallet.unlock buffer to string conversion
MichaelAJay Dec 15, 2025
2670f11
add another backwards compatibility check for bitcoin core wallet
MichaelAJay Dec 16, 2025
1b61909
update IDeriver and implementation classes' privaetKeyToBuffer method…
MichaelAJay Jan 26, 2026
57c4d0c
remove legacy wallet creation code
MichaelAJay Jan 26, 2026
2afe18e
refactored loadWallet to auto-migrate wallets to current version
MichaelAJay Jan 26, 2026
77c541d
add HDPrivateKey toObjectWithBufferPrivateKey
MichaelAJay Jan 26, 2026
2f40d20
update encrypt/decrypt methods with flexibility and buffer-first appr…
MichaelAJay Jan 27, 2026
c05c5b9
remove backwards compatibility, add migration method
MichaelAJay Jan 28, 2026
9750fa2
implement storage method replacements for add/get keys, and overload …
MichaelAJay Jan 29, 2026
3a6794b
Merge branch 'master' of https://github.com/bitpay/bitcore into walle…
MichaelAJay Jan 29, 2026
b3c1078
remove extra decrypt in unlock for old versions
MichaelAJay Jan 29, 2026
34735a8
fix password issue
MichaelAJay Jan 29, 2026
1d861bb
fix privkey buff assignment in key treatment - signTx
MichaelAJay Jan 29, 2026
d6bb7cf
implementation complete, cleanup complete
MichaelAJay Feb 2, 2026
f5cefb8
Merge branch 'master' of https://github.com/bitpay/bitcore into walle…
MichaelAJay Feb 3, 2026
fe6e8d1
Add getPublicKey to DeriverProxy and to IDeriver
MichaelAJay Feb 3, 2026
516c4a0
buffer-based privkey implementation of Deriver.getPublicKey
MichaelAJay Feb 3, 2026
702eca7
fix deriver bug in eth and xrp getPublicKey
MichaelAJay Feb 3, 2026
2169c91
add getPublicKey tests to derivers
MichaelAJay Feb 4, 2026
c476975
add check to lock() to handle not unlocked cases
MichaelAJay Feb 4, 2026
d9fa38b
Merge branch 'master' of https://github.com/bitpay/bitcore into walle…
MichaelAJay Feb 9, 2026
2aa2e35
address feedback
MichaelAJay Feb 9, 2026
cf2b8ce
fix @deprecated comment style (to jsdocs)
MichaelAJay Feb 10, 2026
0b5e9ae
fix naming issue
MichaelAJay Feb 10, 2026
f8930a6
simplify deprecated method implementation
MichaelAJay Feb 10, 2026
ab53de1
rename DeriverProxy method to match IDeriver implementation.
MichaelAJay Feb 10, 2026
5a5bb0d
update method return type - temp buffer to private key
MichaelAJay Feb 10, 2026
aecdc18
fix getPublicKey - rm private key to string
MichaelAJay Feb 13, 2026
e7ff3c0
update encrypt/decrypt methods to handle unspecified error conditions…
MichaelAJay Mar 10, 2026
b2e30fa
add log statements for migration process and update file path for bac…
MichaelAJay Mar 11, 2026
bf9bc36
wallet test stubs
MichaelAJay Mar 11, 2026
5e779ff
implement unlock & migrateWallet tests & add test fixture
MichaelAJay Mar 11, 2026
6e7bc5d
add edge case tests for migrateWallet
MichaelAJay Mar 11, 2026
0fd4944
refactor importKeys and write unit tests
MichaelAJay Mar 13, 2026
05e4d33
refactor derivePrivateKey and test it
MichaelAJay Mar 13, 2026
49bc4ae
add XRP and SOL signing tests
MichaelAJay Mar 13, 2026
21bfe67
Merge branch 'master' of https://github.com/bitpay/bitcore into walle…
MichaelAJay Mar 17, 2026
1a2b91b
remove duplicate import statement
MichaelAJay Mar 17, 2026
b5640eb
fix import statement
MichaelAJay Mar 17, 2026
a1ad584
fix import statements
MichaelAJay Mar 17, 2026
7745ae4
fix getPublicKey methods to return compressed public keys
MichaelAJay Mar 17, 2026
b07f3a5
rm .only on test
MichaelAJay Mar 17, 2026
4763757
fix lack of mkdir in wallet migration and update tests
MichaelAJay Mar 19, 2026
0266e6a
refactor wallet tests to make them less brittle
MichaelAJay Mar 19, 2026
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
76 changes: 64 additions & 12 deletions packages/bitcore-client/src/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,83 @@ export function encryptEncryptionKey(encryptionKey, password) {
return encData;
}

export function decryptEncryptionKey(encEncryptionKey, password) {
export function decryptEncryptionKey(encEncryptionKey, password, toBuffer: true): Buffer;
export function decryptEncryptionKey(encEncryptionKey, password, toBuffer: false): string;
export function decryptEncryptionKey(encEncryptionKey, password, toBuffer?: boolean): Buffer | string {
const password_hash = Buffer.from(SHA512(password));
const key = password_hash.subarray(0, 32);
const iv = password_hash.subarray(32, 48);
const decipher = crypto.createDecipheriv(algo, key, iv);
const decrypted = decipher.update(encEncryptionKey, 'hex', 'hex' as any) + decipher.final('hex');
return decrypted;

let payload: Buffer | undefined;
let final: Buffer | undefined;
let output: Buffer | undefined;
try {
payload = decipher.update(encEncryptionKey, 'hex');
final = decipher.final();
output = Buffer.concat([payload, final]);
return toBuffer ? output : output.toString('hex');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

include decipher.final() in the try.

let final;
try {
  final = decipher.final();
  const output = Buffer.concat([payload, final]);
  return toBuffer ? output : output.toString('hex');
} finally {
  payload.fill(0);
  final?.fill(0);
}

Not sure if defining final outside the try is necessary. If this code is going to error, it's highly likely that it's due to an invalid checksum/tag in the decipher.final() call, so it may be fine to just do Buffer.concat([payload, decipher.final()]);. Whatever is decided also applies to the other encrypt/decrypt buffer methods.

} finally {
payload.fill(0);
final.fill(0);
if (!toBuffer) {
// Don't fill output if it's what's returned directly
output.fill(0);
}
}
}

/** @deprecated - Use encryptBuffer */
export function encryptPrivateKey(privKey, pubKey, encryptionKey) {
const key = Buffer.from(encryptionKey, 'hex');
const doubleHash = Buffer.from(SHA256(SHA256(pubKey)), 'hex');
const iv = doubleHash.subarray(0, 16);
const cipher = crypto.createCipheriv(algo, key, iv);
const encData = cipher.update(privKey, 'utf8', 'hex') + cipher.final('hex');
return encData;
encryptionKey = Buffer.from(encryptionKey, 'hex');
privKey = Buffer.from(privKey, 'utf8');
return encryptBuffer(privKey, pubKey, encryptionKey).toString('hex');
}

function decryptPrivateKey(encPrivateKey: string, pubKey: string, encryptionKey: string) {
const key = Buffer.from(encryptionKey, 'hex');
function decryptPrivateKey(encPrivateKey: string, pubKey: string, encryptionKey: Buffer | string) {
if (!Buffer.isBuffer(encryptionKey)) {
encryptionKey = Buffer.from(encryptionKey, 'hex');
}
const doubleHash = Buffer.from(SHA256(SHA256(pubKey)), 'hex');
const iv = doubleHash.subarray(0, 16);
const decipher = crypto.createDecipheriv(algo, key, iv);
const decipher = crypto.createDecipheriv(algo, encryptionKey, iv);
const decrypted = decipher.update(encPrivateKey, 'hex', 'utf8') + decipher.final('utf8');
return decrypted;
}

function encryptBuffer(data: Buffer, pubKey: string, encryptionKey: Buffer): Buffer {
let payload: Buffer | undefined;
try {
const iv = Buffer.from(SHA256(SHA256(pubKey)), 'hex').subarray(0, 16);
const cipher = crypto.createCipheriv(algo, encryptionKey, iv);
payload = cipher.update(data);
return Buffer.concat([payload, cipher.final()]);
} finally {
if (Buffer.isBuffer(payload)) {
payload.fill(0);
}
}
}

function decryptToBuffer(encHex: string, pubKey: string, encryptionKey: Buffer): Buffer {
let decrypted: Buffer | undefined;
let final: Buffer | undefined;
try {
const iv = Buffer.from(SHA256(SHA256(pubKey)), 'hex').subarray(0, 16);
const decipher = crypto.createDecipheriv(algo, encryptionKey, iv);
decrypted = decipher.update(encHex, 'hex');
final = decipher.final();
return Buffer.concat([decrypted, final]);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Include the decipher.final() in the try.

try {
  return Buffer.concat([decrypted, decipher.final()]);

} finally {
if (Buffer.isBuffer(decrypted)) {
decrypted.fill(0);
}
if (Buffer.isBuffer(final)) {
final.fill(0);
}
}
}

function sha512KDF(passphrase: string, salt: Buffer, derivationOptions: { rounds?: number }): string {
const rounds = derivationOptions.rounds || 1;
// if salt was sent in as a string, we will have to assume the default encoding type
Expand Down Expand Up @@ -134,6 +184,8 @@ export const Encryption = {
decryptEncryptionKey,
encryptPrivateKey,
decryptPrivateKey,
encryptBuffer,
decryptToBuffer,
generateEncryptionKey,
bitcoinCoreDecrypt
};
72 changes: 69 additions & 3 deletions packages/bitcore-client/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ export class Storage {
(this.storageType as Mongo)?.close?.();
}

async loadWallet(params: { name: string }): Promise<void | IWallet> {
const { name } = params;
async loadWallet(params: { name: string }): Promise<void | IWallet>
async loadWallet(params: { name: string; raw: true }): Promise<void | string>
async loadWallet(params: { name: string; raw: false }): Promise<void | IWallet>
async loadWallet(params: { name: string; raw?: boolean }): Promise<void | IWallet | string> {
const { name, raw } = params;
let wallet: string | void;
for (const db of await this.verifyDbs(this.db)) {
try {
Expand All @@ -72,7 +75,7 @@ export class Storage {
if (!wallet) {
return;
}
return JSON.parse(wallet) as IWallet;
return raw ? wallet : JSON.parse(wallet) as IWallet;
}

async deleteWallet(params: { name: string }) {
Expand Down Expand Up @@ -113,6 +116,7 @@ export class Storage {
return this.storageType.saveWallet({ wallet });
}

/** @deprecated - Use getStoredKey */
async getKey(params: {
address: string;
name: string;
Expand All @@ -132,6 +136,7 @@ export class Storage {
}
}

/** @deprecated - Use getStoredKeys */
async getKeys(params: { addresses: string[]; name: string; encryptionKey: string }): Promise<Array<KeyImport>> {
const { addresses, name, encryptionKey } = params;
const keys = new Array<KeyImport>();
Expand All @@ -158,6 +163,7 @@ export class Storage {
return keys;
}

/** @deprecated - Use addKeysSafe */
async addKeys(params: { name: string; keys: KeyImport[]; encryptionKey: string }) {
const { name, keys, encryptionKey } = params;
let open = true;
Expand Down Expand Up @@ -189,4 +195,64 @@ export class Storage {
const { name, limit, skip } = params;
return this.storageType.getAddresses({ name, limit, skip });
}

async addKeysSafe(params: { name: string; keys: KeyImport[] }) {
const { name, keys } = params;
let i = 0;
for (const key of keys) {
const { path } = key;
const pubKey = key.pubKey;
// key.privKey is encrypted - cannot be directly used to retrieve pubKey if required
if (!pubKey) {
throw new Error(`pubKey is undefined for ${name}. Keys not added to storage`);
}
let payload = {};
if (pubKey) {
payload = { key: JSON.stringify(key), pubKey, path };
}
const toStore = JSON.stringify(payload);
// open on first, close on last
await this.storageType.addKeys({ name, key, toStore, open: i === 0, keepAlive: i < keys.length - 1 });
++i;
}
}

async getStoredKeys(params: { addresses: string[]; name: string }): Promise<Array<any>> {
const { addresses, name } = params;
const keys = new Array<any>();
let i = 0;
for (const address of addresses) {
try {
const key = await this.getStoredKey({
name,
address,
open: i === 0, // open on first
keepAlive: i < addresses.length - 1, // close on last
});
keys.push(key);
} catch (err) {
// don't continue from catch - i must be incremented
console.error(err);
}
++i;
}
return keys;
}

private async getStoredKey(params: {
address: string;
name: string;
keepAlive: boolean;
open: boolean;
}): Promise<any> {
const { address, name, keepAlive, open } = params;
const payload = await this.storageType.getKey({ name, address, keepAlive, open });
const json = JSON.parse(payload) || payload;
const { key } = json; // pubKey available - not needed
if (key) {
return JSON.parse(key);
} else {
return json;
}
}
}
Loading