Skip to the content.

Introduction

This library introduces and defines concepts common to transposition ciphers.

Prerequisites

Before working through this lab, you’ll need

You’ll also need experience with

Skills You’ll Learn

This module will define a transposition cipher, in which message characters are transposed in a different order. Additionally, the module will recall some important cipher properties (that decryption recovers encrypted plaintext and that encryption is injective), where these operations are defined in terms of permutations.

Load This Module

This lab is a literate Cryptol document — that is, it can be loaded directly into the Cryptol interpreter. Load this module from within the Cryptol interpreter running in the cryptol-course directory with:

Cryptol> :m labs::Transposition::TranspositionAnswers
Loading module Cryptol
Loading module specs::Primitive::Symmetric::Cipher::Block::Cipher
Loading module specs::Primitive::Symmetric::Cipher::Block::DES
Loading module labs::CryptoProofs::CryptoProofsAnswers
Loading module labs::Transposition::CommonPropertiesAnswers
Loading module labs::Transposition::TranspositionAnswers

We start by defining the module for this lab:

module labs::Transposition::TranspositionAnswers where

You do not need to enter the above into the interpreter; the previous :m ... command loaded this literate Cryptol file automatically. In general, you should run Xcryptol-session commands in the interpreter and only change cryptol code as directed by the exercises, reloading for :m ... to import your changes.

Additionally, we will import some common properties to apply to this spec:

import labs::Transposition::CommonPropertiesAnswers (injective, inverts)

Transposition Ciphers

In a transposition cipher, messages are encrypted by transposing (rearranging) characters, then decrypted by inverting this rearrangement to recover the original order. Confidentiality depends upon a shared secret between a sender and receiver so that only they know how the message was transposed. A permutation mapping, a 1:1 mapping from the ordered set of indices [0..`(m-1)] to a sequence of the same indices in a different order, formalizes this transposition.

Before moving on to simple transposition ciphers such as Scytale, Rail Fence, and Route, we first go over some basics that will be required for transposition ciphers in general.

Permutations and Mappings

A mapping is an operation that associates an element of a given set (domain) with one or more elements of a (same or different) set (range). A bijective mapping maps each element of the domain to a distinct element of the range, and vice-versa. A transposition cipher transposes a message (a sequence of length m) to a scrambled message containing the same elements in a different order (a permutation mapping) known to the sender and receiver. To decrypt a message, a receiver inverts this permutation to yield the original message.

EXERCISE: Define a function isPermutation that returns whether seq': [n]a is a permutation of seq: [n]a. Check your function using isPermutation_test.

(Hint: A helper function will…help.)

/** number of occurrences of `item` in `seq` */
itemCount:
    {n, a}
    (fin n, Eq a) =>
    [n]a -> a -> Integer
itemCount seq item = counts ! 0
  where
    counts = [ 0 ]
           # [ if x == item then count + 1 else count
             | count <- counts
             | x <- seq
             ]

/** is `seq'` a permutation of `seq`? */
isPermutation:
    {n, a}
    (fin n, Eq a) =>
    [n]a -> [n]a -> Bit
isPermutation seq seq' = and
    [ itemCount seq x == itemCount seq' x
    | x <- seq
    ]
/** `isPermutation` test vectors */
property isPermutation_test = and
    [   isPermutation "" ""
    ,   isPermutation "A" "A"
    ,   isPermutation "AA" "AA"
    ,   isPermutation "AB" "AB"
    ,   isPermutation "AB" "BA"
    ,   isPermutation "AAB" "ABA"
    ,   isPermutation "BAB" "ABB"
    ,   isPermutation "BAB" "BBA"

    , ~ isPermutation "A" "B"
    , ~ isPermutation "AA" "AB"
    , ~ isPermutation "AAB" "ABB"
    , ~ isPermutation "BAB" "AAB"
    ]
labs::Transposition::TranspositionAnswers> :check isPermutation_test
Using exhaustive testing.
Passed 1 tests.
Q.E.D.

EXERCISE: Define a function isPermutationMapping, perhaps using the built-in functions all and elem, that recognizes a permutation mapping. Check your function using isPermuationMapping_test.

(Hint: take`{n} [0...] returns the first n numbers starting from 0, i.e. the identity mapping. isPermutationMapping can be defined using this sequence as one of the arguments to all. It may be simpler to define a variant of elem that takes arguments in a different order.)

/** Is `pi` a permutation of `[0,n)`? */
isPermutationMapping:
    {n, w}
    (fin n, Eq w, Integral w, Literal 0 w) =>
    [n]w -> Bit
isPermutationMapping pi = all (elem' pi) (take`{n} [0...])
  where
    elem' p x = elem x p

/** `isPermutationMapping` test vectors */
property isPermutationMapping_test = and
    [   isPermutationMapping []
    ,   isPermutationMapping [0]
    ,   isPermutationMapping [0,1]
    ,   isPermutationMapping [1,0]
    ,   isPermutationMapping [0,1,2]
    ,   isPermutationMapping [0,2,1]
    ,   isPermutationMapping [2,0,1]

    , ~ isPermutationMapping [1]
    , ~ isPermutationMapping [0,0]
    , ~ isPermutationMapping [0,2]
    , ~ isPermutationMapping [1,2]
    , ~ isPermutationMapping [0,1,1]
    , ~ isPermutationMapping [2,0,2]
    ]
labs::Transposition::TranspositionAnswers> :check isPermutationMapping_test
Using exhaustive testing.
Passed 1 tests.
Q.E.D.

EXERCISE: Define a function permute that permutes a sequence seq: [n]a according to a permutation mapping pi: [n]w.

(Hint: Consider built-in function @@.)

permute:
    {n, a, w}
    Integral w =>
    [n]w -> [n]a -> [n]a
permute pi seq = seq @@ pi

EXERCISE: Define a predicate permute_permutes that, given a permutation mapping pi: [n]w and a sequence seq: [n]a, permute returns a permutation of seq. Prove/check this predicate for various sequence lengths and types.

permute_permutes:
    {n, a, w}
    (fin n, Eq a, Eq w, Integral w, Literal 0 w) =>
    [n]w -> [n]a -> Bit
permute_permutes pi seq =
    isPermutationMapping pi ==> 
    isPermutation seq (permute pi seq)
labs::Transposition::TranspositionAnswers> :prove permute_permutes`{4, Char, Integer}
Q.E.D.
(Total Elapsed Time: 0.157s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove permute_permutes`{5, Char, Integer}
Q.E.D.
(Total Elapsed Time: 1.657s, using "Z3")
labs::Transposition::TranspositionAnswers> :check permute_permutes`{8, [2]Char, Integer}
Using random testing.
Passed 100 tests.

EXERCISE: Given a permutation mapping pi: [n]w, return its inverse pi' such that permute pi' inverts permute pi. (inverts is imported from labs::Transposition::CommonProperties.)

(Hint: The idiomatic solution to this exercise, where inverse is defined with take`{n} [0...] as one of the arguments to updates, is perhaps the most elegant in all of Cryptol.)

/** return the inverse of a permutation mapping `pi` */
inverse:
    {n, w}
    (fin n, Integral w, Literal 0 w) =>
    [n]w -> [n]w
inverse pi = updates pi pi (take [0...])

EXERCISE: State a predicate inverse_inverts that inverse satisfies its specification above. Prove this predicate for various sequence lengths and types.

/** `inverse` inverts permutation mapping `pi` */
inverse_inverts:
    {n, a, w}
    (fin n, Eq a, Eq w, Integral w, Literal 0 w) =>
    [n]w -> [n]a -> Bit
inverse_inverts pi seq =
    isPermutationMapping pi ==>
    inverts (permute (inverse pi)) (permute pi) seq
labs::Transposition::TranspositionAnswers> :prove inverse_inverts`{4, Char, Integer}
Q.E.D.
(Total Elapsed Time: 0.022s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove inverse_inverts`{7, Char, Integer}
Q.E.D.
(Total Elapsed Time: 0.871s, using "Z3")
labs::Transposition::TranspositionAnswers> :check inverse_inverts`{128, [2]Char, Integer}
Using random testing.
Passed 100 tests.

EXERCISE: Define a predicate that permute pi is injective if pi is a permutation mapping, and :prove it for various sequence lengths and types. (injective is imported from labs::Transposition::CommonProperties.)

/** `permute pi` is `injective` if `pi` is a permutation mapping */
permute_injective:
    {n, a, w}
    (fin n, Eq a, Eq w, Integral w, Literal 0 w) =>
    [n]w -> [n]a -> [n]a -> Bit
permute_injective pi seq seq' =
    isPermutationMapping pi ==> injective (permute pi) seq seq'
labs::Transposition::TranspositionAnswers> :prove permute_injective`{4, Char, Integer}
Q.E.D.
(Total Elapsed Time: 0.019s, using "Z3")
labs::Transposition::TranspositionAnswers> :check permute_injective`{16, Char, Integer}
Using random testing.
Passed 100 tests.
labs::Transposition::TranspositionAnswers> :check permute_injective`{128, [3]Char, Integer}
Using random testing.
Passed 100 tests.

Encryption and Decryption

With this foundation in place, we can define encrypt and decrypt operations for a transposition cipher in terms of a permutation mapping pi.

EXERCISE: Define encrypt and decrypt in terms of permute and a permutation mapping pi. Do not modify anything left of =.

encrypt = permute
decrypt pi = permute (inverse pi)

EXERCISE: Define predicates cipher_recovery and cipher_injective stating that decrypt pi inverts encrypt pi and that encrypt pi is injective, given a permutation mapping pi. (These are direct assignments to earlier predicate definitions; do not modify anything left of =.)

cipher_recovery = inverse_inverts
cipher_injective = permute_injective

Padding and Filtering

Most transposition ciphers are based on an analogue to block size. For example, Scytale has a rod diameter, but not all messages wrap evenly around this rod, leaving a gap that must be managed when encrypting and decrypting an “uneven” message. Likewise, in Rail Fence, messages are split by “cycles” determined by the number of rails in the fence, and not all messages are a multiple of cycle length. One option to overcome such a limitation is to pad a message, encrypt it, send the encrypted padded message, and remove the padding characters after decryption. However, since transposition ciphers are based on message indices rather than content, the sender can simply derive a permutation mapping based on padded message length, remove indices that are out of place, and send the message permuted with this reduced mapping; the receiver can similarly derive the reduced permutation mapping, based on length, to decrypt the message.

Removing padding characters and reducing permutation mappings are examples of the abstract problem of sequence filtering: Given a predicate f: a -> Bit, return from a sequence seq: [n]a the elements seq': [m]a such that all f seq' == True.

EXERCISE: …just kidding. Normally, we might introduce an exercise to define such a function at this point. However, in Cryptol’s type system, this turns out to be a somewhat difficult problem. Indeed, this module’s author, for whom all prior concepts covered in the lab came naturally, struggled for a day to arrive at what turned out to be an incorrect (but easily correctable) solution. This problem baffled all but the lead instructor for the course. Readers are invited to similarly struggle at this point, but hints showing a couple ways to solve this problem are provided below for those who instead wish to endure a diatribe on different strategies to solve this problem. Even we’re not that mean!

Index swapping

By definition, in a transposition cipher, characters of a message are rearranged. The most basic rearrangement is to swap characters at two positions (indices) in a message (sequence). However, while permutation mappings provide a more efficient mechanism to swap all characters at once, swapping turns out to be useful for the sequence filtering problem…

EXERCISE: Define a function to swap items at indices i and j of a sequence seq: [n]a for number n and arbitrary character type a, using @ and update, then again using @@ and updates (do not use a temporary variable in either definition). Use the swap_equiv predicate to verify that your definitions are equivalent, then the swap_correct predicate to show that one of them is correct. (Because they are equivalent, this will infer that the other swap function is also correct.)

/** Swap `i`th and `j`th entries of sequence `a` via `@`/`update` */
swap_update:
    {n, a, v, w}
    (Integral v, Integral w) =>
    [n]a -> v -> w -> [n]a
swap_update seq i j = update (update seq i (seq @ j)) j (seq @ i)

/** Swap `i`th and `j`th entries of sequence `a` via `@@`/`updates` */
swap_updates:
    {n, a, w}
    Integral w =>
    [n]a -> w -> w -> [n]a
swap_updates seq i j = updates seq [i,j] (seq @@ [j,i])

// Define `swap` as either of above swap functions
swap = swap_updates
/** `swap_update` is functionally equivalent to `swap_updates` */
swap_equiv:
    {n, a, w}
    (fin n, Eq a, Cmp w, Integral w, Literal (max n n) w) =>
    [n]a -> w -> w -> Bit
swap_equiv seq i j =
    0 <= i ==> i < `n ==>
    0 <= j ==> j < `n ==>
    swap_update`{v = w} seq i j == swap_updates seq i j

/** `swap` is correct; it just swaps values at specified indices */
swap_correct:
    {n, a, w}
    (Eq a, Cmp w, Integral w, Literal (max n (max n n)) w) =>
    [n]a -> w -> w -> w -> Bit
swap_correct seq i j k =
    0 <= i ==> i < `n ==>
    0 <= j ==> j < `n ==>
    0 <= k ==> k < `n ==>
    seq' @ k ==
        if k == i then seq @ j
        |  k == j then seq @ i
                  else seq @ k
      where
        seq' = swap seq i j
labs::Transposition::TranspositionAnswers> :prove swap_equiv`{32, Char, Integer}
Q.E.D.
(Total Elapsed Time: 0.025s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove swap_equiv`{512, Char, Integer}
Q.E.D.
(Total Elapsed Time: 0.304s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove swap_equiv`{4096, Char, Integer}
Q.E.D.
(Total Elapsed Time: 28.616s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove swap_correct`{32, Char, Integer}
Q.E.D.
(Total Elapsed Time: 0.049s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove swap_correct`{256, Char, Integer}
Q.E.D.
(Total Elapsed Time: 25.205s, using "Z3")
labs::Transposition::TranspositionAnswers> :check swap_correct`{4096, Char, Integer}
Using random testing.
Passed 100 tests.
Expected test coverage: 0.00% (100 of 2^^32807 values)

Swap-Partitioning

Using swap, we can define a function that “partitions” a sequence into a subsequence of all elements in the original sequence that satisfy a predicate, followed by a subsequence of all elements that do not. To achieve this, we can “walk” through the original sequence, and if the current character satisfies the predicate, keep walking; otherwise, swap the current character with the next one, and branch here while the subsequent character is another padding character. For this strategy, the “current” sequence and index would be captured as a sequence comprehension (much as in a block cipher’s iterations of a round function).

To better visualize this, suppose we’ve been given a String infused with padding characters -, and wish to move them to the end of the message. The following function achieves this for any message length including the empty message "" of length 0:

/** Shift `-` characters to the end of a `String` */
rearrange: {n} (fin n) => String n -> String n
rearrange w = take (last out).0
  where
    out = [(w, 0, 0)]
        # [ if w'@i != '-' then (w', i', j)
             | j <= i      then (w', i, i')
             | w'@j != '-' then (swap w' i j, i', j+1)
            else (w', i, j+1)
            where i' = zext`{width (n + 1)} i + 1
                  j' = zext`{width (n + 1)} j + 1
          | (w', i, j) <- out
          | _ <- tail [0 .. n : [width n]] ]

/** Visualize the steps of `rearrange` */
rearrange_trace: {n} (fin n) => String n -> [_](String (1+n), [width (1 + n)], [width (1 + n)])
rearrange_trace w = out
  where
    out = [(w # ['-'], 0, 0)]
        # [ if w'@i != '-' then (w', i', j)
             | j <= i      then (w', i, i')
             | w'@j != '-' then (swap w' i j, i', j+1)
            else (w', i, j+1)
            where i' = zext`{width (n + 1)} i + 1
                  j' = zext`{width (n + 1)} j + 1
          | (w', i, j) <- out
          | _ <- tail [0 .. n : [width n]] ]

Here’s how this function works for the string "HE-LL-O-":

labs::Transposition::TranspositionAnswers> :s ascii=on
labs::Transposition::TranspositionAnswers> :s base=10
labs::Transposition::TranspositionAnswers> rearrange_trace "HE-LL-O-" 
[("HE-LL-O--", 0, 0), ("HE-LL-O--", 1, 0), ("HE-LL-O--", 2, 0),
 ("HE-LL-O--", 2, 3), ("HEL-L-O--", 3, 4), ("HELL--O--", 4, 5),
 ("HELL--O--", 4, 6), ("HELLO----", 5, 7), ("HELLO----", 5, 8)]

EXERCISE: Using rearrange as a blueprint, define a function partition that, given a predicate f: a -> Bit and sequence seq: [n]a, “partitions” the sequence, returning seq': [n]a such that there exists some i such that all f seqt' == True and all f' seqf' == True, where f' x = ~ f x and (seqt', seqf') = splitAt`{i} seq'. Use the partition_rearranges predicate to :prove (or if you lose patience, :check) that you defined partition correctly, for various sequence lengths.

/**
 * "Partition" a sequence `seq` by a filtering predicate `f` such 
 * that the output `seq'` has all the items satisfying `f`, followed 
 * by all items not satisfying `f`
 */
partition: {n, a} (fin n) => (a -> Bit) -> [n]a -> [n]a
partition f seq = take (last out).0
  where
    out = ([(seq, 0, 0)] : [1]([n]a, [max 1 (width n)], [max 1 (width n)]))
        # [ if f (w' @ i) then (w', i', j)
             | j <= i    then (w', i, i')
             | f (w' @ j)  then (swap w' i j, i', j')
            else (w', i, j')
            where
                i' = i + 1
                j' = j + 1
          | (w', i, j) <- out
          | _ <- tail [0 .. n : [width n]] ]
/** `partition` (with arguments) is equivalent to `rearrange` */
partition_rearranges: {n} (fin n) => String n -> Bit
partition_rearranges =
    partition isNotPadding === rearrange
      where
        isNotPadding c = c != '-'
labs::Transposition::TranspositionAnswers> :prove partition_rearranges`{4}
Q.E.D.
(Total Elapsed Time: 0.242s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove partition_rearranges`{16}
Q.E.D.
(Total Elapsed Time: 1.430s, using "Z3")
labs::Transposition::TranspositionAnswers> :check partition_rearranges`{512}
Using random testing.
Passed 100 tests.
Expected test coverage: 0.00% (100 of 2^^4096 values)

EXERCISE: Is it possible to programmatically determine i for an arbitrary sequence seq and predicate f? Why (not)? Regardless, how might we be able to use partition for transposition ciphers?

Rotate-Partitioning

Another possible approach is to walk through a sequence, rotating the remaining subsequence left iff its first element does not satisfy the filtering predicate. Visualizing this for rearrange on "HE-LL-O-", this approach would proceed as follows:

“HE-LL-O-“ “HELL-O–” “HELLO—”

To pull this off would require a way to enumerate over subsequences split before and after a current index, e.g.

(“”, “HE-LL-O-“) (“H”, “E-LL-O-“) (“HE”, “-LL-O-“) -> (“HE”, “LL-O–”) …

EXERCISE: Is it possible to enumerate over sequence indices and pass each index i to compute splitAt`{i} seq? Why (not)? If it is possible, feel free to submit a pull request! Otherwise, can we apply another technique to operate over these subsequences?

Unfortunately, there is no mechanism for “type sequence comprehensions”. However, we can apply recursion…

/**
 * Recursively shift `-` characters to the end of a string using 
 * sequence rotation
 */
rearrange':
    {n} fin n =>
    String n -> String n
rearrange' w =
    if `n == (0: [width n]) then w
    | w @ 0 == '-' then rearrange' (take`{max 1 n - 1} (w <<< 1)) # (take`{min 1 n} ['-'])
    else (take`{min 1 n} [w @ 0]) # rearrange' (drop`{min 1 n} w)

/** The iterative and recursive `rearrange(')` functions are equivalent */
rearrange_equiv:
    {n} fin n =>
    String n -> Bit
rearrange_equiv = rearrange`{n} === rearrange'`{n}
labs::Transposition::TranspositionAnswers> :prove rearrange_equiv`{8}
Q.E.D.
(Total Elapsed Time: 0.477s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove rearrange_equiv`{12}
Q.E.D.
(Total Elapsed Time: 35.182s, using "Z3")
labs::Transposition::TranspositionAnswers> :check rearrange_equiv`{128}
Using random testing.
Passed 100 tests.
Expected test coverage: 0.00% (100 of 2^^1024 values)

In addition to being recursive, this approach requires tricks with min and max to establish type consistency for an empty sequence. What kind of fool thought this up? (See Introductory Type Hackery for a better, more detailed example of such hackery.)

EXERCISE: Using rearrange' as a blueprint, define a function partition' that does the same as partition, and try to convince yourself (via :prove and/or :check commands using partition'_rearranges) that your definition of partition' is correct for various sequence sizes and types.

partition':
    {n, a} fin n =>
    (a -> Bit) -> [n]a -> [n]a
partition' f w =
    if `n == (0: [width n]) then w
    | ~ (f (w @ 0)) then partition' f (take`{max 1 n - 1} (w <<< 1)) # (take`{min 1 n} [w @ 0])
    else (take`{min 1 n} [w @ 0]) # partition' f (drop`{min 1 n} w)
/** `partition'` (with arguments) is equivalent to `rearrange'` */
partition'_rearranges : {n} (fin n) => String n -> Bit
partition'_rearranges =
    partition' isNotPadding === rearrange'
      where
        isNotPadding c = c != '-'
labs::Transposition::TranspositionAnswers> :prove partition'_rearranges`{8}
Q.E.D.
(Total Elapsed Time: 0.040s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove partition'_rearranges`{16}
Q.E.D.
(Total Elapsed Time: 8.147s, using "Z3")
labs::Transposition::TranspositionAnswers> :check partition'_rearranges`{256}
Using random testing.
Passed 100 tests.
Expected test coverage: 0.00% (100 of 2^^2048 values)

EXERCISE: Define a property partition'_equiv that partition and partition' are functionally equivalent. Are they? If not, why not? Can either or both still be used for transposition ciphers?

/** `partition` and `partition'` are functionally equivalent...or are they? */
partition'_equiv: {n, a} (fin n, Eq a) => (a -> Bit) -> [n]a -> Bit
partition'_equiv f = partition`{n} f === partition'`{n} f
labs::Transposition::TranspositionAnswers> :prove partition'_equiv`{8, Bit} (\b -> b)
Q.E.D.
(Total Elapsed Time: 1.669s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove partition'_equiv`{8, [32]} (\b -> b ! 0)
Counterexample
partition'_equiv`{8, [32]} (\b -> b ! 0)
  [0x00000000, 0x01000000, 0x00000020, 0x40000020, 0x00000040,
   0x00000001, 0x00000001, 0x00000002] = False
(Total Elapsed Time: 0.147s, using "Z3")

EXERCISE: Better yet! Cryptol now has built-in sorting primitives sort and sortBy. Try your hand at using the sortBy primitive to implement partitioning. (Hint: True > False.)

partition'': {n, a} fin n => (a -> Bit) -> [n]a -> [n]a
partition'' f w = sortBy cmp w
  where
    cmp a b = f a >= f b
/** `partition''` and `partition'` are functionally equivalent */
partition''_equiv: {n, a} (fin n, Eq a) => (a -> Bit) -> [n]a -> Bit
partition''_equiv f = partition`{n} f === partition''`{n} f
labs::Transposition::TranspositionAnswers> :prove partition''_equiv`{10, [8]} (\a -> a != '-')
Q.E.D.
(Total Elapsed Time: 1.811s, using "Z3")

I’m sure you’re thinking we should’ve just started with the sortBy solution. Well, us developing this lab is part of the reason the sortBy primitive exists. So, consider the above as an historical reference of our earlier stumblings.

Reduction of Padded Partition Mappings

Phew! Now that we have defined a partition function…

EXERCISE: Define a function unpad that uses partition (or partition') and take to reduce a permutation mapping (n + p) w to a possibly smaller [n]w (where p >= 0). Use unpad_unpads to check your definition of unpad is correct for various valid permutation mappings of various lengths and paddings. (Checking invalid permutation mappings is trivial and inefficient.)
Can you think of a more efficient way to increase confidence in the correctness of this function?

(Note: partition' supports “larger” values of n and p for proofs, but this slows considerably for double-digit values of either. Why? Who knows?)

unpad:
    {n, p}
    (fin n, fin p) =>
    [n + p][width (n + p)] -> [n][width n]
unpad pi =
    map drop (take (partition' ((>) `n) pi))
unpad_unpads:
    {n, p}
    (fin n, fin p) =>
    [n + p][width (n + p)] -> Bit
unpad_unpads pi =
    isPermutationMapping`{n + p} pi ==> isPermutationMapping`{n} (unpad pi)
labs::Transposition::TranspositionAnswers> :prove unpad_unpads`{4, 2}
Q.E.D.
(Total Elapsed Time: 0.031s, using "Z3")
labs::Transposition::TranspositionAnswers> :prove unpad_unpads`{8, 4}
Q.E.D.
(Total Elapsed Time: 1.881s, using "Z3")
labs::Transposition::TranspositionAnswers> :check unpad_unpads`{25, 7}
Using random testing.
Passed 100 tests.
Expected test coverage: 0.00% (100 of 2^^192 values)

Conclusion

This lab presented abstract definitions for transposition ciphers, formalizing definitions for permutations and inverses. Subsequent labs will provide specific examples of transposition ciphers.

Solicitation

How was your experience with this lab? Suggestions are welcome in the form of a ticket on the course GitHub page: https://github.com/weaversa/cryptol-course/issues

From here, you can go somewhere!

     
  ^ Transposition Ciphers  
< Common Properties for Ciphers Transposition (Answers) Esrever >
  ? Transposition