bmresearch / Solnet

Solana's .NET SDK and integration library.
https://blockmountain.io/Solnet
MIT License
319 stars 128 forks source link

[Bug] I cant add Signature to server side #432

Closed Mehrdadgame closed 6 months ago

Mehrdadgame commented 1 year ago

Describe the bug I created a transaction (Tx) in my client and sent the signature to the server. Then, I created a new transaction in the server and added the client's signature to it. However, I encountered an error when trying to send the transaction to the blockchain. I created account in both Tx with tow different phase secret . Is this Correct?

` string MnemonicWords = "okay because peace inside harsh feature boss hood ranch clock text glide"; string MnemonicWordsUser2 = "faculty history noble mosquito sugar best payment memory visual boss thrive check";

////client Code //// Wallet wallet = new Wallet(MnemonicWords);

        Account fromAccount = wallet.GetAccount(10);
        Account toAccount = wallet.GetAccount(8);

        var blockHash = rpcClient.GetLatestBlockHash();
        Console.WriteLine($"BlockHash >> {blockHash.Result.Value.Blockhash}");

        TransactionBuilder txBuilder = new TransactionBuilder()
            .SetRecentBlockHash(blockHash.Result.Value.Blockhash)
            .SetFeePayer(fromAccount)
            .AddInstruction(SystemProgram.Transfer(fromAccount.PublicKey, toAccount.PublicKey, 10000000))
            .AddInstruction(MemoProgram.NewMemo(fromAccount.PublicKey, "Hello from Sol.Net :)"));

        byte[] msgBytes = txBuilder.CompileMessage();
        byte[] signature = fromAccount.Sign(msgBytes);

        byte[] tx = txBuilder.AddSignature(signature)
            .Serialize();

        /////Server Code/////
        ///
        ///
        var NessageData = Transaction.Deserialize(tx);

        Wallet walletUser2 = new(MnemonicWordsUser2);

        Account fromAccountUser2 =  walletUser2.GetAccount(10);
        Account toAccountUser2 = walletUser2.GetAccount(8);

        var blockHashUser2 = rpcClient.GetLatestBlockHash();
        Console.WriteLine($"BlockHash >> {blockHashUser2.Result.Value.Blockhash}");

        var txBuilderUser2 = new TransactionBuilder()
            .SetRecentBlockHash(blockHashUser2.Result.Value.Blockhash)
            .SetFeePayer(fromAccountUser2)
            .AddInstruction(SystemProgram.Transfer(fromAccountUser2, toAccountUser2.PublicKey, 10000000))
            .AddInstruction(MemoProgram.NewMemo(fromAccountUser2, "Hello Mehrdad :)"));

        //byte[] msgBytesUser2 = txBuilderUser2.CompileMessage();
        byte[] signatureUser2 = fromAccountUser2.Sign(txBuilderUser2.CompileMessage());
        //txBuilderUser2.AddSignature(signatureUser2);

        var txMsgDes = Transaction.Deserialize(txBuilderUser2.Serialize());
        txMsgDes.AddSignature(fromAccountUser2.PublicKey, signatureUser2);
        txMsgDes.AddSignature(NessageData.Signatures[0].PublicKey, NessageData.Signatures[0].Signature);
        var verifySignatures = txMsgDes.VerifySignatures();

        RequestResult<string> SecondSig = rpcClient.SendTransaction(txMsgDes.CompileMessage());
        Console.WriteLine($"Secend Tx Signature: {SecondSig.Result}");`

Desktop (please complete the following information):

BifrostTitan commented 1 year ago

Don't craft the transaction twice just pass the message and signature on to the server. When you sign the transaction it hashes the transaction message and since the client transaction and server transaction have different memos it would yield different signatures so the client signature would not match the new transaction the server made. They just need to share the same message when signing. Feel free to hop in the discord if you have any other questions

 //Client App
 TransactionBuilder txBuilder = new TransactionBuilder()
        .SetRecentBlockHash(blockHash.Result.Value.Blockhash)
        .SetFeePayer(fromAccount)
        .AddInstruction(SystemProgram.Transfer(fromAccount.PublicKey, toAccount.PublicKey, 10000000))
        .AddInstruction(MemoProgram.NewMemo(fromAccount.PublicKey, "Hello from Sol.Net :)"));

    byte[] msgBytes = txBuilder.CompileMessage();
    byte[] clientSignature = fromAccount.Sign(msgBytes);

//Server Side handling the signature and message
byte[] serverSignature = fromAccountUser2.Sign(msgBytes);
List<byte[]> signatures = new() { clientSignature, serverSignature };
Transaction tx = Transaction.Populate(Message.Deserialize(msgBytes), signatures);

RequestResult<string> _tx = await rpcClient.SendTransactionAsync(tx.Serialize());
Mehrdadgame commented 1 year ago

`

//Client App Wallet wallet = new(MnemonicWords); Account fromAccount = wallet.GetAccount(10); Account toAccount = wallet.GetAccount(8);

        var blockHash = rpcClient.GetLatestBlockHash();
        Console.WriteLine($"BlockHash >> {blockHash.Result.Value.Blockhash}");

        TransactionBuilder txBuilder = new TransactionBuilder()
      .SetRecentBlockHash(blockHash.Result.Value.Blockhash)
      .SetFeePayer(fromAccount)
      .AddInstruction(SystemProgram.Transfer(fromAccount.PublicKey, toAccount.PublicKey, 10000000))
      .AddInstruction(MemoProgram.NewMemo(fromAccount.PublicKey, "Hello from Sol.Net :)"));

        byte[] msgBytes = txBuilder.CompileMessage();
        byte[] clientSignature = fromAccount.Sign(msgBytes);

        //Server Side handling the signature and message

        Wallet walletUser2 = new(MnemonicWordsUser2);
        Account fromAccountUser2 = walletUser2.GetAccount(10);

        byte[] serverSignature = fromAccountUser2.Sign(msgBytes);
        List<byte[]> signatures = new() { clientSignature };
        Transaction tx = Transaction.Populate(Message.Deserialize(msgBytes), signatures);
        tx.AddSignature(fromAccountUser2, serverSignature);
        var Verify = tx.VerifySignatures();
        RequestResult<string> SecondSig = rpcClient.SendTransaction(tx.Serialize());
        Console.WriteLine($"Secend Tx Signature: {SecondSig.Result}");`

Thank you for answer my question @BifrostTitan But i have an error in Send Transaction Error message : "invalid transaction: Transaction failed to sanitize accounts offsets correctly"

BifrostTitan commented 1 year ago

Server doesn't need to sign the transaction for this type of transaction. The client doesn't need authorization or sign-off from the recipient to send SOL. In this case there is no reason to talk to the server at all. Everything can be done by the client.

For instance if its a "round trip" transaction or essentially a p2p trade where one person sends SOL and the other sends USDC then both would be required to sign like a contract.

There is a few instances where you would want to get a transaction message from a server though. For example you have a blockchain game that needs a specifically crafted transaction that interacts with a games solana program this can be crafted by the server then sent to the client for signing. Using a basic HTTP rest API you can request gameserver oriented transactions and sign/send them from the client's end.

Mehrdadgame commented 1 year ago

`// Set up the RPC client for sending and receiving transactions

        // Set up the wallet for the sender account
        var mnemonicWordsSender = "your mnemonic words for the sender account";
        var walletSender = new Wallet(mnemonicWordsSender);
        var accountSender = walletSender.GetAccount(0);

        // Set up the wallet for the receiver account
        var mnemonicWordsReceiver = "your mnemonic words for the receiver account";
        var walletReceiver = new Wallet(mnemonicWordsReceiver);
        var accountReceiver = walletReceiver.GetAccount(0);

        // Create the transfer transaction
        var tx = new TransactionBuilder()
            .SetRecentBlockHash(rpcClient.GetLatestBlockHash().Result.Value.Blockhash)
            .SetFeePayer(accountSender.PublicKey)
            .AddInstruction(SystemProgram.Transfer(accountSender.PublicKey, accountReceiver.PublicKey, 100000000));

        // Sign the transaction with the sender's account
        tx.Build(accountSender);

        // Serialize the transaction and send it to the server
        var serializedTx = tx.Serialize();
        var request = new JsonRpcRequest("sendTransaction", new List<object> { serializedTx });
        var response = rpcClient.SendBatchRequestAsync(request);

        // Wait for confirmation from the server
        var signature = response.Result.ToString();
        var confirmedTx = rpcClient.GetTransaction(signature).Result;
        while (confirmedTx == null)
        {
            confirmedTx = rpcClient.GetTransaction(signature).Result;
        }

        // Sign the transaction with the server's account
        var accountServer = walletReceiver.GetAccount(1);
        Base58Encoder base58 = new Base58Encoder();
        var signatureBytes = base58.DecodeData(signature);
        var txMessageBytes = tx.CompileMessage();
        var serverSignature = accountServer.Sign(txMessageBytes);

        // Add the server's signature to the transaction
        tx.AddSignature(serverSignature);

        // Serialize the transaction again and send it to the server
        serializedTx = tx.Serialize();

        var sendToBlockchine = rpcClient.SendTransaction(serializedTx);
        Console.WriteLine(sendToBlockchine.Result);`
Mehrdadgame commented 1 year ago

@BifrostTitan @tiago18c @nazbrok Hello, is it check to use this method? The next point is that I use Is this the right solution for what I want to do? var request = new JsonRpcRequest("sendTransaction", new List<object> { serializedTx }); I have an error, how can I solve it?

I learning block chain for Create game

BifrostTitan commented 1 year ago
    var rpcClient = ClientFactory.GetClient(Cluster.MainNet);

    // Set up the wallet for the sender account
    var mnemonicWordsSender = "your mnemonic words for the sender account";
    var walletSender = new Wallet(mnemonicWordsSender);
    var accountSender = walletSender.GetAccount(0);

    PublicKey accountReceiver = new PublicKey("receiver wallet address");

    // Create the transfer transaction
    var tx = new TransactionBuilder()
        .SetRecentBlockHash(rpcClient.GetLatestBlockHash().Result.Value.Blockhash)
        .SetFeePayer(accountSender.PublicKey)
        .AddInstruction(SystemProgram.Transfer(accountSender.PublicKey, accountReceiver, 100000000));

    // Sign the transaction with the sender's account
    tx.Build(accountSender);
    //Send the transaction to the blockchain. There is no need to sign twice and it will fail if you try
    var sentTransaction = rpcClient.SendTransaction(tx);