git.gammaspectra.live/P2Pool/consensus@v0.0.0-20240403173234-a039820b20c9/monero/address/address.go (about) 1 package address 2 3 import ( 4 "bytes" 5 "errors" 6 "git.gammaspectra.live/P2Pool/consensus/monero/crypto" 7 "git.gammaspectra.live/P2Pool/moneroutil" 8 "slices" 9 ) 10 11 type Address struct { 12 SpendPub crypto.PublicKeyBytes 13 ViewPub crypto.PublicKeyBytes 14 Network uint8 15 hasChecksum bool 16 checksum moneroutil.Checksum 17 } 18 19 func (a *Address) Compare(b Interface) int { 20 //compare spend key 21 22 resultSpendKey := crypto.CompareConsensusPublicKeyBytes(&a.SpendPub, b.SpendPublicKey()) 23 if resultSpendKey != 0 { 24 return resultSpendKey 25 } 26 27 // compare view key 28 return crypto.CompareConsensusPublicKeyBytes(&a.ViewPub, b.ViewPublicKey()) 29 } 30 31 func (a *Address) PublicKeys() (spend, view crypto.PublicKey) { 32 return &a.SpendPub, &a.ViewPub 33 } 34 35 func (a *Address) SpendPublicKey() *crypto.PublicKeyBytes { 36 return &a.SpendPub 37 } 38 39 func (a *Address) ViewPublicKey() *crypto.PublicKeyBytes { 40 return &a.ViewPub 41 } 42 43 func (a *Address) ToAddress(network uint8, err ...error) *Address { 44 if a.Network != network || (len(err) > 0 && err[0] != nil) { 45 return nil 46 } 47 return a 48 } 49 50 func (a *Address) ToPackedAddress() PackedAddress { 51 return NewPackedAddressFromBytes(a.SpendPub, a.ViewPub) 52 } 53 54 func FromBase58(address string) *Address { 55 preAllocatedBuf := make([]byte, 0, 69) 56 raw := moneroutil.DecodeMoneroBase58PreAllocated(preAllocatedBuf, []byte(address)) 57 58 if len(raw) != 69 { 59 return nil 60 } 61 62 switch raw[0] { 63 case moneroutil.MainNetwork, moneroutil.TestNetwork, moneroutil.StageNetwork: 64 break 65 case moneroutil.IntegratedMainNetwork, moneroutil.IntegratedTestNetwork, moneroutil.IntegratedStageNetwork: 66 return nil 67 case moneroutil.SubAddressMainNetwork, moneroutil.SubAddressTestNetwork, moneroutil.SubAddressStageNetwork: 68 return nil 69 default: 70 return nil 71 } 72 73 checksum := moneroutil.GetChecksum(raw[:65]) 74 if bytes.Compare(checksum[:], raw[65:]) != 0 { 75 return nil 76 } 77 a := &Address{ 78 Network: raw[0], 79 checksum: checksum, 80 hasChecksum: true, 81 } 82 83 copy(a.SpendPub[:], raw[1:33]) 84 copy(a.ViewPub[:], raw[33:65]) 85 86 return a 87 } 88 89 func FromBase58NoChecksumCheck(address []byte) *Address { 90 preAllocatedBuf := make([]byte, 0, 69) 91 raw := moneroutil.DecodeMoneroBase58PreAllocated(preAllocatedBuf, address) 92 93 if len(raw) != 69 { 94 return nil 95 } 96 97 switch raw[0] { 98 case moneroutil.MainNetwork, moneroutil.TestNetwork, moneroutil.StageNetwork: 99 break 100 case moneroutil.IntegratedMainNetwork, moneroutil.IntegratedTestNetwork, moneroutil.IntegratedStageNetwork: 101 return nil 102 case moneroutil.SubAddressMainNetwork, moneroutil.SubAddressTestNetwork, moneroutil.SubAddressStageNetwork: 103 return nil 104 default: 105 return nil 106 } 107 108 a := &Address{ 109 Network: raw[0], 110 } 111 copy(a.checksum[:], slices.Clone(raw[65:])) 112 a.hasChecksum = true 113 114 copy(a.SpendPub[:], raw[1:33]) 115 copy(a.ViewPub[:], raw[33:65]) 116 117 return a 118 } 119 120 func FromRawAddress(network uint8, spend, view crypto.PublicKey) *Address { 121 var nice [69]byte 122 nice[0] = network 123 copy(nice[1:], spend.AsSlice()) 124 copy(nice[33:], view.AsSlice()) 125 126 //TODO: cache checksum? 127 checksum := crypto.PooledKeccak256(nice[:65]) 128 a := &Address{ 129 Network: nice[0], 130 } 131 copy(a.checksum[:], checksum[:4]) 132 a.hasChecksum = true 133 134 a.SpendPub = spend.AsBytes() 135 a.ViewPub = view.AsBytes() 136 137 return a 138 } 139 140 func (a *Address) verifyChecksum() { 141 if !a.hasChecksum { 142 var nice [69]byte 143 nice[0] = a.Network 144 copy(nice[1:], a.SpendPub.AsSlice()) 145 copy(nice[1+crypto.PublicKeySize:], a.ViewPub.AsSlice()) 146 sum := crypto.PooledKeccak256(nice[:65]) 147 //this race is ok 148 copy(a.checksum[:], sum[:4]) 149 a.hasChecksum = true 150 } 151 } 152 153 func (a *Address) ToBase58() []byte { 154 a.verifyChecksum() 155 buf := make([]byte, 0, 95) 156 return moneroutil.EncodeMoneroBase58PreAllocated(buf, []byte{a.Network}, a.SpendPub.AsSlice(), a.ViewPub.AsSlice(), a.checksum[:]) 157 } 158 159 func (a *Address) MarshalJSON() ([]byte, error) { 160 a.verifyChecksum() 161 buf := make([]byte, 95+2) 162 buf[0] = '"' 163 moneroutil.EncodeMoneroBase58PreAllocated(buf[1:1], []byte{a.Network}, a.SpendPub.AsSlice(), a.ViewPub.AsSlice(), a.checksum[:]) 164 buf[len(buf)-1] = '"' 165 return buf, nil 166 } 167 168 func (a *Address) UnmarshalJSON(b []byte) error { 169 if len(b) < 2 { 170 return errors.New("unsupported length") 171 } 172 173 if addr := FromBase58NoChecksumCheck(b[1 : len(b)-1]); addr != nil { 174 a.Network = addr.Network 175 a.SpendPub = addr.SpendPub 176 a.ViewPub = addr.ViewPub 177 a.checksum = addr.checksum 178 a.hasChecksum = addr.hasChecksum 179 return nil 180 } else { 181 return errors.New("invalid address") 182 } 183 }