---stevan apter" <apter / panix.com> writes:

> i was hoping that my previous post on the solitaire algorithm
> would stimulate an attack on the problem

Well, in the interests of putting a stake in the ground, below is my
simplistic attempt at the Solitaire encrypt/decrypt program. I didn't
do anything clever-I just implemented it from the recipe.

I suspect the interesting part will come from two sources:

1. Optimizing a solution: the code below is pretty much
   stream-of-consciousness stuff. There will be plenty of more optimal 
   ways of doing things. In particular, I don't think it's necessary
   to do all the copying of the deck during cutting.


2. Going terse. Again, I wrote the code below for simplicity, not
   terseness. I suspect Ruby's more functional style of programming
   would lead to some very compact solutions. Let's see it folks - a
   one-liner please!

Anyway, be gentle on me - I've had a long couple of days...

Regards


Dave

(ps. The official Perl code posted on counterpane.com has a bug in
decryption, as I found out when doing comparative testing)



---Content-Disposition: attachment; filename=t1.rb
Content-Description: Solitaire program

#!/usr/bin/env ruby
#############################################################
#
# Quick'n'nasty Ruby implementation of the Solitaire
# cryptosystem.
#
# Dave Thomas <dave / thomases.com>  May 2000
#
#############################################################
#
# We represent the deck internally as the binary bytes 1..54,
# with 53 being the A Joker and 54 the B joker.
#
# The implementation is pretty straightforward. The Deck is created
# and manipulated by the KeyStream class, and encryption and decryption
# by CodeBook.
#

##################################################################
#
#  It's convenient to have some additional functionality for Strings

class String
  # Return a normalized string given arbitrary input
  # Convert lowecase to uppercase, and remove non-alphas
  def normalize
    upcase.scan(/[A-Z]+/).join('')
  end

  # pad a string to a multiple of five characters
  def pad
    return (self + "XXXX")[0...5*((length+4)/5)]
  end

  # Return a new string with a space every 5 characters
  def byFives
    scan(/...../).join(' ')
  end
end

##################################################################
#
# This is the deck of cards

class KeyStream
  A  3; AC  .chr
  B  4; BC  .chr

  # Print a readable form of the Deck for debugging
  def printDeck(deck
eck)
    res  deck.tr("\001-\066", 'A-Za-z@*')
    puts res
  end

  # Initialized ourselves with the given passphrase. The encrypt
  # parameter is true if we're an encrypter, false if we're decrypting
  # We initialize the deck from the passphrase by shuffling and cutting
  # for each character the passphrae contains.

  def initialize(passphrase, encrypt)
    @encrypt  ncrypt ? 1 : -1
    @deck  1..54).to_a.pack('c*')
    passphrase.each_byte do |ch|
      shuffle
      countCut(ch - ?A + 1) 
    end
  end

  # Perform one pass through the deck. Move the A joker down one, the
  # B joker down two, triple cut around the jokers, and then count cut
  # based on the last character in the deck.

  def shuffle
    moveDown(A)
    moveDown(B)
    moveDown(B)
    tripleCut
    countCut
  end

  # Move the given character down, wrapping around the end of the
  # deck if necessary

  def moveDown(char)
    if @deck[53] char
     @deck  deck[0,1] + char.chr + @deck[1..52]
    else
      @deck.sub!(/(#{char.chr})(.)/m) { "#$2#$1" }
    end
  end

  # swap the segments that are before the first joker and after the last

  def tripleCut
    @deck.sub!(/(.*)([#{AC}#{BC}].*[#{AC}#{BC}])(.*)/m) { "#$3#$2#$1" }
  end

  # Swap the segments before and after the 'withChar'th chapter
  # leaving the last character untouched

  def countCut(withChar  deck[53])
    withChar   if withChar B
    @deck[0..52]  deck[withChar..52] + @deck[0...withChar]
  end

  # Generate the next key, ignoring jokers

  def nextKey
    loop {
      shuffle
      offset  deck[0]
      offset   if offset B
      return @encrypt * @deck[offset] if @deck[offset] < A
    }
  end
end

##################################################################
#
# This simple class controls encryption
#

class CodeBook
  def initialize(passphrase, encrypt  rue)
    @keystream  eyStream.new(passphrase.normalize, encrypt)
  end

  # encrypt/decrypt a string
  def encrypt(cleartext)
    text  leartext.normalize.pad
    for i in 0...text.length
      text[i]  text[i] - ?A + @keystream.nextKey)%26 + ?A 
    end
    text
  end
end


##################################################################
#
# Test program. Read alternate lines of passphrase/cleartext/expected.
# Create an encryptor, encrypt the cleartext, then create
# a decryptor and decrypt it again
#

while passphrase  ets
  enc  odeBook.new(passphrase)
  coded  nc.encrypt(gets)
  puts coded.byFives
  expected  ets.chomp
  raise "Failed: '#{coded} ! {expected}" unless coded expected
  dec  odeBook.new(passphrase, false)
  decoded  ec.encrypt(coded)
  puts decoded.byFives
  puts "------------------------------------------------------------"
end

##################################################################
__END__

AAAAA AAAAA
EXKYIZSGEH
FOO
AAAAA AAAAA AAAAA
ITHZUJIWGRFARMW
CRYPTONOMICON
SOLITAIRE
KIRAKSFJAN

---