github.com/MetalBlockchain/metalgo@v1.11.9/ids/id.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package ids
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  
    12  	"github.com/MetalBlockchain/metalgo/utils"
    13  	"github.com/MetalBlockchain/metalgo/utils/cb58"
    14  	"github.com/MetalBlockchain/metalgo/utils/hashing"
    15  	"github.com/MetalBlockchain/metalgo/utils/wrappers"
    16  )
    17  
    18  const (
    19  	IDLen   = 32
    20  	nullStr = "null"
    21  )
    22  
    23  var (
    24  	// Empty is a useful all zero value
    25  	Empty = ID{}
    26  
    27  	errMissingQuotes = errors.New("first and last characters should be quotes")
    28  
    29  	_ utils.Sortable[ID] = ID{}
    30  )
    31  
    32  // ID wraps a 32 byte hash used as an identifier
    33  type ID [IDLen]byte
    34  
    35  // ToID attempt to convert a byte slice into an id
    36  func ToID(bytes []byte) (ID, error) {
    37  	return hashing.ToHash256(bytes)
    38  }
    39  
    40  // FromString is the inverse of ID.String()
    41  func FromString(idStr string) (ID, error) {
    42  	bytes, err := cb58.Decode(idStr)
    43  	if err != nil {
    44  		return ID{}, err
    45  	}
    46  	return ToID(bytes)
    47  }
    48  
    49  // FromStringOrPanic is the same as FromString, but will panic on error
    50  func FromStringOrPanic(idStr string) ID {
    51  	id, err := FromString(idStr)
    52  	if err != nil {
    53  		panic(err)
    54  	}
    55  	return id
    56  }
    57  
    58  func (id ID) MarshalJSON() ([]byte, error) {
    59  	str, err := cb58.Encode(id[:])
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	return []byte(`"` + str + `"`), nil
    64  }
    65  
    66  func (id *ID) UnmarshalJSON(b []byte) error {
    67  	str := string(b)
    68  	if str == nullStr { // If "null", do nothing
    69  		return nil
    70  	} else if len(str) < 2 {
    71  		return errMissingQuotes
    72  	}
    73  
    74  	lastIndex := len(str) - 1
    75  	if str[0] != '"' || str[lastIndex] != '"' {
    76  		return errMissingQuotes
    77  	}
    78  
    79  	// Parse CB58 formatted string to bytes
    80  	bytes, err := cb58.Decode(str[1:lastIndex])
    81  	if err != nil {
    82  		return fmt.Errorf("couldn't decode ID to bytes: %w", err)
    83  	}
    84  	*id, err = ToID(bytes)
    85  	return err
    86  }
    87  
    88  func (id *ID) UnmarshalText(text []byte) error {
    89  	return id.UnmarshalJSON(text)
    90  }
    91  
    92  // Prefix this id to create a more selective id. This can be used to store
    93  // multiple values under the same key. For example:
    94  // prefix1(id) -> confidence
    95  // prefix2(id) -> vertex
    96  // This will return a new id and not modify the original id.
    97  func (id ID) Prefix(prefixes ...uint64) ID {
    98  	packer := wrappers.Packer{
    99  		Bytes: make([]byte, len(prefixes)*wrappers.LongLen+IDLen),
   100  	}
   101  
   102  	for _, prefix := range prefixes {
   103  		packer.PackLong(prefix)
   104  	}
   105  	packer.PackFixedBytes(id[:])
   106  
   107  	return hashing.ComputeHash256Array(packer.Bytes)
   108  }
   109  
   110  // XOR this id and the provided id and return the resulting id.
   111  //
   112  // Note: this id is not modified.
   113  func (id ID) XOR(other ID) ID {
   114  	for i, b := range other {
   115  		id[i] ^= b
   116  	}
   117  	return id
   118  }
   119  
   120  // Bit returns the bit value at the ith index of the byte array. Returns 0 or 1
   121  func (id ID) Bit(i uint) int {
   122  	byteIndex := i / BitsPerByte
   123  	bitIndex := i % BitsPerByte
   124  
   125  	b := id[byteIndex]
   126  
   127  	// b = [7, 6, 5, 4, 3, 2, 1, 0]
   128  
   129  	b >>= bitIndex
   130  
   131  	// b = [0, ..., bitIndex + 1, bitIndex]
   132  	// 1 = [0, 0, 0, 0, 0, 0, 0, 1]
   133  
   134  	b &= 1
   135  
   136  	// b = [0, 0, 0, 0, 0, 0, 0, bitIndex]
   137  
   138  	return int(b)
   139  }
   140  
   141  // Hex returns a hex encoded string of this id.
   142  func (id ID) Hex() string {
   143  	return hex.EncodeToString(id[:])
   144  }
   145  
   146  func (id ID) String() string {
   147  	// We assume that the maximum size of a byte slice that
   148  	// can be stringified is at least the length of an ID
   149  	s, _ := cb58.Encode(id[:])
   150  	return s
   151  }
   152  
   153  func (id ID) MarshalText() ([]byte, error) {
   154  	return []byte(id.String()), nil
   155  }
   156  
   157  func (id ID) Compare(other ID) int {
   158  	return bytes.Compare(id[:], other[:])
   159  }