From 0a61c75df485b2d7a76db5b334c4a2cf298ee5ad Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Thu, 23 May 2024 09:42:14 -0400 Subject: [PATCH 1/5] update segwit livebook to latest release --- livebooks/segwit_wallet/index.livemd | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/livebooks/segwit_wallet/index.livemd b/livebooks/segwit_wallet/index.livemd index c871d61..dc91bca 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 @@ -74,16 +75,15 @@ receive_private_key = master_private_key |> PrivateKey.from_derivation_path!("m/49'/1'/0'/0/0") -receive_public_key_hash = +receive_public_key = receive_private_key |> PublicKey.from_private_key() - |> PublicKeyHash.from_public_key() ``` Convert the `public key hash` to an `address`, which will be needed by faucets to receive funds ```elixir -receive_address = Address.from_script_hash(receive_public_key_hash, :testnet) +receive_address = Address.from_public_key(receive_public_key, :p2sh, :testnet) ``` ## Request some funds @@ -92,7 +92,7 @@ receive_address = Address.from_script_hash(receive_public_key_hash, :testnet) Go to `mempool.space` by directly forging the URL with the address and wait transactions being confirmed. -Ex: `https://mempool.space/testnet/address/2MzhhVCh8GvuXyzbc7wJae9WbYyCH9CUytY` +Ex: `https://mempool.space/testnet/address/2NDHb9Db9ZwPbvmjNHWmsfcGCkmt4uqEWGZ` Now leave that browser tab lying around. Mempool is supposed to issue a cashier bell sound 🔔 when it detects new incoming transactions to that address. When funds become spendable, you'll also hear somewhat of a magic wand sound. 🪄 @@ -126,9 +126,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 = "b2ac5c444ba0c58b39ea2a7464600a4e2bb148efb82930a44ee7422a5afea91b" vout = 0 -script_pub_key = "76a914b3ceddcbe5fa8aea1f672a4e543e57a49da179e388ac" +script_pub_key = "a914dbd5018f8b8388746e806cdedabd0b3a26efe70d87" ``` ## Extract parameters @@ -140,9 +140,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 = 39321 +destination_amount = 1000 +change_amount = 100 fee = original_amount - destination_amount - change_amount ``` @@ -153,7 +153,7 @@ destination_public_key_hash = master_private_key |> PrivateKey.from_derivation_path!("m/44'/1'/0'/0/1") |> PublicKey.from_private_key() - |> PublicKeyHash.from_public_key() + |> PublicKey.hash() ``` ```elixir @@ -161,7 +161,7 @@ change_public_key_hash = master_private_key |> PrivateKey.from_derivation_path!("m/44'/1'/0'/1/1") |> PublicKey.from_private_key() - |> PublicKeyHash.from_public_key() + |> PublicKey.hash() ``` ## Create a transaction @@ -170,20 +170,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 From 2d24ce6a5eb7c8c7e50df3dc6c17084acbe55431 Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Thu, 23 May 2024 09:53:38 -0400 Subject: [PATCH 2/5] update --- livebooks/segwit_wallet/index.livemd | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/livebooks/segwit_wallet/index.livemd b/livebooks/segwit_wallet/index.livemd index dc91bca..fb4767d 100644 --- a/livebooks/segwit_wallet/index.livemd +++ b/livebooks/segwit_wallet/index.livemd @@ -78,12 +78,13 @@ receive_private_key = receive_public_key = receive_private_key |> PublicKey.from_private_key() + |> PublicKey.hash() ``` Convert the `public key hash` to an `address`, which will be needed by faucets to receive funds ```elixir -receive_address = Address.from_public_key(receive_public_key, :p2sh, :testnet) +receive_address = Address.from_script_hash(receive_public_key, :testnet) ``` ## Request some funds @@ -92,7 +93,7 @@ receive_address = Address.from_public_key(receive_public_key, :p2sh, :testnet) Go to `mempool.space` by directly forging the URL with the address and wait transactions being confirmed. -Ex: `https://mempool.space/testnet/address/2NDHb9Db9ZwPbvmjNHWmsfcGCkmt4uqEWGZ` +Ex: `https://mempool.space/testnet/address/2MzhhVCh8GvuXyzbc7wJae9WbYyCH9CUytY` Now leave that browser tab lying around. Mempool is supposed to issue a cashier bell sound 🔔 when it detects new incoming transactions to that address. When funds become spendable, you'll also hear somewhat of a magic wand sound. 🪄 @@ -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 = "b2ac5c444ba0c58b39ea2a7464600a4e2bb148efb82930a44ee7422a5afea91b" +transaction_id = "39efb7042d02341a0f6ecced6de32abedf4d78776e6a42d867550b2cebefa3fd" vout = 0 -script_pub_key = "a914dbd5018f8b8388746e806cdedabd0b3a26efe70d87" +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 = 39321 +original_amount = 1_638_523 destination_amount = 1000 -change_amount = 100 +change_amount = 1_228_523 fee = original_amount - destination_amount - change_amount ``` From fd0c588fc32ecf4c4692e1d94f85a5b51a1aa542 Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Thu, 23 May 2024 09:55:31 -0400 Subject: [PATCH 3/5] fix typo --- livebooks/segwit_wallet/index.livemd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/livebooks/segwit_wallet/index.livemd b/livebooks/segwit_wallet/index.livemd index fb4767d..14d9ddb 100644 --- a/livebooks/segwit_wallet/index.livemd +++ b/livebooks/segwit_wallet/index.livemd @@ -75,7 +75,7 @@ receive_private_key = master_private_key |> PrivateKey.from_derivation_path!("m/49'/1'/0'/0/0") -receive_public_key = +receive_public_key_hash = receive_private_key |> PublicKey.from_private_key() |> PublicKey.hash() @@ -84,7 +84,7 @@ receive_public_key = Convert the `public key hash` to an `address`, which will be needed by faucets to receive funds ```elixir -receive_address = Address.from_script_hash(receive_public_key, :testnet) +receive_address = Address.from_script_hash(receive_public_key_hash, :testnet) ``` ## Request some funds From 9a603af90c871d57f066c697d8da36e4f3ab973e Mon Sep 17 00:00:00 2001 From: Wen Chen Date: Thu, 23 May 2024 11:53:53 -0400 Subject: [PATCH 4/5] change destination key derivation path --- livebooks/segwit_wallet/index.livemd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/livebooks/segwit_wallet/index.livemd b/livebooks/segwit_wallet/index.livemd index 14d9ddb..ab5d944 100644 --- a/livebooks/segwit_wallet/index.livemd +++ b/livebooks/segwit_wallet/index.livemd @@ -152,7 +152,7 @@ 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() |> PublicKey.hash() ``` @@ -160,7 +160,7 @@ destination_public_key_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() |> PublicKey.hash() ``` From 6dadd74abf6177e088b1d20da791e723b72a6d9e Mon Sep 17 00:00:00 2001 From: Marc Lacoursiere Date: Fri, 24 May 2024 09:14:43 -0400 Subject: [PATCH 5/5] reproduced #4 in a test, which is failing --- test/transaction/spec_test.exs | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) 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