|
| 1 | +#! /usr/bin/env python3 |
| 2 | + |
| 3 | +# Copyright (C) 2020 IBM Corp. |
| 4 | +# This program is Licensed under the Apache License, Version 2.0 |
| 5 | +# (the "License"); you may not use this file except in compliance |
| 6 | +# with the License. You may obtain a copy of the License at |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# Unless required by applicable law or agreed to in writing, software |
| 9 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 10 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 11 | +# See the License for the specific language governing permissions and |
| 12 | +# limitations under the License. See accompanying LICENSE file. |
| 13 | + |
| 14 | +import re |
| 15 | +import sys |
| 16 | +import argparse |
| 17 | +import math |
| 18 | +import numth |
| 19 | + |
| 20 | +def parseRange(rangeStr): |
| 21 | + """ |
| 22 | + Parses the ranges and numbers given to it. |
| 23 | +
|
| 24 | + Args: |
| 25 | + rangeStr: a string e.g. '2-5,7,10-11' |
| 26 | + Returns: |
| 27 | + A list of values to try out. |
| 28 | + Usage: |
| 29 | + >>> parseRange('2-5,7,10-11') |
| 30 | + [2, 3, 4, 5, 7, 10, 11] |
| 31 | + """ |
| 32 | + rangeList = rangeStr.split(',') |
| 33 | + |
| 34 | + # Group1 regex for range |
| 35 | + # Group2 start number |
| 36 | + # Group3 end number |
| 37 | + # Group4 OR regex for single number |
| 38 | + # Group5 end number |
| 39 | + |
| 40 | + regex = re.compile(r'^((\d+)\s*-\s*(\d+))|(\s*(\d+)\s*)$') |
| 41 | + |
| 42 | + try: |
| 43 | + matches = [ regex.fullmatch(r).groups() for r in rangeList ] |
| 44 | + except AttributeError: |
| 45 | + raise argparse.ArgumentTypeError(\ |
| 46 | + "Wrong syntax for range given '%s'. Correct example '2-5,7' " % rangeStr) |
| 47 | + |
| 48 | + def buildRanges(match): |
| 49 | + matchRange, startRange, endRange, matchSingle, endSingle = match |
| 50 | + |
| 51 | + if matchRange != None: |
| 52 | + if endRange < startRange: |
| 53 | + raise argparse.ArgumentTypeError(\ |
| 54 | + "Range going from high to low '%s'" % matchRange) |
| 55 | + else: |
| 56 | + return range(int(startRange), int(endRange)+1) |
| 57 | + elif matchSingle != None: |
| 58 | + return range(int(endSingle), int(endSingle)+1) |
| 59 | + else: |
| 60 | + raise ValueError(\ |
| 61 | + "Something went wrong generating a range with match '%s'"%(match, ) ) |
| 62 | + |
| 63 | + # Transform into a list of range objects |
| 64 | + ranges = list(map(buildRanges, matches)) |
| 65 | + # Set Comprehension - guarantee uniqueness |
| 66 | + values = { x for r in ranges for x in r } |
| 67 | + # Convert to sorted list |
| 68 | + values = sorted(list(values)) |
| 69 | + |
| 70 | + return values |
| 71 | + |
| 72 | + |
| 73 | +def parsePrimeRange(rangeStr): |
| 74 | + """ |
| 75 | + Parses the ranges and numbers given to it, but only of primes. |
| 76 | +
|
| 77 | + Args: |
| 78 | + rangeStr: a string e.g. '2-5,7,10-11' |
| 79 | + Returns: |
| 80 | + A list of prime numbers to try out. |
| 81 | + Usage: |
| 82 | + >>> parsePrimeRange('2-5,7,10-11') |
| 83 | + [2, 3, 5, 7, 11] |
| 84 | + """ |
| 85 | + |
| 86 | + p_prime = list(\ |
| 87 | + filter(lambda x: numth.factorize(x)[x] == 1, \ |
| 88 | + parseRange(rangeStr))\ |
| 89 | + ) |
| 90 | + if len(p_prime) == 0: |
| 91 | + raise argparse.ArgumentTypeError("No primes found in range given.") |
| 92 | + |
| 93 | + return p_prime |
| 94 | + |
| 95 | + |
| 96 | +def algebras(m_maxs): |
| 97 | + """ |
| 98 | + Generator for the possible algebras. |
| 99 | +
|
| 100 | + Args: |
| 101 | + m_maxs: Maximum possible m values given p and d as a tuple e.g. (m, p, d). |
| 102 | + Returns: |
| 103 | + A 5-tuple ( m, p, d, phi_m, nSlots ) for the algebra. |
| 104 | + Usage: |
| 105 | + >>> print( '\\n'.join(map(str,algebras( ((24, 5, 2),) )))) |
| 106 | + (2, 5, 1, 1, 1) |
| 107 | + (4, 5, 1, 2, 2) |
| 108 | + (8, 5, 2, 4, 2) |
| 109 | + (3, 5, 2, 2, 1) |
| 110 | + (6, 5, 2, 2, 1) |
| 111 | + (12, 5, 2, 4, 2) |
| 112 | + (24, 5, 2, 8, 4) |
| 113 | + """ |
| 114 | + # Generate the divisors for each max_m |
| 115 | + for m_max,p,d in m_maxs: |
| 116 | + factors = numth.factorize(m_max) |
| 117 | + for mFactors in numth.divisorsFactors(factors): |
| 118 | + d_real = d |
| 119 | + m = numth.calcDivisor(mFactors) |
| 120 | + if m == 1: |
| 121 | + continue |
| 122 | + |
| 123 | + phi_m = numth.phi(mFactors) |
| 124 | + |
| 125 | + # Correct for order of p in mod m a.k.a. d |
| 126 | + phimFactors = numth.factorize(phi_m) |
| 127 | + for e in sorted(numth.divisors(phimFactors)): |
| 128 | + if p**e % m == 1: |
| 129 | + d_real = e |
| 130 | + break |
| 131 | + |
| 132 | + nSlots, r = divmod(phi_m, d_real) |
| 133 | + if r != 0: |
| 134 | + raise ArithmeticError("Fractional nslots should not exist.") |
| 135 | + |
| 136 | + # SolnObj just a 5-tuple ( m, p, d, phi_m, nSlots ) |
| 137 | + yield (m,p,d_real,phi_m,nSlots) |
| 138 | + |
| 139 | + |
| 140 | +def printTable( headers, data, colwidths=None ): |
| 141 | + """ |
| 142 | + Print a nice table of possible algebras. |
| 143 | + |
| 144 | + Args: |
| 145 | + headers: A list/tuple of strings of column headers. |
| 146 | + data: An iterable of list/tuple to print as the row data. |
| 147 | + colwidths: A list/tuple of column widths to be used (forced min. of 8 spaces). |
| 148 | + Default 12 spaces if None given. |
| 149 | + Returns: |
| 150 | + None. |
| 151 | + Usage: |
| 152 | + >>> printTable(('m','p','d','phi(m)','nSlots'),([(24,5,2,8,4),(12,5,2,4,2)]), [1,1,1,1,1]) |
| 153 | + m p d phi(m) nSlots |
| 154 | + 24 5 2 8 4 |
| 155 | + 12 5 2 4 2 |
| 156 | + """ |
| 157 | + if colwidths == None: |
| 158 | + colwidths = [12]*len(headers) |
| 159 | + else: |
| 160 | + colwidths = [ width+2 if width > 8 else 8 for width in colwidths ] |
| 161 | + |
| 162 | + for header, width in zip(headers, colwidths): |
| 163 | + print('{:{align}{width}}'.format(header, align='^', width=width), end='') |
| 164 | + print() |
| 165 | + |
| 166 | + for row in data: |
| 167 | + for datum, width in zip(row, colwidths): |
| 168 | + print('{:{align}{width}}'.format(datum, align='>', width=width), end='') |
| 169 | + print() |
| 170 | + |
| 171 | + |
| 172 | +if __name__ == "__main__": |
| 173 | + |
| 174 | + parser = argparse.ArgumentParser(description="") |
| 175 | + parser.add_argument("-p", type=parsePrimeRange, default="2", |
| 176 | + help="Prime number for plaintext space.") |
| 177 | + parser.add_argument("-d", type=parseRange, default="1", |
| 178 | + help="How many coefficients in a slot (the order of p in ZmStar).") |
| 179 | + parser.add_argument("--self-test", action="store_true", |
| 180 | + help="Run doctest on itself.") |
| 181 | + args = parser.parse_args() |
| 182 | + |
| 183 | + # Run doctest instead of normal operation |
| 184 | + if args.self_test: |
| 185 | + import doctest |
| 186 | + print(doctest.testmod()) |
| 187 | + exit(0) |
| 188 | + |
| 189 | + # Generator for m_max(s) |
| 190 | + m_maxs = [ (p**d - 1, p, d) for p in args.p for d in args.d ] |
| 191 | + |
| 192 | + # SolnObj just a 5-tuple ( m, p, d, phi_m, nSlots ) |
| 193 | + solns = sorted( algebras(m_maxs) ) |
| 194 | + |
| 195 | + m_max = max(m_maxs) |
| 196 | + minWidths = [ math.ceil(math.log10(t)) for t in m_max ] |
| 197 | + minWidths.append( |
| 198 | + math.ceil(math.log10(numth.phi(numth.factorize(m_max[0]))))) |
| 199 | + minWidths.append(minWidths[-1]) |
| 200 | + |
| 201 | + printTable( ('m','p','d','phi(m)','nSlots'), solns, minWidths) |
0 commit comments