initial commit
This commit is contained in:
1
users/user.go
Normal file
1
users/user.go
Normal file
@@ -0,0 +1 @@
|
||||
package users
|
||||
181
users/user_cipher_keys.go
Normal file
181
users/user_cipher_keys.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go-nkode/customer"
|
||||
"go-nkode/models"
|
||||
"go-nkode/util"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type UserCipherKeys struct {
|
||||
AlphaKey []uint64
|
||||
SetKey []uint64
|
||||
PassKey []uint64
|
||||
MaskKey []uint64
|
||||
Salt []byte
|
||||
MaxNKodeLen int
|
||||
}
|
||||
|
||||
func NewUserCipherKeys(keypadSize models.KeypadSize, setVals []uint64, maxNKodeLen int) (*UserCipherKeys, error) {
|
||||
if len(setVals) != keypadSize.AttrsPerKey {
|
||||
return nil, errors.New(fmt.Sprintf("setVals len != attrsPerKey, %d, %d", len(setVals), keypadSize.AttrsPerKey))
|
||||
}
|
||||
|
||||
setKey, err := util.GenerateRandomNonRepeatingUint64(keypadSize.AttrsPerKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setKey, err = util.XorLists(setKey, setVals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
alphakey, _ := util.GenerateRandomNonRepeatingUint64(keypadSize.AttrsPerKey)
|
||||
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,
|
||||
}
|
||||
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 []byte, passcodeAttrIdx []int, customerAttrs customer.CustomerAttributes) error {
|
||||
passcodeCipher := u.encipherCode(passcodeAttrIdx, customerAttrs)
|
||||
passwordDigest, err := u.saltAndDigest(passcodeCipher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bcrypt.CompareHashAndPassword(hashedPassword, passwordDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) EncipherSaltHashCode(passcodeAttrIdx []int, customerAttrs customer.CustomerAttributes) ([]byte, error) {
|
||||
passcodeCipher := u.encipherCode(passcodeAttrIdx, customerAttrs)
|
||||
|
||||
passcodeDigest, err := u.saltAndDigest(passcodeCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u.hashPasscode(passcodeDigest)
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) encipherCode(passcodeAttrIdx []int, customerAttrs customer.CustomerAttributes) []uint64 {
|
||||
passcodeLen := len(passcodeAttrIdx)
|
||||
|
||||
passcodeCipher := make([]uint64, u.MaxNKodeLen)
|
||||
|
||||
for idx := 0; idx < passcodeLen; idx++ {
|
||||
attrIdx := passcodeAttrIdx[idx]
|
||||
alpha := u.AlphaKey[attrIdx]
|
||||
attrVal := customerAttrs.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 customer.CustomerAttributes) (string, error) {
|
||||
paddedPasscodeSets, err := u.PadUserMask(passcodeSet, customerAttrs.SetVals)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cipheredMask := make([]uint64, len(paddedPasscodeSets))
|
||||
for idx := range paddedPasscodeSets {
|
||||
setIdx := customerAttrs.IndexOfSet(paddedPasscodeSets[idx])
|
||||
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 customer.CustomerAttributes) (*models.EncipheredNKode, error) {
|
||||
code, err := u.EncipherSaltHashCode(passcodeAttrIdx, customerAttrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
passcodeSet := make([]uint64, len(passcodeAttrIdx))
|
||||
|
||||
for idx := range passcodeSet {
|
||||
passcodeAttr := customerAttrs.AttrVals[passcodeAttrIdx[idx]]
|
||||
passcodeSet[idx], err = customerAttrs.GetAttrSetVal(passcodeAttr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
mask, err := u.EncipherMask(passcodeSet, customerAttrs)
|
||||
encipheredCode := models.EncipheredNKode{
|
||||
Code: string(code),
|
||||
Mask: mask,
|
||||
}
|
||||
return &encipheredCode, nil
|
||||
}
|
||||
176
users/user_interface.go
Normal file
176
users/user_interface.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go-nkode/hashset"
|
||||
"go-nkode/models"
|
||||
"go-nkode/util"
|
||||
)
|
||||
|
||||
type UserInterface struct {
|
||||
IdxInterface []int
|
||||
KeypadSize models.KeypadSize
|
||||
}
|
||||
|
||||
func NewUserInterface(keypadSize models.KeypadSize) (*UserInterface, error) {
|
||||
idxInterface := util.IdentityArray(keypadSize.TotalAttrs())
|
||||
userInterface := UserInterface{
|
||||
IdxInterface: idxInterface,
|
||||
KeypadSize: keypadSize,
|
||||
}
|
||||
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.KeypadSize.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.KeypadSize.IsDispersable() {
|
||||
return errors.New("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.KeypadSize.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.KeypadSize.AttrsPerKey / 2
|
||||
if u.KeypadSize.AttrsPerKey&1 == 1 {
|
||||
numbOfSelectedSets += util.Choice[int]([]int{0, 1})
|
||||
}
|
||||
setIdxs, err := util.RandomPermutation(u.KeypadSize.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.KeypadSize.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
|
||||
}
|
||||
120
users/user_test.go
Normal file
120
users/user_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go-nkode/customer"
|
||||
"go-nkode/models"
|
||||
py_builtin "go-nkode/py-builtin"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserCipherKeys_EncipherSaltHashCode(t *testing.T) {
|
||||
keypadSize := models.KeypadSize{AttrsPerKey: 10, NumbOfKeys: 5}
|
||||
maxNKodeLen := 10
|
||||
customerAttrs, err := customer.NewCustomerAttributes(keypadSize)
|
||||
assert.NoError(t, err)
|
||||
newUser, err := NewUserCipherKeys(keypadSize, customerAttrs.SetVals, maxNKodeLen)
|
||||
assert.NoError(t, err)
|
||||
passcodeIdx := []int{0, 1, 2, 3}
|
||||
encipher0, err := newUser.EncipherSaltHashCode(passcodeIdx, *customerAttrs)
|
||||
assert.NoError(t, err)
|
||||
err = newUser.ValidPassword(encipher0, passcodeIdx, *customerAttrs)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUserCipherKeys_EncipherDecipherMask(t *testing.T) {
|
||||
keypadSize := models.KeypadSize{AttrsPerKey: 10, NumbOfKeys: 5}
|
||||
maxNKodeLen := 10
|
||||
|
||||
customerAttrs, err := customer.NewCustomerAttributes(keypadSize)
|
||||
assert.NoError(t, err)
|
||||
newUser, err := NewUserCipherKeys(keypadSize, customerAttrs.SetVals, maxNKodeLen)
|
||||
assert.NoError(t, err)
|
||||
passcodeIdx := []int{0, 1, 2, 3}
|
||||
originalSetVals := make([]uint64, len(passcodeIdx))
|
||||
|
||||
for idx, val := range passcodeIdx {
|
||||
attr := customerAttrs.AttrVals[val]
|
||||
originalSetVals[idx], err = customerAttrs.GetAttrSetVal(attr)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
encipheredCode, err := newUser.EncipherNKode(passcodeIdx, *customerAttrs)
|
||||
assert.NoError(t, err)
|
||||
passcodeSetVals, err := newUser.DecipherMask(encipheredCode.Mask, customerAttrs.SetVals, len(passcodeIdx))
|
||||
assert.NoError(t, err)
|
||||
|
||||
for idx, setVal := range passcodeSetVals {
|
||||
assert.Equal(t, setVal, originalSetVals[idx])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserInterface_RandomShuffle(t *testing.T) {
|
||||
keypadSize := models.KeypadSize{
|
||||
AttrsPerKey: 10,
|
||||
NumbOfKeys: 5,
|
||||
}
|
||||
userInterface, err := NewUserInterface(keypadSize)
|
||||
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++ {
|
||||
keypadSize := models.KeypadSize{AttrsPerKey: 7, NumbOfKeys: 10}
|
||||
|
||||
userInterface, err := NewUserInterface(keypadSize)
|
||||
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) {
|
||||
keypadSize := models.KeypadSize{AttrsPerKey: 7, NumbOfKeys: 10}
|
||||
userInterface, err := NewUserInterface(keypadSize)
|
||||
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_builtin.All[bool](shuffleCompare, func(n bool) bool {
|
||||
return n == true
|
||||
})
|
||||
assert.False(t, allTrue)
|
||||
|
||||
allFalse := py_builtin.All[bool](shuffleCompare, func(n bool) bool {
|
||||
return n == false
|
||||
})
|
||||
|
||||
assert.False(t, allFalse)
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user