github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/kbfsblock/id.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package kbfsblock
     6  
     7  import (
     8  	"encoding"
     9  	"encoding/binary"
    10  	"errors"
    11  	"math"
    12  	"math/rand"
    13  
    14  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    15  	"github.com/keybase/client/go/kbfs/kbfshash"
    16  )
    17  
    18  // ID is the (usually content-based) ID for a data block.
    19  type ID struct {
    20  	h kbfshash.Hash
    21  }
    22  
    23  // ZeroID is a zero-valued ID.
    24  var ZeroID = ID{}
    25  
    26  var _ encoding.BinaryMarshaler = ID{}
    27  var _ encoding.BinaryUnmarshaler = (*ID)(nil)
    28  
    29  var _ encoding.TextMarshaler = ID{}
    30  var _ encoding.TextUnmarshaler = (*ID)(nil)
    31  
    32  const (
    33  	// MaxIDStringLength is the maximum length of the string
    34  	// representation of a ID.
    35  	MaxIDStringLength = kbfshash.MaxHashStringLength
    36  )
    37  
    38  // IDFromString creates a ID from the given string. If the
    39  // returned error is nil, the returned ID is valid.
    40  func IDFromString(idStr string) (ID, error) {
    41  	h, err := kbfshash.HashFromString(idStr)
    42  	if err != nil {
    43  		return ID{}, err
    44  	}
    45  	return ID{h}, nil
    46  }
    47  
    48  // IDFromBytes creates a ID from the given bytes. If the returned error is nil,
    49  // the returned ID is valid.
    50  func IDFromBytes(idBytes []byte) (ID, error) {
    51  	h, err := kbfshash.HashFromBytes(idBytes)
    52  	if err != nil {
    53  		return ID{}, err
    54  	}
    55  	return ID{h}, nil
    56  }
    57  
    58  // IsValid returns whether the block ID is valid. A zero block ID is
    59  // considered invalid.
    60  func (id ID) IsValid() bool {
    61  	return id.h.IsValid()
    62  }
    63  
    64  // Bytes returns the bytes of the block ID.
    65  func (id ID) Bytes() []byte {
    66  	return id.h.Bytes()
    67  }
    68  
    69  func (id ID) String() string {
    70  	return id.h.String()
    71  }
    72  
    73  // MarshalBinary implements the encoding.BinaryMarshaler interface for
    74  // ID. Returns an error if the ID is invalid and not the zero
    75  // ID.
    76  func (id ID) MarshalBinary() (data []byte, err error) {
    77  	return id.h.MarshalBinary()
    78  }
    79  
    80  // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
    81  // for ID. Returns an error if the given byte array is non-empty and
    82  // the ID is invalid.
    83  func (id *ID) UnmarshalBinary(data []byte) error {
    84  	return id.h.UnmarshalBinary(data)
    85  }
    86  
    87  // MarshalText implements the encoding.TextMarshaler interface for ID.
    88  func (id ID) MarshalText() ([]byte, error) {
    89  	return id.h.MarshalText()
    90  }
    91  
    92  // UnmarshalText implements the encoding.TextUnmarshaler interface for
    93  // ID.
    94  func (id *ID) UnmarshalText(buf []byte) error {
    95  	return id.h.UnmarshalText(buf)
    96  }
    97  
    98  // HashType returns the type used for this ID.
    99  func (id *ID) HashType() kbfshash.HashType {
   100  	return id.h.GetHashType()
   101  }
   102  
   103  func makeRandomID(ht kbfshash.HashType) (ID, error) {
   104  	var dh kbfshash.RawDefaultHash
   105  	err := kbfscrypto.RandRead(dh[:])
   106  	if err != nil {
   107  		return ID{}, err
   108  	}
   109  	h, err := kbfshash.HashFromRaw(ht, dh[:])
   110  	if err != nil {
   111  		return ID{}, err
   112  	}
   113  	return ID{h}, nil
   114  }
   115  
   116  // MakeTemporaryID generates a temporary block ID with an invalid hash
   117  // type using a CSPRNG. This is used for indirect blocks before
   118  // they're committed to the server.
   119  func MakeTemporaryID() (ID, error) {
   120  	return makeRandomID(kbfshash.TemporaryHashType)
   121  }
   122  
   123  // MakeFakeID generates a fake block ID with a valid hash type using a
   124  // CSPRNG. This is used for fake block IDs in tests.
   125  func MakeFakeID() (ID, error) {
   126  	return makeRandomID(kbfshash.DefaultHashType)
   127  }
   128  
   129  const (
   130  	// UseMathRandForTest tells MakeRandomIDInRange to use math/rand for PRNG.
   131  	UseMathRandForTest = true
   132  	// UseRealRandomness tells MakeRandomIDInRange to use crypto/rand for PRNG.
   133  	UseRealRandomness = false
   134  )
   135  
   136  // MakeRandomIDInRange generates a random block ID using a CSPRNG, distributing
   137  // the random variable over the interval [start, end), where the full range is
   138  // [0, MaxUint64). This corresponds to a normalized representation of the
   139  // range [kbfshash.RawDefaultHash{}, kbfshash.MaxDefaultHash).
   140  func MakeRandomIDInRange(start, end float64, mathRandForTest bool) (ID, error) {
   141  	if start < 0.0 || 1.0 < end || end <= start {
   142  		return ID{}, errors.New("Expected range within the interval [0.0, 1.0)")
   143  	}
   144  	rangeSize := end - start
   145  	randBuf := [8]byte{}
   146  	var err error
   147  	if mathRandForTest {
   148  		_, err = rand.Read(randBuf[:])
   149  	} else {
   150  		err = kbfscrypto.RandRead(randBuf[:])
   151  	}
   152  	if err != nil {
   153  		return ID{}, err
   154  	}
   155  	// Generate a random unsigned int. Endianness doesn't matter here because
   156  	// the bytes are random.
   157  	randUint := binary.BigEndian.Uint64(randBuf[:])
   158  	const maxUintFloat = float64(math.MaxUint64)
   159  	randFloat := float64(randUint) / maxUintFloat
   160  	// This forms the start. We fill in the rest with zeroes.
   161  	randFloatInInterval := rangeSize*randFloat + start
   162  	scaledRandomUint := uint64(randFloatInInterval * maxUintFloat)
   163  	// Now endianness matters, because we are relying on how the system
   164  	// represented integers while doing the calculation.
   165  	var dh kbfshash.RawDefaultHash
   166  	binary.BigEndian.PutUint64(dh[:], scaledRandomUint)
   167  	h, err := kbfshash.HashFromRaw(kbfshash.DefaultHashType, dh[:])
   168  	if err != nil {
   169  		return ID{}, err
   170  	}
   171  	return ID{h}, nil
   172  }
   173  
   174  // MakePermanentID computes the permanent ID of a block given its
   175  // encoded and encrypted contents.
   176  func MakePermanentID(
   177  	encodedEncryptedData []byte, encryptionVer kbfscrypto.EncryptionVer) (
   178  	ID, error) {
   179  	h, err := kbfshash.DoHash(
   180  		encodedEncryptedData, encryptionVer.ToHashType())
   181  	if err != nil {
   182  		return ID{}, err
   183  	}
   184  	return ID{h}, nil
   185  }
   186  
   187  // VerifyID verifies that the given block ID is the permanent block ID
   188  // for the given encoded and encrypted data.
   189  func VerifyID(encodedEncryptedData []byte, id ID) error {
   190  	return id.h.Verify(encodedEncryptedData)
   191  }
   192  
   193  // FakeID returns an ID derived from the given byte, suitable for
   194  // testing.
   195  func FakeID(b byte) ID {
   196  	dh := kbfshash.RawDefaultHash{b}
   197  	h, err := kbfshash.HashFromRaw(kbfshash.DefaultHashType, dh[:])
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  	return ID{h}
   202  }
   203  
   204  // FakeIDAdd returns an ID derived from the given ID and the given
   205  // byte, suitable for testing.
   206  func FakeIDAdd(id ID, b byte) ID {
   207  	return FakeID(id.h.Bytes()[1] + b)
   208  }
   209  
   210  // FakeIDMul returns an ID derived from the given ID and given byte
   211  // using *, suitable for testing.
   212  //
   213  // TODO: Fix the test that breaks when this is replaced with
   214  // FakeIDAdd.
   215  func FakeIDMul(id ID, b byte) ID {
   216  	return FakeID(id.h.Bytes()[1] * b)
   217  }