Author

Topic: Python script to extract information from Peach wallet/software (Read 175 times)

hero member
Activity: 1274
Merit: 681
I rather die on my feet than to live on my knees
Do you mind correcting your spelling error (exrtract) in your thread's title? It hampers searching by keywords in thread titles somewhat and on a minor level it's also maybe a bit embarrassing to have errors in such prominent locations. You could also lock this thread, now that you got a working solution.

Those typos comes from my laptop keyboard having some keys almost dead and I almot have to hammer them to make them to work. And then, what happens is that other keys also get pressed when I hammer the one I wanted.

If I know ow to lock it, I'll will!

Thanks
hero member
Activity: 714
Merit: 1010
Crypto Swap Exchange
Do you mind correcting your spelling error (exrtract) in your thread's title? It hampers searching by keywords in thread titles somewhat and on a minor level it's also maybe a bit embarrassing to have errors in such prominent locations. You could also lock this thread, now that you got a working solution.
hero member
Activity: 1274
Merit: 681
I rather die on my feet than to live on my knees
The private key I'm looking for is:
Quote
KxEd..............B9L

and the one I'm getting is:
Quote
33.................3cZ
That's because WIF isn't just ECDSA Private key encoded to base58. (you've asked ChatGPT?)
It needs to have a "network byte", "compression flag" (to tell clients to derive compressed pubKey) and a 4-Byte "checksum".
To do that, you can also use "bitcoinlib.encoding" and import "double_sha256" to calculate the checksum.

With that, add these to your code: (remove 'print' if you do not want to output those)
Code:
.
.
.
from bitcoinlib.encoding import double_sha256
.
.
.
# Prepend Network Byte and Append Compression Flag
prvKey_data = (b'\x80' + key.private_byte + b'\x01')
print("prvKey_data:",bytes.hex(prvKey_data))

# Calculate the Checksum
checksum = double_sha256(prvKey_data)
print("checksum:",bytes.hex(checksum[0:4]))

# Replace this part:
# private_key_base58 = base58encode(key.private_byte)
# With this:
private_key_base58 = base58encode(prvKey_data + checksum[0:4])

Well, who knows, knows... Thank you. It computed correctly the private key in the format I wanted.
Thank you
legendary
Activity: 2534
Merit: 6080
Self-proclaimed Genius
The private key I'm looking for is:
Quote
KxEd..............B9L

and the one I'm getting is:
Quote
33.................3cZ
That's because WIF isn't just ECDSA Private key encoded to base58. (you've asked ChatGPT?)
It needs to have a "network byte", "compression flag" (to tell clients to derive compressed pubKey) and a 4-Byte "checksum".
To do that, you can also use "bitcoinlib.encoding" and import "double_sha256" to calculate the checksum.

With that, add these to your code: (remove 'print' if you do not want to output those)
Code:
.
.
.
from bitcoinlib.encoding import double_sha256
.
.
.
# Prepend Network Byte and Append Compression Flag
prvKey_data = (b'\x80' + key.private_byte + b'\x01')
print("prvKey_data:",bytes.hex(prvKey_data))

# Calculate the Checksum
checksum = double_sha256(prvKey_data)
print("checksum:",bytes.hex(checksum[0:4]))

# Replace this part:
# private_key_base58 = base58encode(key.private_byte)
# With this:
private_key_base58 = base58encode(prvKey_data + checksum[0:4])
hero member
Activity: 1274
Merit: 681
I rather die on my feet than to live on my knees
A further bit closer. I can now obtain a PK in the format I want. It's just that it's not the correct PK.

The private key I'm looking for is:
Quote
KxEd..............B9L

and the one I'm getting is:
Quote
33.................3cZ

This is the code I have now:
Code:
from bitcoinlib.keys import HDKey
from bitcoinlib.mnemonic import Mnemonic
from bitcoinlib.encoding import base58encode

# Example seed phrase (mnemonic)
mnemonic_phrase = "word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 "

# Create a Mnemonic object
mnemonic = Mnemonic()
# Convert the mnemonic phrase to a seed (binary)
seed = mnemonic.to_seed(mnemonic_phrase)

# Create an HD wallet from the seed
hdkey = HDKey.from_seed(seed, network='bitcoin')

# Derive a private key using the specific derivation path (m/84'/0'/0'/0/2)
key = hdkey.subkey_for_path("m/84'/0'/0'/0/2")

# Ensure we have the private key (HDKey object with private key)
if not key.is_private:
    key = key.to_private()

# Get the private key in WIF format
private_key_wif = key.wif()

# Get the corresponding native SegWit address
bitcoin_address = key.address(encoding='bech32')

# Encode the private key in base58 (just for demonstration, typically WIF is
# already base58 encoded)
private_key_base58 = base58encode(key.private_byte)

print("Private key (WIF):", private_key_wif)
print("Bitcoin address:", bitcoin_address)
print("Private key (Base58):", private_key_base58)
hero member
Activity: 1274
Merit: 681
I rather die on my feet than to live on my knees
So, I'm a little bit closer but the private key is still wrong and is presenting actually an extended public key instead:

Code:
from bitcoinlib.keys import HDKey
from bitcoinlib.mnemonic import Mnemonic

# Example seed phrase (mnemonic)
mnemonic_phrase = "word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 "

# Create a Mnemonic object
mnemonic = Mnemonic()
# Convert the mnemonic phrase to a seed (binary)
seed = mnemonic.to_seed(mnemonic_phrase)

# Create an HD wallet from the seed
hdkey = HDKey.from_seed(seed, network='bitcoin')

# Derive a private key using the specific derivation path (m/84'/0'/0'/0/2)
key = hdkey.subkey_for_path("m/84'/0'/0'/0/2")

# Ensure we have the private key (HDKey object with private key)
if not key.is_private:
    key = key.to_private()

# Get the private key in WIF format
private_key_wif = key.wif()
# Get the corresponding native SegWit address
bitcoin_address = key.address(encoding='bech32')

print("Private key (WIF):", private_key_wif)
print("Bitcoin address:", bitcoin_address)

But the output I get is this:
Quote
Private key (WIF): xpub6Gds........................................NcHTzM
Bitcoin address: bc1qn8l7k52dsl2kmpemq8ualqpc3uk8lyvt7grmyp

So, ig anyone knows how to make this write, please gimme a help! Smiley
hero member
Activity: 1274
Merit: 681
I rather die on my feet than to live on my knees
Hello.

So, this Peach piece of software to purchase Bitcoin via P2P. It has a non-custodial HD wallet. But as far as I know, it doesn't allow you to see your Bitcoin addresses and PKs.
It allows you to backup the wallet with an encrypted file.

So, I tried 2 things:
1 - Try to get the PKs out of the seed prase
2 - Try to check if the backup file would have the PKs in plain text after decryption

Not needed to say that I couldn't make any of them to work, at least to get the PKs like Bitcoin Core shows when we use dumpprivkey, which if I'm not mistaken, is the random number in hex format and base58 enconded.

As a starting point I went to Peach Github and tried to figure out which functions I coul be interested in. I found a couple.

https://github.com/Peach2Peach/peach-app/blob/develop/src/utils/wallet/getDescriptorSecretKey.ts

Code:
import { DescriptorSecretKey, Mnemonic } from "bdk-rn";
import { Network, WordCount } from "bdk-rn/lib/lib/enums";

export const getDescriptorSecretKey = async (
  network: Network,
  seedphrase?: string,
) => {
  let mnemonic = new Mnemonic();

  if (seedphrase) {
    mnemonic = await mnemonic.fromString(seedphrase);
  } else {
    mnemonic = await mnemonic.create(WordCount.WORDS12);
  }

  return new DescriptorSecretKey().create(network, mnemonic);
};


https://github.com/Peach2Peach/peach-app/blob/develop/src/utils/file/writeFile.ts
Code:
import RNFS from "react-native-fs";
import { encrypt } from "../crypto/encrypt";
import { error } from "../log/error";
import { info } from "../log/info";

export const writeFile = async (
  path: string,
  content: string,
  password?: string,
): Promise => {
  info(password ? "Writing encrypted file" : "Writing file", path);
  let encrypted;
  try {
    if (password) {
      encrypted = encrypt(content, password);
    } else {
      encrypted = content;
    }
  } catch (e) {
    error("Data could not be encrypted", e);
    return false;
  }
  try {
    await RNFS.writeFile(RNFS.DocumentDirectoryPath + path, encrypted, "utf8");
    return true;
  } catch (e) {
    error("File could not be written", e);
    return false;
  }
};

And also:
https://github.com/Peach2Peach/peach-app/blob/develop/src/utils/file/readChunkOfFile.ts

Code:
import RNFS from "react-native-fs";

export const readChunkOfFile = (
  uri: string,
  chunksize: number,
  index: number,
) => RNFS.read(uri, chunksize, index, "utf8");

I know nothing about TypeScript and my Python knowledge is also quite limited. And I also don't know all details and aspects of HD wallets, derivation paths, etc, so I went for chatGPT for some help.
I already knew about bitcoinlib python library so I asked chatGPT to use that library and after some adjustments I got one of the codes working for situation 1 where I provide the seed and I get the keys out.
However, what the code gives me is an extended private key, as far as I can tell. And that's not what I'm looking for.

This is the code I got working and returning the zrpv key:
Code:
from bitcoinlib.wallets import Wallet, wallet_create_or_open
from bitcoinlib.wallets import wallet_delete_if_exists
import asyncio


async def get_descriptor_secret_key(network: str, seedphrase: str = None,
                                    name='My Wallet'):
    delete_wallet(name)
    if seedphrase:
        # Use the provided seed phrase to generate a wallet
        wallet = Wallet.create(name=name, keys=seedphrase,
                               network=network, witness_type='segwit')
    else:
        # Generate a new wallet with a new seed phrase
        wallet = wallet_create_or_open(name, witness_type='segwit',
                                       network=network)

    # Print wallet info
    print("Wallet Info:")
    print("Address:", wallet.new_key().address)
    print("Key:", wallet.new_key())
    # print("Descriptor:", wallet.descriptor)
    return wallet


def delete_wallet(name="My Wallet"):
    try:
        wallet_delete_if_exists(name)
        print(f"Wallet '{name}' was deleted.")
    except Exception as e:
        print(f"Faile to delete wallet '{name}': {str(e)}")


# Define the network ('bitcoin' or 'testnet')
network_name = 'bitcoin'

# Optionally, provide a seed phrase
seed_phrase = "word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 word1 word1"
# Example seed phrase
asyncio.run(get_descriptor_secret_key(network_name, seed_phrase))

The output I get from this script is:
Quote
Wallet 'My Wallet' was deleted.
Wallet Info:
Address: bc1qd.........my
Key:

As a last resort, I imported the seed phrase into Electrum and from there, I can get all the PKs of the wallet. So, what do I need to change to get the keys in Base58 format? Or what can I do to achieve my goal?

Edited;
There was someone that gave me a JavaScript code but I don't know how to work with JavaScript and I also tried to convert his function to Python but I got some UTF8 encoding issue that I didn't spend too much time trying to fix.
Code:
const CryptoJS = require('crypto-js');
const fs = require('fs');

fs.readFile('./peach-account.json.enc', 'utf8', function(err, data) {
    const res = CryptoJS.AES.decrypt(data, "").toString(CryptoJS.enc.Utf8);
    fs.writeFile('./peach-account.json', res, function(err) {
        if(err) {
            return console.log(err);
        }
        console.log("The file was saved!");
    })
});
Jump to: