// ethereumAddress provides functions to derive Ethereum addresses for identity hashes, credit account keys, and memo hashes package ethereumAddress import "seekia/internal/cryptography/blake3" import "seekia/internal/identity" import "seekia/internal/encoding" import "golang.org/x/crypto/sha3" import "crypto/rand" import "encoding/hex" import "slices" import "errors" func GetIdentityScoreEthereumAddressFromIdentityHash(identityHash [16]byte)(string, error){ isValid, err := identity.VerifyIdentityHash(identityHash, true, "Moderator") if (err != nil) { return "", err } if (isValid == false){ identityHashHex := encoding.EncodeBytesToHexString(identityHash[:]) return "", errors.New("GetIdentityScoreEthereumAddressFromIdentityHash called with invalid identity hash: " + identityHashHex) } hashInputSuffix, err := encoding.DecodeBase32StringToBytes("ethereum") if (err != nil) { return "", err } hashInput := slices.Concat(identityHash[:], hashInputSuffix) pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(20, hashInput) if (err != nil) { return "", err } resultAddress, err := getEthereumAddressFromBytes(pseudoRandomBytes) if (err != nil) { return "", err } return resultAddress, nil } func GetCreditAccountEthereumAddressFromAccountPublicKey(publicKey [32]byte)(string, error){ hashInputSuffix, err := encoding.DecodeBase32StringToBytes("ethereum") if (err != nil) { return "", err } hashInput := slices.Concat(publicKey[:], hashInputSuffix) pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(20, hashInput) if (err != nil) { return "", err } resultAddress, err := getEthereumAddressFromBytes(pseudoRandomBytes) if (err != nil) { return "", err } return resultAddress, nil } func GetMemoEthereumAddressFromMemoHash(memoHash [32]byte)(string, error){ hashInputSuffix, err := encoding.DecodeBase32StringToBytes("memoethereumaddr") if (err != nil) { return "", err } hashInput := slices.Concat(memoHash[:], hashInputSuffix) pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(20, hashInput) if (err != nil) { return "", err } resultAddress, err := getEthereumAddressFromBytes(pseudoRandomBytes) if (err != nil) { return "", err } return resultAddress, nil } func GetNewRandomEthereumAddress()(string, error){ randomBytes := make([]byte, 20) _, err := rand.Read(randomBytes[:]) if (err != nil) { return "", err } address, err := getEthereumAddressFromBytes(randomBytes) if (err != nil) { return "", err } return address, nil } // Below code is adopted from go-ethereum // github.com/ethereum/go-ethereum/common/types.go func getEthereumAddressFromBytes(inputBytes []byte) (string, error){ if (len(inputBytes) != 20) { return "", errors.New("getEthereumAddressFromBytes called with invalid inputBytes: Invalid length.") } var addressArray [42]byte copy(addressArray[:2], "0x") hex.Encode(addressArray[2:], inputBytes) addressLowercase := string(addressArray[:]) addressString, err := convertLowercaseEthereumAddressToChecksumAddress(addressLowercase) if (err != nil){ return "", err } return addressString, nil } func VerifyEthereumAddress(input string)bool{ if (len(input) != 42){ return false } if (input[0] != '0'){ return false } if (input[1] != 'x' && input[1] != 'X'){ return false } addressWithoutPrefix := input[2:] addressWithoutPrefixCharacterBytes := []byte(addressWithoutPrefix) // We verify all characters are hexadecimal for _, char := range addressWithoutPrefixCharacterBytes{ if ('0' <= char && char <= '9'){ continue } if ('a' <= char && char <= 'f'){ continue } if ('A' <= char && char <= 'F'){ continue } return false } addressWithoutPrefixBytes, err := hex.DecodeString(addressWithoutPrefix) if (err != nil){ return false } if (len(addressWithoutPrefixBytes) != 20){ return false } // Now we compute checksum and compare with input var expectedAddressArray [42]byte copy(expectedAddressArray[:2], "0x") hex.Encode(expectedAddressArray[2:], addressWithoutPrefixBytes) expectedAddressLowercase := string(expectedAddressArray[:]) expectedAddressString, _ := convertLowercaseEthereumAddressToChecksumAddress(expectedAddressLowercase) if (input != expectedAddressString){ return false } return true } // This function performs the checksum // An ethereum address checksum is performed by changing some characters to uppercase // Input: // -[]byte: A hex encoded ethereum address with all lowercase characters // Output: // -string: Input address with checksum performed // -error func convertLowercaseEthereumAddressToChecksumAddress(inputAddress string)(string, error){ inputAddressBytes := []byte(inputAddress) if (len(inputAddressBytes) != 42){ return "", errors.New("convertLowercaseEthereumAddressToChecksumAddress called with invalid address.") } sha := sha3.NewLegacyKeccak256() sha.Write(inputAddressBytes[2:]) hash := sha.Sum(nil) for i := 2; i < len(inputAddressBytes); i++ { hashByte := hash[(i-2)/2] if (i%2 == 0) { hashByte = hashByte >> 4 } else { hashByte &= 0xf } if (inputAddressBytes[i] > '9' && hashByte > 7) { inputAddressBytes[i] -= 32 } } addressString := string(inputAddressBytes) return addressString, nil }