// Copyright Low Entry. Apache License, Version 2.0. #include "LowEntryHashingHashcashLibrary.h" #include "LowEntryExtendedStandardLibrary.h" const FString ULowEntryHashingHashcashLibrary::DATE_FORMAT_STRING = TEXT("%y%m%d%H%M%S"); TArray> ULowEntryHashingHashcashLibrary::BASE_64_CACHE = TArray>(); TArray> ULowEntryHashingHashcashLibrary::GenerateBase64Cache() { TArray> Array; Array.SetNum(MAX_COUNTER); for (int32 i = 0; i < Array.Num(); i++) { Array[i] = base64WithoutLeadingZeroBytes(i); } return Array; } TArray ULowEntryHashingHashcashLibrary::hashArray(const TArray& resource, const int32 bits) { return hashArrayCustomCreationDate(resource, FDateTime::UtcNow(), bits); } TArray ULowEntryHashingHashcashLibrary::hashArrayCustomCreationDate(const TArray& resources, const FDateTime& date, const int32 bits) { TArray result; result.SetNum(resources.Num()); for (int64 i = 0; i < resources.Num(); i++) { result[i] = hashCustomCreationDate(resources[i], date, bits); } return result; } FString ULowEntryHashingHashcashLibrary::hash(const FString& resource, const int32 bits) { return hashCustomCreationDate(resource, FDateTime::UtcNow(), bits); } FString ULowEntryHashingHashcashLibrary::hashCustomCreationDate(const FString& resource, const FDateTime& date, const int32 bits) { FSHA1 hasher; TArray hashBuffer; hashBuffer.SetNum(FSHA1::DigestSize); FString dataPrefix = FString::FromInt(VERSION) + TEXT(":") + FString::FromInt(bits) + TEXT(":") + getDateString(date) + TEXT(":") + resource + TEXT("::"); TArray dataPrefixBytes = ULowEntryExtendedStandardLibrary::StringToBytesUtf8(dataPrefix); TArray buffer; buffer.Reserve(dataPrefixBytes.Num() + 17 + 8); buffer.Append(dataPrefixBytes); int32 prefixpos = buffer.Num(); while (true) { buffer.SetNum(prefixpos, EAllowShrinking::No); TArray randomBytes; ULowEntryExtendedStandardLibrary::GenerateRandomBytes(12, randomBytes); buffer.Append(ULowEntryExtendedStandardLibrary::StringToBytesUtf8(ULowEntryExtendedStandardLibrary::BytesToBase64(randomBytes) + TEXT(":"))); if (BASE_64_CACHE.Num() <= 0) { BASE_64_CACHE = GenerateBase64Cache(); } for (const TArray& base64counter : BASE_64_CACHE) { hasher.Reset(); hasher.Update(buffer.GetData(), buffer.Num()); hasher.Update(base64counter.GetData(), base64counter.Num()); hasher.Final(); hasher.GetHash(hashBuffer.GetData()); int32 leadingZeroBits = countLeadingZeroBits(hashBuffer); if (leadingZeroBits >= bits) { buffer.Append(base64counter); return ULowEntryExtendedStandardLibrary::BytesToStringUtf8(buffer); } } } } TArray ULowEntryHashingHashcashLibrary::parseArray(const TArray& Hashcashes) { TArray result; result.SetNum(Hashcashes.Num()); for (int64 i = 0; i < Hashcashes.Num(); i++) { result[i] = parse(Hashcashes[i]); } return result; } ULowEntryParsedHashcash* ULowEntryHashingHashcashLibrary::parse(const FString& Hashcash) { TArray parts; Hashcash.ParseIntoArray(parts, TEXT(":"), false); if (parts.Num() >= 6) { FString version = parts[0]; if (version.Equals(TEXT("0"))) { FDateTime date = parseDateString(parts[1]); if (date.GetYear() > 1) { int32 bits = countLeadingZeroBits(ULowEntryExtendedStandardLibrary::Sha1(ULowEntryExtendedStandardLibrary::StringToBytesUtf8(Hashcash))); FString resource = parts[2]; for (int32 i = 3; i < (parts.Num() - 3); i++) { resource += TEXT(":") + parts[i]; } return ULowEntryParsedHashcash::Create(true, resource, date, bits); } } else if (version.Equals(TEXT("1"))) { if (parts.Num() >= 7) { FDateTime date = parseDateString(parts[2]); if (date.GetYear() > 1) { int32 bits = FCString::Atoi(*parts[1]); if (countLeadingZeroBits(ULowEntryExtendedStandardLibrary::Sha1(ULowEntryExtendedStandardLibrary::StringToBytesUtf8(Hashcash))) >= bits) { FString resource = parts[3]; for (int32 i = 4; i < (parts.Num() - 3); i++) { resource += TEXT(":") + parts[i]; } return ULowEntryParsedHashcash::Create(true, resource, date, bits); } } } } } return ULowEntryParsedHashcash::Create(false, TEXT(""), FDateTime(0), 0); } TArray ULowEntryHashingHashcashLibrary::base64WithoutLeadingZeroBytes(const int32 value) { TArray bytes; if (value < 0) { bytes.SetNum(4); bytes[0] = static_cast(value >> 24); bytes[1] = static_cast(value >> 16); bytes[2] = static_cast(value >> 8); bytes[3] = static_cast(value); } else if (value < 256) { bytes.SetNum(1); bytes[0] = static_cast(value); } else if (value < 65536) { bytes.SetNum(2); bytes[0] = static_cast(value >> 8); bytes[1] = static_cast(value); } else if (value < 16777216) { bytes.SetNum(3); bytes[0] = static_cast(value >> 16); bytes[1] = static_cast(value >> 8); bytes[2] = static_cast(value); } else { bytes.SetNum(4); bytes[0] = static_cast(value >> 24); bytes[1] = static_cast(value >> 16); bytes[2] = static_cast(value >> 8); bytes[3] = static_cast(value); } return ULowEntryExtendedStandardLibrary::StringToBytesUtf8(ULowEntryExtendedStandardLibrary::BytesToBase64(bytes)); } FString ULowEntryHashingHashcashLibrary::getDateString(const FDateTime& date) { return date.ToString(*DATE_FORMAT_STRING); } FDateTime ULowEntryHashingHashcashLibrary::parseDateString(const FString& date) { if (date.Len() < 6) { return FDateTime(0); } int32 year = FCString::Atoi(*date.Mid(0, 2)); int32 month = FCString::Atoi(*date.Mid(2, 2)); int32 day = FCString::Atoi(*date.Mid(4, 2)); if ((year < 0) || (month < 1) || (month > 12)) { return FDateTime(0); } year += ((FDateTime::Now().GetYear() / 100) * 100); if ((day < 1) || (day > FDateTime::DaysInMonth(year, month))) { return FDateTime(0); } if (date.Len() < 10) { return FDateTime(year, month, day); } int32 hour = FCString::Atoi(*date.Mid(6, 2)); int32 minutes = FCString::Atoi(*date.Mid(8, 2)); if ((hour < 0) || (hour > 23) || (minutes < 0) || (minutes > 59)) { return FDateTime(0); } if (date.Len() < 12) { return FDateTime(year, month, day, hour, minutes); } int32 seconds = FCString::Atoi(*date.Mid(10, 2)); if ((seconds < 0) || (seconds > 59)) { return FDateTime(0); } return FDateTime(year, month, day, hour, minutes, seconds); } int32 ULowEntryHashingHashcashLibrary::countLeadingZeroBits(const TArray& values) { int32 total = 0; for (uint8 value : values) { uint8 ofbyte = countLeadingZeroBits(value); total += ofbyte; if (ofbyte != 8) { break; } } return total; } uint8 ULowEntryHashingHashcashLibrary::countLeadingZeroBits(const uint8 v) { if (v < 0) { return 0; } if (v < 1) { return 8; } if (v < 2) { return 7; } if (v < 4) { return 6; } if (v < 8) { return 5; } if (v < 16) { return 4; } if (v < 32) { return 3; } if (v < 64) { return 2; } if (v < 128) { return 1; } return 0; }