WindowsXPKg/src/libumskt/confid/confid.cpp

848 lines
24 KiB
C++

/**
* This file is a part of the UMSKT Project
*
* Copyleft (C) 2019-2023 UMSKT Contributors (et.al.)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @FileCreated by WitherOrNot on 06/02/2023
* @Maintainer WitherOrNot
*
* @History {
* This algorithm was provided to the UMSKT project by diamondggg
* the history provided by diamondggg is that they are the originator of the code
* and was created in tandem with an acquaintance who knows number theory.
* The file dates suggest this code was written sometime in 2017/2018
* }
*/
#include "confid.h"
#define MOD 0x16A6B036D7F2A79ULL
#define NON_RESIDUE 43
static const QWORD f[6] = {0, 0x21840136C85381ULL, 0x44197B83892AD0ULL, 0x1400606322B3B04ULL, 0x1400606322B3B04ULL, 1};
QWORD ConfirmationID::residue_add(QWORD x, QWORD y)
{
QWORD z = x + y;
//z = z - (z >= MOD ? MOD : 0);
if (z >= MOD)
z -= MOD;
return z;
}
QWORD ConfirmationID::residue_sub(QWORD x, QWORD y)
{
QWORD z = x - y;
//z += (x < y ? MOD : 0);
if (x < y)
z += MOD;
return z;
}
#if defined(__x86_64__) || defined(_M_AMD64) || defined(__aarch64__) || (defined(__arm64__) && defined(__APPLE__))
#ifdef __GNUC__
inline QWORD ConfirmationID::__umul128(QWORD a, QWORD b, QWORD* hi)
{
OWORD r = (OWORD)a * (OWORD)b;
*hi = r >> 64;
return (QWORD) r;
}
#else
#define __umul128 _umul128
#endif
#elif defined(__i386__) || defined(_M_IX86) || defined(__arm__) || defined(__EMSCRIPTEN__)
inline QWORD ConfirmationID::__umul128(QWORD multiplier, QWORD multiplicand, QWORD *product_hi) {
// multiplier = ab = a * 2^32 + b
// multiplicand = cd = c * 2^32 + d
// ab * cd = a * c * 2^64 + (a * d + b * c) * 2^32 + b * d
QWORD a = multiplier >> 32;
QWORD b = multiplier & 0xFFFFFFFF;
QWORD c = multiplicand >> 32;
QWORD d = multiplicand & 0xFFFFFFFF;
//QWORD ac = a * c;
QWORD ad = a * d;
//QWORD bc = b * c;
QWORD bd = b * d;
QWORD adbc = ad + (b * c);
QWORD adbc_carry = adbc < ad ? 1 : 0;
// multiplier * multiplicand = product_hi * 2^64 + product_lo
QWORD product_lo = bd + (adbc << 32);
QWORD product_lo_carry = product_lo < bd ? 1 : 0;
*product_hi = (a * c) + (adbc >> 32) + (adbc_carry << 32) + product_lo_carry;
return product_lo;
}
#else
#error Unknown architecture detected - please edit confid.cpp to tailor __umul128() your architecture
#endif
QWORD ConfirmationID::ui128_quotient_mod(QWORD lo, QWORD hi)
{
// hi:lo * ceil(2**170/MOD) >> (64 + 64 + 42)
QWORD prod1;
__umul128(lo, 0x604fa6a1c6346a87, &prod1);
QWORD part1hi;
QWORD part1lo = __umul128(lo, 0x2d351c6d04f8b, &part1hi);
QWORD part2hi;
QWORD part2lo = __umul128(hi, 0x604fa6a1c6346a87, &part2hi);
QWORD sum1 = part1lo + part2lo;
unsigned sum1carry = (sum1 < part1lo);
sum1 += prod1;
sum1carry += (sum1 < prod1);
QWORD prod2 = part1hi + part2hi + sum1carry;
QWORD prod3hi;
QWORD prod3lo = __umul128(hi, 0x2d351c6d04f8b, &prod3hi);
prod3lo += prod2;
prod3hi += (prod3lo < prod2);
return (prod3lo >> 42) | (prod3hi << 22);
}
QWORD ConfirmationID::residue_mul(QWORD x, QWORD y)
{
// * ceil(2**170/MOD) = 0x2d351 c6d04f8b|604fa6a1 c6346a87 for (p-1)*(p-1) max
QWORD hi;
QWORD lo = __umul128(x, y, &hi);
QWORD quotient = ui128_quotient_mod(lo, hi);
return lo - quotient * MOD;
}
QWORD ConfirmationID::residue_pow(QWORD x, QWORD y)
{
if (y == 0)
return 1;
QWORD cur = x;
while (!(y & 1)) {
cur = residue_mul(cur, cur);
y >>= 1;
}
QWORD res = cur;
while ((y >>= 1) != 0) {
cur = residue_mul(cur, cur);
if (y & 1)
res = residue_mul(res, cur);
}
return res;
}
QWORD ConfirmationID::inverse(QWORD u, QWORD v)
{
//assert(u);
int64_t tmp;
int64_t xu = 1, xv = 0;
QWORD v0 = v;
while (u > 1) {
QWORD d = v / u; QWORD remainder = v % u;
tmp = u; u = remainder; v = tmp;
tmp = xu; xu = xv - d * xu; xv = tmp;
}
xu += (xu < 0 ? v0 : 0);
return xu;
}
QWORD ConfirmationID::residue_inv(QWORD x)
{
return inverse(x, MOD);
// return residue_pow(x, MOD - 2);
}
#define BAD 0xFFFFFFFFFFFFFFFFull
QWORD ConfirmationID::residue_sqrt(QWORD what)
{
if (!what) {
return 0;
}
QWORD g = NON_RESIDUE, z, y, r, x, b, t;
QWORD e = 0, q = MOD - 1;
while (!(q & 1)) {
e++, q >>= 1;
}
z = residue_pow(g, q);
y = z;
r = e;
x = residue_pow(what, (q - 1) / 2);
b = residue_mul(residue_mul(what, x), x);
x = residue_mul(what, x);
while (b != 1) {
QWORD m = 0, b2 = b;
do {
m++;
b2 = residue_mul(b2, b2);
} while (b2 != 1);
if (m == r) {
return BAD;
}
t = residue_pow(y, 1 << (r - m - 1));
y = residue_mul(t, t);
r = m;
x = residue_mul(x, t);
b = residue_mul(b, y);
}
if (residue_mul(x, x) != what) {
//printf("internal error in sqrt\n");
return BAD;
}
return x;
}
int ConfirmationID::find_divisor_v(TDivisor* d)
{
// u | v^2 - f
// u = u0 + u1*x + x^2
// f%u = f0 + f1*x
QWORD v1, f2[6];
for (int i = 0; i < 6; i++) {
f2[i] = f[i];
}
const QWORD u0 = d->u[0];
const QWORD u1 = d->u[1];
for (int j = 4; j--; ) {
f2[j] = residue_sub(f2[j], residue_mul(u0, f2[j + 2]));
f2[j + 1] = residue_sub(f2[j + 1], residue_mul(u1, f2[j + 2]));
f2[j + 2] = 0;
}
// v = v0 + v1*x
// u | (v0^2 - f0) + (2*v0*v1 - f1)*x + v1^2*x^2 = u0*v1^2 + u1*v1^2*x + v1^2*x^2
// v0^2 - f0 = u0*v1^2
// 2*v0*v1 - f1 = u1*v1^2
// v0^2 = f0 + u0*v1^2 = (f1 + u1*v1^2)^2 / (2*v1)^2
// (f1^2) + 2*(f1*u1-2*f0) * v1^2 + (u1^2-4*u0) * v1^4 = 0
// v1^2 = ((2*f0-f1*u1) +- 2*sqrt(-f0*f1*u1 + f0^2 + f1^2*u0))) / (u1^2-4*u0)
const QWORD f0 = f2[0];
const QWORD f1 = f2[1];
const QWORD u0double = residue_add(u0, u0);
const QWORD coeff2 = residue_sub(residue_mul(u1, u1), residue_add(u0double, u0double));
const QWORD coeff1 = residue_sub(residue_add(f0, f0), residue_mul(f1, u1));
if (coeff2 == 0) {
if (coeff1 == 0) {
if (f1 == 0) {
// impossible
//printf("bad f(), double root detected\n");
}
return 0;
}
QWORD sqr = residue_mul(residue_mul(f1, f1), residue_inv(residue_add(coeff1, coeff1)));
v1 = residue_sqrt(sqr);
if (v1 == BAD) {
return 0;
}
} else {
QWORD d = residue_add(residue_mul(f0, f0), residue_mul(f1, residue_sub(residue_mul(f1, u0), residue_mul(f0, u1))));
d = residue_sqrt(d);
if (d == BAD) {
return 0;
}
d = residue_add(d, d);
QWORD inv = residue_inv(coeff2);
QWORD root = residue_mul(residue_add(coeff1, d), inv);
v1 = residue_sqrt(root);
if (v1 == BAD) {
root = residue_mul(residue_sub(coeff1, d), inv);
v1 = residue_sqrt(root);
if (v1 == BAD) {
return 0;
}
}
}
QWORD v0 = residue_mul(residue_add(f1, residue_mul(u1, residue_mul(v1, v1))), residue_inv(residue_add(v1, v1)));
d->v[0] = v0;
d->v[1] = v1;
return 1;
}
// generic short slow code
int ConfirmationID::polynomial_mul(int adeg, const QWORD a[], int bdeg, const QWORD b[], int resultprevdeg, QWORD result[])
{
if (adeg < 0 || bdeg < 0)
return resultprevdeg;
int i, j;
for (i = resultprevdeg + 1; i <= adeg + bdeg; i++)
result[i] = 0;
resultprevdeg = i - 1;
for (i = 0; i <= adeg; i++)
for (j = 0; j <= bdeg; j++)
result[i + j] = residue_add(result[i + j], residue_mul(a[i], b[j]));
while (resultprevdeg >= 0 && result[resultprevdeg] == 0)
--resultprevdeg;
return resultprevdeg;
}
int ConfirmationID::polynomial_div_monic(int adeg, QWORD a[], int bdeg, const QWORD b[], QWORD* quotient)
{
assert(bdeg >= 0);
assert(b[bdeg] == 1);
int i, j;
for (i = adeg - bdeg; i >= 0; i--) {
QWORD q = a[i + bdeg];
if (quotient)
quotient[i] = q;
for (j = 0; j < bdeg; j++)
a[i + j] = residue_sub(a[i + j], residue_mul(q, b[j]));
a[i + j] = 0;
}
i += bdeg;
while (i >= 0 && a[i] == 0)
i--;
return i;
}
void ConfirmationID::polynomial_xgcd(int adeg, const QWORD a[3], int bdeg, const QWORD b[3], int* pgcddeg, QWORD gcd[3], int* pmult1deg, QWORD mult1[3], int* pmult2deg, QWORD mult2[3])
{
int sdeg = -1;
QWORD s[3] = {0, 0, 0};
int mult1deg = 0;
mult1[0] = 1; mult1[1] = 0; mult1[2] = 0;
int tdeg = 0;
QWORD t[3] = {1, 0, 0};
int mult2deg = -1;
mult2[0] = 0; mult2[1] = 0; mult2[2] = 0;
int rdeg = bdeg;
QWORD r[3] = {b[0], b[1], b[2]};
int gcddeg = adeg;
gcd[0] = a[0]; gcd[1] = a[1]; gcd[2] = a[2];
// s*u1 + t*u2 = r
// mult1*u1 + mult2*u2 = gcd
while (rdeg >= 0) {
if (rdeg > gcddeg) {
unsigned tmp;
int tmpi;
tmp = rdeg; rdeg = gcddeg; gcddeg = tmp;
tmpi = sdeg; sdeg = mult1deg; mult1deg = tmpi;
tmpi = tdeg; tdeg = mult2deg; mult2deg = tmpi;
QWORD tmp2;
tmp2 = r[0]; r[0] = gcd[0]; gcd[0] = tmp2;
tmp2 = r[1]; r[1] = gcd[1]; gcd[1] = tmp2;
tmp2 = r[2]; r[2] = gcd[2]; gcd[2] = tmp2;
tmp2 = s[0]; s[0] = mult1[0]; mult1[0] = tmp2;
tmp2 = s[1]; s[1] = mult1[1]; mult1[1] = tmp2;
tmp2 = s[2]; s[2] = mult1[2]; mult1[2] = tmp2;
tmp2 = t[0]; t[0] = mult2[0]; mult2[0] = tmp2;
tmp2 = t[1]; t[1] = mult2[1]; mult2[1] = tmp2;
tmp2 = t[2]; t[2] = mult2[2]; mult2[2] = tmp2;
continue;
}
int delta = gcddeg - rdeg;
QWORD mult = residue_mul(gcd[gcddeg], residue_inv(r[rdeg]));
// quotient = mult * x**delta
assert(rdeg + delta < 3);
for (int i = 0; i <= rdeg; i++)
gcd[i + delta] = residue_sub(gcd[i + delta], residue_mul(mult, r[i]));
while (gcddeg >= 0 && gcd[gcddeg] == 0)
gcddeg--;
assert(sdeg + delta < 3);
for (int i = 0; i <= sdeg; i++)
mult1[i + delta] = residue_sub(mult1[i + delta], residue_mul(mult, s[i]));
if (mult1deg < sdeg + delta)
mult1deg = sdeg + delta;
while (mult1deg >= 0 && mult1[mult1deg] == 0)
mult1deg--;
assert(tdeg + delta < 3);
for (int i = 0; i <= tdeg; i++)
mult2[i + delta] = residue_sub(mult2[i + delta], residue_mul(mult, t[i]));
if (mult2deg < tdeg + delta)
mult2deg = tdeg + delta;
while (mult2deg >= 0 && mult2[mult2deg] == 0)
mult2deg--;
}
// d1 = gcd, e1 = mult1, e2 = mult2
*pgcddeg = gcddeg;
*pmult1deg = mult1deg;
*pmult2deg = mult2deg;
}
int ConfirmationID::u2poly(const TDivisor* src, QWORD polyu[3], QWORD polyv[2])
{
if (src->u[1] != BAD) {
polyu[0] = src->u[0];
polyu[1] = src->u[1];
polyu[2] = 1;
polyv[0] = src->v[0];
polyv[1] = src->v[1];
return 2;
}
if (src->u[0] != BAD) {
polyu[0] = src->u[0];
polyu[1] = 1;
polyv[0] = src->v[0];
polyv[1] = 0;
return 1;
}
polyu[0] = 1;
polyv[0] = 0;
polyv[1] = 0;
return 0;
}
void ConfirmationID::divisor_add(const TDivisor* src1, const TDivisor* src2, TDivisor* dst)
{
QWORD u1[3], u2[3], v1[2], v2[2];
int u1deg = u2poly(src1, u1, v1);
int u2deg = u2poly(src2, u2, v2);
// extended gcd: d1 = gcd(u1, u2) = e1*u1 + e2*u2
int d1deg, e1deg, e2deg;
QWORD d1[3], e1[3], e2[3];
polynomial_xgcd(u1deg, u1, u2deg, u2, &d1deg, d1, &e1deg, e1, &e2deg, e2);
assert(e1deg <= 1);
assert(e2deg <= 1);
// extended gcd again: d = gcd(d1, v1+v2) = c1*d1 + c2*(v1+v2)
QWORD b[3] = {residue_add(v1[0], v2[0]), residue_add(v1[1], v2[1]), 0};
int bdeg = (b[1] == 0 ? (b[0] == 0 ? -1 : 0) : 1);
int ddeg, c1deg, c2deg;
QWORD d[3], c1[3], c2[3];
polynomial_xgcd(d1deg, d1, bdeg, b, &ddeg, d, &c1deg, c1, &c2deg, c2);
assert(c1deg <= 0);
assert(c2deg <= 1);
assert(ddeg >= 0);
QWORD dmult = residue_inv(d[ddeg]);
int i;
for (i = 0; i < ddeg; i++)
d[i] = residue_mul(d[i], dmult);
d[i] = 1;
for (i = 0; i <= c1deg; i++)
c1[i] = residue_mul(c1[i], dmult);
for (i = 0; i <= c2deg; i++)
c2[i] = residue_mul(c2[i], dmult);
QWORD u[5];
int udeg = polynomial_mul(u1deg, u1, u2deg, u2, -1, u);
// u is monic
QWORD v[7], tmp[7];
int vdeg, tmpdeg;
// c1*(e1*u1*v2 + e2*u2*v1) + c2*(v1*v2 + f)
// c1*(e1*u1*(v2-v1) + d1*v1) + c2*(v1*v2 + f)
v[0] = residue_sub(v2[0], v1[0]);
v[1] = residue_sub(v2[1], v1[1]);
tmpdeg = polynomial_mul(e1deg, e1, 1, v, -1, tmp);
vdeg = polynomial_mul(u1deg, u1, tmpdeg, tmp, -1, v);
vdeg = polynomial_mul(d1deg, d1, 1, v1, vdeg, v);
for (i = 0; i <= vdeg; i++)
v[i] = residue_mul(v[i], c1[0]);
memcpy(tmp, f, 6 * sizeof(f[0]));
tmpdeg = 5;
tmpdeg = polynomial_mul(1, v1, 1, v2, tmpdeg, tmp);
vdeg = polynomial_mul(c2deg, c2, tmpdeg, tmp, vdeg, v);
if (ddeg > 0) {
assert(udeg >= 2*ddeg);
QWORD udiv[5];
polynomial_div_monic(udeg, u, ddeg, d, udiv); udeg -= ddeg;
polynomial_div_monic(udeg, udiv, ddeg, d, u); udeg -= ddeg;
if (vdeg >= 0) {
assert(vdeg >= ddeg);
polynomial_div_monic(vdeg, v, ddeg, d, udiv); vdeg -= ddeg;
memcpy(v, udiv, (vdeg + 1) * sizeof(v[0]));
}
}
vdeg = polynomial_div_monic(vdeg, v, udeg, u, NULL);
while (udeg > 2) {
assert(udeg <= 4);
assert(vdeg <= 3);
// u' = monic((f-v^2)/u), v'=-v mod u'
tmpdeg = polynomial_mul(vdeg, v, vdeg, v, -1, tmp);
for (i = 0; i <= tmpdeg && i <= 5; i++)
tmp[i] = residue_sub(f[i], tmp[i]);
for (; i <= tmpdeg; i++)
tmp[i] = residue_sub(0, tmp[i]);
for (; i <= 5; i++)
tmp[i] = f[i];
tmpdeg = i - 1;
QWORD udiv[5];
polynomial_div_monic(tmpdeg, tmp, udeg, u, udiv);
udeg = tmpdeg - udeg;
QWORD mult = residue_inv(udiv[udeg]);
for (i = 0; i < udeg; i++)
u[i] = residue_mul(udiv[i], mult);
u[i] = 1;
for (i = 0; i <= vdeg; i++)
v[i] = residue_sub(0, v[i]);
vdeg = polynomial_div_monic(vdeg, v, udeg, u, NULL);
}
if (udeg == 2) {
dst->u[0] = u[0];
dst->u[1] = u[1];
dst->v[0] = (vdeg >= 0 ? v[0] : 0);
dst->v[1] = (vdeg >= 1 ? v[1] : 0);
} else if (udeg == 1) {
dst->u[0] = u[0];
dst->u[1] = BAD;
dst->v[0] = (vdeg >= 0 ? v[0] : 0);
dst->v[1] = BAD;
} else {
assert(udeg == 0);
dst->u[0] = BAD;
dst->u[1] = BAD;
dst->v[0] = BAD;
dst->v[1] = BAD;
}
}
#define divisor_double(src, dst) divisor_add(src, src, dst)
void ConfirmationID::divisor_mul(const TDivisor* src, QWORD mult, TDivisor* dst)
{
if (mult == 0) {
dst->u[0] = BAD;
dst->u[1] = BAD;
dst->v[0] = BAD;
dst->v[1] = BAD;
return;
}
TDivisor cur = *src;
while (!(mult & 1)) {
divisor_double(&cur, &cur);
mult >>= 1;
}
*dst = cur;
while ((mult >>= 1) != 0) {
divisor_double(&cur, &cur);
if (mult & 1)
divisor_add(dst, &cur, dst);
}
}
void ConfirmationID::divisor_mul128(const TDivisor* src, QWORD mult_lo, QWORD mult_hi, TDivisor* dst)
{
if (mult_lo == 0 && mult_hi == 0) {
dst->u[0] = BAD;
dst->u[1] = BAD;
dst->v[0] = BAD;
dst->v[1] = BAD;
return;
}
TDivisor cur = *src;
while (!(mult_lo & 1)) {
divisor_double(&cur, &cur);
mult_lo >>= 1;
if (mult_hi & 1)
mult_lo |= (1ULL << 63);
mult_hi >>= 1;
}
*dst = cur;
for (;;) {
mult_lo >>= 1;
if (mult_hi & 1)
mult_lo |= (1ULL << 63);
mult_hi >>= 1;
if (mult_lo == 0 && mult_hi == 0)
break;
divisor_double(&cur, &cur);
if (mult_lo & 1)
divisor_add(dst, &cur, dst);
}
}
unsigned ConfirmationID::rol(unsigned x, int shift)
{
//assert(shift > 0 && shift < 32);
return (x << shift) | (x >> (32 - shift));
}
void ConfirmationID::sha1_single_block(unsigned char input[64], unsigned char output[20])
{
unsigned a, b, c, d, e;
a = 0x67452301;
b = 0xEFCDAB89;
c = 0x98BADCFE;
d = 0x10325476;
e = 0xC3D2E1F0;
unsigned w[80];
size_t i;
for (i = 0; i < 16; i++)
w[i] = input[4*i] << 24 | input[4*i+1] << 16 | input[4*i+2] << 8 | input[4*i+3];
for (i = 16; i < 80; i++)
w[i] = rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
for (i = 0; i < 20; i++) {
unsigned tmp = rol(a, 5) + ((b & c) | (~b & d)) + e + w[i] + 0x5A827999;
e = d;
d = c;
c = rol(b, 30);
b = a;
a = tmp;
}
for (i = 20; i < 40; i++) {
unsigned tmp = rol(a, 5) + (b ^ c ^ d) + e + w[i] + 0x6ED9EBA1;
e = d;
d = c;
c = rol(b, 30);
b = a;
a = tmp;
}
for (i = 40; i < 60; i++) {
unsigned tmp = rol(a, 5) + ((b & c) | (b & d) | (c & d)) + e + w[i] + 0x8F1BBCDC;
e = d;
d = c;
c = rol(b, 30);
b = a;
a = tmp;
}
for (i = 60; i < 80; i++) {
unsigned tmp = rol(a, 5) + (b ^ c ^ d) + e + w[i] + 0xCA62C1D6;
e = d;
d = c;
c = rol(b, 30);
b = a;
a = tmp;
}
a += 0x67452301;
b += 0xEFCDAB89;
c += 0x98BADCFE;
d += 0x10325476;
e += 0xC3D2E1F0;
output[0] = a >> 24; output[1] = a >> 16; output[2] = a >> 8; output[3] = a;
output[4] = b >> 24; output[5] = b >> 16; output[6] = b >> 8; output[7] = b;
output[8] = c >> 24; output[9] = c >> 16; output[10] = c >> 8; output[11] = c;
output[12] = d >> 24; output[13] = d >> 16; output[14] = d >> 8; output[15] = d;
output[16] = e >> 24; output[17] = e >> 16; output[18] = e >> 8; output[19] = e;
}
void ConfirmationID::Mix(unsigned char* buffer, size_t bufSize, const unsigned char* key, size_t keySize)
{
unsigned char sha1_input[64];
unsigned char sha1_result[20];
size_t half = bufSize / 2;
//assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
int external_counter;
for (external_counter = 0; external_counter < 4; external_counter++) {
memset(sha1_input, 0, sizeof(sha1_input));
memcpy(sha1_input, buffer + half, half);
memcpy(sha1_input + half, key, keySize);
sha1_input[half + keySize] = 0x80;
sha1_input[sizeof(sha1_input) - 1] = (half + keySize) * 8;
sha1_input[sizeof(sha1_input) - 2] = (half + keySize) * 8 / 0x100;
sha1_single_block(sha1_input, sha1_result);
size_t i;
for (i = half & ~3; i < half; i++)
sha1_result[i] = sha1_result[i + 4 - (half & 3)];
for (i = 0; i < half; i++) {
unsigned char tmp = buffer[i + half];
buffer[i + half] = buffer[i] ^ sha1_result[i];
buffer[i] = tmp;
}
}
}
void ConfirmationID::Unmix(unsigned char* buffer, size_t bufSize, const unsigned char* key, size_t keySize)
{
unsigned char sha1_input[64];
unsigned char sha1_result[20];
size_t half = bufSize / 2;
//assert(half <= sizeof(sha1_result) && half + keySize <= sizeof(sha1_input) - 9);
int external_counter;
for (external_counter = 0; external_counter < 4; external_counter++) {
memset(sha1_input, 0, sizeof(sha1_input));
memcpy(sha1_input, buffer, half);
memcpy(sha1_input + half, key, keySize);
sha1_input[half + keySize] = 0x80;
sha1_input[sizeof(sha1_input) - 1] = (half + keySize) * 8;
sha1_input[sizeof(sha1_input) - 2] = (half + keySize) * 8 / 0x100;
sha1_single_block(sha1_input, sha1_result);
size_t i;
for (i = half & ~3; i < half; i++)
sha1_result[i] = sha1_result[i + 4 - (half & 3)];
for (i = 0; i < half; i++) {
unsigned char tmp = buffer[i];
buffer[i] = buffer[i + half] ^ sha1_result[i];
buffer[i + half] = tmp;
}
}
}
int ConfirmationID::Generate(const char* installation_id_str, char confirmation_id[49])
{
unsigned char installation_id[19]; // 10**45 < 256**19
size_t installation_id_len = 0;
const char* p = installation_id_str;
size_t count = 0, totalCount = 0;
unsigned check = 0;
size_t i;
for (; *p; p++) {
if (*p == ' ' || *p == '-')
continue;
int d = *p - '0';
if (d < 0 || d > 9)
return ERR_INVALID_CHARACTER;
if (count == 5 || p[1] == 0) {
if (!count)
return (totalCount == 45) ? ERR_TOO_LARGE : ERR_TOO_SHORT;
if (d != check % 7)
return (count < 5) ? ERR_TOO_SHORT : ERR_INVALID_CHECK_DIGIT;
check = 0;
count = 0;
continue;
}
check += (count % 2 ? d * 2 : d);
count++;
totalCount++;
if (totalCount > 45)
return ERR_TOO_LARGE;
unsigned char carry = d;
for (i = 0; i < installation_id_len; i++) {
unsigned x = installation_id[i] * 10 + carry;
installation_id[i] = x & 0xFF;
carry = x >> 8;
}
if (carry) {
assert(installation_id_len < sizeof(installation_id));
installation_id[installation_id_len++] = carry;
}
}
if (totalCount != 41 && totalCount < 45)
return ERR_TOO_SHORT;
for (; installation_id_len < sizeof(installation_id); installation_id_len++)
installation_id[installation_id_len] = 0;
static const unsigned char iid_key[4] = {0x6A, 0xC8, 0x5E, 0xD4};
Unmix(installation_id, totalCount == 41 ? 17 : 19, iid_key, 4);
if (installation_id[18] >= 0x10)
return ERR_UNKNOWN_VERSION;
#pragma pack(push, 1)
struct {
QWORD HardwareID;
QWORD ProductIDLow;
unsigned char ProductIDHigh;
unsigned short KeySHA1;
} parsed;
#pragma pack(pop)
memcpy(&parsed, installation_id, sizeof(parsed));
unsigned productId1 = parsed.ProductIDLow & ((1 << 17) - 1);
unsigned productId2 = (parsed.ProductIDLow >> 17) & ((1 << 10) - 1);
unsigned productId3 = (parsed.ProductIDLow >> 27) & ((1 << 25) - 1);
unsigned version = (parsed.ProductIDLow >> 52) & 7;
unsigned productId4 = (parsed.ProductIDLow >> 55) | (parsed.ProductIDHigh << 9);
if (version != (totalCount == 41 ? 4 : 5))
return ERR_UNKNOWN_VERSION;
//printf("Product ID: %05u-%03u-%07u-%05u\n", productId1, productId2, productId3, productId4);
unsigned char keybuf[16];
memcpy(keybuf, &parsed.HardwareID, 8);
QWORD productIdMixed = (QWORD)productId1 << 41 | (QWORD)productId2 << 58 | (QWORD)productId3 << 17 | productId4;
memcpy(keybuf + 8, &productIdMixed, 8);
TDivisor d;
unsigned char attempt;
for (attempt = 0; attempt <= 0x80; attempt++) {
union {
unsigned char buffer[14];
struct {
QWORD lo;
QWORD hi;
};
} u;
u.lo = 0;
u.hi = 0;
u.buffer[7] = attempt;
Mix(u.buffer, 14, keybuf, 16);
QWORD x2 = ui128_quotient_mod(u.lo, u.hi);
QWORD x1 = u.lo - x2 * MOD;
x2++;
d.u[0] = residue_sub(residue_mul(x1, x1), residue_mul(NON_RESIDUE, residue_mul(x2, x2)));
d.u[1] = residue_add(x1, x1);
if (find_divisor_v(&d))
break;
}
if (attempt > 0x80)
return ERR_UNLUCKY;
divisor_mul128(&d, 0x04e21b9d10f127c1, 0x40da7c36d44c, &d);
union {
struct {
QWORD encoded_lo, encoded_hi;
};
struct {
uint32_t encoded[4];
};
} e;
if (d.u[0] == BAD) {
// we can not get the zero divisor, actually...
e.encoded_lo = __umul128(MOD + 2, MOD, &e.encoded_hi);
} else if (d.u[1] == BAD) {
// O(1/MOD) chance
//encoded = (unsigned __int128)(MOD + 1) * d.u[0] + MOD; // * MOD + d.u[0] is fine too
e.encoded_lo = __umul128(MOD + 1, d.u[0], &e.encoded_hi);
e.encoded_lo += MOD;
e.encoded_hi += (e.encoded_lo < MOD);
} else {
QWORD x1 = (d.u[1] % 2 ? d.u[1] + MOD : d.u[1]) / 2;
QWORD x2sqr = residue_sub(residue_mul(x1, x1), d.u[0]);
QWORD x2 = residue_sqrt(x2sqr);
if (x2 == BAD) {
x2 = residue_sqrt(residue_mul(x2sqr, residue_inv(NON_RESIDUE)));
assert(x2 != BAD);
e.encoded_lo = __umul128(MOD + 1, MOD + x2, &e.encoded_hi);
e.encoded_lo += x1;
e.encoded_hi += (e.encoded_lo < x1);
} else {
// points (-x1+x2, v(-x1+x2)) and (-x1-x2, v(-x1-x2))
QWORD x1a = residue_sub(x1, x2);
QWORD y1 = residue_sub(d.v[0], residue_mul(d.v[1], x1a));
QWORD x2a = residue_add(x1, x2);
QWORD y2 = residue_sub(d.v[0], residue_mul(d.v[1], x2a));
if (x1a > x2a) {
QWORD tmp = x1a;
x1a = x2a;
x2a = tmp;
}
if ((y1 ^ y2) & 1) {
QWORD tmp = x1a;
x1a = x2a;
x2a = tmp;
}
e.encoded_lo = __umul128(MOD + 1, x1a, &e.encoded_hi);
e.encoded_lo += x2a;
e.encoded_hi += (e.encoded_lo < x2a);
}
}
unsigned char decimal[35];
for (i = 0; i < 35; i++) {
unsigned c = e.encoded[3] % 10;
e.encoded[3] /= 10;
unsigned c2 = ((QWORD)c << 32 | e.encoded[2]) % 10;
e.encoded[2] = ((QWORD)c << 32 | e.encoded[2]) / 10;
unsigned c3 = ((QWORD)c2 << 32 | e.encoded[1]) % 10;
e.encoded[1] = ((QWORD)c2 << 32 | e.encoded[1]) / 10;
unsigned c4 = ((QWORD)c3 << 32 | e.encoded[0]) % 10;
e.encoded[0] = ((QWORD)c3 << 32 | e.encoded[0]) / 10;
decimal[34 - i] = c4;
}
assert(e.encoded[0] == 0 && e.encoded[1] == 0 && e.encoded[2] == 0 && e.encoded[3] == 0);
char* q = confirmation_id;
for (i = 0; i < 7; i++) {
if (i)
*q++ = '-';
unsigned char* p = decimal + i*5;
q[0] = p[0] + '0';
q[1] = p[1] + '0';
q[2] = p[2] + '0';
q[3] = p[3] + '0';
q[4] = p[4] + '0';
q[5] = ((p[0]+p[1]*2+p[2]+p[3]*2+p[4]) % 7) + '0';
q += 6;
}
*q++ = 0;
return 0;
}