diff --git a/livebooks/segwit_wallet/index.livemd b/livebooks/segwit_wallet/index.livemd index c871d61..ab5d944 100644 --- a/livebooks/segwit_wallet/index.livemd +++ b/livebooks/segwit_wallet/index.livemd @@ -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 @@ -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 @@ -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 @@ -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 ``` @@ -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 @@ -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 diff --git a/test/transaction/spec_test.exs b/test/transaction/spec_test.exs index 0b205cb..d18cf25 100644 --- a/test/transaction/spec_test.exs +++ b/test/transaction/spec_test.exs @@ -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