0:00
/
0:00

ACGS Algorithm for Hidden Number Problems with Chosen Multipliers

Coding the 1988 paper "RSA and Rabin Functions: Certain Parts are as Hard as the Whole" in Python to Understand Alexi-Chor-Goldreich-Schnorr (ACGS) algorithm
Quick Summary
This paper introduces the ACGS algorithm for solving Hidden Number Problems with arbitrary multipliers.

We train the next generation of researchers. Subscribe to learn how to turn advanced math papers into C and Python code.

Abstract for the 1988 paper RSA and Rabin Functions: Certain Parts are as Hard as the Whole (Alexi et al., 1988)

This is part of our series on Practical Hidden Number Problems for Programmers:

Part 1: Quantum Computing and Intro to Hidden Number Problems

Part 2: Increasing Lattice Volume for Improved HNP Attacks

Part 3: Hidden Number Problems with Multiple Noise Holes

Part 4(We are here): Hidden Number Problems with Chosen Multipliers

  • This paper introduces the ACGS algo as an alternative to lattice construction.

As usual, you can code alongside the Google Colab Notebook or the GitHub repo.

1.0 Paper Introduction

The 1988 paper RSA and Rabin Functions: Certain Parts are as Hard as the Whole (Alexi et al., 1988)1 introduces the Alexi-Chor-Goldreich-Schnorr (ACGS) algorithm for polynomial-time solutions to Hidden Number Problems with Chosen Multipliers (HNP-CM) (Boneh & Shparlinski, 2001)2.

Summary of results. Taken from page 1 of (Alexi et al., 1988)

The paper is considered canonical and is included in MIT’s Foundations of Cryptography series.

Beyond the HNP-CM, the paper also demonstrates efficient techniques for constructing random number generators.

2.0 Introduction to HNP-CM

Part 1 and Part 2 introduced the hidden number problem (HNP) where:

Typical hidden number problem

HNP-CM is a hidden number problem where we can choose our multipliers.

Formal definition of HNP-CM taken from section 4 of (Boneh & Shparlinski, 2001)

Unlike the typical HNP, the HNP-CM requires us work with the least-significant-bit (LSB) of the observation as shown in eq 3 in the screenshot above.

3.0 ACGS Algorithm

Alexi-Chor-Goldreich-Schnorr (ACGS) is a polynomial time algorithm for constructing the perfect predictor for HNPs when multiplier choice is at our discretion.

ACGS problem statement

At a high level, ACGS takes a list of HNPs, amplifies them into the perfect LSB predictor, and uses that predictor to recover the full secret.

ACGS algorithm summary taken from section 4 of (Boneh & Shparlinski, 2001)

The algorithm is pretty simple: query our function at certain (independent) points and return the majority bits.

Here’s a detailed breakdown of the step alongside a numerical example:

  1. Assign a prime and a variable epsilon between 0 and 0.5 to denote the oracle accuracy (0.5 means we absolutely know the LSB)

    p = 13441 #It works best for gigantic primes. See section 3.1
    epsilon = 0.5
  2. Assign another variable m (the number of sampling points) such that

    m = int(n * (1 / (epsilon ** 2))) #no of samples
  3. Pick u and v, two random elements in the finite field modulo p.

    #Random u,v
    u = randint(0, prime) 
    v = randint(0, prime) 
  4. Generate r, a list of length m of linearly independent points where each r is a linear combination of u and v:

    def GenerateRList(u, v, m, p):
        r = []
        for i in range(1, m+1):
            r_i = (i * u + v) % p
            r.append(r_i)
        return r
    • (Alexi et al., 1988) prove the ‘randomness’ of r as:

      Proof that r’s construction is linearly independent. Taken from section 4.1 of (Alexi et al., 1988)
  5. Define these variables and functions:

    a_i and b_i are known LSBs. f_i is modulo 2 addition and f(t) is the majority function
  6. Now we need to find the LSB of r_i * alpha. (Alexi et al., 1988) describe the algorithm below. We dedicate the next section to it.

    Finding LSB of r_i * alpha by guessing. Taken from section 4.1 of (Alexi et al., 1988)

3.1 Guessing Bits Correctly

(Alexi et al., 1988) demonstrate how to find b where:

Definition of b. Taken from Theorem 2 of (Boneh & Shparlinski, 2001)

and

Definition of r

As mentioned in (Alexi et al., 1988), first we define two new variables:

Definition of y and z

It follows that

Relating the product and the sum

thus, as summarized in (Boneh & Shparlinski, 2001), guessing the LSB and MSBs eventually leads to the correct LSB.

The intricacies of the guessing algorithm are given in (Alexi et al., 1988):

Don’t skim it. Every word in this paragraph is important lol. aken from section 4.1 of (Alexi et al., 1988)

The python code resembles this. We also provide a test function to show that knowing a few MSBs and the correct LSB is in fact enough to guess w mod p:

import random 
import math
from random import randint
def lsb(x): return x & 1

def GenerateRList(u, v, m, p):
    r = []
    for i in range(1, m+1):
        r_i = (i * u + v) % p
        r.append(r_i)
    return r

def RecoverLSB(i, yApproximate, zApproximate, yLSB, zLSB, eps, p):
    """
    Recover LSB([w_i]_p) using approximate y,z and true LSBs
    """
    # approximate w
    wApproximate = yApproximate + i * zApproximate
    # uncertainty interval
    interval = eps * p / 2
    lower = wApproximate - interval/2
    upper = wApproximate + interval/2
    k_low = math.floor(lower / p)
    k_high = math.floor(upper / p)
    ambiguous = (k_low != k_high)
    if ambiguous:
        q = k_high   # arbitrary choice from paper
    else:
        q = k_low
    # compute LSB(w_i)
    lsb_w = (yLSB + i * zLSB) % 2
    # compute LSB([w_i]_p)
    lsb_mod = lsb_w ^ (q & 1)
    return lsb_mod

def TestGuessLSB():
    prime = 205115282021455665897114700593932402728804164701536103180137503955397371
    epsilon = 0.3
    alpha = random.randint(1, prime-1)
    n = prime.bit_length();m = int(n * (1 / (epsilon ** 2)))
    knownMSBBitlength = math.ceil(2 * math.log(m, 2))
    #Random u,v
    u = randint(0, prime);v = randint(0, prime) 
    r = GenerateRList(u,v,m,prime)
    trueY = (v * alpha) % prime
    trueZ = (u * alpha) % prime
    yLSB = trueY & 1
    zLSB = trueZ & 1
    yApproximate = (trueY >> knownMSBBitlength) << knownMSBBitlength
    zApproximate = (trueZ >> knownMSBBitlength) << knownMSBBitlength
    matchCount = 0
    for i in range(0, len(r)):
        #remember i starts at 1 when generating r
        true_lsb = RecoverLSB(i+1, yApproximate, zApproximate, yLSB, zLSB, epsilon, prime)
        targetLSB = lsb((r[i]*alpha)%prime)
        if(true_lsb == targetLSB):
            matchCount += 1
        print(f"{true_lsb}: {targetLSB} : {matchCount} / {len(r)} matches")

TestGuessLSB()

In the example above, guessing correctly the first 23 bits of a 237-bit prime and the correct LSB gives us the correct w modulo p 2442/2633 times!

That’s what makes the algorithm polynomial time lol.

Only knowing 23 MSB bits and 1 correct LSB gives us incredible accuracy when guessing the LSB of w modulo p

For the curious, this “bit-guessing” trick works best for large primes. The trick fails for small primes like 13441.

For each guessed bit, we query our oracle and add the result modulo 2 and append to a list:

Utilizing the b’s found in Section 3.1. Taken from (Boneh & Shparlinski, 2001)

The majority function gives the LSB of f(t*alpha).

3.2 Retrieving Alpha from Guessed LSBs

Remember, we can choose multipliers in HNP-CM. We retrieve the secret key (alpha) by finding the LSB of (t*alpha) modulo p at powers of 2.

This is proven in Theorem 7 of (Boneh & Venkatesan, 1996)3:

Theorem 7 proves we can recover alpha if we know the LSB of t*alpha using interval reduction. Taken from section 5 of (Boneh & Venkatesan, 1996)

Querying 2*t tells us if reduction occured or not

oracle(2t) = 0  → (αt mod p) < p/2 #no reduction happened
oracle(2t) = 1  → (αt mod p) ≥ p/2 #reduction happened

The python function resembles:

import random

p = 13441
alpha = random.randrange(1, p)

def oracle(t):
    return ((alpha * t) % p) & 1

def RecoverAlpha_IntervalReduction():
    low = 0
    high = p
    t = 1
    for _ in range(p.bit_length() + 2):
        bit = oracle(t * 2)
        mid = (low + high) // 2
        if bit == 0:
            # no reduction → alpha*t mod p < p/2
            high = mid
        else:
            # reduction happened → alpha*t mod p >= p/2
            low = mid
        t *= 2
    return low

guess = RecoverAlpha_IntervalReduction()

print("real alpha:", alpha)
print("recovered :", guess)

Running this yields a pretty decent approximation of alpha:

alpha is almost correct.

The paper ends here. We’re able to guess the LSBs at certain points and this permits us recover alpha without performing lattice reduction.

4.0 Bonus Section

An interesting question arises: What if our oracle leaks multiple LSBs in a single query?

This speeds up the attack by a factor of the number of leaked bits. We incorporate the leaked bits for larger interval reductions like :

#Bonus Section: Interval Reduction with Multiple Leaks
import random
import math 

p = 205115282021455665897114700593932402728804164701536103180137503955397371
alpha = random.randrange(1, p)

LEAK_BITS = 80
MASK = (1 << LEAK_BITS) - 1

def oracle(t, k):
    return (alpha * t % p) & ((1 << k) - 1)

def RecoverAlpha():
    k = LEAK_BITS
    low = 0
    high = p
    t = 1
    inv_p = pow(p, -1, 1 << k)

    for i in range((p.bit_length() // k) + 3):

        t = (t * (1 << k)) % p
        leak = oracle(t, k)

        m = (-leak * inv_p) % (1 << k)

        width = (high - low) // (1 << k)

        low = low + m * width
        high = low + width
        currentRange = high - low
        rangeLength = 0
        if(currentRange != 0):
            rangeLength = math.log(currentRange, 2)
        print(f"{i} {rangeLength}")

    return low


guess = RecoverAlpha()

print("real alpha:", alpha)
print("recovered :", guess)

All math papers should be a brief blog post with code. Subscribe if you agree.

References

1

Alexi, W., Chor, B., Goldreich, O., & Schnorr, C. P. (1988). RSA and Rabin functions: Certain parts are as hard as the whole. SIAM Journal on Computing, *17*(2), 194-209. https://doi.org/10.1137/0217013

2

Boneh, D., & Shparlinski, I. E. (2001). On the unpredictability of bits of the elliptic curve Diffie-Hellman scheme. In Advances in Cryptology — CRYPTO 2001 (Vol. 2139, pp. 201-212). Springer-Verlag. https://doi.org/10.1007/3-540-44647-8_12

3

Boneh, D., & Venkatesan, R. (1996). Hardness of Computing the Most Significant Bits of Secret Keys in Diffie-Hellman and Related Schemes. In: Koblitz, N. (eds) Advances in Cryptology — CRYPTO ’96. CRYPTO 1996. Lecture Notes in Computer Science, vol 1109. Springer, Berlin, Heidelberg. https://doi.org/10.1007/3-540-68697-5_11

Discussion about this video

User's avatar

Ready for more?