diff --git a/dapps/valuetransfers/packages/address/signaturescheme/bls.go b/dapps/valuetransfers/packages/address/signaturescheme/bls.go index 700eb9e4e05d7e4ae3b8f99b5ad2db0acc7f4645..7b10349b0419c144fd89151a071864f7f6bb8e0e 100644 --- a/dapps/valuetransfers/packages/address/signaturescheme/bls.go +++ b/dapps/valuetransfers/packages/address/signaturescheme/bls.go @@ -95,7 +95,7 @@ func (sigscheme *blsSignatureScheme) Sign(data []byte) Signature { if err != nil { panic(err) } - return newBLSSignature(pubKeyBin, sig) + return NewBLSSignature(pubKeyBin, sig) } func (sigscheme *blsSignatureScheme) String() string { @@ -136,7 +136,8 @@ func BLSSignatureFromBytes(data []byte) (result *BLSSignature, consumedBytes int return } -func newBLSSignature(pubKey, signature []byte) *BLSSignature { +// NewBLSSignature creates BLS signature from raw public key and signature data +func NewBLSSignature(pubKey, signature []byte) *BLSSignature { var ret BLSSignature ret[0] = address.VersionBLS copy(ret.pubKey(), pubKey) @@ -223,7 +224,7 @@ func AggregateBLSSignatures(sigs ...Signature) (Signature, error) { if err != nil { return nil, err } - return newBLSSignature(pubKeyBin, sigBin), nil + return NewBLSSignature(pubKeyBin, sigBin), nil } // interface contract (allow the compiler to check if the implementation has all of the required methods). diff --git a/dapps/valuetransfers/packages/transaction/outputs.go b/dapps/valuetransfers/packages/transaction/outputs.go index fd825d65e73b57855c813e5926ccea32b507e1af..4cd77a3ff3962de5e9afb161117f1f420d8da24e 100644 --- a/dapps/valuetransfers/packages/transaction/outputs.go +++ b/dapps/valuetransfers/packages/transaction/outputs.go @@ -1,7 +1,9 @@ package transaction import ( + "bytes" "github.com/iotaledger/hive.go/marshalutil" + "sort" "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" "github.com/iotaledger/goshimmer/packages/binary/datastructure/orderedmap" @@ -16,11 +18,20 @@ type Outputs struct { // NewOutputs is the constructor of the Outputs struct and creates the list of Outputs from the given details. func NewOutputs(outputs map[address.Address][]*balance.Balance) (result *Outputs) { - result = &Outputs{orderedmap.New()} - for address, balances := range outputs { - result.Add(address, balances) + // sorting outputs first before adding to the ordered map to have a deterministic order + toSort := make([]address.Address, 0, len(outputs)) + for a := range outputs { + toSort = append(toSort, a) } + sort.Slice(toSort, func(i, j int) bool { + return bytes.Compare(toSort[i][:], toSort[j][:]) < 0 + }) + + result = &Outputs{orderedmap.New()} + for _, addr := range toSort { + result.Add(addr, outputs[addr]) + } return } @@ -49,7 +60,7 @@ func OutputsFromBytes(bytes []byte, optionalTargetObject ...*Outputs) (result *O // iterate the corresponding times and collect addresses + their details for i := uint32(0); i < addressCount; i++ { // read address - address, addressErr := address.Parse(marshalUtil) + addr, addressErr := address.Parse(marshalUtil) if addressErr != nil { err = addressErr @@ -78,7 +89,7 @@ func OutputsFromBytes(bytes []byte, optionalTargetObject ...*Outputs) (result *O } // add the gathered information as an output - result.Add(address, coloredBalances) + result.Add(addr, coloredBalances) } // return the number of bytes we processed @@ -117,8 +128,8 @@ func (outputs *Outputs) Bytes() []byte { marshalUtil.WriteBytes(address.Bytes()) marshalUtil.WriteUint32(uint32(len(balances))) - for _, balance := range balances { - marshalUtil.WriteBytes(balance.Bytes()) + for _, bal := range balances { + marshalUtil.WriteBytes(bal.Bytes()) } return true @@ -141,10 +152,10 @@ func (outputs *Outputs) String() string { result += " " + address.String() + ": [\n" balancesEmpty := true - for _, balance := range balances { + for _, bal := range balances { balancesEmpty = false - result += " " + balance.String() + ",\n" + result += " " + bal.String() + ",\n" } if balancesEmpty { diff --git a/dapps/valuetransfers/packages/transaction/outputs_test.go b/dapps/valuetransfers/packages/transaction/outputs_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4c76e4cc44032b9078e95138af2c10dbc15d86c5 --- /dev/null +++ b/dapps/valuetransfers/packages/transaction/outputs_test.go @@ -0,0 +1,45 @@ +package transaction + +import ( + "bytes" + + "github.com/stretchr/testify/assert" + + "testing" + + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address" + "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" + "golang.org/x/crypto/blake2b" +) + +func TestOutputs(t *testing.T) { + rndAddrs := make([]address.Address, 15) + for i := range rndAddrs { + rndAddrs[i] = address.RandomOfType(address.VersionED25519) + } + + theMap1 := make(map[address.Address][]*balance.Balance) + for i := 0; i < len(rndAddrs); i++ { + theMap1[rndAddrs[i]] = []*balance.Balance{balance.New(balance.ColorIOTA, int64(i))} + } + out1 := NewOutputs(theMap1) + + theMap2 := make(map[address.Address][]*balance.Balance) + for i := len(rndAddrs) - 1; i >= 0; i-- { + theMap2[rndAddrs[i]] = []*balance.Balance{balance.New(balance.ColorIOTA, int64(i))} + } + out2 := NewOutputs(theMap2) + + h1 := hashOutputs(t, out1) + h2 := hashOutputs(t, out2) + + assert.Equal(t, bytes.Equal(h1, h2), true) +} + +func hashOutputs(t *testing.T, out *Outputs) []byte { + h, err := blake2b.New256(nil) + assert.NoError(t, err) + + h.Write(out.Bytes()) + return h.Sum(nil) +} diff --git a/dapps/valuetransfers/packages/transaction/transaction.go b/dapps/valuetransfers/packages/transaction/transaction.go index 0bbe730cc1b4e83a74c3e2a5f414fa4d85448b30..cf73c9f66443661639e9902c0363b5f664670268 100644 --- a/dapps/valuetransfers/packages/transaction/transaction.go +++ b/dapps/valuetransfers/packages/transaction/transaction.go @@ -1,6 +1,7 @@ package transaction import ( + "errors" "fmt" "sync" @@ -277,6 +278,16 @@ func (transaction *Transaction) Sign(signature signaturescheme.SignatureScheme) return transaction } +// PutSignature validates and adds signature to the transaction +func (transaction *Transaction) PutSignature(signature signaturescheme.Signature) error { + if !signature.IsValid(transaction.EssenceBytes()) { + return errors.New("PutSignature: invalid signature") + } + transaction.signatures.Add(signature.Address(), signature) + + return nil +} + // String returns a human readable version of this Transaction (for debug purposes). func (transaction *Transaction) String() string { id := transaction.ID() diff --git a/dapps/valuetransfers/packages/transaction/transaction_test.go b/dapps/valuetransfers/packages/transaction/transaction_test.go index 757a0060ac92821ab1a87b125018f56d55d30ac8..38bee17bdb987c61c0159fbcfd8cda50b9cdf20a 100644 --- a/dapps/valuetransfers/packages/transaction/transaction_test.go +++ b/dapps/valuetransfers/packages/transaction/transaction_test.go @@ -38,7 +38,7 @@ func TestShortDataPayload(t *testing.T) { dataPayload := []byte("data payload test") err := tx.SetDataPayload(dataPayload) - assert.Equal(t, nil, err) + assert.NoError(t, err) dpBack := tx.GetDataPayload() assert.Equal(t, true, bytes.Equal(dpBack, dataPayload)) @@ -52,7 +52,7 @@ func TestShortDataPayload(t *testing.T) { tx.essenceBytes = nil dataPayload[2] = '?' err = tx.SetDataPayload(dataPayload) - assert.Equal(t, nil, err) + assert.NoError(t, err) // expect signature is not valid check = tx.SignaturesValid() @@ -70,7 +70,7 @@ func TestTooLongDataPayload(t *testing.T) { dataPayload := []byte(strings.Repeat("1", MaxDataPayloadSize+1)) err := tx.SetDataPayload(dataPayload) - assert.Equal(t, true, err != nil) + assert.Error(t, err) } func TestMarshalingEmptyDataPayload(t *testing.T) { @@ -90,9 +90,8 @@ func TestMarshalingEmptyDataPayload(t *testing.T) { tx1 := Transaction{} _, err := tx1.UnmarshalObjectStorageValue(v) - if err != nil { - assert.Error(t, err) - } + assert.NoError(t, err) + assert.Equal(t, true, tx1.SignaturesValid()) assert.Equal(t, true, bytes.Equal(tx1.ID().Bytes(), tx.ID().Bytes())) } @@ -108,7 +107,7 @@ func TestMarshalingDataPayload(t *testing.T) { dataPayload := []byte("data payload test") err := tx.SetDataPayload(dataPayload) - assert.Equal(t, nil, err) + assert.NoError(t, err) tx.Sign(sigScheme) check := tx.SignaturesValid() @@ -119,8 +118,73 @@ func TestMarshalingDataPayload(t *testing.T) { tx1 := Transaction{} _, err = tx1.UnmarshalObjectStorageValue(v) - assert.Equal(t, nil, err) + assert.NoError(t, err) assert.Equal(t, true, tx1.SignaturesValid()) assert.Equal(t, true, bytes.Equal(tx1.ID().Bytes(), tx.ID().Bytes())) } + +func TestPutSignatureValid(t *testing.T) { + sigScheme := signaturescheme.RandBLS() + addr := sigScheme.Address() + o1 := NewOutputID(addr, RandomID()) + inputs := NewInputs(o1) + bal := balance.New(balance.ColorIOTA, 1) + outputs := NewOutputs(map[address.Address][]*balance.Balance{addr: {bal}}) + tx := New(inputs, outputs) + + dataPayload := []byte("data payload test") + err := tx.SetDataPayload(dataPayload) + assert.NoError(t, err) + + signature := sigScheme.Sign(tx.EssenceBytes()) + assert.Equal(t, signature.IsValid(tx.EssenceBytes()), true) + + err = tx.PutSignature(signature) + assert.NoError(t, err) + + check := tx.SignaturesValid() + assert.Equal(t, true, check) +} + +func TestPutSignatureInvalid(t *testing.T) { + sigScheme := signaturescheme.RandBLS() + addr := sigScheme.Address() + o1 := NewOutputID(addr, RandomID()) + inputs := NewInputs(o1) + bal := balance.New(balance.ColorIOTA, 1) + outputs := NewOutputs(map[address.Address][]*balance.Balance{addr: {bal}}) + tx := New(inputs, outputs) + + dataPayload := []byte("data payload test") + err := tx.SetDataPayload(dataPayload) + assert.NoError(t, err) + + signatureValid := sigScheme.Sign(tx.EssenceBytes()) + assert.Equal(t, true, signatureValid.IsValid(tx.EssenceBytes())) + + sigBytes := make([]byte, len(signatureValid.Bytes())) + copy(sigBytes, signatureValid.Bytes()) + // inverse last byte --> corrupt the signatureValid + sigBytes[len(sigBytes)-1] = ^sigBytes[len(sigBytes)-1] + + sigCorrupted, consumed, err := signaturescheme.BLSSignatureFromBytes(sigBytes) + + assert.NoError(t, err) + assert.Equal(t, consumed, len(sigBytes)) + assert.Equal(t, false, sigCorrupted.IsValid(tx.EssenceBytes())) + + err = tx.PutSignature(sigCorrupted) + // error expected + assert.Error(t, err) + + // 0 signatures is not valid + assert.Equal(t, true, !tx.SignaturesValid()) + + err = tx.PutSignature(signatureValid) + // no error expected + assert.NoError(t, err) + + // valid signatures expected + assert.Equal(t, true, tx.SignaturesValid()) +}