more refactoring

This commit is contained in:
2024-11-27 09:41:31 -06:00
parent 35039bf494
commit c0b785ca8d
21 changed files with 167 additions and 151 deletions

View File

@@ -1,84 +0,0 @@
package models
import (
"github.com/google/uuid"
"go-nkode/config"
"go-nkode/internal/security"
"go-nkode/internal/utils"
)
type Customer struct {
Id CustomerId
NKodePolicy NKodePolicy
Attributes CustomerAttributes
}
func NewCustomer(nkodePolicy NKodePolicy) (*Customer, error) {
customerAttrs, err := NewCustomerAttributes()
if err != nil {
return nil, err
}
customer := Customer{
Id: CustomerId(uuid.New()),
NKodePolicy: nkodePolicy,
Attributes: *customerAttrs,
}
return &customer, nil
}
func (c *Customer) IsValidNKode(kp KeypadDimension, passcodeAttrIdx []int) error {
nkodeLen := len(passcodeAttrIdx)
if nkodeLen < c.NKodePolicy.MinNkodeLen || nkodeLen > c.NKodePolicy.MaxNkodeLen {
return config.ErrInvalidNKodeLength
}
if validIdx := kp.ValidateAttributeIndices(passcodeAttrIdx); !validIdx {
return config.ErrInvalidNKodeIdx
}
passcodeSetVals := make(utils.Set[uint64])
passcodeAttrVals := make(utils.Set[uint64])
attrVals, err := c.Attributes.AttrValsForKp(kp)
if err != nil {
return err
}
for idx := 0; idx < nkodeLen; idx++ {
attrVal := attrVals[passcodeAttrIdx[idx]]
setVal, err := c.Attributes.GetAttrSetVal(attrVal, kp)
if err != nil {
return err
}
passcodeSetVals.Add(setVal)
passcodeAttrVals.Add(attrVal)
}
if passcodeSetVals.Size() < c.NKodePolicy.DistinctSets {
return config.ErrTooFewDistinctSet
}
if passcodeAttrVals.Size() < c.NKodePolicy.DistinctAttributes {
return config.ErrTooFewDistinctAttributes
}
return nil
}
func (c *Customer) RenewKeys() ([]uint64, []uint64, error) {
oldAttrs := make([]uint64, len(c.Attributes.AttrVals))
oldSets := make([]uint64, len(c.Attributes.SetVals))
copy(oldAttrs, c.Attributes.AttrVals)
copy(oldSets, c.Attributes.SetVals)
if err := c.Attributes.Renew(); err != nil {
return nil, nil, err
}
attrsXor, err := security.XorLists(oldAttrs, c.Attributes.AttrVals)
if err != nil {
return nil, nil, err
}
setXor, err := security.XorLists(oldSets, c.Attributes.SetVals)
if err != nil {
return nil, nil, err
}
return setXor, attrsXor, nil
}

View File

@@ -1,94 +0,0 @@
package models
import (
"go-nkode/internal/security"
"log"
)
type CustomerAttributes struct {
AttrVals []uint64
SetVals []uint64
}
func NewCustomerAttributes() (*CustomerAttributes, error) {
attrVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs())
if err != nil {
log.Print("unable to generate attribute vals: ", err)
return nil, err
}
setVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey)
if err != nil {
log.Print("unable to generate set vals: ", err)
return nil, err
}
customerAttrs := CustomerAttributes{
AttrVals: attrVals,
SetVals: setVals,
}
return &customerAttrs, nil
}
func NewCustomerAttributesFromBytes(attrBytes []byte, setBytes []byte) CustomerAttributes {
return CustomerAttributes{
AttrVals: security.ByteArrToUint64Arr(attrBytes),
SetVals: security.ByteArrToUint64Arr(setBytes),
}
}
func (c *CustomerAttributes) Renew() error {
attrVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs())
if err != nil {
return err
}
setVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey)
if err != nil {
return err
}
c.AttrVals = attrVals
c.SetVals = setVals
return nil
}
func (c *CustomerAttributes) IndexOfAttr(attrVal uint64) (int, error) {
// TODO: should this be mapped instead?
return security.IndexOf[uint64](c.AttrVals, attrVal)
}
func (c *CustomerAttributes) IndexOfSet(setVal uint64) (int, error) {
// TODO: should this be mapped instead?
return security.IndexOf[uint64](c.SetVals, setVal)
}
func (c *CustomerAttributes) GetAttrSetVal(attrVal uint64, userKeypad KeypadDimension) (uint64, error) {
indexOfAttr, err := c.IndexOfAttr(attrVal)
if err != nil {
return 0, err
}
setIdx := indexOfAttr % userKeypad.AttrsPerKey
return c.SetVals[setIdx], nil
}
func (c *CustomerAttributes) AttrValsForKp(userKp KeypadDimension) ([]uint64, error) {
err := userKp.IsValidKeypadDimension()
if err != nil {
return nil, err
}
return c.AttrVals[:userKp.TotalAttrs()], nil
}
func (c *CustomerAttributes) SetValsForKp(userKp KeypadDimension) ([]uint64, error) {
err := userKp.IsValidKeypadDimension()
if err != nil {
return nil, err
}
return c.SetVals[:userKp.AttrsPerKey], nil
}
func (c *CustomerAttributes) AttrBytes() []byte {
return security.Uint64ArrToByteArr(c.AttrVals)
}
func (c *CustomerAttributes) SetBytes() []byte {
return security.Uint64ArrToByteArr(c.SetVals)
}

View File

@@ -1,57 +0,0 @@
package models
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestCustomer(t *testing.T) {
testNewCustomerAttributes(t)
testCustomerValidKeyEntry(t)
testCustomerIsValidNKode(t)
}
func testNewCustomerAttributes(t *testing.T) {
_, nil := NewCustomerAttributes()
assert.NoError(t, nil)
}
func testCustomerValidKeyEntry(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 9}
nkodePolicy := NewDefaultNKodePolicy()
customer, err := NewCustomer(nkodePolicy)
assert.NoError(t, err)
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err)
userEmail := "testing@example.com"
passcodeIdx := []int{0, 1, 2, 3}
user, err := NewUser(*customer, userEmail, passcodeIdx, *userInterface, kp)
assert.NoError(t, err)
userLoginInterface, err := user.GetLoginInterface()
assert.NoError(t, err)
selectedKeys, err := SelectKeyByAttrIdx(userLoginInterface, passcodeIdx, kp)
assert.NoError(t, err)
validatedPasscode, err := ValidKeyEntry(*user, *customer, selectedKeys)
assert.NoError(t, err)
assert.Equal(t, len(validatedPasscode), len(passcodeIdx))
for idx := range validatedPasscode {
assert.Equal(t, validatedPasscode[idx], passcodeIdx[idx])
}
}
func testCustomerIsValidNKode(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 7}
nkodePolicy := NewDefaultNKodePolicy()
customer, err := NewCustomer(nkodePolicy)
assert.NoError(t, err)
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err)
userEmail := "testing123@example.com"
passcodeIdx := []int{0, 1, 2, 3}
user, err := NewUser(*customer, userEmail, passcodeIdx, *userInterface, kp)
assert.NoError(t, err)
err = customer.IsValidNKode(user.Kp, passcodeIdx)
assert.NoError(t, err)
}

View File

@@ -1,55 +0,0 @@
package models
import (
"go-nkode/config"
py "go-nkode/internal/utils"
)
type KeypadDimension struct {
AttrsPerKey int `json:"attrs_per_key"`
NumbOfKeys int `json:"numb_of_keys"`
}
func (kp *KeypadDimension) TotalAttrs() int {
return kp.AttrsPerKey * kp.NumbOfKeys
}
func (kp *KeypadDimension) IsDispersable() bool {
return kp.AttrsPerKey <= kp.NumbOfKeys
}
func (kp *KeypadDimension) IsValidKeypadDimension() error {
if KeypadMin.AttrsPerKey > kp.AttrsPerKey || KeypadMax.AttrsPerKey < kp.AttrsPerKey || KeypadMin.NumbOfKeys > kp.NumbOfKeys || KeypadMax.NumbOfKeys < kp.NumbOfKeys {
return config.ErrInvalidKeypadDimensions
}
return nil
}
func (kp *KeypadDimension) ValidKeySelections(selectedKeys []int) bool {
return py.All[int](selectedKeys, func(idx int) bool {
return 0 <= idx && idx < kp.NumbOfKeys
})
}
func (kp *KeypadDimension) ValidateAttributeIndices(attrIndicies []int) bool {
return py.All[int](attrIndicies, func(i int) bool {
return i >= 0 && i < kp.TotalAttrs()
})
}
var (
KeypadMax = KeypadDimension{
AttrsPerKey: 16,
NumbOfKeys: 15,
}
KeypadMin = KeypadDimension{
AttrsPerKey: 5,
NumbOfKeys: 4,
}
KeypadDefault = KeypadDimension{
AttrsPerKey: 14,
NumbOfKeys: 7,
}
)

View File

@@ -1,23 +0,0 @@
package models
import (
"errors"
"fmt"
"go-nkode/internal/security"
)
func SelectKeyByAttrIdx(interfaceUser []int, passcodeIdxs []int, keypadSize KeypadDimension) ([]int, error) {
selectedKeys := make([]int, len(passcodeIdxs))
for idx := range passcodeIdxs {
attrIdx, err := security.IndexOf[int](interfaceUser, passcodeIdxs[idx])
if err != nil {
return nil, err
}
keyNumb := attrIdx / keypadSize.AttrsPerKey
if keyNumb >= keypadSize.NumbOfKeys {
return nil, errors.New(fmt.Sprintf("index key number: %d out of range 0-%d", keyNumb, keypadSize.NumbOfKeys-1))
}
selectedKeys[idx] = keyNumb
}
return selectedKeys, nil
}

View File

@@ -1,145 +0,0 @@
package models
import (
"github.com/google/uuid"
"go-nkode/config"
"go-nkode/internal/security"
"log"
)
type User struct {
Id UserId
CustomerId CustomerId
Email UserEmail
EncipheredPasscode EncipheredNKode
Kp KeypadDimension
CipherKeys UserCipherKeys
Interface UserInterface
Renew bool
RefreshToken string
}
func (u *User) DecipherMask(setVals []uint64, passcodeLen int) ([]uint64, error) {
return u.CipherKeys.DecipherMask(u.EncipheredPasscode.Mask, setVals, passcodeLen)
}
func (u *User) RenewKeys(setXor []uint64, attrXor []uint64) error {
u.Renew = true
var err error
u.CipherKeys.SetKey, err = security.XorLists(setXor[:u.Kp.AttrsPerKey], u.CipherKeys.SetKey)
if err != nil {
return err
}
u.CipherKeys.AlphaKey, err = security.XorLists(attrXor[:u.Kp.TotalAttrs()], u.CipherKeys.AlphaKey)
return err
}
func (u *User) RefreshPasscode(passcodeAttrIdx []int, customerAttributes CustomerAttributes) error {
setVals, err := customerAttributes.SetValsForKp(u.Kp)
newKeys, err := NewUserCipherKeys(&u.Kp, setVals, u.CipherKeys.MaxNKodeLen)
if err != nil {
return err
}
encipheredPasscode, err := newKeys.EncipherNKode(passcodeAttrIdx, customerAttributes)
if err != nil {
return err
}
u.CipherKeys = *newKeys
u.EncipheredPasscode = *encipheredPasscode
u.Renew = false
return nil
}
func (u *User) GetLoginInterface() ([]int, error) {
err := u.Interface.PartialInterfaceShuffle()
if err != nil {
return nil, err
}
return u.Interface.IdxInterface, nil
}
func ValidKeyEntry(user User, customer Customer, selectedKeys []int) ([]int, error) {
if validKeys := user.Kp.ValidKeySelections(selectedKeys); !validKeys {
return nil, config.ErrKeyIndexOutOfRange
}
passcodeLen := len(selectedKeys)
if err := customer.NKodePolicy.ValidLength(passcodeLen); err != nil {
return nil, err
}
setVals, err := customer.Attributes.SetValsForKp(user.Kp)
if err != nil {
log.Printf("fatal error in validate key entry;invalid user keypad dimensions for user %s with error %v", user.Email, err)
return nil, config.ErrInternalValidKeyEntry
}
passcodeSetVals, err := user.DecipherMask(setVals, passcodeLen)
if err != nil {
log.Printf("fatal error in validate key entry;something when wrong deciphering mask;user email %s; error %v", user.Email, err)
return nil, config.ErrInternalValidKeyEntry
}
presumedAttrIdxVals := make([]int, passcodeLen)
for idx := range presumedAttrIdxVals {
keyNumb := selectedKeys[idx]
setIdx, err := customer.Attributes.IndexOfSet(passcodeSetVals[idx])
if err != nil {
log.Printf("fatal error in validate key entry;something when wrong getting the IndexOfSet;user email %s; error %v", user.Email, err)
return nil, config.ErrInternalValidKeyEntry
}
selectedAttrIdx, err := user.Interface.GetAttrIdxByKeyNumbSetIdx(setIdx, keyNumb)
if err != nil {
log.Printf("fatal error in validate key entry;something when wrong getting the GetAttrIdxByKeyNumbSetIdx;user email %s; error %v", user.Email, err)
return nil, config.ErrInternalValidKeyEntry
}
presumedAttrIdxVals[idx] = selectedAttrIdx
}
err = customer.IsValidNKode(user.Kp, presumedAttrIdxVals)
if err != nil {
return nil, err
}
attrVals, err := customer.Attributes.AttrValsForKp(user.Kp)
if err != nil {
return nil, err
}
err = user.CipherKeys.ValidPassword(user.EncipheredPasscode.Code, presumedAttrIdxVals, attrVals)
if err != nil {
return nil, err
}
return presumedAttrIdxVals, nil
}
func NewUser(customer Customer, userEmail string, passcodeIdx []int, ui UserInterface, kp KeypadDimension) (*User, error) {
_, err := ParseEmail(userEmail)
if err != nil {
return nil, err
}
setVals, err := customer.Attributes.SetValsForKp(kp)
if err != nil {
return nil, err
}
newKeys, err := NewUserCipherKeys(&kp, setVals, customer.NKodePolicy.MaxNkodeLen)
if err != nil {
return nil, err
}
encipheredNKode, err := newKeys.EncipherNKode(passcodeIdx, customer.Attributes)
if err != nil {
return nil, err
}
newUser := User{
Id: UserId(uuid.New()),
Email: UserEmail(userEmail),
EncipheredPasscode: *encipheredNKode,
CipherKeys: *newKeys,
Interface: ui,
Kp: kp,
CustomerId: customer.Id,
}
return &newUser, nil
}

View File

@@ -1,193 +0,0 @@
package models
import (
"crypto/sha256"
"errors"
"go-nkode/config"
"go-nkode/internal/security"
"golang.org/x/crypto/bcrypt"
)
type UserCipherKeys struct {
AlphaKey []uint64
SetKey []uint64
PassKey []uint64
MaskKey []uint64
Salt []byte
MaxNKodeLen int
Kp *KeypadDimension
}
func NewUserCipherKeys(kp *KeypadDimension, setVals []uint64, maxNKodeLen int) (*UserCipherKeys, error) {
err := kp.IsValidKeypadDimension()
if err != nil {
return nil, err
}
setKey, err := security.GenerateRandomNonRepeatingUint64(kp.AttrsPerKey)
if err != nil {
return nil, err
}
setKey, err = security.XorLists(setKey, setVals)
if err != nil {
return nil, err
}
alphaKey, _ := security.GenerateRandomNonRepeatingUint64(kp.TotalAttrs())
passKey, _ := security.GenerateRandomNonRepeatingUint64(maxNKodeLen)
maskKey, _ := security.GenerateRandomNonRepeatingUint64(maxNKodeLen)
salt, _ := security.RandomBytes(10)
userCipherKeys := UserCipherKeys{
AlphaKey: alphaKey,
PassKey: passKey,
MaskKey: maskKey,
SetKey: setKey,
Salt: salt,
MaxNKodeLen: maxNKodeLen,
Kp: kp,
}
return &userCipherKeys, nil
}
func (u *UserCipherKeys) PadUserMask(userMask []uint64, setVals []uint64) ([]uint64, error) {
if len(userMask) > u.MaxNKodeLen {
return nil, config.ErrUserMaskTooLong
}
paddedUserMask := make([]uint64, len(userMask))
copy(paddedUserMask, userMask)
for i := len(userMask); i < u.MaxNKodeLen; i++ {
paddedUserMask = append(paddedUserMask, setVals[i%len(setVals)])
}
return paddedUserMask, nil
}
func (u *UserCipherKeys) ValidPassword(hashedPassword string, passcodeAttrIdx []int, attrVals []uint64) error {
hashBytes := []byte(hashedPassword)
passcodeCipher := u.encipherCode(passcodeAttrIdx, attrVals)
passwordDigest := u.saltAndDigest(passcodeCipher)
err := bcrypt.CompareHashAndPassword(hashBytes, passwordDigest)
if err != nil {
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return config.ErrInvalidNKode
}
return err
}
return nil
}
func (u *UserCipherKeys) EncipherSaltHashCode(passcodeAttrIdx []int, attrVals []uint64) (string, error) {
passcodeCipher := u.encipherCode(passcodeAttrIdx, attrVals)
passcodeDigest := u.saltAndDigest(passcodeCipher)
passcodeBytes, err := u.hashPasscode(passcodeDigest)
if err != nil {
return "", err
}
return string(passcodeBytes), nil
}
func (u *UserCipherKeys) encipherCode(passcodeAttrIdx []int, attrVals []uint64) []uint64 {
passcodeLen := len(passcodeAttrIdx)
passcodeCipher := make([]uint64, u.MaxNKodeLen)
for idx := 0; idx < passcodeLen; idx++ {
attrIdx := passcodeAttrIdx[idx]
alpha := u.AlphaKey[attrIdx]
attrVal := attrVals[attrIdx]
pass := u.PassKey[idx]
passcodeCipher[idx] = alpha ^ pass ^ attrVal
}
return passcodeCipher
}
func (u *UserCipherKeys) saltAndDigest(passcode []uint64) []byte {
passcodeBytes := security.Uint64ArrToByteArr(passcode)
passcodeBytes = append(passcodeBytes, u.Salt...)
h := sha256.New()
h.Write(passcodeBytes)
return h.Sum(nil)
}
func (u *UserCipherKeys) hashPasscode(passcodeDigest []byte) ([]byte, error) {
hashedPassword, err := bcrypt.GenerateFromPassword(passcodeDigest, bcrypt.DefaultCost)
if err != nil {
return nil, err
}
return hashedPassword, nil
}
func (u *UserCipherKeys) EncipherMask(passcodeSet []uint64, customerAttrs CustomerAttributes, userKp KeypadDimension) (string, error) {
setVals, err := customerAttrs.SetValsForKp(userKp)
if err != nil {
return "", err
}
paddedPasscodeSets, err := u.PadUserMask(passcodeSet, setVals)
if err != nil {
return "", err
}
cipheredMask := make([]uint64, len(paddedPasscodeSets))
for idx := range paddedPasscodeSets {
setIdx, err := customerAttrs.IndexOfSet(paddedPasscodeSets[idx])
if err != nil {
return "", err
}
setKeyVal := u.SetKey[setIdx]
maskKeyVal := u.MaskKey[idx]
setVal := paddedPasscodeSets[idx]
cipheredMask[idx] = setKeyVal ^ maskKeyVal ^ setVal
}
mask := security.EncodeBase64Str(cipheredMask)
return mask, nil
}
func (u *UserCipherKeys) DecipherMask(mask string, setVals []uint64, passcodeLen int) ([]uint64, error) {
decodedMask, err := security.DecodeBase64Str(mask)
if err != nil {
return nil, err
}
decipheredMask, err := security.XorLists(decodedMask, u.MaskKey)
if err != nil {
return nil, err
}
setKeyRandComponent, err := security.XorLists(setVals, u.SetKey)
if err != nil {
return nil, err
}
passcodeSet := make([]uint64, passcodeLen)
for idx, setCipher := range decipheredMask[:passcodeLen] {
setIdx, err := security.IndexOf(setKeyRandComponent, setCipher)
if err != nil {
return nil, err
}
passcodeSet[idx] = setVals[setIdx]
}
return passcodeSet, nil
}
func (u *UserCipherKeys) EncipherNKode(passcodeAttrIdx []int, customerAttrs CustomerAttributes) (*EncipheredNKode, error) {
attrVals, err := customerAttrs.AttrValsForKp(*u.Kp)
code, err := u.EncipherSaltHashCode(passcodeAttrIdx, attrVals)
if err != nil {
return nil, err
}
passcodeSet := make([]uint64, len(passcodeAttrIdx))
for idx := range passcodeSet {
passcodeAttr := attrVals[passcodeAttrIdx[idx]]
passcodeSet[idx], err = customerAttrs.GetAttrSetVal(passcodeAttr, *u.Kp)
if err != nil {
return nil, err
}
}
mask, err := u.EncipherMask(passcodeSet, customerAttrs, *u.Kp)
if err != nil {
return nil, err
}
encipheredCode := EncipheredNKode{
Code: code,
Mask: mask,
}
return &encipheredCode, nil
}

View File

@@ -1,194 +0,0 @@
package models
import (
"go-nkode/config"
"go-nkode/internal/security"
"go-nkode/internal/utils"
"log"
)
type UserInterface struct {
IdxInterface IdxInterface
SvgId SvgIdInterface
Kp *KeypadDimension
}
func NewUserInterface(kp *KeypadDimension, svgId SvgIdInterface) (*UserInterface, error) {
idxInterface := security.IdentityArray(kp.TotalAttrs())
userInterface := UserInterface{
IdxInterface: idxInterface,
SvgId: svgId,
Kp: kp,
}
if err := userInterface.RandomShuffle(); err != nil {
return nil, err
}
return &userInterface, nil
}
func (u *UserInterface) RandomShuffle() error {
err := u.shuffleKeys()
keypadView, err := u.InterfaceMatrix()
if err != nil {
return err
}
setView, err := security.MatrixTranspose(keypadView)
if err != nil {
return err
}
for idx, set := range setView {
err := security.FisherYatesShuffle(&set)
if err != nil {
return nil
}
setView[idx] = set
}
keypadView, err = security.MatrixTranspose(setView)
if err != nil {
return err
}
u.IdxInterface = security.MatrixToList(keypadView)
return nil
}
func (u *UserInterface) InterfaceMatrix() ([][]int, error) {
return security.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
}
func (u *UserInterface) SetViewMatrix() ([][]int, error) {
keypadView, err := u.InterfaceMatrix()
if err != nil {
return nil, err
}
return security.MatrixTranspose(keypadView)
}
func (u *UserInterface) DisperseInterface() error {
if !u.Kp.IsDispersable() {
return config.ErrInterfaceNotDispersible
}
err := u.shuffleKeys()
if err != nil {
return err
}
err = u.randomAttributeRotation()
if err != nil {
return err
}
return nil
}
func (u *UserInterface) shuffleKeys() error {
userInterfaceMatrix, err := security.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
if err != nil {
return err
}
err = security.FisherYatesShuffle[[]int](&userInterfaceMatrix)
if err != nil {
return err
}
u.IdxInterface = security.MatrixToList(userInterfaceMatrix)
return nil
}
func (u *UserInterface) randomAttributeRotation() error {
userInterface, err := u.InterfaceMatrix()
if err != nil {
return err
}
transposeUserInterface, err := security.MatrixTranspose(userInterface)
attrRotation, err := security.RandomPermutation(len(transposeUserInterface))
if err != nil {
return err
}
for idx, attrSet := range transposeUserInterface {
rotation := attrRotation[idx]
transposeUserInterface[idx] = append(attrSet[rotation:], attrSet[:rotation]...)
}
userInterface, err = security.MatrixTranspose(transposeUserInterface)
if err != nil {
return err
}
u.IdxInterface = security.MatrixToList(userInterface)
return nil
}
func (u *UserInterface) AttributeAdjacencyGraph() (map[int]utils.Set[int], error) {
interfaceKeypad, err := u.InterfaceMatrix()
if err != nil {
return nil, err
}
graph := make(map[int]utils.Set[int])
for _, key := range interfaceKeypad {
keySet := utils.NewSetFromSlice(key)
for _, attr := range key {
attrAdjacency := keySet.Copy()
attrAdjacency.Remove(attr)
graph[attr] = attrAdjacency
}
}
return graph, nil
}
func (u *UserInterface) PartialInterfaceShuffle() error {
err := u.shuffleKeys()
if err != nil {
return err
}
numbOfSelectedSets := u.Kp.AttrsPerKey / 2
if u.Kp.AttrsPerKey&1 == 1 {
numbOfSelectedSets += security.Choice[int]([]int{0, 1})
}
setIdxs, err := security.RandomPermutation(u.Kp.AttrsPerKey)
if err != nil {
return err
}
selectedSets := utils.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets])
keypadSetView, err := u.SetViewMatrix()
if err != nil {
return err
}
interfaceBySet := make([][]int, u.Kp.AttrsPerKey)
for idx, attrs := range keypadSetView {
if selectedSets.Contains(idx) {
err = security.FisherYatesShuffle[int](&attrs)
if err != nil {
return err
}
}
interfaceBySet[idx] = attrs
}
keypadView, err := security.MatrixTranspose(interfaceBySet)
if err != nil {
return err
}
u.IdxInterface = security.MatrixToList(keypadView)
return nil
}
func (u *UserInterface) GetAttrIdxByKeyNumbSetIdx(setIdx int, keyNumb int) (int, error) {
if keyNumb < 0 || u.Kp.NumbOfKeys <= keyNumb {
log.Printf("keyNumb %d is out of range 0-%d", keyNumb, u.Kp.NumbOfKeys)
return -1, config.ErrKeyIndexOutOfRange
}
if setIdx < 0 || u.Kp.AttrsPerKey <= setIdx {
log.Printf("setIdx %d is out of range 0-%d", setIdx, u.Kp.AttrsPerKey)
return -1, config.ErrAttributeIndexOutOfRange
}
keypadView, err := u.InterfaceMatrix()
if err != nil {
return -1, err
}
return keypadView[keyNumb][setIdx], nil
}

View File

@@ -1,200 +0,0 @@
package models
import (
"github.com/google/uuid"
"go-nkode/config"
"go-nkode/internal/security"
py "go-nkode/internal/utils"
"log"
"sort"
)
type UserSignSession struct {
Id SessionId
CustomerId CustomerId
LoginUserInterface UserInterface
Kp KeypadDimension
SetIdxInterface IdxInterface
ConfirmIdxInterface IdxInterface
SetKeySelection KeySelection
UserEmail UserEmail
Reset bool
Expire int
Colors []RGBColor
}
func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId CustomerId, svgInterface SvgIdInterface, reset bool) (*UserSignSession, error) {
loginInterface, err := NewUserInterface(&kp, svgInterface)
if err != nil {
return nil, err
}
signupInterface, colors, err := signupInterface(*loginInterface, kp)
if err != nil {
return nil, err
}
session := UserSignSession{
Id: SessionId(uuid.New()),
CustomerId: customerId,
LoginUserInterface: *loginInterface,
SetIdxInterface: signupInterface.IdxInterface,
ConfirmIdxInterface: nil,
SetKeySelection: nil,
UserEmail: userEmail,
Kp: kp,
Reset: reset,
Colors: colors,
}
return &session, nil
}
func (s *UserSignSession) DeducePasscode(confirmKeyEntry KeySelection) ([]int, error) {
validEntry := py.All[int](confirmKeyEntry, func(i int) bool {
return 0 <= i && i < s.Kp.NumbOfKeys
})
if !validEntry {
log.Printf("Invalid Key entry. One or more key index: %#v, not in range 0-%d", confirmKeyEntry, s.Kp.NumbOfKeys)
return nil, config.ErrKeyIndexOutOfRange
}
if s.SetIdxInterface == nil {
log.Print("signup session set interface is nil")
return nil, config.ErrIncompleteUserSignupSession
}
if s.ConfirmIdxInterface == nil {
log.Print("signup session confirm interface is nil")
return nil, config.ErrIncompleteUserSignupSession
}
if s.SetKeySelection == nil {
log.Print("signup session set key entry is nil")
return nil, config.ErrIncompleteUserSignupSession
}
if s.UserEmail == "" {
log.Print("signup session username is nil")
return nil, config.ErrIncompleteUserSignupSession
}
if len(confirmKeyEntry) != len(s.SetKeySelection) {
log.Printf("confirm and set key entry length mismatch %d != %d", len(confirmKeyEntry), len(s.SetKeySelection))
return nil, config.ErrSetConfirmSignupMismatch
}
passcodeLen := len(confirmKeyEntry)
setKeyVals, err := s.getSelectedKeyVals(s.SetKeySelection, s.SetIdxInterface)
if err != nil {
return nil, err
}
confirmKeyVals, err := s.getSelectedKeyVals(confirmKeyEntry, s.ConfirmIdxInterface)
passcode := make([]int, passcodeLen)
for idx := 0; idx < passcodeLen; idx++ {
setKey := py.NewSetFromSlice[int](setKeyVals[idx])
confirmKey := py.NewSetFromSlice[int](confirmKeyVals[idx])
intersection := setKey.Intersect(confirmKey)
if intersection.Size() < 1 {
log.Printf("set and confirm do not intersect at index %d", idx)
return nil, config.ErrSetConfirmSignupMismatch
}
if intersection.Size() > 1 {
log.Printf("set and confirm intersect at more than one point at index %d", idx)
return nil, config.ErrSetConfirmSignupMismatch
}
intersectionSlice := intersection.ToSlice()
passcode[idx] = intersectionSlice[0]
}
return passcode, nil
}
func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface, error) {
validKeySelection := py.All[int](keySelection, func(i int) bool {
return 0 <= i && i < s.Kp.NumbOfKeys
})
if !validKeySelection {
log.Printf("one or key selection is out of range 0-%d", s.Kp.NumbOfKeys-1)
return nil, config.ErrKeyIndexOutOfRange
}
s.SetKeySelection = keySelection
setKp := s.SignupKeypad()
setInterface := UserInterface{IdxInterface: s.SetIdxInterface, Kp: &setKp}
err := setInterface.DisperseInterface()
if err != nil {
return nil, err
}
s.ConfirmIdxInterface = setInterface.IdxInterface
return s.ConfirmIdxInterface, nil
}
func (s *UserSignSession) getSelectedKeyVals(keySelections KeySelection, userInterface []int) ([][]int, error) {
signupKp := s.SignupKeypad()
keypadInterface, err := security.ListToMatrix(userInterface, signupKp.AttrsPerKey)
if err != nil {
return nil, err
}
keyVals := make([][]int, len(keySelections))
for idx, keyIdx := range keySelections {
keyVals[idx] = keypadInterface[keyIdx]
}
return keyVals, nil
}
func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*UserInterface, []RGBColor, error) {
// This method randomly drops sets from the base user interface so it is a square and dispersable matrix
if kp.IsDispersable() {
return nil, nil, config.ErrKeypadIsNotDispersible
}
err := baseUserInterface.RandomShuffle()
if err != nil {
return nil, nil, err
}
// attributes are arranged by key interfaceMatrix
interfaceMatrix, err := baseUserInterface.InterfaceMatrix()
if err != nil {
return nil, nil, err
}
// attributes are arranged by set
attrSetView, err := security.MatrixTranspose(interfaceMatrix)
if err != nil {
return nil, nil, err
}
setIdxs := security.IdentityArray(kp.AttrsPerKey)
if err := security.FisherYatesShuffle[int](&setIdxs); err != nil {
return nil, nil, err
}
setIdxs = setIdxs[:kp.NumbOfKeys]
sort.Ints(setIdxs)
selectedSets := make([][]int, kp.NumbOfKeys)
selectedColors := make([]RGBColor, kp.NumbOfKeys)
for idx, setIdx := range setIdxs {
selectedSets[idx] = attrSetView[setIdx]
selectedColors[idx] = SetColors[setIdx]
}
// convert set view back into key view
selectedSets, err = security.MatrixTranspose(selectedSets)
if err != nil {
return nil, nil, err
}
signupUserInterface := UserInterface{
IdxInterface: security.MatrixToList(selectedSets),
Kp: &KeypadDimension{
AttrsPerKey: kp.NumbOfKeys,
NumbOfKeys: kp.NumbOfKeys,
},
}
return &signupUserInterface, selectedColors, nil
}
func (s *UserSignSession) SignupKeypad() KeypadDimension {
return KeypadDimension{
AttrsPerKey: s.Kp.NumbOfKeys,
NumbOfKeys: s.Kp.NumbOfKeys,
}
}

View File

@@ -1,133 +0,0 @@
package models
import (
"github.com/stretchr/testify/assert"
py "go-nkode/internal/utils"
"testing"
)
func TestUserCipherKeys_EncipherSaltHashCode(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 8}
maxNKodeLen := 10
customerAttrs, err := NewCustomerAttributes()
assert.NoError(t, err)
setVals, err := customerAttrs.SetValsForKp(kp)
assert.NoError(t, err)
attrVals, err := customerAttrs.AttrValsForKp(kp)
assert.NoError(t, err)
newUser, err := NewUserCipherKeys(&kp, setVals, maxNKodeLen)
assert.NoError(t, err)
passcodeIdx := []int{0, 1, 2, 3}
encipher0, err := newUser.EncipherSaltHashCode(passcodeIdx, attrVals)
assert.NoError(t, err)
err = newUser.ValidPassword(encipher0, passcodeIdx, attrVals)
assert.NoError(t, err)
passcodeIdxInvalid := []int{1, 0, 3, 2}
assert.NoError(t, err)
err = newUser.ValidPassword(encipher0, passcodeIdxInvalid, attrVals)
assert.Error(t, err)
}
func TestUserCipherKeys_EncipherDecipherMask(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 8}
maxNKodeLen := 10
customerAttrs, err := NewCustomerAttributes()
assert.NoError(t, err)
setVals, err := customerAttrs.SetValsForKp(kp)
assert.NoError(t, err)
attrVals, err := customerAttrs.AttrValsForKp(kp)
assert.NoError(t, err)
newUser, err := NewUserCipherKeys(&kp, setVals, maxNKodeLen)
assert.NoError(t, err)
passcodeIdx := []int{0, 1, 2, 3}
originalSetVals := make([]uint64, len(passcodeIdx))
for idx, val := range passcodeIdx {
attr := attrVals[val]
originalSetVals[idx], err = customerAttrs.GetAttrSetVal(attr, kp)
assert.NoError(t, err)
}
encipheredCode, err := newUser.EncipherNKode(passcodeIdx, *customerAttrs)
assert.NoError(t, err)
passcodeSetVals, err := newUser.DecipherMask(encipheredCode.Mask, setVals, len(passcodeIdx))
assert.NoError(t, err)
for idx, setVal := range passcodeSetVals {
assert.Equal(t, setVal, originalSetVals[idx])
}
}
func TestUserInterface_RandomShuffle(t *testing.T) {
kp := KeypadDimension{
AttrsPerKey: 10,
NumbOfKeys: 8,
}
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err)
userInterfaceCopy := make([]int, len(userInterface.IdxInterface))
copy(userInterfaceCopy, userInterface.IdxInterface)
err = userInterface.RandomShuffle()
assert.NoError(t, err)
assert.Equal(t, len(userInterface.IdxInterface), len(userInterfaceCopy))
equalCount := 0
for idx, val := range userInterface.IdxInterface {
if val == userInterfaceCopy[idx] {
equalCount++
}
}
assert.NotEqual(t, equalCount, len(userInterface.IdxInterface))
}
func TestUserInterface_DisperseInterface(t *testing.T) {
for idx := 0; idx < 10000; idx++ {
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err)
preDispersion, err := userInterface.AttributeAdjacencyGraph()
assert.NoError(t, err)
err = userInterface.DisperseInterface()
assert.NoError(t, err)
postDispersion, err := userInterface.AttributeAdjacencyGraph()
assert.Equal(t, len(postDispersion), len(preDispersion))
for attr, adjAttrs := range preDispersion {
postAdjAttrs := postDispersion[attr]
assert.Equal(t, adjAttrs.Size(), postAdjAttrs.Size())
assert.True(t, adjAttrs.IsDisjoint(postAdjAttrs))
}
}
}
func TestUserInterface_PartialInterfaceShuffle(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err)
preShuffle := userInterface.IdxInterface
err = userInterface.PartialInterfaceShuffle()
assert.NoError(t, err)
postShuffle := userInterface.IdxInterface
shuffleCompare := make([]bool, len(postShuffle))
for idx, val := range preShuffle {
shuffleCompare[idx] = val == postShuffle[idx]
}
allTrue := py.All[bool](shuffleCompare, func(n bool) bool {
return n == true
})
assert.False(t, allTrue)
allFalse := py.All[bool](shuffleCompare, func(n bool) bool {
return n == false
})
assert.False(t, allFalse)
}