mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Testing Hashcash
This commit is contained in:
parent
2f37ae0e41
commit
29503ad9cf
3 changed files with 243 additions and 6 deletions
127
Xcodes/AppleAPI/Sources/AppleAPI/Hashcash.swift
Normal file
127
Xcodes/AppleAPI/Sources/AppleAPI/Hashcash.swift
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// Hashcash.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-02-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import CommonCrypto
|
||||
|
||||
public struct Hashcash {
|
||||
|
||||
public func mint(resource: String,
|
||||
bits: UInt = 20,
|
||||
ext: String = "",
|
||||
saltCharacters: UInt = 16,
|
||||
stampSeconds: Bool = true,
|
||||
date: String? = nil) -> String? {
|
||||
|
||||
let ver = "1"
|
||||
|
||||
var ts: String
|
||||
if let date = date {
|
||||
ts = date
|
||||
} else {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = stampSeconds ? "yyMMddHHmmss" : "yyMMdd"
|
||||
ts = formatter.string(from: Date())
|
||||
}
|
||||
|
||||
let challenge = "\(ver):\(bits):\(ts):\(resource):"
|
||||
|
||||
var counter = 0
|
||||
let hexDigits = Int(ceil((Double(bits) / 4)))
|
||||
let zeros = String(repeating: "0", count: hexDigits)
|
||||
|
||||
while true {
|
||||
guard let digest = ("\(challenge):\(counter)").sha1 else {
|
||||
print("ERROR: Can't generate SHA1 digest")
|
||||
return nil
|
||||
}
|
||||
|
||||
if digest.prefix(hexDigits) == zeros {
|
||||
return "\(challenge):\(counter)"
|
||||
}
|
||||
counter += 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks whether a stamp is valid
|
||||
- parameter stamp: stamp to check e.g. 1:16:040922:foo::+ArSrtKd:164b3
|
||||
- parameter resource: resource to check against
|
||||
- parameter bits: minimum bit value to check
|
||||
- parameter expiration: number of seconds old the stamp may be
|
||||
- returns: true if stamp is valid
|
||||
*/
|
||||
public func check(stamp: String,
|
||||
resource: String? = nil,
|
||||
bits: UInt,
|
||||
expiration: UInt? = nil) -> Bool {
|
||||
|
||||
guard let stamped = Stamp(stamp: stamp) else {
|
||||
print("Invalid stamp format")
|
||||
return false
|
||||
}
|
||||
|
||||
if let res = resource, res != stamped.resource {
|
||||
print("Resources do not match")
|
||||
return false
|
||||
}
|
||||
|
||||
var count = bits
|
||||
if let claim = stamped.claim {
|
||||
if bits > claim {
|
||||
return false
|
||||
} else {
|
||||
count = claim
|
||||
}
|
||||
}
|
||||
|
||||
if let expiration = expiration {
|
||||
let goodUntilDate = Date(timeIntervalSinceNow: -TimeInterval(expiration))
|
||||
if (stamped.date < goodUntilDate) {
|
||||
print("Stamp expired")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
guard let digest = stamp.sha1 else {
|
||||
return false
|
||||
}
|
||||
|
||||
let hexDigits = Int(ceil((Double(count) / 4)))
|
||||
return digest.hasPrefix(String(repeating: "0", count: hexDigits))
|
||||
}
|
||||
|
||||
/**
|
||||
Generates random string of chosen length
|
||||
- parameter length: length of random string
|
||||
- returns: random string
|
||||
*/
|
||||
internal func salt(length: UInt) -> String {
|
||||
let allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/="
|
||||
var result = ""
|
||||
|
||||
for _ in 0..<length {
|
||||
let randomValue = arc4random_uniform(UInt32(allowedCharacters.count))
|
||||
result += "\(allowedCharacters[allowedCharacters.index(allowedCharacters.startIndex, offsetBy: Int(randomValue))])"
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
var sha1: String? {
|
||||
let data = Data(self.utf8)
|
||||
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
|
||||
data.withUnsafeBytes {
|
||||
_ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
|
||||
}
|
||||
let hexBytes = digest.map { String(format: "%02x", $0) }
|
||||
return hexBytes.joined()
|
||||
}
|
||||
}
|
||||
|
||||
101
Xcodes/AppleAPI/Sources/AppleAPI/Stamp.swift
Normal file
101
Xcodes/AppleAPI/Sources/AppleAPI/Stamp.swift
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// Stamp.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-02-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Stamp {
|
||||
|
||||
private static let DateFormatWithoutTime = "yyMMdd"
|
||||
private static let DateFormatWithTime = "yyMMddHHmmss"
|
||||
|
||||
public let version : UInt
|
||||
public let date : Date
|
||||
public let resource : String
|
||||
|
||||
// Version 1 only
|
||||
public var claim : UInt?
|
||||
public var counter : String?
|
||||
public var ext : String?
|
||||
public var random : String?
|
||||
|
||||
// Version 0 only
|
||||
public var suffix : String?
|
||||
|
||||
init?(stamp: String) {
|
||||
let components = stamp.components(separatedBy: ":")
|
||||
|
||||
if (components.count < 1) {
|
||||
print("No stamp components. Ensure it is separated by a `:`")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let version = UInt(components[0]) else {
|
||||
print("Unable to parse stamp version")
|
||||
return nil
|
||||
}
|
||||
|
||||
self.version = version
|
||||
|
||||
if self.version > 1 {
|
||||
print("Version > 1. Not handled")
|
||||
return nil
|
||||
}
|
||||
|
||||
if (self.version == 0 && components.count < 4) {
|
||||
print("Not enough components for version 0")
|
||||
return nil
|
||||
}
|
||||
|
||||
if (self.version == 1 && components.count < 7) {
|
||||
print("Not enough components for version 1")
|
||||
return nil
|
||||
}
|
||||
|
||||
if (self.version == 0) {
|
||||
if let date = Stamp.parseDate(dateString: components[1]) {
|
||||
self.date = date
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
self.resource = components[2]
|
||||
self.suffix = components[3]
|
||||
} else if (self.version == 1) {
|
||||
if let claim = UInt(components[1]) {
|
||||
self.claim = claim
|
||||
}
|
||||
if let date = Stamp.parseDate(dateString: components[2]) {
|
||||
self.date = date
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
self.resource = components[3]
|
||||
self.ext = components[4]
|
||||
self.random = components[5]
|
||||
self.counter = components[6]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseDate(dateString: String) -> Date? {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = Stamp.DateFormatWithoutTime
|
||||
|
||||
if let date = formatter.date(from: dateString) {
|
||||
return date
|
||||
}
|
||||
|
||||
formatter.dateFormat = Stamp.DateFormatWithTime
|
||||
|
||||
if let date = formatter.date(from: dateString) {
|
||||
return date
|
||||
} else {
|
||||
print("Unable to parse date")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,23 @@ import XCTest
|
|||
@testable import AppleAPI
|
||||
|
||||
final class AppleAPITests: XCTestCase {
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(AppleAPI().text, "Hello, World!")
|
||||
|
||||
func testValidHashCashMint() {
|
||||
let bits: UInt = 10
|
||||
let resource = "bb63edf88d2f9c39f23eb4d6f0281158"
|
||||
let testDate = "20230224001754"
|
||||
|
||||
// "1:11:20230224004345:8982e236688f6ebf588c4bd4b445c4cc::877"
|
||||
// 7395f792caf430dca2d07ae7be0c63fa
|
||||
|
||||
let stamp = Hashcash().mint(resource: resource, bits: bits, date: testDate)
|
||||
XCTAssertNotNil(stamp)
|
||||
XCTAssertEqual(stamp, "1:10:20230224001754:bb63edf88d2f9c39f23eb4d6f0281158::866")
|
||||
|
||||
print(stamp)
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testExample", testExample),
|
||||
("testValidHashCashMint", testValidHashCashMint),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue