NOTE: This page contains all the examples presented during the lectures, as well as all the homework assignments. Click here to go back to the main page with the course information and schedule.

[link]  1. Introduction, Background, and Motivation

[link]  1.1. Overview

When many real-world problems are addressed or solved mathematically and computationally, the details of those problems are abstracted away until they can be represented directly as idealized mathematical structures (e.g., numbers, sets, trees, graphs, matrices, and so on). In this course, we will study a collection of such idealized mathematical objects: integers, groups, residues, and several others. We will see how these structures and their properties can be used for implementing useful computational solutions to problems such as random number generation, prime number generation, error correction, trusted and distributed storage, secure communication, and others.

In covering the material for this course, we will use the standard language and conventions for discussing these mathematical structures that have been developed by the community of mathematicians over the course of history. You will need to become familiar with these conventions in order to find, identify, and use the structures and techniques that have already been developed for representing and solving certain computational problems. At the same time, we will also learn how modern programming languages and programming paradigms can be used to implement these structures and algorithms both accessibly and efficiently.

The development and application of mathematics involves abstraction. A problem can be viewed at multiple levels of abstraction, and in developing mathematics humans have adopted a variety of techniques that allow them to successfully employ abstraction to study natural phenomena and solve problems.

symbolic abstract meaning concrete meaning in
application domain
2 + 3 5 five objects
{(1, 2), (1, 3)} acyclic graph file system
{(1, 2), (2, 3), (3, 1)} graph with cycle network
{(0,1), (1,2), (2,0)} permutation random number sequence

The above illustrates the different levels of abstraction that may exist for a given problem. We employ a language of symbols to denote certain abstract structures, which may correspond to actual structures in the world. A string of symbols corresponds to a particular abstract object. Notice that the actual object being modelled and the abstract structure behave the same way, and that this behavior implies certain rules about how we can manipulate the symbols without changing the object that they name. For example, we can represent the same graph using the two strings of symbols "{(1,2), (2,3), (3,1)}" and "{(1,2), (2,3), (3,1)}", or the same number of objects using "2 + 3", "3 + 2", "1 + 4", and so on.

In this course, we will begin to reviewing the terminology and concepts of logic, integer arithmetic, and set theory, which we will use throughout the course. We will then show that the algebraic properties of the integers also apply to congruence classes of integers (i.e., the properties of modular arithmetic operations), and we will derive and utilize theorems that have useful computer science applications (such as for generating random numbers and creating cryptographic protocols). We will then go further and show that some of the algebraic properties that hold in integer and modular arithmetic can also apply to any data structure, and we will study how to recognize and take advantage of these properties.

[link]  1.2. Informal motivating example: random number generation

Let us informally consider the problem of generating a sequence of random positive integers. Random number generators are needed in many situations and applications, including:
  • generating unique identifiers for database records, objects, etc.;
  • generating a one-time pad for a simple encryption scheme;
  • generating public and private keys for more sophisticated encryption and signature schemes;
  • simulation and approximation methods that employ random sampling (Monte-Carlo, and so on).
Different applications will impose different requirements on what is and is not a sufficiently "random" sequence of number. Suppose we adopt the following method:
  • n0 = a number in the range (inclusive) 0 to 5;
  • ni = (2 ⋅ ni-1 + 1) mod 6.
We can consider another method:
  • n0 = an initial seed integer 104 > n ≥ 103;
  • ni = only the last four digits of ni-12.
Frequent repetition of a sequence may or may not be allowed in our given application. Does the above method produce repeating numbers? How often? For how many initial seeds? How do we choose a good seed? We can measure a physical process or component (a clock, a keyboard), but even under these circumstances we need a way to reason about the range of random values the measurement produces, and the range of random values the application requires. How do we begin to approach and formally characterize these aspects of the problem so that we are certain we are meeting the requirements imposed by the application?

One way to model a random number generation process is to view it is a permutation. In fact, there is more than one way to view the process as a permutation. We could simply count up from 0 to m and apply the same permutation to each 0 ≤ nm in order to produce the nth random number in the sequence. Is there an efficient way (i.e., using no more memory than O(log m)) to compute a random number from each n such that a number never repeats?

In this course we will learn about a variety of mathematical structures and their properties that will allow us to precisely specify the above problem and others like it, to identify what solutions are appropriate for such a problem, and to implement these solutions correctly and, where necessary, efficiently.

[link]  2. Review of Logic with Sets, Relations, and Operators

In this section, we will review several abstract structures and associated properties (and the symbolic language used to represent them) that you should have already encountered in past courses. Simultaneously, we will review one way in which these structures can be implemented and manipulated within the modern programming language Python. As with most human languages that have developed organically over time, mathematics has a rich and often redundant vocabulary. We introduce many terms in this section that we will use consistently in this course. However, keep in mind that there are often other synonyms within mathematics and computer science for these structures.

[link]  2.1. Formulas without quantifiers

Definition: A logical formula or formula is a string of symbols that follow a certain syntax. If the formula is written using a correct syntax, we can ask whether the formula is true or false. The symbols or, and, not, implies, and iff are logical operators.

formula meaning example of one possible
Python representation
true always true True
false always false False
f1 and f2 only true if both f1 and f2 are true True and False
f1 or f2 true if f1 or f2 (or both) are true True or (False and True)
f1 implies f2
  • if f1 is true, then f2 must be true
  • f1 is false, or f2 is true
  • f1 is "less than or equal to" f2
    (if false is 0 and true is 1)
False <= True
f1 iff f2
  • f1 is true if and only if f2 is true
  • f1 and f2 are either
    both true or both false
True == False
¬ f true if f is false not (True or (False and True))
( f ) true if f is true (True and (not (False))
predicate example depends on the definition
of the predicate
isPrime(7)

The following table may help with gaining a good intuition for the meaning of the implies operator.

meaning of
left-hand side
(premise)
meaning of
right-hand side
(conclusion)
meaning of
entire formula
comments
true true true if the premise is true and the conclusion
is true, the claim of implication is true;

thus, the whole formula is true
true false false if the premise is true but the conclusion is
false, the conclusion is not implied
by the premise, so the claim of implication
is false; thus, the formula is false
false true true if the conclusion is true on its own, it doesn't matter
that the premise is false, because anything implies
an independently true conclusion; thus, the claim
of implication is true, and so is the
entire formula
false false true if we assume that a false premise is true, then "false"
itself is "true"; in other words, false
implies itself, so the formula is true
Example: Suppose we have the following formula involving two predicates the sun is visible and it is daytime:
the sun is visible it is daytime
This formula might describe a property of our real-world experience of a person that is in a particular fixed location on the surface of the Earth. We could state that the above formula is always true (i.e., it is always an accurate description of the system it describes). For every possible assignment of values to each variable, the above formula is indeed accurate, in that it is true exactly in those situations that might occur on Earth, and false in any situation that cannot occur:
the sun is visible it is daytime meaning interpretation
true true true a sunny day
true false false
false true true a cloudy day
false false true nighttime
In particular, only one set of values causes the formula to be false: if the sun is in the sky, but it is not daytime. This is indeed impossible; all the others are possible (it may be day or night, or it may be cloudy during the day). The contrapositive of the formula is true if the formula is true:
¬(it is daytime) ¬(the sun is visible)
Notice that the contrapositive of the above is a direct result of the fact that if the sun is visible it is daytime must be true, the rows in the truth table in which it is false must be ignored, and then the only possible row in the truth table in which it is daytime is false is the one in which the sun is visible is also false.

[link]  2.2. Terms: integers and term operators that take integer inputs

Definition: A term is a string of symbols that represents some kind of mathematical structure. In our case, terms will initially represent integers or sets of integers. Terms may contain term operators. We can view these as functions that take terms as input and return terms as output. The term operators for terms that represent integers with which we will be working are +, -, ⋅, and mod.
term what it represents example of one possible
Python representation
0 0 0
1 1 1
z1 + z2 the integer sum of z1 and z2 3 + 4
z1z2 the integer difference of z1 and z2 (1 + 2) - 4
z1z2 the integer product of z1 and z2 3 * 5
z1 mod z2 the remainder of the integer quotient z1 / z2
z1 - ⌊ z1/z2 ⌋ ⋅ z2
17 % 5
z1z2 product of z2 instances of z1 2**3
pow(2,3)

[link]  2.3. Formulas: relational operators and predicates dealing with integers

Definition: A term can only appear in a formula if it is an argument to a predicate. A few common predicates involving integers are represented using relational operators (e.g, ≤, ≥).
formula what it represents example of one possible
Python representation
z1 = z2 true if z1 and z2
have the same meaning;
false otherwise
1 == 2
z1 < z2 true if z1 is less than z2;
false otherwise
4 < 3
z1 > z2 true if z1 is greater than z2;
false otherwise
4 > 3
z1z2 true if z1 is less than or equal to z2;
false otherwise
4 <= 3
z1z2 true if z1 is greater than or equal to z2;
false otherwise
4 >= 3
z1z2 true if z1 is not equal to z2;
false otherwise
4 != 3
Example: We can define our own predicates as well. Notice that one way we can represent these in Python is by defining a function that returns a boolean result.
predicate definition example of one possible
Python representation
P(x)     iff     x > 0 and x < 2 def P(x): return x > 0 and x < 2
Q(x)     iff     x > 3 Q = lambda x: x > 3
formula what it represents example of one possible
Python representation
P(1) true P(1)
P(1) or P(2) true P(1) or P(2)
Q(1) and P(1) false Q(1) and Q(1)
We will use the following predicates throughout the course.
Definition: For any x,y ℤ, x | y    iff   y/x ℤ. If x | y, we then say that x is a factor of y.
Definition: For any y ℤ, y is prime    iff    for any integer x where 2 ≤ x < y, it is not true that x | y. In other words, y is prime if its only factors are 1 and y (itself).
formula what it represents
x | y
  • y / x
  • x divides y
  • y is divisible by x
  • y is an integer multiple of x
  • y mod x = 0
  • x is a factor of y
y is prime
  • y > 1 and
    x | y implies x = 1 or x = y
  • y > 1 and
    y is divisible only by 1 and itself
Example: We can define the divisibility and primality predicates in Python in the following way:

def divides(x,y):
    return y % x == 0   # The remainder of y/x is 0.

def prime(y):
    for x in range(2,y):
        if divides(x,y):
            return False
    return True
        
Definition: For any x,y ℤ, x is a proper factor of y    iff    y/x ℤ and x < y.

[link]  2.4. Terms: finite sets of integers, term operators that take set inputs, and set comprehensions

Definition: A finite set of integers is an unordered, finite collection of zero or more integers with no duplicates. The following are examples of terms the meaning of which is a finite set of integers (with the exception of the set size terms, the meaning of which is a positive integer).
term what it represents example of one possible
Python representation
a set with no elements in it set()
{1,2,3} {1,2,3} {1,2,3}
{2,..,5} {2,3,4,5} set(range(2,6))
{ x | x {1,2,3,4,5,6}, x > 3 } {4,5,6} {x for x in {1,2,3,4,5,6} if x > 3}
|{1,2,3,4}| 4 len({1,2,3,4})
The following are term operators on terms the meaning of which is a finite set of integers.
term what it represents example of one possible
Python representation
S1S2 {z | z ℤ, z S1 or z S2} {1,2,3}.union({4,5})
{1,2,3} | {4,5}
S1S2 {z | z ℤ, z S1 and z S2} {1,2,3}.intersection({2,3,5})
{1,2,3} & {2,3,5}
|S| the number of elements in S len({1,2,3})
While the terms below do not represent finite sets of integers, we introduce the following two set terms in order to reference them throughout the notes.
Definition: Let ℤ be the set of all integers.
Definition: Let ℕ be the set of all positive integers, including 0.
term what it represents
{0, 1, 2, ...}
{..., -2, -1, 0, 1, 2, ...}

[link]  2.5. Formulas: quantifiers over finite sets of integers

Definition: Suppose we define the following two Python functions that take predicates (or, more specifically, functions that represent predicates) as input.

def forall(S, P):
    for x in S:
        if not P(x):
            return False
    return True

def exists(S, P):
    for x in S:
        if P(x):
            return True
    return False
        
We could redefine the above using comprehensions. We will also introduce a subset() operation on sets.

def forall(X, P):
  S = {x for x in X if P(x)}
  return len(S) == len(X)

def exists(X, P):
  S = {x for x in X if P(x)}
  return len(S) > 0

def subset(X,Y):
  return forall(X, lambda x: x in Y)
        
Then we can introduce the following definitions and corresponding Python examples.
formula what it represents example of one possible
Python representation
1 {1,2,3} true 1 in {1,2,3}
4 {1,2,3} false 4 in {1,2,3}
x {1,2,3}, x > 0 and x < 4 true forall({1,2,3}, lambda x: x > 0 and x < 4)
x {1,2,3}, x < 1 and x > 3 false exists({1,2,3}, lambda x: x < 1 or x > 3)
x ∅, f true
x ∅, f false
Notice that when we quantify over an empty set with a universal quantifier ∀, the formula is always true. When we quantify over an empty set with an existential quantifier, the formula is always false (since no element satisfying any formula could exist if no elements exist at all). We can see that the Python functions for these quantifiers are consistent with this interpretation.
Fact: Let X = {x1 , ..., xn} be a finite set and let P be a predicate that applies to a single integer argument. Then we have the following correspondences between quantifiers and logical operators:
x X, P(x)     
iff
     P(x1) and P(x2) and P(x3) and ... and P(xn)
x X, P(x)     
iff
     P(x1) or P(x2) or P(x3) or ... or P(xn)
Notice that if X is empty, the "base case" for ∀ must be true (since that is the identity of the and logical operator), while the "base case" for ∃ must be false (since that is the identity of the or logical operator).
Exercise: Implement Python functions that correspond to formulas which can be used to define each of the following statements about a set X and a predicate P.
  • All the elements of a set X satisfy the predicate P.

    # We provide two equivalent implementations.
                
    def all(X, P):
        return  forall(X, P)
        
    def all(X, P):
      S = {x for x in X if P(x)}
      return len(S) == len(X)
                
  • None of the elements of a set X satisfy the predicate P.

    # We provide two equivalent implementations.

    def none(X, P):
        return  forall(X, lambda x: not P(x))
        
    def none(X, P):
      S = {x for x in X if P(x)}
      return len(S) == 0
                
  • At most one of the elements of a set X satisfy the predicate P.

    def atMostOne(X, P):
      S = {x for x in X if P(x)}
      return len(S) <= 1
                
  • At least one of the elements of a set X satisfy the predicate P.

    # We provide two equivalent implementations.

    def atLeastOne(X, P):
      return exists(X, P)

    def atLeastOne(X, P):
      S = {x for x in X if P(x)}
      return len(S) >= 1
                
Exercise: Use quantifiers to implement a Python function corresponding to the predicate p is prime for any integer p.

def prime(p):
    return  p > 1 and forall(set(range(2, p)), lambda n: p % n != 0)
        

[link]  2.6. Formulas: predicates dealing with finite sets of integers

Definition: The following are examples of formulas that contain relational operators dealing with finite sets of integers.
formula what it represents example of one possible
Python representation
3 {1,2,3} true 3 in {1,2,3}
{1,2} ⊂ {1,2,3} true subset({1,2}, {1,2,3})
{4,5} ⊂ {1,2,3} false subset({4,5}, {1,2,3})
Below are the general forms of formulas containing relational operators dealing with finite sets of integers.
formula what it represents
z S true if z is an element of S; false otherwise
S1S2 z S1, z S2
S1 = S2 S1S2 and S2S1

[link]  2.7. Terms: set products and binary relations

Definition: The product of two sets X and Y is denoted X × Y and is defined to be the set of ordered pairs (x,y) for every possible combination of x X and y Y.
Example:
term what it represents example of one possible
Python representation
{1,2} × {5,6,7} {(1,5),(1,6),(1,7),(2,5),(2,6),(2,7)} { (x,y) for x in {1,2} for y in {4,5,6,7} }
Definition: A set R is a relation between the sets X and Y if RX × Y.

[link]  2.8. Formulas: predicates dealing with relations

There are several common properties that relations may possess.

predicate definition graphical example
X × Y is the set product of X and Y X × Y = { (x,y) | x X, y Y }
R is a relation between X and Y RX × Y
R is a function from X to Y
R is a (many-to-one) map from X to Y
R is a relation between X and Y and
x X,
    there is at most one
    y Y s.t. (x,y) R
R is an injection from X to Y R is a relation between X and Y and
y Y,
    there is at most one
    x X s.t. (x,y) R
R is a surjection from X to Y R is a relation between X and Y and
y Y,
    there is at least one
    x X s.t. (x,y) R
R is a bijection between X and Y R is an injection from X and Y and
R is a surjection from X and Y
R is a permutation on X RX × X and
R is a bijection between X and X
R is a reflexive relation on X RX × X and
x X, (x,x) R
R is a symmetric relation on X RX × X and
x X, ∀ y X, (x,y) R implies (y,x) R
R is a transitive relation on X RX × X and
x X, ∀ y X, ∀ z X,
   ((x,y) R and (y,z) R) implies (x,z) R
R is an equivalence relation on X
R is a congruence relation on X
RX × X and
R is a reflexive relation on X and
R is a symmetric relation on X and
R is a transitive relation on X


Exercise: Define the set of all even numbers between 0 and 100 (inclusive). There are at least two ways we can do this:

evens = { 2 * x for x in set(range(0,51)) }
evens = { x for x in set(range(0,101)) if x % 2 == 0 }
        
Exercise: Implement a Python function that computes the set product of two sets X and Y.

def product(X, Y):
  return { (x,y) for x in X for y in Y }
        
Exercise: Implement a Python function that takes a finite set of integers and builds the relation on that set correspondingto the operator relational operator ≤.

def leq(S):
  return { (x, y) for x in S for y in S if x <= y }
        
Exercise: Implement a Python function that determines whether a relation R is a relation over a set X.

# Using our definition of subset().
def relation(R, X):
  return subset(R, product(X, X))

# Using the built-in set implementation.
def relation(R, X):
  return R.issubset(product(X, X))
        
Exercise: One property of relations that is studied in other subject areas within computer science and mathematics is asymmetry. We say that R is an asymmetric relation on a set X if:
x X, ∀ y X, (x,y) R implies ¬((y,x) R)
One example of an asymmetric relation is the "less than" relation on integers, usually represented using the < relational operator. How can we write a Python function that takes as its input a relation R and a set X and determines whether that relation is asymmetric? Recall that we can represent the implication logical operator using the Python operator <=.

def asymmetric(X, R):
  return relation(R,X) and forall(X, lambda a: forall(X, lambda b: ((a,b) in R) <= (not ((b,a) in R))))
        

[link]  2.9. Terms: set quotients and quotient maps

Given an equivalence relation on a set, we can partition that set into a collection of distinct subsets, called equivalence classes, such that all the elements of each subset are equivalent to one another.
Definition: For any set X and equivalence relation R on X, let the quotient set of X with respect to R, denoted X/R, be defined as:
X/R
=
{{y | y X, (x,y) R} | x X}
Exercise: Implement a Python function that takes two inputs (a set X and an equivalence relation R on that set), and outputs the quotient set X/R.

def quotient(X,R):
    return {frozenset({y for y in X if (x,y) in R}) for x in X}
        
Below, we evaluate the above function on an example input.

>>> quotient({1,2,3,4}, {(1,1),(2,2),(3,3),(2,3),(3,2),(4,4)})

{frozenset({4}), frozenset({2, 3}), frozenset({1})}
        
Definition: For a set X and a relation R over X, the relation that relates each x X to its equivalence class in X under R is called the quotient map. The function is typically denoted using [ ... ]. That is, [x] is the equivalence class of x under R.
Exercise: Implement a Python function that takes two inputs (a set X and an equivalence relation R on that set), and outputs the quotient map taking each element x X to its corresponding equivalence class [x] X/R.

def quotientMap(X,R):
  return {(x, frozenset({y for y in X if (x,y) in R})) for x in X}
        
Exercise: Determine whether {(x,y) | x ℤ, y ℤ, (x + y) mod 2 = 0} is an equivalence relation.
It is reflexive because any integer plus itself must be even (the sum of two odd numbers is even, and the sum of two even numbers is even). Because of commutativity of addition, the relation is symmetric. Finally, it is transitive because if x + y is even and y + z is even, then x and z must either both be even, or they must both be odd, so x + z must be even, as well. Thus, the relation is reflexive, symmetric, and transitive, so it is an equivalence relation.
Example: Let X be a set of humans. Let R be the following relation RX × X:
R
=
{ (x, y) | x X, y X, x is y or x is a relative of y }
Then R is an equivalence relation (we assume everyone is related to themselves, and that if two people are both related to the same person, then they are themselves related). Furthermore, the quotient set X/R is a separation of the humans in X into families of relatives. No one in any equivalence class (a.k.a., a family) in X/R is related to anyone in any other equivalence class, and everyone in each equivalence class in X/R is related to everyone else in that equivalence class. Thus, |X/R| is the number of distinct families of humans in the set X. More generally, we can view the quotient set X/R as the separation of X into the largest number of groups such that no two relatives are separated into separate groups.
We can illustrate this with a Python example. Suppose we have the following relation on the set {'Alice', 'Bob', 'Carl', 'Dan', and 'Eve'}:

R = {\
     ('Alice''Alice'), ('Bob''Bob'), ('Carl''Carl'), ('Dan''Dan'), ('Eve''Eve'),\
     ('Alice''Carl'), ('Carl''Alice'), ('Dan''Eve'), ('Eve''Dan')\
    }
        
We can then compute the set of families:

families = quotient({'Alice''Bob''Carl''Dan''Eve'}, R)
        


[link]  2.10. Assignment #1: Logic, Integers, Sets, and Relations

In this assignment you will define Python functions that represent various constructs. You must submit a single Python source file named hw1/hw1.py. Please follow the gsubmit directions.

Your file may not import any modules or employ any external library functions associated with integers and sets (unless the problem statement explicitly permits this). Solutions to each of the programming problem parts below should fit on 1-3 lines. You will be graded on the correctness, concision, and mathematical legibility of your code. The different problems and problem parts rely on the lecture notes and on each other; carefully consider whether you can use functions from the lecture notes, or functions you define in one part within subsequent parts.

You should include the following definition in your module:

def quotient(X, R):
    return {frozenset({y for y in X if (x,y) in R}) for x in X}
        
You may also include in your module any function that is defined in the lecture notes.
  1. Solve the following problems involving relations. Recall that using Python, we are representing relations as sets of tuples (i.e., pairs such as (1,2)).
    1. Implement a function isSymmetric() that takes as its input two arguments: a set X and a relation R. The function should return True if the relation R is a symmetric relation on X, and it should return False otherwise.

      isSymmetric({1,2}, {(1,1), (2,2), (2,1), (1,2)}) == True
      isSymmetric({1,2,3}, {(1,2), (2,1), (3,3)}) == True
      isSymmetric({'a','b','c'}, {('a','a'), ('b','b'), ('a','c')}) == False
                    
    2. Implement a function isEquivalence() that takes as its input two arguments: a set X and a relation R. The function should return True if the relation R is an equivalence relation on X, and it should return False otherwise. Hint: read the notes in this section carefully and reuse functions that have already been defined.

      isEquivalence({1,2,3}, {(1,1), (2,2), (3,3)}) == True
      isEquivalence({1,2,3}, {(1,2), (2,1), (3,3)}) == False
      isEquivalence({1,2}, {(1,1), (2,2), (1,2), (2,1)}) == True
      isEquivalence({0,3,6}, {(0,3), (3,6), (0,6), (3,0), (6,3), (6,0)}) == False
                    
  2. Solve the following problems by defining appropriate relations.
    1. Include the following definitions in your code:

      X1 = {"a","b","c","d"}
      R1 = ?
                    
      Define the variable R1 above so that R1 is an equivalence relation over X1, and so that the following will evaluate to True:

      >>> isEquivalence(X1,R1)
      True
      >>> quotient(X1,R1) == {frozenset({"a""b"}), frozenset({"c""d"})}
      True
                    
    2. Include the following definitions in your code:

      X2 = {0,1,2,3,4,5}
      R2 = ?
                    
      Define the variable R2 above so that R2 is an equivalence relation over X2, and so that the following will evaluate to True:

      >>> isEquivalence(X2,R2)
      True
      >>> quotient(X2,R2) == {frozenset({0,3}), frozenset({1,4}), frozenset({2,5})}
      True
                    
    3. Include the following definitions in your code:

      X3 = set(range(-1000,1001))
      R3 = ?
                    
      Define the variable R3 above so that R3 is an equivalence relation over X3, and so that the following will evaluate to True (you will want to use a set comprehension):

      >>> isEquivalence(X3,R3)
      True
      >>> quotient(X3,R3) == {frozenset(range(-1000,0)), frozenset({0}), frozenset(range(1,1001))}
      True
                    
    1. Implement a Python function square(n) that takes a single positive integer argument n and returns True if and only if n is a square (i.e., there exists an integer k such that k2 = n). You must use exists() to receive credit. Efficiency is not important.

      >>> square(9)
      True
      >>> square(12)
      False
                    
    2. Implement a Python function properPrimeFactors(n) that takes a single positive integer argument n and returns a finite set of integers containing all the proper prime factors k of that number.

      >>> properPrimeFactors(9)
      {3}
      >>> properPrimeFactors(14)
      {2,7}
                    
    3. Implement a Python function disjoint(S) that takes any finite set of positive integers S and returns a relation over S in which any two numbers in S that do not share any proper prime factors are related. If a number has no proper prime factors, then it shares no proper prime factors with any other number.

      >>> disjoint({2,3,4,5,6})
      {(2,2), (2,3), (2,4), (2,5), (2,6), (3,2), (3,3),
       (3,4), (3,5), (3,6), (4,2), (4,3), (4,5), (5,2), 
       (5,3), (5,4), (5,5), (5,6), (6,2), (6,3), (6,5)}

      >>> disjoint({2,4,8})
      {(2,2), (2,4), (4,2), (2,8), (8,2)}
                    
    4. Determine which of the three properties of an equivalence relation (reflexivity, symmetry, and transitivity) always apply to any result of disjoint() (assuming its input is a finite set of positive integers). Define three veriables in your code:

      reflexive = ?
      symmetric = ?
      transitive = ?
                    
      If a property always applies to any output, simply assign None to the corresponding variable. If a property does not apply to all outputs of disjoint(), assign an input set to the corresponding variable that returns an output relation that does not satisfy that property. For example, if it is not symmetric, then the following behavior is required (note that it may or may not be symmetric, this is just an example):

      >>> isSymmetric(symmetric, disjoint(symmetric))
      False
                    



[link]  3. Modular Arithmetic

Modular arithmetic can be viewed as a variant of integer arithmetic in which we introduce a congruence (or equivalance) relation on the integers and redefine the integer term operators so that they are defined on these congruence (or equivalance) classes.

[link]  3.1. Terms: congruence classes in ℤ/mℤ, term operators, and relations

Definition: For any m ℤ, define:
m
=
{xm | x ℤ}
Definition: For any m ℤ, define:
k + m
=
{k + (xm) | x ℤ}
Exercise: Show that the relation R = {(x,y) | x ℤ, y ℤ, x mod 17 = y mod 17} is an equivalence relation.
Definition: For any given m ℤ, define:
ℤ/m
=
ℤ/{(x,y) | x ℤ, y ℤ, x mod m = y mod m}
Example: How do we determine whether 7 2 + 5ℤ is a true formula? We can expand the notation 2 + 5ℤ into its definition:
7
2 + 5ℤ
7
{ 2 + 5 ⋅ z | z ℤ }
Thus, if 7 is in the set of elements of the form 2 + 5 ⋅ z, then we must be able to solve the following equation on integers for z:
7
=
2 + 5 ⋅ z
5
=
5 ⋅ z
1
=
z
Since we can solve for z, it is true that 7 2 + 5ℤ.
Informally and intuitively, we could think of the structure of the above set as a logical consequence of letting all multiples of m be equivalent to 0. That is, if 0 = m = 2m = ..., then 1 = m + 1 = 2m + 1 = ..., and so on.
term what it represents
z
z mod m
z + m
{z + (am) | a ℤ}
c1 + c2 {(x + y) | x c1, y c2}
c1c2 {(xy) | x c1, y c2}
c1c2 {(xy) | x c1, y c2}
cz c ⋅ ... ⋅ c
c! c ⋅ (c-1) ⋅ (c-2) ⋅ ... ⋅ 1
formula what it represents
c1c2 true only if c1c2 and c2c1, i.e., set equality
applied to the congruence classes c1 and c2;
false otherwise

[link]  3.2. Algebra of congruence classes

We use the familiar symbols +, -, ⋅, and 0, 1, 2, 3, 4, ... to represent operations on congruence classes. When these symbols are used to represent operations on integers, they have certain algebraic properties. This allows us, for example, to solve equations involving integers and variables, such as in the example below (in which we add the same integer to both sides, use associativity of + and commutativity of ⋅, and cancel 2 on both sides of the equation):
2 ⋅ x − 3
=
1
(2 ⋅ x − 3) + 3
=
1 + 3
2 ⋅ x
=
4
2 ⋅ x
=
2 ⋅ 2
x ⋅ 2
=
2 ⋅ 2
x
=
2
Do the operations on congruence classes, represented by the operators +, -, and ⋅, also share the familiar algebraic properties of the corresponding operations on integers? In many cases they do, but in some cases these properties only apply under specific circumstances.
Example: Suppose we write the term 3 + 4 ≡ 2 where 2, 3, and 4 are congruence classes in ℤ/5ℤ. What is the meaning of this term? First, note the following equivalence.
{ x + y | x ℤ, y ℤ} = {z | z ℤ }
Now, we expand the definitions of congruence classes and the operation + on congruence classes below.
3 + 4
(3 + 5ℤ) + (4 + 5ℤ)
=
{3 + a ⋅ 5 | a ℤ} + {4 + b ⋅ 5 | b ℤ}
=
{(x + y)   |   x {3 + a ⋅ 5 | a ℤ}, y {4 + b ⋅ 5 | b ℤ}}
=
{(3 + a ⋅ 5) + (4 + b ⋅ 5)   |   a ℤ, b ℤ}
=
{(3 + 4) + (a ⋅ 5) + (b ⋅ 5)   |   a ℤ, b ℤ}
=
{2 + 5 + (a ⋅ 5) + (b ⋅ 5)   |   a ℤ, b ℤ}
=
{2 + (1 + a + b) ⋅ 5   |   a ℤ, b ℤ}
=
{2 + c ⋅ 5 | c ℤ}
2 + 5ℤ
2
Fact: The set ℤ/mℤ is closed under the operation represented by +.
Fact: It is the case that ℤ/mℤ = {0,...,m-1} where 0,...,m-1 are congruence classes, and thus, |ℤ/mℤ| = m.
Fact: The addition operation on congruence classes in ℤ/mℤ represented by + is commutative, associative, and has the additive identity 0 + mℤ (a.k.a., mℤ, or simply 0).
Fact: The multiplication operation on congruence classes in ℤ/mℤ represented by ⋅ is commutative, associative, and has the multiplicative identity 1 + mℤ (a.k.a., 1).
property definition
ℤ/mℤ is closed under + x,y ℤ/mℤ,
    x + y ℤ/m
+ is commutative on ℤ/m x,y ℤ/mℤ,
    x + yy + x
+ is associative on ℤ/m x,y,z ℤ/mℤ,
    (x + y) + zx + (y + z)
+ has a (left and right) identity 0 in ℤ/m x ℤ/mℤ,
    0 + xx and x + 0x
ℤ/mℤ has inverses with respect to + x ℤ/mℤ,
    (m - x) + x ≡ 0
ℤ/mℤ is closed under ⋅ x,y ℤ/mℤ,
    xy ℤ/m
⋅ is commutative on ℤ/m x,y ℤ/mℤ,
    xyyx
+ is associative on ℤ/m x,y,z ℤ/mℤ,
    (xy) ⋅ zx ⋅ (yz)
+ has a (left and right) identity 1 in ℤ/m x ℤ/mℤ,
    1 ⋅ xx and x ⋅ 1 ≡ x
⋅ distributes across + in ℤ/m x,y,z ℤ/mℤ,
    x ⋅ (y + z) ≡ (xy) + (xz)
In the rest of this subsection, we derive some familiar algebraic properties for congruence classes. We derive some of these properties from the properties of the divisibility predicate (i.e., for any x, y ℤ, x | y iff y/x ℤ). These properties will allow us to use algebra to solve equations involving congruence classes in ℤ/mℤ.
It is worth considering why we choose to work with the set of congruence classes ℤ/mℤ = {0 + mℤ, 1 + mℤ, 2 + mℤ, ..., (m-1) + mℤ} and operations over it rather than simply working with equations involving integer variables and the modulus operator. Modular arithmetic textbooks can be written (and such textbooks exist) in which the techniques covered in these notes are used to solve integer equations of the form f(x) mod m = g(x) mod m for some functions f and g. Some of the reasons for using the set of congruence classes ℤ/mℤ include:
  • it is often possible to find the unique solution to an equation over ℤ/mℤ, while equations over ℤ involving the modulus operation may have infinitely many solutions;
  • the set ℤ/mℤ is finite, so there is always a finite number of possible solutions to test, even if this is very inefficient, while equations over the integers involving modulus have an infinite range of possible solutions to test;
  • the set ℤ/mℤ is a group and is a prototypical example of an algebraic structure, and gaining experience with algebraic structures is one of the purposes of this course, as algebraic structures are ubiquitous in computer science and its areas of application.
Fact: Given an equation involving congruence classes, we are allowed to add the same value to both sides. In other words, for any congruence classes a, b, c ℤ/mℤ, ab implies a + cb + c:
a
b (mod m)
a + c
a + c
To see that this is true, we can simply appeal to algebraic facts about integers:
a
=
b
a + c
=
b + c
(a + c) mod m
=
(b + c) mod m
a + c
b + c (mod m)
Thus, the two congruence classes contain the same elements, so they are equivalent.
Fact: For any congruence classes a, b, c ℤ/mℤ, ab implies a - cb - c. We can adjust the argument for + in the following way:
a
=
b
a - c
=
b - c
(a - c) mod m
=
(b - c) mod m
a - c
b - c (mod m)
We saw that we can add and subtract from both sides of an equation involving congruence classes. Can we also divide both sides by the same factor (or "cancel" that factor) in such an equation? We can do so under certain conditions. In order to show that we can do so, we need a few preliminary facts.
Fact: For any a, m ℤ, a mod m = 0 iff we have that m | a. Thus, the following are all equivalent (i.e., all three are true at the same time):
m
|
a
a mod m
=
0
a
0 (mod m)
0
a (mod m)
We can derive the fact that m | a iff a mod m = 0 as follows. If a mod m = 0 then by definition of mod we have:
a - ⌊ a/m ⌋ ⋅ m
=
0
a
=
a/m ⌋ ⋅ m
a / m
=
a/m
a / m
m
|
a
If m | a then by definition of m | a we have:
m
|
a
a / m
a / m
=
a/m
a
=
a/m ⌋ ⋅ m
a - ⌊ a/m ⌋ ⋅ m
=
0
a mod m
=
0
Fact: For any a, b, c ℕ, if c|a then c|(ab).

Because c|a, it must be that a/c ℤ. But then we have that:
(ab) / c = (a / c) ⋅ b
Since (a / c) ℤ and b ℤ, (a / c) ⋅ b ℤ and (ab) / c ℤ. Thus, c|(ab).
Fact: In ℤ/mℤ, Multiplying by the 0 + mℤ congruence class yields the 0 + mℤ congruence class. For any a, b, m ℕ, if a ≡ 0 (mod m) then ab ≡ 0 (mod m).

We can show this as follows:
a
0 (mod m)
m
|
a
m
|
(ab)
ab
0 (mod m)
Thus, 0 ℤ/mℤ behaves with respect to multiplication over ℤ/mℤ much the same way that 0 ℤ behaves with respect to multiplication over ℤ.
Example: For any a, b, m ℕ, it is not necessarily the case that just because ab ≡ 0 (mod m), either a or b must be the congruence class 0 ℤ/mℤ. For example, let m = 6, a = 4, and b = 9. Then we have:
4 ⋅ 9
0 (mod 6)
(2 ⋅ 2) ⋅ (3 ⋅ 3)
0 (mod 6)
2 ⋅ (2 ⋅ 3) ⋅ 3
0 (mod 6)
2 ⋅ 6 ⋅ 3
0 (mod 6)
However, we have that:
6
4
4
0 (mod 6)
6
9
9
0 (mod 6)
Thus, the congruence class 0 ℤ/mℤ does not always behave the same way that 0 ℤ behaves.
Fact (Euclid's lemma): For any a, b, p ℤ, if p is prime and p | (ab), then it must be that p|a or p|b (or both).
Fact: For any congruence classes a, b, c ℤ/pℤ, if c is not divisible by p then acbc implies ab.

We can derive the above fact by using the following steps:
ac
bc
(ac) - (bc)
0
((ac) - (bc)) mod p
=
0
((a - b) ⋅ c) mod p
=
0
p
|
((a - b) ⋅ c)
By Euclid's lemma, the fact that c is not divisible by p requires that a - b must be divisible by p. Thus:
p
|
(a - b)
(a - b) mod p
=
0
a - b
0
a
b
Example: Solve the following equation for all possible congruence classes x ℤ/3ℤ:
6 ⋅ x
0 (mod 3)
Since 6 ≡ 0 mod 3, we can rewrite the equation as follows:
0 ⋅ x
0 (mod 3)
Thus, any congruence class x {0 + 3ℤ, 1 + 3ℤ, 2 + 3ℤ} is a solution to the equation.
Example: Solve the following equation for all possible congruence classes x ℤ/5ℤ:
2 ⋅ x
0 (mod 5)
We know that 2 ⋅ 0 ≡ 0 (mod 5), so we can rewrite the above by substituting the right-hand side of the equation:
2 ⋅ x
2 ⋅ 0 (mod 5)
We can now cancel 2 on both sides of the equation using the cancellation law because 5 is prime and 2 ≢ 0 (mod 5):
x
0 (mod 5)
Thus, the only solution is the single congruence class x = 0 + 5ℤ.
Example: Solve the following equation for all possible congruence classes x ℤ/11ℤ:
3 ⋅ x + 5
6 (mod 11)
We can begin by subtracting 5 from both sides:
3 ⋅ x
1 (mod 11)
We can then see that 12 ≡ 1 (mod 11), so we can substitute 1 with 12 on the right-hand side:
3 ⋅ x
12 (mod 11)
We can then rewrite 12 as 3 ⋅ 4:
3 ⋅ x
3 ⋅ 4 (mod 11)
Since 11 is prime and 3 ≢ 0 (mod 11), we can cancel the 3 on both sides to solve the problem:
x
4 (mod 11)
Thus, the only solution is the single congruence class x = 4 + 11ℤ.
Example: Let a ℤ be any integer. Solve the following equation for all possible congruence classes x ℤ/7ℤ:
a + 3 ⋅ x
6 - 6 ⋅ a (mod 7)
We can begin by adding 6 ⋅ a to both sides:
(a + 6 ⋅ a) + 3 ⋅ x
6 (mod 7)
Now we can add a + 6 a to obtain 7 ⋅ a:
7 ⋅ a + 3 ⋅ x
6 (mod 7)
We know that 7 ≡ 0 (mod 7), so we know that for any a ℤ, 7 ⋅ a ≡ 0 (mod 7). Thus, we can substitute the term 7 ⋅ a with 0:
0 + 3 ⋅ x
6 (mod 7)
3 ⋅ x
6 (mod 7)
3 ⋅ x
3 ⋅ 2 (mod 7)
Since 7 is prime and 3 ≢ 0 (mod 7), we can cancel 3 on both sides:
x
2 (mod 7)
Thus, the only solution is the single congruence class x = 2 + 7ℤ.
Exercise: Solve the following equation for all possible congruence classes x ℤ/13ℤ:
4 ⋅ x − 2
10 (mod 13)
We can add 2 to both sides to obtain:
4 ⋅ x
12 (mod 13)
At this point, since 13 is prime and 4 is not equivalent to the congruence class 0 + 13ℤ (i.e., it is not a multiple of 13), we know a solution must exist. Since 12 is a multiple of 4, we can cancel 4 on both sides:
4 ⋅ x
4 ⋅ 3 (mod 13)
x
3
Thus, we have a unique solution x = 3 + 13ℤ, which we can also denote using the symbol 3.
Exercise: Let a ℤ be any integer. Solve the following equation for all possible congruence classes x ℤ/19ℤ. Hint: notice that 17 + 19 = 36.
6 ⋅ x − 11
6 (mod 19)
We can add11 to both sides to obtain:
6 ⋅ x
17 (mod 19)
At this point, we know a solution must exist because 19 is prime and 6 is not a multiple of 19 (so it is not in 0 + 19ℤ and not equivalent to the congruence class 0 (mod 19)). However, it is not immediately obvious that we can cancel a multiple of 6 on the right-hand side. What we do know is that there must exist an integer in 17 + 19ℤ that will allow us to cancel. We can list the members of this set:
17 + 19ℤ
=
{..., 17, 36, 55, ...}
We note that 36 is a multiple of 6 and that 17 ≡ 36 (mod 19), so we can cancel:
6 ⋅ x
36 (mod 19)
6 ⋅ x
6 ⋅ 6
x
6
Thus, we have a unique solution x = 6 + 19ℤ, which we can also denote using the symbol 6.

[link]  3.3. Multiplication by a congruence class as a permutation

We have seen that in certain situations, it is possible to cancel on both sides of an equation involving congruence classes. While Euclid's lemma made this possible, we might be interested in finding other ways to understand why cancelling is possible in this particular situation. In fact, the alternative explanation is useful in its own right because it can be applied to the practical problem of generating random numbers.
Let us consider the situations in which we can cancel on both sides in an equation involving integers. Suppose a,b,c ℤ, and:
ac
=
bc
It is possible to cancel in the above equation exactly when the operation of multiplication by c is invertible. In particular, if c is 0, then ac = 0, and all information about a is lost (likewise for bc = 0). So, if c = 0, the operation of multiplication by c is not invertible (i.e., multiple inputs map to the same output, namely 0, so multiplication by c = 0 is not a bijection), and it is not possible to cancel c on both sides. In all other situations where c ≠ 0, the operation is invertible (we can simply perform integer division by c on ac and bc). This raises a natural question: does the ability to cancel congruence classes on both sides of a congruence class equation also imply that the operation of multiplying by the congruence class that can be cancelled on both sides is an invertible operation? The answer is "yes".
For a prime p, multiplication by a congruence class in ℤ/pℤ corresponds to an invertible relation, also known as a bijection or a permutation.
Fact: For any p ℕ, for any a {1,...,p-1}, if p is prime then the following relation R is a permutation from {0, 1,...,p-1} to ℤ/pℤ (the non-zero congruence classes in ℤ/pℤ):
R
=
{ (0, (0 ⋅ a) mod p), (1, (1 ⋅ a) mod p), (2, (2 ⋅ a) mod p), ..., (p-1, ((p-1) ⋅ a) mod p) }
=
{ (i, (ia) mod p) | i {0,...,p-1} }
Recall that R is a permutation if R is a bijection. In order to be a bijection, R must be both an injection and a surjection.
To show that R is an injection, suppose that it is not. We will derive a contradiction from this assumption, which will tell us that the assumption must be false.
If it is not injective, then there exist distinct i {0,...,p-1} and j {0,...,p-1} where without loss of generality j < i such that:
i
j
(ia) mod p
=
(ja) mod p
But the above implies the following:
(ia) mod p
=
(ja) mod p
((ia) - (ja)) mod p
=
0 mod p
((i - j) ⋅ a) mod p
=
0 mod p
p
|
(i - j) ⋅ a
By Euclid's lemma, the above implies that p must divide either (ij) or a. But also know that:
  • because a < p, p does not divide a;
  • because p > i - j > 0, p cannot divide (ij).
Alternatively, notice that in (ia) mod p = (ja) mod p , we should be able to simply divide both sides of the equation by a because p is prime; however, this contradicts our initial assumption!
Since assuming that distinct i and j can be mapped to the same element when they are multiplied by a leads to a contradiction, it must be that this is not possible. Thus, no two distinct i and j map to the same result, so R is an injection from {0,...,p-1} to ℤ/pℤ and we have that:
|{0,...,p-1}|
=
|ℤ/pℤ|
Thus, since R maps to at least p distinct elements, and |ℤ/pℤ| has at most p elements, R must map to every element in ℤ/pℤ, so it is also a surjection by the Pigeonhole principle.
Since R is both an injection and a surjection from {1,...,p-1} to ℤ/pℤ - {0}, it must be a bijection, and thus a permutation.
Example: Consider 2 ℤ/5ℤ. We can write out the results of multiplying all the congruence classes in ℤ/5ℤ by the congruence class 2:
2 ⋅ 0
0 (mod 5)
2 ⋅ 1
2 (mod 5)
2 ⋅ 2
4 (mod 5)
2 ⋅ 3
1 (mod 5)
2 ⋅ 4
3 (mod 5)
Notice that each congruence class in ℤ/5ℤ appears exactly once as a result.

[link]  3.4. Generating random numbers

Suppose we wish to automatically generate a sequence of "random" numbers using an algorithm. Before we can implement an algorithm and determine whether it solves our problem, we must first determine what constitutes an acceptable "random" sequence.
Example: Suppose we want to find a way to generate a "random" sequence v of positive integers. Assume we have only one requirement.
Requirement 1: The sequence v has m distinct positive integers between 0 and m-1, where vi is the ith element in the sequence.
In this case, a relation R ⊂ ℕ × ℤ/mℤ that is a permutation would be sufficient. One such relation is:
v0
=
0 mod m
vi
=
(vi-1 + 1) mod m
R0
=
{(i, vi) | i {0,...,m-1}}
Notice that the second term in (x, x mod m) is in this case the congruence class modulo m that corresponds to x. The relation R0 is indeed a permutation, but it does not satisfy our intuitive notion of a random sequence because it simply counts from 0 to m − 1, so we impose another requirement.
Requirement 2: The sequence v must not be the trivial sequence (0,...,m-1).
Suppose we propose the following relation:
v0
=
0
vi
=
(vi-1 + 2) mod m
R1
=
{(i, vi) | i {0,...,m-1}}
Notice that we can redefine R1 above more concisely:
R1
=
{(i, (0 + 2 ⋅ i) mod m) | i {0,...,m-1}}
Does R1 always satisfy both requirements? Suppose that m is even. Then we have that there exists j {0,...,m-1} such that 2 ⋅ j = m. But this means that 2 ⋅ j ≡ 0, so 2 ⋅ (j+1) ≡ 2 ⋅ j + 2 ⋅ 1 ≡ 2 ⋅ 1 ≡ 2 and so on. This means that R1 is not injective, so the first requirement is not met when m is even. Suppose we define R2 to be a variant of R1 parameterized by some b {0,...,m-1}:
R2
=
{(i, (0 + bi) mod m) | i {0,...,m-1}}
What conditions can we impose on b and m so that they satisfy both requirements?
After examining the permutation we can obtain by multiplying all the congruence classes in some set ℤ/pℤ by a particular a ℤ/pℤ, we might wonder if we can use this fact to implement a random number generator. One immediate benefit of this approach is that this approach would satisfy several conditions that we might associate with a "good" algorithm for generating random numbers:
  • the "state" of the algorithm is easy to store: it consists of a single congruence class in ℤ/pℤ, which can be represented using an integer;
  • the sequence that is generated will contain exactly one instance of all the numbers in the chosen range {0,...,p-1};
  • the sequence that is generated can, at least in some cases, be a non-trivial sequence that might appear "random".
Fact: If m is prime and b {2,...,m-1}, then R2 satisfies both requirements.

We know this is true because in this case, R is a permutation, so it satisfies Requirement 1. Furthermore, element v1 = b, so v is never the trivial sequence. Thus, Requirement 2 is satisfied.
Algorithm: The following is one possible implementation of a simple random number generation algorithm.
  1. inputs: upper bound (prime) p ℕ, seed a {0,...,p-1}, index i {0,...,p-1}
    1. return (ai) mod p
Exercise: What are some drawbacks (or unresolved issues) with building random sequences by choosing a prime m and some a {2,...,m-1}?
The biggest limitation is that the range must be a prime number. This will present a number of difficulties:
  • if a non-prime upper bound is needed, it would be necessary to scale down the results from the smallest prime number that is greater than the desired upper bound, and this would eliminate the bijective property of the generator (multiple indices might map to the same random number);
  • where do we even find a prime p efficiently that is close to the desired upper bound?
Regarding the second point above: we will see that an efficient, commonly used algorithm for finding prime numbers in a given range actually uses a random number generator as a subroutine. This circular dependency would certainly present a problem, so it would be ideal if we could generate a random number without relying on the ability to generate prime numbers within a certain range.

[link]  3.5. Greatest common divisor and related facts

It is actually possible to generalize Euclid's lemma so that it does not rely on prime numbers existing at all. In order to do so, however, we must first introduce concepts that make it possible to reason about a particular relationship between numbers that is similar to the property of primality, but is less restrictive.
Definition: For any two x, y ℤ, we define the greatest common divisor, denoted gcd(x,y), as the greatest integer z ℤ such that z | x and z | y. Equivalently, we can define it as the maximum of a set:
gcd(x,y)
=
max{z | z ℤ, z | x, z | y}
We can also define it recursively (not that z | 0 for all z ℤ because 0/z ℤ):
gcd(x,0)
=
x
gcd(x,y)
=
gcd(y, x mod y)
To see why the recursive definition of gcd works, consider two cases. If x < y, then the two inputs are simply reversed. This ensures that the first input x is eventually larger than the second input y. If xy and they share a greatest common divisor a, then we have for n = ⌊ x/y ⌋ that:
y
=
y' ⋅ a
x
=
x' ⋅ a
x mod y
=
x - (ny)
=
(x' ⋅ a) - (ny)
=
x' ⋅ a - ((ny') ⋅ a)
=
(x' - ny') ⋅ a
Notice that (x' - ny') ⋅ a < x' ⋅ a, but that the new smaller value is still a multiple of a, so the greatest common divisor of this value and y is still a.
Example: Consider the number 8 and 9. The factors of 8 are 1, 2, 4, and 8, while the factors of 9 are 1, 3, and 9. Thus, the maximum of the numbers in the intersection {1,2,4,8} ∩ {1,3,9} is 1, so we have that gcd(8, 9) = 1.
Example: We can implement the inefficient algorithm for the greatest common divisor using Python in the following way:

def gcd(x, y):
  return max({z for z in range(0, min(x,y)) if x % z == 0 and y % z == 0})
         
Exercise: Consider the following relation:
{ (x, y) | gcd(x,y) ≠ 1 }
Is this an equivalence relation?
The relation is reflexive because gcd(x,x) = x, and it is symmetric because gcd(x,y) = gcd(y,x). However it is not transitive. For example, gcd(4,6) = 2 and gcd(6,9) = 3, but gcd(4,9) = 1. Note also that we can define such a relation for some range using Python:

{(x,y) for x in S for y in S if gcd(x,y) != 1}
           
Fact: For any x ℤ, y ℤ, x | y iff gcd(x,y) = x.
Definition: For any x ℤ, y ℤ, x and y are relatively prime, relative primes, and coprime iff gcd(x,y) = 1.
Fact (Euclid's lemma generalization): For any a, b, c ℕ, if a | (bc) and a and b are relatively prime, then it must be that a | c.
Fact: If m, a ℕ and x, y ℤ/mℤ where gcd(a, m) = 1 (i.e., a and m are coprime), and suppose we have that:
x
y (mod m)
Then it must be that:
ax
ay (mod m)
Notice that we can prove the above by contradiction. If we instead suppose that axay (mod m), then because a and m are coprime, by Euclid's generalized theorem, we can canel a on both sides of the equation to obtain xy (mod m).
Fact: For any a ℕ and m ℕ, if gcd(a,m) = 1, then {(i, (ia) mod m) | i {0,...,m-1}} is a permutation.

The above can be proven by noticing that if gcd(a,m) = 1, then a does not divide m and m does not divide a. Notice that in this fact in which p was required to be prime, the fact that p is prime was not used in isolation; only the coprime relationship between p and a was required.
Using the generalization of Euclid's lemma, it is now possible to address the drawback we observed in our initial random number generating algorithm. We can now accept any upper bound m, not just a prime upper bound, and there is no need for either the algorithm or the user to find a prime p before generating random numbers. However, we have a new problem: how to do we obtain a non-trivial coprime for any given m?
Fact: Suppose that we have some m ℕ, and that we choose some b {2,...,m-1} such that b > m/2. Then it is guaranteed that:
gcd(m, b)
<
b
To see why, consider that if gcd(m, b) = b, this would mean that there exists some k ≥ 2 such that bk = m, and this would mean that:
b ⋅ 2
m
b
m/2
This contradicts our assumption that b > m/2, so it must be that gcd(m, b) < b. We can then further conclude that:
b / gcd(m, b)
>
1
Thus, if gcd(m, (b / gcd(m, b))) = 1, this provides a way to find a number that is greater than 1 and coprime with m. However, this is not guaranteed to work every time because it may still be that gcd(m, (b / gcd(m, b))) > 1. Under those conditions, the options would be to try a different b, or to use a different technique.
Fact: Suppose that m ℕ is an odd positive integer. Then for any k ℕ, gcd(m, 2k) = 1. This is because if m had any factors of 2, it would be even.
Fact: For any m ℤ where m ≥ 2, gcd(m,m+1) = 1.

We can prove this fact by contradiction. Suppose there exists a factor z > 1 of m and m+1. In other words, gcd(m,m+1) > 1. Then we have that:
za
=
m
zb
=
m+1
(zb) - (za)
=
m+1-m
z ⋅ (b- a)
=
1
z ⋅ (b- a)
=
1/z
If z > 1 then 1/z ∉ ℤ, so (ba) ∉ ℤ. Since b-a ℤ, this is a contradiction, so it must be that gcd(m,m+1) = z = 1.
Fact: Suppose that m ℕ is of the form 2km' for some odd m' and some k ≥ 1. Then m' ⋅ 2 and m have exactly the same prime factors, which means (m' ⋅ 2) − 1 and m share no factors, so gcd(m, (m' ⋅ 2) − 1) = 1.
Algorithm: The following algorithm uses this fact to generate a new coprime. In the worst case, it runs in a linear amount of time in the length of the bit representation of m, and it may in some cases return m − 1 as a result. Note that the operations below (e.g., multiplication ⋅ and subtraction −) are on congruence classes in ℤ/mℤ and not on integers.
  1. inputs: positive integer m
    1. p := any number in {3,...,m-1}
    2. while p − 1 and m are not coprime
      1. p := p ⋅ gcd(p − 1, m)
    3. return p − 1
At this point, we can define an improved random number generation algorithm that works for any upper bound.
Algorithm: The following is another variant of a simple random number generation algorithm.
  1. inputs: upper bound m ℕ, index i {0,...,m-1}
    1. a := number in {2,...,m-1} s.t. a and m are coprime (always the same a for an m)
    2. return (ai) mod m
This algorithm has a more subtle flaw: poor choices of a (e.g., very small values such as 2) result in a very predictable "random" sequence. It is preferable to choose an a that is coprime with the upper bound m, and that falls somewhere between the middle and the upper quarter of the range {0,...,m-1} (i.e., between 0.5 ⋅ m and 0.75 ⋅ m). However, even this is not ideal, and common random number generators found in standard libraries (such as the linear congruential generator) use a slightly different fact about permutations that results in sequences that appear more "random". This fact and related concepts and results are presented in the latter portions of these notes.
Algorithm: In this variant, the algorithm attempts to find a coprime that is as close as possible to (4/7) ⋅ m. The value 4/7 is chosen in an ad hoc manner in this example. Other values in the range between 1/2 and 3/4 might also produce "nice"-looking results.
  1. inputs: upper bound m ℕ, index i {0,...,m-1}
    1. b := number in {2,...,m-1} s.t. b and m are coprime
    2. for possible powers k in the range 1 to the bit length of m
      1. a := a power bk of b that is as close as possible to ((4/7) ⋅ m)
    3. return (ai) mod m
We introduce another useful fact about greatest common divisors. Notice that the above fact suggests one possible way to build a prime number generator: for a product p of known prime numbers, it is guaranteed that p+1 shares no factors with p, so any prime factors it has must be new.
Exercise: Solve the following problems using the algebraic facts you know about the gcd operation.
  • Find gcd(18,42).
  • Find gcd(21000, 2100).
  • For a positive even integer a ℤ, find gcd(a/2, a - 1).
  • Suppose that for some a ℤ/mℤ, the set {ia mod m | i {1,...,m-1}} contains every number in the set {1,...,m-1}. What is gcd(a, m)?
Exercise: Solve the following equation for all possible congruence classes x ℤ/16ℤ:
9 ⋅ x + 2
4 (mod 16)
We can subtract 2 from both sides to obtain:
9 ⋅ x
2 (mod 16)
At this point, since 9 and 16 are coprime, we know a solution must exist. We can list all the members of 2 + 16ℤ:
2 + 16ℤ
=
{..., 2, 18, 34, ...}
We note that 18 is a multiple of 9, so we can cancel 9 on both sides:
9 ⋅ x
18 (mod 16)
9 ⋅ x
9 ⋅ 2
x
2
Thus, we have a unique solution x = 2 + 16ℤ, which we can also denote using the symbol 2.
Exercise: Solve the following equation for all possible congruence classes x ℤ/15ℤ:
30 ⋅ x
14 (mod 15)
We note that 30 = 15 ⋅ 2, so 15|30, which means:
30
0 (mod 15)
Thus, we can write the equation as:
0 ⋅ x
14 (mod 15)
0
14
There is no possible congruence class x ℤ/15ℤ that will make the above formula true; the formula 0 ≡ 14 is always false in ℤ/15ℤ. Thus, there are no solutions to the original equation.

[link]  3.6. Generating prime numbers

Many applications require the generation of new primes. We have already seen a simple example in which generating new random sequences required prime numbers. Another important class of applications with this requirement are cryptographic schemes and protocols. In this section, we consider the problem of generating prime numbers, and in particular, random prime numbers in a particular range.
Algorithm: There exists a simple algorithm that is guaranteed to generate a new prime number distinct from any of its inputs, but it is not efficient.
  1. inputs: set of primes {p1 ,... , pn}
    1. n := p1 ⋅ ... ⋅ pn + 1
    2. F := factors of n
    3. return any element in F
The above algorithm must return a new prime distinct from any of the primes p1 ,... , pn. To see why, consider the following:
P
=
p1 ⋅ ... ⋅ pn
gcd(P, P + 1)
=
1
There are two possibilities: P+1 is prime, or P+1 is not prime.
  • If P+1 is prime, then P > pi for all i {1,...,n}, so P+1 is a new prime.
  • If P+1 is not prime, it cannot share any factors with P since gcd(P, P + 1) = 1, so no factors of P+1 are in the set {p1 ,... , pn}. But it must have factors, so any of these factors will be different from the primes in the input set {p1 ,... , pn}.
Thus, the algorithm is a guaranteed method for generating new primes. It also constitutes a proof that there are infinitely many primes. Unfortunately, this algorithm is impractical because the new primes produced by it grow exponentially as the set of primes {p1 ,... , pn} is extended with new primes returned by the algorithm.
In practice, most algorithms that need to generate large primes for commercial applications simply choose a range of numbers and filter out non-primes using some efficient algorithm that does not provide an absolute guarantee that the numbers that remain are all prime. As long as it is not too likely that the generated number is not a prime, this may be sufficient.
Example: Suppose we want to generate a d-digit prime number (in decimal representation). The prime number theorem states that for a given N, the number of primes in the range {2,...,N} is about N/(ln(N)). We can roughly estimate the number of primes with d-digit decimal representations using the following formula:
(10d+1-1 / ln(10d+1-1)) - (10d / ln(10d))
For d = 8, this value is about 4,780,406, so we can roughly say that the chances that an 8-digit number chosen at random (here we are ignoring the details of what distribution is used) is prime are about:
4,780,406/((109 - 1) - 108) ≈ 5.5/100
Algorithm: Suppose we defined the following algorithm for generating a prime with a d-digit representation.
  1. inputs: d
    1. do
      1. n := any number from {10d, ..., 10d+1-1}
      while n is not prime
    2. return n
Assuming we were choosing numbers "well" with respect to their distribution (we are being imprecise here), we could optimistically hope that for d = 8, the above algorithm would only need to check for primality about 20 times (since roughly 1 out of every 20 numbers it tries should be a prime).
It remains to define an algorithm for checking whether an arbitrary input m ℕ is prime. We could check every number k between 2 and ⌊ √(m) ⌋ to see if it is a factor of m. However, ⌊ √(m) ⌋ still grows exponentially in the representation size of m. For example, for an n-bit input, an integer m in {0,...,2n-1} which must have a representation size of at least n bits, we have the following exponential running time:
√(m)
=
√(2n)
=
2n/2
=
(21/2)n
1.42n
If we only consider primes and not any of their multiples (i.e., we apply the Sieve of Eratosthenes to the set {2,...,⌊ √(m) ⌋}), we can decrease the number of times we check the divisibility of m. However, we would need to do a lot of extra work to filter out the multiples of primes. Modern algorithms such as ECPP run in polynomial time, in practice it is currently difficult to implement a version of these algorithms that runs quickly enough for many applications.
Algorithm: Given the above considerations, we introduce a modified algorithm.
  1. inputs: d
    1. do
      1. n := any number from {10d-1, ..., 10d-1}
      while n is not probably prime
    2. return n
It remains to define a subroutine for checking whether a number is probably prime (for some appropriate definition of "probably") that is very efficient.

[link]  3.7. Detecting probable prime numbers

In this subsection, we consider the problem of defining a very efficient algorithm to check whether a positive integer m ℕ is prime. In fact, the algorithm we consider will be detectors of some, but not all, composite numbers.
Fact: For any n ℕ, n is composite iff n > 1 and it is not the case that n is prime.
That is, the algorithms we consider recognize prime numbers but with false positives. They only guarantee that there are no false negatives (i.e., if the algorithm outputs that its input is composite, then it is indeed composite; otherwise, the number may or may not be prime and we call it probably prime because we were not able to detect that it is composite). First, consider how an algorithm for checking primality that never has a "false" output behaves:

algorithm input algorithm output meaning description
actually a composite number
(this is not known at time of input)
composite the input is composite true negative
actually a prime number
(this is not known at time of input)
prime the input is prime true positive

Compare the above table to the following table describing three possible conditions (and one forbidden condition) for an algorithm that detects probable primes.

algorithm input algorithm output meaning description
actually a composite number
(this is not known at time of input)
composite the input is
definitely composite
true negative
actually a composite number
(this is not known at time of input)
probably prime the input is either
composite or prime
false positive
actually a prime number
(this is not known at time of input)
probably prime the input is either
composite or prime
true positive
actually a prime number
(this is not known at time of input)
composite impossible false negative
(we will not consider algorithms
that return such outputs)

Below is a comparison of the outputs of four possible probable prime algorithms on inputs in the range {2,...,10} ⊂ ℕ.

input
number
perfect
algorithm
perfect probable
prime algorithm
less accurate
probable prime
algorithm
very inaccurate
probable prime
algorithm
2 prime probably
prime
probably
prime
probably
prime
3 prime probably
prime
probably
prime
probably
prime
4 composite composite probably
prime
probably
prime
5 prime probably
prime
probably
prime
probably
prime
6 composite composite composite probably
prime
7 prime probably
prime
probably
prime
probably
prime
8 composite composite probably
prime
probably
prime
9 composite composite composite probably
prime
10 composite composite probably
prime
probably
prime

Algorithm: We now define our first algorithm for testing whether a number is probably prime.
  1. inputs: m ℕ, k
    1. repeat k times:
      1. a := a random number from {2,...,m-1}
      2. if a | m then return composite
    2. return probably prime
Notice that the above algorithm will never say that a prime number is actually composite. If it does not find a factor of m because it did not run for sufficiently many iterations, then it will indicate that m is probably prime. Thus, it will have no false negatives (i.e., an incorrect output indicating a prime number is composite).
Algorithm: We now define another algorithm for testing whether a number is probably prime.
  1. inputs: m ℕ, k
    1. repeat k times:
      1. a := a random number from {2,...,m-1}
      2. if a | m then return composite
      3. if gcd(a,m) ≠ 1 then return composite
    2. return probably prime
The above algorithm is interesting because by using the gcd operation, we get more value out of each random number we try. In fact, the gcd operation runs in polynomial time but tells us if the intersection between the two sets of factors (the factors of a and the factors of m) contains any numbers. Checking this intersection using the naive approach would take exponential time.

The above algorithm is somewhat problematic if we want to have a good idea of how to set k given our desired level of confidence in the output. For example, how high should k be so that the probability that we detect a composite is more than 1/2? If we require that k ≈ √(m) to be sufficiently confident in the output, we might as well use the brute force method of checking every a {2,..., ⌊ √(m) ⌋}.

To define a more predictable testing approach for our algorithm, we derive a theorem that is frequently used in applications of modular arithmetic (in fact, this fact underlies the prime number generators found in many software applications).
Fact (Fermat's little theorem): For any p ℕ, for any a {0,...,p-1}, if p is prime then it is true that:
ap-1
1 (mod p)
We have already shown that if p is a prime then R defined as below is a permutation:
R
=
{ (1, (1 ⋅ a) mod p), (2, (2 ⋅ a) mod p), ..., (p-1, ((p-1) ⋅ a) mod p) }
=
{ (i, (ia) mod p) | i {1,...,p-1} }
Next, to make our notation more concise, note that:
1 ⋅ 2 ⋅ ... ⋅ p-1
=
(p - 1)!
(1 ⋅ a) ⋅ (2 ⋅ a) ⋅ ... ⋅ ((p-1) ⋅ a)
=
ap-1 (p - 1)!
Recall that p is prime, so p does not divide (p - 1)!. Thus, we can divide by (p - 1)! both sides of the following equation:
ap-1 (p - 1)!
1 ⋅ (p - 1)!
ap-1
1
We now have derived the statement of Fermat's little theorem.
Fact: A number p ℕ is prime iff p > 1 and for all a {1,...,p-1}, ap-1 mod p = 1.
If we negate the statement above, we can define when a number is composite (i.e., when it is not prime) in a way that suggests a straightforward algorithm.
Definition: A number m ℕ is composite iff m > 1 and there exists a {1,...,m-1} such that am-1 mod m ≠ 1. In this case, a is a Fermat witness to the compositeness of m.
Definition: If for composite m ℕ and a {1,...,m-1}, we have that am-1 mod m = 1, then a is a Fermat liar and m is a pseudoprime with respect to a.
Algorithm (Fermat primality test): We now extend our algorithm. The following algorithm can be used to test whether a number is probably prime.
  1. inputs: m ℕ, k
    1. repeat k times:
      1. a := a random number from {2,...,m-1}
      2. if a | m then return composite
      3. if gcd(a,m) ≠ 1 then return composite
      4. if am-1 mod m ≠ 1 then return composite
    2. return probably prime
If m is a prime, the above algorithm will always return probably prime.
For any given candidate a in the above algorithm, if the first test fails and gcd(a,m) ≠ 1 then a is a factor of m. Thus, in the worst case, the first is gcd(a,m) = 1 for all k instances of a that we consider. How many of these k instances must pass the second test before we are confident that m is prime? In fact, for most composite numbers m, k can be very low.
Fact: If for a composite m ℤ there is at least one Fermat witness a {2,...,m-1} such that gcd(a,m) = 1, then at least half of all a such that gcd(a,m) = 1 are Fermat witnesses.
Suppose that a is a Fermat witness and a1,...,an are distinct Fermat liars. Then for every Fermat liar we have that:
(aai)n-1
an-1ain-1
an-1
But a is a Fermat witness, so an-1 mod m ≠ 1. Thus, (aai)n-1 mod m ≠ 1, so aai is also Fermat witness. Since there is a witness for every liar, there are at least as many witness as liars, so at least half the values are witnesses. How many numbers m have at least one Fermat witness? Equivalently, how many numbers have no Fermat witnesses?
Definition: For any m ℤ, if m has no coprime Fermat witnesses, then m is a Carmichael number, also known as a Fermat pseudoprime.
The distribuation of Carmichael numbers is high enough that the Fermat primality test is usually not used in favor of slightly more complex tests for probable primes. However, those tests follow a similar principle. The Fermat primality test is used in some deployed software applications (such as PGP).
for the chosen
a we have...
what it means probability of this occurring
if m is a non-Carmichael composite
a | m a is a non-trivial factor of m,
so m is composite
(# integers in {2,...,m-1} that are factors with m) / (m-2)
gcd(a,m) ≠ 1 m and a have a non-trivial factor,
so m is composite
(# integers in {2,...,m-1} that share factors with m) / (m-2)
am-1 mod m ≠ 1 a is a Fermat witness
that m is composite
at least 1/2
We can consider a particular example input for the primality test to see how each successive check in the algorithm can extract valuable information about whether the input is composite. The following table is for m = 15.
m = 15 and a = ... 2 3 4 5 6 7 8 9 10 11 12 13 14
a | m PP C PP C PP PP PP PP PP PP PP PP PP
gcd(a,m) ≠ 1 PP C PP C C PP PP C C PP C PP PP
am-1 mod m = ... 4 9 1 10 6 4 4 6 10 1 9 4 1
We can now summarize all the facts and algorithms we have introduced and how their relationships allow us to construct a prime number generator.
Euclid's
lemma
generalization
multiples of coprime
a in ℤ/mℤ are a
permutation
Fermat's
little
theorem
random
number
generator
gcd(m,m+1) = 1
greatest
common
divisor
algorithm
Fermat
primality
test
probable
prime
detector
probable
prime
generator


[link]  3.8. Assignment #2: Modular Arithmetic, Random Numbers, and Primes

In this assignment you will solve several equations, and you will define a collection of Python functions that will allow you to generate probable prime numbers of any size. You must submit a single Python source file named hw2/hw2.py. Please follow the gsubmit directions.

You may import the following library functions in your module:

from fractions import gcd
from math import log
        
You may also use the built-in pow() function, which can compute modular exponents efficiently (as in, ak mod n can be written in Python as pow(a,k,n)), the abs() function for computing the absolute value of an integer, and the // for integer division (you should avoid using / because it does not work for very large integers). Your file may not import any other modules or employ any external library functions associated with integers and sets unless explicitly permitted to do so in a particular problem.

Solutions to each of the programming problem parts below should be fairly concise. You will be graded on the correctness, concision, and mathematical legibility of your code. The different problems and problem parts rely on the lecture notes and on each other; carefully consider whether you can use functions from the lecture notes, or functions you define in one part within subsequent parts.
  1. Solve the following equations using step-by-step equational reasoning, and list each step. You must list all solutions (zero or more) for x. You may need to use some automation to perform some of the steps. Your solutions for this problem should appear as comments, delimited using '''...''', in hw2.py. You may use the = ascii character to represent the ≡ relational operator on congruence classes.
    1. x ≡ − 6 (mod 11)
    2. 2 ⋅ x ≡ − 5 (mod 13)
    3. 5 ⋅ x + 7 ≡ 13 (mod 29)
    4. 48 ⋅ x ≡ 4 (mod 12)
    5. 10 ⋅ x − 3 ≡ 3 (mod 5)
    6. 4 ⋅ x + 1 ≡ 8 (mod 16)
    7. 13 ⋅ x + 19 ≡ 188 (mod 401)
    8. 650472472230302 ⋅ x ≡ 1 (mod 8910581811374)
    9. 146467848 ⋅ x ≡ 43698243047256 (mod 7777777777777777777777777)
  2. Implement a function closest(t, ks) that takes two arguments: a target integer t and a list of integers ks. The function should return the integer in ks that is closest to t (i.e., the integer in k in ks that minimizes the absolute value of the difference |tk| between the two numbers). This will serve as a helper function for subsequent problems in this assignment.

    >>> closest(5, [1,3,4,9,10])
    4
    >>> closest(8, [1,3,4,9,10])
    9
              
  3. Implement the following Python functions. These functions take advantage of the generalized Euclid's lemma to make it possible to generate a random number within a specified range. Your implementations must be extremely efficient, and must handle very large inputs, as shown in the examples below. Implementations that perform exhaustive, exponential-time searches will receive no credit.
    1. Implement a function findCoprime(m) that takes a single positive integer argument m and returns an integer b where b > 1 and b is coprime with m. Your implementation does not need to return exactly the same answers as you see in the example outputs. However, the output generated by your implementation must be coprime with the input. Hint: use facts about coprime numbers (e.g., this, this, this, this, and/or this); the efficiency of the probable prime generator you must assemble in the last problem will depend on the coprimes your algorithm finds not being on the "edges" of the range.

      >>> findCoprime(10)
      7
      >>> findCoprime(100)
      63
      >>> findCoprime(872637825353262)
      545398640845789
      >>> findCoprime(2**200)
      1004336277661868922213726307713226626576376871114245522063361
      >>> gcd(findCoprime(2**100000), 2**100000)
      1
                    
    2. Implement a function randByIndex(m, i) that takes two positive integer arguments: m represents the upper bound of random numbers to be generated, and i represents an index specifying which random number in a the sequence should be generated. You may assume m ≥ 4 and that 1 ≤ im − 1. The function must return the ith "random" number in a permutation of the numbers {0, ..., m − 1}. Your implementation does not need to return exactly the same answers as you see in the example outputs. However, the output generated by your implementation must produce a permutation when used in a comprehension, as in the examples below.

      >>> [randByIndex(10, i) for i in {0,1,2,3,4,5,6,7,8,9}]
      [0, 7, 4, 1, 8, 5, 2, 9, 6, 3]
      >>> [randByIndex(77, i) for i in range(0,76)]
      [ 0, 48, 19, 67, 38,  9, 57, 28, 76, 47, 18, 66, 
       37,  8, 56, 27, 75, 46, 17, 65, 36,  7, 55, 26,
       74, 45, 16, 64, 35,  6, 54, 25, 73, 44, 15, 63, 
       34,  5, 53, 24, 72, 43, 14, 62, 33,  4, 52, 23, 
       71, 42, 13, 61, 32,  3, 51, 22, 70, 41, 12, 60, 
       31,  2, 50, 21, 69, 40, 11, 59, 30,  1, 49, 20, 
       68, 39, 10, 58]
      >>> randByIndex(2**200, 2**99+1)
      1004336277661868922213726307713860451876490985814993873666049
                    
      Hints:
      1. do not use floating point numbers and only use the integer division operator // when dividing;
      2. you can use the m.bit_length() method to efficiently obtain the bit length of an integer.
  4. Implement a function probablePrime(m) that takes a single integer argument m where m >= 1. The function should return True if m is probably prime, and False otherwise. Your code should employ the Fermat primality test by generating some number of random witnesses in the appropriate range and using them to test the primality of m. You will need to determine what is a reasonable number of potential witnesses to test. Implementations that perform an exponentially large exhaustive search, even if the algorithm is mathematically correct, will not earn full credit.

    >>> probablePrime(31)
    True
    >>> probablePrime(107)
    True
    >>> probablePrime(230204771)
    True
    >>> probablePrime(10738019798475862873464857984759825354679201872)
    False
              
  5. Implement a function makePrime(d) that takes a single integer argument d where d >= 1 and returns a probably prime number that has exactly d digits. Your implementation should be sufficiently efficient to produce an output for d = 100 in a reasonably short amount of time. Implementations that perform an exponentially large exhaustive search, even if the algorithm is mathematically correct, will not earn full credit.

    >>> makePrime(2)
    47
    >>> makePrime(100)
    3908330587430939367983163094172482420761782436265274101479718696329311615357177668931627057438461519
              


[link]  3.9. Multiplicative inverses

To better understand multiplicative inverses, we first review the definition of an additive inverse.
Fact: For any m ℕ, every element in the set ℤ/mℤ has an inverse with respect to addition defined over ℤ/mℤ (i.e., an additive inverse). Consider any x ℤ/mℤ. Then px ℤ/mℤ and
x + (px)
p (mod p)
0
We denote by −x the additive inverse of x.
Example: What is the additive inverse of 2 ℤ/5ℤ?

The additive inverse is 5 − 2 = 3, since 2 + 3 mod 5 = 0.
There is more than one way to compute multiplicative inverses; in this subsection, we will present facts that will help us build algorithms for computing multiplicative inverses.
Definition: Given a positive integer m ℕ and a congruence classes x ℤ/mℤ, suppose there exists a congruence class y ℤ/mℤ such that:
xy
1 (mod m)
Then we say that y is the multiplicative inverse of x in ℤ/mℤ. We usually denote the multiplicative inverse of x as x-1 (as is often done for multiplicative inverses over the integers, i.e., 2-1 = 1/2).
Fact: Let p ℕ be a prime number, and let a ℤ/pℤ. Then we know by Fermat's little theorem that:
ap-1
1 (mod p)
But we can factor the above to get:
aap-2
1 (mod p)
Thus, the multiplicative inverse of a ℤ/pℤ is:
a-1
ap-2 (mod p)
Note that:
aa-1
aap-2 (mod p)
ap-1
1
Example: What is the multiplicative inverse of 2 ℤ/5ℤ? We can compute it as follows:
2-1
25-2 (mod 5)
23
8
3
We can check to confirm that this is true:
2 ⋅ 3
6 (mod 5)
1

[link]  3.10. Chinese remainder theorem (CRT) and applications

In previous sections we presented facts that allowed us to solve certain individual equations with solution spaces corresponding to sets of congruence classes such as ℤ/mℤ. It is also possible to solve systems of equations over sets of congruence classes.
Theorem (Chinese remainder theorem): The Chinese remainder theorem (CRT) states that given primes p1,...,pk ℕ, for any a1,...,ak ℤ there exists a solution x ℤ to the system of equations:
x mod p1
=
a1
x mod pk
=
ak
We can also state the theorem in terms of congruences. Given primes p1,...,pk ℕ, for any a1 ℤ/p1ℤ, ..., ak ℤ/pkℤ there exists a unique solution x ℤ/(p1 ⋅ ... ⋅ pk)ℤ to the system of equations:
x
a1 (mod p1)
x
ak (mod pk)
In other words, all the solutions to the first system above are from the same congruence class of ℤ/(p1 ⋅ ... ⋅ pk)ℤ. The theorem applies even if p1,...,pk are only relatively prime or coprime.
Example: Solve the following system of equations for the unique solution x ℤ/10ℤ:
x
3 (mod 5)
x
0 (mod 2)
We can list the integers corresponding to each congruence class and find the unique integer in {0, ..., 2 ⋅ 5 - 1} that is in both lists:
3 + 5ℤ
=
{..., 3, 8, 13, 18, 23, 28, ...}
0 + 2ℤ
=
{..., 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, ...}
We can compute the intersection, which should contain all the integers that satisfy both equations:
(3 + 5ℤ) ∩ (0 + 2ℤ)
=
{..., 8, 18, 28, ...}
This appears to be the congruence class 8 + 10ℤ. Thus, we have the unique solution:
x
8 (mod 10)
The Chinese remainder theorem has many applications in a variety of contexts. In this section we present the following algorithms, which all rely on the ability to solve systems of equations involving congruence classes.
efficient
modular
arithmetic
Chinese
remainder
theorem
CRT solver range
ambiguity
resolution
Shamir secret
sharing protocol
Fact: Given a, a ℤ, if a + b {0,...,m-1}, then it is true that
(a mod m) + (b mod m)
=
(a + b) mod m
Likewise, if a ⋅ b {0,...,m-1}, then it is true that
(a mod m) ⋅ (b mod m)
=
(ab) mod m
Example (efficient modular arithmetic): Suppose we want to perform a large number of arithmetic operations in sequence. The operations could be specified as a program that operates on a single variable and performs a sequence of variable updates that correspond to arithmetic operations, such as the example below.
x
:=
3
x
:=
x + 6
x
:=
x ⋅ 2
x
:=
x − 17
x
:=
x + 1
Suppose that over the course of the computation, x might become very large (e.g., 0 ≤ x ≤ 21000000000). However, we have an additional piece of information: once the sequence of operations ends, we know that 0 ≤ x < 1024.

Given our additional information about the range of the final output, we do not need to store 1000000000 bit numbers in order to perform the computation and get a correct result. It is sufficient to instead perform all the operations in ℤ/1024ℤ:
x
:=
3 mod 1024
x
:=
(x + 6) mod 1024
x
:=
(x ⋅ 2) mod 1024
x
:=
(x − 17) mod 1024
x
:=
(x + 1) mod 1024
The above will produce the same result in ℤ/1024ℤ, and we will only need 1024 bits at any single point in the computation to store each intermediate result.
Example (efficient distributed modular arithmetic): Suppose that, as in the previous example, we want to perform a large number of arithmetic operations in sequence on large integers (e.g., in ℤ/270ℤ). However, our resources may be limited. For example, we may only have a collection of processors that can each perform arithmetic on relatively small integers (e.g., in ℤ/28ℤ). Is it possible for us to perform this computation using these processors, and is it possible for us to speed up the computation by running the processors in parallel? We may assume that a single arithmetic computation in ℤ/2nℤ running on a single processor takes n time steps to perform.

Suppose we have ten processors that can perform arithmetic computations in ℤ/28ℤ or any smaller space (such as ℤ/27ℤ). We can approach this problem by choosing a collection of primes p1,...,p10 such that 27 < pi < 28, which implies that:
p1 ⋅ ... ⋅ p10
>
27 ⋅ ... ⋅ 27
270
We can then perform the sequence of computations modulo each of the primes pi to obtain ten results a1, ..., a10. Once we obtain the results, we can apply the Chinese remainder theorem to obtain x:
x
=
a1 mod p1
x
=
a10 mod p10
Since the product of the primes is greater than 270, the unique solution x to the above system of equations will be the correct result of the computation. Since the processors were running in parallel, the computation was about 10 times faster than it would have been if we had performed the computation in ℤ/270ℤ (or in sequence using a single processor that can perform computations in ℤ/28ℤ).
Example (variant of range ambiguity resolution): Suppose we want to build a radar or other sensing device that sends signals out and listens for reflections of those signals in order to detect the distances of obstacles in the environment. The device has a clock that counts up from 0, one integer per second. If the device sends a single signal out that travels at 1 km per second at time 0 and receives a response in 12 seconds at time 12, it knows that the distance to the object and back is 12 km.

However, what if we cannot wait 12 seconds or more? For example, the obstacle may be moving quickly and we want to constantly update our best guess of the distance to that object. We would need to send signals more frequently (for example, every 5 seconds). But then if an object is 12 seconds away, we would have no way to tell when running in a steady state which of the signals we just received.

However, we can obtain some information in this scenario. Suppose we send a signal every 5 seconds, only when the clock's timer is at a multiple of 5. Equivalently, imagine the clock counts up modulo 5 (i.e., 0,1,2,3,4,0,1,2,3,4,0,...) and we only send signals when the clock is at 0. What information can we learn about the object's distance in this scenario? If the distance to the object and back is d, then we would learn d mod 5, because we would get the signal back when the clock is at 0, 1, 2, 3, or 4.

We can use multiple instances of the above device (each device using its own distinct frequency for sending signals) to build a device that can check for obstacles more frequently while not giving up too much accuracy. Pick a collection of primes p1,..., pn such that their product is greater than the distance to any possible obstacle (e.g., if this is a ship or plane, we could derive this by considering the line of sight and the Earth's curvature). Take n instances of the above devices, each with their own clock that counts in cycles through ℤ/piℤ and sends out a signal when the clock is at 0. Running in a steady state, if at any point in time the known offsets are a1,...,an, we would know the following about the distance d to an obstacle:
d
a1 (mod p1)
d
an (mod pn)
We can then use the Chinese remainder theorem to derive the actual distance d < p1 ⋅ ... ⋅ pn.
Protocol (Shamir secret sharing): Suppose there are N participants and we want to divide some secret information among them into N parts so that any k or greater number of participants can reconstruct the secret information, but no subset of fewer than k participants can reconstruct it. Let s ℤ be the secret information. Collect a set of randomly chosen relatively prime integers M = {m1,...,mN} such that:
  • the product of any collection of at least k integers in M is greater than s;
  • the product of any collection of k-1 integers in M is less than s.
Give each participant i {1,...,N} the value s mod mi. Now, any number of participants nk can use the Chinese remainder theorem to solve for s.
Note: There are many alternative ways to implement Shamir secret sharing. Consider the following example using curve-fitting. We choose some large m ℤ, and then randomly select integers c1,...,ck ℤ/mℤ. We then use these integers as coefficients in a polynomial:
f(x)
=
s + c1 x + c2 x2 + ... + ck xk
Each participant i {1,...,N} is given f(i). Any k participants can now use curve-fitting techniques or techniques for solving collections of equations (e.g., computing the reduced row echelon form of a matrix) to determine all the coefficients of f and, thus, solve for s.

[link]  3.11. Solving systems of equations with CRT solutions using multiplicative inverses

The Chinese remainder theorem guarantees that a unique solution exists to particular systems of equations involving congruence classes. But can these solutions be computed automatically and efficiently? In fact, they can. However, computing such solutions requires the ability to compute multiplicative inverses in ℤ/mℤ.
greatest
common
divisor
algorithm
Fermat's
little
theorem
Euler's
theorem
Bézout's
identity
extended
Euclidean
algorithm
algorithm for
finding
multiplicative
inverses
Euler's
totient
function φ
Chinese
remainder
theorem
CRT solver
for two
equations
induction CRT solver
for n
equations
How is computing multiplicative inverses related to solving systems of equations that have solutions according to CRT? Consider the following example.
Example: Suppose we want to solve the following system of equations:
x
1 (mod 5)
x
0 (mod 4)
The above two equations are constraints on the integers that can be in the congruence class x. One way to state these constraints in English is: "x must be a multiple of 4, and x must be in 1 + 5ℤ". But then we can rewrite the above as a single equation:
4 ⋅ y
1 (mod 5)
Then, we only need to solve for y, and let x = 4 ⋅ y. What is y? The above equation implies that y is the multiplicative inverse of 4 in ℤ/5ℤ. Thus, we can compute:
y
4-1 (mod 5)
45-2 (mod 5)
43
64
4
Thus, the multiplicative inverse of 4 in ℤ/5ℤ is itself. Plugging y into x = 4 ⋅ y gives us 16. Thus, we know by CRT that we have our unique solution in ℤ/(4 ⋅ 5)ℤ = ℤ/20ℤ:
x
16 (mod 20)
The above example suggests that we can solve certain pairs of equations with CRT solutions if one of the congruence classes is 0 and the other 1. What if the other congruence class is not 1?
Example: Suppose we want to solve the following system of equations for x ℤ/15ℤ:
x
4 (mod 5)
x
0 (mod 3)
We can observe that we want some x that is a multiple of 3 and is in 4 + 5ℤ. We can set x = 3 ⋅ y for some y, and then we want to solve the following for y ℤ/5ℤ:
3 ⋅ y
4 (mod 5)
Using Fermat's little theorem, we can compute the multiplicative inverse of 3 ℤ/5ℤ:
35-1
1 (mod 5)
35-1 ⋅ 3-1
1 ⋅ 3-1
3(5-1)-1
3-1
35-2
3-1
33
3-1
27
3-1
2
3-1
Thus, we know that the multiplicative inverse of 3 ℤ/5ℤ is 2, and we have that 2 ⋅ 3 ≡ 1 (mod 5). Notice that 4 ≡ 4 ⋅ 1 (mod 5):
3 ⋅ y
4 (mod 5)
3 ⋅ y
4 ⋅ 1 (mod 5)
Since 1 ≡ 2 ⋅ 3 (mod 5), we can substitute:
3 ⋅ y
4 ⋅ (3 ⋅ 2) (mod 5)
We can cancel 3 on both sides using Euclid's lemma (since 5 is prime) or Euclid's generalized lemma (since 3 and 5 are coprime):
y
4 ⋅ 2 (mod 5)
y
8
y
3
Since we originally set x = 3 ⋅ y, we can now substitute and solve for x ℤ/15ℤ:
x
3 ⋅ y (mod 15)
x
3 ⋅ 3
x
9
x
9
Thus, x ≡ 9 (mod 15) is a solution to our equation. We can confirm this:
9
4 (mod 5)
9
0 (mod 3)
Example: Note that what we actually did in the previous example when we cancelled 3 ℤ/5ℤ on both sides is that we multiplied both sides by the multiplicative inverse of 3 ℤ/5ℤ. Suppose we knew that the multiplicative inverse of 3 ℤ/5ℤ is 2. We can use this information to help us solve the following equation:
3 ⋅ x
2 (mod 5)
We multiply both sides by 3-1 ≡ 2 (mod 5):
3-1 ⋅ 3 ⋅ x
3-1 ⋅ 2 (mod 5)
x
2 ⋅ 2
x
4
Notice that we have now reduced the problem of solving an equation with a single coefficient before x into the problem of finding the multiplicative inverse of the coefficient.
Example: Suppose we want to solve the following system of equations:
x
0 (mod 11)
x
4 (mod 7)
The above equations require that x ℤ/77ℤ be divisible by 11, and that x 4 + 7ℤ. Since x is divisible by 11, it is a multiple of 11, so we want to find x = 11 ⋅ y where:
11 ⋅ y
4 (mod 7)
To solve the above, it is sufficient to multiply both sides of the equation by 11-1 (mod 7). Since 11 ≡ 4 (mod 7), it is sufficient to find 4-1 (mod 7).
11-1
4-1 (mod 7)
47-2
45
1024
2
Thus, we can multiply both sides to obtain:
11 ⋅ y
4 (mod 7)
11-1 ⋅ 11 ⋅ y
11-1 ⋅ 4
y
2 ⋅ 4
y
8
y
1
Thus, we have:
x
11 ⋅ y (mod 77)
x
11 ⋅ 1
x
11 (mod 77)
Fact: Suppose we are given two unequal prime numbers p, q ℕ, and the following two equations:
x
1 (mod p)
x
0 (mod q)
This implies x must be a multiple of q, so rewrite x = qy. Then we have:
qy
1 (mod p)
Thus, we can solve for q-1 by computing:
q-1
qp-2 (mod p)
Then we have:
q-1qy
q-1 ⋅ 1 (mod p)
y
q-1 (mod p)
x
qy (mod (pq))
Notice that qy is indeed a solution to the original system because:
qy
1 (mod p)
    
     because yq-1 (mod p);
qy
0 (mod q)
    
     because qy is a multiple of q.
Fact: Suppose we are given two unequal prime numbers p, q ℕ, and the following two equations where a ℤ/pℤ:
x
a (mod p)
x
0 (mod q)
This implies x must be a multiple of q and a multiple of a, so rewrite x = aqy. Then we have:
aqy
a (mod p)
As in the previous fact, the above works if y = q-1 (mod p), so compute:
y
=
q-1 (mod p)
x
aqy (mod (pq))
Notice that aqy is indeed a solution to the original system because:
aqy
a (mod p)
    
     because yq-1 (mod p);
aqy
0 (mod q)
    
     because aqy is a multiple of q.
Fact: Suppose we are given two unequal prime numbers p, q ℕ, and the following two equations where a ℤ/pℤ and b ℤ/qℤ:
x
a (mod p)
x
b (mod q)
Suppose we instead solve the following two systems:
x1
a (mod p)
x1
0 (mod q)
x2
0 (mod p)
x2
b (mod q)
Notice that x1 + x2 is a solution to the original system because:
x1 + x2
x1 + 0 (mod p)
a + 0 (mod p)
a (mod p)
x1 + x2
0 + x2 (mod q)
0 + b (mod q)
b (mod q)
We know how to solve the above two systems separately:
x1
aqq-1 (mod (pq))
x2
bpp-1 (mod (pq))
Thus, we have the solution to the original system:
x
x1 + x2 (mod (pq))
We have shown that we can solve a system of equations with a solution according to CRT if the moduli in the equations are both prime. What if the moduli are merely coprime? So far, we only needed a way to compute multiplicative inverses of numbers modulo a prime, and Fermat's little theorem was sufficient for this purpose. However, if the moduli are not prime, we need some other method to compute multiplicative inverses.
Fact: For any m ℕ, an x ℤ/mℤ has an inverse with respect to multiplication defined over ℤ/mℤ (i.e., a multiplicative inverse) iff gcd(x,m) = 1.
Fact (Bezout's identity): For any two integers x ℤ, y ℤ where x ≠ 0 or y ≠ 0, let z = gcd(x,y). Then there exist a ℤ and b ℤ such that:
ax + by
=
z
Fact: For any two integers x ℤ, y ℤ where x ≠ 0 or y ≠ 0, and gcd(x,y) = 1, there exist a ℤ and b ℤ such that:
ax + by
=
1
This fact is a special case of Bézout's identity (i.e., the case in which gcd(x,y) = 1).
Example: Suppose we have s,t ℤ such that:
5 ⋅ s + 3 ⋅ t
=
1
We can then do the following:
− 5 ⋅ t + (5 ⋅ s + 3 ⋅ t) + 5 ⋅ t
=
1
(5 ⋅ s − 5 ⋅ t) + (3 ⋅ t + 5 ⋅ t)
=
1
5 ⋅ (st) + 8 ⋅ t
=
1
Thus, we have converted a instance of Bézout's identity for 5 and 3 into an instance of Bézout's identity for 5 and 8.

We can repeat the above as many times as we want. Suppose we instead want Bézout's identity for 3 and 13. We can do the following:
− 5 ⋅ 2 ⋅ t + (5 ⋅ s + 3 ⋅ t) + 5 ⋅ 2 ⋅ t
=
1
(5 ⋅ s − 5 ⋅ 2 ⋅ t) + (3 ⋅ t + 5 ⋅ 2 ⋅ t)
=
1
5 ⋅ (s − 2 ⋅ t) + 13 ⋅ t
=
1
Fact: For any two integers a, b, s, t ℤ, suppose we have that:
as + bt
=
1
Let us assume that a > b and that a mod b = r (in other words, for some k,
a mod b
=
r
bk + r
=
a
Then we have that:
as + bt
=
1
bks + (as + bt) + bks
=
1
(asbks) + (b ⋅ (t + ks))
=
1
(abk) ⋅ s + b ⋅ (t + ks)
=
1
rs + b ⋅ (t + ks)
=
1
(a mod b) ⋅ s + b ⋅ (t + ks)
=
1
Thus, for any instance of Bézout's identity for a and b and a > b, there must exist an instance of Bézout's identity for a mod b and b.
The above fact suggests that if we want to find the s and t coefficients for an equation as + bt = 1 given a > b, we should try finding Bézout's identity for a mod b and b. But notice that:
a mod b
<
b
The above implies that the problem of finding the coefficients for an instance of Bézout's identity can be reduced to a smaller version of the problem: find Bézout's identity for a mod b and b can then be reduced further to finding b mod (a mod b) and a mod b. At this point, we have a strictly smaller instance of the problem:
a mod b
<
b
<
a
b mod (a mod b)
<
b
<
a
Thus, we can use recursion; the recursive algorithm that solves this problem is called the extended Euclidean algorithm, and is a modification of the recursive algorithm that computes the gcd of two numbers.
Algorithm (extended Euclidean algorithm): The collection of equations considered in the Chinese remainder theorem can be solved constructively (i.e., in a way that provides a concrete solution and not just a proof that a solution exists) by applying an extended version of the greatest common divisor algorithm. We provide the definition of the algorithm below.
  1. extended Euclidean algorithm: x ℤ, y
    1. if y = 0
      1. (s,t) := (1, 0)
      2. return (s,t)
    2. otherwise
      1. (s,t) := extended Euclidean algorithm(y, x mod y)
      2. return (t, s - (⌊ x/y ⌋ ⋅ t) )
Given two inputs x ℤ, y ℤ, the extended Euclidean algorithm returns two integers u, v such that
ux + vy
=
gcd(x,y)
We can check that the above is indeed a solution to xa (mod m). Consider the following:
um + vn
=
1
vn
=
1 - um
vn
1 (mod m)
Furthermore, we have that:
((um) ⋅ b) mod m
=
0
Then, we can conclude:
((um) ⋅ b + (vn) ⋅ a) mod m
=
0 + ((vn) ⋅ a) mod m
=
0 + (1 ⋅ a) mod m
=
a mod m
Using a similar argument, we can show that the solution is also equivalent to b (mod m).
Example: Suppose we want to find the multiplicative inverse of 49 in ℤ/100ℤ and the multiplicative inverse of 100 in ℤ/49ℤ. We run the extended Euclidean algorithm on the inputs 49 and 100 to obtain the following instance of Bézout's identity:
(-24) ⋅ 100 + 49 ⋅ 49
=
1
We can use the above to find the multiplicative inverse of 49 in ℤ/100ℤ:
(-24) ⋅ 100 + 49 ⋅ 49
=
1
(-24) ⋅ 100 + 49 ⋅ 49
1 (mod 100)
49 ⋅ 49
1 (mod 100)
Thus, 49-1 = 49 in ℤ/100ℤ. We can also find the multiplicative inverse of 100 in ℤ/49ℤ (also known as 2 ℤ/49ℤ):
(-24) ⋅ 100 + 49 ⋅ 49
=
1
(-24) ⋅ 100 + 49 ⋅ 49
1 (mod 49)
-24 ⋅ 100
1 (mod 49)
25 ⋅ 100
1 (mod 49)
Thus, 100-1 = 25 in ℤ/49ℤ.
Example: Suppose we want to solve the following system:
x
23 (mod 100)
x
31 (mod 49)
We use the extended Euclidean algorithm to find that:
(-24) ⋅ 100 + 49 ⋅ 49
=
1
This tells us that −24 is the inverse of 100 in ℤ/49ℤ and that 49 is the inverse of 49 in ℤ/100ℤ. Thus, to build 31 in ℤ/49ℤ, we need:
31
1 ⋅ 31 (mod 49)
(100 ⋅ 100-1) ⋅ 31
(100 ⋅ -24) ⋅ 31
To build 23 in ℤ/100ℤ, we need:
23
1 ⋅ 23 (mod 100)
(49 ⋅ 49-1) ⋅ 23
(49 ⋅ 49) ⋅ 23
Then the solutions to the system are in the congruence class:
x
(100 ⋅ -24) ⋅ 31 + (49 ⋅ 49) ⋅ 23 (mod (100 ⋅ 49))
-19177 mod 4900
423
Algorithm: Suppose we are given a collection of equations of the following form such that m1,...,mk are all pairwise coprime.
x
a1 (mod m1)
x
ak (mod mk)
Let C be the set of these equations, where Ci is the ith equation. The following algorithm can be used to find a solution for this system of equations.
  1. solve system of equations: C is a set of constraints xai mod mi
    1. while |C| > 1
      1. remove two equations Ci and Cj from C and solve them to obtain a new equation xc (mod mimj)
      2. add the new equation to C
    2. return the one equation left in C

[link]  3.12. More practice with CRT

Example: Solve the following equation for x ℤ/5ℤ by multiplying both sides by the appropriate multiplicative inverse:
3 ⋅ x
2 (mod 5)
Example: Solve the following system of equations for x ℤ/35ℤ by finding multiplicative inverses of 5 ℤ/7ℤ and 7 ℤ/5ℤ:
x
4 (mod 5)
x
2 (mod 7)
Example: Suppose you know that 7-1 ≡ 3 (mod 10). Solve the following system of equations:
x
0 (mod 2)
x
1 (mod 5)
x
3 (mod 7)
We first solve the first two equations. We know x must be a multiple of 2 that is in 1 + 5ℤ. Thus, we set x = 2 ⋅ y and we solve:
x
1 (mod 5)
2 ⋅ y
1 (mod 5)
Then we know that:
y
2-1 (mod 5)
25-2
23
8
3
Thus, x ≡ 2 ⋅ 3 ≡ 6 (mod (2 ⋅ 5)). This leaves two equations:
x
6 (mod 10)
x
3 (mod 7)
We first find 10-1 (mod 7):
10-1
107-2 (mod 7)
105
5
We now have 10-1 ≡ 5 (mod 7) and 7-1 ≡ 3 (mod 10), so now we can express 6 as a multiple of 7 in ℤ/10ℤ, and we can express 3 as a multiple of 10 in ℤ/7ℤ:
x
6 (mod 10)
6 ⋅ 1
6 ⋅ (7 ⋅ 7-1)
x
3 (mod 7)
3 ⋅ 1
3 ⋅ (10 ⋅ 10-1)
Thus, we add these two terms to obtain our solution in ℤ/(7 ⋅ 10)ℤ:
x
6 ⋅ (7 ⋅ 7-1) + 3 ⋅ (10 ⋅ 10-1) (mod 70)
6 ⋅ (7 ⋅ 3) + 3 ⋅ (10 ⋅ 5)
6 ⋅ 21 + 3 ⋅ 50
126 + 150
276
66
Example: Suppose we have a single processor that can perform arithmetic operations (addition, subtraction, multiplication, and modulus) on integers that can be represented with at most 11 bits (211 = 2048). On this processor, a single arithmetic operation can be performed in 11 time steps. We also have three other processors that can perform arithmetic on integers that can be represented with at most 4 bits (24 = 16). Each of these processors can perform an arithmetic operation on 4-bit integers in 4 time steps.

For example, suppose we want to perform 1000 arithmetic operations on 11-bit integers. Using a single processor, this would require:
1000 ⋅ 11 = 11,000 time steps
If we use three coprime numbers 13, 14, and 15, and we use each of the three 4-bit processors to perform these operations modulo 13, 14, and 15 in parallel, 1000 operations would require:
1000 ⋅ 4 = 4,000 time steps
Note that 13 ⋅ 14 ⋅ 15 = 2730, and that 2730 > 2048, so:
13 ⋅ 14 ⋅ 15
>
211
Suppose it takes 1400 time steps to solve a system of three congruence equations of the following form:
x
a (mod 13)
x
b (mod 14)
x
c (mod 15)
If we want to perform the computations as quickly as possible and we can use either the 11-bit processor or the three 4-bit processors, how many operations k would we need to perform before we decided to switch from the the 11-bit processor to the 4-bit processors?
To solve this problem, we can first write down two function describing the time cost of the two strategies for k operations:
f(k)
=
k ⋅ 11
g(k)
=
k ⋅ 4 + 1400
Notice that f(1) < g(1), so if we only wanted to perform k = 1 operation, we would prefer to use the 11-bit processor. The point at which we would want to switch would be when f(k) ≥ g(k), so we can find the point at which the two functions intersect:
f(k)
g(k)
k ⋅ 11
k ⋅ 4 + 1400
k ⋅ 7
1400
k
200
Thus, if k ≥ 200, we want to use the 4-bit processors.
Example: Suppose we are using echolocation to measure the distance to a wall that is at most 15 distance units away. We have two devices that emit sounds at two different frequencies. One device emits sound every 3 seconds, while the other device emits a sound every 11 seconds. Suppose we hear the following:
  • the device that emits a sound every 3 seconds hears a response 2 seconds after each time it emits a sound;
  • the device that emits a sound every 11 seconds hears a response 4 seconds after each time it emits a sound.
If sound travels one distance unit per second, how far away is the wall?
Example: Suppose Alice, Bob, and Eve are using the Shamir secret sharing protocol to store a combination for a lock; all three participants would need to work together to retrieve the secret lock combination in ℤ/60ℤ. They are each given the following equations:
Alice:
    
x
1 (mod 3)
Bob:
    
x
3 (mod 4)
Eve:
    
x
2 (mod 5)
  1. What is the lock combination?
  2. The lock only permits anyone to try two incorrect combinations before locking down completely and becoming inaccessible. Suppose Eve has a chance to steal either Bob's secret information or Alice's secret information, but she can only choose one. Whose information should she steal in order to unlock the lock?
Example: Suppose we want to store a number n between 0 and 500,000 on a collection of 5-bit memory regions. However, we want to make sure that if any one of the memory regions is turned off, we can still recover the number exactly, without any missing information or errors. How many memory regions will we need to use? Note that 323 = 32,768 and 324 = 1,048,576.
We will need five memory regions. Suppose we choose five coprime numbers less than or equal to 25 = 32: 25,27,29,31, and 32. Note that any product of four of these numbers is greater than 500,000 because 25 ⋅ 27 ⋅ 29 ⋅ 31 = 606,825. Then with five 5-bit memory regions, we can store each of the following five values in one region:
n mod 25
n mod 27
n mod 29
n mod 31
n mod 32
If any of the above values are lost, it is still possible to recover n by solving a system with four equations. In the worst case, the product of the moduli would be 606,825 > 500,000.
Example: Suppose we make the following simplifications: for every t years,
  • when the Earth revolves around the sun, it travels a circumference of 1 unit, at a rate of 1 ⋅ t (once per year);
  • when the asteroid Ceres revolves around the sun, it travels a circumference of 5 units;
  • when the planet Jupiter revolves around the sun, it travels a circumference of 11 units.
Suppose that on June 21st, 2000, the Earth, Ceres, and Jupiter all align (i.e., one can draw a straight line through all three). Next, suppose that it is June 21st of some year between 2000 and 2055. At this time, there is no alignment. However, Jupiter aligned with earth on June 21st two years ago, and Ceres aligned with Earth on June 21st three year ago. What year is it?

[link]  3.13. Euler's totient function, Euler's theorem, and applications

Definition: For any input m ℕ, define Euler's totient function φ by:
φ(m)
=
|{k | k {1,...,m}, gcd(k,m) = 1}|
Example: Compute φ(15).
φ(15)
=
|{k | k {1,...,15}, gcd(k,15) = 1}|
=
|{1,2,4,7,8,11,13,14}|
=
8
Example: Suppose p ℕ is a prime number. What is φ(p)?

φ(p)
=
|{k | k {1,...,p}, gcd(k,p) = 1}|
=
|{1,2,3,...,p-1}|
=
p-1
Example: What is φ(15)?
φ(15)
=
|{k | k {1,...,15}, gcd(k,15) = 1}|
=
15 - |{k | k {1,...,15}, gcd(k,15) ≠ 1}|
=
15 - |{3,6,9,12,15} ∪ {5,10,15}|
=
15 - |{3,6,9,12}| - |{5,10}| - |{15}|
=
15 - (5-1) - (3-1) - 1
=
15 - 5 - 3 + 1 + 1 - 1
=
15 - 5 - 3 + 1
=
(3 ⋅ 5) - 5 - 3 + 1
=
(3-1) ⋅ (5-1)
=
2 ⋅ 4
=
8
Fact: For any x ℕ and y ℕ, if gcd(x,y) = 1 then:
φ(x) ⋅ φ(y)
=
φ(xy)
Example: Suppose p ℕ and q ℕ are prime numbers. What is φ(pq)?
φ(pq)
=
φ(p) ⋅ φ(q)
=
(p-1) ⋅ (q-1)
Fact: For any prime p ℕ, we have that:
φ(pk)
=
pk - pk-1
Fact: For any a ℕ and m ℕ, if am-1 mod m = 1 then:
am-1 mod m
=
1
am-1
=
1 + km
1
=
gcd(1 + km, km)
=
gcd(am-1, km)
=
gcd(a, km)
=
gcd(a, m)
Thus, a and m are coprime.
Example: Suppose m ℕ is a Carmichael number. At most how many Fermat liars does m have?
Fact: We can use φ to provide a formula for the probability that the Fermat primality test will detect that a Carmichael number m ℕ is actually composite. It is approximately:
(m - φ(m)) / m
To be more precise (since we do not check 0 or 1 in our actual implementation), it is:
((m - 3) - φ(m)) / (m - 3)
Unfortunately, Euler's totient function does not in general have a better upper bound than f(m) = m.
Example: How many elements of ℤ/mℤ have a multiplicative inverse in ℤ/mℤ? Since an x ℤ/mℤ has an inverse iff gcd(x,m) = 1. Thus, the set of such x is exactly the set {x | x {1,...,m}, gcd(k,m) = 1}. But this is the definition of φ(m). Thus, there are φ(m) elements in ℤ/mℤ that have a multiplicative inverse.
Theorem (Euler's theorem): For any m ℕ and a ℤ/mℤ, if gcd(m,a) = 1 then we have that:
aφ(m) mod m
=
1
Notice that if m is a prime number, then φ(m) = m-1. Then for any a ℤ/mℤ, gcd(a,m) = 1 and am-1 mod m = 1. This is exactly the statement of Fermat's little theorem. Thus, Euler's theorem is a generalization of Fermat's little theorem.
Fact: For any m ℕ and a ℤ/mℤ, if gcd(m,a) = 1 then for any i ℤ/φ(m)ℤ such that i ≡ 0 we have that
ai mod m
=
1
This is because:
i
0 (mod φ(m))
=
k ⋅ φ(m)
aφ(m) ⋅ k mod m
=
(aφ(m))k mod m
=
1k mod m
=
1 mod m
Fact: For any p ℕ, if p is prime and a ℤ/pℤ then for any k ℤ we have that:
ak mod p
=
a(k mod (p-1)) mod p
Fact: For any m ℕ and a ℤ/mℤ, if gcd(m,a) = 1 then for any k ℤ we have that:
ak mod m
=
a(k mod φ(m)) mod m
Example: We can compute the integer value 238 mod 7 as follows because 7 is prime:
238
238 mod (7-1) (mod 7)
238 mod 6
22
4
Since the final operation in the integer term is a modulus operation, the congruence class 4 is also exactly the integer result of the term.
Example: We can compute 4210000000 mod 5 as follows because 5 is prime:
4210000000
4210000000 mod (5-1) (mod 5)
=
4210000000 mod 4
=
40
=
1
Example: We can compute 48100+ 3 mod 15 as follows because gcd(4,15) = 1:
4(8100 + 3)
4(8100 + 3) mod φ(15) (mod 15)
=
4(8100 + 3) mod ((5-1) ⋅ (3-1))
=
4(8100 + 3) mod 8
=
43
=
64
=
4
Example: Compute 56603 mod 7.
Fact: For any m ℕ and a ℤ/mℤ where gcd(m,a) = 1, we can use the Euler's theorem to find the inverse of a. Notice that:
aφ(m) mod m
=
1
(aφ(m)-1a) mod m
=
1
Thus, aφ(m)-1 mod m is the multiplicative inverse of a in ℤ/mℤ.
Example: Find the multiplicative inverse of 52 in ℤ/7ℤ.

It is sufficient to notice that 56 ≡ 1 (mod 7), so 52 ⋅ 54 ≡ 1, so 54 is the inverse of 52 in ℤ/7ℤ.
Example: We can find the multiplicative inverse of 3 in ℤ/22ℤ using the following steps. We first compute φ(22) = 10.
φ(22)
=
φ(11 ⋅ 2)
=
φ(11) ⋅ φ(2)
=
(11 − 1) ⋅ (2 − 1)
=
10 ⋅ 1
=
10
Next, we compute the inverse using Euler's theorem.
3-1
3φ(22) − 1 (mod 22)
310 − 1
39
33 ⋅ 33 ⋅ 33
5 ⋅ 5 ⋅ 5
25 ⋅ 5
3 ⋅ 5
15
Definition: For m ℕ, We define (ℤ/mℤ)* to be the following subset of ℤ/mℤ:
(ℤ/mℤ)*
=
{ a | a ℤ/mℤ, a has an inverse in ℤ/mℤ }
Example: Does 11 have an inverse in ℤ/22ℤ (i.e., is it true that 11 (ℤ/22ℤ)*)?
Example: Compute |(ℤ/35ℤ)*|.
|(ℤ/35ℤ)*|
=
|{ a | a ℤ/35ℤ, a has an inverse in ℤ/35ℤ }|
=
|{ a | a ℤ/35ℤ, gcd(a,35) = 1 }|
=
|{ a | a ℤ/35ℤ, gcd(a,35) = 1 }|
=
φ(35)
=
φ(5 ⋅ 7)
=
φ(5) ⋅ φ(7)
=
4 ⋅ 6
=
24
Fact: For any m ℕ, (ℤ/mℤ)* is closed under multiplication modulo m. That is, for any a ℤ/mℤ and b ℤ/mℤ, if there exist a-1 ℤ and b-1 ℤ then (ab) has an inverse (a-1b-1). We can use the commutativity of multiplication to show this:
(ab) ⋅ (a-1b-1)
(aa-1) ⋅ (bb-1)
1 ⋅ 1
1


[link]  3.14. Assignment #3: Multiplicative Inverses, CRT, and Efficient Computation

In this assignment you will solve several equations, and you will define a collection of Python functions for finding inverses of congruence classes, for solving systems of equations using the Chinese remainder theorem, and for employing CRT solutions. You must submit a single Python source file named hw3/hw3.py. Please follow the gsubmit directions.

You may import the following library functions in your module (you may not need all these functions for this assignment depending on how you approach the problems, but they may be used):

from math import floor
from fractions import gcd
        
You may also use the following built-in functions:
  • the pow() function can compute modular exponents efficiently (as in, ak mod n can be written in Python as pow(a,k,n));
  • the sum() function returns the sum of a list of integers (e.g., sum(1,2,3,4) returns 10).
Your file may not import any other modules or employ any external library functions associated with integers and sets unless explicitly permitted to do so in a particular problem.

Solutions to each of the programming problem parts below should be fairly concise. You will be graded on the correctness, concision, and mathematical legibility of your code. The different problems and problem parts rely on the lecture notes and on each other; carefully consider whether you can use functions from the lecture notes, or functions you define in one part within subsequent parts.
  1. Solve the following equations using step-by-step equational reasoning, and list each step. For this problem, you must use Fermat's little theorem and/or Euler's theorem to compute inverses of congruence classes. Your solutions for this problem should appear as comments, delimited using '''...''', in hw3.py. You may use the = ascii character to represent the ≡ relational operator on congruence classes.
    1. Solve the following equation for x ℤ/11ℤ:
      4 ⋅ x ≡ 2 (mod 11)
    2. Solve the following system of equations for x ℤ/35ℤ:
      x
      3 (mod 7)
      x
      1 (mod 5)
    3. Let p and q be unequal prime numbers. If p-1 ≡ 5 (mod q) and q-1 ≡ 3 (mod p), find the solution x ℤ/(pq)ℤ to the following system of equations (your solution should be in terms of p and q):
      x
      2 (mod p)
      x
      4 (mod q)
    4. Solve the following system of equations for a unique congruence class x, and specify the range of congruence classes in which this system of equations has a unique solution:
      x
      4 (mod 5)
      x
      3 (mod 14)
  2. Implement the following Python functions for computing multiplicative inverses of congruence classes.
    1. Implement a function invPrime(a, p) that takes two integers a and p > 1 where p is prime. The function should return the multiplicative inverse of a ℤ/pℤ (if a ≡ 0, it should return None). Your solution must use Fermat's little theorem.

      >>> [invPrime(i, 7) for i in range(0,7)]
      [None, 1, 4, 5, 2, 3, 6]
      >>> [invPrime(i, 13) for i in range(1,13)]
      [1, 7, 9, 10, 8, 11, 2, 5, 3, 4, 6, 12]
                    
    2. Include the following definition in your code. This non-recursive implementation of the extended Euclidean algorithm avoids a stack overflow error on large inputs.

      def egcd(a, b):
          (x, s, y, t) = (0, 1, 1, 0)
          while b != 0:
              k = a // b
              (a, b) = (b, a % b)
              (x, s, y, t) = (s - k*x, x, t - k*y, y)
      return (s, t)
                    
      Given two inputs a and b, egcd(a, b) returns a solution (s, t) to the following instance of Bézout's identity:
      as + bt
      =
      gcd(a, b)
      Using egcd(), implement a function inv(a, m) that takes two integers a and m > 1. If a and m are coprime, it should return the multiplicative inverse of a ℤ/mℤ. If a and m are not coprime, it should return None.

      >>> [inv(i, 13) for i in range(1,13)]
      [1, 7, 9, 10, 8, 11, 2, 5, 3, 4, 6, 12]
      >>> [inv(i, 8) for i in range(1,8)]
      [1, None, 3, None, 5, None, 7]
                    
  3. Implement the following Python functions for solving certain systems of equations involving congruence classes.
    1. Implement a function solveOne(c, a, m) that takes three integers c, a, and m ≥ 1. If c and m are coprime, the function should return the solution x {0, ..., m-1} to the following equation:
      cx
      a (mod m)
      If c and m are not coprime, the function should return None.

      >>> solveOne(3, 4, 7)
      6
      >>> solveOne(1, 5, 11)
      5
      >>> solveOne(2, 3, 8)
      None
                    
    2. Implement a function solveTwo(e1, e2) that takes two tuples e1 and e2 as inputs, each of the form (c, a, m) (i.e., containing three integer elements). Each tuple (c, a, m) corresponds to an equation of the form:
      cx
      a (mod m)
      Thus, the two tuples, if we call them (c, a, m) and (d, b, n), correspond to a system of equations of the form:
      cx
      a (mod m)
      dx
      b (mod n)
      The function solveTwo() should return the unique solution x to the above system of equations. If either equation cannot be solved using solveOne(), or n and m are not coprime, the function should return None.

      >>> solveTwo((3, 4, 7), (1, 5, 11))
      27
                    
    3. Implement a function solveAll(es) that takes a list of one or more equations, each of the form (c, a, m). The list corresponds to the system of equations (assume all the mi are mutually coprime):
      c1x
      a1 (mod m1)
      ckx
      ak (mod mk)
      The function solveAll() should return the unique solution x to the above system of equations. If any individual equation cannot be solved using solveOne(), or if the moduli are not all mutually coprime, the function should return None.

      >>> solveAll([(3,4,7), (1,5,11)])
      27
      >>> solveAll([(5,3,7), (3,5,11), (11,4,13)])
      856
      >>> solveAll([(1,2,3), (7,8,31), (3,5,7), (11,4,13)])
      7109
      >>> solveAll([(3,2,4), (7,8,9), (2,8,25), (4,4,7)])
      554
                    
  4. Suppose we represent the sum of a collection of exponentiation operations as a list of tuples, where each tuple contains two integers: the base and the exponent. For example, the list [(2,4),(3,5),(-6,3)] represents the sum of powers 24 + 35 + (−6)3.

    1. Implement a function sumOfPowers(nes, ps) that takes a list of one or more tuples nes (i.e., nes is of the form [(a1,n1),...,(ak,nk)]) as its first argument, and a list of one or more primes ps (i.e., of the form [p1,...,pm]) as its second argument. The function should return the correct result of the sum of powers as long as the following is true (e.g., on a computer with unlimited memory and time):
      0
      a1n1 + ... + aknk
      <
      p1 ⋅ ... ⋅ pm
      You may assume the second list contains distinct prime numbers. You may not assume that the numbers in the first input list have any particular patterns or relationships; they can be in any order, they can be of any size, and they may or may not share factors. Your implementation must work efficiently on very large inputs (e.g., with computations like 229999999999999999999999999999999996, as presented in the examples below).

      >>> sumOfPowers([(2,3), (5,6)], [3,5,7,11,13,17,19,23,29]) == 2**3 + 5**6
      True
      >>> primes =[\
               15481619,15481633,15481657,15481663,15481727,15481733,15481769,15481787 
              ,15481793,15481801,15481819,15481859,15481871,15481897,15481901,15481933 
              ,15481981,15481993,15481997,15482011,15482023,15482029,15482119,15482123 
              ,15482149,15482153,15482161,15482167,15482177,15482219,15482231,15482263 
              ,15482309,15482323,15482329,15482333,15482347,15482371,15482377,15482387 
              ,15482419,15482431,15482437,15482447,15482449,15482459,15482477,15482479 
              ,15482531,15482567,15482569,15482573,15482581,15482627,15482633,15482639 
              ,15482669,15482681,15482683,15482711,15482729,15482743,15482771,15482773 
              ,15482783,15482807,15482809,15482827,15482851,15482861,15482893,15482911 
              ,15482917,15482923,15482941,15482947,15482977,15482993,15483023,15483029 
              ,15483067,15483077,15483079,15483089,15483101,15483103,15483121,15483151 
              ,15483161,15483211,15483253,15483317,15483331,15483337,15483343,15483359 
              ,15483383,15483409,15483449,15483491,15483493,15483511,15483521,15483553 
              ,15483557,15483571,15483581,15483619,15483631,15483641,15483653,15483659 
              ,15483683,15483697,15483701,15483703,15483707,15483731,15483737,15483749 
              ,15483799,15483817,15483829,15483833,15483857,15483869,15483907,15483971 
              ,15483977,15483983,15483989,15483997,15484033,15484039,15484061,15484087 
              ,15484099,15484123,15484141,15484153,15484187,15484199,15484201,15484211 
              ,15484219,15484223,15484243,15484247,15484279,15484333,15484363,15484387 
              ,15484393,15484409,15484421,15484453,15484457,15484459,15484471,15484489 
              ,15484517,15484519,15484549,15484559,15484591,15484627,15484631,15484643 
              ,15484661,15484697,15484709,15484723,15484769,15484771,15484783,15484817 
              ,15484823,15484873,15484877,15484879,15484901,15484919,15484939,15484951 
              ,15484961,15484999,15485039,15485053,15485059,15485077,15485083,15485143 
              ,15485161,15485179,15485191,15485221,15485243,15485251,15485257,15485273 
              ,15485287,15485291,15485293,15485299,15485311,15485321,15485339,15485341 
              ,15485357,15485363,15485383,15485389,15485401,15485411,15485429,15485441 
              ,15485447,15485471,15485473,15485497,15485537,15485539,15485543,15485549 
              ,15485557,15485567,15485581,15485609,15485611,15485621,15485651,15485653 
              ,15485669,15485677,15485689,15485711,15485737,15485747,15485761,15485773 
              ,15485783,15485801,15485807,15485837,15485843,15485849,15485857,15485863]
      >>> sumOfPowers(\
             [(2,29999999999999999999999999999999996)\
             ,(-8,9999999999999999999999999999999999)\
             ,(2,29999999999999999999999999999999996)\
             ,(7,7),(-13,3)], primes)
      821346
                    
    2. Extra credit: Modify your implementation of sumOfPowers() so that it can handle inputs even if the exponents themselves are extremely large. You must use Euler's theorem to accomplish this; you may not assume that any particular patterns will exist in the bases or exponents.

      >>> sumOfPowers([(2,10**1000000 + 1), (-2,10**1000000 + 1), (3,3)], primes)
      27
                    



[link]  Review #1. Properties, Algorithms, and Applications of Modular Arithmetic

This section contains a comprehensive collection of review problems going over the course material covered until this point. Many of these problems are an accurate representation of the kinds of problems you may see on an exam.
Exercise: For some a ℕ, suppose that a-1 ℤ/21ℤ and a-1 ℤ/10ℤ (that is, a has an inverse in ℤ/21ℤ, and it also has an inverse in ℤ/10ℤ). Determine whether or not a has an inverse in ℤ/210ℤ. Explain why or why not. Hint: use gcd.
If a has an inverse in ℤ/10ℤ and ℤ/21ℤ, then gcd(a,10) = 1 and gcd(a,21) = 1. Since gcd(10,21) = 1, a shares no factors with the product 10 ⋅ 21 = 210, so gcd(a, 210) = 1. Thus, a must have a multiplicative inverse in ℤ/210ℤ.
Exercise: Bob is trying to implement a random number generator. However, he's distracted and keeps making mistakes while building his implementation.
  1. Bob begins his algorithm by generating two coprime numbers a and m such that gcd(a,m) = 1. However, he mixes them up and defines the following computation:
    [      (im) mod a      |      i {1,...,a-1}      ]
    Is Bob going to get a permutation? Why or why not?
    Yes, Bob will still get a permutation because gcd(a,m) = 1, which means m mod a is also coprime with m (we can see this because this is exactly what the extended Euclidean algorithm computes before making a recursive call, or by observing that if m mod a shares a factor with a, so must m).
  2. Bob notices part of his mistake and tries to fix his algorithm; he ends up with the following:
    [      (im) mod m      |      i {1,...,m-1}      ]
    How many distinct elements does the list he gets in his output contain?
    It contains exactly one element: 0 (mod m), since all multiples of the congruence class m are equivalent to 0 (mod m).
  3. Bob notices his algorithm isn't returning a permutation, but he mixes up a few theorems and attempts the following fix:
    [      (iam-1) mod m      |      i {1,...,m-1}      ]
    Bob tests his algorithm on some m values that are prime numbers. How many elements does the set he gets in his output contain?
    It contains all the elements {1,...,m-1}, generated in ascending order, since by Fermat's little theorem, am-1 ≡ 1 (mod m) if m is prime.
  4. Bob doesn't like the fact that his permutation doesn't look very random, so he moves the i term to the exponent:
    [      (ai ⋅ (m-1)) mod m      |      i {1,...,m-1}      ]
    Bob tests his algorithm on some m values that are prime numbers. How many elements does the set he gets in his output contain?
    It contains exactly one element: 1 (mod m), since by the consequences of Fermat's little theorem and Euler's theorem, if m is prime we have:
    ak
    ak mod φ(m) (mod m)
    ak mod (m-1) (mod m)
Exercise: Suppose you have the following instance of Bézout's identity: 2 ⋅ 3 + (−1) ⋅ 5 = 1. Solve the following system of equations:
x
2 (mod 3)
x
3 (mod 5)
Given 3-1 ≡ 2 (mod 5), and 5-1 ≡ −1 ≡ 2 (mod 3), the formula for the solution is:
x
2 ⋅ (5 ⋅ 5-1) + 3 ⋅ (3 ⋅ 3-1) (mod (3 ⋅ 5))
2 ⋅ (5 ⋅ 2) + 3 ⋅ (3 ⋅ 2)
20 + 18
38 (mod 15)
8 (mod 15)
Exercise: Solve the following system of equations:
x
2 (mod 7)
x
3 (mod 5)
To demonstrate an alternative but inefficient method to find a solution, we can list the positive members of the equivalence classes 2 ℤ/7ℤ and 3 ℤ/5ℤ that are less than 7 ⋅ 5 = 35. The one element that appears in both lists is the unique solution in ℤ/35ℤ to the above system.
2 + 7ℤ
=
{..., 2, 9, 16, 23, 30, ...}
3 + 5ℤ
=
{..., 3, 8, 13, 18, 23, 28, 33, ...}
Exercise: Determine the size of the following set:
{x | x ℤ/(11 ⋅ 13)ℤ,      x ≡ 5 mod 11,      x ≡ 7 mod 13}
By the Chinese remainder theorem, we know there exists exactly one solution in ℤ/(11 ⋅ 13)ℤ to the following system of equations:
x
5 (mod 11)
x
7 (mod 13)
Thus, the size of the set is 1.
Exercise: For a given y ℤ/(pq)ℤ where p and q are distinct primes, how many solutions does the following system of equations have:
x
y2 (mod p)
x
y2 (mod q)
The value y2 ℤ/(pq)ℤ is a constant with respect to x, so by the Chinese remainder theorem, there is exactly one solution x ℤ/(pq)ℤ to the above system.
Exercise: Determine the size of the following set:
{x | x ℤ/(11 ⋅ 13)ℤ,      s ℤ/11ℤ,      t ℤ/13ℤ,      xs mod 11,      xt mod 13 }
Consider the following set. Notice that the right-hand side of the comprehension is exactly the same as that of the above. The only difference is that the left-hand side x in the above expression has been replaced with (x, s, t).
{(x, s, t) | x ℤ/(11 ⋅ 13)ℤ, s ℤ/11ℤ, t ℤ/13ℤ, xs mod 11, xt mod 13 }
By the Chinese remainder theorem, we know that exactly one tuple (x, s, t) for each x ℤ/(11 ⋅ 13)ℤ will satisfy the conditions in the comprehension, because for each distinct pair (s, t), exactly one x ℤ/(11 ⋅ 13)ℤ will be a solution to the system of equations:
x
s (mod 11)
x
t (mod 13)
Thus, the conditions in the comprehension will be satisfied at least once for every x ℤ/(11 ⋅ 13)ℤ. Thus, the entire set is exactly the set ℤ/(11 ⋅ 13)ℤ, and it is the case that
|ℤ/(11 ⋅ 13)ℤ|
=
11 ⋅ 13
=
143
Exercise: Suppose that n ℕ is even and n/2 − 1 is odd. Determine the size of the following set:
{i ⋅ (n/2 - 1) mod n | i {0,...,n-1} }
We know that gcd(n/2 − 1, n/2) = 1. Since n/2 − 1 is odd, 2 is not a factor of n/2 − 1, so gcd(n/2 − 1, n) = 1. Thus, n/2 − 1 and n are coprime. Thus, the above set must be a permutation.
Exercise: For any n ℕ, let a ℤ/nℤ have an inverse a-1 ℤ/nℤ. Determine the size of the following set:
{ (ai) mod n | i ℤ/nℤ }
If a has an inverse in ℤ/nℤ (whether or not n is prime), then gcd(a, n) = 1, so the above set must be a permutation.
Exercise: Let p be a prime number. Compute the set size |ℤ/pℤ - (ℤ/pℤ)*|.
Since (ℤ/pℤ)* is the set of elements of ℤ/pℤ that have multiplicative inverses, it is the set of elements a ℤ/pℤ such that gcd(a, p) = 1. However, because p is prime, all of the elements in ℤ/pℤ except 0 have this property (since gcd(p, 0) = p; recall that 0/p ℤ and p|0). Thus,
ℤ/pℤ - (ℤ/pℤ)*
=
{0}
Thus, |ℤ/pℤ - (ℤ/pℤ)*| = 1.

Alternatively, we know that |ℤ/pℤ| = p and |(ℤ/pℤ)*| = φ(p) = p − 1, and p − (p − 1) = 1.
Exercise: In a game, you win if you can guess correctly whether a large number n is prime in under a minute (if you are wrong, you win nothing and you lose nothing). You are given a handheld calculator that can only perform addition, subtraction, multiplication, division, exponentiation, and modulus (the calculator can represent arbitrarily large numbers, and can provides quotients to any precision). Describe one strategy you can use to give yourself a high probability of winning.
If you pick a few random a {2,...,n-1} and compute an-1 mod n, if the result is ever greater than 1 (i.e., the Fermat primality test) then a would be a witness that n is composite. You would then be able to say with certainty that p is not prime, winning the game. Once time is about to run out, you should guess that the number is prime.
Exercise: Suppose that n ℕ. Compute the following:
534 ⋅ n + 1 mod 11
We can compute the exponent 34 ⋅ n + 1 modulo φ(11) by Euler's theorem, because gcd(5,11) = 1. Likewise, we can compute the exponent within the exponent, 4 ⋅ n + 1, modulo φ(φ(11)) because gcd(3,φ(11)) = 1. Thus, we have:
φ(11)
=
11 - 1
=
10
φ(10)
=
φ(5) ⋅ φ(2)
=
(5-1) ⋅ (2-1)
=
4
534 ⋅ n + 1 mod 11
=
534 ⋅ n + 1 mod φ(φ(11)) mod φ(11) mod 11
=
534 ⋅ n + 1 mod 4 mod 10 mod 11
=
531 mod 10 mod 11
=
53 mod 11
=
(25 ⋅ 5) mod 11
=
(3 ⋅ 5) mod 11
=
15 mod 11
=
4 mod 11
Exercise: Suppose we make the following simplifications:
  • the Earth rovolves around the sun once per year;
  • the asteroid Ceres rovolves around the sun every 5 years;
  • the planet Jupiter revolves around the sun every 11 years.
Suppose that on June 21st, 2000, the Earth, Ceres, and Jupiter all align (i.e., one can draw a straight line through all three).
  1. Which two of these objects will align again on June 21st, and in which year?
    The next time Earth and Ceres align will be in five years, in 2005, as x = 5 is the smallest non-zero solution in ℕ to the following system (the first equation represents alignment with Earth; the second equation represents alignment with Ceres):
    x
    0 mod 1
    x
    0 mod 5
  2. How many years will pass before all three align again?
    The smallest non-zero solution in ℕ to the following system is x = 55, so they will all align again in 2055:
    x
    0 mod 1
    x
    0 mod 5
    x
    0 mod 11
  3. Suppose that it is June 21st of some year between 2000 and 2055. At this time, there is no alignment. However, Jupiter aligned with earth on June 21st four years ago, and Ceres aligned with Earth on June 21st one year ago. What year is it?
    The following system of equations captures the situation. Since the solution must be in ℤ/55ℤ, we can find a unique solution using the Chinese remainder theorem. By inspection of the elements of 4 + 11ℤ = {4, 15, 26, ...}, the solution is x = 26.
    x
    0 mod 1
    x
    1 mod 5
    x
    4 mod 11
    Alternatively, we can use the formula; since x ≡ 0 (mod 1) is true for all x, there are actually only two equations:
    x
    1 mod 5
    x
    4 mod 11
    Since 11 ≡ 1 (mod 5), 11-1 ≡ 1 (mod 5); we also have:
    5-1
    59 (mod 11)
    254 ⋅ 5
    34 ⋅ 5
    5 ⋅ 3 ⋅ 5
    4 ⋅ 5
    9
    Then the solution is:
    x
    1 ⋅ (11 ⋅ 1) + 4 ⋅ (5 ⋅ 9) (mod (5 ⋅ 11))
    11 + 180
    191
    26
Exercise: Suppose there exist two devices, where one can either produce or consume exactly 2 units of power and another can either produce or consume exactly 7 units of power:
  • device A: +/− 2 units
  • device B: −/+ 7 units
Suppose we want to produce exactly 1 unit of power using a combination of some number of A devices and B devices. Is this possible?
Yes, this is possible because 2 and 7 are coprime, so there exists an instance of Bézout's identity of the form 2 ⋅ s + 7 ⋅ t = 1.
The following is a breakdown of what you should be able to do at this point in the course (and of what you may be tested on in an exam). Notice that many of the tasks below can be composed. This also means that many problems can be solved in more than one way.
  • problems that can be solved
    • generate a permutation of a set {1,...,m-1}
    • generate a "random" number using permutations
    • find the greatest common divisor of two relatively small integers
    • check if a number is prime
      • using an exhaustive search
      • using the Fermat primality test
    • generate a random number in a certain range
    • generate a random prime in a certain range
    • use random primes to...
      • to share information requiring cooperation using Shamir secret sharing
      • to store information on unreliable storage devices (using Shamir secret sharing)
      • to perform many arithmetic operations x1 ⊕ ... ⊕ xn for some operator ⊕ in sequence more efficiently (using CRT)
        • if the range of the final output is known
        • if working in parallel along multiple distinct pi in ℤ/piℤ, and then performing CRT once is less expensive than working in ℤ/nℤ the whole time
    • solving equations and performing computations
      • solve a system of linear equations where each equation is modulo some ni and the coefficient is coprime with the moduli
        • derive a system of equations from a word problem
          • rotating objects
          • objects that generate/consume different amounts of power
          • Shamir secret sharing and applications
        • solve a system with additive and multiplicative inverses
        • solve generalized cases (gcd(n,m) > 1 and/or c > 1 in cxa (mod n))
        • solve a system of three or four equations
      • compute exponents modulo n efficiently
        • using Euler's theorem and φ (when possible)
        • using the efficient repeated-squaring method
      • compute multiplicative inverses ℤ/n
        • using φ(n)
        • using a given output from the extended Euclidean algorithms or (equivalently) an instance of Bézout's identity
    • recognize when you cannot solve problems efficiently
      • computing φ(n) for an arbitrary n
      • factoring n for an arbitrary n

[link]  4. Computational Complexity of Modular Arithmetic Algorithms

[link]  4.1. Definition of computational problems and their complexity

Below, we review a small set of definitions and facts from complexity theory. We will only use these facts as they relate to problems in modular arithmetic and abstract algebra. A course on computational complexity theory would go into more detail.
Definition: Informally, for some formula f, we call a statement of the following form a problem:
  • "Given x, find y such that f(x, y) is true."
In the above, x can be viewed as the input describing the problem, and y can be viewed as the solution to the problem.
Definition: The computational complexity of a problem refers to the running time of the most efficient algorithm that can solve the problem.

[link]  4.2. Complexity of algorithms for solving tractable problems

In this subsection we consider the running time of efficient algorithms for performing common arithmetic operations (addition, subtraction, multiplication, exponentiation, and division). We consider the complexity of these arithmetic operations on each of the following domains:
  • unbounded positive integers;
  • integers modulo 2n;
  • integers modulo k for some k ℕ.
All of our arithmetic algorithms will operate on bit string representations of positive integers. A bit string representation such as
an-1...a0
is defined to represent the integer
2n-1an-1 + ... + 20a0
Below are some specific examples:
111
=
221 + 211 + 201
1101
=
231 + 221 + 210 + 201
10
=
211 + 200
Since the operations we consider usually take two arguments, we will follow the following conventions:
  • the first (left-hand side) input is x, an n-bit integer;
  • the second (right-hand side) input is y, an m-bit integer.
Thus, x ≤ 2n - 1 and y ≤ 2m - 1.
Algorithm: There exists an algorithm that can compute the sum of an n-bit integer x and an m-bit integer y in time O(max(n,m)+1). The size of the output is O(max(n,m)+1).
  1. addition of unbounded positive integers: n-bit integer x, m-bit integer y
    1. r (a bit vector to store the result)
    2. c := 0 (the carry bit)
    3. for i from 0 to max(n,m)-1
      1. r[i] := (x[i] xor y[i]) xor c
      2. c := (x[i] and y[i]) or (x[i] and c) or (y[i] and c)
    4. r[max(n,m)+1] := c
    5. return r
How can we use the addition algorithm to implement multiplication? One approach for multiplying two positive integers x, y ℕ is to do repeated addition of y (repeating the addition operations x times). However, if x is an n-bit integer, this would require up to 2n-1 addition operations, which would take exponential time in the representation size of the input x.

A more efficient approach is to use the representation of x as a sum of powers of 2, and to apply the distributive property. Suppose x is represented as the binary bit string an-1...a0. Then we have:
xy
=
(an-1 ⋅ 2n-1 + ... + a1 ⋅ 21 + a0 ⋅ 20) ⋅ y
=
an-1 ⋅ 2n-1y + ... + a1 ⋅ 21 + a0 ⋅ 20y
Notice that we have now rewritten multiplication as n-1 addition operations. The only other problem is how to multiply y by powers of 2. We can do so simply by appending a 0 to the bit string representation of y. Suppose y is represented as the binary bit string bn-1...b0. Then we have:
2 ⋅ y
=
2 ⋅ bn-1...b1b0
=
2 ⋅ (bn-1 ⋅ 2n-1 + ... + b1 ⋅ 21 + b0 ⋅ 20)
=
bn-1 ⋅ 2n + ... + b1 ⋅ 22 + b0 ⋅ 21
=
(bn-1 ⋅ 2n + ... + b1 ⋅ 22 + b0 ⋅ 21) + 0 ⋅ 20
=
bn-1...b1b00
Thus, our algorithm only needs to depend on addition, and on shifting bit strings left by one (a.k.a., appending a 0 to the bit string at the position of the least significant bit).
Algorithm: There exists an algorithm that can compute the product of an n-bit integer x and an m-bit integer y in time O(n ⋅ (max(n,m)+1+n)) or O(max(n,m)2). The size of the output is O(n+m) (because the shift left for the 21 case does not contribute to the final result, the m-bit integer is shifted left at most n-1 times, but there may still be a carried bit on the last addition operation performed).
  1. multiplication of unbounded positive integers: n-bit integer x, m-bit integer y
    1. r (a bit vector to store the result)
    2. for i from 0 to n-1
      1. if x[i] is 1
        1. r := r + y (using unbounded integer addition)
      2. shift the bits of y left by one bit (i.e., multiply y by 2)
    3. return r
Algorithm: There exists an algorithm that can compute the exponentiation xy of an n-bit integer x and an m-bit integer y in time O(n ⋅ 2m). The size of the output is O(n ⋅ 2m). Notice that this means that for unbounded integer outputs, the algorithm runs in exponential time.
  1. exponentiation of unbounded positive integers: n-bit integer x, m-bit integer y
    1. r (a bit vector to store the result)
    2. for i from 0 to m-1
      1. if y[i] is 1
        1. r := rx (using unbounded integer multiplication)
      2. x := xx (using unbounded integer multiplication)
    3. return r
Algorithm: There exists an algorithm that can compute the integer quotient ⌊ x / y ⌋ of an n-bit integer x and an m-bit integer y in time O((nn) + (n ⋅ (2 ⋅ n))) or O(n2).
  1. integer division of unbounded positive integers: n-bit integer x, m-bit integer y
    1. if y > x
      1. return 0
    2. for i from 0 to n-1
      1. shift y left by one bit
    3. r (a bit vector to store ⌊ x / y ⌋ ⋅ y)
    4. q (a bit vector to store the integer quotient)
    5. p := 2n-1 (to keep track of the current power of 2)
    6. for i from 0 to n-1
      1. if r+y < x
        1. r := r+y (using unbounded integer addition)
        2. q := q+p (using unbounded integer addition)
      2. shift y right by one bit
      3. shift p right by one bit
    7. return q
Algorithm: There exists an algorithm that can compute x mod y of an n-bit integer x and an m-bit integer y in time O(n2). This is accomplished by first performing integer division, then an integer multiplication, and then a subtraction. This corresponds to the formula for the modulus operation:
x mod y
=
x - ⌊ x/y ⌋ ⋅ y
When we consider the operations above as operating on integers modulo 2n (with results also in 2n), this corresponds to simply dropping any bits beyond the n least-significant bits when performing the computation.
Fact: There exists an algorithm that can compute the sum of two n-bit integers x and y in time O(n). The size of the output is O(n).
Fact: There exists an algorithm that can compute the product of two n-bit integers x and y in time O(n2). The size of the output is O(n).
Fact: There exists an algorithm that can compute xy for two n-bit integers x and y in time O(n3). The size of the output is O(n).
Fact: The recursive algorithm for gcd (and the extended Euclidean algorithm) makes O(log (max(x,y))) recursive calls on an integer inputs x ℕ and y ℕ. Notice that this means that the number of recursive calls is linear, or O(max(n,m)), for inputs consisting of an n-bit integer x and an m-bit integer y.
To see the above, consider the following fact: for any a ℕ, b ℕ, if ba then a mod b < (1/2) ⋅ a. Consider the two possibilities for a and b:
  • if b ≤ (1/2) ⋅ a, then ⌊ a / b ⌋ > 1, so:
    (a mod b)
    <
    b
    (1/2) ⋅ a
  • if b > (1/2) ⋅ a, then ⌊ a / b ⌋ = 1, so:
    a mod b
    =
    a - ⌊ a/b ⌋ ⋅ b
    =
    a - 1 ⋅ b
    =
    a - b
    <
    a - ((1/2) ⋅ a)
    <
    (1/2) ⋅ a
Thus, every time a mod b is computed in the algorithms, size of the second paramter is halved. Since every other invocation switches the two parameters, both parameters are halved. Thus, the number of invocations or iterations for an input m is log(m).
Fact: The recursive algorithm for the extended Euclidean algorithm on inputs consisting of an n-bit integer x and an m-bit integer y runs in time O(max(n,m) ⋅ (2 ⋅ max(n,m)2 + max(n,m))), or O(max(n,m)3). The number of recursive calls is about max(n,m), and each recursive call involves an integer division, a multiplication, and a subtraction.

If all inputs and outputs are integers that can be represented with at most n bits, the running time is then O(n3).
Fact: The following problem can be solved in polynomial time: given x (ℤ/nℤ)*, compute x-1. This can be reduced to running the extended Euclidean algorithm, which has a polynomial running time.

If all inputs and outputs are integers that can be represented with at most n bits, the running time is then O(n3).
Fact: There exists an O(max(n,m)3 + (n+m)2) algorithm that can solve the following system of two equations (for n-bit integers x,x' and m-bit integers y,y') using the Chinese remainder theorem:
s
x' (mod x)
s
y' (mod y)
This algorithm calls the extended Euclidean algorithm on x and y, and then performs four multiplications modulo (xy).

If all inputs and outputs are integers that can be represented with at most n bits, the running time is then O(n3).
Exercise: Multiply the following two numbers (represented in binary) using the multiplication algorithm presented in lecture: 1101101.
We can proceed by repeatedly shifting the first input left (corresponding to multiplication by 2), and multiplying each shifted version by a corresponding digit from the second input:
11011 + 110100 + 1101001
=
1101 + 110100
=
1000001

[link]  4.3. Complexity of (probably) intractable problems

In the previous section we saw that addition, subtraction, multiplication, exponentiation, and division (both integer division, modulus, and multiplication by multiplicative inverses) can all be computed efficiently (i.e., in polynomial time) both over integers and over congruence classes. It is also possible to efficiently compute roots and logarithms of integers (we omit proofs of this fact in this course). However, no efficient algorithms are known for computing roots and logarithms of congruence classes.
Definition: A problem can be solved in polynomial time iff there exists for some constant c an algorithm that solves all instances of the problem in time O(nc). The set of all problems that can be solved in polynomial time is called P, and if a problem can be solved in polynomial time, we say that the problem is in P.
Definition: A problem can be solved in exponential time iff there exists an algorithm that solves all instances of the problem in time O(2n).
Definition: There exists a polynomial-time reduction from a problem A to a problem B iff there exists a polynomial-time algorithm that can convert any instance of problem A into an instance of problem B (i.e., convert an input for A into an input for B, and convert the output from B into an output from A).
A polynomial-time reduction from one problem to another can be viewed as two separate polynomial-time algoritms: a conversion algorithm that takes inputs to problem A and invokes a solver for problem B some polynomial number of times, and a conversion algorithm that takes all the outputs obtained from the solver for problem B and assembles and/or converts them into outputs for problem A.
solver for
problem B


conversion
from output(s) from
B to output from A
⇑⇑⇑
conversion
from input for
A to input(s) for B
solver for
problem A
We can summarize the above diagram by simply saying that problem A reduces to problem B.
problem B problem A
We have already seen examples of such reductions. For example, a CRT solver for two equations makes a single call to the extended Euclidean algorithm. Thus, there exists a polynomial-time reduction from the problem of solving a two-equation system using CRT to the problem of computing multiplicative inverses.
finding
multiplicative
inverses
solving two-equation
systems using CRT
Fact: If there exists a polynomial-time reduction from problem A to problem B, and problem A is not in P (i.e., there exists no polynomial-time algorithm to solve A), then problem B must not be in P, either.

To see why B cannot be in P, we can present a proof by contradiction. Suppose that there does exist a polynomial-time algorithm to solve problem B. Then the polynomial-time reduction from A to B can invoke a polynomial-time algorithm. But then the reduction and algorithm for B working together will constitute a polynomial-time algorithm to solve A. Then it must be that A is in P. But this contradicts the fact that A is not in P, so no such polynomial-time algorithm for B could exist.
The above fact allows us to make conclusions about the computational complexity of certain problems based on their relationships (in terms of implementation) to other problems.
problem B

premise:
can be solved in
polynomial time

B P
problem A

conclusion:
can be solved in
polynomial time

A P
problem B

conclusion:
cannot be solved in
polynomial time

BP
problem A

premise:
cannot be solved in
polynomial time

AP
Intuitively, we can imagine that if problem A is "attached to" (i.e., depends on) problem B, an "easy" problem B will "pull" A down into the set of easily solvable problems P, while a "difficult" problem A will "pull" problem B into the set of hard-to-solve problems.
Conjecture (factoring): The following problem is not in P: given any integer n ℕ where n = pq and p and q are prime, find p and q.
Fact: Suppose that n = pq for two primes p ℕ and q ℕ. Given only n and φ(n), it is possible to compute p and q. Consider the following:
φ(n)
=
(p − 1) ⋅ (q − 1)
φ(n)
=
pqpq + 1
φ(n)
=
n - pq + 1
φ(n) - n
=
- pq + 1
φ(n) - n - 1
=
pq
Thus, it is sufficient to solve the following system of equations for p and q:
n
=
pq
φ(n) - n - 1
=
pq
Example: Suppose that n = 15 and φ(n) = 8. Factor n.

We can plug n and φ(n) into the system of equations derived in the applicable fact:
15
=
pq
8 − 15 − 1
=
pq
With two equations and two unknowns, we can now solve for p and q:
8 − 15 − 1
=
pq
p
=
15 − 8 + 1 − q
=
8 − q
15
=
(8 − q) ⋅ q
0
=
q2 + 8q − 15
0
=
q2 − 8q + 15
At this point, we use the quadratic equation:
q
=
1/2 ⋅ (8 ± √(64 − 4(1)(15)))
q
=
1/2 ⋅ (8 ± √(4))
q
=
1/2 ⋅ (8 ± 2)
q
{3, 5}
{p, q}
=
{3, 5}
Conjecture (computing φ): The following problem is not in P: given any integer n ℕ where n = pq and p and q are prime, find φ(n).

If we can compute φ(n), then we can compute p and q. If computing φ(n) were any easier than factoring n (e.g., if we had a polynomial-time algorithm for computing φ(n)), then our claim about the hardness of factoring n would be a contradiction. In other words, factoring n can be reduced to solving φ(n).
computing φ(n)

conclusion:
cannot be solved in
polynomial time

computing φ(n) ∉ P
factoring n

conjecture:
cannot be solved in
polynomial time

factoring ∉ P
The above fact (i.e., that if factoring n is not in P, then neither is computing φ(n)) holds for arbitrary n, not just a product of two primes. However, the proofs in those cases are more sophisticated [Shoup].

Suppose we are given the following equation:
xy
=
z
There are three computational questions we could ask about the above equation:
  • given x and y, compute z (this is the exponentiation operation);
  • given x and z, compute y (this is the logarithm operation, since we have logx z = y in an equivalent notation);
  • given y and z, compute x (this is the yth root operation, since we have y√(z) = x in an equivalent notation).
We have efficient algorithms for computing all three of the above if x, y, and z are all integers or real numbers. Suppose we instead consider the following equation for some n ℕ:
xy
z (mod n)
In other words, we can interpret the equation as a congruence of equivalence classes in ℤ/nℤ. In this case, we already know that the first operation (exponentiation) has an efficient implementation because exponentiation and modulus are both efficient operations. However, we believe that the other two operations (computation of logarithms and roots of congruence classes) are computationally difficult (no polynomial-time algorithm exists to compute solutions).
Conjecture (RSA problem): The following problem is not in P: compute m given only the following:
n
=
pq
    
for two primes p and q in ℕ
e
ℤ/φ(n)ℤ
    
where e ≥ 3
c
=
me mod n
    
for an unknown m ℤ/n
Notice that the RSA problem is analogous to computing the eth root of c in ℤ/nℤ:
e√(c) (mod n)
=
e√(me) (mod n)
=
m (mod n)
Note also that this can be accomplished by first finding φ(n) and then computing the inverse of e, but this is as difficult as factoring n, and we assume that is not in P. Is there another way to compute m? We do not know, but we assume that there is no other faster (i.e., polynomial-time) way to do so.
Conjecture (discrete logarithm assumption): The following problem is not in P: compute e given only the following:
n
m
{1,...,n − 1}
c
=
me mod n
    
for an unknown e
Notice that this is analogous to computing the logarithm of a value c in ℤ/nℤ with respect to a known base m:
logm (c)
=
logm (me)
=
e
Note that the RSA problem requires that e ≥ 3, so it does not include the problem of computing square roots. The last intractable problem we will consider in this section is the problem of computing square roots of congruence classes within ℤ/nℤ for some n ℕ. We examine this problem separately from the RSA problem because it is possible to reduce factoring directly to the problem of computing square roots (while there is currently no known deterministic polynomial-time reduction from the factoring problem to the RSA problem).

Before we formally define the problem of computing square roots in ℤ/nℤ, we must first introduce some concepts and facts. This is because the problem of computing square roots in ℤ/nℤ is different from the problem of computing square roots of integers in ℕ, and this difference is likely what makes it computationally more difficult.
Definition: Given some n ℕ and some y ℤ/nℤ, we say that y is a quadratic residue in ℤ/nℤ if there exists x ℤ/nℤ such that x2y.
Example: Let us find the quadratic residues in ℤ/7ℤ:
02 ≡ 0 (mod 7)
12 ≡ 1 (mod 7)
22 ≡ 4 (mod 7)
32 ≡ 2 (mod 7)
42 ≡ 2 (mod 7)
52 ≡ 4 (mod 7)
62 ≡ 1 (mod 7)
The quadratic residues in ℤ/7ℤ are 0, 1, 2, and 4. Notice that 3, 5, and 6 are not quadratic residues in ℤ/7ℤ. Thus, the equations x2 ≡ 3, x2 ≡ 5, and x2 ≡ 6 have no solution in ℤ/7ℤ.
Example: Consider the set of congruence classes ℤ/5ℤ. We have that:
02 ≡ 0 (mod 5)
12 ≡ 1 (mod 5)
22 ≡ 4 (mod 5)
32 ≡ 4 (mod 5)
42 ≡ 1 (mod 5)
Notice that 2 and 3 are not quadratic residues in ℤ/5ℤ. Thus, neither x2 ≡ 2 nor x2 ≡ 3 have solutions in ℤ/5ℤ.
Fact: Given some n ℕ and some y ℤ/nℤ, if y and n are coprime and y is a non-zero quadratic residue in ℤ/nℤ then there exist at least two a,b ℤ/nℤ such that ab, a2y, and b2y.

Note that this is analogous to square roots in ℤ (since √(z) ℤ and −√(z) ℤ are both square roots of z ℤ if they exist).

We can prove this fact in the following way: suppose that y is a quadratic residue. Then there exists at least one x ℤ/nℤ such that:
x2 mod n
=
y
But this means that (nx) ℤ/nℤ is such that:
((nx)2) mod n
=
(n2 − (2 ⋅ nx) + x2) mod n
=
x2 mod n
=
y mod n
Thus, x and (nx) are both roots of y.
Example: It is the case that 4 ℤ/5ℤ is a quadratic residue in ℤ/5ℤ, with two roots 2 and 3:
22 mod 5
=
4
32 mod 5
=
9 mod 5
=
4
Example: Consider 0 ℤ/3ℤ. We have that:
02 ≡ 0 (mod 3)
12 ≡ 1 (mod 3)
22 ≡ 1 (mod 3)
Thus, x2 ≡ 0 has exactly one solution in ℤ/3ℤ.
Fact: Let p ℕ be a prime such that p mod 4 = 3, and suppose that y ℤ/pℤ. Then y has either 0, 1, or 2 roots in ℤ/pℤ.
Example: Suppose we want to solve the following equation for x ℤ/7ℤ:
x2
3 (mod 7)
Suppose we start by squaring both sides:
x4
32 (mod 7)
We can then use Euler's theorem to add any multiple of φ(7) to the exponent:
x4
32 ⋅ 1 (mod 7)
x4
32 ⋅ 3φ(7)
x4
32 + φ(7)
Since 7 is prime, φ(7) must be even, so 2 + φ(7) is also even. Thus, we can divide the exponent by 2 on both sides.
x2
3(2 + φ(7))/2
Furthermore, since 7 ≡ 3 (mod 4), we know that 2 + φ(7) is a multiple of 4. Thus, we can actually divide both exponents by 4:
x
3(2 + φ(7))/4
Thus, we have found x as a power of the original quadratic residue 3.
Fact: Let p ℕ be a prime such that p mod 4 = 3, and suppose that y ℤ/pℤ is a quadratic residue with two roots in ℤ/pℤ. Then we can compute the roots using the following formula:
x ≡ ± y(p+1)/4 (mod p)
In fact, if the modulus n is not prime, there may exist more than two roots of a value in ℤ/nℤ.
Example: It is the case that 1,-1,6,-6 ℤ/35ℤ are all square roots of 1 ℤ/35ℤ:
12 mod 35
=
1
(-1)2 mod 35
=
342 mod 35
=
1156 mod 35
=
((33 ⋅ 35)+1) mod 35
=
1 mod 35
62 mod 35
=
36 mod 35
=
1 mod 35
(-6)2 mod 35
=
292 mod 35
=
841 mod 35
=
((24 ⋅ 35)+1) mod 35
=
1 mod 35
Example: Suppose we are given an instance of the congruent squares problem where y = 2 and n = 15. We want to find x ℤ/15ℤ such that x ≢ ± y but x2y2 ≡ 22 ≡ 4. Notice that we have that:
y
2 mod 3
y2
22 mod 3
1 mod 3
(3-y)2
12 mod 3
1 mod 3
Notice also that we have that:
y
2 mod 5
y2
22 mod 5
4 mod 5
(5-y)2
32 mod 5
4 mod 5
Thus, the square roots of 4 in ℤ/3ℤ are 1 and 2, and the square roots of 4 in ℤ/5ℤ are 2 and 3. We can then apply the Chinese remainder theorem to every pair of combinations:
r1
1 mod 3
r1
2 mod 5
r1
7 mod 15
r2
2 mod 3
r2
2 mod 5
r2
2 mod 15
r3
1 mod 3
r3
3 mod 5
r3
13 mod 15
r4
2 mod 3
r4
3 mod 5
r4
8 mod 15
Thus, x = 8 and x = 7 are solutions to x ≢ ± 2 and x2 ≡ 4.
Fact (Hensel's lemma): Let p ℕ be a prime number greater than 2, and let k ℕ be any positive integer (i.e., k ≥ 1). Suppose that x and p are coprime, and that x ℤ/pkℤ can be squared to obtain some quadratic residue r ℤ/pkℤ:
x2r (mod pk)
We can compute y ℤ/pk+1ℤ such that:
y2r (mod pk+1)
We compute it as follows. First, we compute c using the following formula:
c
x-1 ⋅ 2-1 ⋅ ((r - x2) / pk) (mod p)
Then, we have that:
y
x + cpk
To see why Hensel's lemma is true, suppose that we have that:
x2
r (mod pk)
Notice that if it is possible to "lift" x to a root of r in ℤ/pk+1ℤ, the only possibility is that this new root y has an additional multiple of pk. Thus, it must be that for some integer multiple c, we have:
y
=
x + cpk
We can then substitute:
y2
r (mod pk+1)
(x + (cpk))2
r (mod pk+1)
But we can simplify the above equation:
(x + (cpk))2
r (mod pk+1)
x2 + (2 ⋅ xcpk) + (c2p2k)
r (mod pk+1)
But notice that the third term on the left-hand side in the above equation is equivalent to the congruence class 0 + pk+1ℤ:
c2p2k
0 (mod pk+1)
Thus, we have:
x2 + (2 ⋅ xcpk)
r (mod pk+1)
(x2r) + (2 ⋅ xcpk)
0
The above can be rewritten using the divisibility predicate as:
pk+1
|
(x2r) + (2 ⋅ xcpk)
Thus, we can divide both sides of the above relationship by pk to obtain:
p
|
(x2r)/(pk) + (2 ⋅ xc)
We can then rewrite the above as an equation of congruence classes:
(x2r)/(pk) + (2 ⋅ xc)
0 (mod p)
(2 ⋅ xc)
− (x2r)/pk
c
x-1 ⋅ 2-1 ⋅ (− (x2r)/pk)
c
x-1 ⋅ 2-1 ⋅ ((rx2)/pk)
Thus, we have derived the formula in Hensel's lemma.
Example: To better understand Hensel's lemma, we can derive the lemma for a particular example. Let us start with the following equation:
42
2 (mod 7)
Suppose we want to find y ℤ/72ℤ such that:
y2
2 (mod 49)
We know that the difference between 4 and y must be a multiple of 7, so we write:
y
=
4 + 7 ⋅ c
Then we proceed:
y2
2 (mod 49)
(4 + 7 ⋅ c)2
2 (mod 49)
42 + (2 ⋅ 7 ⋅ c ⋅ 4) + (49 ⋅ c2)
2 (mod 49)
42 + (2 ⋅ 7 ⋅ c ⋅ 4)
2 (mod 49)
We simplify further to compute c:
(42 - 2) + (2 ⋅ 7 ⋅ c ⋅ 4)
0 (mod 49)
14 + (2 ⋅ 7 ⋅ c ⋅ 4)
0 (mod 49)
The above can be rewritten using the divisibility predicate as:
49 | 14 + (2 ⋅ 7 ⋅ c ⋅ 4)
7 | 2 + (2 ⋅ c ⋅ 4)
We can again rewrite the above as an equation of congruence classes:
2 + (2 ⋅ c ⋅ 4)
0 (mod 7)
2 ⋅ c ⋅ 4
− 2 (mod 7)
2 ⋅ c ⋅ 4
5 (mod 7)
c
2-1 ⋅ 4-1 ⋅ 5 (mod 7)
c
4 ⋅ 2 ⋅ 5 (mod 7)
c
5 (mod 7)
Thus, we have:
y
4 + 7 ⋅ 5 (mod 49)
y
39 (mod 49)
Since 49 − 39 = 10, we have:
y
± 10 (mod 49)
y2
2 (mod 49)
Example: We want to find both solutions y ℤ/121ℤ to:
y2
5 (mod 121)
Since 121 = 112, we have p = 11, k = 1, and r = 5. We begin by finding x ℤ/11ℤ such that:
x2
5 (mod 11)
Since 11 3 + 4ℤ, we can use an explicit formula:
x
± 5(11+1)/4 (mod 11)
± 53
± 3 ⋅ 5
± 4
Thus, it is sufficient to lift the solution 4 ℤ/11ℤ to a solution in ℤ/121ℤ using Hensel's lemma. We compute c:
c
x-1 ⋅ 2-1 ⋅ ((rx2)/pk) (mod p)
4-1 ⋅ 2-1 ⋅ ((5 − 16)/11) (mod 11)
4-1 ⋅ 2-1 ⋅ (− 1)
3 ⋅ 6 ⋅ (− 1)
(− 18)
4
Thus, we have:
y
4 + 4 ⋅ 11 (mod 121)
4 + 44
48
Thus, we have the solution
y
± 48 (mod 121)
Example: The distance travelled by an object that is at rest at time t = 0 and then immediately begins accelerating at 4 meters/second2 (i.e., the speed of the object increases by the quantity 4 meters/second every second) can be defined in terms of time in second t as:
d
=
1/2 ⋅ 4 ⋅ t2
We might expect an object to behave this way if it is being pulled by gravity, or if it is using a stable propulsion engine (e.g., a rocket).

Suppose we are using a range ambiguity resolution technique to track the distance the object has travelled. If it a particular moment, we know that the distance from the object is in the congruence class 10 + 11ℤ, what can we say about the amount of time t that has elapsed since the object started moving?

Since the distance is in 10 + 11ℤ, we can say:
d
10 (mod 11)
1/2 ⋅ 4 ⋅ t2
10
2 ⋅ t2
10
We know that 2-1 ≡ 6 (mod 11), so we multiply both sides of the above equation to obtain:
t2
60 (mod 11)
t2
5 (mod 11)
Thus, we can compute:
t
5(11+1)/4 (mod 11)
t
53
t
3 ⋅ 5
t
4 (mod 11)
Thus, we can say that the amount of time that has elapsed is in 4 + 11ℤ.
Example: Solve the following system of equations for x ℤ/21ℤ (find all solutions):
x2
1 (mod 3)
x2
1 (mod 7)
We know that there is exactly one solution y ℤ/21ℤ to the following system:
y
1 (mod 3)
y
1 (mod 7)
The solution is simply y = 1, and since there is only one solution, this is the only possibility. Thus, we are looking for all the solutions to the following equation:
x2
1 (mod 21)
Since 3 mod 4 = 7 mod 4 = 3, we know that there are two solutions to each of the following equations:
x2
1 (mod 3)
x2
1 (mod 7)
The solutions are as follows:
x
1 (mod 3)
x
2 (mod 3)
x
1 (mod 7)
x
6 (mod 7)
Taking every pair of combinations with one solution from ℤ/3ℤ and one solution from ℤ/7ℤ, we get:
x1
1 (mod 3)
x1
1 (mod 7)
x1
1 (mod 21)
x2
2 (mod 3)
x2
1 (mod 7)
x2
8 (mod 21)
x3
1 (mod 3)
x3
6 (mod 7)
x3
13 (mod 21)
x4
2 (mod 3)
x4
6 (mod 7)
x4
20 (mod 21)
Example: How many solutions x ℤ/(33 ⋅ 35)ℤ does the following system of equations have:
x2
4 (mod 33)
x2
4 (mod 35)
We know that each of the following equations have two solutions (2 and -2 in the respective sets). Notice that 4 mod 3 = 1.
x2
1 (mod 3)
x2
4 (mod 11)
x2
4 (mod 5)
x2
4 (mod 7)
Thus, there are two possible choices for each of the variables r1 {-2,2}, r2 {-2,2}, r3 {-2,2}, r4 {-2,2}, so there are 2 ⋅ 2 ⋅ 2 ⋅ 2 = 24 = 16 possible systems of the form:
x
r1 (mod 3)
x
r2 (mod 11)
x
r3 (mod 5)
x
r4 (mod 7)
Each system has a unique solution because the tuple (r1, r2, r3, r4) is unique, so there are 16 solutions for x in ℤ/(33 ⋅ 35)ℤ. Alternatively, we could break the problem down into two subproblems. First, we solve the following equation:
x2
4 (mod 33)
We obtain four distinct solutions (r1, r2, r3, r4) in ℤ/33ℤ. Next, we solve the following equation:
x2
4 (mod 35)
We then have four distinct solutions in (s1, s2, s3, s4) ℤ/35ℤ. Since gcd(33,35) = 1, we can then take any combination of solutions ri and si and set up the system:
x
ri (mod 33)
x
si (mod 35)
There will be exactly one solution to each of the above systems. There are 42 = 16 distinct systems, so there will be 16 distinct solutions.
We can summarize everything we know about computing square roots of congruence classes as follows. Suppose we want to find all solutions to the equation x2a (mod n) for some n ℕ and some a ℤ/nℤ.
  • If n is a prime p, the possibilities are:
    • a is not a quadratic residue in ℤ/pℤ, so there are no solutions to the equation,
    • a ≡ 0, in which case x ≡ 0 is the one and only solution to the equation,
    • a is a quadratic residue in ℤ/pℤ, so there are exactly two solutions to the equation, ± x ℤ/pℤ.
  • If n is prime power pk+1 and a is coprime with p, the possibilities are:
    • a is not a quadratic residue in ℤ/pkℤ, so it is not a quadratic residue in ℤ/pk+1ℤ;
    • a is a quadratic residue in ℤ/pkℤ, and both square roots of a in ℤ/pkℤ can be "lifted" to ℤ/pk+1ℤ using Hensel's lemma.
  • If n is a product of two coprime numbers k and m, then there is a solution in ℤ/nℤ for every possible combination of y and z such that:
    y2
    a mod k
    z2
    a mod m
    Each combination corresponds to a solution x ℤ/nℤ defined using CRT as:
    x
    y mod k
    x
    z mod m
Let us consider the problem of finding all of the square roots of a member of ℤ/nℤ. Notice that this problem is analogous to computing all the square roots of y in ℤ/nℤ:
√(y)
=
± x
The main difference is that the number of square roots may be greater than 2. This problem is believed to be computationally difficult (i.e., no algorithm in P exists that can solve the problem). In fact, even finding just one additional square root is believed to be computationally difficult.
Conjecture (congruent squares): The following problem is not in P: given n = pq for two primes p and q in ℕ and y ℤ/nℤ, find an x ℤ/nℤ such that x2y2 but x ≢ ± y.

Factoring can be reduced to finding congruent squares. Suppose we want to factor n. We find x and y such that:
x2 mod n
=
y2 mod n
0 mod n
=
(x2y2) mod n
=
((x + y) ⋅ (xy)) mod n
n
|
(x + y) ⋅ (xy)
Since n cannot divide (x+y) (because x ≢ ± y, so x + yn), and it cannot divide (x-y) (since (x+y) < n), and (x-y) ≠ 0 (since x ≢ ± y), it must be that n shares factors with both (x+y) and (x-y). Thus, it must be that either gcd(n,x + y) or gcd(n,x - y) is a non-trivial factor of n, and this can be computed efficiently.
The following diagram summarizes the relationships between the problems that are conjectured to be intractable (i.e., not in P). Each directed edge represents that there exists a polynomial-time reduction from the source problem to the destination problem. All of the nodes in the graph are conjectured to be not in P.
congruent squares
(square roots of
congruence classes)
computing φ(n)
for n = pq

factoring
n = pq
RSA problem
(eth roots of
congruence classes)
discrete logarithm
(logarithms of
congruence classes)

[link]  4.4. Applications of intractability

The computational intractability of certain problems in modular arithmetic makes it possible to address some practical security issues associated with implementing communication protocols. In particular, it helps address two common problems:
  • parties must communicate over a public communications channel, so everything they send is visible both to their receiver and to anyone that may be eavesdropping;
  • parties trying to communicate cannot physically meet to agree on shared secret information before communicating.
Protocol (hard-to-forge identification with meeting): Suppose Alice and Bob know that Alice will need to send Bob a single message at some point in the future. However, it is possible that Eve might try to impersonate Alice and send a message to Bob while pretending to be Alice.

In order to help Bob confirm that a message is truly from Alice (or to determine which message is from Alice given multiple messages), Alice and Bob meet in person and agree on a secret identifier s. When Alice decides to send a message m to Bob, she will send (m, s). Bob can then compare s to his own copy of s and confirm the message is from Alice.

Eve's only attack strategy is to try and guess s. As long as Alice and Bob choose s from a very large range of integers, the probability that Eve can guess s correctly is small.
A major flaw in the above identification protocol is that Alice and Bob must first meet in person to agree on a secret. Can Alice and Bob agree on a secret without meeting in person?
Protocol (hard-to-forge identification without meeting): Suppose Alice and Bob know that Alice will need to send Bob a single message at some point in the future. Alice prepares for this by doing the following:
  • choose two large primes p and q at random;
  • compute n = pq;
  • send the public identifier n to Bob over a public/non-secure communication channel.
When Alice is ready to send her message m to Bob, Alice will send (m, (p, q)), where (p, q) is the private identifier. Bob can confirm that pq = n, at which point he will know Alice was the one who sent the message.
Conjecture (forging identification): The following problem is not in P: in the previously defined protocol, given a public identifier n, compute the private identifier (p, q).

If it were possible to quickly (i.e., in polynomial time) compute the private identifier, then it would be easy to forge the identity of a sender by recording their public identifier n. However, suppose that this forging computation could be used as a subprocedure in a factoring algorithm. Then it would be possible to implement an efficient factoring algorithm. In other words, factoring n can be reduced to forging a private identifier. Thus, the problem of forging a private identifier must not be in P (i.e., the fastest algorithm that exists for forging an identity is not in P, which means there is no polynomial-time for forging an identity).
forging private
identifier for n

conclusion:
cannot be solved in
polynomial time

forging ∉ P
factoring n

conjecture:
cannot be solved in
polynomial time

factoring ∉ P
Note that the reduction also works in the other direction: an algorithm for factoring can be used for forging an identity. However, this proves nothing about the difficulty of forging! Just because forging can be reduced to an inefficient algorithm for factoring does not mean that there does not exist some other algorithm for forging that does not rely on factoring.
alternative
efficient
algorithm
forging private
identifier for n
factoring n
The above identification protocol improves over the previous protocol because it allows Alice and Bob to agree on an identifier for Alice without meeting in person. Its security relies on the fact that it is unlikely that Eve is capable of forging identifiers because her ability to forge would solve a problem that we believe is very difficult to solve.

However, the protocol still has many other flaws. For example, Eve could preempt Alice and send her own signature n before Alice has a chance to send her signature to Bob. A more thorough examination of such protocols is considered in computer science courses focusing explicitly on the subject of cryptography.
Protocol (Diffie-Hellman key exchange): We introduce the Diffie-Hellman key exchange protocol. This protocol is useful if two parties who cannot meet physically want to agree on a secret value that only they know.
  • Public key generation (performed by one party):
    1. Randomly choose a public large prime number p ℕ and an element g ℤ/pℤ.
  • Private key generation (performed by both parties):
    1. Party A randomly chooses a secret a ℤ/φ(p)ℤ.
    2. Party B randomly chooses a secret b ℤ/φ(p)ℤ.
  • Protocol:
    1. Party A computes (ga mod p) and sends this public value to party B.
    2. Party B computes (gb mod p) and sends this public value to party A.
    3. Party A computes (gb mod p)a mod p.
    4. Party B computes (ga mod p)b mod p.
    5. Since multiplication over ℤ/φ(p)ℤ is commutative, both parties now share a secret gab mod p.
This protocol's security only relies on the discrete logarithm assumption.
It is not known whether the discrete logarithm problem is related to the factoring problem. Factoring can be reduced using a probabilistic approach to the discrete logarithm problem modulo pq.
Protocol (RSA protocol): We introduce the RSA public-key cryptographic protocol. This protocol is useful in many scenarios, such as the following:
  • a sender wants to send the receiver a secret message over a public channel;
  • a receiver wants to allow any number of senders to send him messages over a public channel, and the receiver does not yet know who the senders will be.
This protocol can also be used to prove the identity of the receiver.
  • Key generation (performed by the receiver):
    1. Randomly choose two secret prime numbers p ℕ and q ℕ of similar size.
    2. Compute a public key value n = pq.
    3. Compute the secret value φ(n) = (p-1) ⋅ (q-1).
    4. Choose a public key value e {2,...,φ(n)-1} such that gcd(e, φ(n)) = 1.
    5. Compute the secret private key d = e-1 mod φ(n)
  • Protocol (encryption and decryption): There are two participants: the sender and the receiver.
    1. The sender wants to send a message m {0,...,n-1} where gcd(m,n) = 1 to the receiver.
    2. The receiver reveals the public key (n,e) to the sender.
    3. The sender computes the ciphertext (encrypted message) c = me mod n.
    4. The sender sends c to the receiver.
    5. The receiver can recover the original message by computing m = cd mod n.
The above encryption-decryption process works because for some k ℤ:
ed
1 (mod φ(n))
=
1 + φ(n) ⋅ k
(me)d mod n
=
(m1 + φ(n) ⋅ k) mod n
=
(m ⋅ (mφ(n) ⋅ k)) mod n
=
(m ⋅ (mφ(n) ⋅ k)) mod n
=
(mmφ(n)) mod n
=
(m ⋅ 1) mod n
=
m mod n
Besides the message m, there are three pieces of secret information that an eavesdropper cannot know in order for the encryption to provide any privacy:
  • p and q
  • φ(n)
  • d = e-1
Notice that if an eavesdropper knows p and q where n = pq, the eavesdropper can easily compute φ(n) (which was supposed to be private). If the eavesdropper can compute φ(n), then they can use the extended Euclidean algorithm to compute the inverse d = e-1 of the public key value e. They can then use d to decrypt messages.

Suppose the eavesdropper only knows φ(n). Then the eavesdropper can compute d and decrypt any message. In fact, the eavesdropper can also recover p and q.
Protocol (Rabin cryptosystem): We introduce the Rabin cryptosystem protocol. It is similar to the RSA scheme, but it does not rely on the difficulty of the RSA problem.
  • Key generation (performed by the receiver):
    1. Randomly choose two secret prime numbers p ℕ and q ℕ of similar size.
    2. Compute a public key value n = pq.
  • Protocol (encryption and decryption): There are two participants: the sender and the receiver.
    1. The sender wants to send a message m {0,...,n-1} to the receiver.
    2. The receiver reveals the public key n to the sender.
    3. The sender computes the ciphertext (encrypted message) c = m2 mod n.
    4. The sender sends c to the receiver.
    5. The receiver can recover the original message by computing √(c) in ℤ/pℤ and ℤ/qℤ, and then finding the four solutions to the following system by using the Chinese remainder theorem:
      m
      √(c) mod p
      m
      √(c) mod q.
Notice that the receiver must guess which of the square roots corresponds to the original message. Also notice that it is not a good idea to encrypt messages in the ranges {0, ..., √(n)} and {n − √(n), ..., n − 1} because it is easy to decrypt such messages by computing the integer square root √(c) of c and then confirming that √(c)2c (mod n).
The following diagram summarizes the relationships between algorithms that might break each of the protocols presented in this section, and existing problems that are believed not to be in P. Thus, our conjectures imply that all of the problems below are not in P.
breaking
Rabin encryption
congruent squares
(square roots of
congruence classes)
finding RSA
secret key
computing φ(n)
for n = pq

factoring
n = pq
decrypting individual
RSA messages
RSA problem
(eth roots of
congruence classes)
breaking
Diffie-Hellman
discrete logarithm
(logarithms of
congruence classes)
Example: Bob decides to create his own online currency BobCoin. Bob knows that in order for BobCoin to be successful, it needs to be possible to make more BobCoins as more and more people start using them. However, he also does not want rapid inflation to occur. Thus, Bob issues BobCoins according to the following protocol:
  • every day, Bob chooses two new random primes p and q;
  • Bob computes n = pq, and then discards p and q;
  • Bob posts n online for everyone to see;
  • at any time on that day, anyone can submit a factor f of n;
  • if f is a factor of n, Bob issues that person a BobCoin, invalidates n permanently so that no one else can use it, and generates a new n.
  1. Why is it okay for Bob to discard p and q?
    Because Bob can easily check whether a submitted f actually is a factor by computing gcd(f, n), or simply dividing n by f.
  2. Suppose that Bob always posts numbers n that have 100 digits, and it takes the fastest computer one year to factor a 100-digit number through trial and error. If Alice wants to earn a BobCoin in one day, how many computers will Alice need to run in parallel to earn a BobCoin?
    Since it takes a 365 days to try all possible k factors of a 100-digit number, in one day one computer can try 1/365 ⋅ k factors. Thus, Alice would need 365 computers that she could run for one day, with each computer looking through a different region of factors.
  3. Suppose Bob wants to issue a complimentary BobCoin coupon to a group of 100 people. However, he wants to make sure that they can use their BobCoin coupon only if at least 20 out of those 100 people agree that the coupon should be redeemed for a BobCoin. How can Bob accomplish this?
    Bob can use Shamir secret sharing to accomplish this. Bob can take one of the factors f of n when he generates n, and choose 100 distinct primes such that the product of any 20 of the primes is greater than f while the product of any 19 of these primes is less than f. Bob can then distribute f mod m for each prime modulus m to each of the 100 people. If any 20 (or greater) of these 100 people agree to redeem the coupon, then can use CRT to reconstruct f and submit it to Bob for redemption.


[link]  4.5. Assignment #4: Intractable Problems in Modular Arithmetic

In this assignment you will solve several equations, and you will define a collection of Python functions that demonstrate the relationships between intractable problems in modular arithmetic. You must submit a single Python source file named hw4.py (submitted to the location hw4/hw4.py). Please follow the gsubmit directions.

You may import the following library functions in your module (you may not need all these functions for this assignment depending on how you approach the problems, but they may be used):

from math import floor
from fractions import gcd
        
Your file may not import any other modules or employ any external library functions associated with integers and sets unless explicitly permitted to do so in a particular problem. Solutions to each of the programming problem parts below should be fairly concise. You will be graded on the correctness, concision, and mathematical legibility of your code. The different problems and problem parts rely on the lecture notes and on each other; carefully consider whether you can use functions from the lecture notes, or functions you define in one part within subsequent parts.
  1. Solve the following equations using step-by-step equational reasoning, and list each step. Your solutions for this problem should appear as comments, delimited using '''...''', in hw4.py. You may use the = ascii character to represent the ≡ relational operator on congruence classes.
    1. Solve the following equation for all solutions x ℤ/19ℤ:
      x2
      17 (mod 19)
    2. Solve the following equation for all solutions x ℤ/33ℤ:
      x2
      1 (mod 33)
    3. Solve the following equation for all solutions x ℤ/41ℤ (note that you cannot use the explicit formula for square roots in this case):
      2 ⋅ x2
      32 (mod 41)
    4. Solve the following equation for all solutions x ℤ/49ℤ:
      x2
      11 (mod 49)
    5. Solve the following equation for all solutions x ℤ/21ℤ:
      (11 ⋅ x2) − 6
      12 (mod 21)
  2. Implement the following Python functions for factoring a positive integer. These algorithms must be efficient (i.e., they must run in polynomial time).
    1. Implement a function factorsFromPhi(n, phi_n) that takes two integers n and phi_n. You may assume (i.e., you do not need to verify in your code) that n is a product of two distinct positive prime numbers and that phi_n = φ(n). The function should return both prime factors of n as a tuple.

      >>> factorsFromPhi(77, 60)
      (7, 11)
      >>> factorsFromPhi(14369648346682547857, 14369648335605206820)
      (9576890767, 1500450271)
                    
    2. Implement a function factorsFromRoots(n, x, y) that takes three integers n, x, and y. You may assume that n is a product of two distinct positive prime numbers, and that the following is true:
      x2
      y2 (mod n)
      x
      ± y (mod n)
      The function should return both prime factors of n as a tuple.

      >>> factorsFromRoots(35, 1, 6)
      (5, 7)
      >>> factorsFromRoots(14369648346682547857, 12244055913891446225, 1389727304093947647)
      (1500450271, 9576890767)
                    
    3. Consider the following problem (call it "φ-four"): "given an integer n = pqrs that is the product of four distinct primes, find φ(n)." Notice that the four distinct primes are not known in the specification of the problem. It is only known that n is the product of some four distinct primes.
      Given our assumptions about the intractability of various problems (i.e., that certain problems are not in P), you must show that φ-four is also not in P. To do so, you must implement a function phiFromPhiFour(n) for the existing intractable problem of computing φ that uses a solver for φ-four (call it phi_four()) as a subroutine. Note: remember that the four primes must be distinct; your reduction must work for all possible instances of the intractable problem.
  3. In this problem you will implement the three component algorithms of the RSA cryptographic protocol described in lecture.
    1. Define a Python function generate(k) that takes a single integer input k and returns a tuple (n,e,d) corresponding to the public values n and e and private key d in the RSA cryptographic protocol. The output n must be the product of two distinct, randomly chosen k-digit primes. You may import and use the Python random number generator (from random import random or from random import randint), and you may want to reuse the extended Euclidean algorithm implementation provided in the previous homework assignment.
    2. Define a Python function encrypt(m, t) that takes two inputs: an integer m and a tuple (n,e) representing an RSA public key. It should return a single integer: the RSA ciphertext c.
    3. Define a Python function decrypt(c, t) that takes two inputs: an integer c representing the ciphertext and a tuple of two integers (n,d) representing an RSA private key. It should decrypt c and return the original message m.
  4. In this problem you will implement an algorithm for computing all the square roots of a congruence class in ℤ/nℤ, given a complete factorization of n into its distinct prime factor powers (assuming all the prime factors are in 3 + 4ℤ).
    1. Implement a Python function sqrtsPrime(a, p) that takes two arguments: an integer a and a prime number p. You may assume that a and p are coprime. If p is not in 3 + 4ℤ or a has no square roots in ℤ/pℤ, the function should return None. Otherwise, it should return the two congruence classes in ℤ/pℤ that solve the following equation:
      x2
      a (mod p)

      >>> sqrtsPrime(2, 7)
      (3, 4)
      >>> sqrtsPrime(5, 7) # 5 has no square roots
      None
      >>> sqrtsPrime(5, 17) # 17 mod 4 =/= 3
      None
      >>> sqrtsPrime(763472161, 5754853343)
      (27631, 5754825712)
                    
    2. Implement a Python function sqrtsPrimePower(a, p, k) that takes three arguments: an integer a, a prime number p, and a positive integer k. You may assume that a and p are coprime. If p is not in 3 + 4ℤ or a has no square roots in ℤ/pkℤ, the function should return None. Otherwise, it should return the congruence classes in ℤ/pkℤ that solve the following equation:
      x2
      a (mod pk)

      >>> sqrtsPrimePower(2, 7, 2)
      (10, 39)
      >>> sqrtsPrimePower(763472161, 5754853343, 4)
      (27631, 1096824245608362247285266960246506343570)
                    
    3. Implement a Python function sqrts(a, pks) that takes two arguments: an integer a and a list of tuples pks in which each tuple is a distinct positive prime number paired with a positive integer power. You may assume that a and n are coprime. You may assume that all the primes in pks are in 3 + ℤ/4ℤ (if any are not, the function should return None). Let n be the product of all the prime powers in the list pks. Then the function should return a set of all the distinct square roots of a in ℤ/nℤ that are solutions to the following equation:
      x2
      a (mod n)
      Your implementation must be efficient (i.e., it may not iterate over all possible values in ℤ/nℤ to look for square roots), and it must work for any positive number of entries in pks.

      >>> sqrts(2, [(7,4)])
      {235, 2166}
      >>> sqrts(1, [(7,1), (11,1)])
      {1, 76, 43, 34}
      >>> sqrts(1, [(7,1), (11,1), (3,1)])
      {1, 76, 43, 34, 155, 188, 197, 230}
      >>> sqrts(1, [(7,2), (11,1), (3,2)])
      {1, 197, 881, 1079, 3772, 3970, 4654, 4850}
      >>> sqrts(1, [(7,1), (11,1), (3,1), (19,1)])
      {1, 265, 419, 1198, 1310, 1462, 1616, 1882, 2507, 2773, 2927, 3079, 3191, 3970, 4124, 4388}
      >>> sqrts(76349714515459441, [(1500450271,3), (5754853343,2)])
      {276314521,
       111875075121925861006908948065990250824231090118,
       50900491283175338098734392241768315796265192809,
       60974583838750522908174555824221935028242211830}
                    
      You may find the following helper function useful in implementing your solution.

      def combinations(ls):
          if len(ls) == 0:
              return [[]]
          else:
              return [ [x]+l for x in ls[0] for l in combinations(ls[1:]) ]
                    
      This function takes a list of lists as its input and returns a list of all possible combinations of one element from each list.

      >>> combinations([[1,2], ['a','b'], ['X','Y']])
      [[1, 'a''X'], [1, 'a''Y'], [1, 'b''X'], [1, 'b''Y'], [2, 'a''X'], [2, 'a''Y'], [2, 'b''X'], [2, 'b''Y']]
                    
  5. Suppose you are supplied with an efficient implementation of an algorithm for breaking the Rabin cryptographic protocol. Below, we provide a mockup of an efficient implementation of such an algorithm that only work on a few sample inputs (since we do not believe efficient algorithms for breaking this protocol exists, we must cheat in this way in order to test our code). You may assume that the below algorithm works on all inputs, not just those provided in the fake implementation.

    # Efficiently computes m from (pow(m,2,n), n).
    def decryptMsgRabin(c, n):
        input_output = {\
            (14, 55): 17,\
            (12187081, 8634871258069605953): 7075698730573288811,\
            (122180308849, 16461679220973794359): 349543,\
            (240069004580393641, 19923108241787117701): 489968371\
            }
        return input_output[(c, n)]
              
    Implement an efficient function roots(a, n) that takes two integers a and n and returns all four square roots of a in ℤ/nℤ as a tuple. You may assume that n is the product of two distinct positive prime numbers. Your algorithm in this problem must work on all possible inputs under the assumption that decryptMsgRabin() also works on all inputs (not just the fake inputs handled by the definitions above) and that an encrypted message c is never represented by the same congruence class as the original, unencrypted message m or − m (i.e., the implementation follows the guideline regarding good ranges for messages at the end of the definition of the Rabin protocol).

    >>> roots(12187081, 8634871258069605953)
    (3491, 8634871258069602462, 1559172527496317142, 7075698730573288811)
              



[link]  5. Algebraic Structures

In the previous sections, we studied a specific algebraic structure, ℤ/nℤ, as well as its operations (e.g., addition, multiplication), and its properties (commutativity, associativity, and so on). There exist many other algebraic structures that share some of the properties of ℤ/nℤ. In fact, we can create a hierarchy, or even a web, of algebraic structures by picking which properties of ℤ/nℤ we keep and which we throw away.

Showing that some new algebraic structure is similar or equivalent to another, more familiar structure allows us to make inferences about that new structure based on everything we already know about the familiar structure. In computer science, the ability to compare algebraic structures using their properties is especially relevant because every time a programmer defines a new data structure and operations on that data structure, they are defining an algebraic structure. Which properties that algebraic structure possesses determines what operations can be performed on it, in what order they can be performed, how efficiently they can be performed, and how they can be broken down and reassembled.

[link]  5.1. Permutations

Recall that a permutation on a set X is a bijective relation between X and X (i.e., a subset of the set product X × X). Since a permutation is a bijective map (i.e., a function), we can reason about composition of permutations (it is just the composition of functions). Thus, we can study sets of permutations as algebraic structures under the composition operation o.

Notice that for any set X of finite size n, we can relabel the elements of X to be {0,...,n-1} (that is, we can define a bijection between X and {0,...,n-1}). Thus, we can study permutations on {0,...,n-1} without loss of generality. We will adopt the following notation for permutations:
[a1,...,an]
Where a1,...,an is some rearrangement of the integers from 0 to n-1. For example, the identity permutation on n elements would be:
[0,1,2,3,4,5,...,n-1]
Definition: Any permutation that swaps exactly two elements is called a swap. Examples of swaps are [0,3,2,1], [1,0], and [0,6,2,3,4,5,1].
Definition: Any permutation that swaps exactly two adjacent elements is called an adjacent swap. Examples of adjacent swaps are [0,1,3,2], [1,0,2,3,4], and [0,1,3,2,4,5,6].
Definition: Define Sn to be the set of all permutations of the set {0,...,n-1}.
Example: The set of permutations of {0,1} is S2 = {[0,1], [1,0]}.
Example: The set of permutations of {0,1,2} is S3 = {[0,1,2], [0,2,1], [1,0,2], [1,2,0], [2,0,1], [2,1,0]}.
Fact: The set Sn contains n! permutations.

Suppose we want to construct a permutation [a1,...,an] using the elements in {0,...,n-1}, where we are only allowed to take each element in the set once and assign it to an unassigned entry ai. Then for the first slot, we have n possibilities; for the second, we have n-1 possibilities. For the third, we have n-2 possibilities, and so on until we have only one possibility left. Thus, the number of possible permutations we can make is:
n!
=
n ⋅ (n-1) ⋅ (n-2) ⋅ ... ⋅ 2 ⋅ 1
Definition: Define the set Cn to be the set of all cyclic permutations on n elements. Any permutation that performs a circular shift on elements is a cyclic permutation (also known as a cyclic shift permutation, a circular shift permutation, or just a shift permutation). Examples of shifts are [6,7,0,1,2,3,4,5], [2,3,4,0,1], and [4,0,1,2,3].
Definition: Define the set Mn to be the set of all multiplication-induced permutations on n elements. Any permutation on n elements that corresponds to multiplication by some coprime a < n is called a multiplication-induced permutation. Examples of such permutations include [0,2,4,1,3] (corresponding to multiples 2 ⋅ i for ascending i in ℤ/5ℤ).
Example: The set of multiplication-induced permutations on 6 elements (i.e., permutations of {0,1,2,3,4,5}) is the collection of permutations of the form [a ⋅ 0 mod 6, a ⋅ 1 mod 6, a ⋅ 2 mod 6, a ⋅ 3 mod 6, a ⋅ 4 mod 6, a ⋅ 5 mod 6] for each a that is coprime with 6. Thus, a {1,5}, and so we have:
M6 = {[0,1,2,3,4,5], [0,5,4,3,2,1]}.
Example: The set of multiplication-induced permutations on 7 elements (i.e., permutations of {0,1,2,3,4,5,6}) is:
M7 = {[0,1,2,3,4,5,6], [0,2,4,6,1,3,5], [0,3,6,2,5,1,4], [0,4,1,5,2,6,3], [0,5,3,1,6,4,2], [0,6,5,4,3,2,1]}.
Note that |M7| = φ(7) = 6 because there are 6 possible a ℤ/7ℤ that are coprime with 7.

[link]  5.2. Isomorphisms: Equivalence of Algebraic Structures

An algebraic structure is a set together with a binary operator over that set. All algebraic structures are closed under their binary operation.
Definition: Let S be a set, and let ⊕ be a binary operator. Let closure(S,⊕) be the closure of S under ⊕. We can define the set closure(S,⊕) in the following way:
closure(S, ⊕)
=
{ x1x2 | x1,x2 S } ∪ { x1 ⊕ (x2x3) | x1,x2,x3 S } ∪ { (x1x2) ⊕ x3 | x1,x2,x3 S } ∪ ...
Alternatively, we could define it in the following way using recursion:
closure0(S, ⊕)
=
S
closuren(S, ⊕)
=
{ xy | x,y (closuren-1(S, ⊕) ∪ ... ∪ closure0(S, ⊕)}
closure(S, ⊕)
=
closure0(S, ⊕) ∪ closure1(S, ⊕) ∪ closure2(S, ⊕) ∪ ...
Notice that if a set S is finite, there is a natural way to algorithmically list all elements in closure(S, ⊕) by starting with the elements in S and "building up" all the elements in each of the closure_i(S, ⊕) subsets.
The concept of an isomorphism between two algebraic structures captures the fact that two structures are not only the same size, but that the two structures have the same internal "structure" with respect to their respective operations. Isomorphisms are useful because they allow us to learn more about a structure by studying the structure isomorphic to it. They can also be useful because if two structures are isomorphic, we can perform computations in one structure instead of another structure (e.g., because it is more secure, more efficient, and so on) while obtaining the same final result.
Fact: Let A be an algebraic structure with operator ⊕ and let B be an algebraic structure with operator ⊗. We say that A is isomorphic to B, which we denote as (A,⊕) ≅ (B,⊗) or simply AB, if the following conditions hold:
  • there exists a bijection (i.e., a bijective relation) between A and B, which we denote using  = ;
  • for all a, a' A and b,b' B, if a = b and a' = b' then aa' = bb'.
Another way to state the definition is to write it in terms of a bijective map m between A and B:
  • there exists a bijective map m between A and B;
  • for all a, a' A,      m(aa') = m(a) ⊗ m(a').
In other words, an isomorphism is a bijection that preserves (or respects) the binary operations on the two sets: if any true equation involving elements from A and the operator ⊕ is transformed by replacing all elements of a with their corresponding elements m(b) B and by replacing all instances of ⊕ with ⊗, the resulting equation is still true.
Example: Consider the set of permutations on two elements S2 and the set of congruence classes ℤ/2ℤ. It is true that (S2,o) ≅ (ℤ/2ℤ,+), where o is composition of permutations and where + is addition of congruence classes in ℤ/2ℤ. The following table demonstrates the bijection:
S2 ℤ/2ℤ
[0,1] 0
[1,0] 1
The following table demonstrates that the bijection above respects the two operations.
S2 ℤ/2ℤ
[0,1] o [0,1] = [0,1] 0 + 0 = 0
[0,1] o [1,0] = [1,0] 0 + 1 = 1
[1,0] o [0,1] = [1,0] 1 + 0 = 1
[1,0] o [1,0] = [0,1] 1 + 1 = 0
Another way to demonstrate the above is to show that the "multiplication tables" (though the operation need not be multiplication) for the two operators are exactly the same (i.e., the entries in the multiplication table all correspond according to the bijection).
+
o
0
[0,1]
1
[1,0]
0
[0,1]
0
[0,1]
1
[1,0]
1
[1,0]
1
[1,0]
0
[0,1]
Fact: For any positive integer n ℕ, (ℤ/nℤ,+) ≅ (Cn, o).
Example: Consider the set of cyclic permutations on three elements C3 and the set of congruence classes ℤ/3ℤ. It is true that (C3,o) ≅ (ℤ/3ℤ,+).
C3ℤ/3ℤ
[0,1,2] o [0,1,2] = [0,1,2]0 + 0 = 0
[0,1,2] o [1,2,0] = [1,2,0]0 + 1 = 1
[0,1,2] o [2,0,1] = [2,0,1]0 + 2 = 2
[1,2,0] o [0,1,2] = [1,2,0]1 + 0 = 1
[1,2,0] o [1,2,0] = [2,0,1]1 + 1 = 2
[1,2,0] o [2,0,1] = [0,1,2]1 + 2 = 0
[2,0,1] o [0,1,2] = [2,0,1]2 + 0 = 2
[2,0,1] o [1,2,0] = [0,1,2]2 + 1 = 0
[2,0,1] o [2,0,1] = [1,2,0]2 + 2 = 1
Example: To compute the composition of two permutations [45,46,...,49,0,1,2,...,44] o [3,4,5,...,49,0,1,2], it is sufficient to recognize that [45,46,...,49,0,1,2,...,44] corresponds to 45 ℤ/50ℤ, and [3,4,5,...,49,0,1,2] corresponds to 3 ℤ/50ℤ. Thus, since 45 + 3 = 48, the result of the composition must be [48,49,0,1,2,...,47].
45 + 3
=
48
[45,46,...,49,0,1,2,...,44] o [3,4,5,...,49,0,1,2]
=
[48,49,0,1,2,...,47]
Fact: For any prime p ℕ, (ℤ/φ(p)ℤ,+) ≅ ((ℤ/pℤ)*, ⋅).

Note that |ℤ/φ(p)ℤ| = φ(p) = |(ℤ/pℤ)*|.
Example: Consider the set ℤ/2ℤ with the addition operation + modulo 2, and the set (ℤ/3ℤ)* together with the multiplication operation ⋅ modulo 3. It is true that (ℤ/2ℤ, +) ≅ ((ℤ/3ℤ)*,⋅).
(ℤ/2ℤ, +) ((ℤ/3ℤ)*, ⋅)
0 + 0 = 0 1 ⋅ 1 = 1
0 + 1 = 1 1 ⋅ 2 = 2
1 + 0 = 1 2 ⋅ 1 = 2
1 + 1 = 0 2 ⋅ 2 = 1
Example: Consider the set ℤ/2ℤ with the addition operation + modulo 2, and the set (ℤ/6ℤ)* together with the multiplication operation ⋅ modulo 6. It is true that (ℤ/2ℤ, +) ≅ ((ℤ/6ℤ)*,⋅). Note that (ℤ/6ℤ)* = {1,5}, because only 1 and 5 in the range {0,...5} are coprime with 6.
(ℤ/2ℤ, +) ((ℤ/6ℤ)*, ⋅)
0 + 0 = 0 1 ⋅ 1 = 1
0 + 1 = 1 1 ⋅ 5 = 5
1 + 0 = 1 5 ⋅ 1 = 5
1 + 1 = 0 5 ⋅ 5 = 1
Isomorphisms need not be defined between different sets. It is possible to define an isomorphism between a set and itself that has non-trivial, interesting, and even useful characteristics.
Fact: For any n ℕ and any a ℤ/nℤ where a is coprime with n, (ℤ/nℤ,+) ≅ (ℤ/nℤ,+) under the bijection that relates x ℤ/nℤ with ax ℤ/nℤ. This is because for any x,y ℤ/nℤ, we have:
a ⋅ (x + y)
ax + ay
Fact: For any n ℕ and any e ℤ/φ(n)ℤ where e is coprime with φ(n), ((ℤ/nℤ)*,⋅) ≅ ((ℤ/nℤ)*,⋅) under the bijection that relates x (ℤ/nℤ)* with xe (ℤ/nℤ)*. This is because for any x, y (ℤ/nℤ)*, we have:
(xy)e
xeye (mod n)
Example: Consider the set ℤ/3ℤ with the addition operation + modulo 3, and another instance of the set ℤ/3ℤ with the addition operation + modulo 3. The following is a bijection between (ℤ/3ℤ, +) and (ℤ/3ℤ, +):
ℤ/3ℤℤ/3ℤ
00
12
21
This bijection is an isomorphism:
ℤ/3ℤℤ/3ℤ
0 + 0 = 00 + 0 = 0
0 + 1 = 10 + 2 = 2
0 + 2 = 20 + 1 = 1
1 + 0 = 12 + 0 = 2
1 + 1 = 22 + 2 = 1
1 + 2 = 02 + 1 = 0
2 + 0 = 21 + 0 = 1
2 + 1 = 01 + 2 = 0
2 + 2 = 11 + 1 = 2
Example (partial homomorphic encryption supporting addition): Suppose that for some n ℕ, Alice wants to store a large number of congruence classes b1,...,bk ℤ/nℤ in Eve's database (perhaps Alice will generate or collect these over a long period of time). Alice does not have enough memory to store the congruence classes herself, but she does not want to reveal the congruence classes to Eve.

What Alice can do before she stores anything in Eve's database is to pick some secret a ℤ/nℤ that is coprime with n. Then, every time Alice needs to store some b in Eve's database, Alice will instead send Eve the obfuscated value (ab) mod n. Since a is coprime with n, there exists a-1 ℤ/nℤ. Thus, if Alice retrieves some obfuscated data entry c from Eve's database, she can always recover the original value by computing (a-1c) mod n, because:
a-1 ⋅ (ab)
b (mod n)
Furthermore, Alice can ask Eve to compute the sum (modulo n) of all the entries in the database (or any subset of them). Suppose that Alice has stored obfuscated versions of b1,...,bk ℤ/nℤ in Eve's database. Then if Eve computes the sum of all the obfuscated entries stored in her database, she will get:
ab1 + ... + abk
=
a ⋅ (b1 + ... + bk) (mod n)
Thus, if Alice asks Eve for the sum of all the obfuscated entries in the database, Alice can recover the actual sum of the original entries that she stored in the database because:
a-1 ⋅ (a ⋅ (b1 + ... + bk))
=
b1 + ... + bk (mod n)
In this way, Alice has avoided having to store and add all the database entries, while preventing Eve finding out the actual entries, or their sum.
Example (partial homomorphic encryption supporting multiplication): Suppose that for some n ℕ, Alice wants to store a large number of congruence classes b1,...,bk (ℤ/nℤ)* in Eve's database. Alice does not have enough memory to store the congruence classes herself, but she does not want to reveal the congruence classes to Eve.

We assume that Alice knows or can easily compute φ(n), while Eve does not know and cannot compute it (perhaps Alice generated n using a method similar to the one in RSA encryption protocol).

What Alice can do before she stores anything in Eve's database is to pick some secret e ℤ/φ(n)ℤ that is coprime with φ(n). Since Alice knows e and φ(n), she can compute e-1 using the extended Euclidean algorithm.

Then, every time Alice needs to store some b in Eve's database, Alice will instead send Eve the encrypted value be mod n. If Alice retrieves some encrypted data entry c from Eve's database, she can always recover the original value by computing (ce-1}) mod n, because by Euler's theorem:
(be)e-1
bee-1
b (mod n)
Furthermore, Alice can ask Eve to compute the product (modulo n) of all the entries in the database (or any subset of them). Suppose that Alice has stored encrypted versions of b1,...,bk ℤ/nℤ in Eve's database. Then if Eve computes the product of all the encrypted entries stored in her database, she will get:
b1e ⋅ ... ⋅ bke
=
(b1 ⋅ ... ⋅ bk)e (mod n)
Thus, if Alice asks Eve for the product of all the encrypted entries in the database, Alice can recover the actual product of the original entries that she stored in the database because:
((b1 ⋅ ... ⋅ bk)e)e-1
=
b1 ⋅ ... ⋅ bk (mod n)
In this way, Alice has avoided having to store and multiply all the database entries, while preventing Eve from finding out the actual entries, or their product. Furthermore, because it is believed that factoring n, computing φ(n), and solving the RSA problem is computationally difficult, it is highly unlikely that Eve can decrypt the database entries or the result.
We know by Euler's theorem and the algebraic properties of exponents that for any ℤ/nℤ, any b (ℤ/nℤ)*, and any x, y ℤ/φ(n)ℤ the following identity must hold:
bxby
bx + y (mod n)
b(x + y) mod φ(n) (mod n)
We might naturally ask whether there might be an isomorphism between (ℤ/φ(n)ℤ, +) and ((ℤ/nℤ)*, ⋅). In fact, sometimes there is (although more often it is an isomorphism between a subset of (ℤ/nℤ)* and ℤ/kℤ for k | φ(n)).
Given the above, we might ask whether it might be possible to create a homomorphic encryption protocol using an isomorphism of the form (ℤ/φ(n)ℤ, +) ≅ ((ℤ/nℤ)*, ⋅) in which Alice can encrypt her data x and y by computing bx (mod n) and by (mod n). This should be secure because it is believed that no efficient algorithms for computing discrete logarithms exist. Then, in order to have Eve compute a sum of the data values x and y, Alice can ask Eve to compute the product bxby on her end, which is equivalent to bx + y.
However, there is a flaw in this protocol: Alice has no way to retrieve x + y from bx + y because that requires computing a discrete logarithm, as well. Thus, an isomorphism of the form (ℤ/φ(n)ℤ, +) ≅ ((ℤ/nℤ)*, ⋅) would not necessarily give us a practical homomorphic encryption protocol.
Fact: Given a set of possible data values ℤ/nℤ (e.g., integers within a certain range), any compression algorithm for elements in ℤ/nℤ must be a bijection and a permutation (since it must be invertible in order for decompression to be possible). As a result, it must necessarily expand the representation size of some elements.
Example: Suppose that we are working with elements in ℤ/11ℤ = {0,1,2,3,4,5,6,7,8,9,10}. Suppose we define an algorithm that compresses 10 ℤ/11ℤ into an element in ℤ/11ℤ with a smaller representation size. One example of such an element is 1, since:
10 ⋅ 10
1 (mod 11)
Thus, one possible implementation of a compression algorithm is a function that takes any x ℤ/11ℤ and returns (10 ⋅ x) mod 11. Since 10 has an inverse in ℤ/11ℤ, this function is invertible, so decompression is possible (simply multiply by 10 again). However, this will necessarily expand the representation of at least one value: 1 ℤ/11ℤ:
10 ⋅ 1
10 (mod 11)
Note that this cannot be avoided because multiplying all the elements in ℤ/11ℤ by 10 amounts to a permutation, so at least one compressed element must be 10.

[link]  5.3. Generators of Algebraic Structures

Because an algebraic structure (A,⊕) often consists of a set of objects that can be "built up" using the binary operator ⊕ from a smaller, possibly finite, collection of generators GA, it is often easier to reason about an algebraic structure by first reasoning about its generators, and then applying structural induction.
Fact: Let W be the set of swap permutations on n elements. Then W is a set of generators for the set of permutations Sn, which can be defined as:
S
=
closure(W, o)
Fact: Let A be the set of adjacent swap permutations on n elements. Then A is a set of generators for the set of permutations Sn, which can be defined as:
S
=
closure(A, o)
Fact: The set ℤ/nℤ has a single generator 1 ℤ/nℤ with respect to addition + modulo n:
ℤ/n
=
closure({1}, +)
Fact: If a and n are coprime, then a is a generator for ℤ/nℤ with respect to addition + modulo n:
ℤ/n
=
closure({a}, +)
Fact: Suppose we have two algebraic structures A and B where for some operator ⊕:
A
=
closure({a}, ⊕)
B
=
closure({b}, ⊕)
If the generator a can be expressed in terms of b, i.e., a = b ⊕ ... ⊕ b, then it must be that:
A
B
Furthermore, if in addition to the above, the generator b can be expressed in terms of a, i.e., b = a ⊕ ... ⊕ a, then it must be that:
B
A
This would then imply (by basic set theory):
A
=
B


[link]  5.4. Assignment #5: Algebraic Structures and Isomorphisms

In this assignment you will solve several problems involving algebraic structures and isomorphisms, and you will define a collection of Python functions that exploit isomorphisms between algebraic structures. You must submit a single Python source file named hw5.py (submitted to the location hw5/hw5.py). Please follow the gsubmit directions.

You may import the following library functions in your module (you may not need all these functions for this assignment depending on how you approach the problems, but they may be used):

from math import floor
from fractions import gcd
from random import randint
from urllib.request import urlopen
        
Your file may not import any other modules or employ any external library functions associated with integers and sets unless explicitly permitted to do so in a particular problem. Solutions to each of the programming problem parts below should be fairly concise. You will be graded on the correctness, concision, and mathematical legibility of your code. The different problems and problem parts rely on the lecture notes and on each other; carefully consider whether you can use functions from the lecture notes, or functions you define in one part within subsequent parts.
  1. Solve the following equations using step-by-step equational reasoning, and list each step. Your solutions for this problem should appear as comments, delimited using '''...''', in hw5.py.
    1. Compute the following composition of permutations (the result should be a permutation):
      [2,3,4,0,1] o [4,0,1,2,3] = ?
    2. Compute the following composition of permutations (the result should be a permutation):
      [23,24,...,99,0,1,2,3,4,...,22] o [57,58,...,99,0,1,2,3,4,...,56] = ?
    3. Let p S7 be a swap permutation on 7 elements. Compute the following (your result should be an explicit permutation):
      p o p o p o p = ?
    4. Let p, q C13 be two distinct cyclic permutation on 13 elements. Compute the following (your result should be an explicit permutation):
      p o q o p-1 o q-1 = ?
    5. Define the isomorphism between the two algebraic structures (C2, o) and ((ℤ/4ℤ)*, ⋅). You must write down a bijection that specifies how each element in C2 corresponds to an element of (ℤ/4ℤ)*, and you must write down four pairs of corresponding equation that show that the behavior of o on elements of C2 is the same as the behavior of ⋅ on corresponding elements of (ℤ/4ℤ)*.
    6. Define the isomorphism between the two algebraic structures (closure({2 + 15ℤ}, ⋅), ⋅) and (ℤ/4ℤ, +) (where ⋅ in the first case refers to multiplication modulo 15). You must write down a bijection that specifies how each element in closure({2 + 15ℤ}, ⋅) corresponds to an element of ℤ/4ℤ, but you do not need to write out all the equations.
    7. Explain why there can be no isomorphism between the two algebraic structures (S5, o) and (ℤ/5ℤ, +).
  2. Implement the following Python functions for working with permutations. Any code that does not conform exactly to the specified requirements will receive no credit.
    1. Implement a function permute(p, l) that takes two arguments: a permutation p (represented as a Python list of integers) and a list l of the same length as the permutation. It should return the list after it has been permuted according to the permutation.

      >>> permute([2,1,0], ['a','b','c'])
      ['c','b','a']
                    
    2. Implement a function C(k, m) that takes two integers k and m where k < m and returns the cyclic permutation in Cm that shifts all elements up by k.

      >>> C(1, 4)
      [1, 2, 3, 0]
                    
    3. Implement a function M(a, m) that takes two coprime integers a and m where 0 < a < m and returns the multiplication-induced permutation in Mm that corresponds to multiplication by a modulo m:

      >>> M(2, 5)
      [0, 2, 4, 1, 3]
                    
    4. Implement a function sort(l) that takes a list of integers of some length n and returns:
      • a cyclic permutation p Cn that will sort the list into ascending order, if it exists;
      • a multiplication-induced permutation p Mn that will sort the list into ascending order, if it exists;
      • None otherwise.

      >>> sort([38,16,27])
      [1,2,0]
      >>> permute(sort([38,49,16,27]), [38,49,16,27])
      [16,27,38,49]
      >>> sort([1, 13, 4, 17, 6, 23, 9])
      [0, 2, 4, 6, 1, 3, 5]
      >>> sort([0, 17, 4, 21, 8, 25, 12, 29, 16, 3, 20, 7, 24, 11, 28,\
                15, 2, 19, 6, 23, 10, 27, 14, 1, 18, 5, 22, 9, 26, 13])
      [0, 23, 16, 9, 2, 25,18, 11, 4, 27, 20, 13, 6, 29, 22,
       15, 8, 1, 24, 17, 10, 3, 26, 19, 12, 5, 28, 21, 14, 7]
                    
  3. In this problem you will implement a secure and correct multiplication algorithm by using an untrusted, unreliable third-party web service to perform the actual multiplication. The web service consists of a PHP script at http://cs-people.bu.edu/lapets/235/unreliable.php (e.g., the result of ((2 ⋅ 3 ⋅ 4) mod 7) can be obtained at http://cs-people.bu.edu/lapets/235/unreliable.php?n=7&data=2,3,4). You can use the Python function below to invoke this script (this Python code needs to run on a computer connected to the internet).

    def unreliableUntrustedProduct(xs, n):
        url = 'http://cs-people.bu.edu/lapets/235/unreliable.php'
        return int(urlopen(url+"?n="+str(n)+"&data="+",".join([str(x) for x in xs])).read().decode())
              
    To test your code on a service that does not sometimes return incorrect answers, you can also use the URL http://cs-people.bu.edu/lapets/235/reliable.php.
    1. Implement a function privateProduct(xs, p, q) that takes three inputs: a non-empty list of integers xs, a prime p, and another distinct prime q. The function must compute the product modulo p of all the integers in the list xs (assuming the web service performs its job correctly, which it may sometimes not do):
      privateProduct(xs, p, q)
      =
      (xs[0] * xs[1] * ... * xs[len(xs)-1]) % p
      However, your implementation may not multiply any of the integers in xs on its own; it must use unreliableUntrustedProduct() to do so, and at the same time it must not send the actual integers in xs over the web or reveal them to the web service. Your implementation should leak no information about the entries in xs or the product obtained by multiplying them to anyone (unless they can solve an intractable problem in modular arithmetic).
      To solve this problem, use the extra prime q to create public and private RSA keys, and then encrypt all the integers in xs (you will need to encrypt them modulo n instead of modulo p because RSA needs a composite modulus). Next, use unreliableUntrustedProduct() to compute the product of the RSA-encrypted integers modulo n (i.e., take advantage of this isomorphism as in this homomorphic encryption example). Finally, your privateProduct() implementation should decrypt the result and return the product modulo p.
    2. Implement a function validPrivateProduct(xs, p, q) that takes three inputs: a non-empty list of integers xs, a prime p, and another distinct prime q. The function must always correctly compute the product modulo p of all the integers in the list xs:
      validPrivateProduct(xs, p, q)
      =
      (xs[0] * xs[1] * ... * xs[len(xs)-1]) % p
      As before, your implementation may not multiply any of the integers in xs on its own; it must use unreliableUntrustedProduct() to do so, and at the same time it must not send the actual integers in xs over the web or reveal them to the web service.
      Start with your solution to part (a) and extend it by choosing a random value r in ℤ/qℤ. For each integer xs[i] in the list, convert the pair (xs[i], r) into a value in ℤ/nℤ via the CRT isomorphism. Then encrypt all these ℤ/nℤ values using RSA and use unreliableUntrustedProduct() to compute their product. Finally, your validPrivateProduct() implementation should decrypt the result and convert it back to an answer modulo p via the opposite direction of the CRT isomorphism.
      To check if the answer you obtained from unreliableUntrustedProduct() is actually correct, determine whether the decrypted value modulo q has the expected value (i.e., is it really rlen(xs) modulo q). If this check fails, keep repeating the entire process from the beginning until it succeeds.
  4. Implement a function isomorphism(A, B) that takes two tuples A and B as inputs. Each tuple consists of two entries: the first is an ordered list of elements from an algebraic structure, and the second is a function on elements of that binary structure. Thus, the tuple A represents an algebraic structure (A, ⊕), and the tuple B represents an algebraic structure (B, ⊗). You may assume that the list of elements is closed under the operation represented by the function. You may also assume that the bijection between the sets A and B is already provided by the order of the elements in each list (i.e., the ith entry in the first list corresponds to the ith entry in the second). The isomorphism() function should return True if there is indeed an isomorphism between (A, ⊕) and (B, ⊗), and False otherwise.

    >>> plusMod2 = lambda x,y: (x + y) % 2
    >>> A = ([0,1], plusMod2)
    >>> B = ([C(0,2), C(1,2)], permute)
    >>> isomorphism(A, B)
    True

    >>> plusMod4 = lambda x,y: (x + y) % 4
    >>> multMod8 = lambda x,y: (x * y) % 8
    >>> A = ([0,1,2,3], plusMod4)
    >>> B = ([1,3,5,7], multMod8)
    >>> isomorphism(A, B)
    False
              
  5. Extra credit: In a comment, explain why there cannot be an isomorphism between (ℤ/4ℤ, +) and ((ℤ/8ℤ)*, ⋅), even though the two sets are the same size because |(ℤ/8ℤ)*| = φ(8) = 23 − 22 = 4 = |ℤ/4ℤ|.


[link]  5.5. Isomorphisms and Linear Equations of Congruence Classes

Fact: Let n be a positive integer. Then if + represents addition modulo n, we have:
ℤ/n
closure({1}, +)
In other words, 1 is a generator for ℤ/nℤ with respect to +.
Fact: Let a and n be coprime positive integers. Then if + represents addition modulo n, we have:
ℤ/n
closure({a}, +)
In other words, a can be a single generator for ℤ/nℤ with respect to +. This is equivalent to a fact we have already seen.
Fact: Let a and n be any two positive integers. Then if + represents addition modulo n, we have:
ℤ/(n/gcd(n,a))ℤ
closure({a}, +)
closure({gcd(n,a)}, +)
Example: Consider n = 6 and a = 4. Then we have g = gcd(4,6) = 2. We have:
closure({4}, +)
=
{4 ⋅ 0, 4 ⋅ 1, 4 ⋅ 2}
=
{0, 4, 2}
=
closure({2}, +)
Note that:
4
=
2 + 2 (mod 6)
2
=
4 + 4 (mod 6)
Thus, 2 can be expressed using the generator 4, and 4 can be expressed using the generator 2.
Fact (linear congruence theorem): Suppose that for a positive integer n and two congruence classes a ℤ/nℤ and b ℤ/nℤ where g ≡ gcd(a,n), we are given the following equation:
ax
b (mod n)
Since a and n are not coprime, we cannot solve the above equation. Furthermore, if b ∉ closure({a}, +), we know the equation cannot be solved. Since closure({a}, +) = closure({g}, +), the equation can only be solved if b closure({g}, +). In other words, the equation can only be solved if g|b.

Note that if g ≡ gcd(n,m) and b closure({g}, +), we have:
n
=
n' ⋅ g
a
=
a' ⋅ g
b
=
b' ⋅ g
(a' ⋅ g) ⋅ x
(b' ⋅ g) (mod (n' ⋅ g))
We can then rewrite the above as:
(a' ⋅ g) ⋅ x
=
(b' ⋅ g) + k ⋅ (n' ⋅ g)
We can divide both sides of the above equation by g:
a' ⋅ x
=
b' + kn'
We can convert the above equation back into an equation of congruence classes:
a' ⋅ x
b' mod n'
At this point a' and n' are coprime, we can compute a'-1 (mod n') and multiply both sides by it to find our solution x:
a'-1a' ⋅ x
a'-1b' mod n'
x
a'-1b' mod n'
Example: Solve the following equation for x ℤ/8ℤ, or explain why no solution exists:
2 ⋅ x
3 (mod 8)
Since 3 ∉ closure({2}, +), there is no solution x.
Example: Solve the following equation for x ℤ/24ℤ, or explain why no solution exists:
16 ⋅ x
7 (mod 24)
If we attempt to apply the linear congruence theorem, we will find that gcd(16,24) = 8, and 7 ∉ closure({8},+), which means there is no solution.
Example: Solve the following equation for x ℤ/15ℤ, or explain why no solution exists:
9 ⋅ x
6 (mod 15)
We know that gcd(9,15) = 3, so we can apply the linear congruence theorem:
3 ⋅ x
2 (mod 5)
We can now multiply both sides by 3-1 ≡ 2 (mod 5) to obtain the solution:
3-1 ⋅ 3 ⋅ x
3-1 ⋅ 2 (mod 5)
x
4 (mod 5)

[link]  5.6. Isomorphisms and the Chinese Remainder Theorem

Fact (Chinese remainder theorem isomorphism): Let n and m be coprime positive integers. Let ℤ/nℤ × ℤ/mℤ be the set product of ℤ/nℤ and ℤ/mℤ, and let ⊕ be an operation on ℤ/nℤ × ℤ/mℤ defined as follows:
(a,b) ⊕ (c,d)
=
(a + c,b + d)
Then it is true that (ℤ/nℤ × ℤ/mℤ, ⊕) ≅ (ℤ/(mn)ℤ, +). The bijective relationship in this isomorphism is as follows:
(a mod n, b mod m) ℤ/nℤ × ℤ/m
    
corresponds to
    
(a ⋅ (mm-1) + b ⋅ (nn-1)) ℤ/(mn)ℤ
In other words, given (a, b), we can map it to a ⋅ (mm-1) + b ⋅ (nn-1), and given some c = a ⋅ (mm-1) + b ⋅ (nn-1) from ℤ/(nm)ℤ, we can map it back to ℤ/nℤ × ℤ/mℤ using (c mod n, c mod m).
Example (partial homomorphic encryption with validation): Suppose that for some p ℕ, Alice wants to store a large number of congruence classes b1,...,bk (ℤ/pℤ)* in Eve's database. Alice does not have enough memory to store the congruence classes herself, but she does not want to reveal the congruence classes to Eve. Alice also wants Eve to compute the product of the congruence classes for her as in this example. However, because Alice does not actually have the numbers Eve is multiplying, Alice has no way to know that the product Eve returns to her corresponds to the actual product; perhaps Eve is saving money and cheating by returning a random number to Alice.
To address this, Alice first chooses a new prime q (distinct from p). She then computes n = pq and φ(n) = (p − 1) ⋅ (q − 1), finds e (ℤ/φ(n)ℤ)* and computes de-1 (mod φ(n)). This will allow Alice to encrypt things before storing them in Eve's database. However, at this point Alice will not simply encrypt her congruence classes b1, ..., bk.
Instead, Alice will first choose a single random value r ℤ/qℤ. Then, Alice will map each of her values (bi, r) ℤ/pℤ × ℤ/qℤ via the CRT isomorphism to some values ci ℤ/nℤ. Alice will then encrypt the values ci by computing cie (mod n), and will submit these values to Eve's database. Now, whenever Alice can ask Eve to compute the following product:
c1e ⋅ ... ⋅ cke
(c1 ⋅ ... ⋅ ck)e (mod n)
If Eve returns the product (c1 ⋅ ... ⋅ ck)e to Alice, Alice can decrypt it by computing:
((c1 ⋅ ... ⋅ ck)e)d
c1 ⋅ ... ⋅ ck (mod n)
Next, Alice can compute (c1 ⋅ ... ⋅ ck) mod p to retrieve the actual product in ℤ/pℤ. However, Alice also wants to make sure that Eve actually multiplied all the entries. Alice can do so by computing:
(c1 ⋅ ... ⋅ ck) mod q
r ⋅ ... ⋅ r (mod q)
rk (mod q)
Alice can quickly compute rk (mod q) and compare it to (c1 ⋅ ... ⋅ ck) mod q. This gives Alice some confidence (but not total confidence) that Eve actually computed the product because it ensures that Eve really did multiply k distinct values provided by Alice.
What is one way that Eve can still cheat and save money under these circumstances (if she knows that Alice is using this validation method)? Is there any way Alice can counter this (hint: what if Alice chooses different values r for each entry)?
Fact: Let n and m be positive integers. and let g = gcd(n,m). Let ℤ/(n/g)ℤ × ℤ/(m/g)ℤ × ℤ/gℤ be a set product, and let ⊕ be an operation on ℤ/(n/g)ℤ × ℤ/(m/g)ℤ × ℤ/gℤ defined as follows:
(a,b,c) ⊕ (x,y,z)
=
(a + x,b + y, c + z)
Then it is true that (ℤ/(n/g)ℤ × ℤ/(m/g)ℤ × ℤ/gℤ, ⊕) ≅ (ℤ/((mn)/g)ℤ, +).
Example: Consider the set ℤ/2ℤ × ℤ/3ℤ with the operation ⊕, and the set ℤ/6ℤ together with the operation +. It is true that (ℤ/2ℤ × ℤ/3ℤ, ⊕) ≅ (ℤ/6ℤ, +). The bijection is specified below.
ℤ/2ℤ × ℤ/3ℤ ℤ/6ℤ
(0,0)0
(0,1)4
(0,2)2
(1,0)3
(1,1)1
(1,2)5
The isomorphism is demonstrated below.
ℤ/2ℤ × ℤ/3ℤℤ/6ℤ
(0,0) ⊕ (0,0) = (0,0)0 + 0 = 0
(0,0) ⊕ (0,1) = (0,1)0 + 4 = 4
(0,0) ⊕ (0,2) = (0,2)0 + 2 = 2
(0,0) ⊕ (1,0) = (1,0)0 + 3 = 3
(0,0) ⊕ (1,1) = (1,1)0 + 1 = 1
(0,0) ⊕ (1,2) = (1,2)0 + 5 = 5
(0,1) ⊕ (0,0) = (0,1)4 + 0 = 4
(0,1) ⊕ (0,1) = (0,2)4 + 4 = 2
(0,1) ⊕ (0,2) = (0,0)4 + 2 = 0
(0,1) ⊕ (1,0) = (1,1)4 + 3 = 1
(0,1) ⊕ (1,1) = (1,2)4 + 1 = 5
(0,1) ⊕ (1,2) = (1,0)4 + 5 = 3
ℤ/2ℤ × ℤ/3ℤℤ/6ℤ
(0,2) ⊕ (0,0) = (0,2)2 + 0 = 2
(0,2) ⊕ (0,1) = (0,0)2 + 4 = 0
(0,2) ⊕ (0,2) = (0,1)2 + 2 = 4
(0,2) ⊕ (1,0) = (1,2)2 + 3 = 5
(0,2) ⊕ (1,1) = (1,0)2 + 1 = 3
(0,2) ⊕ (1,2) = (1,1)2 + 5 = 1
(1,0) ⊕ (0,0) = (1,0)3 + 0 = 3
(1,0) ⊕ (0,1) = (1,1)3 + 4 = 1
(1,0) ⊕ (0,2) = (1,2)3 + 2 = 5
(1,0) ⊕ (1,0) = (0,0)3 + 3 = 0
(1,0) ⊕ (1,1) = (0,1)3 + 1 = 4
(1,0) ⊕ (1,2) = (0,2)3 + 5 = 2
ℤ/2ℤ × ℤ/3ℤℤ/6ℤ
(1,1) ⊕ (0,0) = (1,1)1 + 0 = 0
(1,1) ⊕ (0,1) = (1,2)1 + 4 = 5
(1,1) ⊕ (0,2) = (1,0)1 + 2 = 3
(1,1) ⊕ (1,0) = (0,1)1 + 3 = 4
(1,1) ⊕ (1,1) = (0,2)1 + 1 = 2
(1,1) ⊕ (1,2) = (0,0)1 + 5 = 0
(1,2) ⊕ (0,0) = (1,2)5 + 0 = 5
(1,2) ⊕ (0,1) = (1,0)5 + 4 = 3
(1,2) ⊕ (0,2) = (1,1)5 + 2 = 1
(1,2) ⊕ (1,0) = (0,2)5 + 3 = 2
(1,2) ⊕ (1,1) = (0,0)5 + 1 = 0
(1,2) ⊕ (1,2) = (0,1)5 + 5 = 4
Since 1 and 5 are generators for ℤ/6ℤ with respect to +, the corresponding elements (1,1) and (1,2) are generators for ℤ/2ℤ × ℤ/3ℤ.
Fact: Suppose that for two positive integers n and m where g ≡ gcd(n,m) and two congruence classes a ℤ/nℤ and b ℤ/mℤ, we are given the following system of equations:
x
a (mod n)
x
b (mod m)
We then know that:
n
=
n' ⋅ g
m
=
m' ⋅ g
But this means that:
x
a (mod (n' ⋅ g))
x
b (mod (m' ⋅ g))
The above equations can be converted into facts about divisility:
x
=
a + k ⋅ (n' ⋅ g)
x
=
b + l ⋅ (m' ⋅ g)
But note that:
x
=
a + (kn')) ⋅ g
x
=
b + (l ⋅ (m')) ⋅ g
The above implies:
x
=
a (mod g)
x
=
b (mod g)
Since xx, it must be that:
a
=
b (mod g)
Thus, a solution x exists for the system of equations only if ab (mod (gcd(n,m)).
Fact: Suppose that for two positive integers n and m where g ≡ gcd(n,m) and two congruence classes a ℤ/nℤ and b ℤ/mℤ, we are given the following system of equations:
x
a (mod n)
x
b (mod m)
Note that because g ≡ gcd(n,m), we have:
n
=
n' ⋅ g
m
=
m' ⋅ g
To find a solution, first determine whether ab (mod g), and compute r = a (mod g). Then set:
x = y + r
We can now solve the following system for y:
y + r
a (mod n)
y + r
b (mod m)
We substruct r from both sides in both equations. We now have g | a-r and g | b-r, since r was the remainder when dividing a and b by g:
y
(ar) (mod n)
y
(br) (mod m)
Now set:
y = gz
We can now solve the following system for y:
gz
(ar) (mod (n' ⋅ g))
gz
(br) (mod (m' ⋅ g))
Using the linear congruence theorem on both equations, we get:
z
(ar)/g (mod n')
z
(br)/g (mod m')
We know that n' and m' are coprime, so we can solve for z using the usual method for solving systems of equations with coprime moduli. Once we find z, we can compute a solution x to the original system of equations:
x
gz + r (mod ((nm) / g))
Example: Suppose we want to solve the following system of equations:
x
1 (mod 6)
x
3 (mod 8)
First, we compute gcd(6,8) = 2. Then we check that 1 ≡ 3 (mod 2). Since this is true, we know we can find a solution. We proceed by subtracting 1 from both sides of both equations:
x − 1
0 (mod 6)
x − 1
2 (mod 8)
We can now apply the linear congruence theorem to both equations:
 
x − 1
2     
 
0 (mod 3)
 
x − 1
2     
 
1 (mod 4)
We can now solve the above system of equations using the usual CRT solution computation because the moduli are now coprime:
 
x − 1
2     
 
0 ⋅ (4 ⋅ 4-1) + 1 ⋅ (3 ⋅ 3-1)
0 + 1 ⋅ (3 ⋅ 3)
9
We now compute x:
 
x − 1
2     
 
9
x − 1
18
x
19
Since the range of unique CRT solutions with coprime moduli is ℤ/((6 ⋅ 8)/gcd(6,8))ℤ = ℤ/24ℤ, the congruence class solution is:
x
19 (mod 24)
greatest
common
divisor
algorithm
Fermat's
little
theorem
Euler's
theorem
Bézout's
identity
extended
Euclidean
algorithm
algorithm for
finding
multiplicative
inverses
Euler's
totient
function φ
Chinese
remainder
theorem
CRT solver
for two
equations
linear
congruence
theorem
general
CRT solver
for two
equations
induction general
CRT solver
for n
equations





formula for
3+4ℤ
primes
square roots
modulo p
Hensel's lemma square roots
modulo pk
CRT solver
for two
equations
square roots
modulo nm

[link]  5.7. Abstract Algebraic Structures and Axioms

Algebraic structures can be categorized according to the additional algebraic properties they possess. In this section we introduce terminology for several common types of algebraic structure.
Definition: Let G be a set, and let (A, ⊕) be an algebraic structure where A = closure(G, ⊕). In this case, we call A a magma and we call G the generating set or the set of generators of A.

In other words, a set A is a magma under operator ⊕ if:
  • A is closed under ⊕ (i.e., for all x, y A, xy A).
If (A, ⊕) possesses no other algebraic properties, we say (A, ⊕) is a free magma, or that (A, ⊕) is strictly a magma.
Definition: We call an algebraic structure (A, ⊕) a semigroup under ⊕ if:
  • A is closed under ⊕;
  • ⊕ is associative on A (for all x,y,z A, the equation (xy) ⊕ z = x ⊕ (yz) is true).
If (A, ⊕) possesses no other algebraic properties, we call it a free semigroup or we say it is strictly a semigroup.
Definition: We call an algebraic structure (A, ⊕) a monoid under ⊕ if:
  • A is closed under ⊕;
  • ⊕ is associative on A;
  • A contains an identity (which we call 1) in A (where for all x A, the equations 1x = x and x1 = x are always true).
If (A, ⊕) possesses no other algebraic properties, we call it a free monoid or we say it is strictly a monoid.
Definition: We call an algebraic structure (A, ⊕) a group under ⊕ if:
  • A is closed under ⊕;
  • ⊕ is associative on A;
  • A contains an identity;
  • A has inverses with respect to ⊕ (for all x A, there exists x-1 A such that xx-1 = 1 and x-1x = 1 are always true).
If (A, ⊕) possesses no other algebraic properties, we call it a free group or we say it is strictly a group.
Definition: We call an algebraic structure (A, ⊕) an abelian group under ⊕ if:
  • A is closed under ⊕;
  • ⊕ is associative on A;
  • A contains an identity;
  • A has inverses with respect to ⊕;
  • ⊕ is commutative on A (i.e., for all x,y A, the equation xy = yx is always true).
Fact: Define Sn to be the set of permutations of the set {0,...,n-1}. Together with the binary composition operation o on permutations, Sn is a group, and we call it the symmetric group on n element:
  • Sn is closed under composition of permutations (since Sn contains all of them);
  • composition of functions (including permutations) is associative;
  • there is an identity permutation [0,1,2,3,4,5,...,n-1];
  • every permutation p Sn has an inverse p-1 Sn such that p o p-1 = [0,1,...,n-1].
Notice that Sn is not commutative.
Fact: The set of cyclic permutations Cn is an abelian group (i.e., it is a commutative group).
Fact: The set of multiplication-induced permutations Mn is an abelian group (i.e., it is a commutative group).
Fact: For any n ℕ, the algebraic structures (ℤ/nℤ, +) and ((ℤ/nℤ)*, ⋅) are abelian groups.
Example: Recall that a magma is an algebraic structure with a set that is closed under a binary operation, and a free magma is such an algebraic structure with no additional algebraic properties. An example of a Python data structure that corresponds to such a structure is the set of nested lists. An implementation of the binary operation for such a data structure might be:

def op(x, y):
  return [x, y];
        
Notice that the above operation is not associative, has no identity or inverses, and is not commutative.

>>> op(1,2) == op(2,1)
False
>>> op(1,op(2,3)) == op(op(1,2),3)
False
        
Example: A free commutative magma is an algebraic structure with no algebraic properties except commutativity. An example of a Python data structure that corresponds to this algebraic structure is the set of nested sets. An implementation of the binary operation for such a data structure might be:

def op(x, y):
  return {x, y};
        
Notice that the above operation is not associative, has no identity or inverses, but is commutative.

>>> op(1,2) == op(2,1)
True
>>> op(3,op(1,2)) == op(op(2,1),3)
True
>>> op(1,op(2,3)) == op(op(1,2),3)
False
        
Example: A free monoid is an algebraic structure with no algebraic properties except associativity and the existence of an identity. An example of a Python data structure that corresponds to this algebraic structure is the set of lists (not nested lists). An implementation of the binary operation for such a data structure might be:

def op(x, y):
  return x + y # List concatenation done using +.
        
Notice that the above operation is associative and has the identity [].

>>> op([1],op([2],[3])) == op(op([1],[2]),[3])
True
>>> op([1],op([2],[3])) == op(op([1],[2]),[3])
True
>>> op([], [1,2,3]) == [1,2,3]
True
        
Example: Suppose a customer wants us to store a very large individual element x from each of the following sets in a distributed manner (i.e., we want to store it in parts on multiple computers). We assume that the customer does not care if we store (and return upon retrieval) an equivalent element. What additional annotation information will we need in order to reconstruct the element? What information can we eliminate, thus optimizing our use of storage space?
  • x A1, where A1 is a free magma;
  • x A2, where A2 is a monoid;
  • x A3, where A3 is a group and has an element y A3 s.t. yx takes much less space to store than x;
  • x A4, where A4 is a commutative semigroup.
The additional annotation information is as follows:
  • an element of a free magma is a binary tree in which every node has ordered children, so for any node whose children we want to store separately, we would need a unique pair of ordered identifiers for each child subtree;
  • an element x in a monoid is assembled using an associative operation, and we would not need to store anything about the grouping/hierarchization of the individual generators found in an element x, so it would be sufficient to use an integer to represent each separate portion (reassembly would involve sorting the pieces in ascending order by the integer associated with them, and then gluing them back together using the associative operation);
  • whenever we need to store a copy of x, we should instead store yx and return y-1 ⋅ (yx) = x to the user when we need to retrieve the stored element;
  • since a commutative semigroup's operation is both associative and commutative, we can group all the like generators in x together and simply store how many of each there are (for example, if x = abaabbacc, we would only store 4a3b2c).
The following table lists the correspondences between the parts of an algebraic structure and how they might correspond to a concrete data structure.
abstract algebraic structure concrete data structure
element in set of generators S individual character, string, or data object
set of generators S base cases; alphabet; data containers
operator ⊕ constructor for node with children; operator/function/method on data items
element in closure of S under ⊕ individual data object
closure of S under ⊕ set of all possible data objects
Suppose a concrete data structure corresponds to an algebraic structure with certain properties. The following table summarizes how those properties can inform or constrain implementations of algorithms or applications that operate on the concrete data sttructure.
magma commutative
magma
semigroup commutative
semigroup
monoid group abelian
group
typical
data
structures
binary trees
under node
constructor
binary trees
with unordered
branches under
node constructor
lists with
list concatenation;
strings with
string concatenation
sets with
duplicates
lists with
list concatenation
and empty list;
strings with
string concatenation
and empty string
distributed
computation
must operate on
data in its
original
ordering and
hierarchization
must operate on
data in its
original
hierarchization
must operate over
original ordering
can operate on
data in any
order
can employ identity
as a base case;
can ignore
identity entries
can "cancel" pairs of
adjacent inverses
can "cancel" all
elements that
can be paired
with an inverse
distributed
storage
must store original
ordering
and hierarchization
must store original
hierarchization
must store original ordering can store in any
order
no need to
store identities
compression common subtrees common hierarchies run-length encoding distinct elements
with quantities
can ignore
identity entries
can "cancel" pairs of
adjacent inverses;
can apply
invertible
transformations
can "cancel" all
elements that
can be paired
with an inverse
example/
test case
enumeration
can enumerate examples/test cases automatically
proving
implementation
works correctly
for all inputs
can show this by structural induction
Example: Suppose we have an algebraic structure (ℕ, max) consisting of the set ℕ and the binary operator max. Which algebraic properties does this algebraic structure satisfy?
  • ℕ is closed under max
  • max is associative on ℕ
  • max is commutative on ℕ
Notice that there are no identities or inverses. Thus, (ℕ, max) is a commutative semigroup.
Suppose that we have a distributed database (i.e., different parts of the database are on different computers) containing records that indicate the ages of individual people. These age values are represented as elements in ℕ. How can we compute the age of the oldest individual that has a record in the database?
Because max is associative and commutative over A, each computer can compute max over the records that are stored on it. Once each computer has computed its maximum, it can broadcast it to the other computers. One computer can then be designated to receive these maxima and take their maximum to obtain the final result.
Example: Consider the following set A:
A
=
{unknown} ∪ {(a,n) | a ℕ, n ℕ}
Consider the following definition of a binary operator ⊕ over A: given (a,n) and (b,m):
  • if gcd(n,m) = 1, then (a, n) ⊕ (b, m) = (c, (nm)) where c is the unique solution to the system of equations:
    c
    a (mod n)
    c
    b (mod m)
  • if gcd(n,m) ≠ 1, then (a, n) ⊕ (b, m) = unknown;
  • for any (a,n) A, unknown ⊕ (a, n) = unknown;
  • for any (a,n) A, (a, n) ⊕ unknown = unknown.
Is A an algebraic structure? What are its algebraic properties? What does this tell us about how we can implement an algorithm that performs this operation over many elements in A?
Yes, A is an algebraic structure in which the operation ⊕ is associative, commutative, and has (1,0) as the identity element (because every integer is a solution to x ≡ 0 (mod 1)). Thus, A is at least a commutative monoid.
The fact that the operation is commutative and associative tells us that we do not need to worry about the order in which we perform the operation on a collection of elements (we can perform the operation on any subset of the elements in any grouping, then put this solution together with other solutions assembled from other subsets of the overall set of elements). This means we can distribute the computation of the operation across many computers if we wish. The identity element allows us to easily include a base case in an iterative or recursive algorithm for computing the operation over a collection of elements.

[link]  5.8. Subgroups and the Hidden Subgroup Problem

If we look at subsets of the elements in a group, we might find that certain subsets are closed under the operator for that group. These subsets are called subgroups, and the concept of a subgroup can be very useful when studying and using groups.
Definition: Let A be a group under the operator ⊕. We say that B is a subgroup of A if BA, B is closed under ⊕, and B is a group.
Example: The following are all the subgroups of ℤ/4ℤ under addition + modulo 4:
  • {0}, because all terms of the form 0, 0+0, 0+0+0, and so on are equivalent to 0;
  • {0,2}, since closure({0,2}, +) = {0,2};
  • {0,1,2,3} = ℤ/4ℤ.
The following are all the subgroups of ℤ/6ℤ under addition + modulo 6
  • {0}, because all terms of the form 0, 0+0, 0+0+0, and so on are equivalent to 0;
  • {0,2,4}, since closure({2}, +) = closure({0,2,4}, +) = {0,2,4};
  • {0,3}, since closure({3}, +) = closure({0,3}, +) = {0,3};
  • {0,1,2,3,4,5} = closure({1}, +) = ℤ/6ℤ.
Fact: Given some n ℕ and some factor f ℕ such that f|n, then (closure({f}, +), +) is a subgroup of (ℤ/nℤ, +), and it is isomorphic to the group (ℤ/(n/f)ℤ, +).
Example: The following are all the non-trivial subgroups of ℤ/6ℤ under addition + modulo 6, together with their corresponding isomorphic group:
  • ({0,2,4}, +) ≅ (ℤ/(6/2)ℤ) ≅ (ℤ/3ℤ, +);
  • ({0,3}, +) ≅ (ℤ/(6/3)ℤ) ≅ (ℤ/2ℤ, +).
The notion of a subgroup allows us to introduce an alternative definition for prime numbers.
Definition: Given an integer p ℕ where p > 1, we say that p is prime if the only subgroups of (ℤ/pℤ, +) are the trivial subgroups ({0}, +) and (ℤ/pℤ, +).
Conjecture (hidden subgroup problem): The following problem is not in P: given a group (A, ⊕), find a non-trivial subgroup of A (non-trivial means not the subgroup that contains only the identity, ({I}, ⊕), and not the subgroup consisting of the entire group, (A, ⊕)).
Often, we are interested in a more restricted versions of this problem, which are also not believed to be in P:
  • finding a non-trivial subgroup of (ℤ/nℤ, +);
  • finding a non-trivial subgroup of ((ℤ/nℤ)*, ⋅).
Example: Suppose that for some n ℕ, we are given a ℤ/nℤ such that gcd(a, n) = 1. We know from Euler's theorem that aφ(n) ≡ 1 (mod n). However, φ(n) is not necessarily the smallest exponent of a that will yield 1.
For example, consider 3 ℤ/8ℤ. Even though φ(8) = 23 - 22 = 4 and 34 ≡ 1 (mod 8), it is also true that 32 ≡ 9 ≡ 1 (mod 8).
Thus, given some a ℤ/nℤ such that gcd(a, n) = 1, the problem of determining the smallest r such that ar ≡ 1 (mod n) amounts to finding the smallest subgroup of ((ℤ/nℤ)*, ⋅) that contains a.
Example: Suppose we want to find all the solutions in ℤ/6ℤ to the following equation:
2 ⋅ x
4 (mod 6)
Using the linear congruence theorem, we can find the unique solution modulo 3:
x
2 (mod 3)
The solutions modulo 6 will be those congruence classes in ℤ/6ℤ whose integer reprsentatives are members of 2 + 3ℤ = {..., 2, 5, 8, 11, 14, ...}. These are 2 + 6ℤ and 5 + 6ℤ, since 2 ≡ 2 (mod 3) and 5 ≡ 2 (mod 3). Note that:
2 + 3ℤ
=
2 + 6ℤ ∪ 5 + 6ℤ
{..., 2, 5, 8, 11, 14, 17, 20, ...}
=
{..., 2, 8, 14, 20, ...} ∪ {..., 5, 11, 17, ...}

[link]  5.9. Structural Induction and Direct Product of Algebraic Structures

Suppose an algebraic structure A with operator ⊕ has a generating set. Since any element in A must be either a genrator or some finite number of generators combined using the operator ⊕, we can use induction to prove facts (i.e., true formulas) about all the elements in A.
Fact (fundamental theorem of arithmetic): Consider a commutative semigroup A with generating set S = { p | p ℕ, p is prime } and an operator ⊗ that is associative and commutative. Let ℕ be the commutative semigroup of positive integers { n | n ℕ, n ≥ 2 } with the integer multiplication operator. The semigroup A is isomorphic to ℕ:
(A, ⊗)
(ℕ, +)
Equivalently, we can say that every natural number has a unique prime factorization (notice that the algebra A is actually the set of prime factorizations, with ⊗ corresponding to multiplication of prime factorizations).
Example: We have introduced the following definition of the totient function φ.
φ(m)
=
|{k | k {1,...,m}, gcd(k,m) = 1}|
We have also introduced the following properties of the totient function φ.
φ(p)
=
p - 1 & & if p is prime
φ(pk)
=
pk - pk-1 & & if p is prime
φ(nm)
=
φ(n) ⋅ φ(m) & & if gcd(n,m) = 1
Is the set of three equations above equivalent to our one-line definition of φ? That is, if we know the factorization of any input n > 1 to φ, can we compute φ exactly using the three equations above?
The answer is "yes". Consider the algebraic structure consisting of the set {2,3,4,...} and the operation ⋅ (integer multiplication). Then the set of generators of A is the set of prime numbers. Thus, any element x A is a product of some collection of not necessarily distinct prime numbers p1 ⋅ ... ⋅ pk. If we write down n as this product, we can always break down our computation of φ(n) into smaller computations until we reach the base case.


[link]  5.10. Assignment #6: Generalized CRT, Data Structures, and More Isomorphisms

In this assignment you will solve several problems using the generalized Chinese remainder theorem and other techniques and facts you have learned in this course. You must submit a single file named hw6.py (submitted to the location hw6/hw6.py). Please follow the gsubmit directions.

For the programming parts, you may import the following library functions in your module (you may not need all these functions for this assignment depending on how you approach the problems, but they may be used):

from math import floor
from fractions import gcd
from random import randint
        
Your file may not import any other modules or employ any external library functions associated with integers and sets unless explicitly permitted to do so in a particular problem.
  1. Solve the following equations using step-by-step equational reasoning, and list each step.
    1. Solve the following equation for a unique congruence class x if it exists (you must specify both the congruence class and the set of congruence classes of which it is a member); if no solution exists, explain why not:
      8 ⋅ x
      16 (mod 32)
    2. Solve the following system of equations and find a unique congruence class solution if it exists; if no solution exists, explain why not:
      x
      8 (mod 12)
      x
      4 (mod 16)
    3. Solve the following equation for a unique congruence class x if it exists (you must specify both the congruence class and the set of congruence classes of which it is a member); if no solution exists, explain why not:
      x
      10 (mod 14)
      x
      17 (mod 21)
    4. Solve the following system of equations and find all congruence class solutions if any exist; if no solution exists, explain why not:
      x2
      4 (mod 35)
      3 ⋅ x
      15 (mod 21)
    5. Solve the following equation for all congruence classes x ℤ/26ℤ that solve the equation (note that there may be more than one because we are looking for congruence classes in ℤ/26ℤ and not any other set of congruence classes).
      2 ⋅ x
      12 (mod 26)
    6. List all subgroups of the algebraic structure (ℤ/8ℤ, +), where + represents addition modulo 8. You only need to specify the set of elements in each subgroup.
  2. Solve the following problems by setting up an appropriate equation or system of equations and using step-by-step equational reasoning to find the solution.
    1. Suppose that you have a standard 12-hour analog clock. You also have an alarm that rings every 16 hours. At some point in the last two days, the alarm rang at exactly midnight. At this moment, the analog clock reads 3 (you do not know if it is AM or PM), and it has been 11 hours since the alarm last rang. How many hours have passed since the alarm rang at exactly midnight, and what time is it?
    2. Bob needs to reserve virtual machines on a cloud computing service to solve a number of problems before a deadline. He has two options: reserve some number of virtual machines that can each solve 12 problems before the deadline, or reserve some number of virtual machines that can each solve 15 problems before the deadline:
      • with virtual machines that can each solve 12 problems, Bob will end up using all the virtual machines to their full capacity except one, which will have only 2 problems to solve;
      • with virtual machines that can each solve 15 problems, Bob will end up using all the virtual machines to their full capacity except one, which will have only 8 problems to solve.
      Answer both of the following questions:
      • Assuming that Bob has at most 60 problems to solve, how many problems does Bob have?
      • If Bob can only reserve batches of machines that all have the same capacity (capacities are always 2 or greater), and the the fastest machine can only solve 19 problems before the deadline, is there anything Bob can do to avoid having at least one machine not used to its full capacity?
    3. Alice stored a 16-bit integer x in an 8-bit memory location (effectively storing x mod 28), and then she also saved x in a separate 4-bit memory location (effectively storing x mod 24). If the integer x is a password that unlocks Alice's computer, and Eve has has access to both memory locations (and nothing else), what is the maximum number of possible passwords that Eve will need to try if she wants to unlock Alice's computer?
  3. Implement the following Python functions for solving systems of equations involving congruence classes.
    1. Implement a function solveOne(c, a, m) that takes three integers c, a, and m ≥ 1. If it exists, the function should return the solution x {0, ..., m-1} to the following equation:
      cx
      a (mod m)
      If no solution exists, the function should return None. The function must work correctly for all possible equations (you should use the linear congruence theorem).

      >>> solveOne((1,2,3))
      2
      >>> solveOne(3, 4, 7)
      6
      >>> solveOne(1, 5, 11)
      5
      >>> solveOne(2, 3, 8)
      None
      >>> solveOne(6, 2, 8)
      3
                    
    2. Implement a function solveTwo(e1, e2) that takes two tuples e1 and e2 as inputs, each of the form (c, a, m) (i.e., containing three integer elements). Each tuple (c, a, m) corresponds to an equation of the form:
      cx
      a (mod m)
      Thus, the two tuples, if we call them (c, a, m) and (d, b, n), correspond to a system of equations of the form:
      cx
      a (mod m)
      dx
      b (mod n)
      The function solveTwo() should return the unique solution x to the above system of equations. If either equation cannot be solved using solveOne(), the function should return None.

      >>> solveTwo((3, 4, 7), (1, 5, 11))
      27
      >>> solveTwo((1, 1, 6), (1, 3, 8))
      19
      >>> solveTwo((1, 0, 6), (1, 3, 8))
      None
                    
    3. Implement a function solveAll(es) that takes a list of one or more equations, each of the form (c, a, m). The function should return the unique solution x to the system of equations represented by the list of equations. If the system of equations has no solution, the function should return None.


      >>> solveAll([(1,2,3)])
      2
      >>> solveAll([(3,4,7), (1,5,11)])
      27
      >>> solveAll([(5,3,7), (3,5,11), (11,4,13)])
      856
      >>> solveAll([(1,2,3), (7,8,31), (3,5,7), (11,4,13)])
      7109
      >>> solveAll([(3,2,4), (7,8,9), (2,8,25), (4,4,7)])
      554
      >>> solveAll([(1, 1, 6), (1, 3, 8)])
      19
      >>> solveAll([(1, 0, 6), (1, 3, 8)])
      None
                    
  4. In this problem you will implement a reliable addition algorithm for ℤ/256ℤ by using an unreliable addition algorithm for ℤ/256ℤ. You may not use the addition operator + anywhere in your solutions to this problem. You may assume you are given access to a function plus256unreliable(x, y) that returns an answer that is at most 4 away from the true sum modulo 256 (assume there is no chance of this error causing the answer to wrap around):
    | plus256unreliable(x, y) − ((x + y) % 256) |
    4
    You may use the following Python simulation of this unreliable function in order to test your code (note that the below code has been updated, as it had inaccurate behavior that did not reflect the problem statement above):

    from random import randint
    def plus256unreliable(x, y):
        r = randint(0,7) - 4
        return (min(255, max(0, ((x + y)%256) + r)))
              
    1. Implement a Python function plus16(x, y) that reliably returns (x + y) % 16 with 100% accuracy. You may use //, *, plus256unreliable(), and numerical constants, but you may not use anything else in your definition. Hint: you can call plus256unreliable() more than once.
    2. Implement a Python function plus256(x, y) that reliably returns (x + y) % 256 with 100% accuracy. Your solution must use solveAll(), and you are allowed to use %, but you may not use the addition or subtraction operators: choose three or more appropriate prime or mutually coprime moduli, perform the addition operations modulo those moduli, then restore the original result using solveAll().
  5. Extra credit: The group S2C2 ≅ ℤ/2ℤ is the only group containing two elements; there is no way to build a group containing two elements that is not isomorphic to these. Solve the following problems involving other small groups.
    1. Suppose we have an algebraic structure ({a, b, c}, ⊗) where ⊗ is associative, commutative, and there is an identity a where bc = cb = a. Explain why it must be the case that ({a, b, c}, ⊗) ≅ (ℤ/3ℤ, +) by writing out the full multiplication tables for both sets. Hint: in some cases, you will need to use equational reasoning over ({a, b, c}, ⊗) to show why there is only one valid possibility.
    2. The smallest possible non-cyclic group is called the Klein group, and is defined to be V = (closure({a, b, c}, ⊕), ⊕), where a is an identity and:
      bb
      =
      a
      cc
      =
      a
      (bc) ⊕ (bc)
      =
      a
      Note that V is a group, so bc = cb. Determine how many distinct elements there are in the group (i.e., provide an exhaustive set of distinct elements and informally or formally explain why all possible terms involving ⊗, a, b, and c must equal one of these elements).



[link]  Review #2. Algebraic Structures and their Properties

This section contains a comprehensive collection of review problems going over all the course material. Many of these problems are an accurate representation of the kinds of problems you may see on an exam.
Exercise: Find all x ℤ/29ℤ that satisfy the following:
y2
16 (mod 29)
x2
y (mod 29)
We first solve for all possible y. Since 29 > 16, we have that ± 4 are the square roots of 16 in ℤ/29ℤ. Thus, we have two solutions for y:
y
{4, 29 − 4}
y
{4, 25}
We want to find all x that satisfy the system, so we need to solve for x for each possible y. Thus, we have:
y
4 (mod 29)
x2
4 (mod 29)
x
± 2 (mod 29)
x
{2, 29 − 2}
x
{2, 27}
We also have:
y
25 (mod 29)
x2
25 (mod 29)
x
± 5 (mod 29)
x
{5, 29 − 5}
x
{5, 24}
Thus, the possible solutions for x are:
x
{2, 5, 24, 27}
Notice also that the problem could have been stated as:
x4
16 (mod 29)
Exercise: Determine which properties each of the following algebraic structures satisfy.
  1. The generating set G = {∅, {1}, {2}, {3}} with the union operation ∪ as the binary operator.
    We know that closure(G, ∪) is the set of subsets of {1,2,3}:
    closure(G, ∪)
    =
    {∅, {1}, {2}, {3}, {1,2}, {2,3}, {1,3}, {1,2,3}}
    Thus, we know from set theory that the union operation on sets is associative and commutative. There is an identity element ∅. There are no inverses.
  2. For positive integers a,n ℕ where gcd(a,n) = 1, generating set G = {a} with integer addition modulo n.
    The closure is the entire set {0,..., n − 1} because:
    {a, a + a, a + a + a, ..., a + ... + a}
    =
    {(ia) mod n | i {1 ,..., n}}
    Integer addition modulo n is associative and commutative, has identity 0, and for every x ℤ/nℤ, the inverse is n-x ℤ/nℤ.
Exercise: Solve the following problems.
  1. Consider the following two circular shift permutations:
    [8,9,0,1,2,3,4,5,6,7]
    [5,6,7,8,9,0,1,2,3,4]
    How many of each would you need to compose to obtain the permutation [9,0,1,2,3,4,5,6,7,8]?
    These permutations specified are in C10, and we know that C10 ≅ ℤ/10ℤ. Notice that [8,9,0,1,2,3,4,5,6,7] C10 can correspond to 8 ℤ/10ℤ, that [5,6,7,8,9,0,1,2,3,4] C10 can correspond to 5 ℤ/10ℤ, and that [9,0,1,2,3,4,5,6,7,8] C10 can correspond to 9 ℤ/10ℤ. By Bézout's identity we have that:
    8 ⋅ 2 + 5 ⋅ (-3)
    =
    1
    8 ⋅ 2 + 5 ⋅ 7
    1 (mod 10)
    8 ⋅ (9 ⋅ 2) + 5 ⋅ (9 ⋅ 7)
    9 ⋅ 1 (mod 10)
    8 ⋅ 8 + 5 ⋅ 3
    9 (mod 10)
    Thus, we would need to compose 8 instances of the first permutation and 3 instances of the second permutation to obtain [9,0,1,2,3,4,5,6,7,8].
  2. Rewrite the permutation [3,4,0,1,2] as a composition of adjacent swap permutations.
    We can simply run the bubble sort algorithm on the above permutation and record the permutation that represents each swap. This leads to the following sequence:
    p1
    =
    [0,2,1,3,4]
    p2
    =
    [0,1,3,2,4]
    p3
    =
    [0,1,2,4,3]
    p4
    =
    [1,0,2,3,4]
    p5
    =
    [0,2,1,3,4]
    p6
    =
    [0,1,3,2,4]
    Thus, by applying the above sequence of permutations to [0,1,2,3,4] in reverse order, we can obtain [3,4,0,1,2], so the factorization of [3,4,0,1,2] into adjacent swap permutations is:
    [3,4,0,1,2]
    =
    p1 o p2 o p3 o p4 o p5 o p6 o [0,1,2,3,4]
Exercise: Suppose we want to perform k exponentiation operations (e.g., if k = 4, we want to compute (((ab)c)d)e) modulo 21. Assume the following:
  • a single exponentiation operation modulo 21 takes 213 = 9261 steps;
  • a single exponentiation operation modulo 3 takes 33 = 27 steps;
  • a single exponentiation operation modulo 7 takes 73 = 343 steps;
  • an exponentiation operation modulo 3 and an exponentiation operation modulo 7 together take 343 + 27 = 370 steps;
  • solving a two-equation system for two values, a modulo 3 and b modulo 7, takes 8000 steps using CRT;
  • we can either compute the exponentiation sequence directly modulo 21, or we can split it into two sequences of computations (one modulo 3, the other modulo 7) and then recombine using CRT at the end.
  1. What is the number of steps needed to perform k exponentiations modulo 21?
    f(k)
    =
    9261 ⋅ k
  2. What is the number of steps needed to perform k exponentiations modulo 3 and k exponentiations modulo 7, then to recombine using CRT?
    f(k) = 370 ⋅ k + 8000
Exercise: Suppose we want to solve the following equation for any congruence classes in ℤ/6ℤ that solve it:
4 ⋅ x + 3 ⋅ x2
2 (mod 6)
One approach is to use the Chinese remainder theorem and split the problem into two equations by factoring 6:
4 ⋅ x + 3 ⋅ x2
2 (mod 2)
4 ⋅ x + 3 ⋅ x2
2 (mod 3)
We can now simplify each of the above:
3 ⋅ x2
0 (mod 2)
4 ⋅ x
2 (mod 3)
We can simplify each further:
x2
0 (mod 2)
x
2 (mod 3)
We know that the only solution to the first is x ≡ 0 (mod 2), so we have now obtained the following system of equations; we can use CRT to find the unique solution modulo 6:
x
0 (mod 2)
x
2 (mod 3)
x
2 (mod 6)
We can check that, indeed, 4 ⋅ 2 + 3 ⋅ 22 ≡ 20 ≡ 2 (mod 6).
Exercise: Solve the following equation for all congruence classes in ℤ/7ℤ that satisfy it:
x2 − 3 ⋅ x
0 (mod 7)
Exercise: Find solutions to the following problems.
  1. Explain why the following polynomial has no integer solutions (Hint: you only need to evaluate the polynomial for two possible values of x):
    x4 + x2 + 3
    =
    0
    If the above equation has an integer solution x, then it must have an integer solution modulo 2, since we can take the modulus of both sides:
    x4 + x2 + 3
    =
    0
    (x4 + x2 + 3) mod 2
    =
    0 mod 2
    x4 + x2 + 3
    0 (mod 2)
    Thus, we have using logic that:
    (exists solution in ℤ)
    (exists solution modulo 2)
    (no solution modulo 2)
    (no solution in ℤ)
    Note that this only works in one direction. A solution modulo 2 does not necessarily imply that there is an integer solution.

    We see that for x = 0 and x = 1, the left-hand side is odd. The right-hand side is 0, so it is always even. Thus, no integer solution exists.
  2. Find at least one solution x ℤ/10ℤ to the following system of equations (you must use Bézout's identity):
    6 ⋅ y + 5 ⋅ x - 1
    0 (mod 10)
    x2
    y (mod 10)
    Since 5 and 6 are coprime, we can find a solution to the first equations. One such solution is:
    y
    1 (mod 10)
    x
    -1 (mod 10)
    9 (mod 10)
    We also have that 92 ≡ 81 ≡ 1 (mod 10). Thus, since 12 ≡ 1 (mod 10), both equations are satisfied by this solution.
Exercise: Find solutions to the following problems.
  1. Suppose you want to send some s ℤ/nℤ to Alice and Bob, but you want to ensure that the only way Alice and Bob can retrieve s is if they work together. What two distinct pairs (s1, p1) and (s2, p2) would you send to Alice and Bob, respectively, so that they would need to work together to recover s?
    You would need to send (s mod p, p) to Alice and (s mod q, q) to Bob where s < pq and p and q are distinct and coprime.
  2. Suppose Bob is generating a public RSA key; he chooses a very large prime p, and then he chooses q = 2. Why is this not secure?
    Bob must share his public key (n, e). Since n = p ⋅ 2, n is even. This can immediately be seen by looking at the last bit of n (which will be 0, since n mod 2 = 0). It is then easy to recover the secret value p.
  3. Suppose Alice and Bob use Shamir secret sharing to share a password s to a lock that is not protected from brute force attacks (i.e., anyone can keep trying different passwords until they unlock it). Alice holds s mod p and Bob holds s mod q, where s < pq. However, suppose that Bob happens to be using q = 2, and Alice knows this. What can Alice do to quickly break the lock?
    If Alice holds a and Bob holds b, the system of equations that would be set up to recover s is as follows:
    x
    a (mod p)
    x
    b (mod 2)
    Notice that there are only two possibilities for Bob's value b ℤ/2ℤ. Thus, Alice could set up two systems, one for b = 0 and another for b = 1, solve both, and try both secrets s on the lock.
Exercise: Suppose that Alice, Bob, Carl, and Dan are sharing a secret s using Shamir secret sharing, where each participant is assigned a distinct modulus n that is coprime to everyone else's modulus. Each participant is holding a part of the secret s mod n, and the secret can be recovered by any two participants. However, Eve has sabotaged the value stored by one one the participants. Below are the values currently stored by everyone; one of them is corrupted.
  • Alice: nAlice = 3 and (s mod 3) = 2
  • Bob: nBob = 4 and (s mod 4) = 3
  • Carl: nCarl = 5 and (s mod 5) = 2
  • Dan: nDan = 7 and (s mod 7) = 4
  1. Which participant's stored value s mod n has Eve sabotaged?
    Any two participants can recover the secret s by setting up a system with two equations and solving the system using CRT. We list all of the possible combinations and solve for s to find which participant's value has been corrupted.
    • Alice and Bob:
      s
      2 (mod 3)
      s
      3 (mod 4)
      s
      11 (mod 12)
    • Alice and Carl:
      s
      2 (mod 3)
      s
      2 (mod 5)
      s
      2 (mod 15)
    • Alice and Dan:
      s
      2 (mod 3)
      s
      4 (mod 7)
      s
      11 (mod 21)
    • Bob and Carl:
      s
      3 (mod 4)
      s
      2 (mod 5)
      s
      7 (mod 20)
    • Bob and Dan:
      s
      3 (mod 4)
      s
      4 (mod 7)
      s
      11 (mod 28)
    • Carl and Dan:
      s
      2 (mod 5)
      s
      4 (mod 7)
      s
      32 (mod 35)
    Since three of the participants can consistently recover the same secret s = 11, and all pairs of participants in which Carl is present do not yield s = 11 and are inconsistent with one another, it must be Carl's data that has been sabotaged.
  2. What is the correct secret value s?
    Since three of the six possible pairings result in s = 11, and the other three are inconsistent and all involve Carl, it must be that s = 11 was the original uncorrupted secret.
  3. What's the number of different shared secret values these four participants can store (assuming they use the same moduli, and require that any two members should be able to recover the secret).
    Since any two participants must be able to recover s, it must be that s < nm for every possible pair n and m. The smallest two values are 3 and 4, so 3 ⋅ 4 - 1 = 11, where 11 ℤ/(3 ⋅ 4)ℤ, is the largest possible s that can be receovered.
  4. Suppose you want to store an n-bit number s. You want to store it in a way that makes it possible to recover s even if one of the bits is corrupted. How can you accomplish this using at most approximately 2 ⋅ n bits?
    Choose four distinct coprime numbers m1, m2, m3, and m4 such that s is less than the product of every pair of these, but such that the product of any pair is not much larger than s (e.g., m1 and m2 can be stored in the same number of bits as s). Then store (s mod m1, s mod m2, s mod m3, s mod m4) using about 2 ⋅ n bits.

    If any individual bit is corrupted, this corrupts at most one of the four values stored. As with Alice, Bob, and Dan, the original value can still be recovered.

[link]  Appendix A. Using gsubmit

In this course, you will submit your assignments using gsubmit. This section reproduces and extends some of the instructions already made available by the BU Computer Science Department.

[link]  A.1. Register for a CS account

You must obtain a CS account to use the csa machines maintained by the CS Dept. You will need to physically visit the undergraduate computing lab located at 730 Commonwealth Avenue, on the third floor in room 302.

[link]  A.2. Download SSH/SCP client software

You will need an SCP or SFTP client (such as WinSCP for Windows or CyberDuck for OS X) to copy files from your local computer to your csa home directory. If you are using Windows, you will also need an SSH client (such as PuTTY).

[link]  A.3. Submitting assignments using gsubmit

A typical workflow can be described as follows.
  1. You assemble your assignment solution file(s) on your own computer or device.
    local
    device
    hw1
    hw1.py
        your csa2/csa3
    home directory
        your gsubmit
    directory for CS 235
  2. You log into csa2 or csa3 using an SCP or SSH client and create a directory for your submission in your CS account home directory. Note that in the examples below %> represents a terminal prompt, which may look different on your system.

    %> cd ~
    %> mkdir hw1
              
    local
    device
    hw1
    hw1.py
        your csa2/csa3
    home directory
    hw1


        your gsubmit
    directory for CS 235
  3. If you have not already done so (e.g., if you were using an SSH client in the previous step), you log into csa2 or csa3 using an SCP client and copy your completed file(s) into that directory.
    local
    device
    hw1
    hw1.py
    your csa2/csa3
    home directory
    hw1
    hw1.py
        your gsubmit
    directory for CS 235
  4. If you have not already done so, you log into csa2 or csa3 using an SSH client and run the gsubmit commands to copy the files from your CS account home directory to the gsubmit directories to which the course staff has access.

    %> cd ~
    %> gsubmit cs235 hw1
              
    local
    device
    hw1
    hw1.py
    your csa2/csa3
    home directory
    hw1
    hw1.py
    your gsubmit
    directory for CS 235
    hw1
    hw1.py
  5. To view your submitted files, you can use the following command:

    %> gsubmit cs235 -ls
              
    To look at a file that has already been submitted, you can use:

    %> gsubmit cs235 -cat hw1/hw1.py
              
    After grades are posted (normally, this will be announced on the mailing list and in lecture), you can check your grade using:

    %> gsubmit cs235 -cat grade.hw1.txt
              

[link]  Appendix B. Python

The Python programming language will be among the languages we use in this course. This language supports the object-oriented, imperative, and functional programming paradigms, has automatic memory managememt, and natively supports common high-level data structures such as lists and sets. Python is often used as an interpreted language, but it can also be compiled.

[link]  B.1. Obtaining Python

The latest version of Python 3 can be downloaded at: https://www.python.org/downloads/. In this course, we will require the use if Python 3, which has been installed on all the CS Department's undergraduate computing lab machines, as well as on csa2/csa3.

[link]  B.2. Assembling a Python module

The simplest Python program is a single file (called a module) with the file extension .py. For example, suppose the following is contained within a file called example.py:

# This is a comment in example.py.
# Below is a Python statement.
print("Hello, world.")
      
Assuming Python is installed on your system, to run the above program from the command line you can use the following (you may need to use python3, python3.2, python3.3, etc. depending on the Python installation you're using). Note that in the examples below %> represents a terminal prompt, which may look different on your system.

%> python example.py
Hello, world.
      
If you run Python without an argument on the command line, you will enter Python's interactive prompt. You can then evaluate expressions and execute individual statements using this prompt; you can also load and execute a Python module file:

%> python
Python 3.2 ...
Type "help""copyright""credits" or "license" for more information.
>>> exec(open("example.py").read()) # Load example.py module.
Hello, world.
>>> x = "Hello." # Execute an assignment statement.
>>> print(x)     # Execute a print statement.
Hello.
>>> x            # Evaluate a string expression.
'Hello.'
>>> 1 + 2        # Evaluate a numerical expression.
3
      

[link]  B.3. Common data structures (i.e., Python expressions)

Python provides native support for several data structures that we will use throughout this course: integers, strings, lists, tuples, sets, and dictionaries (also known as finite maps). In this subsection, we present how instances of these data structures are represented in Python, as well as the most common operations and functions that can be applied to these data structure instances.
  • Booleans consist of two constants: True and False.
    • The usual logical operations are available using the operators and, or, and not.

    >>> True                                      # A boolean constant.
    True
    >>> False                                     # A boolean constant.
    False
    >>> True and False or True and (not False)    # A boolean expression.
    True
              
  • Integers are written as in most other programming languages (i.e., as a sequence of digits).
    • The usual arithmetic operations are available using the operators +, *, -, and /. The infix operator // represents integer division, and the infix operators ** represents exponentiation. Negative integers are prefixed with the negation operator -.
    • The usual relational operators ==, !=, <, >, <=, >= are available.
    • The int() function can convert a string that looks like an integer into an integer.

    >>> 123                                       # An integer constant.
    True
    >>> 1 * (2 + 3) // 4 - 5                      # An integer expression.
    -4
    >>> 4 * 5 >= 19                               # A boolean expression involving integers.
    True
    >>> int("123")                                # A string being converted into an integer
    123
              
  • Strings are delimited by either ' or " characters. Strings can be treated as lists of single-character strings. Another way to look at this is that there is no distinction between a character and a string: all characters are just strings of length 1. Multiline strings can be delimited using """ or ''' (i.e., three quotation mark characters at the beginning and end of the string literal).
    • The empty string is denoted using '' or "".
    • Two strings can be concatenated using +.
    • The function len() returns the length of a string.
    • Individual characters in a string can be accessed using the bracketed index notation (e.g., s[i]). These characters are also strings themselves.

    >>> 'Example.'                                # A string.
    'Example.'
    >>> "Example."                                # Alternate notation for a string.
    'Example.'
    >>> len("ABCD")                               # String length.
    4
    >>> "ABCD" + "EFG"                            # String concatenation.
    'ABCDEFG'
    >>> "ABCD"[2]                                 # Third character in the string.
    'C'
              
  • Lists are similar to arrays: they are ordered sequences of objects and/or values. The entries of a list can be of a mixture of different types, and lists containing one or more objects are delimited using [ and ], with the individual list entries separated by commas. Lists cannot be members of sets.
    • The empty list is denoted using [].
    • Two lists can be concatenated using +.
    • The function len() returns the length of a list.
    • Individual entries in a list can be accessed using the bracketed index notation (e.g., a[i]).
    • To check if a value is in a list, use the in relational operator.

    >>> [1,2,"A","B"]                             # A list.
    [1, 2, 'A''B']
    >>> [1, 2] + ['A','B']                        # Concatenating lists.
    [1, 2, 'A''B']
    >>> len([1,2,"A","B"] )                       # List length.
    4
    >>> [1,2,"A","B"][0]                          # First entry in the list.
    1
    >>> 1 in [1, 2]                               # List containment check.
    True
              
  • Tuples are similar to lists (they are ordered, and can contain objects of different types), except they are delimited by parentheses ( and ), with entries separated by commas. The main distinction between lists and tuples is that tuples are hashable (i.e., they can be members of sets).
    • The empty tuple is denoted using ().
    • A tuple containing a single object x is denoted using (x, ).
    • Two tuples can be concatenated using +.
    • A tuple can be turned into a list using the list() function.
    • A list can be turned into a tuple using the tuple() function.
    • The function len() returns the length of a tuple.
    • Individual entries in a tuple can be accessed using the bracketed index notation (e.g., t[i]).
    • To check if a value is in a tuple, use the in relational operator.

    >>> (1,2,"A","B")                             # A tuple.
    (1, 2, 'A''B')
    >>> (1,)                                      # Another tuple.
    (1,)
    >>> (1, 2) + ('A','B')                        # Concatenating tuples.
    (1, 2, 'A''B')
    >>> list((1, 2, 'A','B'))                     # A tuple being converted into a list.
    [1, 2, 'A''B']
    >>> tuple([1, 2, 'A','B'])                    # A list being converted into a tuple.
    (1, 2, 'A''B')
    >>> len((1,2,"A","B"))                        # Tuple length.
    4
    >>> (1,2,"A","B")[0]                          # First entry in the tuple.
    1
    >>> 1 in (1, 2)                               # Tuple containment check.
    True
              
  • Sets are unordered sequences that cannot contain duplicates. They are a close approximation of mathematical sets. Sets cannot be members of sets.
    • The empty set is denoted using set().
    • The methods .union() and .intersect correspond to the standard set operations.
    • A list or tuple can be turned into a set using the set() function.
    • A set can be turned into a list or tuple using the list() or list() function, respectively.
    • The function len() returns the size of a set.
    • To access individual entries in a set, it is necessary to turn the set into a list or tuple.
    • To check if a value is in a set, use the in relational operator.

    >>> {1,2,"A","B"}                             # A set.
    {1, 2, 'A''B'}
    >>> ({1,2}.union({3,4})).intersection({4,5})  # Set operations.
    {4}
    >>> set([1, 2]).union(set(('A','B')))         # Converting a list and a tuple to sets.
    {'A', 1, 2, 'B'}
    >>> len({1,2,"A","B"})                        # Set size.
    4
    >>> 1 in {1,2,"A","B"}                        # Tuple containment check.
    True
              
  • Frozen sets are like sets, except they can be members of other sets. A set can be turned into a frozen set using the frozenset() function.

    >>> frozenset({1,2,3})                        # A frozen set.
    frozenset({1, 2, 3})
    >>> {frozenset({1,2}), frozenset({3,4})}      # Set of frozen sets.
    {frozenset({3, 4}), frozenset({1, 2})}
              
  • Dictionaries are unordered collections of associations between some set of keys and some set of values. Dictionaries are also known as finite maps.
    • The empty dictionary is denoted using {}.
    • The list of keys that the dictionary associates with values can be obtained using list(d.keys()).
    • The list of values that the dictionary contains can be obtained using list(d.values()).
    • The function len() returns the number of entries in the dictionary.
    • Individual entries in a dictionary can be accessed using the bracketed index notation (e.g., d[key]).

    >>> {"A":1, "B":2}                            # A dictionary.
    {'A': 1, 'B': 2}
    >>> list({"A":1, "B":2}.keys())               # Dictionary keys.
    ['A''B']
    >>> list({"A":1, "B":2}.values())             # Dictionary values.
    [1, 2]
    >>> len({"A":1, "B":2})                       # Dictionary size.
    2
    >>> {"A":1, "B":2}["A"]                       # Obtain a dictionary value using a key.
    1
              

[link]  B.4. Function, procedure, and method invocations

Python provides a variety of ways to supply parameter arguments when invoking functions, procedures, and methods.
  • Function calls and method/procedure invocations consist of the function, procedure, or method name followed by a parenthesized, comma-delimited list of arguments. For example, suppose a function or procedure example() is defined as follows:

    def example(x, y, z):
      print("Invoked.")
      return x + y + z
              
    To invoke the above definition, we can use one of the following techniques.
    • Passing arguments directly involves listing the comma-delimited arguments directly between parentheses.

      >>> example(1,2,3)
      Invoked.
      6
                    
    • The argument unpacking operator (also known as the *-operator, the scatter operator, or the splat operator) involves providing a list to the function, preceded by the * symbol; the arguments will be drawn from the elements in the list.

      >>> args = [1,2,3]
      >>> example(*args)
      Invoked.
      6
                    
    • The keyword argument unpacking operator (also known as the **-operator) involves providing a dictionary to the function, preceded by the ** symbol; each named paramter in the function definition will be looked up in the dictionary, and the value associated with that dictionary key will be used as the argument passed to that parameter.

      >>> args = {'z':3, 'x':1, 'y':2}
      >>> example(**args)
      Invoked.
      6
                    
  • Default parameter values can be specified in any definition. Suppose the following definition is provided.

    def example(x = 1, y = 2, z = 3):
      return x + y + z
              
    The behavior is then as follows: if an argument corresponding to a parameter is not supplied, the default value found in the definition is used. If an argument is supplied, the supplied argument value is used.

    >>> example(0, 0)
    3
    >>> example(0)
    5
    >>> example()
    6
              

[link]  B.5. Comprehensions

Python provides concise notations for defining data structures and performing logical computations. In particular, it support a comprehension notation that can be used to build lists, tuples, sets, and dictionaries.
  • List comprehensions make it possible to construct a list by iterating over one or more other data structure instances (such as a list, tuple, set, or dictionary) and performing some operation on each element or combination of elements. The resulting list will contain the result of evaluating the body for every combination.

    >>> [ x for x in [1,2,3] ]
    [1, 2, 3]
    >>> [ 2 * x for x in {1,2,3} ]
    [2, 4, 6]
    >>> [ x + y for x in {1,2,3} for y in (1,2,3) ]
    [2, 3, 4, 3, 4, 5, 4, 5, 6]
              
    It is also possible to add conditions anywhere after the first for clause. This will filter which combinations are actually used to add a value to the resulting list.

    >>> [ x for x in {1,2,3} if x < 3 ]
    [1, 2]
    >>> [ x + y for x in {1,2,3} for y in (1,2,3) if x > 2 and y > 1 ]
    [5, 6]
              
  • Set comprehensions make it possible to construct a set by iterating over one or more other data structure instances (such as a list, tuple, set, or dictionary) and performing some operation on each element or combination of elements. The resulting list will contain the result of evaluating the body for every combination. Notice that the result will contain no duplicates because the result is a set.

    >>> { x for x in [1,2,3,1,2,3] }
    {1, 2, 3}
              
  • Dictionary comprehensions make it possible to construct a dictionary by iterating over one or more other data structure instances (such as a list, tuple, set, or dictionary) and performing some operation on each element or combination of elements. The resulting dictionary will contain the result of evaluating the body for every combination.

    >>> { key : 2 for key in ["A","B","C"] }
    {'A': 2, 'C': 2, 'B': 2}
              

[link]  B.6. Other useful built-in functions

The built-in function type() can be used to determine the type of a value. Below, we provide examples of how to check whether a given expression has one of the common Python types:

>>> type(True) == @bool
True
>>> type(123) == int
True
>>> type("ABC") == str
True
>>> type([1,2,3]) == list
True
>>> type(("A",1,{1,2})) == tuple
True
>>> type({1,2,3}) == set
True
>>> type({"A":1, "B":2}) == dict
True
      

[link]  B.7. Common Python definition and control constructs (i.e., Python statements)

A Python program is a sequence of Python statements. Each statement is either a function definition, a variable assignment, a conditional statement (i.e., if, else, and/or elif), an iteration construct (i.e., a for or while loop), a return statement, or a break or continue statement.
  • Variable assignments make it possible to assign a value or object to a variable.

    x = 10
              
    It is also possible to assign a tuple (or any computation that produces a tuple) to another tuple:

    (x, y) = (1, 2)
              
  • Function and procedure definitions consist of the def keyword, followed by the name of the function or procedure, and then by one or more arguments (delimited by parentheses and separated by commas).

    def example(a, b, c):
        return a + b + c
              
  • Conditional statements consist of one or more branches, each with its own boolean expression as the condition (with the exception of else). The body of each branch is an indented sequence of statements.

    def fibonacci(n):
        # Computes the nth Fibonacci number.
        if n <= 0:
            return 0
        elif n <= 2:
            return 1
        else:
            return fibonacci(n-1) + fibonacci(n-2)
              
  • Iteration constructs make it possible to repeat a sequence of statements over and over. The body of an iteration construct is an indented sequence of statements.
    • The while construct has a boolean expression as its condition (much like if). The body is executed over and over until the expression in the condition evaluates to False, or a break statement is encountered.

      def example1(n):
          # Takes an integer n and returns the sum of
          # the integers from 1 to n-1.
          i = 0
          sum = 0
          while i < n:
              sum = sum + i
              i = i + 1
          return sum

      def example2(n):
          # Takes an integer n and returns the sum of
          # the integers from 1 to n-1.
          i = 0
          sum = 0
          while True:
              sum = sum + i
              i = i + 1
              if i == n:
                  break
          return sum
                    
    • The for construct makes it possible to repeat a sequence of statements once for every object in a list, tuple, or set, or once for every key in a dictionary.

      def example3(n):
          # Takes an integer n and returns the sum of
          # the integers from 1 to n-1.
          sum = 0
          for i in range(0,n):
              sum = sum + i
          return sum

      def example4(d):
          # Takes a dictionary d that maps keys to
          # integers and returns the sum of the integers.
          sum = 0
          for key in d:
              sum = sum + d[key]
          return sum