From 1085bec913d691f2b406b737fce0fa7f75e6752e Mon Sep 17 00:00:00 2001 From: Alex Page Date: Wed, 28 Jun 2023 21:54:40 -0400 Subject: [PATCH] Condense code and add docs --- Cargo.toml | 6 ++ README.md | 1 + umskt/src/bink1998.rs | 31 +++--- umskt/src/bink2002.rs | 27 ++++-- umskt/src/crypto.rs | 172 +++++++++++++++++++++++++++++++-- umskt/src/lib.rs | 12 ++- umskt/src/msr.rs | 61 ------------ umskt/src/weierstrass_curve.rs | 88 ----------------- xpkey/Cargo.toml | 6 -- 9 files changed, 221 insertions(+), 183 deletions(-) create mode 100644 README.md delete mode 100644 umskt/src/msr.rs delete mode 100644 umskt/src/weierstrass_curve.rs diff --git a/Cargo.toml b/Cargo.toml index 582ac5a..057ffd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,9 @@ members = [ "umskt", "xpkey", ] + +[profile.release.package.xpkey] +strip = true +opt-level = "z" +lto = true +codegen-units = 1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea57562 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# UMSKT Rust Edition diff --git a/umskt/src/bink1998.rs b/umskt/src/bink1998.rs index 3c893ec..67cd059 100644 --- a/umskt/src/bink1998.rs +++ b/umskt/src/bink1998.rs @@ -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 { 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 { diff --git a/umskt/src/bink2002.rs b/umskt/src/bink2002.rs index 8e182b9..924ad25 100644 --- a/umskt/src/bink2002.rs +++ b/umskt/src/bink2002.rs @@ -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 { 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 { diff --git a/umskt/src/crypto.rs b/umskt/src/crypto.rs index f817858..5ad2491 100644 --- a/umskt/src/crypto.rs +++ b/umskt/src/crypto.rs @@ -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 { + 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; + } } diff --git a/umskt/src/lib.rs b/umskt/src/lib.rs index df0ac79..136140c 100644 --- a/umskt/src/lib.rs +++ b/umskt/src/lib.rs @@ -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; diff --git a/umskt/src/msr.rs b/umskt/src/msr.rs deleted file mode 100644 index 780b216..0000000 --- a/umskt/src/msr.rs +++ /dev/null @@ -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 { - 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; - } -} diff --git a/umskt/src/weierstrass_curve.rs b/umskt/src/weierstrass_curve.rs deleted file mode 100644 index c03fe01..0000000 --- a/umskt/src/weierstrass_curve.rs +++ /dev/null @@ -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 - } -} diff --git a/xpkey/Cargo.toml b/xpkey/Cargo.toml index 16d7f27..23c7367 100644 --- a/xpkey/Cargo.toml +++ b/xpkey/Cargo.toml @@ -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"