Skip to content

Commit

Permalink
Merge pull request #64 from jrakibi/01-02-siganture-topics
Browse files Browse the repository at this point in the history
Add 11 topics on tx signature
  • Loading branch information
jrakibi authored Jan 31, 2025
2 parents 97fbb96 + 8045533 commit 343a199
Show file tree
Hide file tree
Showing 32 changed files with 1,765 additions and 42 deletions.
110 changes: 110 additions & 0 deletions decoding/transaction-signing-00.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: "Step 0: Create Base Transaction"
date: 2024-01-25
lastmod: "2024-01-25"
draft: false
category: Transactions
layout: TopicBanner
order: 0
icon: "FaClipboardList"
images:
[
"/bitcoin-topics/static/images/topics/thumbnails/transaction-module/signature/tx-thumbnail-signature-0.jpg"
]
---

In this step, we define the base structure of the transaction, which includes:

- Version (4 bytes)
- Marker/Flag (required for SegWit transactions)
- Locktime (4 bytes)

Inputs, outputs, and witness data will be added in the next steps.

<div className="dark:hidden w-full rounded-xl overflow-hidden full-width">
<SvgDisplay
src="/bitcoin-topics/static/images/topics/transactions/signature/signature6.svg"
width="100%"
height="auto"
/>
</div>
<div className="hidden dark:block w-full rounded-xl overflow-hidden full-width">
<SvgDisplay
src="/bitcoin-topics/static/images/topics/transactions/signature/signature6.svg"
width="100%"
height="auto"
/>
</div>

<ExpandableAlert
title="SegWit vs Legacy Transactions"
type="important"
expandable={true}
initialLines={3}
>
If a transaction has at least one SegWit input (native or wrapped), it must
include:
- Marker byte (0x00)
- Flag byte (0x01)
</ExpandableAlert>

The transaction structure at this stage is:

<CodeSnippet
language="text"
highlightLines={[4, 5, 6, 12]}
showLineNumbers={true}
code={`Transaction Breakdown:
═══════════════════════════════════════════════════════════════════════════════════
version: 01000000
marker: 00
flag: 01
in: # We'll add inputs in the next step
out: # We'll add outputs later
locktime: 11000000`}
/>

## Code Implementation

<CodeSnippet
code={`def create_basic_tx(
version: int,
inputs: list,
outputs: list,
locktime: int,
segwit: bool = True
) -> bytes:
# 4-byte version in little-endian
tx_version = int_to_little_endian(version, 4)
# Marker + Flag for segwit (only if segwit=True)
marker_flag = b'\\x00\\x01' if segwit else b''
# Number of inputs/outputs (varint)
in_count = varint(len(inputs))
out_count = varint(len(outputs))
# Serialize inputs and outputs
serialized_inputs = b''.join(inputs)
serialized_outputs = b''.join(outputs)
# Locktime (4 bytes)
tx_locktime = int_to_little_endian(locktime, 4)
return (
tx_version +
marker_flag +
in_count +
serialized_inputs +
out_count +
serialized_outputs +
tx_locktime
)`}
language="python"
/>

Let's now add inputs and outputs in the next step!
133 changes: 133 additions & 0 deletions decoding/transaction-signing-01.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: "Step 1: Create Transaction Inputs"
date: 2024-01-25
lastmod: "2024-01-25"
draft: false
category: Transactions
layout: TopicBanner
order: 1
icon: "FaClipboardList"
images:
[
"/bitcoin-topics/static/images/topics/thumbnails/transaction-module/signature/tx-thumbnail-signature-1.jpg"
]
---

Each input in a Bitcoin transaction must specify:

- Transaction ID (32 bytes): Points to the UTXO being spent
- Output Index (4 bytes): Which output from that transaction
- ScriptSig: Placeholder for the unlocking script
- Sequence (4 bytes): Usually 0xFFFFFFFF

<div className="w-full rounded-xl overflow-hidden full-width">
<SvgDisplay
src="/bitcoin-topics/static/images/topics/transactions/signature/signature7.svg"
width="100%"
height="auto"
/>
</div>

The following table summarizes our UTXOs (Transaction IDs shown in big-endian format):

| Input | Transaction ID (big-endian) | Output Index | Sequence |
| ----- | ---------------------------------------------------------------- | ------------ | -------- |
| #1 | 9f96add4e4db413543df3eea1781c3be62637f1e2dd44069fa99801a88f7f7ff | 0 | eeffffff |
| #2 | 8ac60eb9575db5b2d987e29f301b5b819ea83a5c6579d282d189cc04b8e151ef | 1 | ffffffff |

_Note: In the transaction serialization below, these Transaction IDs are converted to little-endian format as required by the Bitcoin protocol._

<CodeSnippet
language="text"
highlightLines={[6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20]}
showLineNumbers={true}
code={`Transaction Breakdown:
═══════════════════════════════════════════════════════════════════════════════════
Version: 01000000
Input Count: 02
input[0]:
Previous TXID: fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f
Output Index: 00000000
ScriptSig Size: 00
ScriptSig:
Sequence: eeffffff
input[1]:
Previous TXID: ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a
Output Index: 01000000
ScriptSig Size: 00
ScriptSig:
Sequence: ffffffff
Locktime: 11000000`}
/>

<ExpandableAlert
title="Why are scriptSig fields empty?"
type="info"
expandable={true}
>
Transaction signing follows a specific order:
1. First, we create the
transaction structure with empty scriptSig fields
2. Then, we calculate the
signature hash (sighash) for each input
3. Finally, we add the signatures to
create the complete transaction
</ExpandableAlert>

## Code Implementation

<CodeSnippet
code={`def create_input(
txid: str,
vout: int,
script_sig: bytes = b'',
sequence: bytes = b'\\xff\\xff\\xff\\xff'
) -> bytes:
# Convert txid from hex string and reverse (to little-endian)
txid_bytes = bytes.fromhex(txid)[::-1]
# Convert vout to 4 bytes, little-endian
vout_bytes = int_to_little_endian(vout, 4)
# Script length and script
script_sig_length = varint(len(script_sig))
return (
txid_bytes + # 32 bytes
vout_bytes + # 4 bytes
script_sig_length + # 1 byte
script_sig + # variable
sequence # 4 bytes
)`}
language="python"
/>

<ExpandableAlert title="Helper Functions" type="info" expandable={true}>
Two essential encoding functions:
1. int_to_little_endian: Converts integers to little-endian byte format
2. varint: Encodes variable-length integers used for counts and lengths

<CodeSnippet
code={`def int_to_little_endian(value: int, length: int) -> bytes:
"""Convert an integer to little-endian bytes"""
return value.to_bytes(length, 'little')
def varint(n: int) -> bytes:
"""Encode an integer as a variable length integer"""
if n < 0xfd:
return bytes([n])
elif n <= 0xffff:
return b'\\xfd' + n.to_bytes(2, 'little')
elif n <= 0xffffffff:
return b'\\xfe' + n.to_bytes(4, 'little')
else:
return b'\\xff' + n.to_bytes(8, 'little')`}
language="python"
/>

</ExpandableAlert>
113 changes: 113 additions & 0 deletions decoding/transaction-signing-02.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: "Step 2: Create Transaction Outputs"
date: 2024-01-25
lastmod: "2024-01-25"
draft: false
category: Transactions
layout: TopicBanner
order: 2
icon: "FaClipboardList"
images:
[
"/bitcoin-topics/static/images/topics/thumbnails/transaction-module/signature/tx-thumbnail-signature-2.jpg"
]
---

Each output in a Bitcoin transaction requires:

- Amount (8 bytes): Value in satoshis
- ScriptPubKey: Locking script that defines spending conditions

<div className="w-full rounded-xl overflow-hidden full-width">
<SvgDisplay
src="/bitcoin-topics/static/images/topics/transactions/signature/signature8.svg"
width="100%"
height="auto"
/>
</div>

---

## Code Implementation

<CodeSnippet
code={`def create_output(
amount: int,
script_pubkey: bytes
) -> bytes:
# Amount (8 bytes, little-endian)
amount_bytes = int_to_little_endian(amount, 8)
return (
amount_bytes + # value in satoshis
script_pubkey # includes length prefix
)`}
language="python"
/>

The following table summarizes our outputs from test vector:

| Output | Amount (BTC) | ScriptPubKey |
| ------ | ------------ | ---------------------------------------------------- |
| #1 | 1.2 | 1976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac |
| #2 | 2.2 | 1976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac |

Our transaction structure at this stage is:

<CodeSnippet
language="text"
highlightLines={[22, 24, 25, 26, 27, 29, 30, 31, 32]}
showLineNumbers={true}
code={`Transaction Breakdown:
═══════════════════════════════════════════════════════════════════════════════════
Version: 01000000
Input Count: 02
input[0]:
Previous TXID: fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f
Output Index: 00000000
ScriptSig Size: 00
ScriptSig:
Sequence: eeffffff
input[1]:
Previous TXID: ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a
Output Index: 01000000
ScriptSig Size: 00
ScriptSig:
Sequence: ffffffff
Output Count: 02
output[0]:
Amount: 202cb20600000000
ScriptPubKey Size: 19
ScriptPubKey: 76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac
output[1]:
Amount: 9093510d00000000
ScriptPubKey Size: 19
ScriptPubKey: 76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac
Locktime: 11000000`}
/>

## Test Vector from BIP-143

Let's test our code with the test vector from the <a href="https://github.com/bitcoin/bips/blob/58ffd93812ff25e87d53d1f202fbb389fdfb85bb/bip-0143.mediawiki?plain=1#L146" target="_blank">official BIP143 proposal</a>.

<div className="flex justify-center items-center w-full full-width">
<iframe
src="https://trinket.io/embed/python3/e3098585e3c8"
width="100%"
height="100%"
style={{
border: "none",
margin: 0
}}
allowFullScreen
className="rounded-md shadow-sm h-[calc(50vh)]"
></iframe>
</div>
Loading

0 comments on commit 343a199

Please sign in to comment.