From fab9343e79c8a3afd11fc4dd8813b64b5483fc43 Mon Sep 17 00:00:00 2001 From: kensato Date: Fri, 28 Feb 2025 18:09:00 +0900 Subject: [PATCH 1/4] update the key dervation examples --- bsv/hd/__init__.py | 6 ++-- examples/hd.py | 74 ++++++++++++++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/bsv/hd/__init__.py b/bsv/hd/__init__.py index 453d8ca..cdf7f1b 100644 --- a/bsv/hd/__init__.py +++ b/bsv/hd/__init__.py @@ -1,3 +1,5 @@ -from .bip32 import Xkey, Xprv, Xpub, ckd, step_to_index, master_xprv_from_seed +from .bip32 import Xkey, Xprv, Xpub, ckd, step_to_index, master_xprv_from_seed, bip32_derive_xprv_from_mnemonic, \ + bip32_derive_xprvs_from_mnemonic, bip32_derive_xkeys_from_xkey from .bip39 import WordList, mnemonic_from_entropy, seed_from_mnemonic, validate_mnemonic -from .bip44 import derive_xkeys_from_xkey, derive_xprvs_from_mnemonic, derive_xprv_from_mnemonic +from .bip44 import derive_xkeys_from_xkey, derive_xprvs_from_mnemonic, derive_xprv_from_mnemonic, \ + bip44_derive_xprv_from_mnemonic, bip44_derive_xprvs_from_mnemonic diff --git a/examples/hd.py b/examples/hd.py index 1a81217..0287feb 100644 --- a/examples/hd.py +++ b/examples/hd.py @@ -1,41 +1,65 @@ -from typing import List +from bsv.hd import mnemonic_from_entropy, seed_from_mnemonic, master_xprv_from_seed +from bsv.hd import derive_xprvs_from_mnemonic, derive_xkeys_from_xkey +from bsv.constants import BIP32_DERIVATION_PATH, BIP44_DERIVATION_PATH -from bsv.hd import mnemonic_from_entropy, seed_from_mnemonic, master_xprv_from_seed, Xprv, derive_xprvs_from_mnemonic +# You can set custom derivation paths in your environment variables as well +# BIP32_DERIVATION_PATH = "m/" +# BIP44_DERIVATION_PATH = "m/44'/236'/0'" # -# HD derivation +# HD derivation (mnemonic, master-xpublickey, master-xprivatekey) # entropy = 'cd9b819d9c62f0027116c1849e7d497f' -# snow swing guess decide congress abuse session subway loyal view false zebra +# Generate mnemonic from entropy mnemonic: str = mnemonic_from_entropy(entropy) -print(mnemonic) +print("Mnemonic:", mnemonic) -seed: bytes = seed_from_mnemonic(mnemonic) -print(seed.hex()) - -master_xprv: Xprv = master_xprv_from_seed(seed) -print(master_xprv) +# Generate seed from mnemonic +seed = seed_from_mnemonic(mnemonic, lang='en') +print("Seed:", seed.hex()) +# Generate master keys +master_xprv = master_xprv_from_seed(seed) +master_xpub = master_xprv.xpub() +print("Master xprv:", master_xprv) +print("Master xpub:", master_xpub) print() -keys: List[Xprv] = derive_xprvs_from_mnemonic(mnemonic, path="m/44'/0'/0'", change=1, index_start=0, index_end=5) -for key in keys: - # XPriv to WIF - print(key.private_key().wif()) - key_xpub = key.xpub() +# Derive keys from mnemonic using BIP32 +keys_from_mnemonic_by_bip32 = derive_xprvs_from_mnemonic( + mnemonic, 0, 3, path=BIP32_DERIVATION_PATH, change=0 +) - # XPub to public key - print(key_xpub.public_key().hex()) +print("Keys from mnemonic by BIP32:") +print("Address 0:", keys_from_mnemonic_by_bip32[0].address()) +print("Private key 1:", keys_from_mnemonic_by_bip32[1].private_key().wif()) +print("Public key 2:", keys_from_mnemonic_by_bip32[2].public_key().hex()) +print() - # XPub to address - print(key_xpub.public_key().address(), '\n') +# Derive keys from xpub using BIP32 +keys_from_xpub_by_bip32 = derive_xkeys_from_xkey( + master_xpub, 0, 3, change=0 +) +print("Keys from xpub by BIP32:") +print("Address 0:", keys_from_xpub_by_bip32[0].address()) +print("Public key 2:", keys_from_xpub_by_bip32[2].public_key().hex()) +print() -# -# random mnemonic -# +# Derive keys from mnemonic using BIP44 +bip44_keys = derive_xprvs_from_mnemonic( + mnemonic, 0, 3, path=BIP44_DERIVATION_PATH, change=0 +) + +print("Keys from mnemonic by BIP44:") +print("Address 0:", bip44_keys[0].address()) +print("Private key 1:", bip44_keys[1].private_key().wif()) +print("Public key 2:", bip44_keys[2].public_key().hex()) print() -print(mnemonic_from_entropy()) -print(mnemonic_from_entropy(lang='en')) -print(mnemonic_from_entropy(lang='zh-cn')) + +# Loop through multiple derived keys +print("All BIP44 derived keys:") +for i, key in enumerate(bip44_keys): + print(f"Address {i}: {key.address()}") + print(f"Private key {i}: {key.private_key().wif()}") \ No newline at end of file From 46fe0664be81d2f56d2289fdadd6b0daa70d8550 Mon Sep 17 00:00:00 2001 From: kensato Date: Mon, 3 Mar 2025 12:14:17 +0900 Subject: [PATCH 2/4] fix the mistakes of the previous code --- bsv/hd/bip32.py | 25 ++++++++++++------------- examples/hd.py | 8 ++++---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/bsv/hd/bip32.py b/bsv/hd/bip32.py index 003b917..9eeffa2 100644 --- a/bsv/hd/bip32.py +++ b/bsv/hd/bip32.py @@ -12,7 +12,6 @@ from ..keys import PublicKey, PrivateKey - class Xkey: """ [ : 4] prefix @@ -210,9 +209,9 @@ def master_xprv_from_seed(seed: Union[str, bytes], network: Network = Network.MA def _derive_xkeys_from_xkey(xkey: Union[Xprv, Xpub], - index_start: Union[str, int], - index_end: Union[str, int], - change: Union[str, int] = 0) -> List[Union[Xprv, Xpub]]: + index_start: Union[str, int], + index_end: Union[str, int], + change: Union[str, int] = 0) -> List[Union[Xprv, Xpub]]: """ this function is internal use only within bip32 module Use bip32_derive_xkeys_from_xkey instead. @@ -236,14 +235,14 @@ def bip32_derive_xprv_from_mnemonic(mnemonic: str, def bip32_derive_xprvs_from_mnemonic(mnemonic: str, - index_start: Union[str, int], - index_end: Union[str, int], - lang: str = 'en', - passphrase: str = '', - prefix: str = 'mnemonic', - path: str = BIP32_DERIVATION_PATH, - change: Union[str, int] = 0, - network: Network = Network.MAINNET) -> List[Xprv]: + index_start: Union[str, int], + index_end: Union[str, int], + lang: str = 'en', + passphrase: str = '', + prefix: str = 'mnemonic', + path: str = BIP32_DERIVATION_PATH, + change: Union[str, int] = 0, + network: Network = Network.MAINNET) -> List[Xprv]: """ Derive a range of extended keys from a nmemonic using BIP32 format """ @@ -295,4 +294,4 @@ def bip32_derive_xkeys_from_xkey(xkey: Union[Xprv, Xpub], child_key = change_level.ckd(i) derived_keys.append(child_key) - return derived_keys \ No newline at end of file + return derived_keys diff --git a/examples/hd.py b/examples/hd.py index 0287feb..f9c8444 100644 --- a/examples/hd.py +++ b/examples/hd.py @@ -1,5 +1,5 @@ from bsv.hd import mnemonic_from_entropy, seed_from_mnemonic, master_xprv_from_seed -from bsv.hd import derive_xprvs_from_mnemonic, derive_xkeys_from_xkey +from bsv.hd import bip32_derive_xprvs_from_mnemonic, bip44_derive_xprvs_from_mnemonic, bip32_derive_xkeys_from_xkey from bsv.constants import BIP32_DERIVATION_PATH, BIP44_DERIVATION_PATH # You can set custom derivation paths in your environment variables as well @@ -27,7 +27,7 @@ print() # Derive keys from mnemonic using BIP32 -keys_from_mnemonic_by_bip32 = derive_xprvs_from_mnemonic( +keys_from_mnemonic_by_bip32 = bip32_derive_xprvs_from_mnemonic( mnemonic, 0, 3, path=BIP32_DERIVATION_PATH, change=0 ) @@ -38,7 +38,7 @@ print() # Derive keys from xpub using BIP32 -keys_from_xpub_by_bip32 = derive_xkeys_from_xkey( +keys_from_xpub_by_bip32 = bip32_derive_xkeys_from_xkey( master_xpub, 0, 3, change=0 ) @@ -48,7 +48,7 @@ print() # Derive keys from mnemonic using BIP44 -bip44_keys = derive_xprvs_from_mnemonic( +bip44_keys = bip44_derive_xprvs_from_mnemonic( mnemonic, 0, 3, path=BIP44_DERIVATION_PATH, change=0 ) From e2f164080d88e61c803d759a32e320f02bad7189 Mon Sep 17 00:00:00 2001 From: kensato Date: Wed, 26 Mar 2025 15:18:03 +0900 Subject: [PATCH 3/4] Fix the default fee rate Previously, the default fee rate was hardcoded to 10 satoshis per kilobyte. This update allows users to configure the default fee rate via the TRANSACTION_FEE_RATE variable in constants.py or through the environment file. A test for the default fee rate has also been added. --- bsv/constants.py | 2 +- bsv/transaction.py | 4 ++-- tests/test_transaction.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/bsv/constants.py b/bsv/constants.py index 6f1556b..1133010 100644 --- a/bsv/constants.py +++ b/bsv/constants.py @@ -7,7 +7,7 @@ TRANSACTION_SEQUENCE: int = int(os.getenv('BSV_PY_SDK_TRANSACTION_SEQUENCE') or 0xffffffff) TRANSACTION_VERSION: int = int(os.getenv('BSV_PY_SDK_TRANSACTION_VERSION') or 1) TRANSACTION_LOCKTIME: int = int(os.getenv('BSV_PY_SDK_TRANSACTION_LOCKTIME') or 0) -TRANSACTION_FEE_RATE: float = float(os.getenv('BSV_PY_SDK_TRANSACTION_FEE_RATE') or 0.5) # satoshi per byte +TRANSACTION_FEE_RATE: int = int(os.getenv('BSV_PY_SDK_TRANSACTION_FEE_RATE') or 1) # satoshi per kilobyte BIP32_DERIVATION_PATH = os.getenv('BSV_PY_SDK_BIP32_DERIVATION_PATH') or "m/" BIP39_ENTROPY_BIT_LENGTH: int = int(os.getenv('BSV_PY_SDK_BIP39_ENTROPY_BIT_LENGTH') or 128) BIP44_DERIVATION_PATH = os.getenv('BSV_PY_SDK_BIP44_DERIVATION_PATH') or "m/44'/236'/0'" diff --git a/bsv/transaction.py b/bsv/transaction.py index c0c419c..3ebf743 100644 --- a/bsv/transaction.py +++ b/bsv/transaction.py @@ -178,8 +178,8 @@ def fee(self, model_or_fee=None, change_distribution='equal'): """ if model_or_fee is None: - model_or_fee = SatoshisPerKilobyte(10) - + model_or_fee = SatoshisPerKilobyte(int(TRANSACTION_FEE_RATE)) + if isinstance(model_or_fee, int): fee = model_or_fee else: diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 3a753d4..8c873cf 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -667,4 +667,37 @@ def test_input_auto_txid(): ) +def test_transaction_fee_with_default_rate(): + from bsv.constants import TRANSACTION_FEE_RATE + + address = "1AfxgwYJrBgriZDLryfyKuSdBsi59jeBX9" + t = Transaction() + t_in = TransactionInput( + source_transaction=Transaction( + [], + [ + None, + TransactionOutput(locking_script=P2PKH().lock(address), satoshis=1000), + ], + ), + source_txid="d2bc57099dd434a5adb51f7de38cc9b8565fb208090d9b5ea7a6b4778e1fdd48", + source_output_index=1, + unlocking_script_template=P2PKH().unlock(PrivateKey()), + ) + t.add_input(t_in) + t.add_output( + TransactionOutput( + P2PKH().lock("1JDZRGf5fPjGTpqLNwjHFFZnagcZbwDsxw"), satoshis=100 + ) + ) + t.add_output(TransactionOutput(P2PKH().lock(address), change=True)) + + t.fee() + + estimated_size = t.estimated_byte_length() + expected_fee = int((estimated_size / 1000) * TRANSACTION_FEE_RATE) + actual_fee = t.get_fee() + + assert abs(actual_fee - expected_fee) <= 1 + # TODO: Test tx.verify() From 1f4f9a201715127a0308966d9fb6f6cb4685719c Mon Sep 17 00:00:00 2001 From: kensato Date: Wed, 26 Mar 2025 15:29:44 +0900 Subject: [PATCH 4/4] Enhancement: Optimize transaction preimage calculation - Refactored tx_preimage function to directly calculate preimage for a single input - Eliminated redundant calculations from tx_preimages function - Improved clarity of sighash flag handling This change improves performance for single-input signing scenarios by avoiding unnecessary preimage calculations for inputs that won't be signed. --- CHANGELOG.md | 13 ++++++++++++ bsv/transaction_preimage.py | 40 ++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33367ee..8e7b83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,19 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - (Notify of any improvements related to security vulnerabilities or potential risks.) --- +## [1.0.3] - 2025-03-26 + +### Fixed +Previously, the default fee rate was hardcoded to 10 satoshis per kilobyte. This update allows users to configure the default fee rate via the TRANSACTION_FEE_RATE variable in constants.py or through the environment file. + +### Added +A test for the default fee rate has also been added. + +### Changed +Optimized transaction preimage calculation by refactoring the tx_preimage function to directly compute the preimage for a specified input, avoiding unnecessary computation for all inputs +Achieved a 3× performance improvement in scenarios with 250 inputs, based on benchmarking + + ## [1.0.2] - 2025-02-28 ### Added diff --git a/bsv/transaction_preimage.py b/bsv/transaction_preimage.py index d0940ee..92fe419 100644 --- a/bsv/transaction_preimage.py +++ b/bsv/transaction_preimage.py @@ -116,4 +116,42 @@ def tx_preimage( tx_version: int, tx_locktime: int, ) -> bytes: - return tx_preimages(inputs, outputs, tx_version, tx_locktime)[input_index] + """ + Calculates and returns the preimage for a specific input index. + """ + sighash = inputs[input_index].sighash + + # hash previous outs + if not sighash & SIGHASH.ANYONECANPAY: + hash_prevouts = hash256( + b"".join( + bytes.fromhex(_in.source_txid)[::-1] + _in.source_output_index.to_bytes(4, "little") + for _in in inputs + ) + ) + else: + hash_prevouts = b"\x00" * 32 + + # hash sequence + if ( + not sighash & SIGHASH.ANYONECANPAY + and sighash & 0x1F != SIGHASH.SINGLE + and sighash & 0x1F != SIGHASH.NONE + ): + hash_sequence = hash256( + b"".join(_in.sequence.to_bytes(4, "little") for _in in inputs) + ) + else: + hash_sequence = b"\x00" * 32 + + # hash outputs + if sighash & 0x1F != SIGHASH.SINGLE and sighash & 0x1F != SIGHASH.NONE: + hash_outputs = hash256( + b"".join(tx_output.serialize() for tx_output in outputs) + ) + elif sighash & 0x1F == SIGHASH.SINGLE and input_index < len(outputs): + hash_outputs = hash256(outputs[input_index].serialize()) + else: + hash_outputs = b"\x00" * 32 + + return _preimage(inputs[input_index], tx_version, tx_locktime, hash_prevouts, hash_sequence, hash_outputs)