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 2 commits
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
36 changes: 35 additions & 1 deletion table_refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,45 @@ 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 := [32 + 2]byte{mh.SHA2_256, 32}
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. The returned key matches 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.
func (rt *RoutingTable) GenRandomKey(targetCpl uint) (ID, error) {
if int(targetCpl+1) >= len(rt.local)*8 {
return nil, fmt.Errorf("cannot generate peer ID for Cpl greater than key length")
}
partialOffset := targetCpl / 8

// output contains the first partialOffset bytes of the local key
// and the remaining bytes are random
output := make([]byte, len(rt.local))
copy(output, rt.local[:partialOffset])
_, err := rand.Read(output[partialOffset:])
if err != nil {
return nil, err
}

remainingBits := targetCpl % 8
orig := rt.local[partialOffset]

origMask := ^uint8(0) << (8 - remainingBits)
randMask := ^origMask >> 1
flippedBitOffset := remainingBits + 1
flippedBitMask := uint8(1) << (8 - flippedBitOffset)
guillaumemichel marked this conversation as resolved.
Show resolved Hide resolved

// restore the remainingBits Most Significant Bits of orig
// and flip the flippedBitOffset-th bit of orig
output[partialOffset] = orig&origMask | (orig & flippedBitMask) ^ flippedBitMask | output[partialOffset]&randMask

return ID(output), nil
}

// 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