bitcoinjs学习1—P2PKH

1. 概述

在本学习笔记中,我们将深入探讨如何使用 bitcoinjs-lib 库构建和签名一个 P2PKH(Pay-to-PubKey-Hash) 比特币交易。P2PKH 是比特币网络中最常见和最基本的交易类型之一,理解其工作原理是掌握比特币交易构建的关键。

想要详细了解p2pkh工作原理可以查看该网址p2pkh(Pay to PublicKey Hash)


1.1 目标

  • 主要目标 :学习如何使用 bitcoinjs-lib 构建、签名并广播一个 P2PKH 交易。
  • 具体任务
    1. 生成一个 P2PKH 地址。
    2. 构建一个包含输入和输出的交易。
    3. 将交易广播到regtest网络中。

1.2 P2PKH 的工作原理

  1. 锁定资金 : 发送方创建一个交易输出,将比特币锁定到接收方的公钥哈希(即地址)。
  2. 解锁资金
  • 接收方在花费比特币时,需要提供:
    • 公钥。
    • 使用私钥生成的签名。
  • 输入脚本(scriptSig)格式:<Signature> <PublicKey>
  1. 验证过程
    • 比特币网络会验证签名是否与公钥匹配,以及公钥哈希是否与输出脚本中的哈希一致。

在比特币脚本中,OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIGP2PKH(Pay-to-PubKey-Hash) 交易的 锁定脚本(scriptPubKey) 的核心部分。它定义了如何锁定比特币以及如何解锁比特币的条件。

以下是对每个操作码的详细解释:


1. OP_DUP

  • 作用:复制栈顶元素。
  • 操作
    • 将栈顶的元素复制一份,并将副本压入栈中。
  • 示例
    • 如果栈顶是 A,执行 OP_DUP 后,栈变为 [A, A]

2. OP_HASH160

  • 作用:对栈顶元素进行哈希运算。
  • 操作
    • 弹出栈顶元素,先进行 SHA-256 哈希,再进行 RIPEMD-160 哈希,最后将结果压入栈中。
  • 示例
    • 如果栈顶是 A,执行 OP_HASH160 后,栈变为 [Hash160(A)]

3. <PubKeyHash>

  • 作用:将接收方的公钥哈希压入栈中。
  • 操作
    • 这是一个数据推送操作,将接收方的公钥哈希(20 字节)压入栈中。
  • 示例
    • 如果接收方的公钥哈希是 B,执行后栈变为 [Hash160(A), B]

4. OP_EQUALVERIFY

  • 作用:验证栈顶两个元素是否相等,并移除它们。
  • 操作
    • 弹出栈顶两个元素,比较它们是否相等。
    • 如果相等,继续执行;如果不相等,脚本执行失败。
  • 示例
    • 如果栈顶是 [Hash160(A), B],执行 OP_EQUALVERIFY 后:
      • 如果 Hash160(A) == B,栈为空,继续执行。
      • 如果 Hash160(A) != B,脚本失败。

5. OP_CHECKSIG

  • 作用:验证签名和公钥是否匹配。
  • 操作
    • 弹出栈顶两个元素:签名和公钥。
    • 使用公钥验证签名是否对当前交易有效。
    • 如果验证成功,将 true 压入栈中;否则,将 false 压入栈中。
  • 示例
    • 如果栈顶是 [Signature, PublicKey],执行 OP_CHECKSIG 后:
      • 如果签名有效,栈变为 [true]
      • 如果签名无效,栈变为 [false]

6. 整体脚本的执行流程

假设解锁脚本(scriptSig)提供了签名和公钥:<Signature> <PublicKey>,锁定脚本(scriptPubKey)为 OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

执行步骤:
  1. 初始栈

    • 解锁脚本将签名和公钥压入栈中:

      复制代码
      [Signature, PublicKey]
  2. 执行 OP_DUP

    • 复制公钥:

      复制代码
      [Signature, PublicKey, PublicKey]
  3. 执行 OP_HASH160

    • 对公钥进行哈希:

      复制代码
      [Signature, PublicKey, Hash160(PublicKey)]
  4. 压入 <PubKeyHash>

    • 将接收方的公钥哈希压入栈中:

      复制代码
      [Signature, PublicKey, Hash160(PublicKey), PubKeyHash]
  5. 执行 OP_EQUALVERIFY

    • 比较 Hash160(PublicKey)PubKeyHash
      • 如果相等,移除这两个元素:

        复制代码
        [Signature, PublicKey]
      • 如果不相等,脚本失败。

  6. 执行 OP_CHECKSIG

    • 使用公钥验证签名:
      • 如果签名有效,栈变为:

        复制代码
        [true]
      • 如果签名无效,栈变为:

        复制代码
        [false]
  7. 脚本结果

    • 如果栈顶为 true,交易有效,资金可以解锁。
    • 如果栈顶为 false,交易无效,资金无法解锁。

1.3 工具和技术栈

1.3.1 bitcoinjs-lib
  • 简介bitcoinjs-lib 是一个用于处理比特币交易的 JavaScript 库。它支持构建、签名和解析比特币交易,适用于 Node.js 和浏览器环境。

  • 安装

bash 复制代码
# 选择一个目录作为项目目录,打开vscode,打开命令行窗口
npm init -y

# 安装依赖
npm i bitcoinjs-lib ecpair tiny-secp256k1
1.3.2 比特币测试网
  • 简介 :比特币测试网(Testnet)是一个用于开发和测试的比特币网络,其功能与主网相同,但比特币没有实际价值。(新手建议还是使用regtest,testnet测试网需要申请领取测试币,每次领取金额都较少,一般为10000聪)
  • 用途
    • 测试交易构建和签名。
    • 避免在主网上浪费真实比特币。
  • 获取测试网比特币
    • 使用测试网水龙头(如 testnet-faucet.com)获取免费的测试网比特币。
1.3.3 比特币测试网浏览器

1.4 p2pkh交易demo

1.4.1准备工作

交易:Alice向Bob转账2个比特币

先使用bitcoin-core向Alice的地址转2个比特币,再用这笔未花费交易给Bob发送

在交易记录中右击刚刚的交易,复制原始交易,再根据上一节预备知识中提到的解析交易

或者使用控制台(命令行窗口)

bash 复制代码
sendtoaddress mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r 2
getrawtransaction txid
decoderawtransaction hex

记录交易的txid、vout中是alice的输出的次序n,金额value

以下是Alice给Bob发送比特币的代码(使用p2pkh)

javascript 复制代码
const bitcoin = require('bitcoinjs-lib');
const network = bitcoin.networks.regtest;

ECPairFactory = require('ecpair').default;
ecc = require('tiny-secp256k1');
const base58check = require('base58check');  //npm i --save base58check
ECPair = ECPairFactory(ecc);
const hashtype = bitcoin.Transaction.SIGHASH_ALL;

// Alice给Bob发送2个比特币
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex',);
const keyPairAlice = ECPair.fromPrivateKey(ONE, { network });
const TWO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000002', 'hex',);
const keyPairBob = ECPair.fromPrivateKey(TWO, { network });
// alice_address: mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r
// bob_address: mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H

// alice上一笔未花费的交易的txid,
const txid = '4a3cc600d4973479aafb62b5789590dd7738c3f9f1956a513a361e20ef56f9bb'
const vout = 1; // 上一笔交易中的输出
const amount = BigInt(200000000);// 转账金额

// 收款人bob的地址
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPairBob.publicKey })
var PayeeBobAddr = base58check.decode(address, 'hex')
console.log('Bob_hashPubkey:', Buffer.from(PayeeBobAddr.data, 'Hex').toString('hex'));


const tx = new bitcoin.Transaction(network);

// 添加输入
tx.addInput(Buffer.from(txid, 'hex').reverse(), vout);

// 添加输出
const outputScript = bitcoin.payments.p2pkh({ pubkey: keyPairBob.publicKey }).output;
tx.addOutput(outputScript, amount - BigInt(10000));

// 添加签名
// 上一笔交易的输出脚本
const preoutputScript = bitcoin.payments.p2pkh({ pubkey: keyPairAlice.publicKey }).output;
// 生成比特币交易中某个输入的签名哈希值(sighash)。hashtype=1表示对交易的所有输入都进行哈希处理
const signhash = tx.hashForSignature(0, preoutputScript, hashtype);

// 构造p2pkh的解锁脚本: [签名 公钥]
const scriptSig = bitcoin.script.signature.encode(keyPairAlice.sign(signhash), hashtype);
tx.setInputScript(0,
    bitcoin.script.compile([scriptSig, keyPairAlice.publicKey])
)

// 输出交易原始数据
console.log('Raw Transaction:', tx.toHex());

// 广播交易
//sendrawtransaction <raw_transaction_hex>

// 获取txHex: getrawtransaction txid
// 解析交易: decoderawtransaction txHex
相关推荐
kida_yuan11 天前
【以太来袭】4. Geth 原理与解析
区块链
西岸行者13 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意13 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码13 天前
嵌入式学习路线
学习
毛小茛13 天前
计算机系统概论——校验码
学习
babe小鑫13 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms13 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下13 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。13 天前
2026.2.25监控学习
学习
im_AMBER13 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode