From e7d4946b623664b681fda46c7893866acdad6706 Mon Sep 17 00:00:00 2001 From: msinkec Date: Tue, 20 Aug 2024 15:10:20 +0200 Subject: [PATCH] Improve examples as per YEN Point's feedback. --- .../EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md | 17 ++++---- guides/sdks/py/examples/EXAMPLE_COMPLEX_TX.md | 15 +++---- guides/sdks/py/examples/EXAMPLE_ECIES.md | 8 ++-- .../EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md | 33 ++++++++++------ .../sdks/py/examples/EXAMPLE_FEE_MODELING.md | 4 +- guides/sdks/py/examples/EXAMPLE_HD_WALLETS.md | 23 ++++++----- .../py/examples/EXAMPLE_MESSAGE_SIGNING.md | 39 ++++++++++--------- .../py/examples/EXAMPLE_SCRIPT_TEMPLATES.md | 28 ++++++------- guides/sdks/py/examples/EXAMPLE_SIMPLE_TX.md | 32 ++++++++------- guides/sdks/py/examples/EXAMPLE_TYPE_42.md | 35 +++++++++-------- .../py/examples/EXAMPLE_VERIFYING_BEEF.md | 8 ++-- .../py/examples/EXAMPLE_VERIFYING_ROOTS.md | 6 +-- .../py/examples/EXAMPLE_VERIFYING_SPENDS.md | 26 ++++--------- 13 files changed, 142 insertions(+), 132 deletions(-) diff --git a/guides/sdks/py/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md b/guides/sdks/py/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md index ba056cd..4f7ba40 100644 --- a/guides/sdks/py/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md +++ b/guides/sdks/py/examples/EXAMPLE_BUILDING_CUSTOM_TX_BROADCASTER.md @@ -4,15 +4,15 @@ This guide walks through the necessary steps for building a custom transaction b ## Overview -A transaction broadcast client is a crucial component in any Bitcoin SV application, allowing it to communicate with the Bitcoin SV network. Implementing a transaction broadcaster can be accomplished using the clearly defined Broadcast interface. +A transaction broadcast client is a crucial component in any Bitcoin SV application, allowing it to communicate with the Bitcoin SV network. Implementing a transaction broadcaster can be accomplished using the clearly defined `Broadcaster` interface. -Our task will be to create a broadcaster that connects with the What's on Chain service. This broadcaster is particularly designed for browser applications and utilizes the standard Fetch API for HTTP communications with the relevant API endpoints. +Our task will be to create a broadcaster that connects with the [What's on Chain service](https://whatsonchain.com). WoC is a blockchain information service for the BSV network, enabling users to retrieve information transactions, blocks, and addresses as well as broadcasting new transactions. ## Getting Started In order to build a compliant broadcast client, we first need to import the interfaces to implement. -```ts +```py from bsv import ( Broadcaster, BroadcastFailure, @@ -21,24 +21,23 @@ from bsv import ( HttpClient, default_http_client, ) +from typing import Union ``` -Next, we create a new class that implements the Broadcaster interface which requires a broadcast function. +Next, we create a new class that implements the `Broadcaster` interface which requires a broadcast function. -We will be implementing a What's on Chain (WOC) broadcaster that runs in a browser context and uses [window.fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch\_API) to send a POST request to the WOC broadcast API endpoint. +We will be implementing a What's on Chain (WOC) broadcaster that uses the `HttpClient` interface to send a POST request to the WOC broadcast API endpoint. ```py class WOC(Broadcaster): - def __init__(self, network: str = "main", http_client: HttpClient = None): """ Constructs an instance of the WOC broadcaster. - :param network: which network to use (test or main) :param http_client: HTTP client to use. If None, will use default. """ self.network = network - self.URL = f"https://api.whatsonchain.com/v1/bsv/{network}/tx/raw" + self.URL = f"" self.http_client = http_client if http_client else default_http_client() async def broadcast( @@ -46,7 +45,6 @@ class WOC(Broadcaster): ) -> Union[BroadcastResponse, BroadcastFailure]: """ Broadcasts a transaction via WOC. - :param tx: The transaction to be broadcasted as a serialized hex string. :returns: BroadcastResponse or BroadcastFailure. """ @@ -55,7 +53,6 @@ class WOC(Broadcaster): "headers": {"Content-Type": "application/json", "Accept": "text/plain"}, "data": {"txhex": tx.hex()}, } - try: response = await self.http_client.fetch(self.URL, request_options) if response.ok: diff --git a/guides/sdks/py/examples/EXAMPLE_COMPLEX_TX.md b/guides/sdks/py/examples/EXAMPLE_COMPLEX_TX.md index c6314e1..3f34652 100644 --- a/guides/sdks/py/examples/EXAMPLE_COMPLEX_TX.md +++ b/guides/sdks/py/examples/EXAMPLE_COMPLEX_TX.md @@ -34,7 +34,8 @@ When constructing a Bitcoin transaction, inputs and outputs form the core compon An input in a Bitcoin transaction represents the bitcoins being spent. It's essentially a reference to a previous transaction's output. Inputs have several key components: -* **`source_transaction` or `source_txid`**: A reference to either the source transaction (another Transaction instance), its TXID. Referencing the transaction itself is always preferred because it exposes more information to the library about the outputs it contains. +* **`source_transaction`**: A `Transaction` object representing the transaction, which contains the UTXO the input is spending. Note, that this transaction may also only be partialy complete. +* **`source_txid`**: The TXID of the transaction where our input is spending an UTXO from. It's important to pass this information if the transaction we passed under the `source_transaction` parameter is incomplete, since the SDK in that case has no way of automatically reconstructing the TXID. * **`source_output_index`**: A zero-based index indicating which output of the referenced transaction is being spent. * **`sequence`**: A sequence number for the input. It allows for the replacement of the input until a transaction is finalized. If omitted, the final sequence number is used. * **`unlocking_script`**: This script proves the spender's right to access the bitcoins from the spent output. It typically contains a digital signature and, optionally, other data like public keys. @@ -51,7 +52,7 @@ k = 1 unlocking_script_template = puz.unlock(k) tx_input = TransactionInput( source_transaction=source_transaction, - source_output_index=0 + source_output_index=0, unlocking_script_template=unlocking_script_template ) @@ -96,7 +97,7 @@ my_tx.add_output(tx_output) ## Change and Fee Computation -The transaction fee is the difference between the total inputs and total outputs of a transaction. Miners collect these fees as a reward for including transactions in a block. The amount of the fee paid will determine the quality of service provided my miners, subject to their policies. +The transaction fee is the difference between the total inputs and total outputs of a transaction. Miners collect these fees as a reward for including transactions in a block. The amount of the fee paid will determine the quality of service provided by miners, subject to their policies. If the total value of the inputs exceeds the total value you wish to send (plus the transaction fee), the excess amount is returned to you as "change." Change is sent back to a destination controlled by the sender, ensuring that no value is lost. When you set the `change` property on an output to `true`, you don't need to define a number of satoshis. This is because the library computes the number of satoshis for you, when the `.fee()` method is called. @@ -117,7 +118,7 @@ my_tx.fee() Once you've defined your inputs and outputs, and once your change has been computed, the next step is to sign your transaction. There are a few things you should note when signing: * Only inputs with an unlocking template will be signed. If you provided an unlocking script yourself, the library assumes the signatures are already in place. -* If you change the inputs or outputs after signing, certain signatures will need to be re-computd, depending on the SIGHASH flags used. +* If you change the inputs or outputs after signing, certain signatures will need to be re-computed, depending on the SIGHASH flags used. * If your templates support it, you can produce partial signatures before serializing and sending to other parties. This is especially useful for multi-signature use-cases. With these considerations in mind, we can now sign our transaction. The `RPuzzle` unlocking templates we configured earlier will be used in this process. @@ -154,15 +155,15 @@ When properly linked, you can serialize your transactions in the SPV formats as ```py # Note: Requires use of source_transaction instead of source_txid for inputs -my_tx.to_BEEF() +my_tx.to_beef() # or -my_tx.to_EF() +my_tx.to_ef() ``` This enables the transactions to be verified properly by recipients, using the `.verify()` method: ```py -incoming_tx = Transaction.from_BEEF('...') +incoming_tx = Transaction.from_beef('...') incoming_tx.verify(chain_tracker) # Provide a source of BSV block headers to verify ``` diff --git a/guides/sdks/py/examples/EXAMPLE_ECIES.md b/guides/sdks/py/examples/EXAMPLE_ECIES.md index 1944bd2..382f497 100644 --- a/guides/sdks/py/examples/EXAMPLE_ECIES.md +++ b/guides/sdks/py/examples/EXAMPLE_ECIES.md @@ -4,7 +4,7 @@ Electrum ECIES is a protocol for exchanging encrypted data between parties. It h ## Message Encryption -In ECIES, a message can be encrypted directly to the public key of the recipient, either from your private key or from a random private key. The public key can either be included or excluded from the message. Check out the below examples: +In ECIES, a message can be encrypted directly to the public key of the recipient, either from your private key or from a random private key. The public key can either be included or excluded from the message. Check out the below example: ```py from bsv import PrivateKey @@ -26,8 +26,8 @@ print(private_key.decrypt_text(encrypted)) This guide has shown how to use Electrum ECIES encryption. While this approach has been used by many legacy systems, the SDK's native encryption has the following benefits: -* **Additional Security Layer**: The native SDK implentation, based on [BRC-78](https://github.com/bitcoin-sv/BRCs/blob/master/peer-to-peer/0078.md), employs an additional layer of security by utilizing a one-off ephemeral key for the encryption process. Even if the key for a particular message is discovered, it does not compromise the private keys of either of the parties. Different keys are used for every message, adding an additional step for attackers. -* **Incompatibility with BRC-43 Invoice Numbers**: The native approach is fully compatible with [BRC-43](https://brc.dev/43) invoice numbers, and the [BRC-2](https://brc.dev/2) encryption process, making it possible for users of the [BRC-56 standard wallet](https://brc.dev/56) able to natively use the system under their MetaNet identities. ECIES is not compatible with these standards. -* **Use of GCM over CBC**: While this is not a security risk, GCM supports range-based encryption and decryption. This may make it better than CBC if you need to send parts of a large encrypted dataset over the network. +- **Additional Security Layer**: The native SDK implementation, based on [BRC-78](https://github.com/bitcoin-sv/BRCs/blob/master/peer-to-peer/0078.md), employs an additional layer of security by utilizing a one-off ephemeral key for the encryption process. Even if the key for a particular message is discovered, it does not compromise the private keys of either of the parties. Different keys are used for every message, adding an additional step for attackers. +- **Incompatibility with BRC-43 Invoice Numbers**: The native approach is fully compatible with [BRC-43](https://brc.dev/43) invoice numbers, and the [BRC-2](https://brc.dev/2) encryption process, making it possible for users of the [BRC-56 standard wallet](https://brc.dev/56) to natively use the system under their MetaNet identities. ECIES is not compatible with these standards. +- **Use of GCM over CBC**: While this is not a security risk, GCM supports range-based encryption and decryption. This may make it better than CBC if you need to send parts of a large encrypted dataset over the network. Despite these drawbacks, Electrum ECIES still remains a fundamentally secure and robust encryption scheme. diff --git a/guides/sdks/py/examples/EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md b/guides/sdks/py/examples/EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md index 4a5842a..d96cdf0 100644 --- a/guides/sdks/py/examples/EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md +++ b/guides/sdks/py/examples/EXAMPLE_ENCRYPT_DECRYPT_MESSAGE.md @@ -10,32 +10,43 @@ Understanding the ins-and-outs of message encryption and decryption is key to im To get started, you will first want to import the required functions / classes. -``py -from bsv import PrivateKey +```py +from bsv import PrivateKey, EncryptedMessage ``` Next, you will want to configure who the sender is, the recipient, and what message you would like to encrypt. -```ts -sender = PrivateKey() -recipient = Private() +```py +# Create private keys for sender and recipient +sender = PrivateKey(15) +recipient = PrivateKey(21) + +# Get the public key of the recipient +recipient_pub = recipient.public_key() +print(recipient_pub) -message = 'The cake is a lie.' +# Define the message as a byte string +message = b'Did you receive the Bitcoin payment?' ``` -Now you are ready to generate the ciphertext using the `encrypt_text` function. +Now you are ready to generate the ciphertext using the `encrypt` function. ```py -encrypted = sender.public_key().encrypt_text(message) -print(encrypted) +# Encrypt the message +encrypted = EncryptedMessage.encrypt(message, sender, recipient_pub) +print(encrypted.hex()) ``` ### Decrypting a Message -To get back your plaintext message, use the `decrypt_text` function and then transform it as needed. +To get back your plaintext message, use the `decrypt` function and then transform it as needed. ```py -print(recipient.decrypt_text(encrypted)) +# Decrypt the message +decrypted = EncryptedMessage.decrypt(encrypted, recipient) + +# Display the decrypted message decoded as UTF-8 +print(decrypted.decode('utf-8')) ``` ## Considerations diff --git a/guides/sdks/py/examples/EXAMPLE_FEE_MODELING.md b/guides/sdks/py/examples/EXAMPLE_FEE_MODELING.md index 8ae334a..6e73382 100644 --- a/guides/sdks/py/examples/EXAMPLE_FEE_MODELING.md +++ b/guides/sdks/py/examples/EXAMPLE_FEE_MODELING.md @@ -106,7 +106,7 @@ class ExampleFeeModel(FeeModel): """ Represents the "satoshis per kilobyte" transaction fee model. Additionally, if the transactions version number is equal to 3301, - then no fees are payed to the miner. + then no fees are paid to the miner. """ def __init__(self, value: int): @@ -168,4 +168,4 @@ class ExampleFeeModel(FeeModel): return fee ``` -Now. when you create a new transaction and call the `.fee()` method with this fee model, it will follow the rules we have set above! +Now when you create a new transaction and call the `.fee()` method with this fee model, it will follow the rules we have set above! diff --git a/guides/sdks/py/examples/EXAMPLE_HD_WALLETS.md b/guides/sdks/py/examples/EXAMPLE_HD_WALLETS.md index 1e5654f..325ae74 100644 --- a/guides/sdks/py/examples/EXAMPLE_HD_WALLETS.md +++ b/guides/sdks/py/examples/EXAMPLE_HD_WALLETS.md @@ -1,20 +1,19 @@ # Example: BIP32 Key Derivation with HD Wallets -For a long time, BIP32 was the standard way to structure a Bitcoin wallet. While [type-42](EXAMPLE\_TYPE\_42.md) has since taken over as the standard approach due to its increased privacy and open-ended invoice numbering scheme, it's sometimes still necessary to interact with legacy systems using BIP32 key derivation. +For a long time, BIP32 was the standard way to structure a Bitcoin wallet. While [type-42](notion://www.notion.so/yenpoint/EXAMPLE_TYPE_42.md) has since taken over as the standard approach due to its increased privacy and open-ended invoice numbering scheme, it's sometimes still necessary to interact with legacy systems using BIP32 key derivation. -This guide will show you how to generate keys, derive child keys, and convert them to WIF and Bitcoin address formats. At the end, we'll compare BIP32 to the [type-42 system and encourage you to adopt the new approach](EXAMPLE\_TYPE\_42.md) to key management. +This guide will show you how to generate keys, derive child keys, and convert them to WIF and Bitcoin address formats. At the end, we'll compare BIP32 to the [type-42 system and encourage you to adopt the new approach](notion://www.notion.so/yenpoint/EXAMPLE_TYPE_42.md) to key management. ## Generating BIP32 keys You can generate a BIP32 seed with the SDK as follows: -```py +```python from bsv.hd import ( mnemonic_from_entropy, seed_from_mnemonic, master_xprv_from_seed, Xprv, derive_xprvs_from_mnemonic ) entropy = 'cd9b819d9c62f0027116c1849e7d497f' # Or use some randomly generated string... - # snow swing guess decide congress abuse session subway loyal view false zebra mnemonic: str = mnemonic_from_entropy(entropy) print(mnemonic) @@ -27,6 +26,10 @@ print(master_xprv) ``` You can also import an existing key as follows: + +```python +# Example of importing an existing key (code snippet missing in the original) +# existing_key = Xprv.from_string("xprv...") ``` Now that you've generated or imported your key, you're ready to derive child keys. @@ -35,12 +38,11 @@ Now that you've generated or imported your key, you're ready to derive child key BIP32 child keys can be derived from a key using the `derive_xprv_from_mnemonic` function. Here's a full example: -```py +```python 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() ``` @@ -50,22 +52,19 @@ Any of the standard derivation paths can be passed into the derivation function. XPRIV keys can be converted to normal `PrivateKey` instances, and from there to WIF keys. XPUB keys can be converted to normal `PublicKey` instances, and from there to Bitcoin addresses. XPRIV keys can also be converted to XPUB keys: -```py +```python # XPriv to WIF print(key.private_key().wif()) - # XPriv to XPub key_xpub = key.xpub() - # XPub to public key print(key_xpub.public_key().hex()) - # XPub to address -print(key_xpub.public_key().address(), '\n') +print(key_xpub.public_key().address(), '\\n') ``` This guide has demonstrated how to use BIP32 for key derivation and format conversion. You can continue to use BIP32 within BSV wallet applications, but it's important to consider the disadvantages and risks of continued use, which are discussed below. ## Disadvantages and Risks -BIP32 allows anyone to derive child keys if they know an XPUB. The number of child keys per parent is limited to 2^31, and there's no support for custom invoice numbering schemes that can be used when deriving a child, only a simple integer. Finally, BIP32 has no support for private derivation, where two parties share a common key universe no one else can link to them, even while knowing the master public key. It's for these reasons that we recommend the use of type-42 over BIP32. You can read an equivalent guide [here](EXAMPLE\_TYPE\_42.md). +BIP32 allows anyone to derive child keys if they know an XPUB. The number of child keys per parent is limited to 2^31, and there's no support for custom invoice numbering schemes that can be used when deriving a child, only a simple integer. Finally, BIP32 has no support for private derivation, where two parties share a common key universe no one else can link to them, even while knowing the master public key. It's for these reasons that we recommend the use of type-42 over BIP32. You can read an equivalent guide [here](notion://www.notion.so/yenpoint/EXAMPLE_TYPE_42.md). \ No newline at end of file diff --git a/guides/sdks/py/examples/EXAMPLE_MESSAGE_SIGNING.md b/guides/sdks/py/examples/EXAMPLE_MESSAGE_SIGNING.md index a048b17..17c9f8b 100644 --- a/guides/sdks/py/examples/EXAMPLE_MESSAGE_SIGNING.md +++ b/guides/sdks/py/examples/EXAMPLE_MESSAGE_SIGNING.md @@ -6,9 +6,9 @@ This guide walks through the necessary steps for both public and private message Message signing is a mechanism that preserves the integrity of secure communications, enabling entities to verify the authenticity of a message's origin. This document emphasizes two primary types of message signing: private and public. -Private message signing is used when a message needs to be verified by a _specific recipient_. In this scenario, the sender creates a unique signature using their private key combined with the recipient's public key. The recipient, using their private key, can then verify the signature and thereby the message's authenticity. +Private message signing is used when a message needs to be verified by a *specific recipient*. In this scenario, the sender creates a unique signature using their private key combined with the recipient's public key. The recipient, using their private key, can then verify the signature and thereby the message's authenticity. -On the other hand, public message signing creates a signature that can be verified by _anyone_, without the need for any specific private key. This is achieved by the sender using only their private key to create the signature. +On the other hand, public message signing creates a signature that can be verified by *anyone*, without the need for any specific private key. This is achieved by the sender using only their private key to create the signature. The choice between private and public message signing hinges on the specific requirements of the communication. For applications that require secure communication where authentication is paramount, private message signing proves most effective. Conversely, when the authenticity of a message needs to be transparent to all parties, public message signing is the go-to approach. Understanding these differences will enable developers to apply the correct method of message signing depending on their specific use case. @@ -16,48 +16,49 @@ The choice between private and public message signing hinges on the specific req To get started, you will first want to import the required functions / classes. -```py -from bsv import PrivateKey, verify_signed_text +```python +from bsv import PrivateKey, SignedMessage + ``` Next, you will want to configure who the sender is, the recipient, and what message you would like to sign. -```py +```python sender = PrivateKey() recipient = PrivateKey() +message = b'Hello world!' -message = 'Hello world!' ``` Now we can sign the message and generate a signature that can only be verified by our specified recipient. -```py -address, signature = sender.sign_text(message) +```python +signature = SignedMessage.sign(message, sender, recipient.public_key()) +verified = SignedMessage.verify(message, signature, recipient) +print('Verified:', verified) -verified = verify_signed_text(message, address, signature) -print('Verified:': verified) ``` ### 2. Example Code - Public Message Signing To create a signature that anyone can verify, the code is very similar to the first example, just without a specified recipient. This will allow anyone to verify the signature generated without requiring them to know a specific private key. -```ts +```python signer = PrivateKey() +message = b'I like big blocks and I cannot lie!' -message = 'I like big blocks and I cannot lie!' +signature = SignedMessage.sign(message, sender) +verified = SignedMessage.verify(message, signature) +print('Verified:', verified) -address, signature = signer.sign_text(message) -verified = verify_signed_text(message, address, signature) -print('Verified:': verified) ``` ## Considerations While these private signing functions are built on industry standards and well-tested code, there are considerations to keep in mind when integrating them into your applications. -* **Private Key Security**: Private keys must be stored securely to prevent unauthorized access, as they can be used to sign fraudulent messages. -* **Use Case Analysis**: As stated in the overview, you should carefully evaluate whether you need a private or publicly verifiable signature based on your use case. -* **Implications of Signature Verifiability**: When creating signatures that anyone can verify, consider the implications. While transparency is achieved, sensitive messages should only be verifiable by intended recipients. +- **Private Key Security**: Private keys must be stored securely to prevent unauthorized access, as they can be used to sign fraudulent messages. +- **Use Case Analysis**: As stated in the overview, you should carefully evaluate whether you need a private or publicly verifiable signature based on your use case. +- **Implications of Signature Verifiability**: When creating signatures that anyone can verify, consider the implications. While transparency is achieved, sensitive messages should only be verifiable by intended recipients. -By understanding and applying these considerations, you can ensure a secure implementation of private signing within your applications. +By understanding and applying these considerations, you can ensure a secure implementation of message signing within your applications. \ No newline at end of file diff --git a/guides/sdks/py/examples/EXAMPLE_SCRIPT_TEMPLATES.md b/guides/sdks/py/examples/EXAMPLE_SCRIPT_TEMPLATES.md index 8b1a72c..35e10e5 100644 --- a/guides/sdks/py/examples/EXAMPLE_SCRIPT_TEMPLATES.md +++ b/guides/sdks/py/examples/EXAMPLE_SCRIPT_TEMPLATES.md @@ -1,33 +1,33 @@ # Example: Creating the R-puzzle Script Template -This guide will provide information about the structure and functionality of script templates within the BSV SDK. Script templates are a powerful abstraction layer designed to simplify the creation and management of the scripts used in Bitcoin transactions. By understanding how these templates work, developers can leverage them to build more sophisticated and efficient blockchain applications. By the end of this example, you'll understand how the R-puzzle script template (P2RPH) was created. +This guide provides information about the structure and functionality of script templates within the BSV SDK. Script templates are a powerful abstraction layer designed to simplify the creation and management of scripts used in Bitcoin transactions. By understanding how these templates work, developers can leverage them to build more sophisticated and efficient blockchain applications. By the end of this example, you'll understand how the R-puzzle script template (P2RPH) was created. -### Understanding Script Templates +## Understanding Script Templates A script template is essentially a blueprint for creating the locking and unlocking scripts that are crucial for securing and spending bitcoins. These templates encapsulate the logic needed to construct these scripts dynamically, based on the parameters passed to them. This approach allows for a modular and reusable codebase, where common scripting patterns can be defined once and then instantiated as needed across different transactions. -#### Locking Script +### Locking Script The locking script, or output script, specifies the conditions under which the bitcoins can be spent. In the BSV SDK, the `lock` function of a script template is responsible for generating this script. By abstracting the creation of locking scripts into a method that accepts parameters, developers can easily create diverse conditions for spending bitcoins without having to write the low-level script code each time. For example, a locking script might require the presentation of a public key that matches a certain hash or the fulfillment of a multi-signature condition. The flexibility of passing parameters to the `lock` function enables the creation of locking scripts tailored to specific requirements. This example will require a signature created with a particular ephemeral K-value, [an R-puzzle](https://wiki.bitcoinsv.io/index.php/R-Puzzles). -#### Unlocking Script +### Unlocking Script The unlocking script, or input script, provides the evidence needed to satisfy the conditions set by the locking script. The `unlock` method in a script template not only generates this script but also offers two key functionalities — it's a function that returns an object with two properties: 1. **`estimate_length`**: Before a transaction is signed and broadcast to the network, it's crucial to estimate its size to calculate the required fee accurately. The `estimateLength` function predicts the length of the unlocking script once it will be created, allowing developers to make informed decisions about fee estimation. 2. **`sign`**: This function generates an unlocking script that includes the necessary signatures or data required to unlock the bitcoins. By accepting a transaction and an input index as arguments, it ensures that the unlocking script is correctly associated with the specific transaction input it intends to fund, allowing signatures to be scoped accordingly. -### Creating a Script Template +## Creating a Script Template To create a script template, developers define a class that adheres to the `ScriptTemplate` interface. This involves implementing the `lock` and `unlock` methods with the specific logic needed for their application. Now that you understand the necessary components, here's the code for the R-puzzle script template: -```py +```python class RPuzzle(ScriptTemplate): - + def __init__(self, puzzle_type: str = 'raw'): """ Constructs an R Puzzle template instance for a given puzzle type. @@ -61,8 +61,7 @@ class RPuzzle(ScriptTemplate): chunks.append(OpCode.OP_EQUALVERIFY) chunks.append(OpCode.OP_CHECKSIG) return Script(b''.join(chunks)) - - + def unlock(self, k: int, private_key: Optional[PrivateKey] = PrivateKey(), sign_outputs: str = 'all', anyone_can_pay: bool = False): """ Creates a function that generates an R puzzle unlocking script along with its signature and length estimation. @@ -83,12 +82,12 @@ class RPuzzle(ScriptTemplate): sighash |= SIGHASH.SINGLE if anyone_can_pay: sighash |= SIGHASH.ANYONECANPAY - + tx.inputs[input_index].sighash = sighash preimage = tx.preimage(input_index) - sig = private_key.sign(preimage, hasher=hash256, k=k) + sighash.to_bytes(1, "little") + sig = private_key.sign(preimage, hasher=hash256, k=k) + sighash.to_bytes(1, 'little') pubkey_for_script = private_key.public_key().serialize() return Script(encode_pushdata(sig) + encode_pushdata(pubkey_for_script)) @@ -99,10 +98,11 @@ class RPuzzle(ScriptTemplate): return 106 return to_unlock_script_template(sign, estimated_unlocking_byte_length) + ``` -In this example, `RPuzzle` defines custom logic for creating both locking and unlocking scripts. The opcodes, intermixed with the various template fields, enable end-users to implement R-puzzles into their applications without being concerned with these low-level details. Check out [this guide](EXAMPLE\_COMPLEX\_TX.md) to see an example of this template used in a transaction. +In this example, `RPuzzle` defines custom logic for creating both locking and unlocking scripts. The opcodes, intermixed with the various template fields, enable end-users to implement R-puzzles into their applications without being concerned with these low-level details. Check out [this guide](notion://www.notion.so/yenpoint/EXAMPLE_COMPLEX_TX.md) to see an example of this template used in a transaction. -### Conclusion +## Conclusion -Script templates in the BSV SDK offer a structured and efficient way to handle the creation of locking and unlocking scripts in Bitcoin transactions. By encapsulating the logic for script generation and providing essential functionalities like signature creation and length estimation, script templates make it easier for developers to implement complex transactional logic. With these tools, template consumers can focus on the higher-level aspects of their blockchain applications, relying on the SDK to manage the intricacies of script handling. +Script templates in the BSV SDK offer a structured and efficient way to handle the creation of locking and unlocking scripts in Bitcoin transactions. By encapsulating the logic for script generation and providing essential functionalities like signature creation and length estimation, script templates make it easier for developers to implement complex transactional logic. With these tools, template consumers can focus on the higher-level aspects of their blockchain applications, relying on the SDK to manage the intricacies of script handling. \ No newline at end of file diff --git a/guides/sdks/py/examples/EXAMPLE_SIMPLE_TX.md b/guides/sdks/py/examples/EXAMPLE_SIMPLE_TX.md index bdcc3e7..c9438df 100644 --- a/guides/sdks/py/examples/EXAMPLE_SIMPLE_TX.md +++ b/guides/sdks/py/examples/EXAMPLE_SIMPLE_TX.md @@ -10,20 +10,24 @@ Transactions in Bitcoin are mechanisms for transferring value and invoking smart Consider the scenario where you need to create a transaction. The process involves specifying inputs (where the bitcoins are coming from) and outputs (where they're going). Here's a simplified example: -```javascript -from bsv import PrivateKey, PublicKey, ARC, P2PKH, Transaction +```python +import asyncio +from bsv import PrivateKey, ARC, P2PKH, Transaction, TransactionInput, TransactionOutput + +priv_key = PrivateKey("...") # Your P2PKH private key +change_priv_key = PrivateKey("...") # Change private key (never re-use addresses) +recipient_address = '...' # Address of the recipient +source_tx = Transaction.from_hex('...') -priv_key = PrivateKey.fromWif('...') # Your P2PKH private key -change_priv_key = PrivateKey.fromWif('...') # Change private key (never re-use addresses) -recipient_address = '1Fd5F7XR8LYHPmshLNs8cXSuVAAQzGp7Hc' # Address of the recipient tx = Transaction() # Add the input tx.add_input( TransactionInput( - source_transaction=Transaction.from_hex('...'), # The source transaction where the output you are spending was created - source_output_index=0, + source_transaction=source_tx, + source_txid=source_tx.txid(), + source_output_index=1, unlocking_script_template=P2PKH().unlock(priv_key), # The script template you are using to unlock the output, in this case P2PKH ) ) @@ -32,14 +36,14 @@ tx.add_input( tx.add_output( TransactionOutput( locking_script=P2PKH().lock(recipient_address), - satoshis=2500 + satoshis=1 ) ) # Send remainder back the change address tx.add_output( TransactionOutput( - lockingScript=P2PKH().lock(change_priv_key.address()), + locking_script=P2PKH().lock(change_priv_key.address()), change=True ) ) @@ -47,11 +51,12 @@ tx.add_output( # Now we can compute the fee and sign the transaction tx.fee() tx.sign() - +print(tx.hex()) # Finally, we broadcast it with ARC. # get your api key from https://console.taal.com -api_key = 'mainnet_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' # replace -await tx.broadcast(ARC('https://api.taal.com/arc', api_key)) +api_key = 'mainnet_...' # replace +asyncio.run(tx.broadcast(ARC('https://api.taal.com/arc', api_key))) + ``` This code snippet demonstrates creating a transaction, adding an input and an output, setting a change script, configuring the fee, signing the transaction, and broadcasting with the ARC broadcaster. It uses the P2PKH Template, which is a specific type of Bitcoin locking program. To learn more about templates, check out this example (link to be provided once complete). @@ -60,13 +65,14 @@ This code snippet demonstrates creating a transaction, adding an input and an ou Moving beyond this basic example into more advanced use-cases enables you to start dealing with custom scripts. If you're provided with a hex-encoded locking script for an output, you can set it directly in the transaction's output as follows: -```py +```python transaction.add_output( TransactionOutput( locking_script=Script.from_asm('OP_ADD OP_MUL ...'), satoshis=100 ) ) + ``` The `Transaction` class abstracts the complexity of Bitcoin's transaction structure. It handles inputs, outputs, scripts, and serialization, offering methods to easily modify and interrogate the transaction. Check out the full code-level documentation, refer to other examples, or reach out to the community to learn more. diff --git a/guides/sdks/py/examples/EXAMPLE_TYPE_42.md b/guides/sdks/py/examples/EXAMPLE_TYPE_42.md index c5707ea..b00b77d 100644 --- a/guides/sdks/py/examples/EXAMPLE_TYPE_42.md +++ b/guides/sdks/py/examples/EXAMPLE_TYPE_42.md @@ -8,18 +8,18 @@ This guide will walk you through using type-42 keys in the context of a Bitcoin In type-42 systems, you provide a counterparty key when deriving, as well as your own. There is always one public key and one private key. It's either: -* Your private key and the public key of your counterparty are used to derive one of your private keys, or -* Your private key and the public key of your counterparty are used to derive one of their public keys +- Your private key and the public key of your counterparty are used to derive one of your private keys, or +- Your private key and the public key of your counterparty are used to derive one of their public keys When you and your counterparty use the same invoice number to derive keys, the public key you derive for them will correspond to the private key they derive for themselves. A private key that you derive for yourself will correspond to the public key they derived for you. Once you understand those concepts, we're ready to jump into some code! -### Alice and Bob +## Alice and Bob -Let's consider the scenario of Alice and Bob, who want to exchange some Bitcoin. How can Alice send Bitcoins to Bob? +Let's consider the scenario of Alice and Bob, who want to exchange some bitcoin. How can Alice send bitcoins to Bob? -1. Alice learns Bob's master public key, and they agree on the Bitcoin aount to exchange. +1. Alice learns Bob's master public key, and they agree on the Bitcoin amount to exchange. 2. They also agree on an invoice number. 3. Alice uses Bob's master public key with her private key to derive the payment key she will use. 4. Alice creates a Bitcoin transaction and pays Bob the money. @@ -27,7 +27,7 @@ Let's consider the scenario of Alice and Bob, who want to exchange some Bitcoin. Here's an example: -```py +```python # Master private keys: alice = PrivateKey() bob = PrivateKey() @@ -36,44 +36,47 @@ bob = PrivateKey() alice_pub = alice.public_key() bob_pub = bob.public_key() -# To pay Alice, they agree on an invoice number and then Bob derives a key where he can pay Alice. -payment_key = alice_pub.derive_child(bob, 'AMZN-44-1191213') +# To pay Bob, they agree on an invoice number and then Alice derives a key where she can pay Bob. +payment_key = bob_pub.derive_child(alice, 'AMZN-44-1191213') # The key can be converted to an address if desired... print(payment_key.address()) -# To unlock the coins, Alice derives the private key with the same invoice number, using Bob's public key. -payment_priv = alice.derive_child(bob_pub, 'AMZN-44-1191213') +# To unlock the coins, Bob derives the private key with the same invoice number, using Alice's public key. +payment_priv = bob.derive_child(alice_pub, 'AMZN-44-1191213') # The key can be converted to WIF if desired... print(payment_priv.wif()) -# To check, Alice can convert the private key back into an address. +# To check, Bob can convert the private key back into an address. assert(payment_priv.public_key().address() == payment_key.address()) + ``` -This provides privacy for Alice and Bob, even if eeryone in the world knows Alice and Bob's master public keys. +This provides privacy for Alice and Bob, even if everyone in the world knows Alice and Bob's master public keys. ## Going Further: Public Derivation -Sometimes, there is a legitimate reason to do "public key derivation" from a key, so that anyone can link a master key to a child key, like in BIP32. To accomplish this, rather than creating a new algorithm, we just use a private key that everyone already knows: the number `1`. +Sometimes, there is a legitimate reason to do "public key derivation" from a key, so that anyone can link a master key to a child key, like in BIP32. To accomplish this, rather than creating a new algorithm, we just use a private key that everyone already knows: the number `1`. This number is chosen because it's simple, universally known, and serves as a neutral, shared secret for public derivation. -```py +```python print('Public keys:') print(alice_pub.derive_child(PrivateKey(1), '1').hex()) print(alice_pub.derive_child(PrivateKey(1), '2').hex()) print(alice_pub.derive_child(PrivateKey(1), 'Bitcoin SV').hex()) print(alice_pub.derive_child(PrivateKey(1), '2-tempo-1').hex()) + ``` Because everyone knows the number `1`, everyone can derive Alice's public keys with these invoice numbers. But only Alice can derive the corresponding private keys: -```py +```python print('Private keys:') print(alice.derive_child(PrivateKey(1).public_key(), '1').hex()) print(alice.derive_child(PrivateKey(1).public_key(), '2').hex()) print(alice.derive_child(PrivateKey(1).public_key(), 'Bitcoin SV').hex()) print(alice.derive_child(PrivateKey(1).public_key(), '2-tempo-1').hex()) + ``` -The type-42 system enables both public and private key derivation, all while providing a more flexible and open-ended invoice numbering scheme than BIP32. +The type-42 system enables both public and private key derivation, all while providing a more flexible and open-ended invoice numbering scheme than BIP32. \ No newline at end of file diff --git a/guides/sdks/py/examples/EXAMPLE_VERIFYING_BEEF.md b/guides/sdks/py/examples/EXAMPLE_VERIFYING_BEEF.md index 077bdb3..5e10045 100644 --- a/guides/sdks/py/examples/EXAMPLE_VERIFYING_BEEF.md +++ b/guides/sdks/py/examples/EXAMPLE_VERIFYING_BEEF.md @@ -20,18 +20,19 @@ For simplicity in this example, we are going to use a mock headers client that a Here is the gullible chain tracker we will be using: -```py +```python class GullibleChainTracker(ChainTracker): async def is_valid_root_for_height(self, root: str, height: int) -> bool: return True # DO NOT USE IN PRODUCTION! + ``` ## Verifying a BEEF Structure Now that you have access to a block headers client (either Pulse on a real project or the above code for a toy example), we can proceed to verifying the BEEF structure with the following code: -```py +```python # Replace with the BEEF structure you'd like to check BEEF_hex = '0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000' @@ -44,6 +45,7 @@ verified = await tx.verify(GullibleChainTracker()) # Print the results print('BEEF verified:', verified) + ``` -The above code allows you to ensure that a given BEEF structure is valid according to the rules of SPV. +The above code allows you to ensure that a given BEEF structure is valid according to the rules of SPV. \ No newline at end of file diff --git a/guides/sdks/py/examples/EXAMPLE_VERIFYING_ROOTS.md b/guides/sdks/py/examples/EXAMPLE_VERIFYING_ROOTS.md index 09d5366..7374839 100644 --- a/guides/sdks/py/examples/EXAMPLE_VERIFYING_ROOTS.md +++ b/guides/sdks/py/examples/EXAMPLE_VERIFYING_ROOTS.md @@ -1,10 +1,10 @@ -# Example: Building a Pulse Block Headers Client +# Example: Building a Block Headers Client -When [verifying BEEF structures](EXAMPLE_VERIFYING_BEEF.md), it's necessary to ensure that all transactions are well-anchored: this is to say, that they come from inputs in the honest chain. The SDK doesn't ship with a headers client, but this guide shows an example of how to use it with [Pulse](https://github.com/bitcoin-sv/block-headers-service): a popular client suitable for a wide range of use-cases. +When [verifying BEEF structures](EXAMPLE_VERIFYING_BEEF.md), it's necessary to ensure that all transactions are well-anchored: this is to say, that they come from inputs in the honest chain. The SDK doesn't ship with a headers client, but this guide shows an example of how to use it with [Block Headers Service](https://github.com/bitcoin-sv/block-headers-service): a popular client suitable for a wide range of use-cases. ## Pre-requisites -As stated in the README, you will need to be running a Pulse instance. Get it up and running, and configure a level of authentication appropriate for your use-case: +As stated in the README, you will need to be running a Block Headers Service instance. Get it up and running, and configure a level of authentication appropriate for your use-case: ```sh docker pull bsvb/block-headers-service diff --git a/guides/sdks/py/examples/EXAMPLE_VERIFYING_SPENDS.md b/guides/sdks/py/examples/EXAMPLE_VERIFYING_SPENDS.md index 76d3b6f..b3fe917 100644 --- a/guides/sdks/py/examples/EXAMPLE_VERIFYING_SPENDS.md +++ b/guides/sdks/py/examples/EXAMPLE_VERIFYING_SPENDS.md @@ -1,4 +1,4 @@ -# Example: Verifying Spends with Script Intrepreter +# Example: Verifying Spends with Script Interpreter The SDK comes with a script interpreter that allows you to verify the chain of spends within Bitcoin. When coins are spent from one transaction to another, the process is carried out between a particular output of the source transaction and a particular input of the spending transaction. The `Spend` class sets up the necessary contextual information for this process, and then evaluates the scripts to determine if the transfer is legitimate. @@ -6,51 +6,41 @@ This guide will walk you through the verification of a real spend, with real dat ## Pre-requisites -We will need two transactions: a source transaction and a spending transaction, in order to set up the `Spend` context in which the transfer of coins occurs between them. When you construct an instance of the `Spend` class, you'll need to provide this information in the correct format. In a new file, let's set up som basic things we'll need: +We will need two transactions: a source transaction and a spending transaction, in order to set up the `Spend` context in which the transfer of coins occurs between them. When you construct an instance of the `Spend` class, you'll need to provide this information in the correct format. In a new file, let's set up some basic things we'll need: -```py +```python from bsv import Spend, Script - spend = Spend({ # Replace with the TXID of the transaction where you are spending from 'sourceTXID': '00' * 32, - # Replace with the output index you are redeeming 'sourceOutputIndex': 0, - # Replace with the number of satoshis in the output you are redeeming. 'sourceSatoshis': 1, - # Replace with the locking script you are spending. 'lockingScript': Script.from_asm('OP_3 OP_ADD OP_7 OP_EQUAL'), - # Replace with the version of the new spending transaction. 'transactionVersion': 1, - # Other inputs from the spending transaction that are needed for verification. # The SIGHASH flags used in signatures may not require this (if SIGHASH_ANYONECANPAY was used). # This is an ordered array of TransactionInputs with the input whose script we're currently evaluating missing. 'otherInputs': [], - # TransactionOutputs from the spending transaction that are needed for verification. - # The SIGHASH flags used in signatures may nnt require this (if SIGHASH_NONE was used). + # The SIGHASH flags used in signatures may not require this (if SIGHASH_NONE was used). # If SIGHASH_SINGLE is used, it's possible for this to be a sparse array, with only the index corresponding to # the inputIndex populated. 'outputs': [], - # This is the index of the input whose script we are currently evaluating. 'inputIndex': 0, - # This is the unlocking script that we are evaluating, to see if it unlocks the source output. 'unlockingScript': Script.from_asm('OP_4'), - # This is the sequence number of the input whose script we are currently evaluating. 'inputSequence': 0xffffffff, - # This is the lock time of the spending transaction. 'lockTime': 0, }) + ``` Once you've provided the context and constructed the Spend, you should have a new spend instance ready for verification. @@ -59,13 +49,13 @@ Once you've provided the context and constructed the Spend, you should have a ne You can use the `validate()` method to run the scripts and validate the spend, as follows: -```py +```python valid = spend.validate() - print('Verified:', valid) assert valid + ``` The result will be a boolean indicating the result of the script. If there is an error thrown, or if the boolean is false, the script is not valid. If the boolean is true, the spend is considered valid. -Errors from the spend will contain useful information, such as the specific opcode and context in which the error occurred (in either the locking or unlocking script). +Errors from the spend will contain useful information, such as the specific opcode and context in which the error occurred (in either the locking or unlocking script). \ No newline at end of file