Condense code and add docs

This commit is contained in:
Alex Page 2023-06-28 21:54:40 -04:00
parent 369b8b04bc
commit 1085bec913
9 changed files with 221 additions and 183 deletions

View file

@ -5,3 +5,9 @@ members = [
"umskt",
"xpkey",
]
[profile.release.package.xpkey]
strip = true
opt-level = "z"
lto = true
codegen-units = 1

1
README.md Normal file
View file

@ -0,0 +1 @@
# UMSKT Rust Edition

View file

@ -1,3 +1,4 @@
//! Structs to deal with older BINK (< `0x40`) product keys
use std::{
cmp::Ordering,
fmt::{Display, Formatter},
@ -12,10 +13,9 @@ use rand::Rng;
use sha1::{Digest, Sha1};
use crate::{
crypto::{EllipticCurve, PrivateKey},
crypto::{EllipticCurve, Point, PrivateKey},
key::{base24_decode, base24_encode, strip_key},
math::bitmask,
weierstrass_curve::{Point, WeierstrassCurve},
};
const FIELD_BITS: u64 = 384;
@ -27,6 +27,9 @@ const SERIAL_LENGTH_BITS: u8 = 30;
const UPGRADE_LENGTH_BITS: u8 = 1;
const EVERYTHING_ELSE: u8 = HASH_LENGTH_BITS + SERIAL_LENGTH_BITS + UPGRADE_LENGTH_BITS;
/// A product key for a BINK ID less than `0x40`
///
/// Every `ProductKey` contains a valid key for its given parameters.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ProductKey {
upgrade: bool,
@ -37,6 +40,9 @@ pub struct ProductKey {
}
impl ProductKey {
/// Generates a new product key for the given parameters.
///
/// The key is verified to be valid before being returned.
pub fn new(
curve: &EllipticCurve,
private_key: &PrivateKey,
@ -62,7 +68,7 @@ impl ProductKey {
// Generate a new random key
let product_key = Self::generate(
&curve.curve,
curve,
&curve.gen_point,
&private_key.gen_order,
&private,
@ -72,24 +78,30 @@ impl ProductKey {
)?;
// Make sure the key is valid
product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
product_key.verify(curve, &curve.gen_point, &curve.pub_point)?;
// Ship it
Ok(product_key)
}
/// Validates an existing product key string and tried to create a new `ProductKey` from it.
///
/// # Arguments
///
/// * `curve` - The elliptic curve to use for verification.
/// * `key` - Should be 25 characters long, not including the (optional) hyphens.
pub fn from_key(curve: &EllipticCurve, key: &str) -> Result<Self> {
let key = strip_key(key)?;
let Ok(packed_key) = base24_decode(&key) else {
bail!("Product key is in an incorrect format!")
};
let product_key = Self::from_packed(&packed_key)?;
product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
product_key.verify(curve, &curve.gen_point, &curve.pub_point)?;
Ok(product_key)
}
fn generate(
e_curve: &WeierstrassCurve,
e_curve: &EllipticCurve,
base_point: &Point,
gen_order: &BigInt,
private_key: &BigInt,
@ -103,16 +115,11 @@ impl ProductKey {
let mut rng = rand::thread_rng();
let product_key = loop {
// Generate a random number c consisting of 384 bits without any constraints.
let c: BigUint = rng.sample(RandomBits::new(FIELD_BITS));
let c: BigInt = c.into();
// Pick a random derivative of the base point on the elliptic curve.
// R = cG;
let r = e_curve.multiply_point(&c, base_point);
// Acquire its coordinates.
// x = R.x; y = R.y;
let (x, y) = match r {
Point::Point { x, y } => (x, y),
Point::Infinity => bail!("Point at infinity!"),
@ -175,7 +182,7 @@ impl ProductKey {
fn verify(
&self,
e_curve: &WeierstrassCurve,
e_curve: &EllipticCurve,
base_point: &Point,
public_key: &Point,
) -> Result<bool> {

View file

@ -1,3 +1,4 @@
//! Structs to deal with newer BINK (>= `0x40`) product keys
use std::{
cmp::Ordering,
fmt::{Display, Formatter},
@ -12,11 +13,9 @@ use rand::Rng;
use sha1::{Digest, Sha1};
use crate::{
crypto::{EllipticCurve, PrivateKey},
crypto::{mod_sqrt, EllipticCurve, Point, PrivateKey},
key::{base24_decode, base24_encode, strip_key},
math::{bitmask, by_dword, next_sn_bits},
msr::mod_sqrt,
weierstrass_curve::{Point, WeierstrassCurve},
};
const FIELD_BITS: u64 = 512;
@ -30,6 +29,9 @@ const UPGRADE_LENGTH_BITS: u8 = 1;
const EVERYTHING_ELSE: u8 =
SIGNATURE_LENGTH_BITS + HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS;
/// A product key for a BINK ID `0x40` or higher
///
/// Every `ProductKey` contains a valid key for its given parameters.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ProductKey {
upgrade: bool,
@ -40,6 +42,9 @@ pub struct ProductKey {
}
impl ProductKey {
/// Generates a new product key for the given parameters.
///
/// The key is verified to be valid before being returned.
pub fn new(
curve: &EllipticCurve,
private_key: &PrivateKey,
@ -63,7 +68,7 @@ impl ProductKey {
// Generate a new random key
let product_key = Self::generate(
&curve.curve,
curve,
&curve.gen_point,
&private_key.gen_order,
&private_key.private_key,
@ -73,19 +78,25 @@ impl ProductKey {
)?;
// Make sure the key is valid
product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
product_key.verify(curve, &curve.gen_point, &curve.pub_point)?;
// Ship it
Ok(product_key)
}
/// Validates an existing product key string and tried to create a new `ProductKey` from it.
///
/// # Arguments
///
/// * `curve` - The elliptic curve to use for verification.
/// * `key` - Should be 25 characters long, not including the (optional) hyphens.
pub fn from_key(curve: &EllipticCurve, key: &str) -> Result<Self> {
let key = strip_key(key)?;
let Ok(packed_key) = base24_decode(&key) else {
bail!("Product key is in an incorrect format!")
};
let product_key = Self::from_packed(&packed_key)?;
let verified = product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
let verified = product_key.verify(curve, &curve.gen_point, &curve.pub_point)?;
if !verified {
bail!("Product key is invalid! Wrong BINK ID?");
}
@ -93,7 +104,7 @@ impl ProductKey {
}
fn generate(
e_curve: &WeierstrassCurve,
e_curve: &EllipticCurve,
base_point: &Point,
gen_order: &BigInt,
private_key: &BigInt,
@ -223,7 +234,7 @@ impl ProductKey {
fn verify(
&self,
e_curve: &WeierstrassCurve,
e_curve: &EllipticCurve,
base_point: &Point,
public_key: &Point,
) -> Result<bool> {

View file

@ -1,15 +1,33 @@
//! Code that deals with elliptic curve cryptography
use anyhow::Result;
use num_bigint::BigInt;
use num_traits::Num;
use num_integer::Integer;
use num_traits::{Num, One, Zero};
use crate::weierstrass_curve::{Point, WeierstrassCurve};
/// Represents a point (possibly) on an elliptic curve.
///
/// This is either the point at infinity, or a point with affine coordinates `x` and `y`.
/// It is not guaranteed to be on the curve.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Point {
Infinity,
Point { x: BigInt, y: BigInt },
}
/// Represents an elliptic curve of the form `y^2 = x^3 + ax + b (mod p)`
///
/// `b` is not used in any of the calculations, so is not stored.
///
/// This implements all the necessary elliptic curve arithmetic for verifying and generating
/// product keys.
pub struct EllipticCurve {
pub curve: WeierstrassCurve,
a: BigInt,
p: BigInt,
pub gen_point: Point,
pub pub_point: Point,
}
/// Stores the additional data necessary to generate product keys.
pub struct PrivateKey {
pub gen_order: BigInt,
pub private_key: BigInt,
@ -27,6 +45,7 @@ impl PrivateKey {
}
impl EllipticCurve {
/// Creates a new elliptic curve from the given parameters. `b` is not necessary.
pub fn new(
p: &str,
a: &str,
@ -42,8 +61,6 @@ impl EllipticCurve {
let public_key_x = BigInt::from_str_radix(public_key_x, 10)?;
let public_key_y = BigInt::from_str_radix(public_key_y, 10)?;
let curve = WeierstrassCurve::new(a, p);
let gen_point = Point::Point {
x: generator_x,
y: generator_y,
@ -55,9 +72,152 @@ impl EllipticCurve {
};
Ok(Self {
curve,
a,
p,
gen_point,
pub_point,
})
}
fn mod_inverse(a: &BigInt, p: &BigInt) -> BigInt {
let egcd = a.extended_gcd(p);
egcd.x.mod_floor(p)
}
fn double_point(&self, point: &Point) -> Point {
match point {
Point::Point { x, y } => {
if y.is_zero() {
Point::Infinity
} else {
let three = BigInt::from(3);
let two = BigInt::from(2);
let lambda = (three * x * x + &self.a) * Self::mod_inverse(&(two * y), &self.p);
let lamba_sqr = (&lambda * &lambda).mod_floor(&self.p);
let x3 = (&lamba_sqr - x - x).mod_floor(&self.p);
let y3 = (&lambda * (x - &x3) - y).mod_floor(&self.p);
Point::Point { x: x3, y: y3 }
}
}
Point::Infinity => Point::Infinity,
}
}
/// Adds two points on the curve together.
///
/// If the points are the same, it doubles the point.
///
/// If one of the points is the point at infinity, it returns the other point.
///
/// If both points are the point at infinity, it returns the point at infinity.
pub(crate) fn add_points(&self, point1: &Point, point2: &Point) -> Point {
match (point1, point2) {
(Point::Point { x: x1, y: y1 }, Point::Point { x: x2, y: y2 }) => {
if point1 == point2 {
self.double_point(point1)
} else {
let lambda = (y2 - y1) * Self::mod_inverse(&(x2 - x1), &self.p);
let x3 = ((&lambda * &lambda) - x1 - x2).mod_floor(&self.p);
let y3: BigInt = ((&lambda * (x1 - &x3)) - y1).mod_floor(&self.p);
Point::Point { x: x3, y: y3 }
}
}
(Point::Point { x, y }, Point::Infinity) | (Point::Infinity, Point::Point { x, y }) => {
Point::Point {
x: x.clone(),
y: y.clone(),
}
}
(Point::Infinity, Point::Infinity) => Point::Infinity,
}
}
/// Multiplies a point by a scalar.
///
/// Uses the double-and-add algorithm.
pub(crate) fn multiply_point(&self, s: &BigInt, point: &Point) -> Point {
let mut res = Point::Infinity;
let mut temp = point.clone();
let mut s = s.clone();
while s > BigInt::zero() {
if (&s % BigInt::from(2)) == BigInt::one() {
res = self.add_points(&res, &temp);
}
temp = self.double_point(&temp);
s >>= 1;
}
res
}
}
/// Calculates the legendre symbol of `p`: `1`, `0`, or `-1 mod p`
fn ls(a: &BigInt, p: &BigInt) -> BigInt {
let exp = (p - BigInt::one()) / BigInt::from(2);
a.modpow(&exp, p)
}
/// Calculates the modular square root of `n` such that `result^2 = n (mod p)`
/// using the Tonelli-Shanks algorithm. Returns `None` if `p` is not prime.
///
/// # Arguments
///
/// * `n` - The number to find the square root of
/// * `p` - The prime modulus (_must_ be prime)
pub(crate) fn mod_sqrt(n: &BigInt, p: &BigInt) -> Option<BigInt> {
if !ls(n, p).is_one() {
return None;
}
let mut q = p - 1;
let mut s = BigInt::zero();
while (&q & &BigInt::one()).is_zero() {
s += 1;
q >>= 1
}
if s.is_one() {
let exp = (p + 1) / 4;
let r1 = n.modpow(&exp, p);
return Some(p - &r1);
}
let mut z = BigInt::from(2);
while ls(&z, p) != p - 1 {
z += 1
}
let mut c = z.modpow(&q, p);
let mut r = n.modpow(&((&q + 1) / 2), p);
let mut t = n.modpow(&q, p);
let mut m = s;
loop {
if t.is_one() {
return Some(p - &r);
}
let mut i = BigInt::zero();
let mut z = t.clone();
let mut b = c.clone();
while !z.is_one() && i < &m - 1 {
z = &z * &z % p;
i += 1;
}
let mut e = &m - &i - 1;
while e > BigInt::zero() {
b = &b * &b % p;
e -= 1;
}
r = &r * &b % p;
c = &b * &b % p;
t = &t * &c % p;
m = i;
}
}

View file

@ -1,8 +1,16 @@
//! # UMSKT Rust Edition
//!
//! This is an unofficial Rust port of the UMSKT project. It is a pure Rust implementation
//! rather than a binding, so it does not require any C or C++ dependencies and can be
//! built for any platform supported by Rust and `std`.
//!
//! It does not include the required `keys.json` file used by UMSKT. That needs to be found elsewhere.
//!
//! See `README.md` for more information.
//!
pub mod bink1998;
pub mod bink2002;
pub mod confid;
pub mod crypto;
mod key;
mod math;
mod msr;
mod weierstrass_curve;

View file

@ -1,61 +0,0 @@
use num_bigint::BigInt;
use num_traits::{One, Zero};
// Legendre symbol, returns 1, 0, or -1 mod p
fn ls(a: &BigInt, p: &BigInt) -> BigInt {
let exp = (p - BigInt::one()) / BigInt::from(2);
a.modpow(&exp, p)
}
// Tonelli-Shanks algorithm
pub fn mod_sqrt(n: &BigInt, p: &BigInt) -> Option<BigInt> {
if !ls(n, p).is_one() {
return None;
}
let mut q = p - 1;
let mut s = BigInt::zero();
while (&q & &BigInt::one()).is_zero() {
s += 1;
q >>= 1
}
if s.is_one() {
let exp = (p + 1) / 4;
let r1 = n.modpow(&exp, p);
return Some(p - &r1);
}
let mut z = BigInt::from(2);
while ls(&z, p) != p - 1 {
z += 1
}
let mut c = z.modpow(&q, p);
let mut r = n.modpow(&((&q + 1) / 2), p);
let mut t = n.modpow(&q, p);
let mut m = s;
loop {
if t.is_one() {
return Some(p - &r);
}
let mut i = BigInt::zero();
let mut z = t.clone();
let mut b = c.clone();
while !z.is_one() && i < &m - 1 {
z = &z * &z % p;
i += 1;
}
let mut e = &m - &i - 1;
while e > BigInt::zero() {
b = &b * &b % p;
e -= 1;
}
r = &r * &b % p;
c = &b * &b % p;
t = &t * &c % p;
m = i;
}
}

View file

@ -1,88 +0,0 @@
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Zero};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Point {
Infinity,
Point { x: BigInt, y: BigInt },
}
#[derive(Debug, Clone)]
pub struct WeierstrassCurve {
a: BigInt,
p: BigInt,
}
impl WeierstrassCurve {
pub fn new(a: BigInt, p: BigInt) -> Self {
WeierstrassCurve { a, p }
}
fn mod_inverse(a: &BigInt, p: &BigInt) -> BigInt {
let egcd = a.extended_gcd(p);
egcd.x.mod_floor(p)
}
fn double_point(&self, point: &Point) -> Point {
match point {
Point::Point { x, y } => {
if y.is_zero() {
Point::Infinity
} else {
let three = BigInt::from(3);
let two = BigInt::from(2);
let lambda = (three * x * x + &self.a) * Self::mod_inverse(&(two * y), &self.p);
let lamba_sqr = (&lambda * &lambda).mod_floor(&self.p);
let x3 = (&lamba_sqr - x - x).mod_floor(&self.p);
let y3 = (&lambda * (x - &x3) - y).mod_floor(&self.p);
Point::Point { x: x3, y: y3 }
}
}
Point::Infinity => Point::Infinity,
}
}
pub fn add_points(&self, point1: &Point, point2: &Point) -> Point {
match (point1, point2) {
(Point::Point { x: x1, y: y1 }, Point::Point { x: x2, y: y2 }) => {
if point1 == point2 {
self.double_point(point1)
} else {
let lambda = (y2 - y1) * Self::mod_inverse(&(x2 - x1), &self.p);
let x3 = ((&lambda * &lambda) - x1 - x2).mod_floor(&self.p);
let y3: BigInt = ((&lambda * (x1 - &x3)) - y1).mod_floor(&self.p);
Point::Point { x: x3, y: y3 }
}
}
(Point::Point { x, y }, Point::Infinity) | (Point::Infinity, Point::Point { x, y }) => {
Point::Point {
x: x.clone(),
y: y.clone(),
}
}
(Point::Infinity, Point::Infinity) => Point::Infinity,
}
}
pub fn multiply_point(&self, s: &BigInt, point: &Point) -> Point {
let mut res = Point::Infinity;
let mut temp = point.clone();
let mut s = s.clone();
while s > BigInt::zero() {
if (&s % BigInt::from(2)) == BigInt::one() {
res = self.add_points(&res, &temp);
}
temp = self.double_point(&temp);
s >>= 1;
}
res
}
}

View file

@ -3,12 +3,6 @@ name = "xpkey"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
[dependencies]
umskt = { path = "../umskt" }
anyhow = "1.0.71"