From ba14b3e4dba48d4f0310f5e55427ab600fed5704 Mon Sep 17 00:00:00 2001 From: Bartossh Date: Tue, 11 Apr 2023 10:31:34 +0200 Subject: [PATCH] Test transaction and wallet, update README. --- :Gofmt | 75 ++++++++++++++++++++++++++++++++ README.md | 20 ++++----- go.mod | 4 ++ go.sum | 9 ++++ transaction/transaction_test.go | 30 +++++++++++++ wallet/verifier_test.go | 74 +++++++++++++++++++++++++++++++ wallet/wallet.go | 13 +++--- wallet/wallet_test.go | 77 +++++++++++++++++++++++++++++++++ 8 files changed, 285 insertions(+), 17 deletions(-) create mode 100644 :Gofmt create mode 100644 transaction/transaction_test.go create mode 100644 wallet/verifier_test.go create mode 100644 wallet/wallet_test.go diff --git a/:Gofmt b/:Gofmt new file mode 100644 index 0000000..ca947fe --- /dev/null +++ b/:Gofmt @@ -0,0 +1,75 @@ +package wallet + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "golang.org/x/text/message" +) + +func generateRandom(bytesNum int) []byte { + rand.Seed(time.Now().UnixNano()) + b := make([]byte, 0, bytesNum) + + for i := 0; i < bytesNum; i++ { + b = append(b, byte(rand.Intn(256))) + } + + return b +} + +func TestAddressVerifySuccess(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) + + addr := w.Address() + assert.NotEmpty(t, addr) + + message := []byte("This is message to sign.") + + hash, sig := w.Sign(message) + assert.NotEmpty(t, hash) + assert.NotEmpty(t, sig) + + err = Helper{}.Verify(message, sig, hash, addr) + assert.Nil(t, err) +} + +func TestAddressVerifyFail(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) + + message := []byte("This is message to sign.") + + hash, sig := w.Sign(message) + assert.NotEmpty(t, hash) + assert.NotEmpty(t, sig) + + nw, err := New() + assert.Nil(t, err) + addr := nw.Address() + assert.NotEmpty(t, addr) + + err = Helper{}.Verify(message, sig, hash, addr) + assert.NotNil(t, err) +} + +func BenchmarkAddressVerifyLargeMessage(b *testing.B) { + w, err := New() + assert.Nil(b, err) + + message := generategenerateRandom(1000000) + + + for n := 0; n < b.N; n++ { + hash, sig := w.Sing(message) + addr := w.Address() + err = Helper{}.Verify(message, sig, hash, addr) + assert.Nil(b,err) + } +} diff --git a/README.md b/README.md index e6517de..3241834 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,25 @@ -# The-Accountant +# The Accountant The accountant is a service that keeps track of transactions between wallets. Each wallet has its own independent history of transactions. There is a set of rules allowing for transactions to happen. -The accountant is not keeping track of all transactions in a single block-chain but rather allowing to keep transaction signed by a authority. Signed transaction is valid transaction only if issuer and receiver of the transaction are existing within the system. +The accountant is not keeping track of all transactions in a single blockchain but rather allows to keep transactions signed by an authority. A signed transaction is valid transaction only if the issuer and receiver of the transaction are existing within the system. ## Transaction rules -0. Transaction may happen only when signed by issuer wallet and receiver wallet. +0. The transaction may happen only when signed by the issuer wallet and receiver wallet. 1. Transaction is unique per wallet owner. -2. Transactions are stored per wallet owner in blocks. +2. Transactions are stored per wallet owner in blocks. ## Blocks rules -0. Block are stored in the block-chain that is wallet dependent. -1. Block may store more then one transaction. The max stored transactions limit per block is adjustable. +0. Block is stored in the blockchain that is wallet dependent. +1. Block may store more than one transaction. The max stored transactions limit per block is adjustable. 2. One block per wallet owner may be cleated per 15 minutes (this value is adjustable). -3. Blocks can only be added to the block-chain, and block-chain cannot be updated. +3. Blocks can only be added to the blockchain, and the blockchain cannot be updated. ## Wallet rules -0. Owner may have only one valid wallet. +0. The owner may have only one valid wallet. 1. Public key is added to the Owner repository. 2. All historical public keys are stored in the Owner repository. 3. Wallet is not stored in the repository and is kept by the Owner independently from the system. @@ -28,6 +28,6 @@ The accountant is not keeping track of all transactions in a single block-chain The Accountant system consists of two separate services: -- Backend REST API - keeps track of transactions, validates and stores transactions per user block-chain, stores users addresses. -- Client Application - communicates with the Backend REST API, signs the transactions, keeps user Wallet private, allows to regenerate the Wallet in case of losing it. +- Backend REST API - keeps track of transactions, validates and stores transactions per user block-chain, and stores users' addresses. +- Client Application - communicates with the Backend REST API, signs the transactions, keeps user Wallet private, allows to regenerate the Wallet in case of Wallet being lost. diff --git a/go.mod b/go.mod index 787f3de..70279a6 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,10 @@ module github.com/bartossh/The-Accountant go 1.20 require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.2 // indirect go.mongodb.org/mongo-driver v1.11.4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e367e19..b707513 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -10,9 +11,16 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= @@ -34,4 +42,5 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/transaction/transaction_test.go b/transaction/transaction_test.go new file mode 100644 index 0000000..ab4bb6f --- /dev/null +++ b/transaction/transaction_test.go @@ -0,0 +1,30 @@ +package transaction + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type testSignerMock struct{} + +func (th testSignerMock) Sign(message []byte) (digest [32]byte, signature []byte) { + return [32]byte{}, []byte("signature") +} + +func (th testSignerMock) Address() string { + return "thisisaddress" +} + +type testVerifierMock struct{} + +func (tv testVerifierMock) Verify(message, signature []byte, hash [32]byte, address string) error { + return nil +} + +func TestTransaction(t *testing.T) { + trx, err := New("subject", []byte("message"), testSignerMock{}) + assert.Nil(t, err) + err = trx.Sign(testSignerMock{}, testVerifierMock{}) + assert.Nil(t, err) +} diff --git a/wallet/verifier_test.go b/wallet/verifier_test.go new file mode 100644 index 0000000..0b5633b --- /dev/null +++ b/wallet/verifier_test.go @@ -0,0 +1,74 @@ +package wallet + +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func generateRandom(bytesNum int) []byte { + rand.Seed(time.Now().UnixNano()) + b := make([]byte, 0, bytesNum) + + for i := 0; i < bytesNum; i++ { + b = append(b, byte(rand.Intn(256))) + } + + return b +} + +func TestAddressVerifySuccess(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) + + addr := w.Address() + assert.NotEmpty(t, addr) + + message := []byte("This is message to sign.") + + hash, sig := w.Sign(message) + assert.NotEmpty(t, hash) + assert.NotEmpty(t, sig) + + err = Helper{}.Verify(message, sig, hash, addr) + assert.Nil(t, err) +} + +func TestAddressVerifyFail(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) + + message := []byte("This is message to sign.") + + hash, sig := w.Sign(message) + assert.NotEmpty(t, hash) + assert.NotEmpty(t, sig) + + nw, err := New() + assert.Nil(t, err) + addr := nw.Address() + assert.NotEmpty(t, addr) + + err = Helper{}.Verify(message, sig, hash, addr) + assert.NotNil(t, err) +} + +func BenchmarkAddressVerifyLargeMessage(b *testing.B) { + w, err := New() + assert.Nil(b, err) + + message := generateRandom(1000000) + addr := w.Address() + + for n := 0; n < b.N; n++ { + hash, sig := w.Sign(message) + err = Helper{}.Verify(message, sig, hash, addr) + assert.Nil(b, err) + } +} diff --git a/wallet/wallet.go b/wallet/wallet.go index 17c48d3..3438fce 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -18,8 +18,8 @@ const ( // Wallet holds public and private key of the wallet owner. type Wallet struct { - private ed25519.PrivateKey - public ed25519.PublicKey + Private ed25519.PrivateKey + Public ed25519.PublicKey } // New tries to creates a new Wallet or returns error otherwise. @@ -28,7 +28,7 @@ func New() (Wallet, error) { if err != nil { return Wallet{}, err } - return Wallet{private: private, public: public}, nil + return Wallet{Private: private, Public: public}, nil } // DecodeGOBWallet tries to decode Wallet from gob representation or returns error otherwise. @@ -48,7 +48,6 @@ func (w *Wallet) EncodeGOB() ([]byte, error) { var content bytes.Buffer gob.Register(elliptic.P256()) - encoder := gob.NewEncoder(&content) err := encoder.Encode(w) if err != nil { @@ -69,7 +68,7 @@ func (w *Wallet) Version() byte { // Address creates address from the public key that contains wallet version and checksum. func (w *Wallet) Address() string { - versionedHash := append([]byte{version}, w.public...) + versionedHash := append([]byte{version}, w.Public...) checksum := checksum(versionedHash) fullHash := append(versionedHash, checksum...) @@ -82,7 +81,7 @@ func (w *Wallet) Address() string { // Returns digest hash sha256 and signature. func (w *Wallet) Sign(message []byte) (digest [32]byte, signature []byte) { digest = sha256.Sum256(message) - signature = ed25519.Sign(w.private, digest[:]) + signature = ed25519.Sign(w.Private, digest[:]) return digest, signature } @@ -93,7 +92,7 @@ func (w *Wallet) Verify(message, signature []byte, hash [32]byte) bool { if hash != digest { return false } - return ed25519.Verify(w.public, digest[:], signature) + return ed25519.Verify(w.Public, digest[:], signature) } func newPair() (ed25519.PrivateKey, ed25519.PublicKey, error) { diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go new file mode 100644 index 0000000..a4ba087 --- /dev/null +++ b/wallet/wallet_test.go @@ -0,0 +1,77 @@ +package wallet + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateWallet(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) +} + +func TestGobEncodingDecoding(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) + + b, err := w.EncodeGOB() + assert.Nil(t, err) + assert.NotNil(t, b) + + nw, err := DecodeGOBWallet(b) + assert.Nil(t, err) + assert.Equal(t, nw.Private, w.Private) + assert.Equal(t, nw.Public, w.Public) +} + +func TestSignVerifySuccess(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) + + message := []byte("This is test message.") + + hash, sig := w.Sign(message) + assert.NotNil(t, hash) + assert.NotNil(t, sig) + + ok := w.Verify(message, sig, hash) + assert.True(t, ok) +} + +func TestSignVerifyFail(t *testing.T) { + w, err := New() + assert.Nil(t, err) + assert.NotNil(t, w.Private) + assert.NotNil(t, w.Public) + + message := []byte("This is test message.") + + nw, err := New() + assert.Nil(t, err) + hash, sig := nw.Sign(message) + assert.NotNil(t, hash) + assert.NotNil(t, sig) + + ok := w.Verify(message, sig, hash) + assert.False(t, ok) +} + +func BenchmarkVerifyLargeMessage(b *testing.B) { + w, err := New() + assert.Nil(b, err) + + message := generateRandom(1000000) + + for n := 0; n < b.N; n++ { + hash, sig := w.Sign(message) + ok := w.Verify(message, sig, hash) + assert.True(b, ok) + } +}