Repository: ethereum/consensus-specs

Issue:
Signing root problems
## Quick definition

- `hash_tree_root` merkleizes all fields
- `signing_root` merkleizes all fields except the last.

### Example:
```python
class BeaconBlock(Container):
    slot: Slot
    parent_root: Hash
    state_root: Hash
    body: BeaconBlockBody
    signature: BLSSignature
```

- `hash_tree_root`: `H(H(slot, parent_root), H(state_root, body))`
- `signing_root`: `H(H(H(slot, parent_root), H(state_root, body)), H(H(signature, Z[0]), Z[1]))`

## Why?

- Consensus only cares about `signing_root` on-chain in case of data types with a signature, since the signature is already verified (as part of block processing)
- `signing_root` provides the hash to sign and create `signature` with.

## Why not?

- Summaries/Expansions in SSZ: The state embeds a `block_root`, but expanding it into a block header or full block is not possible without knowing that it's a signing root.
    - Use separate type definition for the data, omitting the signature field. E.g. `some_msg_root: Root[FieldTypeWithoutSig]`
    - Mark the root as a signing root, and expand into the definition ignoring the signature field. E.g. `some_msg_root: SigRoot[FieldTypeWithSig]`, `some_signed_msg_root: HashRoot[FieldTypeWithSig]`. 
- Tree representation:
    - To represent a `BeaconBlock` as a tree you have `hash_tree_root` and `singing_root` trees.
        - State representation as a tree is useful for:
            - Caching: lazily hash child nodes `a` and `b`, cache `H(a,b)` at the tree node.
            - Fork a state into two trees with common subtrees: effectively only storing the difference to pre-state with each addition. Alike to a merkle datastore.
            - Quickly respond to proof requests by having the tree ready.
    - To navigate the tree, the expansion problem repeats: again a need to separate the two different merkle tree types.
- Inconsistency: Hash-tree-root is universal: everything has a hash-tree-root. But signing-root is not, only containers can have it. 
- Indexing: A signing-root db index is necessary for generally on-chain activity, and a hash-tree-root db index for off-chain (see if a block with specific signature is known)
    - Cannot convert the signing-root to hash-tree-root (except technically for `BeaconBlock`, but only as a by-product of current field count).

## Alternatives?

1. No `signing_root`:
    - Define `BeaconBlock` and `SignedBeaconBlock`.
        ```python
        class BeaconBlock(Container):
            slot: Slot
            parent_root: Hash
            state_root: Hash
            body: BeaconBlockBody
        
        class SignedBeaconBlock(Container):
            data: BeaconBlock
            signature: BLSSignature
        ```
        - Convenient: `hash_tree_root(SignedBeaconBlock) == H(hash_tree_root(BeaconBlock), signature)`
            - No need to provide the full branch to the signature to proof a hash-tree-root or convert a signing root into a hash-tree-root. Just provide the signature.
        - No two different type definitions, always one way to represent the type as a tree.
            - No `SigRoot`/`HashRoot` confusion or duplicate choices of representation.
        - No inconsistency with other types that are signed in different ways.
    - Optional: less repetitive definitions, add a `Signed[MsgType]` helper type to wrap any container with a signature
    - No confusion between two root types in block explorers/etc, the types have names now.
2. Type the output, work around for only some of the issues:
    - Backwards compatible, no functional change. `signing_root` stays.
    - Define `HashRoot` and `SigRoot`, both derived from `Bytes32`
    - Wherever a root needs to be expanded into a type, it can be done based on its `HashRoot` / `SigRoot`
    - Does not solve the represent-as-tree problem: `BeaconBlock` still has two different tree representations.
    - Does not solve the inconsistency problem: there will still be cases where a regular `SigRoot` does not fit due to different message / signature requirements.

## Edge cases

- `DepositData` in the spec: hash-tree-roots with signatures included are part of the deposits list root. But processing the signature is done with signing root.
    - `process_deposit`: We do not like to change the spec here: but removal of signing root doesn't require a change to the deposit contract. We just need to define a `DepositMsg` with the fields that were signed over, functionally producing the same old signing_root behavior.
- Networking: signatures may not be changed and the appearance of a block has to be checked by.
    - Things improve here with option 1: getting a root that includes the signature is as easy as hashing the signing root with the signature.

## Related

Expansions/summaries are difficult with two root variants and effectively different trees:
- https://github.com/ethereum/eth2.0-specs/issues/1436
    - The temporary "solution" is to think of it as an implementation problem. 
        - "Two options: define explicit types with/without signature yourself, or type the roots yourself"
- Light client desire for `Root[UnderlyingType]` to easily expand to `UnderlyingType` (and its further expansions). With two root types this gets difficult.
- The original introduction of "signed_root", with similar but slightly different definition, and mostly focused on unification of type definitions / line count reduction: https://github.com/ethereum/eth2.0-specs/pull/625

## Proposal

Since SSZ itself is not quite frozen, but the core functionality for Phase 0 was, so this proposal will go slower and through more review.

- Implement alternative 1 in a PR to the specs repo
- Personally expect that it will mostly be a welcome change for clients:
    - cost: defining a few simple containers, updating signing_root calls to hash_tree_root these signature-less containers instead.
    - benefit: less merkleization variation to worry about, enable caching/trees/more.
- Others involved with SSZ share their feedback / concerns / support.
- Decide next Eth2 implementers call (early Dec.) if it can go in.





Discussion:
Yes! +1 for alternative 1

* It would potentially be nice to maintain the tuple `[data, signature]` throughout as a pattern, or indeed use a generic notation for it - either is fine.
* This also solves the problem of `BeaconBlockHeader` in `BeaconState` containing an always-zeroed signature
* It solves an ambiguity whether blocks should be requested by `signing_root` or `hash_tree_root` in the network protocol (there were a few back-and-forths and signing-roots were chosen)
+1 for alternative 1
+1 for alternative 1

Resolving patch:
diff --git a/scripts/build_spec.py b/scripts/build_spec.py
--- a/scripts/build_spec.py
+++ b/scripts/build_spec.py
@@ -19,10 +19,7 @@
     field,
 )
 
-from eth2spec.utils.ssz.ssz_impl import (
-    hash_tree_root,
-    signing_root,
-)
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
 from eth2spec.utils.ssz.ssz_typing import (
     boolean, Container, List, Vector, uint64,
     Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
@@ -50,7 +47,6 @@
 
 from eth2spec.utils.ssz.ssz_impl import (
     hash_tree_root,
-    signing_root,
     is_zero,
 )
 from eth2spec.utils.ssz.ssz_typing import (


Test patch:
diff --git a/specs/test_formats/ssz_generic/README.md b/specs/test_formats/ssz_generic/README.md
--- a/specs/test_formats/ssz_generic/README.md
+++ b/specs/test_formats/ssz_generic/README.md
@@ -42,7 +42,6 @@ The expected roots are encoded into the metadata yaml:
 
 ```yaml
 root: Bytes32             -- Hash-tree-root of the object
-signing_root: Bytes32     -- Signing-root of the object 
 ```
 
 The `Bytes32` is encoded as a string, hexadecimal encoding, prefixed with `0x`.
diff --git a/specs/test_formats/ssz_static/core.md b/specs/test_formats/ssz_static/core.md
--- a/specs/test_formats/ssz_static/core.md
+++ b/specs/test_formats/ssz_static/core.md
@@ -24,8 +24,6 @@ The output parts are: `roots.yaml`, `serialized.ssz`, `value.yaml`
 
 ```yaml
 root: bytes32         -- string, hash-tree-root of the value, hex encoded, with prefix 0x
-signing_root: bytes32 -- string, signing-root of the value, hex encoded, with prefix 0x. 
-                           *Optional*, present if type is a container and ends with a ``signature`` field.
 ```
 
 ### `serialized.ssz`
@@ -47,11 +45,9 @@ A test-runner can implement the following assertions:
     - Serialization in 2 steps: deserialize `serialized`, then serialize the result, 
        and verify if the bytes match the original `serialized`.
 - Hash-tree-root: After parsing the `value` (or deserializing `serialized`), Hash-tree-root it: the output should match `root`
-    - Optionally also check `signing_root`, if present.
 
 
 ## References
 
 **`serialized`**—[SSZ serialization](../../simple-serialize.md#serialization)   
 **`root`**—[hash_tree_root](../../simple-serialize.md#merkleization) function  
-**`signing_root`**—[signing_root](../../simple-serialize.md#self-signed-containers) function
diff --git a/test_generators/ssz_generic/ssz_test_case.py b/test_generators/ssz_generic/ssz_test_case.py
--- a/test_generators/ssz_generic/ssz_test_case.py
+++ b/test_generators/ssz_generic/ssz_test_case.py
@@ -1,4 +1,4 @@
-from eth2spec.utils.ssz.ssz_impl import serialize, hash_tree_root, signing_root
+from eth2spec.utils.ssz.ssz_impl import serialize, hash_tree_root
 from eth2spec.debug.encode import encode
 from eth2spec.utils.ssz.ssz_typing import SSZValue, Container
 from typing import Callable
@@ -10,8 +10,6 @@ def case_fn():
         yield "value", "data", encode(value)
         yield "serialized", "ssz", serialize(value)
         yield "root", "meta", '0x' + hash_tree_root(value).hex()
-        if isinstance(value, Container):
-            yield "signing_root", "meta", '0x' + signing_root(value).hex()
     return case_fn
 
 
diff --git a/test_generators/ssz_static/main.py b/test_generators/ssz_static/main.py
--- a/test_generators/ssz_static/main.py
+++ b/test_generators/ssz_static/main.py
@@ -7,7 +7,6 @@
 from eth2spec.utils.ssz.ssz_typing import Container
 from eth2spec.utils.ssz.ssz_impl import (
     hash_tree_root,
-    signing_root,
     serialize,
 )
 from gen_base import gen_runner, gen_typing
@@ -24,8 +23,6 @@ def create_test_case(rng: Random, typ, mode: random_value.RandomizationMode, cha
     roots_data = {
         "root": '0x' + hash_tree_root(value).hex()
     }
-    if isinstance(value, Container) and hasattr(value, "signature"):
-        roots_data["signing_root"] = '0x' + signing_root(value).hex()
     yield "roots", "data", roots_data
 
 
diff --git a/test_libs/pyspec/eth2spec/test/fork_choice/test_get_head.py b/test_libs/pyspec/eth2spec/test/fork_choice/test_get_head.py
--- a/test_libs/pyspec/eth2spec/test/fork_choice/test_get_head.py
+++ b/test_libs/pyspec/eth2spec/test/fork_choice/test_get_head.py
@@ -4,19 +4,19 @@
 from eth2spec.test.helpers.state import state_transition_and_sign_block
 
 
-def add_block_to_store(spec, store, block):
-    pre_state = store.block_states[block.parent_root]
-    block_time = pre_state.genesis_time + block.slot * spec.SECONDS_PER_SLOT
+def add_block_to_store(spec, store, signed_block):
+    pre_state = store.block_states[signed_block.message.parent_root]
+    block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT
 
     if store.time < block_time:
         spec.on_tick(store, block_time)
 
-    spec.on_block(store, block)
+    spec.on_block(store, signed_block)
 
 
 def add_attestation_to_store(spec, store, attestation):
     parent_block = store.blocks[attestation.data.beacon_block_root]
-    pre_state = store.block_states[spec.signing_root(parent_block)]
+    pre_state = store.block_states[spec.hash_tree_root(parent_block)]
     block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
     next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT
 
@@ -32,7 +32,7 @@ def test_genesis(spec, state):
     # Initialization
     store = spec.get_genesis_store(state)
     genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
-    assert spec.get_head(store) == spec.signing_root(genesis_block)
+    assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
 
 
 @with_all_phases
@@ -41,19 +41,19 @@ def test_chain_no_attestations(spec, state):
     # Initialization
     store = spec.get_genesis_store(state)
     genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
-    assert spec.get_head(store) == spec.signing_root(genesis_block)
+    assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
 
     # On receiving a block of `GENESIS_SLOT + 1` slot
     block_1 = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block_1)
-    add_block_to_store(spec, store, block_1)
+    signed_block_1 = state_transition_and_sign_block(spec, state, block_1)
+    add_block_to_store(spec, store, signed_block_1)
 
     # On receiving a block of next epoch
     block_2 = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block_2)
-    add_block_to_store(spec, store, block_2)
+    signed_block_2 = state_transition_and_sign_block(spec, state, block_2)
+    add_block_to_store(spec, store, signed_block_2)
 
-    assert spec.get_head(store) == spec.signing_root(block_2)
+    assert spec.get_head(store) == spec.hash_tree_root(block_2)
 
 
 @with_all_phases
@@ -64,22 +64,22 @@ def test_split_tie_breaker_no_attestations(spec, state):
     # Initialization
     store = spec.get_genesis_store(state)
     genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
-    assert spec.get_head(store) == spec.signing_root(genesis_block)
+    assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
 
     # block at slot 1
     block_1_state = genesis_state.copy()
     block_1 = build_empty_block_for_next_slot(spec, block_1_state)
-    state_transition_and_sign_block(spec, block_1_state, block_1)
-    add_block_to_store(spec, store, block_1)
+    signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1)
+    add_block_to_store(spec, store, signed_block_1)
 
     # additional block at slot 1
     block_2_state = genesis_state.copy()
     block_2 = build_empty_block_for_next_slot(spec, block_2_state)
     block_2.body.graffiti = b'\x42' * 32
-    state_transition_and_sign_block(spec, block_2_state, block_2)
-    add_block_to_store(spec, store, block_2)
+    signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2)
+    add_block_to_store(spec, store, signed_block_2)
 
-    highest_root = max(spec.signing_root(block_1), spec.signing_root(block_2))
+    highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
 
     assert spec.get_head(store) == highest_root
 
@@ -92,23 +92,23 @@ def test_shorter_chain_but_heavier_weight(spec, state):
     # Initialization
     store = spec.get_genesis_store(state)
     genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
-    assert spec.get_head(store) == spec.signing_root(genesis_block)
+    assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
 
     # build longer tree
     long_state = genesis_state.copy()
     for i in range(3):
         long_block = build_empty_block_for_next_slot(spec, long_state)
-        state_transition_and_sign_block(spec, long_state, long_block)
-        add_block_to_store(spec, store, long_block)
+        signed_long_block = state_transition_and_sign_block(spec, long_state, long_block)
+        add_block_to_store(spec, store, signed_long_block)
 
     # build short tree
     short_state = genesis_state.copy()
     short_block = build_empty_block_for_next_slot(spec, short_state)
     short_block.body.graffiti = b'\x42' * 32
-    state_transition_and_sign_block(spec, short_state, short_block)
-    add_block_to_store(spec, store, short_block)
+    signed_short_block = state_transition_and_sign_block(spec, short_state, short_block)
+    add_block_to_store(spec, store, signed_short_block)
 
     short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True)
     add_attestation_to_store(spec, store, short_attestation)
 
-    assert spec.get_head(store) == spec.signing_root(short_block)
+    assert spec.get_head(store) == spec.hash_tree_root(short_block)
diff --git a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py
--- a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py
+++ b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_attestation.py
@@ -31,12 +31,12 @@ def test_on_attestation_current_epoch(spec, state):
     spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * 2)
 
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
     # store block in store
-    spec.on_block(store, block)
+    spec.on_block(store, signed_block)
 
-    attestation = get_valid_attestation(spec, state, slot=block.slot)
+    attestation = get_valid_attestation(spec, state, slot=block.slot, signed=True)
     assert attestation.data.target.epoch == spec.GENESIS_EPOCH
     assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == spec.GENESIS_EPOCH
 
@@ -50,12 +50,12 @@ def test_on_attestation_previous_epoch(spec, state):
     spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH)
 
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
     # store block in store
-    spec.on_block(store, block)
+    spec.on_block(store, signed_block)
 
-    attestation = get_valid_attestation(spec, state, slot=block.slot)
+    attestation = get_valid_attestation(spec, state, slot=block.slot, signed=True)
     assert attestation.data.target.epoch == spec.GENESIS_EPOCH
     assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == spec.GENESIS_EPOCH + 1
 
@@ -73,11 +73,11 @@ def test_on_attestation_past_epoch(spec, state):
 
     # create and store block from 3 epochs ago
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
-    spec.on_block(store, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
+    spec.on_block(store, signed_block)
 
     # create attestation for past block
-    attestation = get_valid_attestation(spec, state, slot=state.slot)
+    attestation = get_valid_attestation(spec, state, slot=state.slot, signed=True)
     assert attestation.data.target.epoch == spec.GENESIS_EPOCH
     assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == spec.GENESIS_EPOCH + 2
 
@@ -99,8 +99,8 @@ def test_on_attestation_target_not_in_store(spec, state):
 
     # do not add target block to store
 
-    attestation = get_valid_attestation(spec, state, slot=target_block.slot)
-    assert attestation.data.target.root == target_block.signing_root()
+    attestation = get_valid_attestation(spec, state, slot=target_block.slot, signed=True)
+    assert attestation.data.target.root == target_block.hash_tree_root()
 
     run_on_attestation(spec, state, store, attestation, False)
 
@@ -116,19 +116,19 @@ def test_on_attestation_beacon_block_not_in_store(spec, state):
     transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1)
 
     target_block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, target_block)
+    signed_target_block = state_transition_and_sign_block(spec, state, target_block)
 
     # store target in store
-    spec.on_block(store, target_block)
+    spec.on_block(store, signed_target_block)
 
     head_block = build_empty_block_for_next_slot(spec, state)
     state_transition_and_sign_block(spec, state, head_block)
 
     # do not add head block to store
 
-    attestation = get_valid_attestation(spec, state, slot=head_block.slot)
-    assert attestation.data.target.root == target_block.signing_root()
-    assert attestation.data.beacon_block_root == head_block.signing_root()
+    attestation = get_valid_attestation(spec, state, slot=head_block.slot, signed=True)
+    assert attestation.data.target.root == target_block.hash_tree_root()
+    assert attestation.data.beacon_block_root == head_block.hash_tree_root()
 
     run_on_attestation(spec, state, store, attestation, False)
 
@@ -141,15 +141,15 @@ def test_on_attestation_future_epoch(spec, state):
     spec.on_tick(store, time)
 
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
     # store block in store
-    spec.on_block(store, block)
+    spec.on_block(store, signed_block)
 
     # move state forward but not store
     state.slot = block.slot + spec.SLOTS_PER_EPOCH
 
-    attestation = get_valid_attestation(spec, state, slot=state.slot)
+    attestation = get_valid_attestation(spec, state, slot=state.slot, signed=True)
     run_on_attestation(spec, state, store, attestation, False)
 
 
@@ -161,13 +161,13 @@ def test_on_attestation_future_block(spec, state):
     spec.on_tick(store, time)
 
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    spec.on_block(store, block)
+    spec.on_block(store, signed_block)
 
     # attestation for slot immediately prior to the block being attested to
     attestation = get_valid_attestation(spec, state, slot=block.slot - 1, signed=False)
-    attestation.data.beacon_block_root = block.signing_root()
+    attestation.data.beacon_block_root = block.hash_tree_root()
     sign_attestation(spec, state, attestation)
 
     run_on_attestation(spec, state, store, attestation, False)
@@ -181,11 +181,11 @@ def test_on_attestation_same_slot(spec, state):
     spec.on_tick(store, time)
 
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    spec.on_block(store, block)
+    spec.on_block(store, signed_block)
 
-    attestation = get_valid_attestation(spec, state, slot=block.slot)
+    attestation = get_valid_attestation(spec, state, slot=block.slot, signed=True)
     run_on_attestation(spec, state, store, attestation, False)
 
 
@@ -197,11 +197,11 @@ def test_on_attestation_invalid_attestation(spec, state):
     spec.on_tick(store, time)
 
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    spec.on_block(store, block)
+    spec.on_block(store, signed_block)
 
-    attestation = get_valid_attestation(spec, state, slot=block.slot)
+    attestation = get_valid_attestation(spec, state, slot=block.slot, signed=True)
     # make invalid by using an invalid committee index
     attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH
 
diff --git a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py
--- a/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py
+++ b/test_libs/pyspec/eth2spec/test/fork_choice/test_on_block.py
@@ -1,33 +1,35 @@
 from copy import deepcopy
-from eth2spec.utils.ssz.ssz_impl import signing_root
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
 
 from eth2spec.test.context import with_all_phases, spec_state_test
-from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
+from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block, transition_unsigned_block, \
+    build_empty_block
 from eth2spec.test.helpers.state import next_epoch, next_epoch_with_attestations, state_transition_and_sign_block
 
 
-def run_on_block(spec, store, block, valid=True):
+def run_on_block(spec, store, signed_block, valid=True):
     if not valid:
         try:
-            spec.on_block(store, block)
+            spec.on_block(store, signed_block)
         except AssertionError:
             return
         else:
             assert False
 
-    spec.on_block(store, block)
-    assert store.blocks[signing_root(block)] == block
+    spec.on_block(store, signed_block)
+    assert store.blocks[hash_tree_root(signed_block.message)] == signed_block.message
 
 
 def apply_next_epoch_with_attestations(spec, state, store):
-    _, new_blocks, post_state = next_epoch_with_attestations(spec, state, True, False)
-    for block in new_blocks:
-        block_root = signing_root(block)
+    _, new_signed_blocks, post_state = next_epoch_with_attestations(spec, state, True, False)
+    for signed_block in new_signed_blocks:
+        block = signed_block.message
+        block_root = hash_tree_root(block)
         store.blocks[block_root] = block
         store.block_states[block_root] = post_state
-        last_block = block
+        last_signed_block = signed_block
     spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
-    return post_state, store, last_block
+    return post_state, store, last_signed_block
 
 
 @with_all_phases
@@ -41,16 +43,15 @@ def test_basic(spec, state):
 
     # On receiving a block of `GENESIS_SLOT + 1` slot
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
-    run_on_block(spec, store, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
+    run_on_block(spec, store, signed_block)
 
     # On receiving a block of next epoch
     store.time = time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
-    block = build_empty_block_for_next_slot(spec, state)
-    block.slot += spec.SLOTS_PER_EPOCH
-    state_transition_and_sign_block(spec, state, block)
+    block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    run_on_block(spec, store, block)
+    run_on_block(spec, store, signed_block)
 
     # TODO: add tests for justified_root and finalized_root
 
@@ -65,10 +66,10 @@ def test_on_block_checkpoints(spec, state):
 
     next_epoch(spec, state)
     spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
-    state, store, last_block = apply_next_epoch_with_attestations(spec, state, store)
+    state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store)
     next_epoch(spec, state)
     spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
-    last_block_root = signing_root(last_block)
+    last_block_root = hash_tree_root(last_signed_block.message)
 
     # Mock the finalized_checkpoint
     fin_state = store.block_states[last_block_root]
@@ -77,8 +78,8 @@ def test_on_block_checkpoints(spec, state):
     )
 
     block = build_empty_block_for_next_slot(spec, fin_state)
-    state_transition_and_sign_block(spec, deepcopy(fin_state), block)
-    run_on_block(spec, store, block)
+    signed_block = state_transition_and_sign_block(spec, deepcopy(fin_state), block)
+    run_on_block(spec, store, signed_block)
 
 
 @with_all_phases
@@ -91,8 +92,8 @@ def test_on_block_future_block(spec, state):
 
     # Fail receiving block of `GENESIS_SLOT + 1` slot
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
-    run_on_block(spec, store, block, False)
+    signed_block = state_transition_and_sign_block(spec, state, block)
+    run_on_block(spec, store, signed_block, False)
 
 
 @with_all_phases
@@ -105,14 +106,14 @@ def test_on_block_bad_parent_root(spec, state):
 
     # Fail receiving block of `GENESIS_SLOT + 1` slot
     block = build_empty_block_for_next_slot(spec, state)
-    spec.state_transition(state, block)
+    transition_unsigned_block(spec, state, block)
     block.state_root = state.hash_tree_root()
 
     block.parent_root = b'\x45' * 32
 
-    sign_block(spec, state, block)
+    signed_block = sign_block(spec, state, block)
 
-    run_on_block(spec, store, block, False)
+    run_on_block(spec, store, signed_block, False)
 
 
 @with_all_phases
@@ -130,8 +131,8 @@ def test_on_block_before_finalized(spec, state):
 
     # Fail receiving block of `GENESIS_SLOT + 1` slot
     block = build_empty_block_for_next_slot(spec, state)
-    state_transition_and_sign_block(spec, state, block)
-    run_on_block(spec, store, block, False)
+    signed_block = state_transition_and_sign_block(spec, state, block)
+    run_on_block(spec, store, signed_block, False)
 
 
 @with_all_phases
@@ -144,10 +145,10 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state):
 
     next_epoch(spec, state)
     spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
-    state, store, last_block = apply_next_epoch_with_attestations(spec, state, store)
+    state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store)
     next_epoch(spec, state)
     spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
-    last_block_root = signing_root(last_block)
+    last_block_root = hash_tree_root(last_signed_block.message)
 
     # Mock the justified checkpoint
     just_state = store.block_states[last_block_root]
@@ -158,9 +159,9 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state):
     just_state.current_justified_checkpoint = new_justified
 
     block = build_empty_block_for_next_slot(spec, just_state)
-    state_transition_and_sign_block(spec, deepcopy(just_state), block)
+    signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block)
     assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED
-    run_on_block(spec, store, block)
+    run_on_block(spec, store, signed_block)
 
     assert store.justified_checkpoint == new_justified
 
@@ -175,10 +176,10 @@ def test_on_block_outside_safe_slots_and_old_block(spec, state):
 
     next_epoch(spec, state)
     spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
-    state, store, last_block = apply_next_epoch_with_attestations(spec, state, store)
+    state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store)
     next_epoch(spec, state)
     spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
-    last_block_root = signing_root(last_block)
+    last_block_root = hash_tree_root(last_signed_block.message)
 
     # Mock justified block in store
     just_block = build_empty_block_for_next_slot(spec, state)
@@ -195,11 +196,11 @@ def test_on_block_outside_safe_slots_and_old_block(spec, state):
     just_state.current_justified_checkpoint = new_justified
 
     block = build_empty_block_for_next_slot(spec, just_state)
-    state_transition_and_sign_block(spec, deepcopy(just_state), block)
+    signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block)
 
     spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.SECONDS_PER_SLOT)
     assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED
-    run_on_block(spec, store, block)
+    run_on_block(spec, store, signed_block)
 
     assert store.justified_checkpoint != new_justified
     assert store.best_justified_checkpoint == new_justified
diff --git a/test_libs/pyspec/eth2spec/test/helpers/attestations.py b/test_libs/pyspec/eth2spec/test/helpers/attestations.py
--- a/test_libs/pyspec/eth2spec/test/helpers/attestations.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/attestations.py
@@ -1,6 +1,7 @@
 from typing import List
 
-from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
+from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \
+    build_empty_block
 from eth2spec.test.helpers.keys import privkeys
 from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
 from eth2spec.utils.ssz.ssz_typing import Bitlist
@@ -122,10 +123,8 @@ def fill_aggregate_attestation(spec, state, attestation, signed=False):
 
 
 def add_attestations_to_state(spec, state, attestations, slot):
-    block = build_empty_block_for_next_slot(spec, state)
-    block.slot = slot
+    block = build_empty_block(spec, state, slot)
     for attestation in attestations:
         block.body.attestations.append(attestation)
     spec.process_slots(state, block.slot)
-    sign_block(spec, state, block)
-    spec.state_transition(state, block)
+    transition_unsigned_block(spec, state, block)
diff --git a/test_libs/pyspec/eth2spec/test/helpers/block.py b/test_libs/pyspec/eth2spec/test/helpers/block.py
--- a/test_libs/pyspec/eth2spec/test/helpers/block.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/block.py
@@ -2,26 +2,30 @@
 
 from eth2spec.test.helpers.keys import privkeys
 from eth2spec.utils.bls import bls_sign, only_with_bls
-from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
 
 
-# Fully ignore the function if BLS is off, beacon-proposer index calculation is slow.
-@only_with_bls()
-def sign_block(spec, state, block, proposer_index=None):
-    assert state.slot <= block.slot
-
+def get_proposer_index_maybe(spec, state, slot, proposer_index=None):
     if proposer_index is None:
-        if block.slot == state.slot:
+        assert state.slot <= slot
+        if slot == state.slot:
             proposer_index = spec.get_beacon_proposer_index(state)
         else:
-            if spec.compute_epoch_at_slot(state.slot) + 1 > spec.compute_epoch_at_slot(block.slot):
+            if spec.compute_epoch_at_slot(state.slot) + 1 > spec.compute_epoch_at_slot(slot):
                 print("warning: block slot far away, and no proposer index manually given."
                       " Signing block is slow due to transition for proposer index calculation.")
             # use stub state to get proposer index of future slot
             stub_state = deepcopy(state)
-            spec.process_slots(stub_state, block.slot)
+            spec.process_slots(stub_state, slot)
             proposer_index = spec.get_beacon_proposer_index(stub_state)
+    return proposer_index
+
 
+@only_with_bls()
+def apply_randao_reveal(spec, state, block, proposer_index=None):
+    assert state.slot <= block.slot
+
+    proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index)
     privkey = privkeys[proposer_index]
 
     block.body.randao_reveal = bls_sign(
@@ -33,8 +37,18 @@ def sign_block(spec, state, block, proposer_index=None):
             domain_type=spec.DOMAIN_RANDAO,
         )
     )
-    block.signature = bls_sign(
-        message_hash=signing_root(block),
+
+
+# Fully ignore the function if BLS is off, beacon-proposer index calculation is slow.
+@only_with_bls()
+def apply_sig(spec, state, signed_block, proposer_index=None):
+    block = signed_block.message
+
+    proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index)
+    privkey = privkeys[proposer_index]
+
+    signed_block.signature = bls_sign(
+        message_hash=hash_tree_root(block),
         privkey=privkey,
         domain=spec.get_domain(
             state,
@@ -42,17 +56,26 @@ def sign_block(spec, state, block, proposer_index=None):
             spec.compute_epoch_at_slot(block.slot)))
 
 
+def sign_block(spec, state, block, proposer_index=None):
+    signed_block = spec.SignedBeaconBlock(message=block)
+    apply_sig(spec, state, signed_block, proposer_index)
+    return signed_block
+
+
+def transition_unsigned_block(spec, state, block):
+    spec.process_slots(state, block.slot)
+    spec.process_block(state, block)
+
+
 def apply_empty_block(spec, state):
     """
     Transition via an empty block (on current slot, assuming no block has been applied yet).
-    :return: the empty block that triggered the transition.
     """
-    block = build_empty_block(spec, state, signed=True)
-    spec.state_transition(state, block)
-    return block
+    block = build_empty_block(spec, state)
+    transition_unsigned_block(spec, state, block)
 
 
-def build_empty_block(spec, state, slot=None, signed=False):
+def build_empty_block(spec, state, slot=None):
     if slot is None:
         slot = state.slot
     empty_block = spec.BeaconBlock()
@@ -61,13 +84,10 @@ def build_empty_block(spec, state, slot=None, signed=False):
     previous_block_header = deepcopy(state.latest_block_header)
     if previous_block_header.state_root == spec.Root():
         previous_block_header.state_root = state.hash_tree_root()
-    empty_block.parent_root = signing_root(previous_block_header)
-
-    if signed:
-        sign_block(spec, state, empty_block)
-
+    empty_block.parent_root = hash_tree_root(previous_block_header)
+    apply_randao_reveal(spec, state, empty_block)
     return empty_block
 
 
-def build_empty_block_for_next_slot(spec, state, signed=False):
-    return build_empty_block(spec, state, state.slot + 1, signed=signed)
+def build_empty_block_for_next_slot(spec, state):
+    return build_empty_block(spec, state, state.slot + 1)
diff --git a/test_libs/pyspec/eth2spec/test/helpers/block_header.py b/test_libs/pyspec/eth2spec/test/helpers/block_header.py
--- a/test_libs/pyspec/eth2spec/test/helpers/block_header.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/block_header.py
@@ -1,5 +1,5 @@
 from eth2spec.utils.bls import bls_sign
-from eth2spec.utils.ssz.ssz_impl import signing_root
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
 
 
 def sign_block_header(spec, state, header, privkey):
@@ -7,8 +7,8 @@ def sign_block_header(spec, state, header, privkey):
         state=state,
         domain_type=spec.DOMAIN_BEACON_PROPOSER,
     )
-    header.signature = bls_sign(
-        message_hash=signing_root(header),
+    return spec.SignedBeaconBlockHeader(message=header, signature=bls_sign(
+        message_hash=hash_tree_root(header),
         privkey=privkey,
         domain=domain,
-    )
+    ))
diff --git a/test_libs/pyspec/eth2spec/test/helpers/deposits.py b/test_libs/pyspec/eth2spec/test/helpers/deposits.py
--- a/test_libs/pyspec/eth2spec/test/helpers/deposits.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/deposits.py
@@ -1,7 +1,7 @@
 from eth2spec.test.helpers.keys import pubkeys, privkeys
 from eth2spec.utils.bls import bls_sign
 from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof
-from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
 from eth2spec.utils.ssz.ssz_typing import List
 
 
@@ -26,8 +26,12 @@ def sign_deposit_data(spec, deposit_data, privkey, state=None):
             spec.DOMAIN_DEPOSIT,
         )
 
+    deposit_message = spec.DepositMessage(
+        pubkey=deposit_data.pubkey,
+        withdrawal_credentials=deposit_data.withdrawal_credentials,
+        amount=deposit_data.amount)
     signature = bls_sign(
-        message_hash=signing_root(deposit_data),
+        message_hash=hash_tree_root(deposit_message),
         privkey=privkey,
         domain=domain,
     )
diff --git a/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py
--- a/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py
@@ -6,7 +6,7 @@
     only_with_bls,
 )
 from eth2spec.utils.ssz.ssz_impl import (
-    signing_root,
+    hash_tree_root,
 )
 
 from .attestations import (
@@ -22,7 +22,7 @@ def sign_shard_block(spec, beacon_state, shard_state, block, proposer_index=None
     privkey = privkeys[proposer_index]
 
     block.signature = bls_sign(
-        message_hash=signing_root(block),
+        message_hash=hash_tree_root(block),
         privkey=privkey,
         domain=spec.get_domain(
             beacon_state,
@@ -44,12 +44,12 @@ def build_empty_shard_block(spec,
     previous_beacon_header = deepcopy(beacon_state.latest_block_header)
     if previous_beacon_header.state_root == spec.Bytes32():
         previous_beacon_header.state_root = beacon_state.hash_tree_root()
-    beacon_block_root = spec.signing_root(previous_beacon_header)
+    beacon_block_root = hash_tree_root(previous_beacon_header)
 
     previous_block_header = deepcopy(shard_state.latest_block_header)
     if previous_block_header.state_root == spec.Bytes32():
         previous_block_header.state_root = shard_state.hash_tree_root()
-    parent_root = signing_root(previous_block_header)
+    parent_root = hash_tree_root(previous_block_header)
 
     block = spec.ShardBlock(
         shard=shard_state.shard,
diff --git a/test_libs/pyspec/eth2spec/test/helpers/proposer_slashings.py b/test_libs/pyspec/eth2spec/test/helpers/proposer_slashings.py
--- a/test_libs/pyspec/eth2spec/test/helpers/proposer_slashings.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/proposer_slashings.py
@@ -20,12 +20,16 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):
     header_2.parent_root = b'\x99' * 32
 
     if signed_1:
-        sign_block_header(spec, state, header_1, privkey)
+        signed_header_1 = sign_block_header(spec, state, header_1, privkey)
+    else:
+        signed_header_1 = spec.SignedBeaconBlockHeader(message=header_1)
     if signed_2:
-        sign_block_header(spec, state, header_2, privkey)
+        signed_header_2 = sign_block_header(spec, state, header_2, privkey)
+    else:
+        signed_header_2 = spec.SignedBeaconBlockHeader(message=header_2)
 
     return spec.ProposerSlashing(
         proposer_index=validator_index,
-        header_1=header_1,
-        header_2=header_2,
+        signed_header_1=signed_header_1,
+        signed_header_2=signed_header_2,
     )
diff --git a/test_libs/pyspec/eth2spec/test/helpers/state.py b/test_libs/pyspec/eth2spec/test/helpers/state.py
--- a/test_libs/pyspec/eth2spec/test/helpers/state.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/state.py
@@ -1,6 +1,8 @@
 from copy import deepcopy
+
+from eth2spec.test.context import expect_assertion_error
 from eth2spec.test.helpers.attestations import get_valid_attestation
-from eth2spec.test.helpers.block import sign_block, build_empty_block_for_next_slot
+from eth2spec.test.helpers.block import sign_block, build_empty_block_for_next_slot, transition_unsigned_block
 
 
 def get_balance(state, index):
@@ -40,14 +42,17 @@ def get_state_root(spec, state, slot) -> bytes:
     return state.state_roots[slot % spec.SLOTS_PER_HISTORICAL_ROOT]
 
 
-def state_transition_and_sign_block(spec, state, block):
+def state_transition_and_sign_block(spec, state, block, expect_fail=False):
     """
     State transition via the provided ``block``
-    then package the block with the state root and signature.
+    then package the block with the correct state root and signature.
     """
-    spec.state_transition(state, block)
+    if expect_fail:
+        expect_assertion_error(lambda: transition_unsigned_block(spec, state, block))
+    else:
+        transition_unsigned_block(spec, state, block)
     block.state_root = state.hash_tree_root()
-    sign_block(spec, state, block)
+    return sign_block(spec, state, block)
 
 
 def next_epoch_with_attestations(spec,
@@ -57,7 +62,7 @@ def next_epoch_with_attestations(spec,
     assert state.slot % spec.SLOTS_PER_EPOCH == 0
 
     post_state = deepcopy(state)
-    blocks = []
+    signed_blocks = []
     for _ in range(spec.SLOTS_PER_EPOCH):
         block = build_empty_block_for_next_slot(spec, post_state)
         if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
@@ -65,17 +70,17 @@ def next_epoch_with_attestations(spec,
             committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest)
             if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)):
                 for index in range(committees_per_slot):
-                    cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index)
+                    cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True)
                     block.body.attestations.append(cur_attestation)
 
         if fill_prev_epoch:
             slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
             committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest)
             for index in range(committees_per_slot):
-                prev_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index)
+                prev_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True)
                 block.body.attestations.append(prev_attestation)
 
-        state_transition_and_sign_block(spec, post_state, block)
-        blocks.append(block)
+        signed_block = state_transition_and_sign_block(spec, post_state, block)
+        signed_blocks.append(signed_block)
 
-    return state, blocks, post_state
+    return state, signed_blocks, post_state
diff --git a/test_libs/pyspec/eth2spec/test/helpers/voluntary_exits.py b/test_libs/pyspec/eth2spec/test/helpers/voluntary_exits.py
--- a/test_libs/pyspec/eth2spec/test/helpers/voluntary_exits.py
+++ b/test_libs/pyspec/eth2spec/test/helpers/voluntary_exits.py
@@ -1,24 +1,17 @@
 from eth2spec.utils.bls import bls_sign
-from eth2spec.utils.ssz.ssz_impl import signing_root
-
-
-def build_voluntary_exit(spec, state, epoch, validator_index, privkey, signed=False):
-    voluntary_exit = spec.VoluntaryExit(
-        epoch=epoch,
-        validator_index=validator_index,
-    )
-    if signed:
-        sign_voluntary_exit(spec, state, voluntary_exit, privkey)
-    return voluntary_exit
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
 
 
 def sign_voluntary_exit(spec, state, voluntary_exit, privkey):
-    voluntary_exit.signature = bls_sign(
-        message_hash=signing_root(voluntary_exit),
-        privkey=privkey,
-        domain=spec.get_domain(
-            state=state,
-            domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
-            message_epoch=voluntary_exit.epoch,
+    return spec.SignedVoluntaryExit(
+        message=voluntary_exit,
+        signature=bls_sign(
+            message_hash=hash_tree_root(voluntary_exit),
+            privkey=privkey,
+            domain=spec.get_domain(
+                state=state,
+                domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
+                message_epoch=voluntary_exit.epoch,
+            )
         )
     )
diff --git a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py
--- a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py
+++ b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py
@@ -1,10 +1,7 @@
 from copy import deepcopy
 
-from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
-from eth2spec.test.helpers.block import (
-    build_empty_block_for_next_slot,
-    sign_block
-)
+from eth2spec.test.context import spec_state_test, expect_assertion_error, with_all_phases
+from eth2spec.test.helpers.block import build_empty_block_for_next_slot
 from eth2spec.test.helpers.state import next_slot
 
 
@@ -37,16 +34,8 @@ def run_block_header_processing(spec, state, block, valid=True):
 @with_all_phases
 @spec_state_test
 def test_success_block_header(spec, state):
-    block = build_empty_block_for_next_slot(spec, state, signed=True)
-    yield from run_block_header_processing(spec, state, block)
-
-
-@with_all_phases
-@spec_state_test
-@always_bls
-def test_invalid_sig_block_header(spec, state):
     block = build_empty_block_for_next_slot(spec, state)
-    yield from run_block_header_processing(spec, state, block, valid=False)
+    yield from run_block_header_processing(spec, state, block)
 
 
 @with_all_phases
@@ -54,7 +43,6 @@ def test_invalid_sig_block_header(spec, state):
 def test_invalid_slot_block_header(spec, state):
     block = build_empty_block_for_next_slot(spec, state)
     block.slot = state.slot + 2  # invalid slot
-    sign_block(spec, state, block)
 
     yield from run_block_header_processing(spec, state, block, valid=False)
 
@@ -64,7 +52,6 @@ def test_invalid_slot_block_header(spec, state):
 def test_invalid_parent_root(spec, state):
     block = build_empty_block_for_next_slot(spec, state)
     block.parent_root = b'\12' * 32  # invalid prev root
-    sign_block(spec, state, block)
 
     yield from run_block_header_processing(spec, state, block, valid=False)
 
@@ -80,6 +67,6 @@ def test_proposer_slashed(spec, state):
     # set proposer to slashed
     state.validators[proposer_index].slashed = True
 
-    block = build_empty_block_for_next_slot(spec, state, signed=True)
+    block = build_empty_block_for_next_slot(spec, state)
 
     yield from run_block_header_processing(spec, state, block, valid=False)
diff --git a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py
--- a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py
+++ b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py
@@ -88,8 +88,10 @@ def test_epochs_are_different(spec, state):
     proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False)
 
     # set slots to be in different epochs
-    proposer_slashing.header_2.slot += spec.SLOTS_PER_EPOCH
-    sign_block_header(spec, state, proposer_slashing.header_2, privkeys[proposer_slashing.proposer_index])
+    header_2 = proposer_slashing.signed_header_2.message
+    header_2.slot += spec.SLOTS_PER_EPOCH
+    proposer_slashing.signed_header_2 = sign_block_header(
+        spec, state, header_2, privkeys[proposer_slashing.proposer_index])
 
     yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
 
@@ -100,7 +102,7 @@ def test_headers_are_same(spec, state):
     proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False)
 
     # set headers to be the same
-    proposer_slashing.header_2 = proposer_slashing.header_1
+    proposer_slashing.signed_header_2 = proposer_slashing.signed_header_1
 
     yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
 
diff --git a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py
--- a/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py
+++ b/test_libs/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py
@@ -1,9 +1,9 @@
 from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
 from eth2spec.test.helpers.keys import pubkey_to_privkey
-from eth2spec.test.helpers.voluntary_exits import build_voluntary_exit, sign_voluntary_exit
+from eth2spec.test.helpers.voluntary_exits import sign_voluntary_exit
 
 
-def run_voluntary_exit_processing(spec, state, voluntary_exit, valid=True):
+def run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=True):
     """
     Run ``process_voluntary_exit``, yielding:
       - pre-state ('pre')
@@ -11,19 +11,19 @@ def run_voluntary_exit_processing(spec, state, voluntary_exit, valid=True):
       - post-state ('post').
     If ``valid == False``, run expecting ``AssertionError``
     """
-    validator_index = voluntary_exit.validator_index
+    validator_index = signed_voluntary_exit.message.validator_index
 
     yield 'pre', state
-    yield 'voluntary_exit', voluntary_exit
+    yield 'voluntary_exit', signed_voluntary_exit
 
     if not valid:
-        expect_assertion_error(lambda: spec.process_voluntary_exit(state, voluntary_exit))
+        expect_assertion_error(lambda: spec.process_voluntary_exit(state, signed_voluntary_exit))
         yield 'post', None
         return
 
     pre_exit_epoch = state.validators[validator_index].exit_epoch
 
-    spec.process_voluntary_exit(state, voluntary_exit)
+    spec.process_voluntary_exit(state, signed_voluntary_exit)
 
     yield 'post', state
 
@@ -41,9 +41,10 @@ def test_success(spec, state):
     validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
     privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
 
-    voluntary_exit = build_voluntary_exit(spec, state, current_epoch, validator_index, privkey, signed=True)
+    signed_voluntary_exit = sign_voluntary_exit(
+        spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey)
 
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit)
 
 
 @with_all_phases
@@ -55,11 +56,14 @@ def test_invalid_signature(spec, state):
 
     current_epoch = spec.get_current_epoch(state)
     validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
-    privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
 
-    voluntary_exit = build_voluntary_exit(spec, state, current_epoch, validator_index, privkey)
+    voluntary_exit = spec.VoluntaryExit(
+        epoch=current_epoch,
+        validator_index=validator_index,
+    )
+    signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, 12345)
 
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit, False)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
 
 
 @with_all_phases
@@ -77,14 +81,11 @@ def test_success_exit_queue(spec, state):
     exit_queue = []
     for index in initial_indices:
         privkey = pubkey_to_privkey[state.validators[index].pubkey]
-        exit_queue.append(build_voluntary_exit(
-            spec,
-            state,
-            current_epoch,
-            index,
-            privkey,
-            signed=True,
-        ))
+
+        signed_voluntary_exit = sign_voluntary_exit(
+            spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=index), privkey)
+
+        exit_queue.append(signed_voluntary_exit)
 
     # Now run all the exits
     for voluntary_exit in exit_queue:
@@ -95,18 +96,13 @@ def test_success_exit_queue(spec, state):
     # exit an additional validator
     validator_index = spec.get_active_validator_indices(state, current_epoch)[-1]
     privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
-    voluntary_exit = build_voluntary_exit(
-        spec,
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-        signed=True,
-    )
+
+    signed_voluntary_exit = sign_voluntary_exit(
+        spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey)
 
     # This is the interesting part of the test: on a pre-state with a full exit queue,
     #  when processing an additional exit, it results in an exit in a later epoch
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit)
 
     assert (
         state.validators[validator_index].exit_epoch ==
@@ -124,18 +120,13 @@ def test_validator_exit_in_future(spec, state):
     validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
     privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
 
-    voluntary_exit = build_voluntary_exit(
-        spec,
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-        signed=False,
+    voluntary_exit = spec.VoluntaryExit(
+        epoch=current_epoch + 1,
+        validator_index=validator_index,
     )
-    voluntary_exit.epoch += 1
-    sign_voluntary_exit(spec, state, voluntary_exit, privkey)
+    signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, privkey)
 
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit, False)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
 
 
 @with_all_phases
@@ -148,18 +139,13 @@ def test_validator_invalid_validator_index(spec, state):
     validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
     privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
 
-    voluntary_exit = build_voluntary_exit(
-        spec,
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-        signed=False,
+    voluntary_exit = spec.VoluntaryExit(
+        epoch=current_epoch,
+        validator_index=len(state.validators),
     )
-    voluntary_exit.validator_index = len(state.validators)
-    sign_voluntary_exit(spec, state, voluntary_exit, privkey)
+    signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, privkey)
 
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit, False)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
 
 
 @with_all_phases
@@ -171,17 +157,10 @@ def test_validator_not_active(spec, state):
 
     state.validators[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
 
-    # build and test voluntary exit
-    voluntary_exit = build_voluntary_exit(
-        spec,
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-        signed=True,
-    )
+    signed_voluntary_exit = sign_voluntary_exit(
+        spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey)
 
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit, False)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
 
 
 @with_all_phases
@@ -197,16 +176,10 @@ def test_validator_already_exited(spec, state):
     # but validator already has exited
     state.validators[validator_index].exit_epoch = current_epoch + 2
 
-    voluntary_exit = build_voluntary_exit(
-        spec,
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-        signed=True,
-    )
+    signed_voluntary_exit = sign_voluntary_exit(
+        spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey)
 
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit, False)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
 
 
 @with_all_phases
@@ -216,18 +189,12 @@ def test_validator_not_active_long_enough(spec, state):
     validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
     privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
 
-    voluntary_exit = build_voluntary_exit(
-        spec,
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-        signed=True,
-    )
+    signed_voluntary_exit = sign_voluntary_exit(
+        spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey)
 
     assert (
         current_epoch - state.validators[validator_index].activation_epoch <
         spec.PERSISTENT_COMMITTEE_PERIOD
     )
 
-    yield from run_voluntary_exit_processing(spec, state, voluntary_exit, False)
+    yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False)
diff --git a/test_libs/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py b/test_libs/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py
--- a/test_libs/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py
+++ b/test_libs/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py
@@ -140,7 +140,7 @@ def test_skipped_slots(spec, state):
     assert shard_state.slot == block.slot
     latest_block_header = deepcopy(shard_state.latest_block_header)
     latest_block_header.state_root = shard_state.hash_tree_root()
-    assert latest_block_header.signing_root() == block.signing_root()
+    assert latest_block_header.hash_tree_root() == block.hash_tree_root()
 
 
 @with_all_phases_except(['phase0'])
diff --git a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py
--- a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py
+++ b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py
@@ -1,17 +1,18 @@
 from copy import deepcopy
 
-from eth2spec.utils.ssz.ssz_impl import signing_root
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
 from eth2spec.utils.bls import bls_sign
 
 from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block
-from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block
+from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block, \
+    transition_unsigned_block
 from eth2spec.test.helpers.keys import privkeys, pubkeys
 from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
 from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
 from eth2spec.test.helpers.attestations import get_valid_attestation
 from eth2spec.test.helpers.deposits import prepare_state_and_deposit
 
-from eth2spec.test.context import spec_state_test, with_all_phases, expect_assertion_error
+from eth2spec.test.context import spec_state_test, with_all_phases, expect_assertion_error, always_bls
 
 
 @with_all_phases
@@ -20,13 +21,18 @@ def test_prev_slot_block_transition(spec, state):
     # Go to clean slot
     spec.process_slots(state, state.slot + 1)
     # Make a block for it
-    block = build_empty_block(spec, state, slot=state.slot, signed=True)
+    block = build_empty_block(spec, state, slot=state.slot)
+    proposer_index = spec.get_beacon_proposer_index(state)
     # Transition to next slot, above block will not be invalid on top of new state.
     spec.process_slots(state, state.slot + 1)
 
     yield 'pre', state
-    expect_assertion_error(lambda: state_transition_and_sign_block(spec, state, block))
-    yield 'blocks', [block]
+    # State is beyond block slot, but the block can still be realistic when invalid.
+    # Try the transition, and update the state root to where it is halted. Then sign with the supposed proposer.
+    expect_assertion_error(lambda: transition_unsigned_block(spec, state, block))
+    block.state_root = state.hash_tree_root()
+    signed_block = sign_block(spec, state, block, proposer_index=proposer_index)
+    yield 'blocks', [signed_block]
     yield 'post', None
 
 
@@ -36,13 +42,13 @@ def test_same_slot_block_transition(spec, state):
     # Same slot on top of pre-state, but move out of slot 0 first.
     spec.process_slots(state, state.slot + 1)
 
-    block = build_empty_block(spec, state, slot=state.slot, signed=True)
+    block = build_empty_block(spec, state, slot=state.slot)
 
     yield 'pre', state
 
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
 
@@ -54,15 +60,15 @@ def test_empty_block_transition(spec, state):
 
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(spec, state, signed=True)
+    block = build_empty_block_for_next_slot(spec, state)
 
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert len(state.eth1_data_votes) == pre_eth1_votes + 1
-    assert spec.get_block_root_at_slot(state, pre_slot) == block.parent_root
+    assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root
     assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Bytes32()
 
 
@@ -73,28 +79,58 @@ def test_invalid_state_root(spec, state):
 
     block = build_empty_block_for_next_slot(spec, state)
     block.state_root = b"\xaa" * 32
-    sign_block(spec, state, block)
+    signed_block = sign_block(spec, state, block)
 
-    expect_assertion_error(
-        lambda: spec.state_transition(state, block, validate_state_root=True))
+    expect_assertion_error(lambda: spec.state_transition(state, signed_block))
 
     yield 'blocks', [block]
     yield 'post', None
 
 
+@with_all_phases
+@spec_state_test
+@always_bls
+def test_zero_block_sig(spec, state):
+    block = build_empty_block_for_next_slot(spec, state)
+    invalid_signed_block = spec.SignedBeaconBlock(message=block)
+    expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
+
+    yield 'blocks', [invalid_signed_block]
+    yield 'post', None
+
+
+@with_all_phases
+@spec_state_test
+@always_bls
+def test_invalid_block_sig(spec, state):
+    block = build_empty_block_for_next_slot(spec, state)
+    invalid_signed_block = spec.SignedBeaconBlock(
+        message=block,
+        signature=bls_sign(
+            message_hash=hash_tree_root(block),
+            privkey=123456,
+            domain=spec.get_domain(
+                state,
+                spec.DOMAIN_BEACON_PROPOSER,
+                spec.compute_epoch_at_slot(block.slot)))
+    )
+    expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
+
+    yield 'blocks', [invalid_signed_block]
+    yield 'post', None
+
+
 @with_all_phases
 @spec_state_test
 def test_skipped_slots(spec, state):
     pre_slot = state.slot
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(spec, state)
-    block.slot += 3
-    sign_block(spec, state, block)
+    block = build_empty_block(spec, state, state.slot + 4)
 
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert state.slot == block.slot
@@ -109,13 +145,11 @@ def test_empty_epoch_transition(spec, state):
     pre_slot = state.slot
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(spec, state)
-    block.slot += spec.SLOTS_PER_EPOCH
-    sign_block(spec, state, block)
+    block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
 
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert state.slot == block.slot
@@ -136,10 +170,10 @@ def test_empty_epoch_transition_not_finalizing(spec, state):
     yield 'pre', state
 
     spec.process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH * 5))
-    block = build_empty_block_for_next_slot(spec, state, signed=True)
-    state_transition_and_sign_block(spec, state, block)
+    block = build_empty_block_for_next_slot(spec, state)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert state.slot == block.slot
@@ -165,11 +199,10 @@ def test_proposer_slashing(spec, state):
     #
     block = build_empty_block_for_next_slot(spec, state)
     block.body.proposer_slashings.append(proposer_slashing)
-    sign_block(spec, state, block)
 
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     # check if slashed
@@ -199,11 +232,10 @@ def test_attester_slashing(spec, state):
     #
     block = build_empty_block_for_next_slot(spec, state)
     block.body.attester_slashings.append(attester_slashing)
-    sign_block(spec, state, block)
 
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     slashed_validator = state.validators[validator_index]
@@ -229,17 +261,9 @@ def test_expected_deposit_in_block(spec, state):
     yield 'pre', state
 
     block = build_empty_block_for_next_slot(spec, state)
-    sign_block(spec, state, block)
-    bad = False
-    try:
-        state_transition_and_sign_block(spec, state, block)
-        bad = True
-    except AssertionError:
-        pass
-    if bad:
-        raise AssertionError("expected deposit was not enforced")
+    signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', None
 
 
@@ -257,11 +281,9 @@ def test_deposit_in_block(spec, state):
 
     block = build_empty_block_for_next_slot(spec, state)
     block.body.deposits.append(deposit)
-    sign_block(spec, state, block)
-
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert len(state.validators) == initial_registry_len + 1
@@ -285,11 +307,10 @@ def test_deposit_top_up(spec, state):
 
     block = build_empty_block_for_next_slot(spec, state)
     block.body.deposits.append(deposit)
-    sign_block(spec, state, block)
 
-    state_transition_and_sign_block(spec, state, block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert len(state.validators) == initial_registry_len
@@ -308,23 +329,19 @@ def test_attestation(spec, state):
 
     # Add to state via block transition
     pre_current_attestations_len = len(state.current_epoch_attestations)
-    attestation_block = build_empty_block_for_next_slot(spec, state)
-    attestation_block.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+    attestation_block = build_empty_block(spec, state, state.slot + 1 + spec.MIN_ATTESTATION_INCLUSION_DELAY)
     attestation_block.body.attestations.append(attestation)
-    sign_block(spec, state, attestation_block)
-    state_transition_and_sign_block(spec, state, attestation_block)
+    signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block)
 
     assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
 
     # Epoch transition should move to previous_epoch_attestations
     pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations)
 
-    epoch_block = build_empty_block_for_next_slot(spec, state)
-    epoch_block.slot += spec.SLOTS_PER_EPOCH
-    sign_block(spec, state, epoch_block)
-    state_transition_and_sign_block(spec, state, epoch_block)
+    epoch_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
+    signed_epoch_block = state_transition_and_sign_block(spec, state, epoch_block)
 
-    yield 'blocks', [attestation_block, epoch_block]
+    yield 'blocks', [signed_attestation_block, signed_epoch_block]
     yield 'post', state
 
     assert len(state.current_epoch_attestations) == 0
@@ -348,30 +365,30 @@ def test_voluntary_exit(spec, state):
         epoch=spec.get_current_epoch(state),
         validator_index=validator_index,
     )
-    voluntary_exit.signature = bls_sign(
-        message_hash=signing_root(voluntary_exit),
-        privkey=privkeys[validator_index],
-        domain=spec.get_domain(
-            state=state,
-            domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
+    signed_voluntary_exit = spec.SignedVoluntaryExit(
+        message=voluntary_exit,
+        signature=bls_sign(
+            message_hash=hash_tree_root(voluntary_exit),
+            privkey=privkeys[validator_index],
+            domain=spec.get_domain(
+                state=state,
+                domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
+            )
         )
     )
 
     # Add to state via block transition
     initiate_exit_block = build_empty_block_for_next_slot(spec, state)
-    initiate_exit_block.body.voluntary_exits.append(voluntary_exit)
-    sign_block(spec, state, initiate_exit_block)
-    state_transition_and_sign_block(spec, state, initiate_exit_block)
+    initiate_exit_block.body.voluntary_exits.append(signed_voluntary_exit)
+    signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block)
 
     assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
 
     # Process within epoch transition
-    exit_block = build_empty_block_for_next_slot(spec, state)
-    exit_block.slot += spec.SLOTS_PER_EPOCH
-    sign_block(spec, state, exit_block)
-    state_transition_and_sign_block(spec, state, exit_block)
+    exit_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
+    signed_exit_block = state_transition_and_sign_block(spec, state, exit_block)
 
-    yield 'blocks', [initiate_exit_block, exit_block]
+    yield 'blocks', [signed_initiate_exit_block, signed_exit_block]
     yield 'post', state
 
     assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
@@ -391,12 +408,10 @@ def test_balance_driven_status_transitions(spec, state):
     yield 'pre', state
 
     # trigger epoch transition
-    block = build_empty_block_for_next_slot(spec, state)
-    block.slot += spec.SLOTS_PER_EPOCH
-    sign_block(spec, state, block)
-    state_transition_and_sign_block(spec, state, block)
+    block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
@@ -410,11 +425,10 @@ def test_historical_batch(spec, state):
 
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(spec, state, signed=True)
-    sign_block(spec, state, block)
-    state_transition_and_sign_block(spec, state, block)
+    block = build_empty_block_for_next_slot(spec, state)
+    signed_block = state_transition_and_sign_block(spec, state, block)
 
-    yield 'blocks', [block]
+    yield 'blocks', [signed_block]
     yield 'post', state
 
     assert state.slot == block.slot
@@ -430,7 +444,6 @@ def test_eth1_data_votes_consensus(spec, state):
         return
 
     offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
-    sign_block(spec, state, offset_block)
     state_transition_and_sign_block(spec, state, offset_block)
     yield 'pre', state
 
@@ -444,9 +457,8 @@ def test_eth1_data_votes_consensus(spec, state):
         block = build_empty_block_for_next_slot(spec, state)
         # wait for over 50% for A, then start voting B
         block.body.eth1_data.block_hash = b if i * 2 > spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
-        sign_block(spec, state, block)
-        state_transition_and_sign_block(spec, state, block)
-        blocks.append(block)
+        signed_block = state_transition_and_sign_block(spec, state, block)
+        blocks.append(signed_block)
 
     assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
     assert state.eth1_data.block_hash == a
@@ -454,9 +466,8 @@ def test_eth1_data_votes_consensus(spec, state):
     # transition to next eth1 voting period
     block = build_empty_block_for_next_slot(spec, state)
     block.body.eth1_data.block_hash = c
-    sign_block(spec, state, block)
-    state_transition_and_sign_block(spec, state, block)
-    blocks.append(block)
+    signed_block = state_transition_and_sign_block(spec, state, block)
+    blocks.append(signed_block)
 
     yield 'blocks', blocks
     yield 'post', state
@@ -477,7 +488,6 @@ def test_eth1_data_votes_no_consensus(spec, state):
     pre_eth1_hash = state.eth1_data.block_hash
 
     offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
-    sign_block(spec, state, offset_block)
     state_transition_and_sign_block(spec, state, offset_block)
     yield 'pre', state
 
@@ -490,9 +500,8 @@ def test_eth1_data_votes_no_consensus(spec, state):
         block = build_empty_block_for_next_slot(spec, state)
         # wait for precisely 50% for A, then start voting B for other 50%
         block.body.eth1_data.block_hash = b if i * 2 >= spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
-        sign_block(spec, state, block)
-        state_transition_and_sign_block(spec, state, block)
-        blocks.append(block)
+        signed_block = state_transition_and_sign_block(spec, state, block)
+        blocks.append(signed_block)
 
     assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
     assert state.eth1_data.block_hash == pre_eth1_hash
diff --git a/test_libs/pyspec/eth2spec/utils/ssz/ssz_impl.py b/test_libs/pyspec/eth2spec/utils/ssz/ssz_impl.py
--- a/test_libs/pyspec/eth2spec/utils/ssz/ssz_impl.py
+++ b/test_libs/pyspec/eth2spec/utils/ssz/ssz_impl.py
@@ -155,10 +155,3 @@ def hash_tree_root(obj: SSZValue):
         return mix_in_length(merkleize_chunks(leaves, limit=chunk_count(obj.type())), len(obj))
     else:
         return merkleize_chunks(leaves)
-
-
-def signing_root(obj: Container):
-    # ignore last field
-    fields = [field for field in obj][:-1]
-    leaves = [hash_tree_root(f) for f in fields]
-    return merkleize_chunks(chunkify(b''.join(leaves)))
diff --git a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py
--- a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py
+++ b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py
@@ -156,10 +156,6 @@ def hash_tree_root(self):
         from .ssz_impl import hash_tree_root
         return hash_tree_root(self)
 
-    def signing_root(self):
-        from .ssz_impl import signing_root
-        return signing_root(self)
-
     def __setattr__(self, name, value):
         if name not in self.__class__.__annotations__:
             raise AttributeError("Cannot change non-existing SSZ-container attribute")