package cardanoAddress // Much of the code in this file is taken from btcd // Repository: github.com/btcsuite/btcd // Soure file: /btcutil/bech32/bech32.go import "seekia/internal/cryptography/blake3" import "seekia/internal/identity" import "seekia/internal/encoding" import "errors" import "crypto/rand" import "strings" import "slices" func GetIdentityScoreCardanoAddressFromIdentityHash(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("GetIdentityScoreCardanoAddressFromIdentityHash called with invalid identity hash: " + identityHashHex) } hashInputSuffix, err := encoding.DecodeBase32StringToBytes("cardanoidentityscoreaddresshashsaltbytes") if (err != nil) { return "", err } hashInput := slices.Concat(identityHash[:], hashInputSuffix) pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(28, hashInput) if (err != nil) { return "", err } resultAddress, err := getCardanoAddressFromBytes(pseudoRandomBytes) if (err != nil) { return "", err } return resultAddress, nil } func GetCreditAccountCardanoAddressFromAccountPublicKey(publicKey [32]byte)(string, error){ hashInputSuffix, err := encoding.DecodeBase32StringToBytes("cardanocreditaccountsalt") if (err != nil) { return "", err } hashInput := slices.Concat(publicKey[:], hashInputSuffix) pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(28, hashInput) if (err != nil) { return "", err } resultAddress, err := getCardanoAddressFromBytes(pseudoRandomBytes) if (err != nil) { return "", err } return resultAddress, nil } func GetMemoCardanoAddressFromMemoHash(memoHash [32]byte)(string, error){ hashInputSuffix, err := encoding.DecodeBase32StringToBytes("memocardanoaddresshashinputbytes") if (err != nil) { return "", err } hashInput := slices.Concat(memoHash[:], hashInputSuffix) pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(28, hashInput) if (err != nil) { return "", err } resultAddress, err := getCardanoAddressFromBytes(pseudoRandomBytes) if (err != nil) { return "", err } return resultAddress, nil } func GetNewRandomCardanoAddress()(string, error){ randomBytes := make([]byte, 28) _, err := rand.Read(randomBytes[:]) if (err != nil) { return "", err } address, err := getCardanoAddressFromBytes(randomBytes) if (err != nil) { return "", err } return address, nil } // bech32Charset is the set of characters used in the data section of bech32 strings. // Note that this is ordered, such that for a given charset[i], i is the binary value of the character. const bech32Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" // gen encodes the generator polynomial for the bech32 BCH checksum. var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} func getCardanoAddressFromBytes(paymentKeyHash []byte)(string, error){ if (len(paymentKeyHash) != 28){ // This should be 228 bits, or 28 bytes return "", errors.New("getCardanoAddressFromBytes called with invalid length paymentKeyHash.") } // We first construct the address header (1 byte) // First 4 bytes: 0110 = Enterprise Address with PaymentKeyHash // Last 4 bytes: 0001 = Mainnet // Header bits == 01100001 // 1100001 in binary == 97 addressBytes := []byte{97} addressBytes = append(addressBytes, paymentKeyHash...) convertedAddressBytes, err := convertBits(addressBytes, 8, 5, true) if (err != nil) { return "", err } addressString := "addr1" // Now we encode the rest of the address as bech32 for _, addressByte := range convertedAddressBytes { if (addressByte >= 32) { return "", errors.New("convertBits returning out-of-range byte.") } addressByteCharacter := bech32Charset[addressByte] addressString += string(addressByteCharacter) } // Now we compute the checksum polymod := bech32Polymod(convertedAddressBytes, nil) ^ 1 for i := 0; i < 6; i++ { b := byte((polymod >> uint(5*(5-i))) & 31) // This can't fail, given we explicitly cap the previous b byte by the first 31 bits. character := bech32Charset[b] addressString += string(character) } return addressString, nil } // This function is partially adopted from btcd // Repository: github.com/btcsuite/btcd // File: /btcutil/bech32/bech32.go // This function verifies that a Cardano address in an Enterprise address, encoded in bech32 // This is the format for all Seekia Cardano addresses func VerifyCardanoSeekiaAddress(address string)bool{ if (len(address) != 58){ return false } addressWithoutPrefix, hasPrefix := strings.CutPrefix(address, "addr1") if (hasPrefix == false){ return false } if (len([]rune(addressWithoutPrefix)) != 53){ return false } decodedAddress := make([]byte, 0, 53) for _, addressCharacter := range addressWithoutPrefix{ index := strings.IndexByte(bech32Charset, byte(addressCharacter)) if (index < 0) { // This character is not in the bech32 charset. return false } decodedAddress = append(decodedAddress, byte(index)) } decodedAddressWithoutChecksum := decodedAddress[:47] decodedAddressChecksum := decodedAddress[47:] polymod := bech32Polymod(decodedAddressWithoutChecksum, decodedAddressChecksum) if (polymod != 1){ return false } convertedBytes, err := convertBits(decodedAddressWithoutChecksum, 5, 8, false) if err != nil { return false } if (convertedBytes[0] != 97){ return false } return true } // This function is taken from btcd // Repository: github.com/btcsuite/btcd // File: /btcutil/bech32/bech32.go // bech32Polymod calculates the BCH checksum for a given HumanReadablePart, values and checksum data. // Checksum is optional, and if nil a 0 checksum is assumed. // // Values and checksum (if provided) MUST be encoded as 5 bits per element (base // 32), otherwise the results are undefined. // // For more details on the polymod calculation, please refer to BIP 173. func bech32Polymod(values []byte, checksum []byte) int { humanReadablePart := "addr" humanReadablePartLength := 4 chk := 1 // Account for the high bits of the HumanReadablePart in the checksum. for i := 0; i < humanReadablePartLength; i++ { b := chk >> 25 hiBits := int(humanReadablePart[i]) >> 5 chk = (chk&0x1ffffff)<<5 ^ hiBits for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] } } } // Account for the separator (0) between high and low bits of the HumanReadablePart. // x^0 == x, so we eliminate the redundant xor used in the other rounds. b := chk >> 25 chk = (chk & 0x1ffffff) << 5 for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] } } // Account for the low bits of the HumanReadablePart. for i := 0; i < humanReadablePartLength; i++ { b := chk >> 25 loBits := int(humanReadablePart[i]) & 31 chk = (chk&0x1ffffff)<<5 ^ loBits for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] } } } // Account for the values. for _, v := range values { b := chk >> 25 chk = (chk&0x1ffffff)<<5 ^ int(v) for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] } } } if (checksum == nil) { // A nil checksum is used during encoding, so assume all bytes are zero. // x^0 == x, so we eliminate the redundant xor used in the other rounds. for v := 0; v < 6; v++ { b := chk >> 25 chk = (chk & 0x1ffffff) << 5 for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] } } } } else { // Checksum is provided during decoding, so use it. for _, v := range checksum { b := chk >> 25 chk = (chk&0x1ffffff)<<5 ^ int(v) for i := 0; i < 5; i++ { if (b>>uint(i))&1 == 1 { chk ^= gen[i] } } } } return chk } // This function is taken from btcd // Repository: github.com/btcsuite/btcd // File: /btcutil/bech32/bech32.go // ConvertBits converts a byte slice where each byte is encoding fromBits bits, // to a byte slice where each byte is encoding toBits bits. func convertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) { if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 { return nil, errors.New("convertBits called with invalid bit groups.") } // Determine the maximum size the resulting array can have after base // conversion, so that we can size it a single time. This might be off // by a byte depending on whether padding is used or not and if the input // data is a multiple of both fromBits and toBits, but we ignore that and // just size it to the maximum possible. maxSize := len(data)*int(fromBits)/int(toBits) + 1 // The final bytes, each byte encoding toBits bits. regrouped := make([]byte, 0, maxSize) // Keep track of the next byte we create and how many bits we have // added to it out of the toBits goal. nextByte := byte(0) filledBits := uint8(0) for _, b := range data { // Discard unused bits. b <<= 8 - fromBits // How many bits remaining to extract from the input data. remFromBits := fromBits for remFromBits > 0 { // How many bits remaining to be added to the next byte. remToBits := toBits - filledBits // The number of bytes to next extract is the minimum of remFromBits and remToBits. toExtract := remFromBits if remToBits < toExtract { toExtract = remToBits } // Add the next bits to nextByte, shifting the already added bits to the left. nextByte = (nextByte << toExtract) | (b >> (8 - toExtract)) // Discard the bits we just extracted and get ready for next iteration. b <<= toExtract remFromBits -= toExtract filledBits += toExtract // If the nextByte is completely filled, we add it to // our regrouped bytes and start on the next byte. if filledBits == toBits { regrouped = append(regrouped, nextByte) filledBits = 0 nextByte = 0 } } } // We pad any unfinished group if specified. if pad && filledBits > 0 { nextByte <<= toBits - filledBits regrouped = append(regrouped, nextByte) filledBits = 0 nextByte = 0 } // Any incomplete group must be <= 4 bits, and all zeroes. if filledBits > 0 && (filledBits > 4 || nextByte != 0) { return nil, errors.New("Invalid incomplete group") } return regrouped, nil }