implement and test sql db accessor

This commit is contained in:
2024-08-27 19:27:52 -05:00
parent e6947e714d
commit fe06a95c98
24 changed files with 745 additions and 403 deletions

91
core/model/customer.go Normal file
View File

@@ -0,0 +1,91 @@
package m
import (
"errors"
"fmt"
"github.com/google/uuid"
"go-nkode/hashset"
py "go-nkode/py-builtin"
"go-nkode/util"
)
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 {
return errors.New(fmt.Sprintf("NKode length %d is too short. Minimum nKode length is %d", nkodeLen, c.NKodePolicy.MinNkodeLen))
}
validIdx := py.All[int](passcodeAttrIdx, func(i int) bool {
return i >= 0 && i < kp.TotalAttrs()
})
if !validIdx {
return errors.New(fmt.Sprintf("One or more idx out of range 0-%d in IsValidNKode", kp.TotalAttrs()-1))
}
passcodeSetVals := make(hashset.Set[uint64])
passcodeAttrVals := make(hashset.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 errors.New(fmt.Sprintf("passcode has two few distinct sets min %d, has %d", c.NKodePolicy.DistinctSets, passcodeSetVals.Size()))
}
if passcodeAttrVals.Size() < c.NKodePolicy.DistinctAttributes {
return errors.New(fmt.Sprintf("passcode has two few distinct attributes min %d, has %d", c.NKodePolicy.DistinctAttributes, passcodeAttrVals.Size()))
}
return nil
}
func (c *Customer) RenewKeys() ([]uint64, []uint64) {
oldAttrs := make([]uint64, len(c.Attributes.AttrVals))
oldSets := make([]uint64, len(c.Attributes.SetVals))
copy(oldAttrs, c.Attributes.AttrVals)
copy(oldSets, c.Attributes.SetVals)
err := c.Attributes.Renew()
if err != nil {
panic(err)
}
attrsXor, err := util.XorLists(oldAttrs, c.Attributes.AttrVals)
if err != nil {
panic(err)
}
setXor, err := util.XorLists(oldSets, c.Attributes.SetVals)
if err != nil {
panic(err)
}
return setXor, attrsXor
}

View File

@@ -0,0 +1,97 @@
package m
import (
"errors"
"fmt"
"go-nkode/util"
)
type CustomerAttributes struct {
AttrVals []uint64
SetVals []uint64
}
func NewCustomerAttributes() (*CustomerAttributes, error) {
attrVals, errAttr := util.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs())
if errAttr != nil {
return nil, errAttr
}
setVals, errSet := util.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey)
if errSet != nil {
return nil, errSet
}
customerAttrs := CustomerAttributes{
AttrVals: attrVals,
SetVals: setVals,
}
return &customerAttrs, nil
}
func NewCustomerAttributesFromBytes(attrBytes []byte, setBytes []byte) CustomerAttributes {
return CustomerAttributes{
AttrVals: util.ByteArrToUint64Arr(attrBytes),
SetVals: util.ByteArrToUint64Arr(setBytes),
}
}
func (c *CustomerAttributes) Renew() error {
attrVals, errAttr := util.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs())
if errAttr != nil {
return errAttr
}
setVals, errSet := util.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey)
if errSet != nil {
return errSet
}
c.AttrVals = attrVals
c.SetVals = setVals
return nil
}
func (c *CustomerAttributes) IndexOfAttr(attrVal uint64) int {
// TODO: should this be mapped instead?
return util.IndexOf[uint64](c.AttrVals, attrVal)
}
func (c *CustomerAttributes) IndexOfSet(setVal uint64) (int, error) {
// TODO: should this be mapped instead?
idx := util.IndexOf[uint64](c.SetVals, setVal)
if idx == -1 {
return -1, errors.New(fmt.Sprintf("Set Val %d is invalid", setVal))
}
return idx, nil
}
func (c *CustomerAttributes) GetAttrSetVal(attrVal uint64, userKeypad KeypadDimension) (uint64, error) {
indexOfAttr := c.IndexOfAttr(attrVal)
if indexOfAttr == -1 {
return 0, errors.New(fmt.Sprintf("No attribute %d", attrVal))
}
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 util.Uint64ArrToByteArr(c.AttrVals)
}
func (c *CustomerAttributes) SetBytes() []byte {
return util.Uint64ArrToByteArr(c.SetVals)
}

272
core/model/nkode_handler.go Normal file
View File

@@ -0,0 +1,272 @@
package m
import (
"encoding/json"
"go-nkode/core/api"
"log"
"net/http"
)
type NKodeHandler struct {
Api NKodeAPIInterface
}
func (h *NKodeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case api.CreateNewCustomer:
h.CreateNewCustomerHandler(w, r)
case api.GenerateSignupInterface:
h.GenerateSignupInterfaceHandler(w, r)
case api.SetNKode:
h.SetNKodeHandler(w, r)
case api.ConfirmNKode:
h.ConfirmNKodeHandler(w, r)
case api.GetLoginInterface:
h.GetLoginInterfaceHandler(w, r)
case api.Login:
h.LoginHandler(w, r)
case api.RenewAttributes:
h.RenewAttributesHandler(w, r)
default:
w.WriteHeader(http.StatusNotFound)
_, err := w.Write([]byte("404 not found"))
log.Println(err)
}
}
func (h *NKodeHandler) CreateNewCustomerHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
methodNotAllowed(w)
return
}
var customerPost NewCustomerPost
err := decodeJson(w, r, &customerPost)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
customerId, err := h.Api.CreateNewCustomer(customerPost.NKodePolicy)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
respBody := CreateNewCustomerResp{
CustomerId: *customerId,
}
respBytes, err := json.Marshal(respBody)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
_, err = w.Write(respBytes)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *NKodeHandler) GenerateSignupInterfaceHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
methodNotAllowed(w)
return
}
var signupPost GenerateSignupInterfacePost
err := decodeJson(w, r, &signupPost)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
resp, err := h.Api.GenerateSignupInterface(signupPost.CustomerId, KeypadDefault)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
respBytes, err := json.Marshal(resp)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
_, err = w.Write(respBytes)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *NKodeHandler) SetNKodeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
methodNotAllowed(w)
return
}
var setNKodePost SetNKodePost
err := decodeJson(w, r, &setNKodePost)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
confirmInterface, err := h.Api.SetNKode(setNKodePost.Username, setNKodePost.CustomerId, setNKodePost.SessionId, setNKodePost.KeySelection)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
respBody := SetNKodeResp{UserInterface: confirmInterface}
respBytes, err := json.Marshal(respBody)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
_, err = w.Write(respBytes)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *NKodeHandler) ConfirmNKodeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
methodNotAllowed(w)
return
}
var confirmNKodePost ConfirmNKodePost
err := decodeJson(w, r, &confirmNKodePost)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
err = h.Api.ConfirmNKode(confirmNKodePost.CustomerId, confirmNKodePost.SessionId, confirmNKodePost.KeySelection)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *NKodeHandler) GetLoginInterfaceHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
methodNotAllowed(w)
return
}
var loginInterfacePost GetLoginInterfacePost
err := decodeJson(w, r, &loginInterfacePost)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
loginInterface, err := h.Api.GetLoginInterface(loginInterfacePost.Username, loginInterfacePost.CustomerId)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
respBody := GetLoginInterfaceResp{UserInterface: loginInterface}
respBytes, err := json.Marshal(respBody)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
_, err = w.Write(respBytes)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *NKodeHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
methodNotAllowed(w)
return
}
var loginPost LoginPost
err := decodeJson(w, r, &loginPost)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
err = h.Api.Login(loginPost.CustomerId, loginPost.Username, loginPost.KeySelection)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *NKodeHandler) RenewAttributesHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
methodNotAllowed(w)
return
}
var renewAttributesPost RenewAttributesPost
err := decodeJson(w, r, &renewAttributesPost)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
err = h.Api.RenewAttributes(renewAttributesPost.CustomerId)
if err != nil {
internalServerErrorHandler(w)
log.Fatal(err)
return
}
w.WriteHeader(http.StatusOK)
}
func decodeJson(w http.ResponseWriter, r *http.Request, post any) error {
err := json.NewDecoder(r.Body).Decode(&post)
if err != nil {
internalServerErrorHandler(w)
return err
}
return nil
}
func internalServerErrorHandler(w http.ResponseWriter) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 Internal Server Error"))
}
func methodNotAllowed(w http.ResponseWriter) {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("405 method not allowed"))
}

View File

@@ -31,5 +31,8 @@ func (p *NKodePolicy) ValidLength(nkodeLen int) error {
if nkodeLen < p.MinNkodeLen || nkodeLen > p.MaxNkodeLen {
return InvalidNKodeLen
}
// TODO: validate Max > Min
// Validate lockout
// Add Lockout To User
return nil
}

20
core/model/test_helper.go Normal file
View File

@@ -0,0 +1,20 @@
package m
import (
"errors"
"fmt"
"go-nkode/util"
)
func SelectKeyByAttrIdx(interfaceUser []int, passcodeIdxs []int, keypadSize KeypadDimension) ([]int, error) {
selectedKeys := make([]int, len(passcodeIdxs))
for idx := range passcodeIdxs {
attrIdx := util.IndexOf[int](interfaceUser, passcodeIdxs[idx])
keyNumb := attrIdx / keypadSize.AttrsPerKey
if keyNumb < 0 || 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
}

58
core/model/user.go Normal file
View File

@@ -0,0 +1,58 @@
package m
import (
"go-nkode/util"
)
type User struct {
Id UserId
CustomerId CustomerId
Username Username
EncipheredPasscode EncipheredNKode
Kp KeypadDimension
UserKeys UserCipherKeys
Interface UserInterface
Renew bool
}
func (u *User) DecipherMask(setVals []uint64, passcodeLen int) ([]uint64, error) {
return u.UserKeys.DecipherMask(u.EncipheredPasscode.Mask, setVals, passcodeLen)
}
func (u *User) RenewKeys(setXor []uint64, attrXor []uint64) error {
u.Renew = true
var err error
u.UserKeys.SetKey, err = util.XorLists(setXor[:u.Kp.AttrsPerKey], u.UserKeys.SetKey)
if err != nil {
panic(err)
}
u.UserKeys.AlphaKey, err = util.XorLists(attrXor[:u.Kp.TotalAttrs()], u.UserKeys.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.UserKeys.MaxNKodeLen)
if err != nil {
return err
}
encipheredPasscode, err := newKeys.EncipherNKode(passcodeAttrIdx, customerAttributes)
if err != nil {
return err
}
u.UserKeys = *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
}

View File

@@ -0,0 +1,193 @@
package m
import (
"crypto/sha256"
"errors"
"go-nkode/util"
"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 := util.GenerateRandomNonRepeatingUint64(kp.AttrsPerKey)
if err != nil {
return nil, err
}
setKey, err = util.XorLists(setKey, setVals)
if err != nil {
return nil, err
}
alphakey, _ := util.GenerateRandomNonRepeatingUint64(kp.TotalAttrs())
passKey, _ := util.GenerateRandomNonRepeatingUint64(maxNKodeLen)
maskKey, _ := util.GenerateRandomNonRepeatingUint64(maxNKodeLen)
salt, _ := util.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, errors.New("user mask length exceeds max nkode length")
}
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, err := u.saltAndDigest(passcodeCipher)
if err != nil {
return err
}
err = bcrypt.CompareHashAndPassword(hashBytes, passwordDigest)
if err != nil {
return err
}
return nil
}
func (u *UserCipherKeys) EncipherSaltHashCode(passcodeAttrIdx []int, attrVals []uint64) (string, error) {
passcodeCipher := u.encipherCode(passcodeAttrIdx, attrVals)
passcodeDigest, err := u.saltAndDigest(passcodeCipher)
if err != nil {
return "", err
}
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[idx]
pass := u.PassKey[idx]
passcodeCipher[idx] = alpha ^ pass ^ attrVal
}
return passcodeCipher
}
func (u *UserCipherKeys) saltAndDigest(passcode []uint64) ([]byte, error) {
passcodeBytes := util.Uint64ArrToByteArr(passcode)
passcodeBytes = append(passcodeBytes, u.Salt...)
h := sha256.New()
passcodeSha, err := h.Write(passcodeBytes)
if err != nil {
return nil, err
}
passcodeDigest := uint64(passcodeSha)
return util.Uint64ArrToByteArr([]uint64{passcodeDigest}), 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 := util.EncodeBase64Str(cipheredMask)
return mask, nil
}
func (u *UserCipherKeys) DecipherMask(mask string, setVals []uint64, passcodeLen int) ([]uint64, error) {
decodedMask, err := util.DecodeBase64Str(mask)
if err != nil {
return nil, err
}
decipheredMask, err := util.XorLists(decodedMask, u.MaskKey)
if err != nil {
return nil, err
}
setKeyRandComponent, err := util.XorLists(setVals, u.SetKey)
if err != nil {
return nil, err
}
passcodeSet := make([]uint64, passcodeLen)
for idx, setCipher := range decipheredMask[:passcodeLen] {
setIdx := util.IndexOf(setKeyRandComponent, setCipher)
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)
encipheredCode := EncipheredNKode{
Code: code,
Mask: mask,
}
return &encipheredCode, nil
}

View File

@@ -0,0 +1,191 @@
package m
import (
"errors"
"fmt"
"go-nkode/hashset"
"go-nkode/util"
)
type UserInterface struct {
IdxInterface IdxInterface
Kp *KeypadDimension
}
func NewUserInterface(kp *KeypadDimension) (*UserInterface, error) {
idxInterface := util.IdentityArray(kp.TotalAttrs())
userInterface := UserInterface{
IdxInterface: idxInterface,
Kp: kp,
}
err := userInterface.RandomShuffle()
if 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 := util.MatrixTranspose(keypadView)
if err != nil {
return err
}
for idx, set := range setView {
err := util.FisherYatesShuffle(&set)
if err != nil {
return nil
}
setView[idx] = set
}
keypadView, err = util.MatrixTranspose(setView)
if err != nil {
return err
}
u.IdxInterface = util.MatrixToList(keypadView)
return nil
}
func (u *UserInterface) InterfaceMatrix() ([][]int, error) {
return util.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
}
func (u *UserInterface) SetViewMatrix() ([][]int, error) {
keypadView, err := u.InterfaceMatrix()
if err != nil {
return nil, err
}
return util.MatrixTranspose(keypadView)
}
func (u *UserInterface) DisperseInterface() error {
if !u.Kp.IsDispersable() {
panic("interface is not dispersable")
}
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 := util.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
if err != nil {
return err
}
err = util.FisherYatesShuffle[[]int](&userInterfaceMatrix)
if err != nil {
return err
}
u.IdxInterface = util.MatrixToList(userInterfaceMatrix)
return nil
}
func (u *UserInterface) randomAttributeRotation() error {
userInterface, err := u.InterfaceMatrix()
if err != nil {
return err
}
transposeUserInterface, err := util.MatrixTranspose(userInterface)
attrRotation, err := util.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 = util.MatrixTranspose(transposeUserInterface)
if err != nil {
return err
}
u.IdxInterface = util.MatrixToList(userInterface)
return nil
}
func (u *UserInterface) AttributeAdjacencyGraph() (map[int]hashset.Set[int], error) {
interfaceKeypad, err := u.InterfaceMatrix()
if err != nil {
return nil, err
}
graph := make(map[int]hashset.Set[int])
for _, key := range interfaceKeypad {
keySet := hashset.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 += util.Choice[int]([]int{0, 1})
}
setIdxs, err := util.RandomPermutation(u.Kp.AttrsPerKey)
if err != nil {
return err
}
selectedSets := hashset.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 = util.FisherYatesShuffle[int](&attrs)
if err != nil {
return err
}
}
interfaceBySet[idx] = attrs
}
keypadView, err := util.MatrixTranspose(interfaceBySet)
if err != nil {
return err
}
u.IdxInterface = util.MatrixToList(keypadView)
return nil
}
func (u *UserInterface) GetAttrIdxByKeyNumbSetIdx(setIdx int, keyNumb int) (int, error) {
if keyNumb < 0 || u.Kp.NumbOfKeys <= keyNumb {
return -1, errors.New(fmt.Sprintf("keyNumb %d is out of range 0-%d", keyNumb, u.Kp.NumbOfKeys))
}
if setIdx < 0 || u.Kp.AttrsPerKey <= setIdx {
return -1, errors.New(fmt.Sprintf("setIdx %d is out of range 0-%d", setIdx, u.Kp.AttrsPerKey))
}
keypadView, err := u.InterfaceMatrix()
if err != nil {
return -1, err
}
return keypadView[keyNumb][setIdx], nil
}

126
core/model/user_test.go Normal file
View File

@@ -0,0 +1,126 @@
package m
import (
"github.com/stretchr/testify/assert"
py "go-nkode/py-builtin"
"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)
}
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,
}
userInterface, err := NewUserInterface(&kp)
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}
userInterface, err := NewUserInterface(&kp)
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}
userInterface, err := NewUserInterface(&kp)
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)
}