diff --git a/go.mod b/go.mod index 63fdbf6535517e0b1da5b55154749e8dec3b65d0..c5d3bdb29630e9d3f518624f442510cacd9c7ec3 100644 --- a/go.mod +++ b/go.mod @@ -19,12 +19,16 @@ require ( github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 // indirect github.com/magiconair/properties v1.8.1 + github.com/mr-tron/base58 v1.1.3 + github.com/oasislabs/ed25519 v0.0.0-20191122104632-9d9ffc15f526 + github.com/panjf2000/ants/v2 v2.2.2 github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.8.1 github.com/rivo/tview v0.0.0-20190829161255-f8bc69b90341 github.com/rivo/uniseg v0.1.0 // indirect github.com/sasha-s/go-deadlock v0.2.0 // indirect - golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf + github.com/stretchr/testify v1.4.0 + golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect golang.org/x/text v0.3.2 // indirect diff --git a/go.sum b/go.sum index 1e1d9424f2c40e5e58505542caa9c35584af4513..2eba5bd28b5cd8aa55e7460ca98744cd8e39f84b 100644 --- a/go.sum +++ b/go.sum @@ -115,14 +115,20 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/oasislabs/ed25519 v0.0.0-20191122104632-9d9ffc15f526 h1:xKlK+m6tNFucKVOP4V0GDgU4IgaLbS+HRoiVbN3W8Y4= +github.com/oasislabs/ed25519 v0.0.0-20191122104632-9d9ffc15f526/go.mod h1:xIpCyrK2ouGA4QBGbiNbkoONrvJ00u9P3QOkXSOAC0c= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/panjf2000/ants/v2 v2.2.2 h1:TWzusBjq/IflXhy+/S6u5wmMLCBdJnB9tPIx9Zmhvok= +github.com/panjf2000/ants/v2 v2.2.2/go.mod h1:1GFm8bV8nyCQvU5K4WvBCTG1/YBFOD2VzjffD8fV55A= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= @@ -168,6 +174,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -196,8 +203,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= -golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/packages/binary/identity/constants.go b/packages/binary/identity/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..b8fb985060952f9cdca9bffc0a5b058f12b9d597 --- /dev/null +++ b/packages/binary/identity/constants.go @@ -0,0 +1,7 @@ +package identity + +const ( + PublicKeySize = 32 + PrivateKeySize = 64 + SignatureSize = 64 +) diff --git a/packages/binary/identity/identity.go b/packages/binary/identity/identity.go new file mode 100644 index 0000000000000000000000000000000000000000..8cf7ce3aab387384a7c68d389c09ada342278047 --- /dev/null +++ b/packages/binary/identity/identity.go @@ -0,0 +1,48 @@ +package identity + +import ( + "crypto/rand" + + "github.com/oasislabs/ed25519" +) + +type Identity struct { + Type Type + PublicKey []byte + PrivateKey []byte +} + +func New(publicKey []byte, optionalPrivateKey ...[]byte) *Identity { + this := &Identity{ + PublicKey: make([]byte, len(publicKey)), + } + + copy(this.PublicKey, publicKey) + + if len(optionalPrivateKey) == 0 { + this.Type = PUBLIC_TYPE + } else { + this.Type = PRIVATE_TYPE + this.PrivateKey = optionalPrivateKey[0] + } + + return this +} + +func Generate() *Identity { + if public, private, err := ed25519.GenerateKey(rand.Reader); err != nil { + panic(err) + } else { + return New(public, private) + } +} + +func (identity *Identity) Sign(data []byte) (sig []byte) { + sig = ed25519.Sign(identity.PrivateKey, data) + + return +} + +func (identity *Identity) VerifySignature(data []byte, signature []byte) bool { + return ed25519.Verify(identity.PublicKey, data, signature) +} diff --git a/packages/binary/identity/identity_test.go b/packages/binary/identity/identity_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f53ed7603bf3fa5ef43cde8ddbf403588bc436e3 --- /dev/null +++ b/packages/binary/identity/identity_test.go @@ -0,0 +1,44 @@ +package identity + +import ( + "fmt" + "sync" + "testing" + + "github.com/panjf2000/ants/v2" + + "github.com/stretchr/testify/assert" +) + +func BenchmarkIdentity_VerifySignature(b *testing.B) { + identity := Generate() + data := []byte("TESTDATA") + signature := identity.Sign(data) + + var wg sync.WaitGroup + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + wg.Add(1) + + _ = ants.Submit(func() { + identity.VerifySignature(data, signature) + + wg.Done() + }) + } + + wg.Wait() +} + +func Test(t *testing.T) { + identity := Generate() + + signature := identity.Sign([]byte("TESTDATA1")) + + fmt.Println(len(signature)) + + assert.Equal(t, true, identity.VerifySignature([]byte("TESTDATA1"), signature)) + assert.Equal(t, false, identity.VerifySignature([]byte("TESTDATA2"), signature)) +} diff --git a/packages/binary/identity/type.go b/packages/binary/identity/type.go new file mode 100644 index 0000000000000000000000000000000000000000..f89c4769614f33ccee6dd539e81449128e36608b --- /dev/null +++ b/packages/binary/identity/type.go @@ -0,0 +1,8 @@ +package identity + +type Type int + +const ( + PRIVATE_TYPE = Type(0) + PUBLIC_TYPE = Type(1) +) diff --git a/packages/binary/transaction/transaction.go b/packages/binary/transaction/transaction.go index 06a0e56ced017f453f19d489ad639bccce80669b..a5b24d8db40f222d9db297794b20056cf74ce137 100644 --- a/packages/binary/transaction/transaction.go +++ b/packages/binary/transaction/transaction.go @@ -3,8 +3,13 @@ package transaction import ( "sync" + "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/stringify" + "github.com/iotaledger/hive.go/objectstorage" + "github.com/mr-tron/base58" + "golang.org/x/crypto/blake2b" ) @@ -15,6 +20,7 @@ type Transaction struct { // core properties (they are part of the transaction when being sent) trunkTransactionId Id branchTransactionId Id + issuer *identity.Identity payload Payload // derived properties @@ -24,8 +30,22 @@ type Transaction struct { payloadIdMutex sync.RWMutex bytes []byte bytesMutex sync.RWMutex + signature [identity.SignatureSize]byte + signatureMutex sync.RWMutex +} + +// Allows us to "issue" a transaction. +func New(trunkTransactionId Id, branchTransactionId Id, issuer *identity.Identity, payload Payload) (result *Transaction) { + return &Transaction{ + trunkTransactionId: trunkTransactionId, + branchTransactionId: branchTransactionId, + issuer: issuer, + payload: payload, + } } +// Get's called when we restore a transaction from storage. The bytes and the content will be unmarshaled by an external +// caller (the objectStorage factory). func FromStorage(id []byte) (result *Transaction) { var transactionId Id copy(transactionId[:], id) @@ -37,10 +57,27 @@ func FromStorage(id []byte) (result *Transaction) { return } +func FromBytes(bytes []byte) (result *Transaction, err error) { + result = &Transaction{} + err = result.UnmarshalBinary(bytes) + + return +} + +func (transaction *Transaction) VerifySignature() (result bool) { + transactionBytes := transaction.GetBytes() + + transaction.signatureMutex.RLock() + result = transaction.issuer.VerifySignature(transactionBytes[:len(transactionBytes)-identity.SignatureSize], transaction.signature[:]) + transaction.signatureMutex.RUnlock() + + return +} + func (transaction *Transaction) GetId() (result Id) { transaction.idMutex.RLock() if transaction.id == nil { - transaction.idMutex.RLock() + transaction.idMutex.RUnlock() transaction.idMutex.Lock() if transaction.id == nil { @@ -54,7 +91,7 @@ func (transaction *Transaction) GetId() (result Id) { } else { result = *transaction.id - transaction.idMutex.RLock() + transaction.idMutex.RUnlock() } return @@ -63,7 +100,7 @@ func (transaction *Transaction) GetId() (result Id) { func (transaction *Transaction) GetPayloadId() (result PayloadId) { transaction.payloadIdMutex.RLock() if transaction.payloadId == nil { - transaction.payloadIdMutex.RLock() + transaction.payloadIdMutex.RUnlock() transaction.payloadIdMutex.Lock() if transaction.payloadId == nil { @@ -77,38 +114,18 @@ func (transaction *Transaction) GetPayloadId() (result PayloadId) { } else { result = *transaction.payloadId - transaction.payloadIdMutex.RLock() + transaction.payloadIdMutex.RUnlock() } return } -func (transaction *Transaction) GetBytes() (result []byte) { - transaction.bytesMutex.RLock() - if transaction.bytes == nil { - transaction.bytesMutex.RLock() - - transaction.bytesMutex.Lock() - if transaction.bytes == nil { - var err error - - if result, err = transaction.MarshalBinary(); err != nil { - // this should never happen - panic(err) - } - - transaction.bytes = result - } else { - result = transaction.bytes - } - transaction.bytesMutex.Unlock() +func (transaction *Transaction) GetBytes() []byte { + if result, err := transaction.MarshalBinary(); err != nil { + panic(err) } else { - result = transaction.bytes - - transaction.bytesMutex.RLock() + return result } - - return } func (transaction *Transaction) calculateTransactionId() Id { @@ -124,7 +141,7 @@ func (transaction *Transaction) calculateTransactionId() Id { offset += transactionIdLength copy(hashBase[offset:], payloadId[:]) - offset += payloadIdLength + // offset += payloadIdLength return blake2b.Sum512(hashBase) } @@ -135,22 +152,81 @@ func (transaction *Transaction) calculatePayloadId() PayloadId { return blake2b.Sum512(bytes[2*transactionIdLength:]) } +// Since transactions are immutable and do not get changed after being created, we cache the result of the marshaling. func (transaction *Transaction) MarshalBinary() (result []byte, err error) { - result = make([]byte, 2*transactionIdLength) + transaction.bytesMutex.RLock() + if transaction.bytes == nil { + transaction.bytesMutex.RUnlock() - if serializedPayload, serializationErr := transaction.payload.MarshalBinary(); serializationErr != nil { - err = serializationErr + transaction.bytesMutex.Lock() + if transaction.bytes == nil { + var serializedPayload []byte + if transaction.payload != nil { + if serializedPayload, err = transaction.payload.MarshalBinary(); err != nil { + return + } + } + serializedPayloadLength := len(serializedPayload) + + result = make([]byte, transactionIdLength+transactionIdLength+identity.PublicKeySize+serializedPayloadLength+identity.SignatureSize) + offset := 0 + + copy(result[offset:], transaction.trunkTransactionId[:]) + offset += transactionIdLength - return + copy(result[offset:], transaction.branchTransactionId[:]) + offset += transactionIdLength + + copy(result[offset:], transaction.issuer.PublicKey) + offset += identity.PublicKeySize + + // TODO: MARSHAL PAYLOAD LENGTH + + if serializedPayloadLength != 0 { + copy(result[offset:], serializedPayload) + offset += serializedPayloadLength + } + + transaction.signatureMutex.Lock() + copy(transaction.signature[:], transaction.issuer.Sign(result[:offset])) + transaction.signatureMutex.Unlock() + copy(result[offset:], transaction.signature[:]) + // offset += identity.SignatureSize + + transaction.bytes = result + } else { + result = transaction.bytes + } + transaction.bytesMutex.Unlock() } else { - result = append(result, serializedPayload...) + result = transaction.bytes + + transaction.bytesMutex.RUnlock() } return } -// TODO: FINISH -func (transaction *Transaction) UnmarshalBinary(date []byte) (err error) { +func (transaction *Transaction) UnmarshalBinary(data []byte) (err error) { + offset := 0 + + copy(transaction.trunkTransactionId[:], data[offset:]) + offset += transactionIdLength + + copy(transaction.branchTransactionId[:], data[offset:]) + offset += transactionIdLength + + transaction.issuer = identity.New(data[offset : offset+identity.PublicKeySize]) + offset += identity.PublicKeySize + + // TODO: UNMARSHAL PAYLOAD LENGTH + CONTENT + + copy(transaction.signature[:], data[offset:]) + // offset += identity.SignatureSize + + transaction.bytes = make([]byte, len(data)) + copy(transaction.bytes, data) + return } @@ -160,5 +236,16 @@ func (transaction *Transaction) GetStorageKey() []byte { return transactionId[:] } -// TODO: FINISH -func (transaction *Transaction) Update(other objectstorage.StorableObject) {} +func (transaction *Transaction) Update(other objectstorage.StorableObject) { + panic("transactions should never be overwritten and only stored once to optimize IO") +} + +func (transaction *Transaction) String() string { + transactionId := transaction.GetId() + + return stringify.Struct("Transaction", + stringify.StructField("id", base58.Encode(transactionId[:])), + stringify.StructField("trunkTransactionId", base58.Encode(transaction.trunkTransactionId[:])), + stringify.StructField("trunkTransactionId", base58.Encode(transaction.branchTransactionId[:])), + ) +} diff --git a/packages/binary/transaction/transaction_test.go b/packages/binary/transaction/transaction_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e2082701357893ba3ce4d153c1fd2abdd7a995d5 --- /dev/null +++ b/packages/binary/transaction/transaction_test.go @@ -0,0 +1,25 @@ +package transaction + +import ( + "fmt" + "testing" + + "github.com/magiconair/properties/assert" + + "github.com/iotaledger/goshimmer/packages/binary/identity" +) + +func TestNew(t *testing.T) { + newTransaction1 := New(Id{}, Id{}, identity.Generate(), nil) + assert.Equal(t, newTransaction1.VerifySignature(), true) + + newTransaction2 := New(newTransaction1.GetId(), Id{}, identity.Generate(), nil) + assert.Equal(t, newTransaction2.VerifySignature(), true) + + newTransaction3, _ := FromBytes(newTransaction2.GetBytes()) + assert.Equal(t, newTransaction3.VerifySignature(), true) + + fmt.Println(newTransaction1) + fmt.Println(newTransaction2) + fmt.Println(newTransaction3) +} diff --git a/packages/curl/batch_hasher_test.go b/packages/curl/batch_hasher_test.go index 71eac47150aa9e4a951dfe173588256a382abdbd..0c61251a5b322540b13bd363bee2ca641e8deba2 100644 --- a/packages/curl/batch_hasher_test.go +++ b/packages/curl/batch_hasher_test.go @@ -2,6 +2,7 @@ package curl import ( "crypto/ed25519" + "fmt" "sync" "testing" @@ -22,6 +23,8 @@ func BenchmarkEd25519(b *testing.B) { var zero zeroReader public, private, _ := ed25519.GenerateKey(zero) + fmt.Println(len(public)) + message := make([]byte, 75) sig := ed25519.Sign(private, message)