Skip to content

Instantly share code, notes, and snippets.

@Sajjon
Created April 8, 2019 19:31
Show Gist options
  • Select an option

  • Save Sajjon/cd17a0e09bcc5ff9b1a4d5be3da29b64 to your computer and use it in GitHub Desktop.

Select an option

Save Sajjon/cd17a0e09bcc5ff9b1a4d5be3da29b64 to your computer and use it in GitHub Desktop.
RIPEMD160 in Swift 5
/// RIPEMD160 implementation in Swift 5.
///
/// Based on the work of Sjors Provoost, found on [Github CryptoCoinSwift][1]
///
/// Migrated to Swift 5 by [Alex Cyon a.k.a. Sajjon][2]
///
/// [1]: https://github.com/CryptoCoinSwift/RIPEMD-Swift
/// [2]: https://github.com/Sajjon
///
public struct RIPEMD160 {}
public extension RIPEMD160 {
static func hash(message data: Data) -> Data {
return digest(data)
}
static func digest(_ input: Data) -> Data {
let paddedData = pad(input)
var block = RIPEMD160.Block()
let endIndex = (paddedData.count / 64)
for i in 0..<endIndex {
let part = getWordsInSection(paddedData, i)
block.compress(part)
}
return encodeWords(block.hash)
}
}
internal extension RIPEMD160 {
// Pads the input to a multiple 64 bytes. First it adds 0x80 followed by zeros.
// It then needs 8 bytes at the end where it writes the length (in bits, little endian).
// If this doesn't fit it will add another block of 64 bytes.
static func pad(_ data: Data) -> Data {
var paddedData = data
// Put 0x80 after the last character:
paddedData += [UInt8(0x80)] // 2^8
// Pad with zeros until there are 64 * k - 8 bytes.
var numberOfZerosToPad: Int;
if paddedData.count % 64 == 56 {
// No padding needed
numberOfZerosToPad = 0
} else if paddedData.count % 64 < 56 {
numberOfZerosToPad = 56 - (paddedData.count % 64)
} else {
// Add an extra round
numberOfZerosToPad = 56 + (64 - paddedData.count % 64)
}
let zeroBytes = [UInt8](repeating: 0, count: numberOfZerosToPad)
paddedData += zeroBytes
// Append length of message:
let length: UInt32 = UInt32(data.count) * 8
let lengthBytes: [UInt32] = [length, UInt32(0x00_00_00_00)]
paddedData += RIPEMD160.encodeWords(lengthBytes)
return paddedData
}
static func getWordsInSection(_ data: Data, _ section: Int) -> [UInt32] {
let numberOfBytesToCopy = 64
let offset = section * numberOfBytesToCopy
assert(data.count >= Int(offset + numberOfBytesToCopy), "Data too short")
let startIndex: Data.Index = data.startIndex.advanced(by: offset)
let endIndex: Data.Index = startIndex.advanced(by: numberOfBytesToCopy)
let bytesInSection: Data = data[startIndex..<endIndex]
var words = [UInt32]()
var word = [UInt8]()
for byte in bytesInSection {
defer {
if word.count == 4 {
let asData = Data(word)
let uint32: UInt32 = asData.withUnsafeBytes {
$0.load(as: UInt32.self)
}
words.append(uint32)
word = [UInt8]()
}
}
word.append(byte)
}
return words
}
static func encodeWords(_ input: [UInt32]) -> Data {
var int32Array = input
return Data(buffer: UnsafeBufferPointer(start: &int32Array, count: int32Array.count))
}
static func hexStringDigest(_ hexString: String) -> Data {
let data = Data(hex: hexString)
return digest(data)
}
static func asciiDigest(_ input: String) -> Data {
let data = input.data(using: .ascii)!
return digest(data)
}
}
extension RIPEMD160 {
internal struct Block {
// Initial values
var h₀: UInt32 = 0x67452301
var h₁: UInt32 = 0xEFCDAB89
var h₂: UInt32 = 0x98BADCFE
var h₃: UInt32 = 0x10325476
var h₄: UInt32 = 0xC3D2E1F0
var message: [UInt32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
init() {}
}
}
internal extension RIPEMD160.Block {
var hash: [UInt32] {
return [h₀, h₁, h₂, h₃, h₄]
}
mutating func compress(_ message: [UInt32]) -> () {
assert(message.count == 16, "Wrong message size")
var Aᴸ = h₀
var Bᴸ = h₁
var Cᴸ = h₂
var Dᴸ = h₃
var Eᴸ = h₄
var Aᴿ = h₀
var Bᴿ = h₁
var Cᴿ = h₂
var Dᴿ = h₃
var Eᴿ = h₄
for j in 0...79 {
// Left side
let indexOfRLeftJ = R.left[j]
let wordᴸ = message[indexOfRLeftJ]
let functionᴸ = f(j)
let Tᴸ: UInt32 = ((Aᴸ &+ functionᴸ(Bᴸ, Cᴸ, Dᴸ) &+ wordᴸ &+ K.left[j]) ~<< S.left[j]) &+ Eᴸ
Aᴸ = Eᴸ
Eᴸ = Dᴸ
Dᴸ = Cᴸ ~<< 10
Cᴸ = Bᴸ
Bᴸ = Tᴸ
// Right side
let wordᴿ = message[R.right[j]]
let functionᴿ = f(79 - j)
let Tᴿ: UInt32 = ((Aᴿ &+ functionᴿ(Bᴿ, Cᴿ, Dᴿ) &+ wordᴿ &+ K.right[j]) ~<< S.right[j]) &+ Eᴿ
Aᴿ = Eᴿ
Eᴿ = Dᴿ
Dᴿ = Cᴿ ~<< 10
Cᴿ = Bᴿ
Bᴿ = Tᴿ
}
let T = h₁ &+ Cᴸ &+ Dᴿ
h₁ = h₂ &+ Dᴸ &+ Eᴿ
h₂ = h₃ &+ Eᴸ &+ Aᴿ
h₃ = h₄ &+ Aᴸ &+ Bᴿ
h₄ = h₀ &+ Bᴸ &+ Cᴿ
h₀ = T
}
func f(_ j: Int) -> ((UInt32, UInt32, UInt32) -> UInt32) {
switch j {
case _ where j < 0:
assert(false, "Invalid j")
return {(_, _, _) in 0 }
case _ where j <= 15:
return {(x, y, z) in x ^ y ^ z }
case _ where j <= 31:
return {(x, y, z) in (x & y) | (~x & z) }
case _ where j <= 47:
return {(x, y, z) in (x | ~y) ^ z }
case _ where j <= 63:
return {(x, y, z) in (x & z) | (y & ~z) }
case _ where j <= 79:
return {(x, y, z) in x ^ (y | ~z) }
default:
assert(false, "Invalid j")
return {(_, _, _) in 0 }
}
}
enum K {
case left, right
subscript(j: Int) -> UInt32 {
switch j {
case _ where j < 0:
assert(false, "Invalid j")
return 0
case _ where j <= 15:
return self == .left ? 0x00000000: 0x50A28BE6
case _ where j <= 31:
return self == .left ? 0x5A827999: 0x5C4DD124
case _ where j <= 47:
return self == .left ? 0x6ED9EBA1: 0x6D703EF3
case _ where j <= 63:
return self == .left ? 0x8F1BBCDC: 0x7A6D76E9
case _ where j <= 79:
return self == .left ? 0xA953FD4E: 0x00000000
default:
assert(false, "Invalid j")
return 0
}
}
}
enum R {
case left, right
subscript (j: Int) -> Int {
switch j {
case _ where j < 0:
assert(false, "Invalid j")
return 0
case let index where j <= 15:
if self == .left {
return index
} else {
return [5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12][index]
}
case let index where j <= 31:
if self == .left {
return [ 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8][index - 16]
} else {
return [ 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2][index - 16]
}
case let index where j <= 47:
if self == .left {
return [3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12][index - 32]
} else {
return [15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13][index - 32]
}
case let index where j <= 63:
if self == .left {
return [1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2][index - 48]
} else {
return [8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14][index - 48]
}
case let index where j <= 79:
if self == .left {
return [ 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13][index - 64]
} else {
return [12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11][index - 64]
}
default:
assert(false, "Invalid j")
return 0
}
}
}
enum S {
case left, right
subscript(j: Int) -> Int {
switch j {
case _ where j < 0:
assert(false, "Invalid j")
return 0
case _ where j <= 15:
return (self == .left ? [11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8] : [8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6])[j]
case _ where j <= 31:
return (self == .left ? [7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12] : [9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11])[j - 16]
case _ where j <= 47:
return (self == .left ? [11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5] : [9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5])[j - 32]
case _ where j <= 63:
return (self == .left ? [11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12] : [15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8])[j - 48]
case _ where j <= 79:
return (self == .left ? [9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6] : [8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11])[j - 64]
default:
assert(false, "Invalid j")
return 0
}
}
}
}
// Circular left shift: http: //en.wikipedia.org/wiki/Circular_shift
// Precendence should be the same as <<
//infix operator ~<< { precedence 160 associativity none }
infix operator ~<< //: TernaryPrecedence (picked at random by alex)
internal func ~<< (lhs: UInt32, rhs: Int) -> UInt32 {
return (lhs << UInt32(rhs)) | (lhs >> UInt32(32 - rhs));
}
extension Data {
// https://stackoverflow.com/a/26503955/1311272
init(hex: String) {
var hex = hex
var data = Data()
while(hex.count > 0) {
let subIndex = hex.index(hex.startIndex, offsetBy: 2)
let c = String(hex[..<subIndex])
hex = String(hex[subIndex...])
var ch: UInt32 = 0
Scanner(string: c).scanHexInt32(&ch)
var char = UInt8(ch)
data.append(&char, count: 1)
}
self = data
}
func toHexString() -> String {
return reduce("", { $0 + String(format: "%02x", $1) })
}
}
@XiaoYulong
Copy link
Copy Markdown

XiaoYulong commented Nov 26, 2025

Really helps me! Thank you @Sajjon. No issue with my project which using Swift 6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment