bitcoinjs学习笔记2-P2SH

news/2025/2/25 17:13:47

BitcoinJS 学习笔记 2 - P2SH

1.概述

  • 1.1 P2SH 是什么?
    • 1.1.1 定义:Pay to Script Hash
      • P2SH 允许你将比特币发送到一个“脚本的哈希值”上,而不是像P2PKH直接发送到一个“公钥的哈希值”上
      • 即P2SH的地址的脚本的哈希值,而P2PKH的地址是公钥的哈希值。
  • 1.2 P2SH 地址
    • 1.2.1 生成方式:对赎回脚本进行哈希运算,并进行 Base58Check 编码
      • 赎回脚本 (redeemScript) 是 P2SH 交易中用于花费比特币的脚本。
      • 生成 P2SH 地址时,首先需要对赎回脚本进行哈希运算 HASH160(通常是 SHA256 + RIPEMD160),然后将哈希值进行 Base58Check 编码。
    • 1.2.2 与 P2PKH 地址的区别
      • 锁定脚本不同: P2PKH 使用公钥哈希锁定比特币,而 P2SH 使用脚本哈希锁定比特币。
      • 功能不同: P2PKH 只能实现简单的支付功能,而 P2SH 可以实现更复杂的支付逻辑,例如多重签名、时间锁等。
  • 1.3 P2SH锁定脚本与解锁脚本
    在这里插入图片描述
  • scriptSig (解锁脚本):包含Signatures签名 和 redeemScript赎回脚本。
  • scriptPubKey(锁定脚本): 图中的scrtipHash是**赎回脚本**经过HASH160`处理得到的哈希值

想要详细了解p2sh工作原理可以查看该网址:p2pkh(Pay to Script Hash)
(如果使用校园网可能打不开该网址,搭梯子可能也打不开,可以手机开热点给电脑用)


bitcoinjsP2SH_26">2.bitcoinjs实现P2SH过程

  • 2.1 创建 P2SH 地址

    • 构建赎回脚本 (redeemScript)**
      • 多重签名脚本

        • 多重签名脚本需要指定多个公钥和所需的签名数量。
        • 创建一个 2-of-3 的多重签名脚本,可以借助p2ms(多重签名协议)来生成 2-of-3 的多重签名脚本
          const pubkeys = [
              '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
              '02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5',
              '02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'
          ];
          const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys, network });
          const redeemScript = p2ms.output;
          console.log("redeemScript(p2ms):", Buffer.from(p2ms.output).toString('hex'));
          

          控制台打印redeemScript: 52210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f953ae
          在p2sh网站中有解析脚本16进制字符串的工具以及HASH160工具,点击即可展开使用
          在这里插入图片描述2-of-3多重签名脚本为:OP_2 PUBKEY1 PUBKEY2 PUBKEY3 OP_CHECKMULTISIG
          OP_PUSHBYTES_33表示将33字节压入栈中
          在这里插入图片描述15fc0754e73eb85d1cbce08786fadb7320ecb8dcredeemScript经过HASH160处理后得到的哈希值,等会生成p2sh地址后,使用bitcoin-core的控制台发送2个比特币后,可以查看该交易中对应的输出,查看锁定脚本中的赎回脚本哈希是否与我们现在得到的一致。

      • 其他复杂脚本

        • 除了多重签名脚本,P2SH 还可以支持其他复杂脚本,例如时间锁脚本、哈希锁脚本等。
        • 可以使用 BitcoinJS 提供的 script 模块构建各种复杂的赎回脚本。
  • 2.2 生产p2sh地址

    使用bitcoin.payment来生成各种协议的地址

    const p2sh = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network }, network })
    console.log('p2sh.address: ', p2sh.address);
    
  • 2.3 构建 P2SH 交易

    • 2.3.1 输入部分
      • 引用之前的交易输出
        • 需要指定要花费的 UTXO 的交易 ID 和输出索引。
      • 提供解锁脚本 (scriptSig)
        • 解锁脚本用于证明你有权花费该 UTXO。
        • 对于 P2SH 交易,解锁脚本需要包含签名和赎回脚本,对于2.2提及的2-of-3多重签名脚本(作为赎回脚本)需要提供2个有效的签名,只要三个公钥中有两个公钥可以验证这两个签名即有效。
    • 2.3.2 输出部分
      • 指定新的 P2SH 地址
        • 将比特币锁定到新的 P2SH 地址。
      • 设置转账金额
        • 指定要转账的比特币数量。
  • 2.4 签名和广播交易

    • 2.4.1 使用私钥对交易进行签名
      • 使用 BitcoinJS 提供的 ECPair 模块创建密钥对,并使用私钥对交易进行签名。
    • 2.4.2 将交易广播到比特币网络
      • 使用 bitcoin-core的控制台将交易广播到比特币网络。

3.代码示例

查看以下代码前,可以再看看该网站 https://learnmeabitcoin.com/technical/script/p2sh/ 学习

3.1 示例1—2-0f-3多重签名**

使用2-0f-3多重签名作为赎回脚本
解锁脚本需提供两个签名,以及赎回脚本,要求3个公钥中有两个公钥可以验证这两个签名,满足即可解锁资金

  • 生成使用2-0f-3多重签名作为赎回脚本的p2sh地址
const bitcoin = require('bitcoinjs-lib');
const network = bitcoin.networks.regtest;

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

const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex',);
const keyPairAlice = ECPair.fromPrivateKey(ONE, { network });
const TWO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000002', 'hex',);
const keyPairBob = ECPair.fromPrivateKey(TWO, { network });
const THTEE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000003', 'hex',);
const keyPairCandy = ECPair.fromPrivateKey(THTEE, { network });
// alice_address: mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r
// bob_address: mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H


// 1.赎回脚本 2-of-3 多重签名
// 1.1生成公钥
const pubkeys = [
    Buffer.from(keyPairAlice.publicKey, 'hex'),
    Buffer.from(keyPairBob.publicKey, 'hex'),
    Buffer.from(keyPairCandy.publicKey, 'hex'),
];
// console.log("pubkeys:", pubkeys[0], pubkeys[1], pubkeys[2]);
// pubkeys: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9

// 1.2使用p2ms来生成赎回脚本
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys, network });
console.log("redeemScript(p2ms):", Buffer.from(p2ms.output).toString('hex'));
const redeemScript = p2ms.output;
/**
 * redeemScript(p2ms) hex: 52210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f953ae
 * 将hex转成asm的工具地址:https://learnmeabitcoin.com/technical/script/p2sh/
 * redeemScript(p2ms) asm: OP_2 OP_PUSHBYTES_33 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_PUSHBYTES_33 02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 OP_PUSHBYTES_33 02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 OP_3 OP_CHECKMULTISIG
 */

// 2.生成p2sh地址
const p2sh = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network }, network })
console.log('p2sh.address: ', p2sh.address);
// p2sh.address:  2MuFU6ZyBLtDNadMA6RnwJdXGWUSUaoKLeS
// 使用bitcoin-core向p2sh地址转账一个比特币

使用bitcoin-core查看p2sh的地址:2MuFU6ZyBLtDNadMA6RnwJdXGWUSUaoKLeS
向该地址发送一个比特币,查看锁定脚本中scrtipHash是否与2.1中赎回脚本使用HASH160工具生成的哈希值(15fc0754e73eb85d1cbce08786fadb7320ecb8dc )一致,经检验一致。
在这里插入图片描述
至此,我们得到一笔使用 2-of-3多重签名作为赎回脚本的交易,接下来我们将使用这笔交易。

  • 构建 P2SH 交易
// 3.构造交易
const previousTxHex0 = '02000000000101351878f0e784388627348ddaf93ee9647038fa78900e00fc24314b08ca083f2e0000000000fdffffff02d4241a1e0100000017a914dee8a50fd4fd2c59509a420a13f5bd0b13ae9b4d8700c2eb0b0000000017a91415fc0754e73eb85d1cbce08786fadb7320ecb8dc87024730440220300f13f139dfec5bd0c3a38aeed97b77a0f1565af5d936345d6b11dc98cc59900220406193d6c37900d8ad221f4a565aec9ffdaa5ff2a8f29f4a03da69f1e19bb1780121036fd98cc721091b19f3347383d1beadc8ae5a17351e0db0f491476725d0122d8400000000';
const utxo0 = bitcoin.Transaction.fromHex(previousTxHex0);
const txid = utxo0.getId(); // 上一笔交易的txid
const vout = 1; // 上一笔交易中的输出
console.log("txid: ", Buffer.from(txid, 'hex').toString('hex')); 

const tx = new bitcoin.Transaction(network);

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

// 3.4添加输出 输出地址为Bob的p2pkh地址
const outputScript = bitcoin.address.toOutputScript('mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H', network);
tx.addOutput(outputScript, BigInt(utxo0.outs[vout].value - BigInt(10000)));

// 3.2构造 one & three 的签名 (one, two, therr其中两个即可)
const signhash = tx.hashForSignature(0, p2shOutputScript, hashtype); // 生成待签名消息
const sigAlice = keyPairAlice.sign(signhash);
console.log("sigAlice:", Buffer.from(sigAlice).toString('hex'));
const sigBob = keyPairBob.sign(signhash);
const sigCandy = keyPairCandy.sign(signhash);

// 6.确保签名符合 DER 规范
const canonicalSignatureAlice = bitcoin.script.signature.encode(sigAlice, hashtype);
const canonicalSignatureBob = bitcoin.script.signature.encode(sigBob, hashtype);
const canonicalSignatureCandy = bitcoin.script.signature.encode(sigCandy, hashtype);

// 3.3构造p2sh的解锁脚本 [sigAlice sigBob redeemScript]
const scriptSig = bitcoin.script.compile([
    canonicalSignatureAlice,
    canonicalSignatureBob,
    redeemScript
]);
console.log("scriptSig:", Buffer.from(scriptSig).toString('hex'));
tx.setInputScript(0, scriptSig);

// 3.5输出交易原始数据
console.log('decoderawtransaction ' + '"' + tx.toHex() + '"');

console.log('sendrawtransaction ' + '"' + tx.toHex() + '"');

记录问题:使用bitcoin-core控制台进行广播,出现错误:
mandatory-script-verify-flag-failed (Operation not valid with the current stack size) (code -26)
仍未解决

3.2 示例2(数学问题作为赎回脚本)

前言:6+7数学问题作为赎回脚本可以看看这个网页 p2sh-6+7,开始学习p2sh也是看的这个例子

const bitcoin = require('bitcoinjs-lib')
ECPairFactory = require('ecpair').default;
ecc = require('tiny-secp256k1');
ECPair = ECPairFactory(ecc);
const network = bitcoin.networks.regtest

// 1.设置赎回脚本 两个数相加等于13
const redeemScript = bitcoin.script.compile([
    bitcoin.opcodes.OP_ADD,
    bitcoin.opcodes.OP_13,
    bitcoin.opcodes.OP_EQUALVERIFY,
    bitcoin.opcodes.OP_1
]);
console.log('redeem script: ' + Buffer.from(redeemScript).toString('hex'));

// 2.生成p2sh地址
const p2sh = bitcoin.payments.p2sh({ redeem: { output: redeemScript, network }, network })
console.log('p2sh.address: ', p2sh.address);
// 2NCibkYCCdQrMcQPoPdbTAtUGtcsFtYPpnh 使用bitcoin-core向p2sh地址转账2个比特币
const outputScript = bitcoin.address.toOutputScript(p2sh.address, network);
console.log('Output Script: ' + Buffer.from(outputScript).toString('hex'));
const asmCode = bitcoin.script.toASM(outputScript);
console.log('Output Script ASM:', asmCode);

// 3.构造交易
const previousTxHex0 = '02000000000101c90a36e787b617b67cf58dbddec91c3dede6371c3d106ae481df0c4abed9c2400000000000fdffffff02d4241a1e0100000017a914465d18f6b11821907549d27bd3685b1d679ab9718700c2eb0b0000000017a914d597c9a73051aa37cebefb2fae06b16a860cc801870247304402206c359b88506ae84b53dec33b8d1826ae08fd5d55006efe91b645236f928e2e3a02204326e1e1775407322e6a50a47c1ba2591267112c879c5d9fffef50f4ad6bcd6f0121036fd98cc721091b19f3347383d1beadc8ae5a17351e0db0f491476725d0122d8400000000';
const utxo0 = bitcoin.Transaction.fromHex(previousTxHex0);
const txid = utxo0.getId();
const voutidx = 1; // 上一笔交易中的输出的顺序
// console.log(utxo0.outs[1].value);
// console.log("txid: ", Buffer.from("txid", 'hex').toString('hex'));

const tx = new bitcoin.Transaction(network);

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

// 3.2 创建解锁脚本
const scriptSig = bitcoin.script.compile([
    bitcoin.opcodes.OP_6, // 代表数字6
    bitcoin.opcodes.OP_7, // 代表数字7
    redeemScript
]);
tx.setInputScript(0, scriptSig);

// 3.3添加输出 输出地址为Bob的p2pkh地址
const outputScript_bob = bitcoin.address.toOutputScript('mg8Jz5776UdyiYcBb9Z873NTozEiADRW5H', network);
tx.addOutput(outputScript_bob, BigInt(utxo0.outs[voutidx].value - BigInt(10000)));

// 3.4输出交易原始数据
console.log('decoderawtransaction ' + '"' + tx.toHex() + '"');
console.log('\n');
console.log('sendrawtransaction ' + '"' + tx.toHex() + '"');

四、 总结

  • 4.1 P2SH 的意义和应用场景

    • P2SH 是比特币脚本系统的重要组成部分,它为比特币带来了更强大的功能和灵活性。
    • P2SH 广泛应用于多重签名钱包、时间锁合约、哈希锁合约等场景。
  • 4.2 目的:实现更复杂的交易脚本,增强比特币脚本的灵活性

    • 缩短交易脚本长度,节省区块空间: 将复杂脚本移到交易输入中,交易输出只需存储脚本哈希值,大大减少了交易体积。
    • 增强隐私性,隐藏脚本细节: 交易输出只显示脚本哈希值,无法直接看出脚本的具体内容,提高了交易的隐私性。
    • 支持多重签名等复杂脚本: P2SH 为多重签名、时间锁等复杂脚本的实现提供了便利,极大地扩展了比特币脚本的应用场景。

http://www.niftyadmin.cn/n/5865752.html

相关文章

服务器能否拒绝非浏览器发起的HTTP请求?

互联网各领域资料分享专区(不定期更新): Sheet 前言 服务器可以采取多种方法来拒绝非浏览器发起的HTTP请求,但需要明确的是:HTTP协议本身并不限制客户端类型,任何符合协议规范的请求都会被处理。因此,拒绝非浏览器请求需依赖额外策略。 正文 一、基于请求头过滤 1、Us…

【备赛】点亮LED

LED部分的原理图 led前面有锁存器,这是为了防止led会受到lcd的干扰(lcd也需要用到这些引脚)。 每次想要对led操作,就需要先打开锁存器,再执行操作,最后关闭锁存器。 这里需要注意的是,引脚配置…

mysql 学习17 SQL 锁

概述 全局锁 通过全局锁 进行数据备份 表级锁 表锁 元数据锁 意向锁 原先 A线程 开启了一个事务,udpate id 3的数据,就会有一个行级锁,锁定第三行 这时候如果B线程要 lock tables 这个表 read,那么理论上就要锁定这一行表。 那…

8.spring对logback的支持

文章目录 一、入口二、源码解析LoggingApplicationListener 三、其它支持四、总结 本节以logback为背景介绍的 一、入口 gav: org.springframework.boot:spring-boot:3.3.4 spring.factories文件中有如下两个配置 org.springframework.boot.logging.LoggingSystemFactory\ …

【Java 8】Lambda表达式介绍

目录 1、Lambda简介 2、语法介绍 3、Lambda表达式示例 3.1、无参数的 Lambda 表达式 3.2、单个参数的 Lambda 表达式 3.3、多个参数的 Lambda 表达式 3.4、带语句块的 Lambda 表达式 4、Lambda使用场景 4.1、替代匿名内部类 4.2、集合操作 4.3、排序 4.4、函数式接口…

Java使用EasyExcel实现异步导出

以下是使用 EasyExcel 工具类实现异步导出功能的 Demo,包括用户发起导出请求后,系统先返回响应,后台读取数据并上传至 COS,最后通知用户下载的完整流程。 实现步骤 用户发起导出请求 前端调用导出接口,后端立即返回响应…

【Linux】Ubuntu中,如何创建软件的快捷方式放到桌面上

本文主要介绍Ubuntu中,如何创建软件的快捷方式放到桌面上 首先进入到/usr/share/applications/路径下,找到自己想要的软件,这里以我的vim为例子 ricardoDESKTOP-8T8LHV5:/usr/share/applications$ ls byobu.desktop io.snapcraft.SessionA…

AI回答:Linux C/C++编程学习路线

Linux C/C编程学习路线需要结合Linux系统特性和C/C语言的特点,以下是一个系统化的学习路径,适合从初学者到进阶者: 第一阶段:Linux基础 Linux操作系统基础 学习Linux基本命令:ls、cd、mkdir、rm、grep、find等。 理解…