;A while ago I posted a message claiming a speed of 238,000 ;bytes/sec for an implementation of IDEA on a 33Mh 486. Below is ;an explanation and some code to show how it works. The basic ;trick should be useful on many (but not all) processors. I ;expect only those familiar with IDEA and its reference ;implementation will be able to follow the discussion. See: ; ;Lai, Xueja and Massey, James L. A Proposal for a New Block ;Encryption Standard, Eurocrypt 90 ; ;For those who have been asking for the code, sorry I kept ;putting it off. I wanted to get it out of Turbo Pascal ;ideal-mode, but I never had the time. ; ;Colin Plum wrote IDEA-386 code which is included in PGP ;2.3a and uses the same tricks. I don't know who's is ;faster, but I expect they will be very close. Now ;here's how it's done. ; ;A major bottleneck in software IDEA is the mul() routine, which ;is used 34 times per 64 bit block. The routine performs ;multiplication in the multiplicative group mod 2^16+1. The two ;factors are each in a 16 bit word, and the output is also in a 16 ;bit word. Note that 0 is not a member of the multiplicative ;group and 2^16 does not fit in 16 bits. We therefor use the 0 ;word to represent 2^16. Now group elements map one to one onto ;all possible 16 bit words, since 2^16+1 is prime. ; ;Here is (essentially) the reference implementation from [Lai]. ; ; ;unsigned mul( unsigned a, unsigned b ) { ; long int p ; ; long unsigned q ; ; if( a==0 ) p= 0x00010001 - b ; ; else if( b==0 ) p= 0x00010001 - a ; ; else { ; q= a*b; ; p= (q & 0xffff) - (q>>16) ; if( p<0 ) p= p + 0x00010001 ; ; } ; return (unsigned)(p & 0xffff) ; ;} ; ; ;Note the method of reducing a 32 bit word modulo 2^16-1. We ;subtract the high word from the low word, and add the modulus ;back if the result is less than 0. [Lai] contains a proof that ;this works, and you can convince yourself fairly easily. ; ;To speed up this routine, we note that the tests for a=0 and b=0 ;will rarely be false. With the possible exception of the first 2 ;of the 34 multiplications, 0 should be no more likely than any of ;the other 65535 numbers. Note that if (and only if) either a or ;b is 0 then q will also be 0, and we can check for this in one ;instruction if our processor sets a zero flag for multiplication ;(as the 68000 does but 80x86 does not). ; ;Fortunately p will also be zero after the subtraction if and only ;if either a or b is 0. Proof: r will be zero when the high order ;word of q equals the low order word, and that happens when q is ;divisible by 00010001 hex. Since 00010001h = 2^16+1 is prime, ;this happens if either a or b is a multiple of 2^16+1, and 0 is ;the only such multiple which will fit in a 16 bit word. ; ;The speed-up strategy is to proceed under the assumption that a ;and b are not 0, check to be sure in one instruction, and ;recompute if the assumption was wrong. Here's some 8086 ;assembler code: ; ; mov ax, [a] ; mul [b] ; ax is implied. q is now in DX AX ; sub ax, dx ; mod 2^16+1 ; jnz not0 ; Jump if neither op was 0. Usually taken. ; ; mov ax, 1 ; recompute result knowing one op is 0. ; sub ax, [a] ; sub ax, [b] ; jmp out ; Just jump over adding the carry. ;not0: ; adc ax, 0 ; If r<0 add 1, otherwise do nothing. ;out: ; Result is now in ax ; ; ;Note that when r<0 we add 1 instead of 2^16+1 since the 2^16 part ;overflows out of the result. The "adc ax, 0" does all the work ;of checking for a negative result and adding the modulus if ;needed. ; ;The multiplication takes 9 instructions, 4 of which are rarely ;executed. I believe similar tricks are possible on many ;processors. The one drawback to the check-after-multiply tactic ;is that we can't let the multiply overwrite the only copy of an ;operand. ; ;Note that most software implementations of IDEA will run at ;slightly different speeds when 0's come up in the multiply ;routine. The reference implementation is faster on 0, this one ;is faster on non-zero. This may be a problem for some real-time ;stuff, and also suggests an attack based on timing. ; ;Finally, below is an implementation of the complete encryption ;function in 8086 assembler, to replace the cipher_idea() function ;in PGP. It takes the same parameters as the function from PGP, ;and uses the c language calling conventions. I tested it using ;the debug features of the idea.c file in PGP. You will need to ;add segment/assume directives. This version uses no global data ;and should be reentrant. ; ;The handling of zero multipliers is outside the inner loop so ;that a short conditional jump can loop back to the beginning. ;Forward conditional jumps are usually not taken and backward ;jumps are usually taken, which is consistent with 586 branch ;prediction (or so I've heard). Stalls where the output of one ;instruction is needed for the next seem unavoidable. ; ;Last I heard, IDEA was patent pending. My code is up for grabs, ;although I would get a kick out being credited if you use it. ;On the other hand Colin's code is already tested and ready ;to assemble and link with PGP. ; ;--Bryan ; ;____________________CODE STARTS BELOW THIS LINE_________ ; Called as: asmcrypt( inbuff, outbuff, zkey ) just like PGP PROC _asmcrypt ; establish parameter and local space on stack ; follow c language calling conventions ARG inblock:Word, outblock:Word, zkey:Word LOCAL sx1:Word,sx4:Word,skk:Word,done8:Word =stacksize push bp mov bp, sp sub sp, stacksize ; push ax ; My compiler assumes these are not saved. ; push bx ; push cx ; push dx push si push di ; Put the 16 bit sub-blocks in registers and/or local variables mov si, [inblock] mov ax, [si] mov [sx1], ax ; x1 is in ax and sx1 mov di, [si+2] ; x2 is in di mov bx, [si+4] ; x3 is in bx mov dx, [si+6] mov [sx4], dx ; x4 is in sx4 mov si, [zkey] ; si points to next subkey mov [done8], si add [done8], 96 ; we will be finished with 8 rounds ; when si=done8 @@loop: ; 8 rounds of this add di, [si+2] ; x2+=zkey[2] is in di add bx, [si+4] ; x3+=zkey[4] is in bx mul [Word si] ;x1 *= zkey[0] sub ax, dx jz @@x1 ; if 0, use special case multiply adc ax, 0 @@x1out: mov [sx1], ax ; x1 is in ax and sx1 xor ax, bx ; ax= x1^x3 mul [Word si+8] ; compute kk sub ax, dx ; if 0, use special case multiply jz @@kk adc ax, 0 @@kkout: mov cx, ax ; kk is in cx mov ax, [sx4] ; x4 *= zkey[6] mul [Word si+6] sub ax, dx jz @@x4 ; if 0, use special case multiply adc ax, 0 @@x4out: mov [sx4], ax ; x4 is in sx4 and ax xor ax, di ; x4^x2 add ax, cx ; kk+(x2^x4) mul [Word si+10] ; compute t1 sub ax, dx jz @@t1 ; if 0, use special case multiply adc ax, 0 @@t1out: ; t1 is in ax add cx, ax ; t2 is in cx kk+t1 xor [sx4], cx ; x4 in sx4 xor di, cx ; new x3 in di xor bx, ax ; new x2 in bx xchg bx, di ; x2 in di, x3 in bx xor ax, [sx1] ; x1 in ax mov [sx1], ax ; and [sx1] add si, 12 ; point to next subkey cmp si, [done8] jne @@loop jmp @@out8 ;------------------------------------------ ; Special case multiplications, when one factor is 0 @@x1: mov ax, 1 sub ax, [sx1] sub ax, [Word si] jmp @@x1out @@kk: mov ax, [sx1] ; rebuild overwritten operand xor ax, bx neg ax inc ax sub ax, [si+8] jmp @@kkout @@x4: mov ax, 1 sub ax, [sx4] sub ax, [Word si+6] jmp @@x4out @@t1: mov ax, [sx4] ; rebuild xor ax, di add ax, cx neg ax inc ax sub ax, [si+10] jmp @@t1out ;--------------------------------------------------- ; 8 rounds are done, now that extra pseudo-round @@out8: push di mov di, [outblock] mul [Word si] sub ax, dx jnz @@o1n ; jump over special case code mov ax, 1 sub ax, [sx1] sub ax, [si] jmp @@o1out @@o1n: adc ax, 0 @@o1out: mov [di], ax ; final ciphertext block 1 mov ax, [sx4] mul [Word si+6] sub ax, dx jnz @@o4n ; jump over special case code mov ax, 1 sub ax, [sx4] sub ax, [si+6] jmp @@o4out @@o4n: adc ax, 0 @@o4out: mov [di+6], ax ; final ciphertext block 4 add bx, [si+2] mov [di+2], bx ; final ciphertext block 2 pop ax add ax, [si+4] mov [di+4], ax ; final ciphertext block 3 ; Restore the stack and return pop di pop si ; pop dx ; pop cx ; pop bx ; pop ax mov sp, bp pop bp ret ENDP _asmcrypt