什么是BitcoinJ?

加密货币是一种安全、去中心化的价值存储方式,采用点对点(P2P)网络进行交易的传播和验证。

BitcoinJ是一个 Java 库,它简化了创建比特币应用程序的过程,使用户能够无缝地执行加密货币交易。

在本教程中,我们将深入研究 BitcoinJ 的主要功能和组件。此外,我们还将探索如何创建钱包、为钱包充值以及如何将一些硬币发送到另一个钱包。

什么是BitcoinJ?
BitcoinJ 是一个 Java 库,用于简化创建比特币应用程序的过程。它提供了创建和管理比特币钱包、发送和接收交易以及与比特币主网 mainnet、testnet 和 regtest 网络集成的工具。

此外,它还提供简化支付验证(SPV)来与比特币网络交互,而无需下载整个区块链。

BitcoinJ 的特点
BitcoinJ 允许我们轻松创建比特币钱包,包括生成地址、管理私钥和公钥以及处理用于钱包恢复的种子短语。

此外,它还提供发送和接收比特币交易的功能,使我们能够构建一个可以处理比特币转账的应用程序。

此外,它还支持与现实世界中进行比特币交易的比特币主网 (mainnet) 集成。此外,它还支持用于测试和原型设计的 testnet 和 regtest 网络。

最后,它允许事件监听器响应各种事件,例如传入的交易或区块链中的变化。

基本设置
要开始与库交互,让我们将bitcoinj-core依赖项添加到pom.xml:

<dependency>
    <groupId>org.bitcoinj</groupId>
    <artifactId>bitcoinj-core</artifactId>
    <version>0.17-alpha4</version>
</dependency>

此依赖项提供了Wallet和WalletKitApp类来创建比特币钱包。

此外,让我们添加用于日志记录的slf4j-api和slf4j-simple  依赖项:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.1.0-alpha1</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>2.1.0-alpha1</version>
</dependency>

这些依赖关系对于记录应用程序的活动至关重要。

.钱包类
钱包是加密货币的重要组成部分,因为它提供了管理交易的功能。BitcoinJ 库提供了Wallet和WalletKitApp类来创建钱包。

1. 创建钱包
在创建钱包之前,我们需要定义钱包与之交互的网络。

BitcoinJ 支持三种类型的网络——用于生产的主网、用于测试的测试网和用于本地回归测试的 regtest。

让我们创建NetworkParameters对象来连接到测试网络:

 NetworkParameters params = TestNet3Params.get();
接下来,让我们创建一个接受NetworkParameters作为参数的Wallet实例:

void createWallet() throws IOException {
    Wallet wallet = Wallet.createDeterministic(params, Script.ScriptType.P2PKH);
    File walletFile = new File("baeldung.dat");
    wallet.saveToFile(walletFile);
}

在上述方法中,我们创建了一个与测试网络交互的Wallet 。我们使用P2PKH 脚本类型,该脚本类型代表 Pay-to-Pubkey-Hash 地址,这是比特币地址的常见类型。最后,我们将钱包保存为baeldung.dat。

值得注意的是,钱包地址、种子短语以及公钥和私钥是在创建钱包时生成的。

2. 检索钱包详细信息
创建钱包后,我们可以检索地址、公钥、私钥和种子短语等基本详细信息:

Wallet loadWallet() throws IOException, UnreadableWalletException {
    File walletFile = new File("baeldung.dat");
    Wallet wallet = Wallet.loadFromFile(walletFile);
    logger.info(
"Address: " + wallet.currentReceiveAddress().toString());
    logger.info(
"Seed Phrase: " + wallet.getKeyChainSeed().getMnemonicString());
    logger.info(
"Balance: " + wallet.getBalance().toFriendlyString());
    logger.info(
"Public Key: " + wallet.findKeyFromAddress(wallet.currentReceiveAddress()).getPublicKeyAsHex());
    logger.info(
"Private Key: " + wallet.findKeyFromAddress(wallet.currentReceiveAddress()).getPrivateKeyAsHex());
    return wallet;
}

在这里,我们通过调用Wallet实例上的loadFromFile()方法从baeldung.dat文件加载钱包。然后,我们将钱包的地址、种子短语和余额记录到控制台。

此外,我们将公钥和私钥记录到控制台。

3. 恢复钱包
如果我们无法访问钱包文件但有种子短语,我们可以使用种子短语恢复钱包:

Wallet loadUsingSeed(String seedWord) throws UnreadableWalletException {
    DeterministicSeed seed = new DeterministicSeed(seedWord, null, "", Utils.currentTimeSeconds());
    return Wallet.fromSeed(params, seed);
}

在上面的代码中,我们创建了一个DeterministicSeed 对象,它接受种子短语和时间作为参数。然后我们调用Wallet.fromSeed()方法,传递params和seed作为参数。这将根据提供的种子短语和网络参数创建一个新的Wallet实例。

4. 保护钱包
我们需要确保私钥和种子短语不被未经授权的访问。获得私钥的访问权限可让攻击者访问我们的钱包并花掉可用资金。

BitcoinJ 提供了encrypt()方法来为我们的钱包设置密码:

// ...
wallet.encrypt(
"password");
wallet.saveToFile(walletFile);
// ...

在这里,我们在Wallet 对象上调用encrypt() 方法。它接受预期的密码作为参数。钱包加密后,任何人都无法在不先解密钱包的情况下访问私钥:

// ...
Wallet wallet = Wallet.loadFromFile(walletFile);
wallet.decrypt(
"password");
// ...

在这里,我们使用加密钱包的密码来解密钱包。值得注意的是,务必将钱包文件、种子短语和私钥保密,不让外部来源获取。

连接到对等组网络
目前,我们的钱包处于隔离状态,无法识别交易,因为它与区块链不同步。我们需要连接到对等组网络:

void connectWalletToPeer() throws BlockStoreException, UnreadableWalletException, IOException {
    Wallet wallet = loadWallet();
    BlockStore blockStore = new MemoryBlockStore(params);
    BlockChain chain = new BlockChain(params, wallet, blockStore);
    PeerGroup peerGroup = new PeerGroup(params, chain);
    peerGroup.addPeerDiscovery(new DnsDiscovery(params));
    peerGroup.addWallet(wallet);
    peerGroup.start();
    peerGroup.downloadBlockChain();
}

在这里,我们首先加载钱包,然后创建一个BlockStore实例,将区块链存储在内存中。此外,我们创建一个BlockChain实例,用于管理比特币背后的数据结构。

最后,我们创建一个PeerGroup 实例来建立网络连接。这些类对于与测试网络交互以及将我们的钱包与网络同步是必需的。

但是下载整个区块链可能会耗费大量资源。因此,WalletKitApp 类默认使用 SPV 来简化此过程。

WalletKitApp 类
BitcoinJ 提供了WalletKitApp类,简化了设置钱包的过程。该类抽象了创建BlockStore、BlockChain和PeerGroup实例,使其更容易与比特币网络配合使用。

让我们使用 WalletKitApp 创建一个钱包,并在创建后记录钱包详细信息:

NetworkParameters params = TestNet3Params.get();
WalletAppKit kit = new WalletAppKit(params, new File("."), "baeldungkit") {
    @Override
    protected void onSetupCompleted() {
        logger.info(
"Wallet created and loaded successfully.");
        logger.info(
"Receive address: " + wallet().currentReceiveAddress());
        logger.info(
"Seed Phrase: " + wallet().getKeyChainSeed());
        logger.info(
"Balance: " + wallet().getBalance().toFriendlyString());
        logger.info(
"Public Key: " + wallet().findKeyFromAddress(wallet().currentReceiveAddress())
          .getPublicKeyAsHex());
        logger.info(
"Private Key: " + wallet().findKeyFromAddress(wallet().currentReceiveAddress())
          .getPrivateKeyAsHex());
        wallet().encrypt(
"password");
    }
};
kit.startAsync();
kit.awaitRunning();
kit.setAutoSave(true);;

在这里,我们使用测试网参数设置一个新钱包,并指定存储钱包数据的目录。我们异步启动WalletAppKit对象。我们启用自动保存以在本地存储最新的钱包信息。

以下是钱包的详细信息:

[ STARTING] INFO com.baeldung.bitcoinj.Kit - Wallet created and loaded successfully.
[ STARTING] INFO com.baeldung.bitcoinj.Kit - Receive address: moqVLcdRFjyXehgRAK5bJBK6rDN2vq14Wc
[ STARTING] INFO com.baeldung.bitcoinj.Kit - Seed Phrase: DeterministicSeed{unencrypted}
[ STARTING] INFO com.baeldung.bitcoinj.Kit - Balance: 0

该钱包目前有零比特币。

添加事件监听器
我们可以向钱包添加事件监听器来响应各种事件,例如传入的交易:

kit.wallet()
  .addCoinsReceivedEventListener((wallet, tx, prevBalance, newBalance) -> {
      logger.info("Received tx for " + tx.getValueSentToMe(wallet));
      logger.info(
"New balance: " + newBalance.toFriendlyString());
  });

上述事件监听器记录传入的交易以及交易 ID 和收到的金额。

此外,当我们将硬币发送到另一个钱包时,让我们添加一个事件监听器来记录当前钱包余额:

kit.wallet()
  .addCoinsSentEventListener((wallet, tx, prevBalance, newBalance) -> logger.info("new balance: " + newBalance.toFriendlyString()));

上面的代码监听一个事件以将硬币从钱包中发送出来并记录新的余额。

发送比特币
我们可以通过创建Coin实例并指定要发送的金额,轻松地将比特币发送到另一个地址:

String receiveAddress = "n1vb1YZXyMQxvEjkc53VULi5KTiRtcAA9G";
Coin value = Coin.valueOf(200);
final Coin amountToSend = value.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
final Wallet.SendResult sendResult = kit.wallet()
  .sendCoins(kit.peerGroup(), Address.fromString(params, receiveAddress), amountToSend);

在上面的代码中,我们指定了要发送200 satoshi 的地址。此外,我们减去矿工的交易费。最后,我们调用sendCoins(),启动转移硬币的过程。