(个人学习用,可能理解上存在谬误)
上篇中,签名的步骤如下,
solver = build_hash160_lookup([exponent])
signed_new_tx = unsigned_new_tx.sign(solver)
对于pycoin是如何完成交易签名的,很有必要从其源码中进行研究
def sign(self, hash160_lookup, hash_type=None, **kwargs):
"""
Sign a standard transaction.
hash160_lookup:
A dictionary (or another object with .get) where keys are hash160 and
values are tuples (secret exponent, public_pair, is_compressed) or None
(in which case the script will obviously not be signed).
"""
if hash_type is None:
hash_type = self.SIGHASH_ALL #1
self.check_unspents() #2
for idx, tx_in in enumerate(self.txs_in):
if self.is_signature_ok(idx) or tx_in.is_coinbase():
continue
try:
if self.unspents[idx]:
self.sign_tx_in(
hash160_lookup, idx, self.unspents[idx].script, hash_type=hash_type, **kwargs)#3
except SolvingError:
pass
return self
大致流程:
1)先检查是否交易输入都有其对应的锁定脚本(#2处)
2)逐个对未签名过的交易输入进行签名操作(#3处)
#1处,将hash_type默认设为SIGHASH_ALL,这也是目前最主流的方式,具体细节可自行搜索
继续进入sign_tx_in方法:
def sign_tx_in(self, hash160_lookup, tx_in_idx, tx_out_script, hash_type=None, **kwargs):
if hash_type is None:
hash_type = self.SIGHASH_ALL
r = self.solve(hash160_lookup, tx_in_idx, tx_out_script,
hash_type=hash_type, **kwargs) #1
if isinstance(r, bytes):
self.txs_in[tx_in_idx].script = r #2
else:
self.txs_in[tx_in_idx].script = r[0] #3
self.set_witness(tx_in_idx, r[1])
在#1处调用solve进行签名,返回解锁脚本。然后在#2处将解锁脚本设置入相应的交易输入TxIn的script字段中,完成了对该交易输入的签名。
#3处涉及到segwit相关的东西,不深入研究了
继续跟进solve
def solve(self, hash160_lookup, tx_in_idx, tx_out_script, hash_type=None, **kwargs):
"""
Sign a standard transaction.
hash160_lookup:
An object with a get method that accepts a hash160 and returns the
corresponding (secret exponent, public_pair, is_compressed) tuple or
None if it's unknown (in which case the script will obviously not be signed).
A standard dictionary will do nicely here.
tx_in_idx:
the index of the tx_in we are currently signing
tx_out:
the tx_out referenced by the given tx_in
"""
if hash_type is None:
hash_type = self.SIGHASH_ALL
tx_in = self.txs_in[tx_in_idx]
is_p2h = (len(tx_out_script) == 23 and byte2int(tx_out_script) == opcodes.OP_HASH160 and
indexbytes(tx_out_script, -1) == opcodes.OP_EQUAL) #1
if is_p2h:
hash160 = ScriptPayToScript.from_script(tx_out_script).hash160
p2sh_lookup = kwargs.get("p2sh_lookup")
if p2sh_lookup is None:
raise SolvingError("p2sh_lookup not set")
if hash160 not in p2sh_lookup:
raise SolvingError("hash160=%s not found in p2sh_lookup" %
b2h(hash160))
script_to_hash = p2sh_lookup[hash160]
else:
script_to_hash = tx_out_script #2
# Leave out the signature from the hash, since a signature can't sign itself.
# The checksig op will also drop the signatures from its hash.
def signature_for_hash_type_f(hash_type, script):#3
return self.signature_hash(script, tx_in_idx, hash_type)
def witness_signature_for_hash_type(hash_type, script):
return self.signature_for_hash_type_segwit(script, tx_in_idx, hash_type)
witness_signature_for_hash_type.skip_delete = True
signature_for_hash_type_f.witness = witness_signature_for_hash_type
if tx_in.verify(
tx_out_script, signature_for_hash_type_f, lock_time=self.lock_time,
tx_version=self.version):
return
the_script = script_obj_from_script(tx_out_script)
solution = the_script.solve(
hash160_lookup=hash160_lookup, signature_type=hash_type,
existing_script=self.txs_in[tx_in_idx].script, existing_witness=tx_in.witness,
script_to_hash=script_to_hash, signature_for_hash_type_f=signature_for_hash_type_f, **kwargs)#4
return solution
只关注主干流程。#1处判断是否为p2sh脚本,此处使用的是p2pkh因此为false,进入#2;否则其实是找寻赎回脚本redeem script,这方面留待以后研究。
#3处定义了闭包,返回的是当前交易的哈希值(即待签名的值)。
#4处开始正式生成解锁脚本
因此接下来需要对两个重要流程进行跟踪,
首先是#3处的signature_hash方法:
def signature_hash(self, tx_out_script, unsigned_txs_out_idx, hash_type):
"""
Return the canonical hash for a transaction. We need to
remove references to the signature, since it's a signature
of the hash before the signature is applied.
tx_out_script: the script the coins for unsigned_txs_out_idx are coming from
unsigned_txs_out_idx: where to put the tx_out_script
hash_type: one of SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ALL,
optionally bitwise or'ed with SIGHASH_ANYONECANPAY
"""
# In case concatenating two scripts ends up with two codeseparators,
# or an extra one at the end, this prevents all those possible incompatibilities.
tx_out_script = tools.delete_subscript(tx_out_script, int2byte(opcodes.OP_CODESEPARATOR))
# blank out other inputs' signatures
txs_in = [self._tx_in_for_idx(i, tx_in, tx_out_script, unsigned_txs_out_idx)
for i, tx_in in enumerate(self.txs_in)] #1
txs_out = self.txs_out
# Blank out some of the outputs
if (hash_type & 0x1f) == self.SIGHASH_NONE:
# Wildcard payee
txs_out = []
# Let the others update at will
for i in range(len(txs_in)):
if i != unsigned_txs_out_idx:
txs_in[i].sequence = 0
elif (hash_type & 0x1f) == self.SIGHASH_SINGLE:
# This preserves the ability to validate existing legacy
# transactions which followed a buggy path in Satoshi's
# original code; note that higher level functions for signing
# new transactions (e.g., is_signature_ok and sign_tx_in)
# check to make sure we never get here (or at least they
# should)
if unsigned_txs_out_idx >= len(txs_out):
# This should probably be moved to a constant, but the
# likelihood of ever getting here is already really small
# and getting smaller
return (1 << 248)
# Only lock in the txout payee at same index as txin; delete
# any outputs after this one and set all outputs before this
# one to "null" (where "null" means an empty script and a
# value of -1)
txs_out = [self.TxOut(0xffffffffffffffff, b'')] * unsigned_txs_out_idx
txs_out.append(self.txs_out[unsigned_txs_out_idx])
# Let the others update at will
for i in range(len(self.txs_in)):
if i != unsigned_txs_out_idx:
txs_in[i].sequence = 0
# Blank out other inputs completely, not recommended for open transactions
if hash_type & self.SIGHASH_ANYONECANPAY:
txs_in = [txs_in[unsigned_txs_out_idx]]
tmp_tx = self.__class__(self.version, txs_in, txs_out, self.lock_time) #2
return from_bytes_32(tmp_tx.hash(hash_type=hash_type))#3
只关注SIGHASH_ALL的情况,#1处是最为关键的步骤,它对当前交易的所有交易输入进行以下操作:
def _tx_in_for_idx(self, idx, tx_in, tx_out_script, unsigned_txs_out_idx):
if idx == unsigned_txs_out_idx:
return self.TxIn(tx_in.previous_hash, tx_in.previous_index, tx_out_script, tx_in.sequence)
return self.TxIn(tx_in.previous_hash, tx_in.previous_index, b'', tx_in.sequence)
即,对当前要签名的交易输入,将锁定脚本放到该TxIn结构的script字段中(此处最终是放入解锁脚本的地方);对不是当前要签名的TxIn,将其script设为空。
这样处理后,生成一个新的tx(#2处),并将其序列化,然后哈希之,这个值就是当前想要进行签名的交易输入所对应的待签名值(#3处)。
回到solve方法,#4处是最终实际签名(生成解锁脚本)的地方,是ScriptPayToAddress对象(因为此处是p2pkh)的solve方法,跟进之:
def solve(self, **kwargs):
"""
The kwargs required depend upon the script type.
hash160_lookup:
dict-like structure that returns a secret exponent for a hash160
signature_for_hash_type_f:
function returning sign value for a given signature type
signature_type:
usually SIGHASH_ALL (1)
"""
# we need a hash160 => secret_exponent lookup
db = kwargs.get("hash160_lookup")
if db is None:
raise SolvingError("missing hash160_lookup parameter")
result = db.get(self.hash160)
if result is None:
raise SolvingError("can't find secret exponent for %s" % self.address())
# we got it
signature_for_hash_type_f = kwargs.get("signature_for_hash_type_f")
signature_type = kwargs.get("signature_type")
script_to_hash = kwargs.get("script_to_hash")
secret_exponent, public_pair, compressed = result
binary_signature = self._create_script_signature(
secret_exponent, signature_for_hash_type_f, signature_type, script_to_hash)#1
binary_public_pair_sec = encoding.public_pair_to_sec(public_pair, compressed=compressed)#2
solution = tools.bin_script([binary_signature, binary_public_pair_sec])#3
return solution
#1处产生了签名值,_create_script_signature方法为:
def _create_script_signature(
self, secret_exponent, signature_for_hash_type_f, signature_type, script):
sign_value = signature_for_hash_type_f(signature_type, script)
order = ecdsa.generator_secp256k1.order()
r, s = ecdsa.sign(ecdsa.generator_secp256k1, secret_exponent, sign_value)
if s + s > order:
s = order - s
大致上是用椭圆曲线,对待签名值签名然后返回
#2处获得公钥,#3处打包成解锁脚本
总结下对交易进行签名的大致流程:即对每一项交易输入TxIn,生成其对应的待签名值,签名生成解锁脚本并设置入该交易输入TxIn的script字段。
特此记录,方便日后复查。