diff --git a/BTC/payment/P2PKH.md b/BTC/payment/P2PKH.md index 1fc997190..2f1289657 100644 --- a/BTC/payment/P2PKH.md +++ b/BTC/payment/P2PKH.md @@ -79,9 +79,9 @@ P2PKH支付方式从公钥生成地址遵循以下详细步骤: 假设有一个公钥,步骤如下: -1. **公钥**: `04480b6822120e9936b43859d84c380583c3d0292409b21453ae962815090f8117883c2a3fd7571b12f34491809d48467dae4e2f162aef23de91e4532d0fc1e0c5` -2. **SHA-256哈希**:计算结果。 -3. **RIPEMD-160哈希**:计算结果。 +1. **公钥**: `022c3ee7cdb92394e32e82ec5bc8860c8888df6a9910537e90c75079726a2a8469` +2. **SHA-256哈希**:计算结果: `98a21cf747c0fcd80845afffa2260feb64aa7cc2b73ac681407b25229bad3bbc`。 +3. **RIPEMD-160哈希**:计算结果: `999ff7726530ed0d0a7eb3b7442c5143f643f638`。 4. **添加版本字节**:`00` + [RIPEMD-160哈希结果]。 5. **计算校验和**:对上述结果执行两次SHA-256,取前四个字节。 -6. **Base58编码**:将最终字节串转换为Base58编码:13GZvxKVCw1YadDCBJ7rAuerLiYGXLQBVp +6. **Base58编码**:将最终字节串转换为Base58编码:`1F1J22NAAgnNHXNojT7W8GfP9JSXXjTV8N` diff --git a/BTC/payment/P2SH-P2PKH.md b/BTC/payment/P2SH-P2PKH.md new file mode 100644 index 000000000..3ec7e608d --- /dev/null +++ b/BTC/payment/P2SH-P2PKH.md @@ -0,0 +1,113 @@ +# 简介 + +[P2SH-P2PKH(Pay to Script Hash - Pay to Public Key Hash)](../wallet/address.js#L43)是一种混合地址类型,结合了P2SH(支付到脚本哈希)和P2PKH(支付到公钥哈希)的特点。这种地址类型旨在增强比特币交易的灵活性和安全性。`P2SH`是在2012年通过比特币改进提案BIP 0016引入的,允许用户发送比特币到一个脚本的哈希值,而不是直接发送到公钥的哈希值。这样做的好处是可以隐藏脚本的具体细节,只有在花费比特币时才需要揭示。这对于实现更复杂的交易类型(如多重签名)非常有用。`P2SH-P2PKH`结合了`P2SH`和`P2PKH`的特性。在这种地址类型中,交易输出不是直接支付到公钥哈希,而是支付到一个包含`P2PKH`脚本的`P2SH`地址。`P2SH-P2PKH`地址通常以“3”开始,不同于传统的`P2PKH`地址,后者以“1”开始。这种地址类型是比特币地址方案中的一种进阶形式,适用于需要额外安全或复杂交易类型的用户。 + +# 工作过程 + +## ScriptPubKey(锁定脚本) + +在P2SH-P2PKH交易中,锁定脚本不直接包含传统的P2PKH脚本。相反,它包含了对应P2PKH脚本的哈希值。锁定脚本的形式如下: + +```js +// 模版 +OP_HASH160 OP_EQUAL +其中 +scriptHash = hash160(OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG) + +OP_HASH160 // 弹出栈顶元素,计算其SHA-256散列,然后计算RIPEMD-160散列 +OP_PUSHBYTES_20 // 推送20字节到栈顶 +18e1fad25b2983d5dbb2e2b96e3ce756a69b3bc2 // 脚本哈希 +OP_EQUAL // 比较栈顶两个元素相等 +``` + +## ScriptSig(解锁脚本) + +要解锁花费此脚本,上面公钥的所有者需要提供公钥,有效签名以及原始脚本 + +```js +// 模版 + + +OP_PUSHBYTES_72 // 推送72字节到栈顶 +3045022100c233c3a8a510e03ad18b0a24694ef00c78101bfd5ac075b8c1037952ce26e91e02205aa5f8f88f29bb4ad5808ebc12abfd26bd791256f367b04c6d955f01f28a772401 // 签名数据 +OP_PUSHBYTES_33 // 推送33字节到栈顶 +03480b6822120e9936b43859d84c380583c3d0292409b21453ae962815090f8117 // 压缩公钥 +OP_PUSHBYTES_25 // 推送25字节到栈顶 +76a9148876d7c7a76e29c4c9d160c066fead1d2adfe5a788ac // 赎回脚本 +``` + +## 执行过程 + +1. 脚本合并,ScriptSig在前ScriptPubKey在后 + +```js + OP_HASH160 OP_EQUAL + +其中 +redeemScript = OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG + +scriptHash = hash160(OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG) +``` + +2. 栈内执行 + +- 提取和验证`redeemScript`: + 首先,从`ScriptSig`中提取`redeemScript`。 + 然后,计算`redeemScript`的哈希值,并检查它是否与`ScriptPubKey`中的``相匹配。 + +- 执行`redeemScript`: + 如果`redeemScript`的哈希值验证通过,接下来的步骤是执行`redeemScript`本身。 + 这时,栈中应该已经有了签名``和公钥``。 + redeemScript首先复制公钥`OP_DUP`,计算公钥的哈希`OP_HASH160`,并与栈中的公钥哈希``进行比较`OP_EQUALVERIFY`。 + 如果公钥哈希匹配,最后一步是验证签名是否正确`OP_CHECKSIG`。这一步检查提供的签名是否是用相关的私钥对交易的其余部分进行了签名。 + +- 完成验证: + 如果以上步骤都成功,说明交易中的资金可以被花费,否则交易将被拒绝。 + +## 地址生成 + +P2SH-P2PKH地址生成遵循以下步骤,这些步骤与生成P2PKH地址的步骤相似,但涉及一些关键的不同之处,特别是在脚本的使用和版本字节的应用上。 + +### 步骤1: 创建并哈希化 `P2PKH` 脚本 + +1. **创建 `P2PKH` 脚本**: + - 构建一个典型的P2PKH脚本:`OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG`,其中``是公钥的RIPEMD-160哈希。 +2. **计算脚本的SHA-256哈希值**: + - 对整个脚本进行SHA-256哈希运算。 +3. **计算RIPEMD-160哈希值**: + - 接着,对SHA-256的结果再进行RIPEMD-160哈希运算。这个哈希值称为脚本哈希(Script Hash)。 + +### 步骤2: 添加版本字节 + +- **添加版本字节**: + - 在脚本哈希前添加版本字节(比特币主网络的P2SH地址的版本是0x05, 测试网是0xc4)。这有助于钱包软件识别和处理P2SH地址。 + +### 步骤3: 计算校验和 + +1. **双重SHA-256哈希**: + - 对带有版本字节的脚本哈希进行两次SHA-256哈希运算。 +2. **取前四个字节**: + - 从双重哈希的结果中取出前四个字节作为校验和。 + +### 步骤4: 生成地址 + +- **组合和Base58编码**: + - 将版本字节、脚本哈希以及校验和组合在一起,结构如下: + ```js + [version byte][script hash][checksum] + ``` + - 然后,对整个字节串进行Base58编码,生成最终的P2SH-P2PKH地址。 + +### 示例 + +假设有一个公钥,步骤如下: + +1. **公钥**: `04480b6822120e9022c3ee7cdb92394e32e82ec5bc8860c8888df6a9910537e90c75079726a2a8469936b43859d84c380583c3d0292409b21453ae962815090f8117883c2a3fd7571b12f34491809d48467dae4e2f162aef23de91e4532d0fc1e0c5` +2. **SHA-256哈希**:计算结果: `98a21cf747c0fcd80845afffa2260feb64aa7cc2b73ac681407b25229bad3bbc` +3. **RIPEMD-160哈希**:计算结果: `999ff7726530ed0d0a7eb3b7442c5143f643f638` +4. **创建并哈希化 P2PKH 脚本**: + 创建一个P2PKH脚本:OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG。 + 计算脚本哈希:将整个P2PKH脚本进行SHA-256哈希运算,然后再进行RIPEMD-160哈希运算,生成脚本哈希。 +5. **添加版本字节**:`05` + [RIPEMD-160哈希结果]。 +6. **计算校验和**:对上述结果执行两次SHA-256,取前四个字节。 +7. **Base58编码**:将最终字节串转换为Base58编码:`3NB2grHkCeer9o7Guvr6qCXCsDsjX3mTan` diff --git a/BTC/payment/P2TR.md b/BTC/payment/P2TR.md new file mode 100644 index 000000000..18a54bc6b --- /dev/null +++ b/BTC/payment/P2TR.md @@ -0,0 +1,68 @@ +# 简介 + +[P2TR(Pay to Taproot)](../wallet/address.js#L45)是一种使用Taproot技术的比特币地址类型,由BIP 0341在2020年正式引入。Taproot是通过使用Schnorr签名和MAST(Merkelized Abstract Syntax Trees)来增强比特币的隐私性和效率。Taproot Output 是版本为 1 的隔离见证 Output,统一了这两种形式。P2TR 的 Output 的 scriptPubKey 字段是一样的,我们无法从 Output 的格式来得知这个 Output 是由 Schnorr 签名锁定(即 Key Path)还是由脚本锁定(即 Script Path),这样有更好的隐私。这种类型的地址支持更复杂的条件,同时在外部看起来与普通交易无异。P2TR地址通常使用Bech32m编码格式,以“bc1p”开始。 + +# 工作过程 + +## ScriptPubKey(锁定脚本) + +在P2TR交易中,锁定脚本包括: + +```js +// 模版 +OP_1 + + +OP_1 // 表示版本号,对于P2TR是1 +OP_PUSHBYTES_20 // 推送20字节到栈顶 +18e1fad25b2983d5dbb2e2b96e3ce756a69b3bc2 // tweaked-public-key +``` + +- `OP_1`:标识Taproot版本号,目前为1。 +- ``:是tweak公钥的X坐标,key path和script path在生成方式上不同,采用32字节的压缩格式。 + +## Witness(见证数据) + +解锁P2TR输出,需要在见证字段提供以下信息,具体取决于花费条件: + +```js +// 模版 +[signature] [control block] [script] + +01 // Witness 元素的个数 +40 // schnorr签名长度 +743bbb3df4e95df5e70c2a3e72dd9d05933a018ad1bbd4b700841824ed18bebbc54d1b4d03b252aaa5c373390c68ba119fdf70e0bab71694a4db0da3fe8fefcc // schnorr签名 +``` + +- `signature`:Schnorr签名。 +- `control block`:如果是通过脚本路径花费,包含与Taproot脚本路径相关的数据 +- `script`:如果是通过脚本路径花费,此处包含实际脚本。 + +## 执行过程 + +P2TR分为key path和script path,通过在花费 P2TR UTXO 时,Witness 中包含元素个数来区分,只包含一个元素签名的即为key path。签名算法可以参考[Schnorr](../signature/README.md#L105),P2TR的验签过称可以参考[taproot](https://aandds.com/blog/bitcoin-taproot.html) + +## 地址生成 + +P2TR地址包含key path和script path两种情况,目前常见的钱包支持基于用户公钥的key path地址,改地址的生成过程如下: + +### 步骤1: 获取用户公钥 + +获取用户公钥的`internalPubkey`坐标 `P` + $P = toXOnly(internalPubkey)$ + +### 步骤1: 计算扭曲公钥 + +$Q = P + t\*G = P + TaggedHash('TapTweak', P)G$ + +### 步骤2: Bech32m编码 + +使用Bech32m编码方案将曲公钥编码为一个P2TR地址。这种编码格式提供了比Bech32更好的错误校验能力。 + +### 示例 + +假设有一个公钥的X坐标,步骤如下: + +1. **公钥X坐标**: `2c3ee7cdb92394e32e82ec5bc8860c8888df6a9910537e90c75079726a2a8469` +2. **扭曲公钥X坐标**: `84d67b14669b9eccf5fa76ac48294527e44e829a4534f0e695061e5a1a5a5c20` +3. **Bech32m编码**:将扭曲公钥X坐标转换为Bech32m编码:`bc1psnt8k9rxnw0vea06w6kys229yljyaq56g560pe54qc095xj6tssq3fpwfp` diff --git a/BTC/payment/P2WPKH.md b/BTC/payment/P2WPKH.md new file mode 100644 index 000000000..f8884e9a6 --- /dev/null +++ b/BTC/payment/P2WPKH.md @@ -0,0 +1,71 @@ +# 简介 + +[P2WPKH(Pay to Witness Public Key Hash)](../wallet/address.js#L44)是一种新型地址格式,它是隔离见证(SegWit)的一部分,由比特币改进提案BIP 0141在2017年引入。这种地址类型主要目的是减小交易的大小,从而降低手续费,同时增加交易的修改抵抗力(malleability resistance)。`P2WPKH`简化了标准的`P2PKH`地址脚本,将签名和公钥信息从交易的脚本部分移至见证部分,这样做有助于更高效的签名验证和交易大小的减小。`P2WPKH`地址通常以“bc1”开始,使用Bech32编码格式,与传统的`P2PKH`和`P2SH`地址格式(分别以“1”和“3”开始)显著不同。 + +# 工作过程 + +## ScriptPubKey(锁定脚本) + +在P2WPKH交易中,锁定脚本变得非常简洁: + +```js +// 模版 +OP_0 + +OP_0 // 表示版本号,对于P2WPKH是0 +OP_PUSHBYTES_20 // 推送20字节到栈顶 +18e1fad25b2983d5dbb2e2b96e3ce756a69b3bc2 // 公钥哈希 +``` + +- `OP_0`:表示版本号,对于P2WPKH是0。 +- ``:是公钥的SHA-256哈希后再进行RIPEMD-160哈希的结果,和传统P2PKH的处理方式相同。 + +## Witness(见证数据) + +为了解锁P2WPKH输出,需要在见证字段提供以下信息: + +```js +// 模版 + + +02 // 见证数据的数量 +48 // 第一个见证数据的长度72字节 +3045022100a5eec95c65d2dd679e8fdcc10668d5d51446f24d1557859cb543e570201257c6022011df04f803a08c97de213ddf211ad193045ff25e59857324a28656de1961351c01 // 签名数据 +21 // 第二个见证数据的长度33字节 +0295f97f41d0d523bddf39e77e16cc0dda2e56b9bd9fdea10133656635b2c28a39 // 压缩公钥 +``` + +## 执行过程 + +1. 脚本合并同P2PKH,使用Witness替换ScriptSig在前,ScriptPubKey在后 + +```js + OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG +``` + +2. 栈内执行通P2PKH + +- OP_DUP 从栈里取出公钥,复制并做HASH160推进栈顶 +- OP_EQUALVERIFY 对比上一步的计算结果和Public Key Hash +- OP_CHECKSIG 校验数字签名,如果通过锁定脚本合法可以花费对应UTXO,如果不通过交易失败 + +## 地址生成 + +P2WPKH地址的生成过程如下: + +### 步骤1: 计算公钥哈希 + +- 使用公钥生成哈希:首先对公钥进行SHA-256,然后进行RIPEMD-160哈希运算。 + +### 步骤2: Bech32编码 + +- 使用Bech32编码方案将公钥哈希编码为一个P2WPKH地址。这包括将数据与校验和一起编码,提供了更好的错误检测能力。 + +### 示例 + +假设有一个公钥,步骤如下: + +1. **公钥**: `022c3ee7cdb92394e32e82ec5bc8860c8888df6a9910537e90c75079726a2a8469` +2. **SHA-256哈希**:计算结果: `98a21cf747c0fcd80845afffa2260feb64aa7cc2b73ac681407b25229bad3bbc` +3. **RIPEMD-160哈希**:计算结果: `999ff7726530ed0d0a7eb3b7442c5143f643f638` +4. **Bech32编码**:将最终字节串转换为Bech32编码:`bc1qnx0lwun9xrks6zn7kwm5gtz3g0my8a3cqde85n` diff --git a/BTC/payment/index.md b/BTC/payment/README.md similarity index 100% rename from BTC/payment/index.md rename to BTC/payment/README.md diff --git a/BTC/wallet/address.md b/BTC/wallet/README.md similarity index 100% rename from BTC/wallet/address.md rename to BTC/wallet/README.md