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 }