diff --git a/table_refresh.go b/table_refresh.go index 5156040..446d4e7 100644 --- a/table_refresh.go +++ b/table_refresh.go @@ -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 := 8 - targetCpl%8 + orig := rt.local[partialOffset] + + origMask := ^uint8(0) << remainingBits + randMask := ^origMask >> 1 + flippedBitOffset := remainingBits - 1 + flippedBitMask := uint8(1) << flippedBitOffset + + // 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) diff --git a/table_refresh_test.go b/table_refresh_test.go index e99a89a..97cda57 100644 --- a/table_refresh_test.go +++ b/table_refresh_test.go @@ -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()