文章目录
《区块链编程》第七章
jiaoyi的创建与验证
审核不通过,提示违法违规, 尝试一下是否是jy两个字的原因
练习1
p129
代码实现
# -*- coding: utf-8 -*-
# @Author: 从化北(喵星人)
# @Date: 2022-01-09 18:28:43
# @Last Modified by: 从化北
# @Last Modified time: 2022-01-09 18:53:24
class Tx:
...
def sig_hash(self, input_index):
'''Returns the integer representation of the hash that needs to get
signed for index input_index'''
# start the serialization with version
# use int_to_little_endian in 4 bytes
s = int_to_little_endian(self.version, 4)
# add how many inputs there are using encode_varint
s += encode_varint(len(self.tx_ins))
# loop through each input using enumerate, so we have the input index
for i, tx_in in enumerate(self.tx_ins):
# if the input index is the one we're signing
if i == input_index:
# Only the signature script of the specified input is replaced,
# and the signature scripts of other inputs are empty.
# the previous tx's ScriptPubkey is the ScriptSig
# Otherwise, the ScriptSig is empty
# add the serialization of the input with the ScriptSig we want
s += TxIn(
prev_tx=tx_in.prev_tx,
prev_index=tx_in.prev_index,
script_sig=tx_in.script_pubkey(self.testnet),
sequence=tx_in.sequence,
).serialize()
else:
s += TxIn(prev_tx=tx_in.prev_tx,
prev_index=tx_in.prev_index,
sequence=tx_in.sequence,
).serialize()
# add how many outputs there are using encode_varint
s += encode_varint(len(self.tx_outs))
# add the serialization of each output
for tx_out in self.tx_outs:
s += tx_out.serialize()
# add the locktime using int_to_little_endian in 4 bytes
s += int_to_little_endian(self.locktime, 4)
# add SIGHASH_ALL using int_to_little_endian in 4 bytes
s += int_to_little_endian(SIGHASH_ALL, 4)
# hash256 the serialization
h256 = hash256(s)
# convert the result to an integer using int.from_bytes(x, 'big')
return int.from_bytes(h256, 'big')
对上述函数代码的解释
对sig_hash函数的解释:
输入:交易中input的索引
输出:z(签名哈希)
内部执行逻辑:
首先说明一个前提: 每一个input对应一个z,(换句话说,每一个签名脚本对应一个z)
求当前input的z,将该input的签名脚本更换为其父交易output中的公钥脚本。(p126)
其他各部分均不变。
而后对更改后的交易,进行hash256,转换为大端序的int值,即为当前input的z。
练习2
p129
代码实现
# -*- coding: utf-8 -*-
# @Author: 从化北(喵星人)
# @Date: 2022-01-09 18:53:45
# @Last Modified by: 从化北
# @Last Modified time: 2022-01-09 18:58:41
class Tx:
...
def verify_input(self, input_index):
'''Returns whether the input has a valid signature'''
# get the relevant input
tx_in = self.tx_ins[input_index]
# grab the previous ScriptPubKey
script_pubkey = tx_in.script_pubkey(testnet=self.testnet)
# get the signature hash (z)
z = self.sig_hash(input_index)
# combine the current ScriptSig and the previous ScriptPubKey
combined = tx_in.script_sig + script_pubkey
# evaluate the combined script
return combined.evaluate(z)
对上述函数代码的解释
对verify_input函数的解释:
输入:交易中input的索引
输出:True/False
函数要做的事情:验证input是否有效
验证逻辑:
验证input有效,就是验证Script对象运行结果非0。
一个Script对象的组成:
当前input的签名脚本
当前input父交易的公钥脚本
而Script对象需要z来验证。具体深入请看Script章节以及椭圆曲线密码学数字签名算法部分
练习3
p134
代码实现
# -*- coding: utf-8 -*-
# @Author: 从化北(喵星人)
# @Date: 2022-01-09 19:35:33
# @Last Modified by: 从化北
# @Last Modified time: 2022-01-09 19:35:33
class Tx:
...
def sign_input(self, input_index, private_key):
# get the signature hash (z)
z = self.sig_hash(input_index)
# get der signature of z from private key
der = private_key.sign(z).der()
# append the SIGHASH_ALL to der (use SIGHASH_ALL.to_bytes(1, 'big'))
sig = der + SIGHASH_ALL.to_bytes(1, 'big')
# calculate the sec
sec = private_key.point.sec()
# initialize a new script with [sig, sec] as the cmds
# change input's script_sig to new script
self.tx_ins[input_index].script_sig = Script([sig, sec])
# return whether sig is valid using self.verify_input
return self.verify_input(input_index)
对上述函数代码的解释
对sign_input函数的解释:
输入:
交易中input的索引
私钥
输出:输出input签名脚本的验证结果( True / False)
函数要做的事情:
填充某一input的签名脚本
验证input签名是否有效
p2pk标准脚本的脚本签名包括两个元素:
sig = der + 签名授权哈希
sec
练习4
p135
代码实现
# -*- coding: utf-8 -*-
# @Author: 从化北(喵星人)
# @Date: 2022-01-09 21:58:03
# @Last Modified by: 从化北
# @Last Modified time: 2022-01-10 16:21:53
from ecc import PrivateKey
from helper import decode_base58, SIGHASH_ALL, little_endian_to_int, hash256
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
# 父交易ID,现在申请的,里面有0.001=十万聪
prev_tx = bytes.fromhex('941999e918522d1d9c4691ab67dcd0effe22ed3a5664ab93f397fe865c600553')
# 父交易的序号
prev_index = 0
# 目标地址,(donate address)
target_address = '37NFX8KWAQbaodUG6pE1hNUH1dXgkpzbyZ'
# 金额
target_mount = 0.00006
# 剩于部分发送的地址,另一个新建的地址,第三个
change_address = 'mosFzEQBzLyMgEPGu9LXoaB4iDjo4okffL'
# 向“剩余地址”发送的金额,其他作为手续费
change_mount = 0.0008
# 秘钥
secret = b"***********"
secret = little_endian_to_int(hash256(secret))
# 由秘钥创建的私钥
priv = PrivateKey(secret=secret)
# 构建input
tx_ins = []
# 添加input
tx_ins.append(TxIn(prev_tx, prev_index))
# 构建output
tx_outs = []
# 将目的地址的base58形式解析出哈希地址
h160 = decode_base58(target_address)
# 构建交易的公钥脚本
script_pubkey = p2pkh_script(h160)
# 交易以聪为单位,1 比特币 = 1亿 聪
target_satoshis = int(target_mount * 100000000)
# 构建并添加output
tx_outs.append(TxOut(target_satoshis, script_pubkey))
# 将“剩余地址”的base58形式解析出哈希地址
h160 = decode_base58(change_address)
# 构建交易的公钥脚本
script_pubkey = p2pkh_script(h160)
# 交易以聪为单位,1 比特币 = 1亿 聪
change_satoshis = int(change_mount * 100000000)
# 构建并添加output
tx_outs.append(TxOut(change_satoshis, script_pubkey))
# 构建交易
tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True)
# 校验签名可用
print(tx_obj.sign_input(0, priv))
# 打印交易的完整hex
print(tx_obj.serialize().hex())
# 广播交易的网站
# https://live.blockcypher.com/btc/pushtx
运行结果
补充说明, 这个交易已经被我发布了.
广播交易的一个网站
True
01000000015305605c86fe97f393ab64563aed22feefd0dc67ab91469c1d2d5218e9991994000000006a473044022100f34a7dbbaa83de7e064c1ce5664ac80eb6c201538eafd4fe94a453432ac2b8c8021f5037b3d9fc4f60bc9879bcd8fcd5a711885892989b475033f48c2c6112273701210284bfcdd7f8d2cc4732cabc508c7f309b5ac5705698a8753a183ddff550e2d153ffffffff0270170000000000001976a9143e443375e10eef0236cdb243bdec473918c9a1dd88ac80380100000000001976a9145b98183ab5ffaeee7d03f3a58da039db9b88cd9a88ac00000000
[Finished in 1.5s]
看一下发布后的样子
- 一个input 两个output
- 广播交易的网站之一(这个突然挂掉了,下面test05,使用了另一个广播交易的网站)
- tx_id : 941999e918522d1d9c4691ab67dcd0effe22ed3a5664ab93f397fe865c600553
- 感兴趣大家可以自己查一下这个交易链接,这里给出另一个区块链浏览器
练习5
p135
代码实现
# -*- coding: utf-8 -*-
# @Author: 从化北(喵星人)
# @Date: 2022-01-10 16:28:04
# @Last Modified by: 从化北
# @Last Modified time: 2022-01-10 18:03:42
from ecc import PrivateKey
from helper import decode_base58, SIGHASH_ALL, little_endian_to_int, hash256
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
# 1 btc = 1亿 聪
RATE = 100000000
# 父交易1的ID;上一个找零交易, 0.0008 btc
prev_tx_1 = bytes.fromhex('50bf400cfbd2669434be467e9e277e1db88610f0ef2f8ec5a1631f0bf9f5ae78')
# 父交易1的序号
prev_index_1 = 1
# 父交易2的ID,水龙头交易;0.0001 btc
prev_tx_2 = bytes.fromhex('019ea8b778aac75b6b344e69912a5a98927bcefccaaf366f84e31ddc74668407')
# 父交易1的序号
prev_index_2 = 0
# 一共0.0009 btc
# 目标地址1,自己的一个地址
target_address = 'mvgTF5toE4Y7d59ibvq5E7fMka7uiAYprs'
target_mount = 0.00006
# 剩于部分发送的地址,自己的另一个地址
# https://bitcoinfaucet.uo1.net/send.php
denote_address = 'mh7ymnwwfG22BcdLmYf5cnHYLjjXpEFbcj'
# 向“剩余地址”发送的金额,留出0.001作为手续费
denote_mount = 0.00002
# 秘钥数字, 花钱
secret = b"******************"
secret = little_endian_to_int(hash256(secret))
# 由秘钥创建的私钥
priv = PrivateKey(secret=secret)
# 构建input
tx_ins = []
# 添加两个input
tx_ins.append(TxIn(prev_tx_1, prev_index_1))
tx_ins.append(TxIn(prev_tx_2, prev_index_2))
# 构建output
tx_outs = []
# 将目的地址的base58形式解析出哈希地址
h160_1 = decode_base58(target_address)
h160_2 = decode_base58(denote_address)
# 构建交易的公钥脚本
script_pubkey_1 = p2pkh_script(h160_1)
script_pubkey_2 = p2pkh_script(h160_2)
# print(script_pubkey)
# 交易以聪为单位,1 比特币 = 1亿 聪
target_satoshis = int(target_mount * RATE)
decote_satoshis = int(denote_mount * RATE)
# 构建并添加output
tx_outs.append(TxOut(target_satoshis, script_pubkey_1))
tx_outs.append(TxOut(target_satoshis, script_pubkey_2))
# 构建交易
tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True)
print(tx_obj.fee())
# 校验签名可用
print(tx_obj.sign_input(0, priv))
print(tx_obj.sign_input(1, priv))
# 打印交易的完整hex
print(tx_obj.serialize().hex())
运行结果
补充说明, 这个交易已经被我发布了.
广播交易的一个另一个网站,之前那个不知怎么,暂时挂掉了
True
True
010000000278aef5f90b1f63a1c58e2feff01086b81d7e279e7e46be349466d2fb0c40bf50010000006b48304502210089c548abd0e736c66dcb3f80a91457316b8b38c35e28dab6e80c689d90795d9102206ce6a37dbc709eed10552ba21504ac31dc5672277204cc6ee90eae9c64234fcb012102fbb36a53ef74d0ed81e114fc5e47624d8f9e4d329983db90020b3dbbcd25a21cffffffff07846674dc1de3846f36afcafcce7b92985a2a91694e346b5bc7aa78b7a89e01000000006b483045022100a094452d4475dd0dc74f212ab259761f32c04b6ef9ef276461d4d6926603cf7a02201638858c428f79f0bb0ec48bd101353c238528b8c7ddb997f64f1d5f4e71be21012102fbb36a53ef74d0ed81e114fc5e47624d8f9e4d329983db90020b3dbbcd25a21cffffffff0270170000000000001976a914a655cbb0b31bcd08157a6aeecc5d76a2e5be2bbe88ac70170000000000001976a9141197eb1e848ae52638df6bf205ffd9dd2d93c26788ac00000000
[Finished in 2.2s]
看一下发布后的样子
两个input 两个output
tx_id : 5e33aadfa079c3dac8176203ab5644a75d2129cf98f4085499b52887e73c2543
感兴趣大家可以自己查一下这个交易链接,这里给出另一个区块链浏览器