Bit-Wasp / bitcoin-php

Bitcoin implementation in PHP
The Unlicense
1.05k stars 419 forks source link

how to preform a basic transaction without flying numbers #861

Open oussamaelhajoui opened 3 years ago

oussamaelhajoui commented 3 years ago

Hello,

I am pretty new in the blockchain world and i came across your library. It has alot of functionality but the documentation is very brief. I am trying to build a transaction. I managed to understand how to generate a bitcoin address but not yet how to preform a basic transaction, i've seen that this question has been asked before. The OP was told to look at the many examples you guys have on transactions.

After looking at the examples i could not figure out which one was the right one to use and where all the flying data/numbers came from used as inputs or outpoint

This is the code i use, could you please help me fill in the blanks, i don't know where i should get the information to build the transaction.

<?php
require __DIR__ . "/vendor/autoload.php";

use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress;
use BitWasp\Bitcoin\Bitcoin;
use BitWasp\Bitcoin\Crypto\Random\Random;
use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory;

use BitWasp\Bitcoin\Script\Interpreter\InterpreterInterface as I;
use BitWasp\Bitcoin\Script\ScriptFactory;
use BitWasp\Bitcoin\Transaction\Factory\Signer;
use BitWasp\Bitcoin\Transaction\Factory\TxBuilder;
use BitWasp\Bitcoin\Transaction\OutPoint;
use BitWasp\Bitcoin\Transaction\TransactionOutput;
use BitWasp\Buffertools\Buffer;
use BitWasp\Bitcoin\Script\WitnessScript;
use BitWasp\Bitcoin\Transaction\Factory\SignData;

function generateBitcoinKeys()
{

    $result = array();

    $network = Bitcoin::getNetwork();
    $random = new Random();
    $privKeyFactory = new PrivateKeyFactory();
    $privateKey = $privKeyFactory->generateCompressed($random);
    $publicKey = $privateKey->getPublicKey();

    $result['privKey'] = $privateKey;
    $result['privWIF'] = $privateKey->toWif($network);
    $result['pubKey'] = $publicKey;
}

$keys = generateBitcoinKeys();

// Spend from a P2WSH P2PKH
$witnessScript = new WitnessScript(ScriptFactory::scriptPubKey()->payToPubKeyHash($keys['privKey']->getPubKeyHash()));

// UTXO
// What is the hashprevious output and where can i get this
// where should i get the nPrevinput?
$outpoint = new OutPoint(Buffer::hex('??', 32), 0);

// what is this 99999??
$txOut = new TransactionOutput(99990000, $witnessScript->getOutputScript());

// how can i get the pub keyhash from just a btc address
$dest = new PayToPubKeyHashAddress(/*Shouldnt this be the public key hash from the user that receives the btc?*/); 

// Create unsigned transaction
$tx = (new TxBuilder())
    ->spendOutPoint($outpoint)
    ->payToAddress(1.5, $dest)
    ->get();

// Sign
$signData = (new SignData())
    ->p2wsh($witnessScript);

$signer = new Signer($tx);
$input = $signer->input(0, $txOut, $signData);
$input->sign($key);
$signed = $signer->get();

// Check signatures
echo "Script validation result: " . ($input->verify(I::VERIFY_P2SH | I::VERIFY_WITNESS) ? "yay\n" : "nay\n");

echo PHP_EOL;
echo "Witness serialized transaction: " . $signed->getHex() . PHP_EOL . PHP_EOL;
echo "Base serialized transaction: " . $signed->getBaseSerialization()->getHex() . PHP_EOL;
afk11 commented 3 years ago

Yea I know what you mean.. The codebase is a bit fragmented in terminology, a PR renaming some args could help a lot. This can be helpful as at least the classes have the right names :) https://en.bitcoin.it/wiki/Protocol_documentation

Ok, first tip is to just generate a static private key and test with that. You'll want to debug your transaction and that's tricky when the keys and addresses keep changing

Use something like this in your generateBitcoinKeys function. Feel free to generate another by uncommenting the code and taking a new one!

    $random = new Random();
    $privKeyFactory = new PrivateKeyFactory();
    $privateKey = $privKeyFactory->fromWif("L2Dtj9Ua5cspVEmYvi6QnJAaNsMYReti9KiTrhfDx81NTxLB2Pr3");
    // $privateKey = $privKeyFactory->generateCompressed($random);
    // echo $privateKey->toWif().PHP_EOL;
    $publicKey = $privateKey->getPublicKey();

// What is the hashprevious output and where can i get this // where should i get the nPrevinput?

So, assuming you have an address, and someone sent funds there. The transaction that sent the funds has a transaction ID, which is a hash of the binary encoded transaction. That's $hashPrevOutput. Next, we need to identify which 'output' in that transaction we want to spend, because there's a list of outputs only one has an address that pays us. It's identified by it's position in the list - $nPrevOut.

// what is this 99999?? The amount of bitcoin transferred to us in satoshis. The line is re-creating the output in the previous transaction which paid us, because to sign, we need both it's amount and the scriptPubKey.

// how can i get the pub keyhash from just a btc address You can decode it using AddressCreator, which will take care of working out what type of address it is.

//Shouldnt this be the public key hash from the user that receives the btc? Yes, that's right.

This line actually // ->payToAddress(1.5, $dest) Bitcoin libraries normally only expose operations on satoshis, because of floating point precision problems. So normally you might receive a string "1.0123191", then convert that to satoshis, and operate on that. See the Amount class

afk11 commented 3 years ago

Once you get an example like this working, it's a good idea to try and test yourself by generating a transaction and getting it accepted by the network. Testnet isn't bad, but regtest is a good way to experiment without waiting around for hours

oussamaelhajoui commented 3 years ago

Thank the for the extensive reply @afk11 !

I will look at the protocol documentation you have addressed. And i think that a PR which renames arguments will clarify alot!

But 3 other questions are raised to me now:

  1. Is it possible with this library to show the transactions from a private/public key/btc address or do i have to use an api of some sort to get transaction information from a wallet?
  2. How can i execute the transaction? Is that not done with the verify method? Can i even execute a a transaction in this library?
  3. How can i switch to a testnet btc network or any other network?

Btw one last thing, Can i use this library to check how much i have on my own wallet or how much resises on another wallet?

afk11 commented 3 years ago

Is it possible with this library to show the transactions from a private/public key/btc address or do i have to use an api of some sort to get transaction information from a wallet?

Unfortunately not, people tend to implement that part so differently there was no point in adding it here. Any blockexplorer API will probably do, or maybe write something against Bitcoin Core.

How can i execute the transaction? Is that not done with the verify method? Can i even execute a a transaction in this library?

It's not clear what you mean, because bitcoin transactions aren't executed. Perhaps you mean verify, or broadcast? Verify means testing that the signatures are valid. Broadcasting means sending over the p2p network.

How can i switch to a testnet btc network or any other network?

Check out NetworkInterface - it has all the stuff that changes from network to network (like address prefixes, private key prefixes, etc). Look at NetworkFactory since it makes testnet/regtest/etc there. Some methods you'll see (like PrivateKeyInterface::toWIF, or Address::toAddress) take an optional NetworkInterface.

If it's null, we use the 'default' (bitcoin) If it's not null, use the provided one

You can override the default calling Bitcoin::setNetwork but usually leads to a mess - you should be directly passing the Network object.

Can i use this library to check how much i have on my own wallet or how much resises on another wallet?

See question 1

oussamaelhajoui commented 3 years ago

I can't express how thankful i am for your answers @afk11 šŸ‘!

Sorry that i did not made the question more clear regarding executing transactions. but what i meant was broadcasting a transaction, is that possible in this library and what class do i need to use? I thought verifying a transaction will broadcast it as well but this seems not the case.
Could you tell me how i can broadcast a transaction, i should use the hex of the transaction right?

where should i directly pass the network?

And i will check some blockexplorer api to extract information from wallets.

BTW can i support this project and or you in some way?

afk11 commented 3 years ago

but what i meant was broadcasting a transaction, is that possible in this library and what class do i need to use?

Got it. See what your blockexplorer API supports, most of those come with an endpoint to broadcast for instance https://github.com/Blockstream/esplora/blob/master/API.md#post-tx

where should i directly pass the network?

Any function that prints out an base58 encoded string (functions for encoded address, encoded xpub, etc)

BTW can i support this project and or you in some way?

Let me see if I can set something up! Thanks for the thought