Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions livebooks/segwit_wallet/index.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

```elixir
Mix.install([
{:bitcoinlib, "~> 0.2.4"}
{:bitcoinlib, "~> 0.4.7"}
])

alias BitcoinLib.Key.{PrivateKey, PublicKey, PublicKeyHash, Address}
alias BitcoinLib.Address
alias BitcoinLib.Key.{PrivateKey, PublicKey}
alias BitcoinLib.Key.HD.SeedPhrase
alias BitcoinLib.Transaction
alias BitcoinLib.Script
Expand Down Expand Up @@ -77,7 +78,7 @@ receive_private_key =
receive_public_key_hash =
receive_private_key
|> PublicKey.from_private_key()
|> PublicKeyHash.from_public_key()
|> PublicKey.hash()
```

Convert the `public key hash` to an `address`, which will be needed by faucets to receive funds
Expand Down Expand Up @@ -126,9 +127,9 @@ Here lies the `script pub key`, also known as the `locking script`. This is what
The following code is defining variables containing what we just talked about.

```elixir
transaction_id = "420c552d7821da5da61be91dffe984537f460dc668f0766f6c2e1c7a10287610"
transaction_id = "39efb7042d02341a0f6ecced6de32abedf4d78776e6a42d867550b2cebefa3fd"
vout = 0
script_pub_key = "76a914b3ceddcbe5fa8aea1f672a4e543e57a49da179e388ac"
script_pub_key = "a91451caa671181d8819ccff9b81ffeb8fdafd95f91f87"
```

## Extract parameters
Expand All @@ -140,9 +141,9 @@ Spending from a UTXO invalidates it, creating new ones in the process.
The current UTXO contains `10000` sats. We intend to send `5000` sats to a new address. This leaves `5000` sats remaining. They must end up in two other destinations: `the change` and `the transaction fee`. The change is another UTXO and we'll keep `4000` sats. The fee is what's leftover and the miner who will include the transaction in a new block will keep it. It is thus implied from the three previous amounts:

```elixir
original_amount = 10000
destination_amount = 5000
change_amount = 4000
original_amount = 1_638_523
destination_amount = 1000
change_amount = 1_228_523
fee = original_amount - destination_amount - change_amount
```

Expand All @@ -151,17 +152,17 @@ Let's create two public key hashes: a `destination` and a `change`.
```elixir
destination_public_key_hash =
master_private_key
|> PrivateKey.from_derivation_path!("m/44'/1'/0'/0/1")
|> PrivateKey.from_derivation_path!("m/49'/1'/0'/0/1")
|> PublicKey.from_private_key()
|> PublicKeyHash.from_public_key()
|> PublicKey.hash()
```

```elixir
change_public_key_hash =
master_private_key
|> PrivateKey.from_derivation_path!("m/44'/1'/0'/1/1")
|> PrivateKey.from_derivation_path!("m/49'/1'/0'/1/1")
|> PublicKey.from_private_key()
|> PublicKeyHash.from_public_key()
|> PublicKey.hash()
```

## Create a transaction
Expand All @@ -170,20 +171,20 @@ Now we've got everything we need to create a new transaction and sign it. Let's

```elixir
%Transaction.Spec{}
|> Transaction.Spec.add_input(
|> Transaction.Spec.add_input!(
txid: transaction_id,
vout: vout,
redeem_script: script_pub_key
)
|> Transaction.Spec.add_output(
Script.Types.P2pkh.create(destination_public_key_hash),
Script.Types.P2sh.create(destination_public_key_hash),
destination_amount
)
|> Transaction.Spec.add_output(
Script.Types.P2pkh.create(change_public_key_hash),
Script.Types.P2sh.create(change_public_key_hash),
change_amount
)
|> Transaction.Spec.sign_and_encode(receive_private_key)
|> Transaction.Spec.sign_and_encode([receive_private_key])
```

## Move funds
Expand Down
63 changes: 63 additions & 0 deletions test/transaction/spec_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
defmodule BitcoinLib.Transaction.SpecTest do
use ExUnit.Case, async: true

alias BitcoinLib.{Script, Transaction}
alias BitcoinLib.Key.{PrivateKey, PublicKey}
alias BitcoinLib.Key.HD.SeedPhrase

doctest BitcoinLib.Transaction.Spec

test "create a segwit transaction and decode it" do
{:ok, encoded_transaction} = create_signed_transaction()
{:ok, transaction, ""} = decode_transaction(encoded_transaction)

assert transaction.segwit?
end

defp create_signed_transaction do
{:ok, seed_phrase} =
SeedPhrase.from_dice_rolls("12345612345612345612345612345612345612345612345612")

master_private_key = PrivateKey.from_seed_phrase(seed_phrase)

receive_private_key =
master_private_key
|> PrivateKey.from_derivation_path!("m/49'/1'/0'/0/0")

transaction_id = "39efb7042d02341a0f6ecced6de32abedf4d78776e6a42d867550b2cebefa3fd"
vout = 0
script_pub_key = "a91451caa671181d8819ccff9b81ffeb8fdafd95f91f87"

destination_amount = 1000
change_amount = 1_228_523

destination_public_key_hash =
master_private_key
|> PrivateKey.from_derivation_path!("m/49'/1'/0'/0/1")
|> PublicKey.from_private_key()
|> PublicKey.hash()

change_public_key_hash =
master_private_key
|> PrivateKey.from_derivation_path!("m/49'/1'/0'/1/1")
|> PublicKey.from_private_key()
|> PublicKey.hash()

%Transaction.Spec{}
|> Transaction.Spec.add_input!(
txid: transaction_id,
vout: vout,
redeem_script: script_pub_key
)
|> Transaction.Spec.add_output(
Script.Types.P2sh.create(destination_public_key_hash),
destination_amount
)
|> Transaction.Spec.add_output(
Script.Types.P2sh.create(change_public_key_hash),
change_amount
)
|> Transaction.Spec.sign_and_encode([receive_private_key])
end

defp decode_transaction(encoded_transaction) do
encoded_transaction
|> Binary.from_hex()
|> Transaction.decode()
end
end