Move to Project.toml, bump to Julia v1 (#18)

This commit is contained in:
Patrick Stevens
2019-09-12 19:18:28 +01:00
committed by GitHub
parent 3196694bc1
commit 2a0eb5c630
24 changed files with 128 additions and 105 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
*.jl.cov
*.jl.*.cov
*.jl.mem
Manifest.toml
.vscode/*

View File

@@ -2,10 +2,9 @@
language: julia
os:
- linux
- osx
julia:
- 0.5
- 0.6
- 1.0
- 1.1
- nightly
notifications:
email: false

15
Project.toml Normal file
View File

@@ -0,0 +1,15 @@
name = "ClassicalCiphers"
uuid = "ecf26e93-935c-5e64-9b21-bc8ac81b4723"
[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
[compat]
julia = "≥ 1.0.0"
[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test"]

View File

@@ -1,3 +0,0 @@
julia 0.5
Compat 0.9.5
Iterators

View File

@@ -2,8 +2,6 @@ module ClassicalCiphers
# Monoalphabetic
using Compat
include("common.jl")
include("monoalphabetic.jl")
include("caesar.jl")

View File

@@ -1,5 +1,3 @@
using Iterators
"""
Encrypts the given plaintext according to the Affine cipher.
The key is given as a pair of integers: first the multiplier, then
@@ -42,6 +40,26 @@ function decrypt_affine(ciphertext, mult::Integer, add::Integer; offset=0)
decrypt_monoalphabetic(ciphertext, keystr)
end
function max_by(arr, f)
currMax = undef
currAns = undef
set = false
for i in arr
if set == false
set = true
currMax = f(i)
currAns = i
else
t = f(i)
if currMax < t
currMax = t
currAns = i
end
end
end
currAns
end
"""
Cracks the given ciphertext according to the Affine cipher.
Returns ((multiplier, additive constant), decrypted string).
@@ -51,15 +69,11 @@ Converts the input to lowercase, but retains symbols.
Optional arguments: mult=0, which specifies the multiplier if known;
add=-1, which specifies the additive constant if known.
"""
function crack_affine(ciphertext; mult=0, add=-1)
function crack_affine(ciphertext::AbstractString; mult=0, add=-1)
mults = mult != 0 ? [mult] : [i for i in filter(x -> (x % 2 != 0 && x % 13 != 0), 1:25)]
adds = add != -1 ? [add] : (0:25)
possible_keys = product(mults, adds)
possible_keys = Iterators.product(mults, adds)
decrypts = [(i, decrypt_affine(ciphertext, i[1], i[2])) for i in possible_keys]
sort!(decrypts, by=(x -> string_fitness(x[2])))
reverse(decrypts[end])
reverse(max_by([(i, decrypt_affine(ciphertext, i[1], i[2])) for i in possible_keys], x -> string_fitness(x[2])))
end

View File

@@ -5,7 +5,7 @@ so encrypt_caesar("abc", 1) == "BCD".
Converts the input to uppercase.
"""
function encrypt_caesar(plaintext, key::Integer)
function encrypt_caesar(plaintext, key::T) where {T<:Integer}
# plaintext: string; key: integer offset, so k=1 sends "a" to "b"
key = ((key-1) % 26) + 1
keystr = join([collect(Char(97+key):'z'); collect('a':Char(97+key-1))])

View File

@@ -29,10 +29,6 @@ function rotateRightStr(st::AbstractString, n)
join(rotateRight(split(st, ""), n), "")
end
flatten{T}(a::Array{T,1}) = any(x->isa(x,Array),a)? flatten(vcat(map(flatten,a)...)): a
flatten{T}(a::Array{T}) = reshape(a,prod(size(a)))
flatten(a)=a
function splitBy(arr, func)
# implementation of the Mathematica function SplitBy
# splits the array into sublists so that each list has the same value of func
@@ -58,7 +54,7 @@ function get_trigram_fitnesses()
for l in lines
(ngram, fitness) = split(l)
dict[ngram] = parse(fitness)
dict[ngram] = parse(Int32, fitness)
end
close(f)
@@ -72,7 +68,7 @@ Performs a trigram analysis on the input string, to determine how close it
is to English. That is, splits the input string into groups of three letters,
and assigns a score based on the frequency of the trigrams in true English.
"""
function string_fitness(input; alreadystripped=false)
function string_fitness(input::AbstractString; alreadystripped=false)
if !alreadystripped
str = letters_only(input)
else

View File

@@ -72,15 +72,15 @@ Ring is a string - for example, "AAA" - being the offset applied to each rotor.
"AAA", for example, signifies no offset. The string must be three letters.
skip_stecker_check=false, which when `true` skips validation of stecker settings.
"""
function encrypt_enigma{I <: Integer}(plaintext,
rotors::Array{I, 1}, key::AbstractString;
reflector_id='B', ring::AbstractString = "AAA",
stecker = Tuple{Char, Char}[],
skip_stecker_check = false)
function encrypt_enigma(plaintext,
rotors::Array{T, 1}, key::AbstractString;
reflector_id='B', ring::AbstractString = "AAA",
stecker = Tuple{Char, Char}[],
skip_stecker_check = false) where {T<:Integer}
parsed_stecker = parse_stecker(stecker)
# validate stecker settings
if !skip_stecker_check
if flatten(parsed_stecker) != unique(flatten(parsed_stecker))
if collect(Iterators.flatten(parsed_stecker)) != collect(unique(Iterators.flatten(parsed_stecker)))
error("No letter may appear more than once in stecker settings.")
end
end

View File

@@ -1,4 +1,4 @@
using Iterators
using LinearAlgebra
"""
Encrypts the given plaintext according to the Hill cipher.
@@ -12,7 +12,7 @@ is thrown.
The matrix must be invertible modulo 26. If it is not, an error is thrown.
"""
function encrypt_hill{I <: Integer}(plaintext, key::Array{I, 2})
function encrypt_hill(plaintext::AbstractString, key::AbstractArray{T, 2}) where {T<:Integer}
if round(Integer, det(key)) % 26 == 0
error("Key must be invertible mod 26.")
end
@@ -30,7 +30,7 @@ function encrypt_hill{I <: Integer}(plaintext, key::Array{I, 2})
# split
split_text = reshape(chars, (keysize, div(length(text), keysize)))
encrypted = mapslices(group -> 65 .+ ((key * group) .% 26), split_text, 1)
encrypted = mapslices(group -> 65 .+ ((key * group) .% 26), split_text, dims = [1])
ans = IOBuffer()
for group in encrypted
@@ -42,24 +42,21 @@ function encrypt_hill{I <: Integer}(plaintext, key::Array{I, 2})
end
function encrypt_hill(plaintext, key::AbstractString)
function encrypt_hill(plaintext::AbstractString, key::AbstractString)
if round(Integer, sqrt(length(key))) != sqrt(length(key))
error("Hill key must be of square integer size.")
end
matrix_dim = round(Integer, sqrt(length(key)))
key_string = uppercase(letters_only(key))
keys = map(x -> Int(x) - 65, collect(uppercase(letters_only(key))))
key_matrix = Array{Integer}(matrix_dim, matrix_dim)
for i in 1:length(key)
key_matrix[ind2sub([matrix_dim, matrix_dim], i)...] = Int(key_string[i]) - 65
end
key_matrix = reshape(keys, matrix_dim, matrix_dim)
encrypt_hill(plaintext, transpose(key_matrix))
end
function minor{I <: Integer}(mat::Array{I, 2}, i, j)
function minor(mat::AbstractArray{T, 2}, i, j) where {T<:Integer}
d = det(mat[[1:i-1; i+1:end], [1:j-1; j+1:end]])
round(Integer, d)
end
@@ -67,13 +64,13 @@ end
"""
Computes the adjugate matrix for given matrix.
"""
function adjugate{I <: Integer}(mat::Array{I, 2})
arr = [(-1)^(i+j) * minor(mat, i, j) for (i, j) in product(1:size(mat, 1), 1:size(mat, 2))]
function adjugate(mat::AbstractArray{T, 2}) where {T<:Integer}
arr = [(-1)^(i+j) * minor(mat, i, j) for (i, j) in Iterators.product(1:size(mat, 1), 1:size(mat, 2))]
ans = reshape(arr, size(mat))
Array{Integer, 2}(transpose(ans))
end
function decrypt_hill{I <: Integer}(ciphertext, key::Array{I, 2})
function decrypt_hill(ciphertext, key::AbstractArray{T, 2}) where {T<:Integer}
if ndims(key) != 2
error("Key must be a two-dimensional matrix.")
end
@@ -97,12 +94,9 @@ function decrypt_hill(ciphertext, key::AbstractString)
end
matrix_dim = round(Integer, sqrt(length(key)))
key_string = uppercase(letters_only(key))
keys = map(x -> Int(x) - 65, collect(uppercase(letters_only(key))))
key_matrix = Array{Integer}(matrix_dim, matrix_dim)
for i in 1:length(key)
key_matrix[ind2sub([matrix_dim, matrix_dim], i)...] = Int(key_string[i]) - 65
end
key_matrix = reshape(keys, matrix_dim, matrix_dim)
decrypt_hill(ciphertext, transpose(key_matrix))
end

View File

@@ -47,7 +47,7 @@ function decrypt_monoalphabetic(ciphertext, key::AbstractString)
# working in lowercase; key is assumed only to have each element appearing once
# and to be in lowercase
# so decrypt_monoalphabetic("cb", "cbade…") is "ab"
dict = Dict(a => Char(96 + search(lowercase(key), a)) for a in lowercase(key))
dict = Dict(a => Char(96 + findfirst(i -> i == a, lowercase(key))) for a in lowercase(key))
encrypt_monoalphabetic(lowercase(ciphertext), dict)
end

View File

@@ -11,8 +11,8 @@ function playfair_key_to_square(key::AbstractString, replacement)
# delete duplicates etc from key
key_sanitised = union(uppercase(letters_only(key)))
# construct key square
remaining = collect(filter(x -> (x != replacement[2] && findfirst(key_sanitised, x) == 0), 'A':'Z'))
keysquare = reshape([key_sanitised; remaining], 5, 5)
remaining = filter(x -> (x != replacement[2] && !(in(x, key_sanitised))), 'A':'Z')
keysquare = reshape(collect(Iterators.flatten([key_sanitised; remaining])), 5, 5)
return permutedims(keysquare, (2, 1)) # transpose() is deprecated
end
@@ -39,7 +39,7 @@ combined=('I', 'J'), marks the characters which are to be combined in the text.
"""
function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped=false, combined=('I', 'J'))
if !stripped
if findfirst(key, combined[2]) != 0
if in(combined[2], key)
error("Key must not contain symbol $(combined[2]), as it was specified to be combined.")
end
plaintext_sanitised = uppercase(letters_only(plaintext))
@@ -88,8 +88,8 @@ function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped=false, combin
l1 = plaintext_sanitised[i]
l2 = plaintext_sanitised[i+1]
l1pos = ind2sub((5, 5), findfirst(key, l1))
l2pos = ind2sub((5, 5), findfirst(key, l2))
l1pos = CartesianIndices((5, 5))[findfirst(i -> i == l1, key)]
l2pos = CartesianIndices((5, 5))[findfirst(i -> i == l2, key)]
@assert l1pos != l2pos
@@ -124,7 +124,6 @@ Does not attempt to delete X's inserted as padding for double letters.
"""
function decrypt_playfair(ciphertext, key::Array{Char, 2}; combined=('I', 'J'))
# to obtain the decrypting keysquare, reverse every row and every column
keysquare = mapslices(reverse, key, 2)
keysquare = permutedims(mapslices(reverse, permutedims(keysquare, (2, 1)), 2), (2, 1))
keysquare = reverse(reverse(key, dims=1), dims=2)
lowercase(encrypt_playfair(ciphertext, keysquare, combined=combined))
end

View File

@@ -1,8 +1,8 @@
function next_solitaire(deckIn)
function next_solitaire(deckIn::AbstractVector{T}) :: AbstractVector{Integer} where {T<:Integer}
# performs one round of Solitaire on the given deck
# first joker
deck = deckIn
jokerPos = findin(deck, 53)[1]
jokerPos = findfirst(i -> i == 53, deck)
if jokerPos != length(deck)
inter = deck[jokerPos+1]
deck[jokerPos+1] = deck[jokerPos]
@@ -14,7 +14,7 @@ function next_solitaire(deckIn)
deck = rotateRight(deck)
end
# second joker
jokerPos = findin(deck, 54)[1]
jokerPos = findfirst(i -> i == 54, deck)
if jokerPos <= length(deck) - 2
inter = deck[jokerPos]
deck[jokerPos] = deck[jokerPos+1]
@@ -49,7 +49,7 @@ function next_solitaire(deckIn)
split_deck[1] = split_deck[end]
split_deck[end] = inter
end
deck = flatten(split_deck)
deck = collect(Iterators.flatten(split_deck))
# take bottom of deck and put it just above last card
cardsToTake = (deck[end] > 52) ? 0 : deck[end]
@@ -57,28 +57,34 @@ function next_solitaire(deckIn)
append!(intermediate, [deck[end]])
deck = intermediate
return deck
return collect(deck)
end
function keychar_from_deck(deck::Vector)
function keychar_from_deck(deck::AbstractVector{T}) :: Integer where {T<:Integer}
# given a deck, returns an integer which is the Solitaire key value
# output by that deck
deck[((deck[1]==54 ? 53 : deck[1]) % length(deck)) + 1]
end
type SolitaireKeyStream
deck::Vector
struct SolitaireKeyStreamStruct
deck::AbstractVector{Integer}
end
Base.start(b::SolitaireKeyStream) = (next_solitaire(b.deck))
function Base.next(b::SolitaireKeyStream, state)
curState = state
function Base.iterate(b::SolitaireKeyStreamStruct)
(0, next_solitaire(b.deck))
end
function Base.iterate(b::SolitaireKeyStreamStruct, state)
curState = state::AbstractVector{Integer}
while keychar_from_deck(curState) > 52
curState = next_solitaire(curState)
end
(keychar_from_deck(curState), next_solitaire(curState))
(keychar_from_deck(curState)::Integer, next_solitaire(curState))
end
function SolitaireKeyStream(initialDeck::AbstractVector{T}) where {T<:Integer}
Iterators.filter(i -> i <= 52, Iterators.drop(SolitaireKeyStreamStruct(initialDeck), 1))
end
Base.done(b::SolitaireKeyStream, state) = false
"""
Encrypts the given plaintext according to the Solitaire cipher.
@@ -86,16 +92,11 @@ The key may be given either as a vector initial deck, where the cards are
1 through 54 (the two jokers being 53, 54), or as a string.
Schneier's keying algorithm is used to key the deck if the key is a string.
"""
function encrypt_solitaire(string, initialDeck::Vector)
function encrypt_solitaire(string, initialDeck::AbstractVector{T}) :: AbstractString where {T<:Integer}
inp = uppercase(letters_only(string))
ans = ""
i = 0
for keyval in SolitaireKeyStream(initialDeck)
i += 1
if i > length(inp)
break
end
ans *= encrypt_caesar(inp[i], keyval)
for (keyval::Integer, input::Char) in zip(SolitaireKeyStream(initialDeck), collect(inp))
ans *= encrypt_caesar(input, keyval)
end
return ans
end
@@ -110,17 +111,13 @@ function decrypt_solitaire(string, initialDeck::Vector)
inp = uppercase(letters_only(string))
ans = ""
i = 0
for keyval in SolitaireKeyStream(initialDeck)
i += 1
if i > length(inp)
break
end
ans *= decrypt_caesar(inp[i], keyval)
for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp))
ans *= decrypt_caesar(input, keyval)
end
return ans
end
function key_deck(key::AbstractString)
function key_deck(key::AbstractString) :: AbstractVector{Integer}
# returns the Solitaire deck after it has been keyed with the given string
deck = collect(1:54)
for keyval in uppercase(letters_only(key))
@@ -133,8 +130,9 @@ function key_deck(key::AbstractString)
deck
end
function encrypt_solitaire(string, key::AbstractString)
encrypt_solitaire(string, key_deck(key))
function encrypt_solitaire(string::AbstractString, key::AbstractString) :: AbstractString
key = key_deck(key)
encrypt_solitaire(string, key)
end
function decrypt_solitaire(string, key::AbstractString)

View File

@@ -1,3 +1,5 @@
using Statistics
"""
Encrypts the given string using the Vigenere cipher according to the given vector of offsets.
For example, encrypt_vigenere("ab", [0, 1]) returns "AC".
@@ -14,7 +16,7 @@ For example, decrypt_vigenere("ac", [0, 1]) returns "ab".
"""
function decrypt_vigenere(ciphertext, key::Array)
# ciphertext: string; key: vector of integer offsets, so [0, 1] decrypts "ac" as "ab"
lowercase(encrypt_vigenere(ciphertext, 26-key))
lowercase(encrypt_vigenere(ciphertext, map(x -> 26-x, key)))
end
"""

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
# docs examples

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
@test encrypt_caesar("THIS CODE WAS INVENTED BY JULIUS CAESAR", 3) == "WKLV FRGH ZDV LQYHQWHG EB MXOLXV FDHVDU"

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
@test (encrypt_enigma("AAA", [1,2,3], "AAA") == "BDZ")
@test (decrypt_enigma("BDZ", [1,2,3], "AAA") == "aaa")

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
# Wikipedia examples

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
@test encrypt_monoalphabetic("aBcbD", Dict{Char, Char}('a' => '5', 'B' => '@', 'b' => 'o', 'D' => 'D')) == "5@coD"

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
# Wikipedia example
@test encrypt_playfair("Hide the gold in the tree stump", "playfair example") == "BMODZBXDNABEKUDMUIXMMOUVIF"

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
@test encrypt_portas("DEFENDTHEEASTWALLOFTHECASTLE", "FORTIFICATION") == uppercase("synnjscvrnrlahutukucvryrlany")
@test decrypt_portas("synnjscvrnrlahutukucvryrlany", "FORTIFICATION") == lowercase("DEFENDTHEEASTWALLOFTHECASTLE")

View File

@@ -1,8 +1,17 @@
using ClassicalCiphers
using Test
tests = ["vigenere", "monoalphabetic", "solitaire",
"caesar", "portas", "affine", "hill", "playfair",
"enigma"]
tests = [
"playfair",
"vigenere",
"monoalphabetic",
"caesar",
"portas",
"affine",
"enigma",
"hill",
"solitaire",
]
println("Running tests:")

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
@test encrypt_solitaire("aaaaaaaaaaaaaaa", "") == "EXKYIZSGEHUNTIQ"
@test encrypt_solitaire("aaaaaaaaaaaaaaa", "f") == "XYIUQBMHKKJBEGY"

View File

@@ -1,5 +1,5 @@
using ClassicalCiphers
using Base.Test
using Test
# doc examples
@test encrypt_vigenere("ab", [0, 1]) == "AC"
@@ -9,7 +9,7 @@ using Base.Test
# others
@test decrypt_vigenere("DYIMXMESTEZDPNFVVAMJ", [11, 18, 5, 13, 12, 9, 14]-1) == "theamericanshaverobb"
@test decrypt_vigenere("DYIMXMESTEZDPNFVVAMJ", map(x -> x - 1, [11, 18, 5, 13, 12, 9, 14])) == "theamericanshaverobb"
@test decrypt_vigenere("DYIMXMESTEZDPNFVVAMJ", "kremlin") == "theamericanshaverobb"