Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GenRandomKey function #116

Merged
merged 3 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion table_refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,58 @@ func (rt *RoutingTable) GenRandPeerID(targetCpl uint) (peer.ID, error) {

// Convert to a known peer ID.
key := keyPrefixMap[targetPrefix]
id := [34]byte{mh.SHA2_256, 32}
id := [Keysize + 2]byte{mh.SHA2_256, Keysize}
binary.BigEndian.PutUint32(id[2:], key)
return peer.ID(id[:]), nil
}

// GenRandomKey generates a random key matching a provided Common Prefix Length (Cpl) wrt. the local identity
func (rt *RoutingTable) GenRandomKey(targetCpl uint) (ID, error) {
// targetCpl cannot be larger than the key size in bits
if targetCpl >= Keysize*8 {
return nil, fmt.Errorf("cannot generate peer ID for Cpl greater than key length")
}

// the generated key must match the targetCpl first bits of the local key,
// the following bit is the inverse of the local key's bit at position targetCpl+1
// and the remaining bits are randomly generated
//
// The returned ID takes the first targetCpl/8 bytes from the local key, the next byte is
// targetCpl%8 bits from the local key, 1 bit from the local key inverted and the remaining
// bits are random, and the last bytes are random

// generate random bytes
nRandBytes := Keysize - (targetCpl+1)/8
randBytes := make([]byte, nRandBytes)
_, err := rand.Read(randBytes)
if err != nil {
return nil, err
}

// byte and bit offset for the given cpl
byteOffset := targetCpl / 8
bitOffset := targetCpl % 8

// copy the local key to a new slice
randKey := make([]byte, len(rt.local))
copy(randKey, []byte(rt.local))

// compute the mask for the local key, the first targetCpl bits must be the same as the local key
// hence the mask is 1s for bits up to targetCpl and 0s for the rest
localMask := (^byte(0)) << (8 - bitOffset)
// compute the bit that is flipped in the local key (at position targetCpl+1)
bucketBit := (byte(0x80) >> bitOffset) & ^rt.local[targetCpl/8]

// first bitOffset bits are the same as the local key, the next bit is flipped and the remaining bits are random
randKey[byteOffset] = (localMask & randKey[byteOffset]) | bucketBit | (randBytes[0] & ((^localMask) >> 1))
// the remaining bytes are random
for i := uint(1); i < nRandBytes; i++ {
randKey[byteOffset+i] = randBytes[i]
}

return randKey, nil
}
Jorropo marked this conversation as resolved.
Show resolved Hide resolved

// ResetCplRefreshedAtForID resets the refresh time for the Cpl of the given ID.
func (rt *RoutingTable) ResetCplRefreshedAtForID(id ID, newTime time.Time) {
cpl := CommonPrefixLen(id, rt.local)
Expand Down
80 changes: 80 additions & 0 deletions table_refresh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,86 @@ func TestGenRandPeerID(t *testing.T) {
}
}

func TestGenRandomKey(t *testing.T) {
// test can be run in parallel
t.Parallel()

// run multiple occurences to make sure the test wasn't just lucky
for i := 0; i < 100; i++ {
// generate routing table with random local peer ID
local := test.RandPeerIDFatal(t)
m := pstore.NewMetrics()
rt, err := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m, NoOpThreshold, nil)
require.NoError(t, err)

// GenRandomKey fails for cpl >= 256
_, err = rt.GenRandomKey(256)
require.Error(t, err)
_, err = rt.GenRandomKey(300)
require.Error(t, err)

// bitwise comparison legend:
// O for same bit, X for different bit, ? for don't care

// we compare the returned generated key with the local key
// for CPL = X, the first X bits should be the same, bit X+1 should be
// different, and the rest should be random / don't care

// cpl = 0 should return a different first bit
// X??????? ???...
key0, err := rt.GenRandomKey(0)
require.NoError(t, err)
// most significant bit should be different
require.NotEqual(t, key0[0]>>7, rt.local[0]>>7)

// cpl = 1 should return a different second bit
// OX?????? ???...
key1, err := rt.GenRandomKey(1)
require.NoError(t, err)
// MSB should be equal, as cpl = 1
require.Equal(t, key1[0]>>7, rt.local[0]>>7)
// 2nd MSB should be different
require.NotEqual(t, (key1[0]<<1)>>6, (rt.local[0]<<1)>>6)

// cpl = 2 should return a different third bit
// OOX????? ???...
key2, err := rt.GenRandomKey(2)
require.NoError(t, err)
// 2 MSB should be equal, as cpl = 2
require.Equal(t, key2[0]>>6, rt.local[0]>>6)
// 3rd MSB should be different
require.NotEqual(t, (key2[0]<<2)>>5, (rt.local[0]<<2)>>5)

// cpl = 7 should return a different eighth bit
// OOOOOOOX ???...
key7, err := rt.GenRandomKey(7)
require.NoError(t, err)
// 7 MSB should be equal, as cpl = 7
require.Equal(t, key7[0]>>1, rt.local[0]>>1)
// 8th MSB should be different
require.NotEqual(t, key7[0]<<7, rt.local[0]<<7)

// cpl = 8 should return a different ninth bit
// OOOOOOOO X???...
key8, err := rt.GenRandomKey(8)
require.NoError(t, err)
// 8 MSB should be equal, as cpl = 8
require.Equal(t, key8[0], rt.local[0])
// 9th MSB should be different
require.NotEqual(t, key8[1]>>7, rt.local[1]>>7)

// cpl = 53 should return a different 54th bit
// OOOOOOOO OOOOOOOO OOOOOOOO OOOOOOOO OOOOOOOO OOOOOOOO OOOOOX?? ???...
key53, err := rt.GenRandomKey(53)
require.NoError(t, err)
// 53 MSB should be equal, as cpl = 53
require.Equal(t, key53[:6], rt.local[:6])
require.Equal(t, key53[6]>>3, rt.local[6]>>3)
// 54th MSB should be different
require.NotEqual(t, (key53[6]<<5)>>7, (rt.local[6]<<5)>>7)
}
}

func TestRefreshAndGetTrackedCpls(t *testing.T) {
t.Parallel()

Expand Down
3 changes: 3 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
// behaviour
var ErrLookupFailure = errors.New("failed to find any peer in table")

// Keysize is the size of the Kademlia ID in bytes
const Keysize = 32

// ID for IpfsDHT is in the XORKeySpace
//
// The type dht.ID signifies that its contents have been hashed from either a
Expand Down