From 49fc1c6c1a3f44a959872a6ac8314c6055d88e34 Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@monax.io>
Date: Wed, 6 Jun 2018 23:29:54 +0100
Subject: [PATCH] Introduce transaction envelope model, drop go-wire

Signed-off-by: Silas Davis <silas@monax.io>
---
 crypto/crypto.go                  |  20 +--
 execution/events/events.go        |   2 +-
 execution/execution.go            |   1 +
 genesis/spec/genesis_spec.go      | 107 -------------
 genesis/spec/template_account.go  | 116 ++++++++++++++
 rpc/result.go                     |   4 +-
 rpc/result_test.go                |   2 +-
 rpc/service.go                    |   2 +-
 rpc/tm/integration/client_test.go |   2 +-
 rpc/tm/methods.go                 |   2 +-
 txs/bond_tx.go                    |  49 +-----
 txs/call_tx.go                    |  30 +---
 txs/gov_tx.go                     |  91 -----------
 txs/governance_tx.go.txt          |  77 +++++++++
 txs/name_tx.go                    |  31 +---
 txs/permission_tx.go              |  30 +---
 txs/rebond_tx.go                  |  56 -------
 txs/send_tx.go                    |  46 +-----
 txs/tx.go                         | 253 +++++++++++++++++++++---------
 txs/tx_input.go                   |  16 +-
 txs/tx_test.go                    |  57 +++----
 txs/unbond_tx.go                  |  25 +--
 22 files changed, 435 insertions(+), 584 deletions(-)
 delete mode 100644 txs/gov_tx.go
 create mode 100644 txs/governance_tx.go.txt
 delete mode 100644 txs/rebond_tx.go

diff --git a/crypto/crypto.go b/crypto/crypto.go
index fc8ebf9e..80372257 100644
--- a/crypto/crypto.go
+++ b/crypto/crypto.go
@@ -1,9 +1,9 @@
 package crypto
 
 import (
-	"bytes"
 	"fmt"
-	"io"
+
+	"github.com/pkg/errors"
 )
 
 type CurveType int8
@@ -57,15 +57,15 @@ type Signer interface {
 // Signable is an interface for all signable things.
 // It typically removes signatures before serializing.
 type Signable interface {
-	WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
+	SignBytes(chainID string) ([]byte, error)
 }
 
-// SignBytes is a convenience method for getting the bytes to sign of a Signable.
-func SignBytes(chainID string, o Signable) []byte {
-	buf, n, err := new(bytes.Buffer), new(int), new(error)
-	o.WriteSignBytes(chainID, buf, n, err)
-	if *err != nil {
-		panic(fmt.Sprintf("could not write sign bytes for a signable: %s", *err))
+// SignBytes is a convenience method for getting the bytes to sign of a Signable. Will panic if there is an error
+// generating SignBytes.
+func SignBytes(chainID string, signable Signable) []byte {
+	bs, err := signable.SignBytes(chainID)
+	if err != nil {
+		panic(errors.Wrap(err, "could not write sign bytes for a signable"))
 	}
-	return buf.Bytes()
+	return bs
 }
diff --git a/execution/events/events.go b/execution/events/events.go
index b9aa92df..d983df7a 100644
--- a/execution/events/events.go
+++ b/execution/events/events.go
@@ -37,7 +37,7 @@ var callTxQuery = event.NewQueryBuilder().
 	AndEquals(event.TxTypeKey, reflect.TypeOf(&txs.CallTx{}).String())
 
 type eventDataTx struct {
-	Tx        txs.Wrapper
+	Tx        txs.Body
 	Return    []byte
 	Exception string
 }
diff --git a/execution/execution.go b/execution/execution.go
index 3e7068f2..3c67293d 100644
--- a/execution/execution.go
+++ b/execution/execution.go
@@ -168,6 +168,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) {
 				debug.Stack())
 		}
 	}()
+	body = txs.ChainWrap(exe.chainID, tx)
 
 	txHash := tx.Hash(exe.chainID)
 	logger := exe.logger.WithScope("executor.Execute(tx txs.Tx)").With(
diff --git a/genesis/spec/genesis_spec.go b/genesis/spec/genesis_spec.go
index 48d8443a..b1f6e920 100644
--- a/genesis/spec/genesis_spec.go
+++ b/genesis/spec/genesis_spec.go
@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/hyperledger/burrow/crypto"
 	"github.com/hyperledger/burrow/genesis"
 	"github.com/hyperledger/burrow/keys"
 	"github.com/hyperledger/burrow/permission"
@@ -31,112 +30,6 @@ type GenesisSpec struct {
 	Accounts          []TemplateAccount `json:",omitempty" toml:",omitempty"`
 }
 
-type TemplateAccount struct {
-	// Template accounts sharing a name will be merged when merging genesis specs
-	Name string `json:",omitempty" toml:",omitempty"`
-	// Address  is convenient to have in file for reference, but otherwise ignored since derived from PublicKey
-	Address      *crypto.Address   `json:",omitempty" toml:",omitempty"`
-	PublicKey    *crypto.PublicKey `json:",omitempty" toml:",omitempty"`
-	Amount       *uint64           `json:",omitempty" toml:",omitempty"`
-	AmountBonded *uint64           `json:",omitempty" toml:",omitempty"`
-	Permissions  []string          `json:",omitempty" toml:",omitempty"`
-	Roles        []string          `json:",omitempty" toml:",omitempty"`
-}
-
-func (ta TemplateAccount) Validator(keyClient keys.KeyClient, index int) (*genesis.Validator, error) {
-	var err error
-	gv := new(genesis.Validator)
-	gv.PublicKey, gv.Address, err = ta.RealisePubKeyAndAddress(keyClient)
-	if err != nil {
-		return nil, err
-	}
-	if ta.AmountBonded == nil {
-		gv.Amount = DefaultAmountBonded
-	} else {
-		gv.Amount = *ta.AmountBonded
-	}
-	if ta.Name == "" {
-		gv.Name = accountNameFromIndex(index)
-	} else {
-		gv.Name = ta.Name
-	}
-
-	gv.UnbondTo = []genesis.BasicAccount{{
-		Address:   gv.Address,
-		PublicKey: gv.PublicKey,
-		Amount:    gv.Amount,
-	}}
-	return gv, nil
-}
-
-func (ta TemplateAccount) AccountPermissions() (ptypes.AccountPermissions, error) {
-	basePerms, err := permission.BasePermissionsFromStringList(ta.Permissions)
-	if err != nil {
-		return permission.ZeroAccountPermissions, nil
-	}
-	return ptypes.AccountPermissions{
-		Base:  basePerms,
-		Roles: ta.Roles,
-	}, nil
-}
-
-func (ta TemplateAccount) Account(keyClient keys.KeyClient, index int) (*genesis.Account, error) {
-	var err error
-	ga := new(genesis.Account)
-	ga.PublicKey, ga.Address, err = ta.RealisePubKeyAndAddress(keyClient)
-	if err != nil {
-		return nil, err
-	}
-	if ta.Amount == nil {
-		ga.Amount = DefaultAmount
-	} else {
-		ga.Amount = *ta.Amount
-	}
-	if ta.Name == "" {
-		ga.Name = accountNameFromIndex(index)
-	} else {
-		ga.Name = ta.Name
-	}
-	if ta.Permissions == nil {
-		ga.Permissions = permission.DefaultAccountPermissions.Clone()
-	} else {
-		ga.Permissions, err = ta.AccountPermissions()
-		if err != nil {
-			return nil, err
-		}
-	}
-	return ga, nil
-}
-
-// Adds a public key and address to the template. If PublicKey will try to fetch it by Address.
-// If both PublicKey and Address are not set will use the keyClient to generate a new keypair
-func (ta TemplateAccount) RealisePubKeyAndAddress(keyClient keys.KeyClient) (pubKey crypto.PublicKey, address crypto.Address, err error) {
-	if ta.PublicKey == nil {
-		if ta.Address == nil {
-			// If neither PublicKey or Address set then generate a new one
-			address, err = keyClient.Generate(ta.Name, crypto.CurveTypeEd25519)
-			if err != nil {
-				return
-			}
-		} else {
-			address = *ta.Address
-		}
-		// Get the (possibly existing) key
-		pubKey, err = keyClient.PublicKey(address)
-		if err != nil {
-			return
-		}
-	} else {
-		address = (*ta.PublicKey).Address()
-		if ta.Address != nil && *ta.Address != address {
-			err = fmt.Errorf("template address %s does not match public key derived address %s", ta.Address,
-				ta.PublicKey)
-		}
-		pubKey = *ta.PublicKey
-	}
-	return
-}
-
 func (gs *GenesisSpec) RealiseKeys(keyClient keys.KeyClient) error {
 	for _, templateAccount := range gs.Accounts {
 		_, _, err := templateAccount.RealisePubKeyAndAddress(keyClient)
diff --git a/genesis/spec/template_account.go b/genesis/spec/template_account.go
index f09cd57a..51e43049 100644
--- a/genesis/spec/template_account.go
+++ b/genesis/spec/template_account.go
@@ -1 +1,117 @@
 package spec
+
+import (
+	"fmt"
+
+	"github.com/hyperledger/burrow/crypto"
+	"github.com/hyperledger/burrow/genesis"
+	"github.com/hyperledger/burrow/keys"
+	"github.com/hyperledger/burrow/permission"
+	ptypes "github.com/hyperledger/burrow/permission/types"
+)
+
+type TemplateAccount struct {
+	// Template accounts sharing a name will be merged when merging genesis specs
+	Name string `json:",omitempty" toml:",omitempty"`
+	// Address  is convenient to have in file for reference, but otherwise ignored since derived from PublicKey
+	Address      *crypto.Address   `json:",omitempty" toml:",omitempty"`
+	PublicKey    *crypto.PublicKey `json:",omitempty" toml:",omitempty"`
+	Amount       *uint64           `json:",omitempty" toml:",omitempty"`
+	AmountBonded *uint64           `json:",omitempty" toml:",omitempty"`
+	Permissions  []string          `json:",omitempty" toml:",omitempty"`
+	Roles        []string          `json:",omitempty" toml:",omitempty"`
+}
+
+func (ta TemplateAccount) Validator(keyClient keys.KeyClient, index int) (*genesis.Validator, error) {
+	var err error
+	gv := new(genesis.Validator)
+	gv.PublicKey, gv.Address, err = ta.RealisePubKeyAndAddress(keyClient)
+	if err != nil {
+		return nil, err
+	}
+	if ta.AmountBonded == nil {
+		gv.Amount = DefaultAmountBonded
+	} else {
+		gv.Amount = *ta.AmountBonded
+	}
+	if ta.Name == "" {
+		gv.Name = accountNameFromIndex(index)
+	} else {
+		gv.Name = ta.Name
+	}
+
+	gv.UnbondTo = []genesis.BasicAccount{{
+		Address:   gv.Address,
+		PublicKey: gv.PublicKey,
+		Amount:    gv.Amount,
+	}}
+	return gv, nil
+}
+
+func (ta TemplateAccount) AccountPermissions() (ptypes.AccountPermissions, error) {
+	basePerms, err := permission.BasePermissionsFromStringList(ta.Permissions)
+	if err != nil {
+		return permission.ZeroAccountPermissions, nil
+	}
+	return ptypes.AccountPermissions{
+		Base:  basePerms,
+		Roles: ta.Roles,
+	}, nil
+}
+
+func (ta TemplateAccount) Account(keyClient keys.KeyClient, index int) (*genesis.Account, error) {
+	var err error
+	ga := new(genesis.Account)
+	ga.PublicKey, ga.Address, err = ta.RealisePubKeyAndAddress(keyClient)
+	if err != nil {
+		return nil, err
+	}
+	if ta.Amount == nil {
+		ga.Amount = DefaultAmount
+	} else {
+		ga.Amount = *ta.Amount
+	}
+	if ta.Name == "" {
+		ga.Name = accountNameFromIndex(index)
+	} else {
+		ga.Name = ta.Name
+	}
+	if ta.Permissions == nil {
+		ga.Permissions = permission.DefaultAccountPermissions.Clone()
+	} else {
+		ga.Permissions, err = ta.AccountPermissions()
+		if err != nil {
+			return nil, err
+		}
+	}
+	return ga, nil
+}
+
+// Adds a public key and address to the template. If PublicKey will try to fetch it by Address.
+// If both PublicKey and Address are not set will use the keyClient to generate a new keypair
+func (ta TemplateAccount) RealisePubKeyAndAddress(keyClient keys.KeyClient) (pubKey crypto.PublicKey, address crypto.Address, err error) {
+	if ta.PublicKey == nil {
+		if ta.Address == nil {
+			// If neither PublicKey or Address set then generate a new one
+			address, err = keyClient.Generate(ta.Name, crypto.CurveTypeEd25519)
+			if err != nil {
+				return
+			}
+		} else {
+			address = *ta.Address
+		}
+		// Get the (possibly existing) key
+		pubKey, err = keyClient.PublicKey(address)
+		if err != nil {
+			return
+		}
+	} else {
+		address = (*ta.PublicKey).Address()
+		if ta.Address != nil && *ta.Address != address {
+			err = fmt.Errorf("template address %s does not match public key derived address %s", ta.Address,
+				ta.PublicKey)
+		}
+		pubKey = *ta.PublicKey
+	}
+	return
+}
diff --git a/rpc/result.go b/rpc/result.go
index 63e05cf1..4879381a 100644
--- a/rpc/result.go
+++ b/rpc/result.go
@@ -201,7 +201,7 @@ func (rbt ResultBroadcastTx) UnmarshalJSON(data []byte) (err error) {
 
 type ResultListUnconfirmedTxs struct {
 	NumTxs int
-	Txs    []txs.Wrapper
+	Txs    []txs.Body
 }
 
 type ResultGetName struct {
@@ -213,7 +213,7 @@ type ResultGenesis struct {
 }
 
 type ResultSignTx struct {
-	Tx txs.Wrapper
+	Tx txs.Body
 }
 
 type TendermintEvent struct {
diff --git a/rpc/result_test.go b/rpc/result_test.go
index 2aa750ba..90312ddf 100644
--- a/rpc/result_test.go
+++ b/rpc/result_test.go
@@ -53,7 +53,7 @@ func TestResultBroadcastTx(t *testing.T) {
 func TestListUnconfirmedTxs(t *testing.T) {
 	res := &ResultListUnconfirmedTxs{
 		NumTxs: 3,
-		Txs: []txs.Wrapper{
+		Txs: []txs.Body{
 			txs.Wrap(&txs.CallTx{
 				Address: &crypto.Address{1},
 			}),
diff --git a/rpc/service.go b/rpc/service.go
index 0a360749..f50a874c 100644
--- a/rpc/service.go
+++ b/rpc/service.go
@@ -108,7 +108,7 @@ func (s *Service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, err
 	if err != nil {
 		return nil, err
 	}
-	wrappedTxs := make([]txs.Wrapper, len(transactions))
+	wrappedTxs := make([]txs.Body, len(transactions))
 	for i, tx := range transactions {
 		wrappedTxs[i] = txs.Wrap(tx)
 	}
diff --git a/rpc/tm/integration/client_test.go b/rpc/tm/integration/client_test.go
index 87bb496a..12c22e4e 100644
--- a/rpc/tm/integration/client_test.go
+++ b/rpc/tm/integration/client_test.go
@@ -323,7 +323,7 @@ func TestListUnconfirmedTxs(t *testing.T) {
 		code := []byte{0x60, 0x5, 0x60, 0x1, 0x55}
 		// Call with nil address will create a contract
 		tx := txs.Wrap(makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee))
-		txChan := make(chan []txs.Wrapper)
+		txChan := make(chan []txs.Body)
 
 		// We want to catch the Tx in mempool before it gets reaped by tendermint
 		// consensus. We should be able to do this almost always if we broadcast our
diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go
index 987d4893..103109be 100644
--- a/rpc/tm/methods.go
+++ b/rpc/tm/methods.go
@@ -63,7 +63,7 @@ func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*server.
 	logger = logger.WithScope("GetRoutes")
 	return map[string]*server.RPCFunc{
 		// Transact
-		BroadcastTx: server.NewRPCFunc(func(tx txs.Wrapper) (*rpc.ResultBroadcastTx, error) {
+		BroadcastTx: server.NewRPCFunc(func(tx txs.Body) (*rpc.ResultBroadcastTx, error) {
 			receipt, err := service.Transactor().BroadcastTx(tx.Unwrap())
 			if err != nil {
 				return nil, err
diff --git a/txs/bond_tx.go b/txs/bond_tx.go
index a7100ddf..f0a65555 100644
--- a/txs/bond_tx.go
+++ b/txs/bond_tx.go
@@ -2,12 +2,8 @@ package txs
 
 import (
 	"fmt"
-	"io"
-
-	acm "github.com/hyperledger/burrow/account"
 	"github.com/hyperledger/burrow/account/state"
 	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
 )
 
 type BondTx struct {
@@ -28,25 +24,8 @@ func NewBondTx(pubkey crypto.PublicKey) (*BondTx, error) {
 	}, nil
 }
 
-func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err)
-	for i, in := range tx.Inputs {
-		in.WriteSignBytes(w, n, err)
-		if i != len(tx.Inputs)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(fmt.Sprintf(`],"pub_key":`)), w, n, err)
-	wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err)
-	wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err)
-	for i, out := range tx.UnbondTo {
-		out.WriteSignBytes(w, n, err)
-		if i != len(tx.UnbondTo)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(`]}]}`), w, n, err)
+func (tx *BondTx) Type() TxType {
+	return TxTypeBond
 }
 
 func (tx *BondTx) GetInputs() []TxInput {
@@ -57,10 +36,6 @@ func (tx *BondTx) String() string {
 	return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo)
 }
 
-func (tx *BondTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
 func (tx *BondTx) AddInput(st state.AccountGetter, pubkey crypto.PublicKey, amt uint64) error {
 	addr := pubkey.Address()
 	acc, err := st.GetAccount(addr)
@@ -90,23 +65,3 @@ func (tx *BondTx) AddOutput(addr crypto.Address, amt uint64) error {
 	})
 	return nil
 }
-
-func (tx *BondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != len(tx.Inputs)+1 {
-		return fmt.Errorf("BondTx expects %v SigningAccounts but got %v", len(tx.Inputs)+1,
-			len(signingAccounts))
-	}
-	var err error
-	tx.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
-	if err != nil {
-		return fmt.Errorf("could not sign %v: %v", tx, err)
-	}
-	for i := 1; i <= len(signingAccounts); i++ {
-		tx.Inputs[i].PublicKey = signingAccounts[i].PublicKey()
-		tx.Inputs[i].Signature, err = crypto.ChainSign(signingAccounts[i], chainID, tx)
-		if err != nil {
-			return fmt.Errorf("could not sign tx %v input %v: %v", tx, tx.Inputs[i], err)
-		}
-	}
-	return nil
-}
diff --git a/txs/call_tx.go b/txs/call_tx.go
index ebfdb6f4..a1e593f0 100644
--- a/txs/call_tx.go
+++ b/txs/call_tx.go
@@ -2,12 +2,9 @@ package txs
 
 import (
 	"fmt"
-	"io"
 
-	acm "github.com/hyperledger/burrow/account"
 	"github.com/hyperledger/burrow/account/state"
 	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
 )
 
 type CallTx struct {
@@ -56,28 +53,9 @@ func NewCallTxWithSequence(from crypto.PublicKey, to *crypto.Address, data []byt
 	}
 }
 
-func (tx *CallTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != 1 {
-		return fmt.Errorf("CallTx expects a single AddressableSigner for its single Input but %v were provieded",
-			len(signingAccounts))
-	}
-	var err error
-	tx.Input.PublicKey = signingAccounts[0].PublicKey()
-	tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
-	if err != nil {
-		return fmt.Errorf("could not sign %v: %v", tx, err)
-	}
-	return nil
-}
-
-func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err)
-	tx.Input.WriteSignBytes(w, n, err)
-	wire.WriteTo([]byte(`}]}`), w, n, err)
+func (tx *CallTx) Type() TxType {
+	return TxTypeCall
 }
-
 func (tx *CallTx) GetInputs() []TxInput {
 	return []TxInput{*tx.Input}
 }
@@ -85,7 +63,3 @@ func (tx *CallTx) GetInputs() []TxInput {
 func (tx *CallTx) String() string {
 	return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data)
 }
-
-func (tx *CallTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
diff --git a/txs/gov_tx.go b/txs/gov_tx.go
deleted file mode 100644
index ebfdb6f4..00000000
--- a/txs/gov_tx.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package txs
-
-import (
-	"fmt"
-	"io"
-
-	acm "github.com/hyperledger/burrow/account"
-	"github.com/hyperledger/burrow/account/state"
-	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
-)
-
-type CallTx struct {
-	Input *TxInput
-	// Pointer since CallTx defines unset 'to' address as inducing account creation
-	Address  *crypto.Address
-	GasLimit uint64
-	Fee      uint64
-	Data     []byte
-	txHashMemoizer
-}
-
-var _ Tx = &CallTx{}
-
-func NewCallTx(st state.AccountGetter, from crypto.PublicKey, to *crypto.Address, data []byte,
-	amt, gasLimit, fee uint64) (*CallTx, error) {
-
-	addr := from.Address()
-	acc, err := st.GetAccount(addr)
-	if err != nil {
-		return nil, err
-	}
-	if acc == nil {
-		return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from)
-	}
-
-	sequence := acc.Sequence() + 1
-	return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil
-}
-
-func NewCallTxWithSequence(from crypto.PublicKey, to *crypto.Address, data []byte,
-	amt, gasLimit, fee, sequence uint64) *CallTx {
-	input := &TxInput{
-		Address:   from.Address(),
-		Amount:    amt,
-		Sequence:  sequence,
-		PublicKey: from,
-	}
-
-	return &CallTx{
-		Input:    input,
-		Address:  to,
-		GasLimit: gasLimit,
-		Fee:      fee,
-		Data:     data,
-	}
-}
-
-func (tx *CallTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != 1 {
-		return fmt.Errorf("CallTx expects a single AddressableSigner for its single Input but %v were provieded",
-			len(signingAccounts))
-	}
-	var err error
-	tx.Input.PublicKey = signingAccounts[0].PublicKey()
-	tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
-	if err != nil {
-		return fmt.Errorf("could not sign %v: %v", tx, err)
-	}
-	return nil
-}
-
-func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err)
-	tx.Input.WriteSignBytes(w, n, err)
-	wire.WriteTo([]byte(`}]}`), w, n, err)
-}
-
-func (tx *CallTx) GetInputs() []TxInput {
-	return []TxInput{*tx.Input}
-}
-
-func (tx *CallTx) String() string {
-	return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data)
-}
-
-func (tx *CallTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
diff --git a/txs/governance_tx.go.txt b/txs/governance_tx.go.txt
new file mode 100644
index 00000000..b9da8ef2
--- /dev/null
+++ b/txs/governance_tx.go.txt
@@ -0,0 +1,77 @@
+package txs
+
+import (
+	"fmt"
+	acm "github.com/hyperledger/burrow/account"
+	"github.com/hyperledger/burrow/account/state"
+	"github.com/hyperledger/burrow/crypto"
+	"github.com/hyperledger/burrow/genesis/spec"
+	"bytes"
+)
+
+type GovernanceTx struct {
+	GovTxPayload
+	txHashMemoizer
+}
+
+type GovTxPayload struct {
+	AccountUpdates []spec.TemplateAccount
+}
+
+var _ Tx = &GovernanceTx{}
+
+func NewGovTx(st state.AccountGetter, from crypto.PublicKey, accounts ...spec.TemplateAccount) (*GovernanceTx, error) {
+	addr := from.Address()
+	acc, err := st.GetAccount(addr)
+	if err != nil {
+		return nil, err
+	}
+	if acc == nil {
+		return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from)
+	}
+
+	sequence := acc.Sequence() + 1
+	return NewGovTxWithSequence(from, sequence, accounts), nil
+}
+
+func NewGovTxWithSequence(from crypto.PublicKey, sequence uint64, accounts []spec.TemplateAccount) *GovernanceTx {
+	return &GovernanceTx{
+		TxHeader: TxHeader{
+			PublicKey: from,
+			Sequence:  sequence,
+		},
+		GovTxPayload: GovTxPayload{
+			AccountUpdates: accounts,
+		},
+	}
+}
+
+func (tx *GovernanceTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
+	if len(signingAccounts) != 1 {
+		return fmt.Errorf("GovernanceTx expects a single AddressableSigner for its single Input but %v were provieded",
+			len(signingAccounts))
+	}
+	var err error
+	tx.Input.PublicKey = signingAccounts[0].PublicKey()
+	tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
+	if err != nil {
+		return fmt.Errorf("could not sign %v: %v", tx, err)
+	}
+	return nil
+}
+
+func (tx *GovernanceTx) SignBytes(chainID string) ([]byte, error) {
+	buf := new(bytes.Buffer)
+}
+
+func (tx *GovernanceTx) GetInputs() []TxInput {
+	return []TxInput{*tx.Input}
+}
+
+func (tx *GovernanceTx) String() string {
+	return fmt.Sprintf("GovernanceTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data)
+}
+
+func (tx *GovernanceTx) Hash(chainID string) []byte {
+	return tx.txHashMemoizer.hash(chainID, tx)
+}
diff --git a/txs/name_tx.go b/txs/name_tx.go
index d16ff0d1..479f7d46 100644
--- a/txs/name_tx.go
+++ b/txs/name_tx.go
@@ -2,14 +2,10 @@ package txs
 
 import (
 	"fmt"
-	"io"
-
 	"regexp"
 
-	acm "github.com/hyperledger/burrow/account"
 	"github.com/hyperledger/burrow/account/state"
 	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
 )
 
 // Name should be file system lik
@@ -57,27 +53,8 @@ func NewNameTxWithSequence(from crypto.PublicKey, name, data string, amt, fee, s
 	}
 }
 
-func (tx *NameTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != 1 {
-		return fmt.Errorf("NameTx expects a single AddressableSigner for its single Input but %v were provieded",
-			len(signingAccounts))
-	}
-	var err error
-	tx.Input.PublicKey = signingAccounts[0].PublicKey()
-	tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
-	if err != nil {
-		return fmt.Errorf("could not sign %v: %v", tx, err)
-	}
-	return nil
-}
-
-func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err)
-	wire.WriteTo([]byte(`,"input":`), w, n, err)
-	tx.Input.WriteSignBytes(w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err)
-	wire.WriteTo([]byte(`}]}`), w, n, err)
+func (tx *NameTx) Type() TxType {
+	return TxTypeName
 }
 
 func (tx *NameTx) GetInputs() []TxInput {
@@ -110,10 +87,6 @@ func (tx *NameTx) String() string {
 	return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data)
 }
 
-func (tx *NameTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-
 // filter strings
 func validateNameRegEntryName(name string) bool {
 	return regexpAlphaNum.Match([]byte(name))
diff --git a/txs/permission_tx.go b/txs/permission_tx.go
index 2ec99eb8..2bda5c5b 100644
--- a/txs/permission_tx.go
+++ b/txs/permission_tx.go
@@ -2,13 +2,10 @@ package txs
 
 import (
 	"fmt"
-	"io"
 
-	acm "github.com/hyperledger/burrow/account"
 	"github.com/hyperledger/burrow/account/state"
 	"github.com/hyperledger/burrow/crypto"
 	"github.com/hyperledger/burrow/permission/snatives"
-	"github.com/tendermint/go-wire"
 )
 
 type PermissionsTx struct {
@@ -47,27 +44,8 @@ func NewPermissionsTxWithSequence(from crypto.PublicKey, args snatives.PermArgs,
 	}
 }
 
-func (tx *PermissionsTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != 1 {
-		return fmt.Errorf("PermissionsTx expects a single AddressableSigner for its single Input but %v were provieded",
-			len(signingAccounts))
-	}
-	var err error
-	tx.Input.PublicKey = signingAccounts[0].PublicKey()
-	tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
-	if err != nil {
-		return fmt.Errorf("could not sign %v: %v", tx, err)
-	}
-	return nil
-}
-
-func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err)
-	wire.WriteJSON(&tx.PermArgs, w, n, err)
-	wire.WriteTo([]byte(`","input":`), w, n, err)
-	tx.Input.WriteSignBytes(w, n, err)
-	wire.WriteTo([]byte(`}]}`), w, n, err)
+func (tx *PermissionsTx) Type() TxType {
+	return TxTypePermissions
 }
 
 func (tx *PermissionsTx) GetInputs() []TxInput {
@@ -77,7 +55,3 @@ func (tx *PermissionsTx) GetInputs() []TxInput {
 func (tx *PermissionsTx) String() string {
 	return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs)
 }
-
-func (tx *PermissionsTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
diff --git a/txs/rebond_tx.go b/txs/rebond_tx.go
deleted file mode 100644
index 8816937d..00000000
--- a/txs/rebond_tx.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package txs
-
-import (
-	"fmt"
-	"io"
-
-	acm "github.com/hyperledger/burrow/account"
-	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
-)
-
-type RebondTx struct {
-	Address   crypto.Address
-	Height    int
-	Signature crypto.Signature
-	txHashMemoizer
-}
-
-var _ Tx = &RebondTx{}
-
-func NewRebondTx(addr crypto.Address, height int) *RebondTx {
-	return &RebondTx{
-		Address: addr,
-		Height:  height,
-	}
-}
-
-func (tx *RebondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != 1 {
-		return fmt.Errorf("RebondTx expects a single AddressableSigner for its signature but %v were provieded",
-			len(signingAccounts))
-	}
-	var err error
-	tx.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
-	if err != nil {
-		return fmt.Errorf("could not sign %v: %v", tx, err)
-	}
-	return nil
-}
-
-func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err)
-}
-
-func (tx *RebondTx) GetInputs() []TxInput {
-	return nil
-}
-
-func (tx *RebondTx) String() string {
-	return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature)
-}
-
-func (tx *RebondTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
diff --git a/txs/send_tx.go b/txs/send_tx.go
index 421d9f19..84735d97 100644
--- a/txs/send_tx.go
+++ b/txs/send_tx.go
@@ -2,12 +2,8 @@ package txs
 
 import (
 	"fmt"
-	"io"
-
-	acm "github.com/hyperledger/burrow/account"
 	"github.com/hyperledger/burrow/account/state"
 	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
 )
 
 type SendTx struct {
@@ -25,35 +21,16 @@ func NewSendTx() *SendTx {
 	}
 }
 
-func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err)
-	for i, in := range tx.Inputs {
-		in.WriteSignBytes(w, n, err)
-		if i != len(tx.Inputs)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(`],"outputs":[`), w, n, err)
-	for i, out := range tx.Outputs {
-		out.WriteSignBytes(w, n, err)
-		if i != len(tx.Outputs)-1 {
-			wire.WriteTo([]byte(","), w, n, err)
-		}
-	}
-	wire.WriteTo([]byte(`]}]}`), w, n, err)
-}
-
 func (tx *SendTx) GetInputs() []TxInput {
 	return copyInputs(tx.Inputs)
 }
 
-func (tx *SendTx) String() string {
-	return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs)
+func (tx *SendTx) Type() TxType {
+	return TxTypeSend
 }
 
-func (tx *SendTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
+func (tx *SendTx) String() string {
+	return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs)
 }
 
 func (tx *SendTx) AddInput(st state.AccountGetter, pubkey crypto.PublicKey, amt uint64) error {
@@ -87,18 +64,3 @@ func (tx *SendTx) AddOutput(addr crypto.Address, amt uint64) error {
 	return nil
 }
 
-func (tx *SendTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != len(tx.Inputs) {
-		return fmt.Errorf("SendTx has %v Inputs but was provided with %v SigningAccounts", len(tx.Inputs),
-			len(signingAccounts))
-	}
-	var err error
-	for i, signingAccount := range signingAccounts {
-		tx.Inputs[i].PublicKey = signingAccount.PublicKey()
-		tx.Inputs[i].Signature, err = crypto.ChainSign(signingAccount, chainID, tx)
-		if err != nil {
-			return fmt.Errorf("could not sign tx %v input %v: %v", tx, tx.Inputs[i], err)
-		}
-	}
-	return nil
-}
diff --git a/txs/tx.go b/txs/tx.go
index e20292ef..f76261b1 100644
--- a/txs/tx.go
+++ b/txs/tx.go
@@ -18,11 +18,9 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io"
 
 	acm "github.com/hyperledger/burrow/account"
 	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire/data"
 	"golang.org/x/crypto/ripemd160"
 )
 
@@ -52,40 +50,50 @@ Admin Txs:
  - PermissionsTx
 */
 
+type TxType int8
+
 // Types of Tx implementations
 const (
+	TxTypeUnknown = TxType(0x00)
 	// Account transactions
-	TxTypeSend = byte(0x01)
-	TxTypeCall = byte(0x02)
-	TxTypeName = byte(0x03)
+	TxTypeSend = TxType(0x01)
+	TxTypeCall = TxType(0x02)
+	TxTypeName = TxType(0x03)
 
 	// Validation transactions
-	TxTypeBond   = byte(0x11)
-	TxTypeUnbond = byte(0x12)
-	TxTypeRebond = byte(0x13)
+	TxTypeBond   = TxType(0x11)
+	TxTypeUnbond = TxType(0x12)
 
 	// Admin transactions
-	TxTypePermissions = byte(0x1f)
+	TxTypePermissions = TxType(0x21)
+	TxTypeGovernance  = TxType(0x22)
 )
 
-var mapper = data.NewMapper(Wrapper{}).
-	RegisterImplementation(&SendTx{}, "send_tx", TxTypeSend).
-	RegisterImplementation(&CallTx{}, "call_tx", TxTypeCall).
-	RegisterImplementation(&NameTx{}, "name_tx", TxTypeName).
-	RegisterImplementation(&BondTx{}, "bond_tx", TxTypeBond).
-	RegisterImplementation(&UnbondTx{}, "unbond_tx", TxTypeUnbond).
-	RegisterImplementation(&RebondTx{}, "rebond_tx", TxTypeRebond).
-	RegisterImplementation(&PermissionsTx{}, "permissions_tx", TxTypePermissions)
+var txNameFromType = map[TxType]string{
+	TxTypeUnknown:     "UnknownTx",
+	TxTypeSend:        "SendTx",
+	TxTypeCall:        "CallTx",
+	TxTypeName:        "NameTx",
+	TxTypeBond:        "BondTx",
+	TxTypeUnbond:      "UnbondTx",
+	TxTypePermissions: "PermissionsTx",
+	TxTypeGovernance:  "GovernanceTx",
+}
+
+var txTypeFromName = make(map[string]TxType)
+
+func init() {
+	for t, n := range txNameFromType {
+		txTypeFromName[n] = t
+	}
+}
 
-	//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
 
-// TODO: replace with sum-type struct like ResultEvent
 type Tx interface {
-	WriteSignBytes(chainID string, w io.Writer, n *int, err *error)
 	String() string
 	GetInputs() []TxInput
-	Hash(chainID string) []byte
-	Sign(chainID string, signingAccounts ...acm.AddressableSigner) error
+	Type() TxType
 }
 
 type Encoder interface {
@@ -96,6 +104,45 @@ type Decoder interface {
 	DecodeTx(txBytes []byte) (Tx, error)
 }
 
+func NewTx(txType TxType) Tx {
+	switch txType {
+	case TxTypeSend:
+		return &SendTx{}
+	case TxTypeCall:
+		return &CallTx{}
+	case TxTypeName:
+		return &NameTx{}
+	case TxTypeBond:
+		return &BondTx{}
+	case TxTypeUnbond:
+		return &UnbondTx{}
+	case TxTypePermissions:
+		return &PermissionsTx{}
+	}
+	return nil
+}
+
+func (txType TxType) String() string {
+	name, ok := txNameFromType[txType]
+	if ok {
+		return name
+	}
+	return "UnknownTx"
+}
+
+func TxTypeFromString(name string) TxType {
+	return txTypeFromName[name]
+}
+
+func (txType TxType) MarshalText() ([]byte, error) {
+	return []byte(txType.String()), nil
+}
+
+func (txType *TxType) UnmarshalText(data []byte) error {
+	*txType = TxTypeFromString(string(data))
+	return nil
+}
+
 // BroadcastTx or Transact
 type Receipt struct {
 	TxHash          []byte
@@ -103,69 +150,145 @@ type Receipt struct {
 	ContractAddress crypto.Address
 }
 
-type Wrapper struct {
-	Tx `json:"unwrap"`
+type Envelope struct {
+	Signatures []crypto.Signature
+	Body
 }
 
-// Wrap the Tx in a struct that allows for go-wire JSON serialisation
-func Wrap(tx Tx) Wrapper {
-	if txWrapped, ok := tx.(Wrapper); ok {
-		return txWrapped
+func (env *Envelope) Sign(signingAccounts ...acm.AddressableSigner) error {
+	signBytes, err := env.Body.SignBytes()
+	if err != nil {
+		return err
 	}
-	return Wrapper{
-		Tx: tx,
+	for _, sa := range signingAccounts {
+		sig, err := sa.Sign(signBytes)
+		if err != nil {
+			return err
+		}
+		env.Signatures = append(env.Signatures, sig)
 	}
+	return nil
 }
 
-// A serialisation wrapper that is itself a Tx
-func (txw Wrapper) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	txw.Tx.WriteSignBytes(chainID, w, n, err)
+func SignTx(chainID string, tx Tx, signingAccounts ...acm.AddressableSigner) (*Envelope, error) {
+	env := &Envelope{
+		Body: Body{
+			ChainID: chainID,
+			Tx:      tx,
+		},
+	}
+	err := env.Sign(signingAccounts...)
+	if err != nil {
+		return nil, err
+	}
+	return env, nil
 }
 
-func (txw Wrapper) MarshalJSON() ([]byte, error) {
-	return mapper.ToJSON(txw.Tx)
+// The
+type Body struct {
+	ChainID string
+	TxType  TxType
+	Tx
+	txHash []byte
 }
 
-func (txw *Wrapper) UnmarshalJSON(data []byte) (err error) {
-	parsed, err := mapper.FromJSON(data)
-	if err == nil && parsed != nil {
-		txw.Tx = parsed.(Tx)
+// Wrap the Tx in Body required for signing and serialisation
+func Wrap(tx Tx) *Body {
+	switch t := tx.(type) {
+	case Body:
+		return &t
+	case *Body:
+		return t
+	}
+	return &Body{
+		TxType: tx.Type(),
+		Tx:     tx,
 	}
-	return err
 }
 
-// Get the inner Tx that this Wrapper wraps
-func (txw *Wrapper) Unwrap() Tx {
-	return txw.Tx
+func ChainWrap(chainID string, tx Tx) *Body {
+	body := Wrap(tx)
+	body.ChainID = chainID
+	return body
 }
 
-// Avoid re-hashing the same in-memory Tx
-type txHashMemoizer struct {
-	txHashBytes []byte
-	chainID     string
+func (body *Body) MustSignBytes() []byte {
+	bs, err := body.SignBytes()
+	if err != nil {
+		panic(err)
+	}
+	return bs
 }
 
-func (thm *txHashMemoizer) hash(chainID string, tx Tx) []byte {
-	if thm.txHashBytes == nil || thm.chainID != chainID {
-		thm.chainID = chainID
-		thm.txHashBytes = TxHash(chainID, tx)
+func (body *Body) SignBytes() ([]byte, error) {
+	bs, err := json.Marshal(body)
+	if err != nil {
+		return nil, fmt.Errorf("could not generate canonical SignBytes for tx %v: %v", body.Tx, err)
 	}
-	return thm.txHashBytes
+	return bs, nil
+}
+
+// Serialisation intermediate for switching on type
+type wrapper struct {
+	ChainID string
+	TxType  TxType
+	Tx      json.RawMessage
 }
 
-func TxHash(chainID string, tx Tx) []byte {
-	signBytes := crypto.SignBytes(chainID, tx)
+func (body Body) MarshalJSON() ([]byte, error) {
+	bs, err := json.Marshal(body.Tx)
+	if err != nil {
+		return nil, err
+	}
+	return json.Marshal(wrapper{
+		ChainID: body.ChainID,
+		TxType:  body.Type(),
+		Tx:      bs,
+	})
+}
+
+func (body *Body) UnmarshalJSON(data []byte) error {
+	w := new(wrapper)
+	err := json.Unmarshal(data, w)
+	if err != nil {
+		return err
+	}
+	body.ChainID = w.ChainID
+	body.TxType = w.TxType
+	body.Tx = NewTx(w.TxType)
+	return json.Unmarshal(w.Tx, body.Tx)
+}
+
+// Get the inner Tx that this Wrapper wraps
+func (body *Body) Unwrap() Tx {
+	return body.Tx
+}
+
+func (body *Body) Hash() []byte {
+	if body.txHash == nil {
+		return body.Rehash()
+	}
+	return body.txHash
+}
+
+func (body *Body) Rehash() []byte {
 	hasher := ripemd160.New()
-	hasher.Write(signBytes)
-	// Calling Sum(nil) just gives us the digest with nothing prefixed
-	return hasher.Sum(nil)
+	hasher.Write(body.MustSignBytes())
+	body.txHash = hasher.Sum(nil)
+	return body.txHash
+}
+
+// Avoid re-hashing the same in-memory Tx
+type txHashMemoizer struct {
+	txHashBytes []byte
+	chainID     string
 }
 
-func GenerateReceipt(chainId string, tx Tx) Receipt {
+func (body *Body) GenerateReceipt() Receipt {
 	receipt := Receipt{
-		TxHash: tx.Hash(chainId),
+		TxHash: body.Hash(),
 	}
-	if callTx, ok := tx.(*CallTx); ok {
+	if callTx, ok := body.Tx.(*CallTx); ok {
 		receipt.CreatesContract = callTx.Address == nil
 		if receipt.CreatesContract {
 			receipt.ContractAddress = crypto.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence)
@@ -202,13 +325,3 @@ func copyInputs(inputs []*TxInput) []TxInput {
 	}
 	return inputsCopy
 }
-
-// Contract: This function is deterministic and completely reversible.
-func jsonEscape(str string) string {
-	// TODO: escape without panic
-	escapedBytes, err := json.Marshal(str)
-	if err != nil {
-		panic(fmt.Errorf("error json-escaping string: %s", str))
-	}
-	return string(escapedBytes)
-}
diff --git a/txs/tx_input.go b/txs/tx_input.go
index b8d4d4a9..610000c8 100644
--- a/txs/tx_input.go
+++ b/txs/tx_input.go
@@ -2,22 +2,20 @@ package txs
 
 import (
 	"fmt"
-	"io"
-
 	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
+	"bytes"
 )
 
 type TxInput struct {
 	Address   crypto.Address
+	PublicKey crypto.PublicKey
+	Signature crypto.Signature
 	Amount    uint64
 	Sequence  uint64
-	Signature crypto.Signature
-	PublicKey crypto.PublicKey
 }
 
 func (txIn *TxInput) ValidateBasic() error {
-	if len(txIn.Address) != 20 {
+	if txIn.Address == crypto.ZeroAddress {
 		return ErrTxInvalidAddress
 	}
 	if txIn.Amount == 0 {
@@ -26,8 +24,10 @@ func (txIn *TxInput) ValidateBasic() error {
 	return nil
 }
 
-func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err)
+func (txIn *TxInput) SignBytes() ([]byte, error) {
+	buf := new(bytes.Buffer)
+	buf.WriteString(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence))
+	return buf.Bytes(), nil
 }
 
 func (txIn *TxInput) String() string {
diff --git a/txs/tx_test.go b/txs/tx_test.go
index 44f9892a..0be9effb 100644
--- a/txs/tx_test.go
+++ b/txs/tx_test.go
@@ -60,7 +60,7 @@ func TestSendTxSignable(t *testing.T) {
 			},
 		},
 	}
-	signBytes := crypto.SignBytes(chainID, sendTx)
+	signBytes := ChainWrap(chainID, sendTx).MustSignBytes()
 	signStr := string(signBytes)
 	expected := fmt.Sprintf(`{"chain_id":"%s","tx":[1,{"inputs":[{"address":"%s","amount":12345,"sequence":67890},{"address":"%s","amount":111,"sequence":222}],"outputs":[{"address":"%s","amount":333},{"address":"%s","amount":444}]}]}`,
 		chainID, sendTx.Inputs[0].Address.String(), sendTx.Inputs[1].Address.String(), sendTx.Outputs[0].Address.String(), sendTx.Outputs[1].Address.String())
@@ -83,7 +83,7 @@ func TestCallTxSignable(t *testing.T) {
 		Fee:      222,
 		Data:     []byte("data1"),
 	}
-	signBytes := crypto.SignBytes(chainID, callTx)
+	signBytes := ChainWrap(chainID, callTx).MustSignBytes()
 	signStr := string(signBytes)
 	expected := fmt.Sprintf(`{"chain_id":"%s","tx":[2,{"address":"%s","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"%s","amount":12345,"sequence":67890}}]}`,
 		chainID, callTx.Address.String(), callTx.Input.Address.String())
@@ -103,7 +103,7 @@ func TestNameTxSignable(t *testing.T) {
 		Data: "secretly.not.google.com",
 		Fee:  1000,
 	}
-	signBytes := crypto.SignBytes(chainID, nameTx)
+	signBytes := ChainWrap(chainID, nameTx).MustSignBytes()
 	signStr := string(signBytes)
 	expected := fmt.Sprintf(`{"chain_id":"%s","tx":[3,{"data":"secretly.not.google.com","fee":1000,"input":{"address":"%s","amount":12345,"sequence":250},"name":"google.com"}]}`,
 		chainID, nameTx.Input.Address.String())
@@ -153,7 +153,8 @@ func TestBondTxSignable(t *testing.T) {
 		bondTx.UnbondTo[0].Address.String(),
 		bondTx.UnbondTo[1].Address.String())
 
-	assert.Equal(t, expected, string(crypto.SignBytes(chainID, bondTx)), "Unexpected sign string for BondTx")
+	signBytes := ChainWrap(chainID, bondTx).MustSignBytes()
+	assert.Equal(t, expected, string(signBytes), "Unexpected sign string for BondTx")
 }
 
 func TestUnbondTxSignable(t *testing.T) {
@@ -161,7 +162,7 @@ func TestUnbondTxSignable(t *testing.T) {
 		Address: makeAddress("address1"),
 		Height:  111,
 	}
-	signBytes := crypto.SignBytes(chainID, unbondTx)
+	signBytes := ChainWrap(chainID, unbondTx).MustSignBytes()
 	signStr := string(signBytes)
 	expected := fmt.Sprintf(`{"chain_id":"%s","tx":[18,{"address":"%s","height":111}]}`,
 		chainID, unbondTx.Address.String())
@@ -170,20 +171,6 @@ func TestUnbondTxSignable(t *testing.T) {
 	}
 }
 
-func TestRebondTxSignable(t *testing.T) {
-	rebondTx := &RebondTx{
-		Address: makeAddress("address1"),
-		Height:  111,
-	}
-	signBytes := crypto.SignBytes(chainID, rebondTx)
-	signStr := string(signBytes)
-	expected := fmt.Sprintf(`{"chain_id":"%s","tx":[19,{"address":"%s","height":111}]}`,
-		chainID, rebondTx.Address.String())
-	if signStr != expected {
-		t.Errorf("Got unexpected sign string for RebondTx")
-	}
-}
-
 func TestPermissionsTxSignable(t *testing.T) {
 	permsTx := &PermissionsTx{
 		Input: &TxInput{
@@ -194,13 +181,18 @@ func TestPermissionsTxSignable(t *testing.T) {
 		PermArgs: snatives.SetBaseArgs(makeAddress("address1"), 1, true),
 	}
 
-	signBytes := crypto.SignBytes(chainID, permsTx)
-	signStr := string(signBytes)
-	expected := fmt.Sprintf(`{"chain_id":"%s","tx":[31,{"args":"{"PermFlag":%v,"Address":"%s","Permission":1,"Value":true}","input":{"address":"%s","amount":12345,"sequence":250}}]}`,
-		chainID, ptypes.SetBase, permsTx.PermArgs.Address.String(), permsTx.Input.Address.String())
-	if signStr != expected {
-		t.Errorf("Got unexpected sign string for PermsTx. Expected:\n%v\nGot:\n%v", expected, signStr)
+	body := ChainWrap(chainID, permsTx)
+	env := Envelope{
+		Body: *body,
 	}
+	signBytes := body.MustSignBytes()
+	bodyOut := new(Body)
+
+	bs, err := json.MarshalIndent(env, "", "  ")
+	require.NoError(t, err)
+	fmt.Println(string(bs))
+	require.NoError(t, json.Unmarshal(signBytes, bodyOut))
+	assert.Equal(t, signBytes, body.MustSignBytes())
 }
 
 func TestTxWrapper_MarshalJSON(t *testing.T) {
@@ -228,24 +220,13 @@ func TestNewPermissionsTxWithSequence(t *testing.T) {
 }
 
 func testTxMarshalJSON(t *testing.T, tx Tx) {
-	txw := &Wrapper{Tx: tx}
+	txw := &Body{Tx: tx}
 	bs, err := json.Marshal(txw)
 	require.NoError(t, err)
-	txwOut := new(Wrapper)
+	txwOut := new(Body)
 	err = json.Unmarshal(bs, txwOut)
 	require.NoError(t, err)
 	bsOut, err := json.Marshal(txwOut)
 	require.NoError(t, err)
 	assert.Equal(t, string(bs), string(bsOut))
 }
-
-func TestTxHashMemoizer(t *testing.T) {
-	tx := &CallTx{
-		Input: &TxInput{
-			Sequence: 4,
-		},
-	}
-	hsh := tx.Hash("foo")
-	assert.Equal(t, hsh, tx.txHashMemoizer.txHashBytes)
-	assert.Equal(t, "foo", tx.txHashMemoizer.chainID)
-}
diff --git a/txs/unbond_tx.go b/txs/unbond_tx.go
index 850de4f5..4b77ced0 100644
--- a/txs/unbond_tx.go
+++ b/txs/unbond_tx.go
@@ -2,11 +2,8 @@ package txs
 
 import (
 	"fmt"
-	"io"
 
-	acm "github.com/hyperledger/burrow/account"
 	"github.com/hyperledger/burrow/crypto"
-	"github.com/tendermint/go-wire"
 )
 
 type UnbondTx struct {
@@ -25,22 +22,8 @@ func NewUnbondTx(addr crypto.Address, height int) *UnbondTx {
 	}
 }
 
-func (tx *UnbondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error {
-	if len(signingAccounts) != 1 {
-		return fmt.Errorf("UnbondTx expects a single AddressableSigner for its signature but %v were provided",
-			len(signingAccounts))
-	}
-	var err error
-	tx.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx)
-	if err != nil {
-		return fmt.Errorf("could not sign %v: %v", tx, err)
-	}
-	return nil
-}
-
-func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) {
-	wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err)
-	wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err)
+func (tx *UnbondTx) Type() TxType {
+	return TxTypeUnbond
 }
 
 func (tx *UnbondTx) GetInputs() []TxInput {
@@ -50,7 +33,3 @@ func (tx *UnbondTx) GetInputs() []TxInput {
 func (tx *UnbondTx) String() string {
 	return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature)
 }
-
-func (tx *UnbondTx) Hash(chainID string) []byte {
-	return tx.txHashMemoizer.hash(chainID, tx)
-}
-- 
GitLab