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 }