Firmar una transacción sin procesar en Go

Necesito firmar una transacción fuera de línea en golang. Tengo lo siguiente, que es una ligera modificación de esta respuesta a una pregunta similar:

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
}

Lo que quiero es la carga útil de la transacción sin procesar, pero lo que obtengo es incorrecto. Creo que quiero parsed_data.Data, pero no estoy seguro. Lo que estoy buscando es un Go análogo a la siguiente función JS:

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')

¿Dónde serializedTxestá una cadena (la carga útil que quiero).

Soy un novato de Golang, así que sospecho que solo estoy haciendo un mal uso de geth, pero cualquier ayuda sería muy apreciada. He dedicado bastante tiempo a este problema.

Respuestas (3)

Puede usar dos métodos para obtener el RLP de transacción sin procesar

  1. Obtenga el String()de la transacción firmada. Puede invocarlo directamente o dejar que la fmtbiblioteca lo haga por usted:

    my_string_var = signed_tx.String()
    

    o

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

El problema es que necesitará analizar la salida, que será algo así como

    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 el método GetRlp()de la estructura Transactions(plural, con una s) en su lugar

    Creamos una variable tsy la rellenamos con esta transacción firmada

    ts := types.Transactions{signed_tx}
    

    Entonces simplemente invocamos

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

    Que contendrá la cadena de transacción sin procesar deseada

    f88b80881bc16d674ec80000830186a0941737b4e8e4101334b1b1965d3d739c41cc54f09680a4deaa59df000000000000000000000000cbfdfb9fb838b9090a7fe1976ed98017632b44f178a00a484a59015d08e736f59edf07ffb32f73151fddec52885b1f29cbcfd7aac2039f842a3a63c2fb1771cd0495b93a4db94692d4733baa9e96c559ddc4ff600422
    
Esta respuesta es correcta, pero solo quiero agregar un aparte no obvio. Al formar su transacción, debe convertir su datacampo en uno de los tipos geth: common.FromHex(string(data)), donde dataestá my []byte.
Le di un más uno para esta Respuesta, pero ¿puedo preguntar por qué necesitaría la transacción sin procesar?
@Cyberience Es más seguro firmar a nivel de aplicación que a nivel de nodo, de esa manera no necesita almacenar las claves privadas en el nodo y no necesita desbloquear su cuenta. Algunos ataques usan el período de desbloqueo para intentar robar éter o apuntan a servidores que contienen nodos para robar claves privadas.

Crear una transacción sin procesar:

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
}

Transmitiendo la transacción sin procesar:

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
}

"Creación de transacciones sin conexión/sin procesar con Go-Ethereum" @akshay_111meher https://medium.com/@akshay_111meher/creating-offline-raw-transactions-with-go-ethereum-8d6cc8174c5d