diff --git a/packages/binary/marshalutil/marshalutil.bytes.go b/packages/binary/marshalutil/marshalutil.bytes.go
index 0ee36484ac69805bb70ddb22c7b5b5920acf7ae1..9e58affdf422462de2011713f3b31e93fb588d53 100644
--- a/packages/binary/marshalutil/marshalutil.bytes.go
+++ b/packages/binary/marshalutil/marshalutil.bytes.go
@@ -3,6 +3,10 @@ package marshalutil
 // WriteBytes appends the given bytes to the internal buffer.
 // It returns the same MarshalUtil so calls can be chained.
 func (util *MarshalUtil) WriteBytes(bytes []byte) *MarshalUtil {
+	if bytes == nil {
+		return util
+	}
+
 	writeEndOffset := util.expandWriteCapacity(len(bytes))
 
 	copy(util.bytes[util.writeOffset:writeEndOffset], bytes)
diff --git a/packages/binary/valuetransfer/payload/id.go b/packages/binary/valuetransfer/payload/id.go
index d35da66160d2818026caecc01881b069cae30cfe..313af04e90758de12b082d06967639819845d9f8 100644
--- a/packages/binary/valuetransfer/payload/id.go
+++ b/packages/binary/valuetransfer/payload/id.go
@@ -1,6 +1,7 @@
 package payload
 
 import (
+	"crypto/rand"
 	"fmt"
 
 	"github.com/mr-tron/base58"
@@ -71,6 +72,20 @@ func IdFromBytes(bytes []byte, optionalTargetObject ...*Id) (result Id, err erro
 	return
 }
 
+// Random creates a random id which can for example be used in unit tests.
+func RandomId() (id Id) {
+	// generate a random sequence of bytes
+	idBytes := make([]byte, IdLength)
+	if _, err := rand.Read(idBytes); err != nil {
+		panic(err)
+	}
+
+	// copy the generated bytes into the result
+	copy(id[:], idBytes)
+
+	return
+}
+
 // String returns a base58 encoded version of the payload id.
 func (id Id) String() string {
 	return base58.Encode(id[:])
diff --git a/packages/binary/valuetransfer/tangle/attachment.go b/packages/binary/valuetransfer/tangle/attachment.go
index 033e3f2b2303f266e7dc43c0f7b1320442da12af..9dcfadb462596bd48d54d50977f88ed8faf9f2ca 100644
--- a/packages/binary/valuetransfer/tangle/attachment.go
+++ b/packages/binary/valuetransfer/tangle/attachment.go
@@ -1,15 +1,73 @@
 package tangle
 
 import (
+	"github.com/iotaledger/hive.go/objectstorage"
+	"github.com/iotaledger/hive.go/stringify"
+
+	"github.com/iotaledger/goshimmer/packages/binary/marshalutil"
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload"
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
 )
 
-// Attachment stores the information which transaction was attached by which transaction. We need this to perform
-// reverse lookups for which Payloads contain which Transactions.
+// Attachment stores the information which transaction was attached by which payload. We need this to be able to perform
+// reverse lookups from transactions to their corresponding payloads, that attach them.
 type Attachment struct {
+	objectstorage.StorableObjectFlags
+
 	transactionId transaction.Id
 	payloadId     payload.Id
+
+	storageKey []byte
+}
+
+// NewAttachment creates an attachment object with the given information.
+func NewAttachment(transactionId transaction.Id, payloadId payload.Id) *Attachment {
+	return &Attachment{
+		transactionId: transactionId,
+		payloadId:     payloadId,
+
+		storageKey: marshalutil.New(AttachmentLength).
+			WriteBytes(transactionId.Bytes()).
+			WriteBytes(payloadId.Bytes()).
+			Bytes(),
+	}
+}
+
+// AttachmentFromBytes unmarshals a MissingOutput from a sequence of bytes - it either creates a new object or fills the
+// optionally provided one with the parsed information.
+func AttachmentFromBytes(bytes []byte, optionalTargetObject ...*Attachment) (result *Attachment, err error, consumedBytes int) {
+	// determine the target object that will hold the unmarshaled information
+	switch len(optionalTargetObject) {
+	case 0:
+		result = &Attachment{}
+	case 1:
+		result = optionalTargetObject[0]
+	default:
+		panic("too many arguments in call to AttachmentFromBytes")
+	}
+
+	// parse the bytes
+	marshalUtil := marshalutil.New(bytes)
+	if result.transactionId, err = transaction.ParseId(marshalUtil); err != nil {
+		return
+	}
+	if result.payloadId, err = payload.ParseId(marshalUtil); err != nil {
+		return
+	}
+	consumedBytes = marshalUtil.ReadOffset()
+
+	return
+}
+
+// AttachmentFromStorage gets called when we restore an Attachment from the storage - it parses the key bytes and
+// returns the new object.
+func AttachmentFromStorage(keyBytes []byte) objectstorage.StorableObject {
+	result, err, _ := AttachmentFromBytes(keyBytes)
+	if err != nil {
+		panic(err)
+	}
+
+	return result
 }
 
 // TransactionId returns the transaction id of this Attachment.
@@ -21,3 +79,44 @@ func (attachment *Attachment) TransactionId() transaction.Id {
 func (attachment *Attachment) PayloadId() payload.Id {
 	return attachment.payloadId
 }
+
+// Bytes marshals an Attachment into a sequence of bytes.
+func (attachment *Attachment) Bytes() []byte {
+	return attachment.GetStorageKey()
+}
+
+// String returns a human readable version of the Attachment.
+func (attachment *Attachment) String() string {
+	return stringify.Struct("Attachment",
+		stringify.StructField("transactionId", attachment.TransactionId()),
+		stringify.StructField("payloadId", attachment.PayloadId()),
+	)
+}
+
+// GetStorageKey returns the key that is used to store the object in the database.
+func (attachment *Attachment) GetStorageKey() []byte {
+	return attachment.storageKey
+}
+
+// MarshalBinary marshals the "content part" of an Attachment to a sequence of bytes. Since all of the information for
+// this object are stored in its key, this method does nothing and is only required to conform with the interface.
+func (attachment *Attachment) MarshalBinary() (data []byte, err error) {
+	return
+}
+
+// UnmarshalBinary unmarshals the "content part" of an Attachment from a sequence of bytes. Since all of the information
+// for this object are stored in its key, this method does nothing and is only required to conform with the interface.
+func (attachment *Attachment) UnmarshalBinary(data []byte) (err error) {
+	return
+}
+
+// Update is disabled - updates are supposed to happen through the setters (if existing).
+func (attachment *Attachment) Update(other objectstorage.StorableObject) {
+	panic("update forbidden")
+}
+
+// Interface contract: make compiler warn if the interface is not implemented correctly.
+var _ objectstorage.StorableObject = &Attachment{}
+
+// AttachmentLength holds the length of a marshaled Attachment in bytes.
+const AttachmentLength = transaction.IdLength + payload.IdLength
diff --git a/packages/binary/valuetransfer/tangle/missingoutput.go b/packages/binary/valuetransfer/tangle/missingoutput.go
index 039bc60cc2685e94a5185e18c60e01020d168dec..92b34c383995a0127d5b19f2775630102f4d4633 100644
--- a/packages/binary/valuetransfer/tangle/missingoutput.go
+++ b/packages/binary/valuetransfer/tangle/missingoutput.go
@@ -25,8 +25,8 @@ func NewMissingOutput(outputId transaction.OutputId) *MissingOutput {
 	}
 }
 
-// MissingOutput unmarshals a MissingOutput from a sequence of bytes - it either creates a new object or fills the
-// optionally provided one with the parsed information.
+// MissingOutputFromBytes unmarshals a MissingOutput from a sequence of bytes - it either creates a new object or fills
+// the optionally provided one with the parsed information.
 func MissingOutputFromBytes(bytes []byte, optionalTargetObject ...*MissingOutput) (result *MissingOutput, err error, consumedBytes int) {
 	// determine the target object that will hold the unmarshaled information
 	switch len(optionalTargetObject) {
diff --git a/packages/binary/valuetransfer/tangle/tangle_test.go b/packages/binary/valuetransfer/tangle/tangle_test.go
index cd83c7796dc7d3b5797bb7bcf652cd69798a3c4b..93e35e7d176b6acbff1b41055cb2b8c6267d8727 100644
--- a/packages/binary/valuetransfer/tangle/tangle_test.go
+++ b/packages/binary/valuetransfer/tangle/tangle_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/iotaledger/hive.go/events"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/iotaledger/goshimmer/packages/binary/signature/ed25119"
@@ -18,6 +19,25 @@ import (
 	"github.com/iotaledger/goshimmer/plugins/config"
 )
 
+func TestAttachment(t *testing.T) {
+	transactionId := transaction.RandomId()
+	payloadId := payload.RandomId()
+
+	attachment := NewAttachment(transactionId, payloadId)
+
+	assert.Equal(t, transactionId, attachment.TransactionId())
+	assert.Equal(t, payloadId, attachment.PayloadId())
+
+	clonedAttachment, err, consumedBytes := AttachmentFromBytes(attachment.Bytes())
+	if err != nil {
+		panic(err)
+	}
+
+	assert.Equal(t, AttachmentLength, consumedBytes)
+	assert.Equal(t, transactionId, clonedAttachment.TransactionId())
+	assert.Equal(t, payloadId, clonedAttachment.PayloadId())
+}
+
 func TestTangle_AttachPayload(t *testing.T) {
 	dir, err := ioutil.TempDir("", t.Name())
 	require.NoError(t, err)
diff --git a/packages/binary/valuetransfer/transaction/id.go b/packages/binary/valuetransfer/transaction/id.go
index 396b5d28abda3c24edaa508a603fb4c585fb81f5..99b954bb847782596fc0593a78c146e0a3b412e7 100644
--- a/packages/binary/valuetransfer/transaction/id.go
+++ b/packages/binary/valuetransfer/transaction/id.go
@@ -58,7 +58,7 @@ func ParseId(marshalUtil *marshalutil.MarshalUtil) (Id, error) {
 	}
 }
 
-// Random creates a random address, which can for example be used in unit tests.
+// Random creates a random id which can for example be used in unit tests.
 func RandomId() (id Id) {
 	// generate a random sequence of bytes
 	idBytes := make([]byte, IdLength)