Quick Summary
This paper introduces the ACGS algorithm for solving Hidden Number Problems with arbitrary multipliers.

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.
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:
HNP-CM is a hidden number problem where we can choose our multipliers.
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.
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.
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:
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.5Assign another variable m (the number of sampling points) such that
m = int(n * (1 / (epsilon ** 2))) #no of samplesPick u and v, two random elements in the finite field modulo p.
#Random u,v u = randint(0, prime) v = randint(0, prime)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:
Define these variables and functions:
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.
3.1 Guessing Bits Correctly
(Alexi et al., 1988) demonstrate how to find b where:
and
As mentioned in (Alexi et al., 1988), first we define two new variables:
It follows that
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):

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.

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:
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:

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 happenedThe 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:
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)References
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
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
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























