Skip to content

Commit 5cf4427

Browse files
committed
test: refactor: deduplicate legacy ECDSA signing for tx inputs
There are several instances in functional tests and the framework (MiniWallet, feature_block.py, p2p_segwit.py) where we create a legacy ECDSA signature for a certain transaction's input by doing the following steps: 1) calculate the `LegacySignatureHash` with the desired sighash type 2) create the actual digital signature by calling `ECKey.sign_ecdsa` on the signature message hash calculated above 3) put the DER-encoded result as CScript data push into tx input's scriptSig Create a new helper `sign_input_legacy` which hides those details and takes only the necessary parameters (tx, input index, relevant scriptPubKey, private key, sighash type [SIGHASH_ALL by default]). For further convenience, the signature is prepended to already existing data-pushes in scriptSig, in order to avoid rehashing the transaction after calling the new signing function.
1 parent 61d59fe commit 5cf4427

File tree

4 files changed

+24
-25
lines changed

4 files changed

+24
-25
lines changed

test/functional/feature_block.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@
4343
OP_INVALIDOPCODE,
4444
OP_RETURN,
4545
OP_TRUE,
46-
SIGHASH_ALL,
47-
LegacySignatureHash,
46+
sign_input_legacy,
4847
)
4948
from test_framework.script_util import (
5049
script_to_p2sh_script,
@@ -539,12 +538,8 @@ def run_test(self):
539538
# second input is corresponding P2SH output from b39
540539
tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b''))
541540
# Note: must pass the redeem_script (not p2sh_script) to the signature hash function
542-
(sighash, err) = LegacySignatureHash(redeem_script, tx, 1, SIGHASH_ALL)
543-
sig = self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))
544-
scriptSig = CScript([sig, redeem_script])
545-
546-
tx.vin[1].scriptSig = scriptSig
547-
tx.rehash()
541+
tx.vin[1].scriptSig = CScript([redeem_script])
542+
sign_input_legacy(tx, 1, redeem_script, self.coinbase_key)
548543
new_txs.append(tx)
549544
lastOutpoint = COutPoint(tx.sha256, 0)
550545

@@ -1338,8 +1333,7 @@ def sign_tx(self, tx, spend_tx):
13381333
if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend
13391334
tx.vin[0].scriptSig = CScript()
13401335
return
1341-
(sighash, err) = LegacySignatureHash(spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL)
1342-
tx.vin[0].scriptSig = CScript([self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))])
1336+
sign_input_legacy(tx, 0, spend_tx.vout[0].scriptPubKey, self.coinbase_key)
13431337

13441338
def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])):
13451339
tx = self.create_tx(spend_tx, 0, value, script)

test/functional/p2p_segwit.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@
7171
SIGHASH_NONE,
7272
SIGHASH_SINGLE,
7373
SegwitV0SignatureHash,
74-
LegacySignatureHash,
7574
hash160,
75+
sign_input_legacy,
7676
)
7777
from test_framework.script_util import (
7878
key_to_p2pk_script,
@@ -1529,10 +1529,8 @@ def test_uncompressed_pubkey(self):
15291529
tx5 = CTransaction()
15301530
tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b""))
15311531
tx5.vout.append(CTxOut(tx4.vout[0].nValue - 1000, CScript([OP_TRUE])))
1532-
(sig_hash, err) = LegacySignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL)
1533-
signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
1534-
tx5.vin[0].scriptSig = CScript([signature, pubkey])
1535-
tx5.rehash()
1532+
tx5.vin[0].scriptSig = CScript([pubkey])
1533+
sign_input_legacy(tx5, 0, script_pubkey, key)
15361534
# Should pass policy and consensus.
15371535
test_transaction_acceptance(self.nodes[0], self.test_node, tx5, True, True)
15381536
block = self.build_next_block()

test/functional/test_framework/script.py

+10
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,16 @@ def LegacySignatureHash(*args, **kwargs):
689689
else:
690690
return (hash256(msg), err)
691691

692+
def sign_input_legacy(tx, input_index, input_scriptpubkey, privkey, sighash_type=SIGHASH_ALL):
693+
"""Add legacy ECDSA signature for a given transaction input. Note that the signature
694+
is prepended to the scriptSig field, i.e. additional data pushes necessary for more
695+
complex spends than P2PK (e.g. pubkey for P2PKH) can be already set before."""
696+
(sighash, err) = LegacySignatureHash(input_scriptpubkey, tx, input_index, sighash_type)
697+
assert err is None
698+
der_sig = privkey.sign_ecdsa(sighash)
699+
tx.vin[input_index].scriptSig = bytes(CScript([der_sig + bytes([sighash_type])])) + tx.vin[input_index].scriptSig
700+
tx.rehash()
701+
692702
# TODO: Allow cached hashPrevouts/hashSequence/hashOutputs to be provided.
693703
# Performance optimization probably not necessary for python tests, however.
694704
# Note that this corresponds to sigversion == 1 in EvalScript, which is used

test/functional/test_framework/wallet.py

+7-10
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,11 @@
3636
)
3737
from test_framework.script import (
3838
CScript,
39-
LegacySignatureHash,
4039
LEAF_VERSION_TAPSCRIPT,
4140
OP_NOP,
4241
OP_RETURN,
4342
OP_TRUE,
44-
SIGHASH_ALL,
43+
sign_input_legacy,
4544
taproot_construct,
4645
)
4746
from test_framework.script_util import (
@@ -166,18 +165,16 @@ def scan_txs(self, txs):
166165

167166
def sign_tx(self, tx, fixed_length=True):
168167
if self._mode == MiniWalletMode.RAW_P2PK:
169-
(sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL)
170-
assert err is None
171168
# for exact fee calculation, create only signatures with fixed size by default (>49.89% probability):
172169
# 65 bytes: high-R val (33 bytes) + low-S val (32 bytes)
173-
# with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes
174-
der_sig = b''
175-
while not len(der_sig) == 71:
176-
der_sig = self._priv_key.sign_ecdsa(sighash)
170+
# with the DER header/skeleton data of 6 bytes added, plus 2 bytes scriptSig overhead
171+
# (OP_PUSHn and SIGHASH_ALL), this leads to a scriptSig target size of 73 bytes
172+
tx.vin[0].scriptSig = b''
173+
while not len(tx.vin[0].scriptSig) == 73:
174+
tx.vin[0].scriptSig = b''
175+
sign_input_legacy(tx, 0, self._scriptPubKey, self._priv_key)
177176
if not fixed_length:
178177
break
179-
tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))])
180-
tx.rehash()
181178
elif self._mode == MiniWalletMode.RAW_OP_TRUE:
182179
for i in tx.vin:
183180
i.scriptSig = CScript([OP_NOP] * 43) # pad to identical size

0 commit comments

Comments
 (0)