Skip to content

Commit

Permalink
feat(BUX-400): add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
arkadiuszos4chain committed Apr 5, 2024
1 parent addb240 commit 79438de
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 34 deletions.
67 changes: 65 additions & 2 deletions contacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"testing"

"github.com/bitcoin-sv/spv-wallet-go-client/fixtures"
"github.com/bitcoin-sv/spv-wallet/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestRejectContact will test the RejectContact method
// TestContactActionsRouting will test routing
func TestContactActionsRouting(t *testing.T) {
tcs := []struct {
name string
Expand Down Expand Up @@ -55,6 +57,16 @@ func TestContactActionsRouting(t *testing.T) {
return err
},
},
{name: "ConfirmContact",
route: "/contact/confirmed/",
responsePayload: "{}",
f: func(c *WalletClient) error {
contact := models.Contact{PubKey: fixtures.PubKey}

passcode, _ := c.GenerateTotpForContact(&contact, 30, 2)
return c.ConfirmContact(context.Background(), &contact, passcode, 30, 2)
},
},
}

for _, tc := range tcs {
Expand All @@ -68,7 +80,7 @@ func TestContactActionsRouting(t *testing.T) {
Client: WithHTTPClient,
}

client := getTestWalletClient(tmq, true)
client := getTestWalletClient(tmq, false)

// when
err := tc.f(client)
Expand All @@ -79,3 +91,54 @@ func TestContactActionsRouting(t *testing.T) {
}

}

func TestConfirmContact(t *testing.T) {
t.Run("TOTP is valid - call Confirm Action", func(t *testing.T) {
// given
tmq := testTransportHandler{
Type: fixtures.RequestType,
Path: "/contact/confirmed/",
Result: "{}",
ClientURL: fixtures.ServerURL,
Client: WithHTTPClient,
}

client := getTestWalletClient(tmq, false)

contact := &models.Contact{PubKey: fixtures.PubKey}
totp, err := client.GenerateTotpForContact(contact, 30, 2)
require.NoError(t, err)

// when
result := client.ConfirmContact(context.Background(), contact, totp, 30, 2)

// then
require.Nil(t, result)
})

t.Run("TOTP is invalid - do not call Confirm Action", func(t *testing.T) {
// given
tmq := testTransportHandler{
Type: fixtures.RequestType,
Path: "/unknown/",
Result: "{}",
ClientURL: fixtures.ServerURL,
Client: WithHTTPClient,
}

client := getTestWalletClient(tmq, false)

alice := &models.Contact{PubKey: "034252e5359a1de3b8ec08e6c29b80594e88fb47e6ae9ce65ee5a94f0d371d2cde"}
a_totp, err := client.GenerateTotpForContact(alice, 30, 2)
require.NoError(t, err)

bob := &models.Contact{PubKey: "02dde493752f7bc89822ed8a13e0e4aa04550c6c4430800e4be1e5e5c2556cf65b"}

// when
result := client.ConfirmContact(context.Background(), bob, a_totp, 30, 2)

// then
require.NotNil(t, result)
require.Equal(t, result.Error(), "totp is invalid")
})
}
1 change: 1 addition & 0 deletions fixtures/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
XPrivString = "xprv9s21ZrQH143K3N6qVJQAu4EP51qMcyrKYJLkLgmYXgz58xmVxVLSsbx2DfJUtjcnXK8NdvkHMKfmmg5AJT2nqqRWUrjSHX29qEJwBgBPkJQ"
AccessKeyString = "7779d24ca6f8821f225042bf55e8f80aa41b08b879b72827f51e41e6523b9cd0"
PaymailAddress = "[email protected]"
PubKey = "034252e5359a1de3b8ec08e6c29b80594e88fb47e6ae9ce65ee5a94f0d371d2cde"
)

func MarshallForTestHandler(object any) string {
Expand Down
23 changes: 18 additions & 5 deletions totp.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
package walletclient

import (
"encoding/hex"
"errors"
"fmt"

"github.com/bitcoin-sv/spv-wallet-go-client/totp"
"github.com/bitcoin-sv/spv-wallet-go-client/utils"
"github.com/bitcoin-sv/spv-wallet/models"
"github.com/bitcoinschema/go-bitcoin/v2"
"github.com/libsv/go-bk/bec"
"github.com/libsv/go-bk/bip32"
)

var ErrClientInitNoXpriv = errors.New("init client with xPriv first")

// GenerateTotpForContact creates one time-based one-time password based on secret shared between the user and the contact
func (b *WalletClient) GenerateTotpForContact(contact *models.Contact, validationPeriod, digits uint) (string, error) {
if b.xPriv == nil {
return "", errors.New("init client with xPriv first")
return "", ErrClientInitNoXpriv
}

xpriv, err := deriveXprivForPki(b.xPriv)
if err != nil {
return "", err
}

cXpub, err := bip32.NewKeyFromString(contact.PubKey)
cXpub, err := convertPubKey(contact.PubKey)
if err != nil {
return "", fmt.Errorf("contact's PubKey is invalid: %w", err)
}
Expand All @@ -32,21 +36,21 @@ func (b *WalletClient) GenerateTotpForContact(contact *models.Contact, validatio
Digits: digits,
}

return ts.GenarateTotp(xpriv, cXpub)
return ts.GenerateTotp(xpriv, cXpub)
}

// ValidateTotpForContact validates one time-based one-time password based on secret shared between the user and the contact
func (b *WalletClient) ValidateTotpForContact(contact *models.Contact, passcode string, validationPeriod, digits uint) (bool, error) {
if b.xPriv == nil {
return false, errors.New("init client with xPriv first")
return false, ErrClientInitNoXpriv
}

xpriv, err := deriveXprivForPki(b.xPriv)
if err != nil {
return false, err
}

cXpub, err := bip32.NewKeyFromString(contact.PubKey)
cXpub, err := convertPubKey(contact.PubKey)
if err != nil {
return false, fmt.Errorf("contact's PubKey is invalid: %w", err)
}
Expand All @@ -70,3 +74,12 @@ func deriveXprivForPki(xpriv *bip32.ExtendedKey) (*bip32.ExtendedKey, error) {

return pkiXpriv.Child(0)
}

func convertPubKey(pubKey string) (*bec.PublicKey, error) {
hex, err := hex.DecodeString(pubKey)
if err != nil {
return nil, err
}

return bec.ParsePubKey(hex, bec.S256())
}
15 changes: 5 additions & 10 deletions totp/totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ type Service struct {
}

// GenerateTotp creates one time-based one-time password based on secrets calculated from the keys
func (s *Service) GenarateTotp(xPriv, xPub *bip32.ExtendedKey) (string, error) {
secret, err := sharedSecret(xPriv, xPub)
func (s *Service) GenerateTotp(xPriv *bip32.ExtendedKey, pubKey *bec.PublicKey) (string, error) {
secret, err := sharedSecret(xPriv, pubKey)
if err != nil {
return "", err
}
Expand All @@ -27,8 +27,8 @@ func (s *Service) GenarateTotp(xPriv, xPub *bip32.ExtendedKey) (string, error) {
}

// ValidateTotp checks if given one-time password is valid
func (s *Service) ValidateTotp(xPriv, xPub *bip32.ExtendedKey, passcode string) (bool, error) {
secret, err := sharedSecret(xPriv, xPub)
func (s *Service) ValidateTotp(xPriv *bip32.ExtendedKey, pubKey *bec.PublicKey, passcode string) (bool, error) {
secret, err := sharedSecret(xPriv, pubKey)
if err != nil {
return false, err
}
Expand All @@ -43,17 +43,12 @@ func (s *Service) getOpts() totp.ValidateOpts {
}
}

func sharedSecret(xPriv, xPub *bip32.ExtendedKey) (string, error) {
func sharedSecret(xPriv *bip32.ExtendedKey, pubKey *bec.PublicKey) (string, error) {
privKey, err := xPriv.ECPrivKey()
if err != nil {
return "", err
}

pubKey, err := xPub.ECPubKey()
if err != nil {
return "", err
}

x, _ := bec.S256().ScalarMult(pubKey.X, pubKey.Y, privKey.D.Bytes())

return base32.StdEncoding.EncodeToString(x.Bytes()), nil
Expand Down
38 changes: 21 additions & 17 deletions totp/totp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"
"time"

"github.com/libsv/go-bk/bec"
"github.com/libsv/go-bk/bip32"
"github.com/stretchr/testify/require"
)
Expand All @@ -16,16 +17,15 @@ func TestTotpService(t *testing.T) {
// given
const givenDigits = 4

a_xpriv, _ := bip32.NewKeyFromString(a_xprivStr)
b_xpriv, _ := bip32.NewKeyFromString(b_xprivStr)
b_xpub, _ := b_xpriv.Neuter()
a_xpriv, _ := getKeyPair(a_xprivStr)
_, b_pk := getKeyPair(b_xprivStr)

sut := &Service{
Digits: givenDigits,
}

// when
pc, err := sut.GenarateTotp(a_xpriv, b_xpub)
pc, err := sut.GenerateTotp(a_xpriv, b_pk)

// then
require.NoError(t, err)
Expand All @@ -34,19 +34,17 @@ func TestTotpService(t *testing.T) {

t.Run("Passcode is valid", func(t *testing.T) {
// given
a_xpriv, _ := bip32.NewKeyFromString(a_xprivStr)
a_xpub, _ := a_xpriv.Neuter()
b_xpriv, _ := bip32.NewKeyFromString(b_xprivStr)
b_xpub, _ := b_xpriv.Neuter()
a_xpriv, a_pk := getKeyPair(a_xprivStr)
b_xpriv, b_pk := getKeyPair(b_xprivStr)

sut := &Service{
Digits: 2,
}
a_passcode, err := sut.GenarateTotp(a_xpriv, b_xpub)
a_passcode, err := sut.GenerateTotp(a_xpriv, b_pk)
require.NoError(t, err)

// when
isValid, err := sut.ValidateTotp(b_xpriv, a_xpub, a_passcode)
isValid, err := sut.ValidateTotp(b_xpriv, a_pk, a_passcode)
require.NoError(t, err)

// then
Expand All @@ -58,30 +56,36 @@ func TestTotpService(t *testing.T) {
// given
const givenSeconds = 3

a_xpriv, _ := bip32.NewKeyFromString(a_xprivStr)
a_xpub, _ := a_xpriv.Neuter()
b_xpriv, _ := bip32.NewKeyFromString(b_xprivStr)
b_xpub, _ := b_xpriv.Neuter()
a_xpriv, a_pk := getKeyPair(a_xprivStr)
b_xpriv, b_pk := getKeyPair(b_xprivStr)

sut := &Service{
Digits: 2,
Period: givenSeconds,
}
a_passcode, err := sut.GenarateTotp(a_xpriv, b_xpub)
a_passcode, err := sut.GenerateTotp(a_xpriv, b_pk)
require.NoError(t, err)

// when
isValid, err := sut.ValidateTotp(b_xpriv, a_xpub, a_passcode)
isValid, err := sut.ValidateTotp(b_xpriv, a_pk, a_passcode)
require.NoError(t, err)
require.True(t, isValid)

time.Sleep(givenSeconds * time.Second)

isValid, err = sut.ValidateTotp(b_xpriv, a_xpub, a_passcode)
isValid, err = sut.ValidateTotp(b_xpriv, a_pk, a_passcode)

// then
require.NoError(t, err)
require.False(t, isValid)
})

}

func getKeyPair(xprivStr string) (xpriv *bip32.ExtendedKey, pk *bec.PublicKey) {
xpriv, _ = bip32.NewKeyFromString(xprivStr)
xpub, _ := xpriv.Neuter()
pk, _ = xpub.ECPubKey()

return
}
Loading

0 comments on commit 79438de

Please sign in to comment.