diff --git a/packages/client/address.go b/packages/client/address.go
new file mode 100644
index 0000000000000000000000000000000000000000..9107cd39b3e9448c1eedc9964038885ba8e79985
--- /dev/null
+++ b/packages/client/address.go
@@ -0,0 +1,30 @@
+package client
+
+import (
+	"github.com/iotaledger/iota.go/consts"
+	"github.com/iotaledger/iota.go/trinary"
+)
+
+type Address struct {
+	trytes        trinary.Trytes
+	securityLevel consts.SecurityLevel
+	privateKey    trinary.Trits
+}
+
+func NewAddress(trytes trinary.Trytes) *Address {
+	return &Address{
+		trytes: trytes,
+	}
+}
+
+func (address *Address) GetTrytes() trinary.Trytes {
+	return address.trytes
+}
+
+func (address *Address) GetSecurityLevel() consts.SecurityLevel {
+	return address.securityLevel
+}
+
+func (address *Address) GetPrivateKey() trinary.Trits {
+	return address.privateKey
+}
diff --git a/packages/client/bundle.go b/packages/client/bundle.go
new file mode 100644
index 0000000000000000000000000000000000000000..200cd04aeab315b8aeda071412f066a03eb78ad1
--- /dev/null
+++ b/packages/client/bundle.go
@@ -0,0 +1,41 @@
+package client
+
+import (
+	"github.com/iotaledger/goshimmer/packages/curl"
+	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
+	"github.com/iotaledger/iota.go/trinary"
+)
+
+type Bundle struct {
+	essenceHash  trinary.Trytes
+	transactions []*value_transaction.ValueTransaction
+}
+
+func (bundle *Bundle) GetEssenceHash() trinary.Trytes {
+	return bundle.essenceHash
+}
+
+func (bundle *Bundle) GetTransactions() []*value_transaction.ValueTransaction {
+	return bundle.transactions
+}
+
+func CalculateBundleHash(transactions []*value_transaction.ValueTransaction) trinary.Trytes {
+	var lastInputAddress trinary.Trytes
+
+	var concatenatedBundleEssences = make(trinary.Trits, len(transactions)*value_transaction.BUNDLE_ESSENCE_SIZE)
+	for i, bundleTransaction := range transactions {
+		if bundleTransaction.GetValue() <= 0 {
+			lastInputAddress = bundleTransaction.GetAddress()
+		}
+
+		copy(concatenatedBundleEssences[value_transaction.BUNDLE_ESSENCE_SIZE*i:value_transaction.BUNDLE_ESSENCE_SIZE*(i+1)], bundleTransaction.GetBundleEssence(lastInputAddress != bundleTransaction.GetAddress()))
+	}
+
+	var bundleHash = make(trinary.Trits, 243)
+
+	hasher := curl.NewCurl(243, 81)
+	hasher.Absorb(concatenatedBundleEssences, 0, len(concatenatedBundleEssences))
+	hasher.Squeeze(bundleHash, 0, 243)
+
+	return trinary.MustTritsToTrytes(bundleHash)
+}
diff --git a/packages/client/bundlefactory.go b/packages/client/bundlefactory.go
new file mode 100644
index 0000000000000000000000000000000000000000..e064d76966f36e817d6fc0e070cabefd7b980a74
--- /dev/null
+++ b/packages/client/bundlefactory.go
@@ -0,0 +1,145 @@
+package client
+
+import (
+	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
+	"github.com/iotaledger/iota.go/consts"
+	"github.com/iotaledger/iota.go/converter"
+	"github.com/iotaledger/iota.go/signing"
+	"github.com/iotaledger/iota.go/trinary"
+)
+
+type BundleFactory struct {
+	inputs  []bundleFactoryInputEntry
+	outputs []bundleFactoryOutputEntry
+}
+
+func NewBundleFactory() *BundleFactory {
+	return &BundleFactory{
+		inputs:  make([]bundleFactoryInputEntry, 0),
+		outputs: make([]bundleFactoryOutputEntry, 0),
+	}
+}
+
+func (bundleFactory *BundleFactory) AddInput(address *Address, value int64) {
+	bundleFactory.inputs = append(bundleFactory.inputs, bundleFactoryInputEntry{
+		address: address,
+		value:   value,
+	})
+}
+
+func (bundleFactory *BundleFactory) AddOutput(address *Address, value int64, message ...string) {
+	if len(message) >= 1 {
+		messageTrytes, err := converter.ASCIIToTrytes(message[0])
+		if err != nil {
+			panic(err)
+		}
+
+		bundleFactory.outputs = append(bundleFactory.outputs, bundleFactoryOutputEntry{
+			address: address,
+			value:   value,
+			message: trinary.Pad(messageTrytes, value_transaction.SIGNATURE_MESSAGE_FRAGMENT_SIZE),
+		})
+	} else {
+		bundleFactory.outputs = append(bundleFactory.outputs, bundleFactoryOutputEntry{
+			address: address,
+			value:   value,
+		})
+	}
+}
+
+func (bundleFactory *BundleFactory) GenerateBundle(branchTransactionHash trinary.Trytes, trunkTransactionHash trinary.Trytes) *Bundle {
+	transactions := bundleFactory.generateTransactions()
+
+	bundleHash := bundleFactory.signTransactions(transactions)
+
+	bundleFactory.connectTransactions(transactions, branchTransactionHash, trunkTransactionHash)
+
+	return &Bundle{
+		essenceHash:  bundleHash,
+		transactions: transactions,
+	}
+}
+
+func (bundleFactory *BundleFactory) generateTransactions() []*value_transaction.ValueTransaction {
+	transactions := make([]*value_transaction.ValueTransaction, 0)
+
+	for _, input := range bundleFactory.inputs {
+		transaction := value_transaction.New()
+		transaction.SetValue(input.value)
+		transaction.SetAddress(input.address.trytes)
+
+		transactions = append(transactions, transaction)
+
+		for i := 1; i < int(input.address.securityLevel); i++ {
+			transaction := value_transaction.New()
+			transaction.SetValue(0)
+			transaction.SetAddress(input.address.trytes)
+
+			transactions = append(transactions, transaction)
+		}
+	}
+
+	for _, output := range bundleFactory.outputs {
+		transaction := value_transaction.New()
+		transaction.SetValue(output.value)
+		transaction.SetAddress(output.address.trytes)
+
+		if len(output.message) != 0 {
+			transaction.SetSignatureMessageFragment(output.message)
+		}
+
+		transactions = append(transactions, transaction)
+	}
+
+	transactions[0].SetHead(true)
+	transactions[len(transactions)-1].SetTail(true)
+
+	return transactions
+}
+
+func (bundleFactory *BundleFactory) signTransactions(transactions []*value_transaction.ValueTransaction) trinary.Trytes {
+	bundleHash := CalculateBundleHash(transactions)
+	normalizedBundleHash := signing.NormalizedBundleHash(bundleHash)
+
+	signedTransactions := 0
+	for _, input := range bundleFactory.inputs {
+		securityLevel := input.address.securityLevel
+		privateKey := input.address.privateKey
+
+		for i := 0; i < int(securityLevel); i++ {
+			signedFragTrits, _ := signing.SignatureFragment(
+				normalizedBundleHash[i*consts.HashTrytesSize/3:(i+1)*consts.HashTrytesSize/3],
+				privateKey[i*consts.KeyFragmentLength:(i+1)*consts.KeyFragmentLength],
+			)
+
+			transactions[signedTransactions].SetSignatureMessageFragment(trinary.MustTritsToTrytes(signedFragTrits))
+
+			signedTransactions++
+		}
+	}
+
+	return bundleHash
+}
+
+func (bundleFactory *BundleFactory) connectTransactions(transactions []*value_transaction.ValueTransaction, branchTransactionHash trinary.Trytes, trunkTransactionHash trinary.Trytes) {
+	transactionCount := len(transactions)
+
+	transactions[transactionCount-1].SetBranchTransactionHash(branchTransactionHash)
+	transactions[transactionCount-1].SetTrunkTransactionHash(trunkTransactionHash)
+
+	for i := transactionCount - 2; i >= 0; i-- {
+		transactions[i].SetBranchTransactionHash(branchTransactionHash)
+		transactions[i].SetTrunkTransactionHash(transactions[i+1].GetHash())
+	}
+}
+
+type bundleFactoryInputEntry struct {
+	address *Address
+	value   int64
+}
+
+type bundleFactoryOutputEntry struct {
+	address *Address
+	value   int64
+	message trinary.Trytes
+}
diff --git a/packages/client/options.go b/packages/client/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..da13c8ef3ca33e1788a7b9560e0ade4e46b5f6f6
--- /dev/null
+++ b/packages/client/options.go
@@ -0,0 +1 @@
+package client
diff --git a/packages/client/seed.go b/packages/client/seed.go
new file mode 100644
index 0000000000000000000000000000000000000000..c746f73cdbecfb68b74eab999a7069c11dfd7094
--- /dev/null
+++ b/packages/client/seed.go
@@ -0,0 +1,55 @@
+package client
+
+import (
+	"github.com/iotaledger/iota.go/address"
+	"github.com/iotaledger/iota.go/consts"
+	"github.com/iotaledger/iota.go/signing"
+	"github.com/iotaledger/iota.go/trinary"
+)
+
+type Seed struct {
+	trytes        trinary.Trytes
+	securityLevel consts.SecurityLevel
+	subSeeds      map[uint64]trinary.Trits
+}
+
+func NewSeed(trytes trinary.Trytes, securityLevel consts.SecurityLevel) *Seed {
+	return &Seed{
+		trytes:        trytes,
+		securityLevel: securityLevel,
+		subSeeds:      make(map[uint64]trinary.Trits),
+	}
+}
+
+func (seed *Seed) GetAddress(index uint64) *Address {
+	addressTrytes, err := address.GenerateAddress(seed.trytes, index, seed.securityLevel)
+	if err != nil {
+		panic(err)
+	}
+
+	privateKey, err := signing.Key(seed.GetSubSeed(index), seed.securityLevel)
+	if err != nil {
+		panic(err)
+	}
+
+	return &Address{
+		trytes:        addressTrytes,
+		securityLevel: seed.securityLevel,
+		privateKey:    privateKey,
+	}
+}
+
+func (seed *Seed) GetSubSeed(index uint64) trinary.Trits {
+	subSeed, subSeedExists := seed.subSeeds[index]
+	if !subSeedExists {
+		generatedSubSeed, err := signing.Subseed(seed.trytes, index)
+		if err != nil {
+			panic(err)
+		}
+		subSeed = generatedSubSeed
+
+		seed.subSeeds[index] = subSeed
+	}
+
+	return subSeed
+}
diff --git a/packages/client/valuebundlefactory.go b/packages/client/valuebundlefactory.go
new file mode 100644
index 0000000000000000000000000000000000000000..066c0877629793c1cfd3272153f774827271a630
--- /dev/null
+++ b/packages/client/valuebundlefactory.go
@@ -0,0 +1,26 @@
+package client
+
+import (
+	"github.com/iotaledger/iota.go/consts"
+	"github.com/iotaledger/iota.go/trinary"
+)
+
+type ValueBundleFactory struct {
+	seed          trinary.Trytes
+	securityLevel consts.SecurityLevel
+	seedInputs    map[uint64]uint64
+	seedOutputs   map[uint64]uint64
+}
+
+func New(seed trinary.Trytes, securityLevel consts.SecurityLevel) *ValueBundleFactory {
+	return &ValueBundleFactory{
+		seed:          seed,
+		securityLevel: securityLevel,
+		seedInputs:    make(map[uint64]uint64),
+		seedOutputs:   make(map[uint64]uint64),
+	}
+}
+
+func (factory *ValueBundleFactory) AddInput(addressIndex uint64, value uint64) {
+	factory.seedInputs[addressIndex] = value
+}
diff --git a/packages/curl/batch_hasher.go b/packages/curl/batch_hasher.go
index 8d5592a946e83bb4f0c96cc007ad8a935193132d..ba174808d0e73d417c29d190991ac994f8d6164e 100644
--- a/packages/curl/batch_hasher.go
+++ b/packages/curl/batch_hasher.go
@@ -1,7 +1,6 @@
 package curl
 
 import (
-	"fmt"
 	"strconv"
 
 	"github.com/iotaledger/goshimmer/packages/batchworkerpool"
@@ -40,7 +39,7 @@ func (this *BatchHasher) processHashes(tasks []batchworkerpool.Task) {
 		}
 		bcTrits, err := multiplexer.Extract()
 		if err != nil {
-			fmt.Println(err)
+			panic(err)
 		}
 
 		// calculate the hash
diff --git a/packages/model/value_transaction/value_transaction.go b/packages/model/value_transaction/value_transaction.go
index f0470a401311c2feeac92823044efa02f0a503b9..e4972d271af93578c7d43fb147fddabcdf915ba5 100644
--- a/packages/model/value_transaction/value_transaction.go
+++ b/packages/model/value_transaction/value_transaction.go
@@ -197,22 +197,20 @@ func (this *ValueTransaction) SetTimestamp(timestamp uint) bool {
 	return false
 }
 
-func (this *ValueTransaction) GetBundleEssence() (result trinary.Trits) {
-	this.addressMutex.RLock()
-	this.valueMutex.RLock()
+func (this *ValueTransaction) GetBundleEssence(includeSignatureMessageFragment bool) (result trinary.Trits) {
 	this.signatureMessageFragmentMutex.RLock()
 
 	result = make(trinary.Trits, BUNDLE_ESSENCE_SIZE)
 
+	this.addressMutex.RLock()
 	copy(result[0:], this.trits[ADDRESS_OFFSET:VALUE_END])
+	this.addressMutex.RUnlock()
 
-	if this.GetValue() >= 0 {
+	if includeSignatureMessageFragment {
 		copy(result[VALUE_END:], this.trits[SIGNATURE_MESSAGE_FRAGMENT_OFFSET:SIGNATURE_MESSAGE_FRAGMENT_END])
 	}
 
 	this.signatureMessageFragmentMutex.RUnlock()
-	this.valueMutex.RUnlock()
-	this.addressMutex.RUnlock()
 
 	return
 }
diff --git a/plugins/bundleprocessor/bundleprocessor.go b/plugins/bundleprocessor/bundleprocessor.go
index 4e7a80ed44de49a3efd4318ba20a0ce1171314b6..a3287b073b78389e113aa654453f758322dabe12 100644
--- a/plugins/bundleprocessor/bundleprocessor.go
+++ b/plugins/bundleprocessor/bundleprocessor.go
@@ -1,6 +1,8 @@
 package bundleprocessor
 
 import (
+	"fmt"
+
 	"github.com/iotaledger/goshimmer/packages/errors"
 	"github.com/iotaledger/goshimmer/packages/model/bundle"
 	"github.com/iotaledger/goshimmer/packages/model/transactionmetadata"
@@ -11,16 +13,18 @@ import (
 )
 
 var workerPool = workerpool.New(func(task workerpool.Task) {
-	if _, err := ProcessSolidBundleHead(task.Param(0).(*value_transaction.ValueTransaction)); err != nil {
+	if err := ProcessSolidBundleHead(task.Param(0).(*value_transaction.ValueTransaction)); err != nil {
 		Events.Error.Trigger(err)
 	}
+
+	task.Return(nil)
 }, workerpool.WorkerCount(WORKER_COUNT), workerpool.QueueSize(2*WORKER_COUNT))
 
 const WORKER_COUNT = 10000
 
-func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction) (*bundle.Bundle, errors.IdentifiableError) {
+func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction) errors.IdentifiableError {
 	// only process the bundle if we didn't process it, yet
-	return tangle.GetBundle(headTransaction.GetHash(), func(headTransactionHash trinary.Trytes) (*bundle.Bundle, errors.IdentifiableError) {
+	_, err := tangle.GetBundle(headTransaction.GetHash(), func(headTransactionHash trinary.Trytes) (*bundle.Bundle, errors.IdentifiableError) {
 		// abort if bundle syntax is wrong
 		if !headTransaction.IsHead() {
 			return nil, ErrProcessBundleFailed.Derive(errors.New("invalid parameter"), "transaction needs to be head of bundle")
@@ -53,7 +57,7 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
 			currentTransactionMetadata.SetBundleHeadHash(headTransactionHash)
 
 			// update value bundle flag
-			if !newBundle.IsValueBundle() && currentTransaction.GetValue() != 0 {
+			if !newBundle.IsValueBundle() && currentTransaction.GetValue() < 0 {
 				newBundle.SetValueBundle(true)
 			}
 
@@ -75,11 +79,16 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
 			// try to iterate to next turn
 			if nextTransaction, err := tangle.GetTransaction(currentTransaction.GetTrunkTransactionHash()); err != nil {
 				return nil, ErrProcessBundleFailed.Derive(err, "failed to retrieve trunk while processing bundle")
+			} else if nextTransaction == nil {
+				fmt.Println(ErrProcessBundleFailed.Derive(errors.New("missing transaction "+currentTransaction.GetTrunkTransactionHash()), "failed to retrieve trunk while processing bundle"))
+				return nil, ErrProcessBundleFailed.Derive(err, "failed to retrieve trunk while processing bundle")
 			} else {
 				currentTransaction = nextTransaction
 			}
 		}
 	})
+
+	return err
 }
 
 func mapTransactionsToTransactionHashes(transactions []*value_transaction.ValueTransaction) (result []trinary.Trytes) {
diff --git a/plugins/bundleprocessor/bundleprocessor_test.go b/plugins/bundleprocessor/bundleprocessor_test.go
index 989c67d55770ab6887ae194758ca3c1a9f08c974..f8966e3d7d805fa4e805e0a1f9f36d97c16fc800 100644
--- a/plugins/bundleprocessor/bundleprocessor_test.go
+++ b/plugins/bundleprocessor/bundleprocessor_test.go
@@ -1,51 +1,122 @@
 package bundleprocessor
 
 import (
-	"fmt"
+	"sync"
 	"testing"
 
-	"github.com/iotaledger/goshimmer/packages/node"
-
 	"github.com/iotaledger/goshimmer/packages/events"
 	"github.com/iotaledger/goshimmer/packages/model/bundle"
 	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
+
+	"github.com/iotaledger/goshimmer/plugins/tipselection"
+
+	"github.com/iotaledger/goshimmer/packages/client"
+
+	"github.com/iotaledger/goshimmer/packages/node"
+
 	"github.com/iotaledger/goshimmer/plugins/tangle"
-	"github.com/iotaledger/iota.go/trinary"
+	"github.com/iotaledger/iota.go/consts"
 	"github.com/magiconair/properties/assert"
 )
 
-func TestProcessSolidBundleHead(t *testing.T) {
+var seed = client.NewSeed("YFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCMSJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9Z", consts.SecurityLevelMedium)
+
+func TestValidateSignatures(t *testing.T) {
+	bundleFactory := client.NewBundleFactory()
+	bundleFactory.AddInput(seed.GetAddress(0), -400)
+	bundleFactory.AddOutput(seed.GetAddress(1), 400, "Testmessage")
+	bundleFactory.AddOutput(client.NewAddress("SJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9ZYFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCM"), 400, "Testmessage")
+
+	generatedBundle := bundleFactory.GenerateBundle(tipselection.GetRandomTip(), tipselection.GetRandomTip())
+
+	successfull, err := ValidateSignatures(generatedBundle.GetEssenceHash(), generatedBundle.GetTransactions())
+	if err != nil {
+		t.Error(err)
+	}
+	assert.Equal(t, successfull, true, "validation failed")
+}
+
+func TestProcessSolidBundleHead_Data(t *testing.T) {
 	// show all error messages for tests
-	*node.LOG_LEVEL.Value = node.LOG_LEVEL_DEBUG
+	*node.LOG_LEVEL.Value = node.LOG_LEVEL_FAILURE
 
 	// start a test node
 	node.Start(tangle.PLUGIN, PLUGIN)
 
-	tx := value_transaction.New()
-	tx.SetTail(true)
-	tx.SetValue(3)
+	bundleFactory := client.NewBundleFactory()
+	bundleFactory.AddOutput(seed.GetAddress(1), 400, "Testmessage")
+	bundleFactory.AddOutput(client.NewAddress("SJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9ZYFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCM"), 400, "Testmessage")
 
-	tx1 := value_transaction.New()
-	tx1.SetTrunkTransactionHash(tx.GetHash())
-	tx1.SetHead(true)
+	generatedBundle := bundleFactory.GenerateBundle(tipselection.GetRandomTip(), tipselection.GetRandomTip())
 
-	tangle.StoreTransaction(tx)
-	tangle.StoreTransaction(tx1)
+	for _, transaction := range generatedBundle.GetTransactions() {
+		tangle.StoreTransaction(transaction)
+	}
 
-	Events.BundleSolid.Attach(events.NewClosure(func(bundle *bundle.Bundle, transactions []*value_transaction.ValueTransaction) {
-		fmt.Println("IT HAPPENED")
-		fmt.Println(bundle.GetHash())
-		fmt.Println(bundle.GetBundleEssenceHash())
-	}))
+	var wg sync.WaitGroup
 
-	result, err := ProcessSolidBundleHead(tx1)
-	if err != nil {
+	testResults := events.NewClosure(func(bundle *bundle.Bundle, transactions []*value_transaction.ValueTransaction) {
+		assert.Equal(t, bundle.GetHash(), generatedBundle.GetTransactions()[0].GetHash(), "invalid bundle hash")
+		assert.Equal(t, bundle.IsValueBundle(), false, "invalid value bundle status")
+
+		wg.Done()
+	})
+	Events.BundleSolid.Attach(testResults)
+
+	wg.Add(1)
+
+	if err := ProcessSolidBundleHead(generatedBundle.GetTransactions()[0]); err != nil {
 		t.Error(err)
-	} else {
-		assert.Equal(t, result.GetHash(), trinary.Trytes("UFWJYEWKMEQDNSQUCUWBGOFRHVBGHVVYEZCLCGRDTRQSMAFALTIPMJEEYFDPMQCNJWLXUWFMBZGHQRO99"), "invalid bundle hash")
-		assert.Equal(t, result.IsValueBundle(), true, "invalid value bundle status")
 	}
 
+	wg.Wait()
+
+	Events.BundleSolid.Detach(testResults)
+
+	// shutdown test node
+	node.Shutdown()
+}
+
+func TestProcessSolidBundleHead_Value(t *testing.T) {
+	// show all error messages for tests
+	*node.LOG_LEVEL.Value = node.LOG_LEVEL_FAILURE
+
+	// start a test node
+	node.Start(tangle.PLUGIN, PLUGIN)
+
+	bundleFactory := client.NewBundleFactory()
+	bundleFactory.AddInput(seed.GetAddress(0), -400)
+	bundleFactory.AddOutput(seed.GetAddress(1), 400, "Testmessage")
+	bundleFactory.AddOutput(client.NewAddress("SJKUKQP9SWUSUCPRQXCMDVDVZ9SHHESHIQNCXWBJF9UJSWE9ZYFHQWAUPCXC9S9DSHP9NDF9RLNPMZVCM"), 400, "Testmessage")
+
+	generatedBundle := bundleFactory.GenerateBundle(tipselection.GetRandomTip(), tipselection.GetRandomTip())
+
+	for _, transaction := range generatedBundle.GetTransactions() {
+		tangle.StoreTransaction(transaction)
+	}
+
+	var wg sync.WaitGroup
+
+	testResults := events.NewClosure(func(bundle *bundle.Bundle, transactions []*value_transaction.ValueTransaction) {
+		assert.Equal(t, bundle.GetHash(), generatedBundle.GetTransactions()[0].GetHash(), "invalid bundle hash")
+		assert.Equal(t, bundle.GetBundleEssenceHash(), generatedBundle.GetEssenceHash(), "invalid bundle essence hash")
+		assert.Equal(t, bundle.IsValueBundle(), true, "invalid value bundle status")
+
+		wg.Done()
+	})
+
+	Events.BundleSolid.Attach(testResults)
+
+	wg.Add(1)
+
+	if err := ProcessSolidBundleHead(generatedBundle.GetTransactions()[0]); err != nil {
+		t.Error(err)
+	}
+
+	wg.Wait()
+
+	Events.BundleSolid.Detach(testResults)
+
 	// shutdown test node
 	node.Shutdown()
 }
diff --git a/plugins/bundleprocessor/valuebundleprocessor.go b/plugins/bundleprocessor/valuebundleprocessor.go
index 9b606e45470a0a7b58d0f2420d7c9033221e1572..08c208d965cff885d88f2acad06fb48a6ec4f79c 100644
--- a/plugins/bundleprocessor/valuebundleprocessor.go
+++ b/plugins/bundleprocessor/valuebundleprocessor.go
@@ -6,6 +6,7 @@ import (
 	"github.com/iotaledger/goshimmer/packages/model/bundle"
 	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
 	"github.com/iotaledger/goshimmer/packages/workerpool"
+	"github.com/iotaledger/iota.go/signing"
 	"github.com/iotaledger/iota.go/trinary"
 )
 
@@ -13,23 +14,70 @@ var valueBundleProcessorWorkerPool = workerpool.New(func(task workerpool.Task) {
 	if err := ProcessSolidValueBundle(task.Param(0).(*bundle.Bundle), task.Param(1).([]*value_transaction.ValueTransaction)); err != nil {
 		Events.Error.Trigger(err)
 	}
+
+	task.Return(nil)
 }, workerpool.WorkerCount(WORKER_COUNT), workerpool.QueueSize(2*WORKER_COUNT))
 
 func ProcessSolidValueBundle(bundle *bundle.Bundle, bundleTransactions []*value_transaction.ValueTransaction) errors.IdentifiableError {
-	var concatenatedBundleEssences = make(trinary.Trits, len(bundleTransactions)*value_transaction.BUNDLE_ESSENCE_SIZE)
-	for i, bundleTransaction := range bundleTransactions {
-		copy(concatenatedBundleEssences[value_transaction.BUNDLE_ESSENCE_SIZE*i:value_transaction.BUNDLE_ESSENCE_SIZE*(i+1)], bundleTransaction.GetBundleEssence())
+	bundle.SetBundleEssenceHash(CalculateBundleHash(bundleTransactions))
+
+	Events.BundleSolid.Trigger(bundle, bundleTransactions)
+
+	return nil
+}
+
+func CalculateBundleHash(transactions []*value_transaction.ValueTransaction) trinary.Trytes {
+	var lastInputAddress trinary.Trytes
+
+	var concatenatedBundleEssences = make(trinary.Trits, len(transactions)*value_transaction.BUNDLE_ESSENCE_SIZE)
+	for i, bundleTransaction := range transactions {
+		if bundleTransaction.GetValue() <= 0 {
+			lastInputAddress = bundleTransaction.GetAddress()
+		}
+
+		copy(concatenatedBundleEssences[value_transaction.BUNDLE_ESSENCE_SIZE*i:value_transaction.BUNDLE_ESSENCE_SIZE*(i+1)], bundleTransaction.GetBundleEssence(lastInputAddress != bundleTransaction.GetAddress()))
 	}
 
-	var resp = make(trinary.Trits, 243)
+	var bundleHash = make(trinary.Trits, 243)
 
 	hasher := curl.NewCurl(243, 81)
 	hasher.Absorb(concatenatedBundleEssences, 0, len(concatenatedBundleEssences))
-	hasher.Squeeze(resp, 0, 243)
+	hasher.Squeeze(bundleHash, 0, 243)
 
-	bundle.SetBundleEssenceHash(trinary.MustTritsToTrytes(resp))
+	return trinary.MustTritsToTrytes(bundleHash)
+}
 
-	Events.BundleSolid.Trigger(bundle, bundleTransactions)
+func ValidateSignatures(bundleHash trinary.Hash, txs []*value_transaction.ValueTransaction) (bool, error) {
+	for i, tx := range txs {
+		// ignore all non-input transactions
+		if tx.GetValue() >= 0 {
+			continue
+		}
 
-	return nil
+		address := tx.GetAddress()
+
+		// it is unknown how many fragments there will be
+		fragments := []trinary.Trytes{tx.GetSignatureMessageFragment()}
+
+		// each consecutive meta transaction with the same address contains another signature fragment
+		for j := i; j < len(txs)-1; j++ {
+			otherTx := txs[j+1]
+			if otherTx.GetValue() != 0 || otherTx.GetAddress() != address {
+				break
+			}
+
+			fragments = append(fragments, otherTx.GetSignatureMessageFragment())
+		}
+
+		// validate all the fragments against the address using Kerl
+		valid, err := signing.ValidateSignatures(address, fragments, bundleHash, signing.NewKerl)
+		if err != nil {
+			return false, err
+		}
+		if !valid {
+			return false, nil
+		}
+	}
+
+	return true, nil
 }