Files
atomic-swap/crypto/monero/address.go

195 lines
5.8 KiB
Go

// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only
package mcrypto
import (
"bytes"
"errors"
"fmt"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/crypto"
)
// Network is the Monero network type
type Network string
// Monero networks
const (
Mainnet Network = "mainnet"
Stagenet Network = "stagenet"
Testnet Network = "testnet"
)
// AddressType is the type of Monero address: Standard or Subaddress
type AddressType string
// Monero address types. We don't support Integrated.
const (
Standard AddressType = "standard"
Subaddress AddressType = "subaddress"
)
// Network prefix byte. The 1st decoded byte of a monero address defines both
// the network (mainnet, stagenet, testnet) and the type of address (standard,
// integrated, and subaddress).
const (
netPrefixStdAddrMainnet = 18
netPrefixSubAddrMainnet = 42
netPrefixStdAddrStagenet = 24
netPrefixSubAddrStagenet = 36
netPrefixStdAddrTestnet = 53
netPrefixSubAddrTestnet = 63
)
var (
errAddressNotInitialized = errors.New("monero address is not initialized")
errChecksumMismatch = errors.New("invalid address checksum")
errInvalidAddressLength = errors.New("invalid monero address length")
errInvalidAddressEncoding = errors.New("invalid monero address encoding")
errInvalidPrefixGotMainnet = errors.New("invalid monero address: expected stagenet, got mainnet")
errInvalidPrefixGotStagenet = errors.New("invalid monero address: expected mainnet, got stagenet")
errInvalidPrefixGotTestnet = errors.New("invalid monero address: monero testnet not yet supported")
)
// Address represents a Monero address
type Address struct {
// decoded is the bytes (prefix, pub spend key, pub view key, checksum) that
// get base58 encoded. Package private, as it is a semi-arbitrary
// implementation detail.
decoded [addressBytesLen]byte
}
// NewAddress converts a string to a monero Address with validation.
func NewAddress(addrStr string, env common.Environment) (*Address, error) {
addr := new(Address)
if err := addr.UnmarshalText([]byte(addrStr)); err != nil {
return nil, err
}
return addr, addr.ValidateEnv(env)
}
func (a *Address) String() string {
return addrBytesToBase58(a.decoded[:])
}
// Network returns the Monero network of the address
func (a *Address) Network() Network {
switch a.decoded[0] {
case netPrefixStdAddrMainnet, netPrefixSubAddrMainnet:
return Mainnet
case netPrefixStdAddrStagenet, netPrefixSubAddrStagenet:
return Stagenet
case netPrefixStdAddrTestnet, netPrefixSubAddrTestnet:
return Testnet
default:
// Our methods to deserialize and create Address values all verify
// that the address byte is valid
panic("address has invalid network prefix")
}
}
// Type returns the Address type
func (a *Address) Type() AddressType {
switch a.decoded[0] {
case netPrefixStdAddrMainnet, netPrefixStdAddrStagenet, netPrefixStdAddrTestnet:
return Standard
case netPrefixSubAddrTestnet, netPrefixSubAddrStagenet, netPrefixSubAddrMainnet:
return Subaddress
default:
// Our methods to deserialize and create Address values all verify
// that the address byte is valid
panic("address has invalid network prefix")
}
}
// validateDecoded ensures that the checksum and network prefix of the address
// are valid. The Network() and Type() methods are not safe to use until
// this base level validation is performed.
func (a *Address) validateDecoded() error {
checksum := getChecksum(a.decoded[:65])
if !bytes.Equal(checksum[:], a.decoded[65:69]) {
return errChecksumMismatch
}
netPrefix := a.decoded[0]
switch netPrefix {
case netPrefixStdAddrMainnet, netPrefixSubAddrMainnet,
netPrefixStdAddrStagenet, netPrefixSubAddrStagenet,
netPrefixStdAddrTestnet, netPrefixSubAddrTestnet:
// we are good, do nothing
default:
return fmt.Errorf("monero address has unknown network prefix %d", netPrefix)
}
return nil
}
// Equal returns true if the addresses are identical, otherwise false.
func (a *Address) Equal(b *Address) bool {
if b == nil {
return false
}
return a.decoded == b.decoded
}
// ValidateEnv validates that the monero network matches the passed environment.
// This validation can't be performed when decoding JSON, as the environment is
// not known at that time.
func (a *Address) ValidateEnv(env common.Environment) error {
if a == nil || a.decoded == new(Address).decoded {
return errAddressNotInitialized
}
switch a.Network() {
case Mainnet:
if env != common.Mainnet && env != common.Development {
return errInvalidPrefixGotMainnet
}
case Stagenet:
if env != common.Stagenet {
return errInvalidPrefixGotStagenet
}
case Testnet:
return errInvalidPrefixGotTestnet
default:
panic("unhandled network")
}
return nil
}
func getChecksum(data ...[]byte) (result [4]byte) {
keccak256 := crypto.Keccak256(data...)
copy(result[:], keccak256[:4])
return
}
// Address returns the address as bytes for a PublicKeyPair with the given environment (ie. mainnet or stagenet)
func (kp *PublicKeyPair) Address(env common.Environment) *Address {
address := new(Address)
var prefix byte
switch env {
case common.Mainnet, common.Development:
prefix = netPrefixStdAddrMainnet
case common.Stagenet:
prefix = netPrefixStdAddrStagenet
default:
panic(fmt.Sprintf("unhandled env %d", env))
}
// address encoding is:
// (network_prefix) + (32-byte public spend key) + (32-byte-byte public view key)
// + first_4_Bytes(Hash(network_prefix + (32-byte public spend key) + (32-byte public view key)))
address.decoded[0] = prefix // 1-byte network prefix
copy(address.decoded[1:33], kp.sk.Bytes()) // 32-byte public spend key
copy(address.decoded[33:65], kp.vk.Bytes()) // 32-byte public view key
checksum := getChecksum(address.decoded[0:65])
copy(address.decoded[65:69], checksum[:])
return address
}