Skip to content

Commit f0cb169

Browse files
authored
Merge pull request #24 from hartbit/hash-algorithm
Introduce a new HashAlgorithm protocol and make SHA256 conform to it
2 parents 9dae756 + 257d07d commit f0cb169

File tree

4 files changed

+40
-55
lines changed

4 files changed

+40
-55
lines changed

Sources/TSCBasic/CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
add_library(TSCBasic
1010
Await.swift
1111
ByteString.swift
12+
CStringArray.swift
1213
CacheableSequence.swift
1314
CodableResult.swift
1415
CollectionAlgorithms.swift
1516
CollectionExtensions.swift
1617
Condition.swift
17-
CStringArray.swift
1818
DeltaAlgorithm.swift
1919
DiagnosticsEngine.swift
2020
DictionaryExtensions.swift
@@ -23,12 +23,12 @@ add_library(TSCBasic
2323
FileInfo.swift
2424
FileSystem.swift
2525
GraphAlgorithms.swift
26+
HashAlgorithms.swift
2627
JSON.swift
2728
JSONMapper.swift
2829
KeyedPair.swift
2930
LazyCache.swift
3031
Lock.swift
31-
misc.swift
3232
OSLog.swift
3333
ObjectIdentifierProtocol.swift
3434
OrderedDictionary.swift
@@ -41,14 +41,14 @@ add_library(TSCBasic
4141
ProcessSet.swift
4242
RegEx.swift
4343
Result.swift
44-
SHA256.swift
4544
SortedArray.swift
4645
StringConversions.swift
4746
SynchronizedQueue.swift
4847
TemporaryFile.swift
4948
TerminalController.swift
5049
Thread.swift
51-
Tuple.swift)
50+
Tuple.swift
51+
misc.swift)
5252
target_link_libraries(TSCBasic PUBLIC
5353
TSCLibc)
5454
if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin)

Sources/TSCBasic/SHA256.swift renamed to Sources/TSCBasic/HashAlgorithms.swift

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,31 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11+
public protocol HashAlgorithm {
12+
13+
/// Hashes the input bytes, returning the digest.
14+
///
15+
/// - Parameters:
16+
/// - bytes: The input bytes.
17+
/// - Returns: The output digest.
18+
func hash(_ bytes: ByteString) -> ByteString
19+
}
20+
21+
extension HashAlgorithm {
22+
public func hash(_ string: String) -> ByteString {
23+
hash(ByteString([UInt8](string.utf8)))
24+
}
25+
}
26+
1127
/// SHA-256 implementation from Secure Hash Algorithm 2 (SHA-2) set of
1228
/// cryptographic hash functions (FIPS PUB 180-2).
13-
public final class SHA256 {
29+
public struct SHA256: HashAlgorithm {
1430

1531
/// The length of the output digest (in bits).
16-
let digestLength = 256
32+
private static let digestLength = 256
1733

1834
/// The size of each blocks (in bits).
19-
let blockBitSize = 512
35+
private static let blockBitSize = 512
2036

2137
/// The initial hash value.
2238
private static let initalHashValue: [UInt32] = [
@@ -35,60 +51,28 @@ public final class SHA256 {
3551
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
3652
]
3753

38-
/// The hash that is being computed.
39-
private var hash = SHA256.initalHashValue
40-
41-
/// The input that was provided. It will be padded when computing the digest.
42-
private var input: [UInt8]
43-
44-
/// The result, once computed.
45-
private var result: [UInt8]?
46-
47-
public init(_ input: [UInt8]) {
48-
self.input = input
54+
public init() {
4955
}
5056

51-
public init(_ bytes: ByteString) {
52-
self.input = bytes.contents
53-
}
54-
55-
public init(_ string: String) {
56-
self.input = [UInt8](string.utf8)
57-
}
58-
59-
/// Returns the digest as hexadecimal string.
60-
public func digestString() -> String {
61-
return digest().reduce("") {
62-
var str = String($1, radix: 16)
63-
// The above method does not do zero padding.
64-
if str.count == 1 {
65-
str = "0" + str
66-
}
67-
return $0 + str
68-
}
69-
}
70-
71-
/// Returns the digest.
72-
public func digest() -> [UInt8] {
73-
74-
// If we already have the result, we're done.
75-
if let result = self.result {
76-
return result
77-
}
57+
public func hash(_ bytes: ByteString) -> ByteString {
58+
var input = bytes.contents
7859

7960
// Pad the input.
8061
pad(&input)
8162

8263
// Break the input into N 512-bit blocks.
83-
let messageBlocks = input.blocks(size: blockBitSize / 8)
64+
let messageBlocks = input.blocks(size: SHA256.blockBitSize / 8)
65+
66+
/// The hash that is being computed.
67+
var hash = SHA256.initalHashValue
8468

8569
// Process each block.
8670
for block in messageBlocks {
87-
process(block)
71+
process(block, hash: &hash)
8872
}
8973

9074
// Finally, compute the result.
91-
var result = [UInt8](repeating: 0, count: digestLength / 8)
75+
var result = [UInt8](repeating: 0, count: SHA256.digestLength / 8)
9276
for (idx, element) in hash.enumerated() {
9377
let pos = idx * 4
9478
result[pos + 0] = UInt8((element >> 24) & 0xff)
@@ -97,12 +81,11 @@ public final class SHA256 {
9781
result[pos + 3] = UInt8(element & 0xff)
9882
}
9983

100-
self.result = result
101-
return result
84+
return ByteString(result)
10285
}
10386

10487
/// Process and compute hash from a block.
105-
private func process(_ block: ArraySlice<UInt8>) {
88+
private func process(_ block: ArraySlice<UInt8>, hash: inout [UInt32]) {
10689

10790
// Compute message schedule.
10891
var W = [UInt32](repeating: 0, count: SHA256.konstants.count)

Tests/TSCBasicPerformanceTests/SHA256PerfTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ import TSCTestSupport
1515

1616
class SHA256PerfTests: XCTestCasePerf {
1717
func test20MBDigest_X1000() {
18+
let sha256 = SHA256()
1819
let byte = "f"
1920
let stream = BufferedOutputByteStream()
2021
for _ in 0..<20000 {
2122
stream <<< byte
2223
}
2324
measure {
2425
for _ in 0..<1000 {
25-
XCTAssertEqual(SHA256(stream.bytes).digestString(), "23d00697ba26b4140869bab958431251e7e41982794d41b605b6a1d5dee56abf")
26+
XCTAssertEqual(sha256.hash(stream.bytes).hexadecimalRepresentation, "23d00697ba26b4140869bab958431251e7e41982794d41b605b6a1d5dee56abf")
2627
}
2728
}
2829
}

Tests/TSCBasicTests/SHA256Tests.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import TSCBasic
1515
class SHA256Tests: XCTestCase {
1616

1717
func testBasics() throws {
18+
let sha256 = SHA256()
1819
let knownHashes = [
1920
"": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
2021
"The quick brown fox jumps over the lazy dog": "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
@@ -25,7 +26,7 @@ class SHA256Tests: XCTestCase {
2526

2627
// Test known hashes.
2728
for (input, hash) in knownHashes {
28-
XCTAssertEqual(SHA256(input).digestString(), hash, "Incorrect value for \(input)")
29+
XCTAssertEqual(sha256.hash(ByteString(input)).hexadecimalRepresentation, hash, "Incorrect value for \(input)")
2930
}
3031

3132
// Test a big input.
@@ -34,12 +35,12 @@ class SHA256Tests: XCTestCase {
3435
for _ in 0..<20000 {
3536
stream <<< byte
3637
}
37-
XCTAssertEqual(SHA256(stream.bytes).digestString(), "23d00697ba26b4140869bab958431251e7e41982794d41b605b6a1d5dee56abf")
38+
XCTAssertEqual(sha256.hash(stream.bytes).hexadecimalRepresentation, "23d00697ba26b4140869bab958431251e7e41982794d41b605b6a1d5dee56abf")
3839
}
3940

4041
func testLargeData() {
4142
let data: [UInt8] = (0..<1788).map { _ in 0x03 }
42-
let digest = SHA256(data).digestString()
43+
let digest = SHA256().hash(ByteString(data)).hexadecimalRepresentation
4344
XCTAssertEqual(digest, "907422e2f24d749d0add2b504ccae8ad1aa392477591905880fb2dc494e33d63")
4445
}
4546
}

0 commit comments

Comments
 (0)