373 lines
10 KiB
Go
373 lines
10 KiB
Go
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
|
|
}
|
|
|
|
|