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  }