# ripemd128.tcl - Copyright (C) 2003 Pat Thoyts <patthoyts@users.sf.net>
#
# This is a Tcl-only implementation of the RIPEMD-128 hash algorithm as 
# described in [RIPE].
# Included is an implementation of keyed message authentication using 
# the RIPEMD-128 function [HMAC].
#
# See http://www.esat.kuleuven.ac.be/~cosicart/pdf/AB-9601/
#
# [RIPE] Dobbertin, H., Bosselaers A., and Preneel, B.
#        "RIPEMD-160: A Strengthened Version of RIPEMD" 
#        Fast Software Encryption, LNCS 1039, D. Gollmann, Ed., 
#        Springer-Verlag, 1996, pp. 71-82
# [HMAC] Krawczyk, H., Bellare, M., and R. Canetti, 
#       "HMAC: Keyed-Hashing for Message Authentication",
#        RFC 2104, February 1997.
#
# RFC 2286, ``Test cases for HMAC-RIPEMD160 and HMAC-RIPEMD128,''
# Internet Request for Comments 2286, J. Kapp, 
#
# -------------------------------------------------------------------------
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
# -------------------------------------------------------------------------
#
# $Id: ripemd128.tcl,v 1.4 2004/02/18 23:49:09 patthoyts Exp $

package require Tcl 8.2;                # tcl minimum version
#catch {package require ripemdc 1.0};   # tcllib critcl alternative

namespace eval ::ripemd {
    namespace eval ripemd128 {
        variable version 1.0.0
        variable rcsid {$Id: ripemd128.tcl,v 1.4 2004/02/18 23:49:09 patthoyts Exp $}
        variable usetrf 0

        # Trf 2.1p1 is buggy for what we want to do.
        catch {
            package require Trf
            package require Memchan
            if {[package vsatisfies \
                     [string map {p .} [package provide Trf]] 2.1.2]} {
                set usetrf 1
            }
        }

        variable uid
        if {![info exists uid]} {
            set uid 0
        }

        namespace export ripemd128 hmac128 Hex \
            RIPEMD128Init RIPEMD128Update RIPEMD128Final \
            RIPEHMAC128Init RIPEHMAC128Update RIPEHMAC128Final
    }
}

# -------------------------------------------------------------------------

# RIPEMD128Init - create and initialize an MD4 state variable. This will be
# cleaned up when we call MD4Final
#
proc ::ripemd::ripemd128::RIPEMD128Init {} {
    variable usetrf
    variable uid
    set token [namespace current]::[incr uid]
    upvar #0 $token tok

    # Initialize RIPEMD-128 state structure (same as MD4).
    array set tok \
        [list \
             A [expr {0x67452301}] \
             B [expr {0xefcdab89}] \
             C [expr {0x98badcfe}] \
             D [expr {0x10325476}] \
             n 0 i "" ]
    if {$usetrf} {
        catch {
            set s [::null]
            ::ripemd128 -attach $s -mode write \
                -read-type variable \
                -read-destination [subst $token](trfread) \
                -write-type variable \
                -write-destination [subst $token](trfwrite)
            fconfigure $s -translation binary -buffering none
            set tok(trf) $s
        }
    }
    return $token
}

proc ::ripemd::ripemd128::RIPEMD128Update {token data} {
    # FRINK: nocheck
    variable $token
    upvar 0 $token state

    if {[info exists state(trf)]} {
        puts -nonewline $state(trf) $data
        return
    }

    # Update the state values
    incr state(n) [string length $data]
    append state(i) $data

    # Calculate the hash for any complete blocks
    set len [string length $state(i)]
    for {set n 0} {($n + 64) <= $len} {} {
        RIPEMD128Hash $token [string range $state(i) $n [incr n 64]]
    }

    # Adjust the state for the blocks completed.
    set state(i) [string range $state(i) $n end]
    return
}

proc ::ripemd::ripemd128::RIPEMD128Final {token} {
    # FRINK: nocheck
    variable $token
    upvar 0 $token state

    if {[info exists state(trf)]} {
        close $state(trf)
        set r $state(trfwrite)
        unset state
        return $r
    }

    # RFC1320:3.1 - Padding
    #
    set len [string length $state(i)]
    set pad [expr {56 - ($len % 64)}]
    if {$len % 64 > 56} {
        incr pad 64
    }
    if {$pad == 0} {
        incr pad 64
    }
    append state(i) [binary format a$pad \x80]

    # RFC1320:3.2 - Append length in bits as little-endian wide int.
    append state(i) [binary format ii [expr {8 * $state(n)}] 0]

    # Calculate the hash for the remaining block.
    set len [string length $state(i)]
    for {set n 0} {($n + 64) <= $len} {} {
        RIPEMD128Hash $token [string range $state(i) $n [incr n 64]]
    }

    # RFC1320:3.5 - Output
    set r [bytes $state(A)][bytes $state(B)][bytes $state(C)][bytes $state(D)]
    unset state
    return $r
}

# -------------------------------------------------------------------------
# HMAC Hashed Message Authentication (RFC 2104)
#
# hmac = H(K xor opad, H(K xor ipad, text))
#
proc ::ripemd::ripemd128::RIPEHMAC128Init {K} {

    # Key K is adjusted to be 64 bytes long. If K is larger, then use
    # the RIPEMD-128 digest of K and pad this instead.
    set len [string length $K]
    if {$len > 64} {
        set tok [RIPEMD128Init]
        RIPEMD128Update $tok $K
        set K [RIPEMD128Final $tok]
        set len [string length $K]
    }
    set pad [expr {64 - $len}]
    append K [string repeat \0 $pad]

    # Cacluate the padding buffers.
    set Ki {}
    set Ko {}
    binary scan $K i16 Ks
    foreach k $Ks {
        append Ki [binary format i [expr {$k ^ 0x36363636}]]
        append Ko [binary format i [expr {$k ^ 0x5c5c5c5c}]]
    }

    set tok [RIPEMD128Init]
    RIPEMD128Update $tok $Ki;                 # initialize with the inner pad
    
    # preserve the Ko value for the final stage.
    # FRINK: nocheck
    set [subst $tok](Ko) $Ko

    return $tok
}

proc ::ripemd::ripemd128::RIPEHMAC128Update {token data} {
    RIPEMD128Update $token $data
    return
}

proc ::ripemd::ripemd128::RIPEHMAC128Final {token} {
    # FRINK: nocheck
    variable $token
    upvar 0 $token state

    set tok [RIPEMD128Init];            # init the outer hashing function
    RIPEMD128Update $tok $state(Ko);    # prepare with the outer pad.
    RIPEMD128Update $tok [RIPEMD128Final $token];  # hash the inner result
    return [RIPEMD128Final $tok]
}

# -------------------------------------------------------------------------

set ::ripemd::ripemd128::RIPEMD128Hash_body {
    variable $token
    upvar 0 $token state

    # RFC1320:3.4 - Process Message in 16-Word Blocks
    binary scan $msg i* blocks
    foreach {X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15} $blocks {
        set A $state(A)   ;  set AA $state(A)
        set B $state(B)   ;  set BB $state(B)
        set C $state(C)   ;  set CC $state(C)
        set D $state(D)   ;  set DD $state(D)

        # Round 1 (track 1). 
        #    F(x,y,z) = x ^ y ^ z
        # Let [abcd x s] denote the operation
        #    a = (a + F(b,c,d) + X[x]) <<< s.
        # Do the following 16 operations.
        # [ABCD  0  11]  [DABC  1  14]  [CDAB  2 15]  [BCDA  3 12]
        set A [expr {($A + [F $B $C $D] + $X0)  <<< 11}]
        set D [expr {($D + [F $A $B $C] + $X1)  <<< 14}]
        set C [expr {($C + [F $D $A $B] + $X2)  <<< 15}]
        set B [expr {($B + [F $C $D $A] + $X3)  <<< 12}]
        # [ABCD  4  5]  [DABC  5  8]  [CDAB  6 7]  [BCDA  7 9]
        set A [expr {($A + [F $B $C $D] + $X4)  <<< 5}]
        set D [expr {($D + [F $A $B $C] + $X5)  <<< 8}]
        set C [expr {($C + [F $D $A $B] + $X6)  <<< 7}]
        set B [expr {($B + [F $C $D $A] + $X7)  <<< 9}]
        # [ABCD  8  11]  [DABC  9  13]  [CDAB 10 14]  [BCDA 11 15]
        set A [expr {($A + [F $B $C $D] + $X8)  <<< 11}]
        set D [expr {($D + [F $A $B $C] + $X9)  <<< 13}]
        set C [expr {($C + [F $D $A $B] + $X10) <<< 14}]
        set B [expr {($B + [F $C $D $A] + $X11) <<< 15}]
        # [ABCD 12  6]  [DABC 13  7]  [CDAB 14 9]  [BCDA 15 8]
        set A [expr {($A + [F $B $C $D] + $X12) <<< 6}]
        set D [expr {($D + [F $A $B $C] + $X13) <<< 7}]
        set C [expr {($C + [F $D $A $B] + $X14) <<< 9}]
        set B [expr {($B + [F $C $D $A] + $X15) <<< 8}]

        # Round 2 (track 1).
        #   G(x, y, z) = (x & y) | (~x & z)
        # Let [abcd k s] denote the operation
        #   a = (a + G(b,c,d) + X[k] + 5A827999) <<< s
        # Do the following 16 operations.
        # [ABCD  7  7]  [DABC  4  6]  [CDAB  13  8]  [BCDA 1 13]
        set A [expr {($A + [G $B $C $D] + $X7  + 0x5a827999) <<< 7}]
        set D [expr {($D + [G $A $B $C] + $X4  + 0x5a827999) <<< 6}]
        set C [expr {($C + [G $D $A $B] + $X13 + 0x5a827999) <<< 8}]
        set B [expr {($B + [G $C $D $A] + $X1  + 0x5a827999) <<< 13}]
        # [ABCD  10  11]  [DABC  6  9]  [CDAB  15  7]  [BCDA 3 15]
        set A [expr {($A + [G $B $C $D] + $X10 + 0x5a827999) <<< 11}]
        set D [expr {($D + [G $A $B $C] + $X6  + 0x5a827999) <<< 9}]
        set C [expr {($C + [G $D $A $B] + $X15 + 0x5a827999) <<< 7}]
        set B [expr {($B + [G $C $D $A] + $X3  + 0x5a827999) <<< 15}]
        # [ABCD 12  7]  [DABC  0  12]  [CDAB 9  15]  [BCDA 5 9]
        set A [expr {($A + [G $B $C $D] + $X12 + 0x5a827999) <<< 7}]
        set D [expr {($D + [G $A $B $C] + $X0  + 0x5a827999) <<< 12}]
        set C [expr {($C + [G $D $A $B] + $X9  + 0x5a827999) <<< 15}]
        set B [expr {($B + [G $C $D $A] + $X5  + 0x5a827999) <<< 9}]
        # [ABCD  2  11]  [DABC  14  7]  [CDAB 11  13]  [BCDA 8 12]
        set A [expr {($A + [G $B $C $D] + $X2  + 0x5a827999) <<< 11}]
        set D [expr {($D + [G $A $B $C] + $X14 + 0x5a827999) <<< 7}]
        set C [expr {($C + [G $D $A $B] + $X11 + 0x5a827999) <<< 13}]
        set B [expr {($B + [G $C $D $A] + $X8  + 0x5a827999) <<< 12}]
        
        # Round 3 (track 1).
        #   H(x,y,z) = (x | ~y) ^ z
        # Let [abcd k s] denote the operation
        #   a = (a + H(b,c,d) + X[k] + 6ED9EBA1) <<< s.
        # Do the following 16 operations.
        # [ABCD  3  11]  [DABC  10  13]  [CDAB  14 6]  [BCDA 4 7]
        set A [expr {($A + [H $B $C $D] + $X3  + 0x6ed9eba1) <<< 11}]
        set D [expr {($D + [H $A $B $C] + $X10 + 0x6ed9eba1) <<< 13}]
        set C [expr {($C + [H $D $A $B] + $X14 + 0x6ed9eba1) <<< 6}]
        set B [expr {($B + [H $C $D $A] + $X4  + 0x6ed9eba1) <<< 7}]
        # [ABCD  9  14]  [DABC 15  9]  [CDAB  8 13]  [BCDA 1 15]
        set A [expr {($A + [H $B $C $D] + $X9  + 0x6ed9eba1) <<< 14}]
        set D [expr {($D + [H $A $B $C] + $X15 + 0x6ed9eba1) <<< 9}]
        set C [expr {($C + [H $D $A $B] + $X8  + 0x6ed9eba1) <<< 13}]
        set B [expr {($B + [H $C $D $A] + $X1  + 0x6ed9eba1) <<< 15}]
        # [ABCD  2  14]  [DABC  7  8]  [CDAB  0 13]  [BCDA 6 6]
        set A [expr {($A + [H $B $C $D] + $X2  + 0x6ed9eba1) <<< 14}]
        set D [expr {($D + [H $A $B $C] + $X7  + 0x6ed9eba1) <<< 8}]
        set C [expr {($C + [H $D $A $B] + $X0  + 0x6ed9eba1) <<< 13}]
        set B [expr {($B + [H $C $D $A] + $X6  + 0x6ed9eba1) <<< 6}]
        # [ABCD  13  5]  [DABC 11  12]  [CDAB  5 7]  [BCDA 12  5]
        set A [expr {($A + [H $B $C $D] + $X13 + 0x6ed9eba1) <<< 5}]
        set D [expr {($D + [H $A $B $C] + $X11 + 0x6ed9eba1) <<< 12}]
        set C [expr {($C + [H $D $A $B] + $X5  + 0x6ed9eba1) <<< 7}]
        set B [expr {($B + [H $C $D $A] + $X12 + 0x6ed9eba1) <<< 5}]

        # Round 4 (track 1).
        #   I(x,y,z) = (x & z) | (y & ^ ~z)
        # Let [abcd k s] denote the operation
        #   a = (a + I(b,c,d) + X[k] + 8F1BBCDC) <<< s.
        # Do the following 16 operations.
        # [ABCD  1  11]  [DABC  9  12]  [CDAB  11 14]  [BCDA 10 15]
        set A [expr {($A + [I $B $C $D] + $X1  + 0x8f1bbcdc) <<< 11}]
        set D [expr {($D + [I $A $B $C] + $X9  + 0x8f1bbcdc) <<< 12}]
        set C [expr {($C + [I $D $A $B] + $X11 + 0x8f1bbcdc) <<< 14}]
        set B [expr {($B + [I $C $D $A] + $X10 + 0x8f1bbcdc) <<< 15}]
        # [ABCD  0  14]  [DABC 8  15]  [CDAB 12 9]  [BCDA 4 8]
        set A [expr {($A + [I $B $C $D] + $X0  + 0x8f1bbcdc) <<< 14}]
        set D [expr {($D + [I $A $B $C] + $X8  + 0x8f1bbcdc) <<< 15}]
        set C [expr {($C + [I $D $A $B] + $X12 + 0x8f1bbcdc) <<< 9}]
        set B [expr {($B + [I $C $D $A] + $X4  + 0x8f1bbcdc) <<< 8}]
        # [ABCD  13  9]  [DABC  3  14]  [CDAB  7 5]  [BCDA 15 6]
        set A [expr {($A + [I $B $C $D] + $X13 + 0x8f1bbcdc) <<< 9}]
        set D [expr {($D + [I $A $B $C] + $X3  + 0x8f1bbcdc) <<< 14}]
        set C [expr {($C + [I $D $A $B] + $X7  + 0x8f1bbcdc) <<< 5}]
        set B [expr {($B + [I $C $D $A] + $X15 + 0x8f1bbcdc) <<< 6}]
        # [ABCD  14  8]  [DABC 5  6]  [CDAB  6 5]  [BCDA 2 12]
        set A [expr {($A + [I $B $C $D] + $X14 + 0x8f1bbcdc) <<< 8}]
        set D [expr {($D + [I $A $B $C] + $X5  + 0x8f1bbcdc) <<< 6}]
        set C [expr {($C + [I $D $A $B] + $X6  + 0x8f1bbcdc) <<< 5}]
        set B [expr {($B + [I $C $D $A] + $X2  + 0x8f1bbcdc) <<< 12}]


        # Round 1 (track 2).
        #   I(x,y,z) = (x & z) | (y & ^ ~z)
        # Let [abcd k s] denote the operation
        #   a = (a + I(b,c,d) + X[k] + 50A28BE6) <<< s.
        # Do the following 16 operations.
        # [ABCD  5  8]  [DABC  14  9]  [CDAB  7 9]  [BCDA 0 11]
        set AA [expr {($AA + [I $BB $CC $DD] + $X5  + 0x50a28be6) <<< 8}]
        set DD [expr {($DD + [I $AA $BB $CC] + $X14 + 0x50a28be6) <<< 9}]
        set CC [expr {($CC + [I $DD $AA $BB] + $X7  + 0x50a28be6) <<< 9}]
        set BB [expr {($BB + [I $CC $DD $AA] + $X0  + 0x50a28be6) <<< 11}]
        # [ABCD  9  13]  [DABC 2  15]  [CDAB 11 15]  [BCDA 4 5]
        set AA [expr {($AA + [I $BB $CC $DD] + $X9  + 0x50a28be6) <<< 13}]
        set DD [expr {($DD + [I $AA $BB $CC] + $X2  + 0x50a28be6) <<< 15}]
        set CC [expr {($CC + [I $DD $AA $BB] + $X11 + 0x50a28be6) <<< 15}]
        set BB [expr {($BB + [I $CC $DD $AA] + $X4  + 0x50a28be6) <<< 5}]
        # [ABCD  13  7]  [DABC  6  7]  [CDAB  15 8]  [BCDA 8 11]
        set AA [expr {($AA + [I $BB $CC $DD] + $X13 + 0x50a28be6) <<< 7}]
        set DD [expr {($DD + [I $AA $BB $CC] + $X6  + 0x50a28be6) <<< 7}]
        set CC [expr {($CC + [I $DD $AA $BB] + $X15 + 0x50a28be6) <<< 8}]
        set BB [expr {($BB + [I $CC $DD $AA] + $X8  + 0x50a28be6) <<< 11}]
        # [ABCD  1  14]  [DABC 10  14]  [CDAB  3 12]  [BCDA 12 6]
        set AA [expr {($AA + [I $BB $CC $DD] + $X1  + 0x50a28be6) <<< 14}]
        set DD [expr {($DD + [I $AA $BB $CC] + $X10 + 0x50a28be6) <<< 14}]
        set CC [expr {($CC + [I $DD $AA $BB] + $X3  + 0x50a28be6) <<< 12}]
        set BB [expr {($BB + [I $CC $DD $AA] + $X12 + 0x50a28be6) <<< 6}]

        # Round 2 (track 2).
        #   H(x,y,z) = (x | ~y) ^ z
        # Let [abcd k s] denote the operation
        #   a = (a + H(b,c,d) + X[k] + 5C4DD124) <<< s.
        # Do the following 16 operations.
        # [ABCD  6  9]  [DABC  11  13]  [CDAB  3 15]  [BCDA 7 7]
        set AA [expr {($AA + [H $BB $CC $DD] + $X6  + 0x5c4dd124) <<< 9}]
        set DD [expr {($DD + [H $AA $BB $CC] + $X11 + 0x5c4dd124) <<< 13}]
        set CC [expr {($CC + [H $DD $AA $BB] + $X3  + 0x5c4dd124) <<< 15}]
        set BB [expr {($BB + [H $CC $DD $AA] + $X7  + 0x5c4dd124) <<< 7}]
        # [ABCD  0  12]  [DABC 13  8]  [CDAB 5 9]  [BCDA 10 11]
        set AA [expr {($AA + [H $BB $CC $DD] + $X0  + 0x5c4dd124) <<< 12}]
        set DD [expr {($DD + [H $AA $BB $CC] + $X13 + 0x5c4dd124) <<< 8}]
        set CC [expr {($CC + [H $DD $AA $BB] + $X5  + 0x5c4dd124) <<< 9}]
        set BB [expr {($BB + [H $CC $DD $AA] + $X10 + 0x5c4dd124) <<< 11}]
        # [ABCD  14  7]  [DABC  15  7]  [CDAB  8 12]  [BCDA 12 7]
        set AA [expr {($AA + [H $BB $CC $DD] + $X14 + 0x5c4dd124) <<< 7}]
        set DD [expr {($DD + [H $AA $BB $CC] + $X15 + 0x5c4dd124) <<< 7}]
        set CC [expr {($CC + [H $DD $AA $BB] + $X8  + 0x5c4dd124) <<< 12}]
        set BB [expr {($BB + [H $CC $DD $AA] + $X12 + 0x5c4dd124) <<< 7}]
        # [ABCD  4  6]  [DABC 9  15]  [CDAB  1 13]  [BCDA 2 11]
        set AA [expr {($AA + [H $BB $CC $DD] + $X4  + 0x5c4dd124) <<< 6}]
        set DD [expr {($DD + [H $AA $BB $CC] + $X9  + 0x5c4dd124) <<< 15}]
        set CC [expr {($CC + [H $DD $AA $BB] + $X1  + 0x5c4dd124) <<< 13}]
        set BB [expr {($BB + [H $CC $DD $AA] + $X2  + 0x5c4dd124) <<< 11}]

        # Round 3 (track 2).
        #   G(x, y, z) = (x & y) | (~x & z)
        # Let [abcd k s] denote the operation
        #   a = (a + G(b,c,d) + X[k] + 6D703EF3) <<< s.
        # Do the following 16 operations.
        # [ABCD  15  9]  [DABC  5 7]  [CDAB  1 15]  [BCDA 3 11]
        set AA [expr {($AA + [G $BB $CC $DD] + $X15 + 0x6d703ef3) <<< 9}]
        set DD [expr {($DD + [G $AA $BB $CC] + $X5  + 0x6d703ef3) <<< 7}]
        set CC [expr {($CC + [G $DD $AA $BB] + $X1  + 0x6d703ef3) <<< 15}]
        set BB [expr {($BB + [G $CC $DD $AA] + $X3  + 0x6d703ef3) <<< 11}]
        # [ABCD  7  8]  [DABC 14  6]  [CDAB 6 6]  [BCDA 9 14]
        set AA [expr {($AA + [G $BB $CC $DD] + $X7  + 0x6d703ef3) <<< 8}]
        set DD [expr {($DD + [G $AA $BB $CC] + $X14 + 0x6d703ef3) <<< 6}]
        set CC [expr {($CC + [G $DD $AA $BB] + $X6  + 0x6d703ef3) <<< 6}]
        set BB [expr {($BB + [G $CC $DD $AA] + $X9  + 0x6d703ef3) <<< 14}]
        # [ABCD  11  12]  [DABC  8  13]  [CDAB  12 5]  [BCDA 2 14]
        set AA [expr {($AA + [G $BB $CC $DD] + $X11 + 0x6d703ef3) <<< 12}]
        set DD [expr {($DD + [G $AA $BB $CC] + $X8  + 0x6d703ef3) <<< 13}]
        set CC [expr {($CC + [G $DD $AA $BB] + $X12 + 0x6d703ef3) <<< 5}]
        set BB [expr {($BB + [G $CC $DD $AA] + $X2  + 0x6d703ef3) <<< 14}]
        # [ABCD  10  13]  [DABC 0  13]  [CDAB  4 7]  [BCDA 13 5]
        set AA [expr {($AA + [G $BB $CC $DD] + $X10 + 0x6d703ef3) <<< 13}]
        set DD [expr {($DD + [G $AA $BB $CC] + $X0  + 0x6d703ef3) <<< 13}]
        set CC [expr {($CC + [G $DD $AA $BB] + $X4  + 0x6d703ef3) <<< 7}]
        set BB [expr {($BB + [G $CC $DD $AA] + $X13 + 0x6d703ef3) <<< 5}]

        # Round 4 (track 2).
        #   F(x,y,z) = x ^ y ^ z
        # Let [abcd k s] denote the operation
        #   a = (a + F(b,c,d) + X[k]) <<< s.
        # Do the following 16 operations.
        # [ABCD  8  15]  [DABC  6 5]  [CDAB  4 8]  [BCDA 1 11]
        set AA [expr {($AA + [F $BB $CC $DD] + $X8)  <<< 15}]
        set DD [expr {($DD + [F $AA $BB $CC] + $X6)  <<< 5}]
        set CC [expr {($CC + [F $DD $AA $BB] + $X4)  <<< 8}]
        set BB [expr {($BB + [F $CC $DD $AA] + $X1)  <<< 11}]
        # [ABCD  3  14]  [DABC 11 14]  [CDAB 15 6]  [BCDA 0 14]
        set AA [expr {($AA + [F $BB $CC $DD] + $X3)  <<< 14}]
        set DD [expr {($DD + [F $AA $BB $CC] + $X11) <<< 14}]
        set CC [expr {($CC + [F $DD $AA $BB] + $X15) <<< 6}]
        set BB [expr {($BB + [F $CC $DD $AA] + $X0)  <<< 14}]
        # [ABCD  5  6]  [DABC  12 9]  [CDAB  2 12]  [BCDA 13 9]
        set AA [expr {($AA + [F $BB $CC $DD] + $X5)  <<< 6}]
        set DD [expr {($DD + [F $AA $BB $CC] + $X12) <<< 9}]
        set CC [expr {($CC + [F $DD $AA $BB] + $X2)  <<< 12}]
        set BB [expr {($BB + [F $CC $DD $AA] + $X13) <<< 9}]
        # [ABCD  9  12]  [DABC 7  5]  [CDAB  10 15]  [BCDA 14 8]
        set AA [expr {($AA + [F $BB $CC $DD] + $X9)  <<< 12}]
        set DD [expr {($DD + [F $AA $BB $CC] + $X7)  <<< 5}]
        set CC [expr {($CC + [F $DD $AA $BB] + $X10) <<< 15}]
        set BB [expr {($BB + [F $CC $DD $AA] + $X14) <<< 8}]

        # Then perform the following additions to combine the results.
        set DD       [expr {$state(B) + $C + $DD}]
        set state(B) [expr {$state(C) + $D + $AA}]
        set state(C) [expr {$state(D) + $A + $BB}]
        set state(D) [expr {$state(A) + $B + $CC}]
        set state(A) $DD
    }

    return
}

proc ::ripemd::ripemd128::byte {n v} {expr {((0xFF << (8 * $n)) & $v) >> (8 * $n)}}
proc ::ripemd::ripemd128::bytes {v} { 
    #format %c%c%c%c [byte 0 $v] [byte 1 $v] [byte 2 $v] [byte 3 $v]
    format %c%c%c%c \
        [expr {0xFF & $v}] \
        [expr {(0xFF00 & $v) >> 8}] \
        [expr {(0xFF0000 & $v) >> 16}] \
        [expr {((0xFF000000 & $v) >> 24) & 0xFF}]
}

# 32bit rotate-left
proc ::ripemd::ripemd128::<<< {v n} {
    return [expr {((($v << $n) \
                        | (($v >> (32 - $n)) \
                               & (0x7FFFFFFF >> (31 - $n))))) \
                      & 0xFFFFFFFF}]
}

# Convert our <<< pseuodo-operator into a procedure call.
regsub -all -line \
    {\[expr {(.*) <<< (\d+)}\]} \
    $::ripemd::ripemd128::RIPEMD128Hash_body \
    {[<<< [expr {\1}] \2]} \
    ::ripemd::ripemd128::RIPEMD128Hash_body

#  F(x,y,z) = x ^ y ^ z
proc ::ripemd::ripemd128::F {X Y Z} {
    return [expr {$X ^ $Y ^ $Z}]
}
# Inline the F function F
regsub -all -line \
    {\[F (\$[ABCD]{1,2}) (\$[ABCD]{1,2}) (\$[ABCD]{1,2})\]} \
    $::ripemd::ripemd128::RIPEMD128Hash_body \
    {(\1 ^ \2 ^ \3)} \
    ::ripemd::ripemd128::RIPEMD128Hash_body
    
# G(x,y,z) = (x & y) | (~x & z)
proc ::ripemd::ripemd128::G {X Y Z} {
    return [expr {($X & $Y) | (~$X & $Z)}]
}
# Inline the G function
regsub -all -line \
    {\[G (\$[ABCD]{1,2}) (\$[ABCD]{1,2}) (\$[ABCD]{1,2})\]} \
    $::ripemd::ripemd128::RIPEMD128Hash_body \
    {((\1 \& \2) | (~\1 \& \3))} \
    ::ripemd::ripemd128::RIPEMD128Hash_body

# H(x,y,z) = (x | ~y) ^ z
proc ::ripemd::ripemd128::H {X Y Z} {
    return [expr {($X | ~$Y) ^ $Z}]
}
# Inline the H function
regsub -all -line \
    {\[H (\$[ABCD]{1,2}) (\$[ABCD]{1,2}) (\$[ABCD]{1,2})\]} \
    $::ripemd::ripemd128::RIPEMD128Hash_body \
    {( (\1 | ~\2) ^ \3)} \
    ::ripemd::ripemd128::RIPEMD128Hash_body

# I(x,y,z) = (x & z) | (y & ~z)
proc ::ripemd::ripemd128::I {X Y Z} {
    return [expr {($X & $Z) | ($Y & ~$Z)}]
}
# Inline the I function
regsub -all -line \
    {\[I (\$[ABCD]{1,2}) (\$[ABCD]{1,2}) (\$[ABCD]{1,2})\]} \
    $::ripemd::ripemd128::RIPEMD128Hash_body \
    {( (\1 \& \3) | (\2 \& ~\3) )} \
    ::ripemd::ripemd128::RIPEMD128Hash_body

# Define the MD4 hashing procedure with inline functions.
proc ::ripemd::ripemd128::RIPEMD128Hash {token msg} $::ripemd::ripemd128::RIPEMD128Hash_body

# -------------------------------------------------------------------------

if {[package provide Trf] != {}} {
    interp alias {} ::ripemd::ripemd128::Hex {} ::hex -mode encode --
} else {
    proc ::ripemd::ripemd128::Hex {data} {
        set result {}
        binary scan $data c* r
        foreach c $r {
            append result [format "%02X" [expr {$c & 0xff}]]
        }
        return $result
    }
}

# -------------------------------------------------------------------------

# Description:
#  Pop the nth element off a list. Used in options processing.
#
proc ::ripemd::ripemd128::Pop {varname {nth 0}} {
    upvar $varname args
    set r [lindex $args $nth]
    set args [lreplace $args $nth $nth]
    return $r
}

# -------------------------------------------------------------------------

# fileevent handler for chunked file hashing.
#
proc ::ripemd::ripemd128::Chunk {token channel {chunksize 4096}} {
    # FRINK: nocheck
    variable $token
    upvar 0 $token state
    
    if {[eof $channel]} {
        fileevent $channel readable {}
        set state(reading) 0
    }
        
    RIPEMD128Update $token [read $channel $chunksize]
}

# -------------------------------------------------------------------------

proc ::ripemd::ripemd128::ripemd128 {args} {
    array set opts {-hex 0 -filename {} -channel {} -chunksize 4096}
    while {[string match -* [set option [lindex $args 0]]]} {
        switch -glob -- $option {
            -hex       { set opts(-hex) 1 }
            -file*     { set opts(-filename) [Pop args 1] }
            -channel   { set opts(-channel) [Pop args 1] }
            -chunksize { set opts(-chunksize) [Pop args 1] }
            --         { Pop args ; break }
            default {
                set err [join [lsort [array names opts]] ", "]
                return -code error "bad option $option:\
                    must be one of $err"
            }
        }
        Pop args
    }

    if {$opts(-filename) != {}} {
        set opts(-channel) [open $opts(-filename) r]
        fconfigure $opts(-channel) -translation binary
    }

    if {$opts(-channel) == {}} {

        if {[llength $args] != 1} {
            return -code error "wrong # args:\
                should be \"ripemd128 ?-hex? -filename file | string\""
        }
        set tok [RIPEMD128Init]
        RIPEMD128Update $tok [lindex $args 0]
        set r [RIPEMD128Final $tok]

    } else {

        set tok [RIPEMD128Init]
        # FRINK: nocheck
        set [subst $tok](reading) 1
        fileevent $opts(-channel) readable \
            [list [namespace origin Chunk] \
                 $tok $opts(-channel) $opts(-chunksize)]
        vwait [subst $tok](reading)
        set r [RIPEMD128Final $tok]

        # If we opened the channel - we should close it too.
        if {$opts(-filename) != {}} {
            close $opts(-channel)
        }
    }
    
    if {$opts(-hex)} {
        set r [Hex $r]
    }
    return $r
}

# -------------------------------------------------------------------------

proc ::ripemd::ripemd128::hmac128 {args} {
    array set opts {-hex 0 -filename {} -channel {} -chunksize 4096 -key {}}
    while {[string match -* [set option [lindex $args 0]]]} {
        switch -glob -- $option {
            -key       { set opts(-key) [Pop args 1] }
            -hex       { set opts(-hex) 1 }
            -file*     { set opts(-filename) [Pop args 1] }
            -channel   { set opts(-channel) [Pop args 1] }
            -chunksize { set opts(-chunksize) [Pop args 1] }
            --         { Pop args ; break }
            default {
                set err [join [lsort [array names opts]] ", "]
                return -code error "bad option $option:\
                    must be one of $err"
            }
        }
        Pop args
    }

    if {$opts(-key) == {}} {
        return -code error "wrong # args:\
            should be \"hmac128 ?-hex? -key key -filename file | string\""
    }

    if {$opts(-filename) != {}} {
        set opts(-channel) [open $opts(-filename) r]
        fconfigure $opts(-channel) -translation binary
    }

    if {$opts(-channel) == {}} {

        if {[llength $args] != 1} {
            return -code error "wrong # args:\
                should be \"hmac128 ?-hex? -key key -filename file | string\""
        }
        set tok [RIPEHMAC128Init $opts(-key)]
        RIPEHMAC128Update $tok [lindex $args 0]
        set r [RIPEHMAC128Final $tok]

    } else {

        set tok [RIPEHMAC128Init $opts(-key)]
        # FRINK: nocheck
        set [subst $tok](reading) 1
        fileevent $opts(-channel) readable \
            [list [namespace origin Chunk] \
                 $tok $opts(-channel) $opts(-chunksize)]
        vwait [subst $tok](reading)
        set r [RIPEHMAC128Final $tok]

        # If we opened the channel - we should close it too.
        if {$opts(-filename) != {}} {
            close $opts(-channel)
        }
    }
    
    if {$opts(-hex)} {
        set r [Hex $r]
    }
    return $r
}

# -------------------------------------------------------------------------

namespace eval ::ripemd {

    namespace import -force [namespace current]::ripemd128::*

    namespace export ripemd128 hmac128 \
        RIPEMD128Init RIPEMD128Update RIPEMD128Final \
        RIPEHMAC128Init RIPEHMAC128Update RIPEHMAC128Final
}

# -------------------------------------------------------------------------

package provide ripemd128 $::ripemd::ripemd128::version

# -------------------------------------------------------------------------
# Local Variables:
#   mode: tcl
#   indent-tabs-mode: nil
# End:


