6

I need to sign an offline transaction in golang. I have the following, which is a slight modification of this answer to a similar question:

import "github.com/ethereum/go-ethereum/core/types"
import "github.com/ethereum/go-ethereum/common"
import "github.com/ethereum/go-ethereum/crypto"

type GethTxn struct {
  To   string     `json:"to"`
  From string     `json:"from"`
  Gas string      `json:"gas"`
  GasPrice string `json:"gasPrice"`
  Value string    `json:"value"`
  Data string     `json:"input"`
}

func SignTxn(from string, _to string, data []byte, nonce uint64, value int64, gas *big.Int, gasPrice *big.Int, privkey *ecdsa.PrivateKey) (*GethTxn, error) {

  var parsed_tx = new(GethTxn)
  var amount = big.NewInt(value)
  var bytesto [20]byte
  _bytesto, _ := hex.DecodeString(_to[2:])
  copy(bytesto[:], _bytesto)
  to := common.Address([20]byte(bytesto))

  signer := types.NewEIP155Signer(nil)
  tx := types.NewTransaction(nonce, to, amount, gas, gasPrice, data)
  signature, _ := crypto.Sign(tx.SigHash(signer).Bytes(), privkey)
  signed_tx, _ := tx.WithSignature(signer, signature)

  json_tx, _ := signed_tx.MarshalJSON()
  _ = json.Unmarshal(json_tx, parsed_tx)
  parsed_tx.From = from
  fmt.Println("data", parsed_tx.Data)
  return parsed_tx, nil
}

What I want is the raw transaction payload, but what I get is incorrect. I believe I want parsed_data.Data, but I'm not sure. What I'm looking for is a Go analog to the following JS function:

var Tx = require('ethereumjs-tx');

var privateKey = new Buffer(pkey, 'hex')
var tx = new Tx(txn);
tx.sign(privateKey);
var serializedTx = tx.serialize().toString('hex')

Where serializedTx is a string (the payload I want).

I'm a Golang noob so I suspect I'm just misusing geth, but any help would be very much appreciated. I have spent quite a long time on this problem.

3 Answers 3

6

You can use two methods to obtain the raw transation RLP

  1. Get the String() of the signed transaction. You can invoke it directly, or let the fmt library do it for you:

    my_string_var = signed_tx.String()
    

    or

    my_string_var = fmt.Sprintf("%v", signed_tx)
    

The problem is that you will need to parse the output which will be something like

    TX(57c9544749f223acdff0be77876a4a45125cef360530caa97a92c359e4d7a6ce)
    Contract: false
    From:     1600da1bcbef5599e09532f230ced99db0619b95
    To:       1737b4e8e4101334b1b1965d3d739c41cc54f096
    Nonce:    0
    GasPrice: 0x1bc16d674ec80000
    GasLimit  0x186a0
    Value:    0x0
    Data:     0xdeaa59df000000000000000000000000cbfdfb9fb838b9090a7fe1976ed98017632b44f1
    V:        0x78
    R:        0xa484a59015d08e736f59edf07ffb32f73151fddec52885b1f29cbcfd7aac203
    S:        0x842a3a63c2fb1771cd0495b93a4db94692d4733baa9e96c559ddc4ff600422
    Hex:      f88b80881bc16d674ec80000830186a0941737b4e8e4101334b1b1965d3d739c41cc54f09680a4deaa59df000000000000000000000000cbfdfb9fb838b9090a7fe1976ed98017632b44f178a00a484a59015d08e736f59edf07ffb32f73151fddec52885b1f29cbcfd7aac2039f842a3a63c2fb1771cd0495b93a4db94692d4733baa9e96c559ddc4ff600422
  1. Use the method GetRlp() of the struct Transactions (plural, with an s) instead

    We create a variable ts, and populate it with this signed transaction

    ts := types.Transactions{signed_tx}
    

    Then we just invoke

    my_string_var = fmt.Sprintf("%x", ts.getRlp(0))
    

    Which will contain the desired raw transaction string

    f88b80881bc16d674ec80000830186a0941737b4e8e4101334b1b1965d3d739c41cc54f09680a4deaa59df000000000000000000000000cbfdfb9fb838b9090a7fe1976ed98017632b44f178a00a484a59015d08e736f59edf07ffb32f73151fddec52885b1f29cbcfd7aac2039f842a3a63c2fb1771cd0495b93a4db94692d4733baa9e96c559ddc4ff600422
    
3
  • 1
    This answer is correct, but I just want to add a non-obvious aside. When forming your transaction, you need to cast your data field to one of the geth types: common.FromHex(string(data)), where my data is []byte. Commented May 23, 2017 at 23:26
  • I gave a plus one for this Answer, but may I ask why you would need the raw transaction?
    – Cyberience
    Commented May 29, 2018 at 11:32
  • 1
    @Cyberience Its safer to sign at app level rather than at node level, that way you don't need to store the private keys on the node, and you don't need to unlock your account. Some attacks use the unlock period to try steal ether, or target servers holding nodes to steal private keys Commented Feb 6, 2019 at 16:08
2

Create a raw transaction:

package main

import (
    "context"
    "crypto/ecdsa"
    "encoding/hex"
    "fmt"
    "log"
    "math/big"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
    if err != nil {
        log.Fatal(err)
    }

    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        log.Fatal("error casting public key to ECDSA")
    }

    fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Fatal(err)
    }

    value := big.NewInt(1000000000000000000) // in wei (1 eth)
    gasLimit := uint64(21000)                // in units
    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
    var data []byte
    tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)

    chainID, err := client.NetworkID(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    if err != nil {
        log.Fatal(err)
    }

    ts := types.Transactions{signedTx}
    rawTx := hex.EncodeToString(ts.GetRlp(0))

    fmt.Printf(rawTx) // f86...772
}

Broadcasting the raw transaction:

package main

import (
    "context"
    "encoding/hex"
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/ethereum/go-ethereum/rlp"
)

func main() {
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772"

    var tx *types.Transaction

    rawTxBytes, err := hex.DecodeString(rawTx)
    rlp.DecodeBytes(rawTxBytes, &tx)

    err = client.SendTransaction(context.Background(), tx)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f
}
0

“Creating offline/raw transactions with Go-Ethereum” @akshay_111meher https://medium.com/@akshay_111meher/creating-offline-raw-transactions-with-go-ethereum-8d6cc8174c5d

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.