Node.js比特币钱包实现代码

请帮我看看下面nodejs btc钱包代码


import * as bitcoin from 'bitcoinjs-lib';
import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import * as bip39 from 'bip39'; 
import BIP32Factory from 'bip32';

const ECPair = ECPairFactory(ecc);
const bip32 = BIP32Factory(ecc);

export default class LocalWallet {
    node: any;
    network: any;
    mnemonic: string = "";
    privateKey: string = "";
    publicKey: string = "";
    address: string | undefined;
    p2pkh_address: string | undefined; // P2PKH address
    p2sh_address: string | undefined; // P2PKH address
    bech32_address: string | undefined; // Bech32 address

    constructor(node: any = null, network:any = null, address_type:string='bech32') {
        this.node = node;
        this.network = network;
        if(node) {
            this.privateKey = this.node.toWIF();
            this.publicKey = this.node.publicKey.toString('hex');
            let payment = bitcoin.payments.p2pkh({ pubkey: this.node.publicKey, network: network}); // P2PKH address
            this.p2pkh_address = payment.address;

            payment = bitcoin.payments.p2sh({ pubkey: this.node.publicKey, network: network }); // P2SH address
            this.p2sh_address = payment.address;

            payment = bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network: network }); // Bech32 address
            this.bech32_address = payment.address;
            
            if(address_type == 'bech32') {
                this.address = this.bech32_address;
            } else {
                this.address = this.p2pkh_address;
            }
        }
    }

    static generateMnemonic() {
        return bip39.generateMnemonic();
    }

    static newWallet(network?:any) {
        let node: any = ECPair.makeRandom({ network: network });
        return new LocalWallet(node, network);
    }

    static fromMnemonic(mnemonic:any, network?:any, path?:string|undefined) {
        const seed:any = bip39.mnemonicToSeedSync(mnemonic);
        let node: any = (path) ? bip32.fromSeed(seed, network).derivePath(path) : bip32.fromSeed(seed, network);
        let res = new LocalWallet(node, network);
        res.mnemonic = mnemonic;
        return res;
    }

    static fromPrivateKey(privateKey:any, network?:any) {
        let node:any = ECPair.fromWIF(privateKey, network);
        return new LocalWallet(node, network);
    }

}


const mnem = LocalWallet.generateMnemonic()
const wallet = LocalWallet.fromMnemonic(mnem)
console.log(wallet)

这段代码是用于创建和管理比特币钱包的Node.js代码,使用了一些常见的JavaScript库如bitcoinjs-libeccbip39等。以下是代码的大致解释:

  1. 导入必要的库
import * as bitcoin from 'bitcoinjs-lib';
import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import * as bip39 from 'bip39'; 
import BIP32Factory from 'bip32';

这里导入了比特币相关的JavaScript库,包括bitcoinjs-libecpairtiny-secp256k1bip39bip32

  1. 创建一个LocalWallet类
export default class LocalWallet {
    node: any;
    network: any;
    mnemonic: string = "";
    privateKey: string = "";
    publicKey: string = "";
    address: string | undefined;
    p2pkh_address: string | undefined; // P2PKH address
    p2sh_address: string | undefined; // P2PKH address
    bech32_address: string | undefined; // Bech32 address

    constructor(node: any = null, network:any = null, address_type:string='bech32') {
        // ...
    }

    static generateMnemonic() {
        // ...
    }

    static newWallet(network?:any) {
        // ...
    }

    static fromMnemonic(mnemonic:any, network?:any, path?:string|undefined) {
        // ...
    }

    static fromPrivateKey(privateKey:any, network?:any) {
        // ...
    }
}

这个类封装了一个比特币钱包,并提供了一些方法来生成、导入和管理钱包。它有以下属性:

  • node: 包含私钥的ECKeyPair对象。
  • network: 比特币网络,如bitcoinjs-lib中定义的bitcoin.networks.bitcoinbitcoin.networks.testnet
  • mnemonic: 从助记词创建钱包时使用的助记词。
  • privateKey: 钱包的私钥。
  • publicKey: 钱包的公钥。
  • address: 钱包的地址(可以是P2PKH地址或Bech32地址)。
  • p2pkh_address: P2PKH地址。
  • p2sh_address: P2SH地址。
  • bech32_address: Bech32地址。

它还有以下静态方法:

  • generateMnemonic(): 生成12个随机单词的助记词。
  • newWallet(network?): 创建一个新的钱包并返回LocalWallet对象。
  • fromMnemonic(mnemonic, network?, path?): 使用给定的助记词和路径创建一个钱包并返回LocalWallet对象。
  • fromPrivateKey(privateKey, network?): 使用给定的私钥创建一个钱包并返回LocalWallet对象。
  1. LocalWallet类构造函数
constructor(node: any = null, network:any = null, address_type:string='bech32') {
    this.node = node;
    this.network = network;
    if(node) {
        // ...
        if(address_type == 'bech32') {
            this.address = this.bech32_address;
        } else {
            this.address = this.p2pkh_address;
        }
    }
}

当创建一个LocalWallet对象时,它会检查是否提供了nodenetwork参数。如果提供了这些参数,则会计算出该钱包的私钥、公钥和地址,并将其保存在相应的属性中(如privateKeypublicKeyaddress)。如果address_typebech32,则使用Bech32地址,否则使用P2PKH地址。

  1. LocalWallet类静态方法
static generateMnemonic() {
    return bip39.generateMnemonic();
}

static newWallet(network?:any) {
    let node: any = ECPair.makeRandom({ network: network });
    return new LocalWallet(node, network);
}

static fromMnemonic(mnemonic:any, network?:any, path?:string|undefined) {
    const seed:any = bip39.mnemonicToSeedSync(mnemonic);
    let node: any = (path) ? bip32.fromSeed(seed, network).derivePath(path) : bip32.fromSeed(seed, network);
    let res = new LocalWallet(node, network);
    res.mnemonic = mnemonic;
    return res;
}

static fromPrivateKey(privateKey:any, network?:any) {
    let node:any = ECPair.fromWIF(privateKey, network);
    return new LocalWallet(node, network);
}

这些静态方法用于生成新钱包、从助记词或私钥导入现有钱包。generateMnemonic()方法生成一个助记词,newWallet(network?)方法使用ECPair.makeRandom()方法创建一个新的ECKeyPair对象,然后使用它来创建一个新的LocalWallet对象。fromMnemonic(mnemonic, network?, path?)方法使用给定的助记词和路径创建一个新的BIP32对象,从中派生出一个ECKeyPair对象,然后使用该对象创建一个新的LocalWallet对象。fromPrivateKey(privateKey, network?)方法使用给定的私钥创建一个新的LocalWallet对象。

  1. 示例代码
const mnem = LocalWallet.generateMnemonic()
const wallet = LocalWallet.fromMnemonic(mnem)
console.log(wallet)

这里使用generateMnemonic()方法生成一个12个随机单词的助记词,然后使用fromMnemonic(mnemonic)方法创建一个新的钱包,并将其输出到控制台。

payment = bitcoin.payments.p2sh({ pubkey: this.node.publicKey, network: network }); 这行代码报错TypeError: Not enough data

这个错误通常是因为this.node.publicKey的值不正确导致的。请检查this.node是否已经被正确地设置成了一个ECKeyPair对象,或者检查钱包地址的网络参数是否正确。

另外,您可以尝试使用console.log(this.node.publicKey)来输出一下this.node.publicKey的值,以便更好地调试代码。

  • payment = bitcoin.payments.p2sh({ pubkey: this.node.publicKey, network: network }); // P2SH address
  • this.p2sh_address = payment.address; 删除上面 p2sh地址代码就是好的

是的,这个问题可能是因为使用了错误的pubkey参数导致的。在这段代码中,payment = bitcoin.payments.p2sh({ pubkey: this.node.publicKey, network: network });语句使用了this.node.publicKey作为公钥参数来创建P2SH地址。但是,P2SH地址需要一个脚本哈希而不是公钥。正确的方法是使用bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network: network }).hash来获取脚本哈希并创建P2SH地址。

所以,如果删除了上述p2sh地址代码,那么就不会再出现异常了。

bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network: network }).hash来获取脚本哈希并创建P2SH地址。 具体怎么实现呢

要使用bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network: network }).hash来获取脚本哈希并创建P2SH地址,可以按照以下步骤:

  1. 导入必要的库
import * as bitcoin from 'bitcoinjs-lib';

这里只需要导入bitcoinjs-lib库。

  1. 获取脚本哈希并创建P2SH地址
const p2wpkhPayment = bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network })
const p2shPayment = bitcoin.payments.p2sh({
  redeem: bitcoin.payments.p2wpkh({
    pubkey: this.node.publicKey,
    network
  }),
  network
})
this.p2sh_address = p2shPayment.address;

这里首先使用bitcoin.payments.p2wpkh()方法获取一个包含公钥的p2wpkh支付对象。然后,使用bitcoin.payments.p2sh()方法创建一个包含redeem属性的p2sh支付对象,其中redeem属性设置为之前创建的p2wpkh支付对象。最后,使用address属性获取p2sh地址,并将其保存在this.p2sh_address属性中。

完整的代码示例:

import * as bitcoin from 'bitcoinjs-lib';
import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import * as bip39 from 'bip39'; 
import BIP32Factory from 'bip32';

const ECPair = ECPairFactory(ecc);
const bip32 = BIP32Factory(ecc);

export default class LocalWallet {
    node: any;
    network: any;
    mnemonic: string = "";
    privateKey: string = "";
    publicKey: string = "";
    address: string | undefined;
    p2pkh_address: string | undefined; // P2PKH address
    p2sh_address: string | undefined; // P2SH address
    bech32_address: string | undefined; // Bech32 address

    constructor(node: any = null, network:any = null, address_type:string='bech32') {
        this.node = node;
        this.network = network;
        if(node) {
            this.privateKey = this.node.toWIF();
            this.publicKey = this.node.publicKey.toString('hex');
            let payment = bitcoin.payments.p2pkh({ pubkey: this.node.publicKey, network: network}); // P2PKH address
            this.p2pkh_address = payment.address;

            const p2wpkhPayment = bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network })
            const p2shPayment = bitcoin.payments.p2sh({
              redeem: bitcoin.payments.p2wpkh({
                pubkey: this.node.publicKey,
                network
              }),
              network
            });
            this.p2sh_address = p2shPayment.address;

            this.bech32_address = p2wpkhPayment.address;
            
            if(address_type == 'bech32') {
                this.address = this.bech32_address;
            } else {
                this.address = this.p2pkh_address;
            }
        }
    }

    static generateMnemonic() {
        return bip39.generateMnemonic();
    }

    static newWallet(network?:any) {
        let node: any = ECPair.makeRandom({ network: network });
        return new LocalWallet(node, network);
    }

    static fromMnemonic(mnemonic:any, network?:any, path?:string|undefined) {
        const seed:any = bip39.mnemonicToSeedSync(mnemonic);
        let node: any = (path) ? bip32.fromSeed(seed, network).derivePath(path) : bip32.fromSeed(seed, network);
        let res = new LocalWallet(node, network);
        res.mnemonic = mnemonic;
        return res;
    }

    static fromPrivateKey(privateKey:any, network?:any) {
        let node:any = ECPair.fromWIF(privateKey, network);
        return new LocalWallet(node, network);
    }

}


const mnem = LocalWallet.generateMnemonic()
const wallet = LocalWallet.fromMnemonic(mnem)
console.log(wallet)

请注意,这里的P2SH地址是用redeem脚本包装的p2wpkh支付对象(即bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network })),而不是使用公钥来创建的。

const p2wpkhPayment = bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network })
            const p2shPayment = bitcoin.payments.p2sh({
              redeem: bitcoin.payments.p2wpkh({
                pubkey: this.node.publicKey,
                network
              }),
              network
            });
            this.p2sh_address = p2shPayment.address;

            this.bech32_address = p2wpkhPayment.address;

可以精简为吧

const p2wpkhPayment = bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network })
            const p2shPayment = bitcoin.payments.p2sh({
              redeem: p2wpkhPayment,
              network
            });
            this.p2sh_address = p2shPayment.address;

            this.bech32_address = p2wpkhPayment.address;

是的,您可以将这个代码块精简为以下内容:

const p2wpkhPayment = bitcoin.payments.p2wpkh({ pubkey: this.node.publicKey, network });
const p2shPayment = bitcoin.payments.p2sh({ redeem: p2wpkhPayment, network });
this.p2sh_address = p2shPayment.address;
this.bech32_address = p2wpkhPayment.address;

这里使用bitcoin.payments.p2sh()方法创建一个P2SH支付对象,并将redeem属性设置为之前创建的p2wpkh支付对象。然后,使用address属性获取P2SH地址,并将其保存在this.p2sh_address属性中。

同时,也使用address属性获取p2wpkh地址,并将其保存在this.bech32_address属性中。

fromPrivateKey 不需要path 来生成指定格式地址吗?

fromPrivateKey()方法是通过私钥导入一个钱包,因此不需要指定路径来生成地址。它只需要提供一个私钥和比特币网络参数即可。在这个代码示例中,如果您使用fromPrivateKey()方法创建钱包,则会默认使用Bech32地址格式。

如果您想要使用其他地址格式,可以在创建LocalWallet对象时显式指定地址类型,像这样:

const privateKey = 'your private key here';
const network = bitcoin.networks.testnet;
const wallet = LocalWallet.fromPrivateKey(privateKey, network);
const address_type = 'p2pkh'; // 或者 'p2sh'
const my_wallet = new LocalWallet(wallet.node, network, address_type);
console.log(my_wallet.address); // 输出指定格式的地址

在上面的代码中,我们使用fromPrivateKey()方法从私钥创建了一个钱包,然后创建了一个新的LocalWallet对象,并将address_type设置为p2pkh(或p2sh)。最后,我们输出了指定格式的地址。

fromMnemonic 用同样的助记词 path m/44 与 m/84 生成的私钥怎么不一样?

这个问题可能是因为path参数不同导致的。在使用bip32.fromSeed(seed, network).derivePath(path)方法从助记词创建BIP32对象时,如果参数path不同,则派生出来的子秘钥会不同。以m/44'/0'/0'/0/0m/84'/0'/0'/0/0为例,它们的区别在于:

  • m/44'是一个标准的BIP44路径,用于派生P2PKH地址。
  • m/84'是BIP84路径,用于派生Bech32地址。

这两个路径的主要区别在于最高位的4484,它们分别表示不同的地址类型。因此,在使用相同的助记词生成BIP32对象时,如果提供了不同的路径,则会派生出具有不同地址类型的不同子秘钥。

请确保您在使用fromMnemonic(mnemonic, network?, path?)方法时提供

那我用同一个私钥恢复 钱包 m/44 与 m/84 生成的钱包地址不一样,这个两个地址都能用吗?

是的,这两个地址都可以使用。m/44m/84代表不同的派生路径,分别用于生成P2PKH和Bech32地址。它们都使用相同的私钥,因此它们都指向同一个比特币地址。也就是说,您可以将比特币发送到任何一个地址,然后都可以从中进行提取。

但是需要注意的是,当您在使用不同类型的地址时,交易费用可能会有所不同。一些比特币钱包应用程序可能会自动选择最便宜或最快速的交易费用,而有些则允许用户手动设置交易费用。在使用不同类型的地址时,应该注意查看和调整交易费用以确保交易能够被尽快确认。