Showing posts with label done. Show all posts
Showing posts with label done. Show all posts
[mxhniflk] Test a computer by computing modular exponentiation

[mxhniflk] Test a computer by computing modular exponentiation

December 11, 2019 Add Comment

Consider testing a computer's CPU by running a long computation and verifying that the result is correct.  We restrict to interesting computations only, a purely subjective quality, because if one is going to use a lot of CPU time, one might as well compute something interesting.

Our interesting computation will be to compute 2^n mod p the Slow Way by repeating (x=2*x mod p) n times.

Because of the nature of the computation, an arithmetic error at any point during computation will likely become magnified and visible as an obviously incorrect final result.  It is also extremely unlikely for multiple arithmetic errors to cancel each other out and yield a correct final result.

The kind of errors we might see through this kind of testing are sporadic arithmetic errors caused by the heat of pushing a CPU to the max for an extended period of time.  Or maybe a cosmic ray flipping a bit.

The result can be verified the Fast Way: modular exponentiation by squaring, the standard method, usually implemented with recursion, that implicitly uses the binary representation of the exponent.

p should be a prime number which has 2 as a primitive root (generator).  We'll use the 128-bit unsigned integer type provided by GCC on 64-bit platforms, because the wider the integer type, the more interesting the computation: it has a longer period.   If p is less than 2^127, for example 2^127-309, then it is straightforward to implement the Slow Way without needing clever tricks or an arbitrary precision arithmetic library (e.g., gmp).  Is it quicker to always compute mod p by the % operator, or is it quicker to subtract p if the result after doubling is greater than or equal to p?  The former requires division, but the latter has a branch that will stymie any branch predictor because whether the branch is taken is essentially random.  A small amount of experimentation found that predicated subtraction was faster.

We are only testing left shift (multiplication by 2) and subtraction in the ALU.  It might be fun to test multiplication, division, and remainder circuitry, but that is a project for another day.

(Doing all this modulo a prime slightly less than 2^128 instead of 2^127, e.g., 2^128-275 seems not too difficult, but we did not pursue this.  It might affect ILP discussed below.)

Verification the Fast Way can easily be done using software like Pari/GP or using a library like GNU MP that provides modular exponentiation.  Incidentally, the security of time-lock puzzles like LCS35 rests on the assumption that there are no ways to compute modular exponentiation significantly faster than the Fast Way.

We can make the computer do more work in the same amount time by taking advantage of instruction level parallelism (ILP).  Do several independent computations (with different initial values) in the same thread, interleaved.  ILP will, for example, cause 2 interleaved computations to take less than twice as long as just 1.  This source code demonstrates doing multiple parallel computations in the same thread.  We found that 5-way parallelization had the most throughput.  Less than that probably stalled due to dependency on results from the previous iteration.  More than that probably ran out of registers.  It's interesting how one can learn rather arcane details of the underlying hardware through this kind of timing.

Optimization trying to take advantage of ILP was delicate.  We found that seemingly harmless changes to code caused large decreases in performance.  It was probably affecting loop unrolling.  In particular, our first attempt at adding checkpoint output every 2^n iterations, by checking that the lower n bits of the iteration counter were zero, hugely deceased performance.

We use a different initial value for the different independent computations within a thread; that is, we computed INITIAL*2^n mod p for different values of INITIAL but the same value of p.  In contrast to all computations starting with the same initial value, this provides variability between the computations, preventing a compiler from (hypothetically) optimizing away redundant computation if it reasons that the parallel computations are all exactly the same.  Such a complaint about hypothetical optimization could be made of most benchmark and system verification programs which repeat an identical computation many times: a compiler could optimize away all but the first iteration.

When using different initial values, we used odd numbers starting at 1, namely 1 3 5 7 9 as the initial values for 5-way ILP.  If we had had (way) more parallelism, we should avoid 309.  Starting at 309 is the same as starting at 1, delayed by 127 iterations: 2^x mod (2^127-309) = 309*(2^(x-127)) mod (2^127-309).  Substitute x=127 for a simpler equation: 2^127 mod (2^127-309) = 309*(2^0) mod (2^127-309) = 309.

We should also avoid odd multiples of 309.  Starting at 3*309 = 927 is the same as starting at 3, shifted by 127: 3*2^x mod (2^127-309) = 927*(2^(x-127)) mod (2^127-309) .  We should omit these initial values, multiples of 309, because doing two instances of the same computation, shifted very slightly in time, is not interesting.

Incidentally, Pari/GP can compute a discrete logarithm by this modulus in about 2 minutes, requiring a stack size of 64 megabytes.  For example, we can find that

3 = 2^121183092480708561560055562363468055686 (mod 2^127-309)
(or 2^2^126.51045603311334562760769429562182365479 if you want to know the magnitude of the exponent in bits.)

For computations across multiple cores, you don't conserve register file space by using the same modulus as we did above with different initial values.  Therefore, use a different modulus per core in order to be interesting.  Here are more moduli and source code for finding them.

Future: try this on a GPU over thousands of cores.  We might have to drop down to (less interesting) 64-bit or even 32-bit because GPUs are natively 32-bit only (or less).  But rolling one's own 128-bit arithmetic library, needing only subtraction, left shift, and comparison does not seem too difficult, and likely already exists.

The largest computations I've ever done which I'm confident have had no errors are probably this verification of the compositeness of the 22nd Fermat number, and a similar length partial computation of LCS35, abandoned after someone else found the solution first.  For LCS35, we multiplied the modulus by a medium-sized prime to have high confidence in intermediate results, as described in the original paper.  Both of these computations also happened to be modular exponentiation, albeit the Fast Way, and not with a prime modulus.  These computations did have several restarts from checkpoints after the computer went down (e.g., OS updates requiring reboots, perhaps some system crashes).  Hypothetically, if a system makes no arithmetic errors but catastrophically crashes, has it passed a test of correctness of its arithmetic calculations?

[dvxljvju] Cornu spiral

[dvxljvju] Cornu spiral

October 20, 2019 Add Comment

Most of the images of the Cornu spiral (a.k.a. clothoid, Euler spiral) that I could find "give up" in plotting the spiral long before it approaches the limit point (the center of the spiral), giving the incorrect impression that the spiral converges to a circle and not a point.

Below are some images of the Cornu spiral that follow the spiral far enough that all the pixels near the center are black.  Going to t=1500 was sufficient.  We used this Cephes implementation of the Normalized Fresnel integral function, with bugs fixed by SciPy.  Here is the source code of the Cornu spiral plotter, along with the images available for download at full resolution.

Cornu spiral

This one was plotted with a thicker line width:

Cornu spiral

Future projects:

The spiral looks like the integral symbol, mirrored and rotated.  Create some art substituting the spiral for the integral symbol in the definition of the Fresnel integrals: S(t) = integral from 0 to t of sin(x^2) dx, etc.  This should be easy.

Plotting the spiral as a path requires choosing how wide to make the pen as well as complexities (e.g., antialiasing) regarding how to plot points that don't fall exactly on a pixel.  Avoid some of these complexities as follows.  Divide the plane in half with the diagonal line y=x.  This creates a bunch of regions bounded by the spiral and the diagonal line.  Color the regions like a checkerboard, avoiding coloring adjacent regions the same color.

Or, dynamically adjust the pen width to keep it appropriate for where we are in the spiral.

Is the Cornu spiral with the normalized Fresnel integrals different, other than in scale, from the one using unnormalized Fresnel integrals?

[ttvdpsoo] Installing Ubuntu with LUKS2 Argon2i and dm-integrity

[ttvdpsoo] Installing Ubuntu with LUKS2 Argon2i and dm-integrity

July 11, 2019 Add Comment

Recent Linux kernels and cryptsetup offer new features for disk encryption: Argon2i for transforming a password into a key, and dm-integrity, a form of Authenticated Encryption.  Unfortunately, the Ubuntu installer (18.04.2 Server) does not expose these functionalities, and is missing libraries ("libgcc_s.so.1 must be installed for pthread_cancel to work.") and kernel modules ("Kernel doesn't support dm-integrity mapping.") even to try do it via the command line via Ctrl-Alt-F2 etc., during install.

Here's how to do it anyway.

  1. Install encrypted to the extent that the regular installer can do it.

  2. Reboot into a live CD.

  3. cryptsetup open

  4. Use dd to take an image of the plaintext completed installed logical volume of the root directory.  Take note of its size in 4M extents.

  5. vgchange -a n

  6. cryptsetup close

  7. Reformat with fancier disk encryption: /usr/bin/time sudo cryptsetup luksFormat -v --type luks2 --label mylabel --integrity hmac-sha256 --pbkdf argon2i -i 60000 --pbkdf-memory 4194304 --pbkdf-parallel 2 -s 512 -h sha512 --use-random /dev/sda1

  8. "cryptsetup open /dev/sda1 sda1_crypt" It is important to use the same crypt container name, e.g., sda1_crypt, that the installer used, as recorded in /etc/crypttab , because update-initramfs needs that name.

  9. Set up LVM within the encrypted container, creating a volume group and logical volume with the same name and size as the original.

  10. Restore the image, mount.

  11. Edit /etc/crypttab to reflect the new UUID of the encrypted partition (ls -l /dev/disk/by-uuid).

  12. Add dm_integrity to /etc/initramfs-tools/modules .

  13. update-initramfs within a chroot.

  14. Grow the logical volume (lvextend -l +100%FREE /dev/v/root) and file system (btrfs filesystem resize max /mnt).

  15. umount; btrfs check

Our set up in step 1 was unencrypted /boot in its own partition (putting /boot inside the encrypted container does not work) and encrypted LVM in another partition containing root and swap.  This is very similar to "Guided - use entire disk and set up encrypted LVM".  However, the initial install was onto a small logical volume (3 GB; 2GB is sufficient if using btrfs compress=zstd), because that makes taking an image easier.  We also made the boot partition large (5GB) because that's a convenient place to temporarily stash the image (encrypted with gpg -c --s2k-cipher-algo AES256 --s2k-digest-algo SHA512 --s2k-mode 3 --s2k-count 65000000) while reformatting.  After restoring, we can extend the logical volume and filesystem, then install more packages with tasksel.

We used the Lubuntu live CD to reformat because its low memory requirement allows giving more memory to Argon2i.  SystemRescueCD text mode is even better.

Unfortunately, these instructions do not work for Debian Buster (RC2) (and also probably later versions of Ubuntu), because of recent changes to cryptsetup, in particular /usr/share/initramfs/hooks/cryptroot . The first error ("Source mismatch") happens in print_crypttab_entry, where dmsetup info -c -o devnos_used returns a different major number when dealing with a dm_integrity device.