use pgrx::prelude::*;
use num_bigint::BigInt;
use num_traits::{One, Zero};
use sha2::{Digest, Sha256};
pgrx::pg_module_magic!();
#[derive(Debug, Clone, PartialEq)]
struct Point3D {
x: BigInt,
y: BigInt,
z: BigInt,
}
#[derive(Debug, Clone, PartialEq)]
struct EcdsaSignature {
r: BigInt,
s: BigInt,
}
#[derive(Debug, Clone)]
struct EcdsaCurve {
prime: BigInt,
a: BigInt,
b: BigInt,
g_x: BigInt,
g_y: BigInt,
n: BigInt,
}
fn number_from_byte_string(bytes: &[u8]) -> BigInt {
BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes)
}
fn to_jacobian(p: &Point3D) -> Point3D {
Point3D {
x: p.x.clone(),
y: p.y.clone(),
z: BigInt::one(),
}
}
fn from_jacobian(p: &Point3D, prime: &BigInt) -> Option<Point3D> {
if p.z.is_zero() {
return Some(Point3D {
x: BigInt::zero(),
y: BigInt::zero(),
z: BigInt::zero(),
});
}
let z_inv = p.z.modinv(prime)?;
let z_inv2 = &z_inv * &z_inv;
let z_inv3 = &z_inv * &z_inv2;
Some(Point3D {
x: (&p.x * &z_inv2) % prime,
y: (&p.y * &z_inv3) % prime,
z: BigInt::zero(),
})
}
fn jacobian_double(p: &Point3D, a: &BigInt, prime: &BigInt) -> Point3D {
if p.y.is_zero() {
return Point3D {
x: BigInt::zero(),
y: BigInt::zero(),
z: BigInt::zero(),
};
}
let ysq = (&p.y * &p.y) % prime;
let s = (BigInt::from(4) * &p.x * &ysq) % prime;
let m = (BigInt::from(3) * &p.x * &p.x + a * &p.z * &p.z * &p.z * &p.z) % prime;
let nx = (&m * &m - BigInt::from(2) * &s) % prime;
let ny = (&m * (&s - &nx) - BigInt::from(8) * &ysq * &ysq) % prime;
let nz = (BigInt::from(2) * &p.y * &p.z) % prime;
Point3D {
x: nx,
y: ny,
z: nz,
}
}
fn jacobian_add(p: &Point3D, q: &Point3D, a: &BigInt, prime: &BigInt) -> Point3D {
if p.y.is_zero() {
return q.clone();
}
if q.y.is_zero() {
return p.clone();
}
let u1 = (&p.x * &q.z * &q.z) % prime;
let u2 = (&q.x * &p.z * &p.z) % prime;
let s1 = (&p.y * &q.z * &q.z * &q.z) % prime;
let s2 = (&q.y * &p.z * &p.z * &p.z) % prime;
if u1 == u2 {
if s1 != s2 {
return Point3D {
x: BigInt::zero(),
y: BigInt::zero(),
z: BigInt::one(),
};
}
return jacobian_double(p, a, prime);
}
let h = &u2 - &u1;
let r = &s2 - &s1;
let h2 = (&h * &h) % prime;
let h3 = (&h * &h2) % prime;
let u1h2 = (&u1 * &h2) % prime;
let nx = (&r * &r - &h3 - BigInt::from(2) * &u1h2) % prime;
let ny = (&r * (&u1h2 - &nx) - &s1 * &h3) % prime;
let nz = (&h * &p.z * &q.z) % prime;
Point3D {
x: nx,
y: ny,
z: nz,
}
}
fn jacobian_multiply(p: &Point3D, i: &BigInt, n: &BigInt, a: &BigInt, prime: &BigInt) -> Point3D {
if p.y.is_zero() || i.is_zero() {
return Point3D {
x: BigInt::zero(),
y: BigInt::zero(),
z: BigInt::one(),
};
}
if i.is_one() {
return p.clone();
}
if i < &BigInt::zero() || i >= n {
return jacobian_multiply(p, &(i % n), n, a, prime);
}
if i % BigInt::from(2) == BigInt::zero() {
return jacobian_double(&jacobian_multiply(p, &(i / BigInt::from(2)), n, a, prime), a, prime);
}
jacobian_add(
&jacobian_double(&jacobian_multiply(p, &(i / BigInt::from(2)), n, a, prime), a, prime),
p,
a,
prime,
)
}
fn multiply(p: &Point3D, i: &BigInt, n: &BigInt, a: &BigInt, prime: &BigInt) -> Option<Point3D> {
from_jacobian(&jacobian_multiply(&to_jacobian(p), i, n, a, prime), prime)
}
fn add(p: &Point3D, q: &Point3D, a: &BigInt, prime: &BigInt) -> Option<Point3D> {
from_jacobian(&jacobian_add(&to_jacobian(p), &to_jacobian(q), a, prime), prime)
}
fn contains(p: &Point3D, a: &BigInt, b: &BigInt, prime: &BigInt) -> bool {
if p.x < BigInt::zero() || p.x >= *prime {
return false;
}
if p.y < BigInt::zero() || p.y >= *prime {
return false;
}
(&p.y * &p.y - (&p.x * &p.x * &p.x + a * &p.x + b)) % prime == BigInt::zero()
}
fn verify(
message_hash: &[u8],
sig: EcdsaSignature,
public_key: Point3D,
curve: EcdsaCurve,
) -> bool {
let number_message = number_from_byte_string(message_hash);
if !contains(&public_key, &curve.a, &curve.b, &curve.prime) {
return false;
}
if sig.r < BigInt::one() || sig.r >= curve.n {
return false;
}
if sig.s < BigInt::one() || sig.s >= curve.n {
return false;
}
let inv = match sig.s.modinv(&curve.n) {
Some(inv) => inv,
None => return false,
};
let u1 = match multiply(
&Point3D {
x: curve.g_x.clone(),
y: curve.g_y.clone(),
z: BigInt::zero(),
},
&((number_message * &inv) % &curve.n),
&curve.n,
&curve.a,
&curve.prime,
) {
Some(point) => point,
None => return false,
};
let u2 = match multiply(&public_key, &((&sig.r * &inv) % &curve.n), &curve.n, &curve.a, &curve.prime) {
Some(point) => point,
None => return false,
};
let v = match add(&u1, &u2, &curve.a, &curve.prime) {
Some(point) => point,
None => return false,
};
if v.y.is_zero() {
return false;
}
v.x % &curve.n == sig.r
}
#[pg_extern]
pub fn ecdsa_verify(
public_key: &[u8],
input_data: &[u8],
signature: &[u8],
hash_func: &str,
curve_name: &str,
) -> bool {
let curve = match curve_name {
"secp256r1" => secp256r1(),
"secp256k1" => secp256k1(),
_ => panic!("Unsupported curve: {}", curve_name),
};
let message_hash = match hash_func {
"sha256" => Sha256::digest(input_data).to_vec(),
_ => panic!("Unsupported hash function: {}", hash_func),
};
let sig = EcdsaSignature {
r: number_from_byte_string(&signature[..32]),
s: number_from_byte_string(&signature[32..]),
};
let public_key = Point3D {
x: number_from_byte_string(&public_key[..32]),
y: number_from_byte_string(&public_key[32..]),
z: BigInt::zero(),
};
verify(&message_hash, sig, public_key, curve)
}
fn secp256k1() -> EcdsaCurve {
EcdsaCurve {
prime: BigInt::parse_bytes(
b"00ffffffffffffffffffffffffffff\
fffffffffffffffffffffffffffeff\
fffc2f", 16).unwrap(),
a: BigInt::zero(),
b: BigInt::from(7),
g_x: BigInt::parse_bytes(
b"79be667ef9dcbbac55a06295ce87\
0b07029bfcdb2dce28d959f2815b16\
f81798", 16).unwrap(),
g_y: BigInt::parse_bytes(
b"483ada7726a3c4655da4fbfc\
0e1108a8fd17b448a68554199c47d0\
8ffb10d4b8", 16).unwrap(),
n: BigInt::parse_bytes(
b"00ffffffffffffffffffffffffffff\
fffebaaedce6af48a03bbfd25e8cd0\
364141", 16).unwrap(),
}
}
fn secp256r1() -> EcdsaCurve {
EcdsaCurve {
prime: BigInt::parse_bytes(
b"00ffffffff00000001000000000000\
000000000000ffffffffffffffffff\
ffffff", 16).unwrap(),
a: BigInt::parse_bytes(
b"00ffffffff00000001000000000000\
000000000000ffffffffffffffffff\
fffffc", 16).unwrap(),
b: BigInt::parse_bytes(
b"5ac635d8aa3a93e7b3ebbd55769886\
bc651d06b0cc53b0f63bce3c3e27d2\
604b", 16).unwrap(),
g_x: BigInt::parse_bytes(
b"6b17d1f2e12c4247f8bce6e563a4\
40f277037d812deb33a0f4a13945d8\
98c296", 16).unwrap(),
g_y: BigInt::parse_bytes(
b"4fe342e2fe1a7f9b8ee7eb4a\
7c0f9e162bce33576b315ececbb640\
6837bf51f5", 16).unwrap(),
n: BigInt::parse_bytes(
b"00ffffffff00000000ffffffffffff\
ffffbce6faada7179e84f3b9cac2fc\
632551", 16).unwrap(),
}
}
#[cfg(any(test, feature = "pg_test"))]
#[pg_schema]
mod tests {
use super::*;
use num_bigint::BigInt;
use std::str::FromStr;
#[pg_test]
fn test_number_from_byte_string() {
let bytes = b"\x01\x02\x03";
assert_eq!(number_from_byte_string(bytes), BigInt::from(66051));
}
#[pg_test]
fn test_to_jacobian() {
let p = Point3D {
x: BigInt::from(2),
y: BigInt::from(3),
z: BigInt::zero(),
};
let expected = Point3D {
x: BigInt::from(2),
y: BigInt::from(3),
z: BigInt::one(),
};
assert_eq!(to_jacobian(&p), expected);
}
#[pg_test]
fn test_from_jacobian() {
let p = Point3D {
x: BigInt::from(2),
y: BigInt::from(3),
z: BigInt::one(),
};
let prime = BigInt::from(5);
let expected = Some(Point3D {
x: BigInt::from(2),
y: BigInt::from(3),
z: BigInt::zero(),
});
assert_eq!(from_jacobian(&p, &prime), expected);
}
#[pg_test]
fn test_jacobian_double() {
let p = Point3D {
x: BigInt::from(2),
y: BigInt::from(3),
z: BigInt::one(),
};
let a = BigInt::zero();
let prime = BigInt::from(5);
let expected = Point3D {
x: BigInt::zero(),
y: BigInt::from(-4),
z: BigInt::one(),
};
assert_eq!(jacobian_double(&p, &a, &prime), expected);
}
#[pg_test]
fn test_jacobian_add() {
let p = Point3D {
x: BigInt::from(2),
y: BigInt::from(3),
z: BigInt::one(),
};
let q = Point3D {
x: BigInt::from(1),
y: BigInt::from(4),
z: BigInt::one(),
};
let a = BigInt::zero();
let prime = BigInt::from(5);
let expected = Point3D {
x: BigInt::from(-2),
y: BigInt::from(2),
z: BigInt::from(-1),
};
assert_eq!(jacobian_add(&p, &q, &a, &prime), expected);
}
#[pg_test]
fn test_multiply() {
let p = Point3D {
x: BigInt::from_str("55066263022277343669578718895168534326250603453777594175500187360389116729240").unwrap(),
y: BigInt::from_str("32670510020758816978083085130507043184471273380659243275938904335757337482424").unwrap(),
z: BigInt::zero(),
};
let i = BigInt::from_str("76650304483176495741675648870262264680257041494540363405951857559263604352053").unwrap();
let n = BigInt::from_str("115792089237316195423570985008687907852837564279074904382605163141518161494337").unwrap();
let a = BigInt::zero();
let prime = BigInt::from_str("115792089237316195423570985008687907853269984665640564039457584007908834671663").unwrap();
let expected = Some(Point3D {
x: BigInt::from_str("85217781944227650815758470769803916518073404692604958778490580619436728646316").unwrap(),
y: BigInt::from_str("-52225341456763567064525879521783886427788861703590286785555970483835115400271").unwrap(),
z: BigInt::zero(),
});
assert_eq!(multiply(&p, &i, &n, &a, &prime), expected);
}
#[pg_test]
fn test_add() {
let p = Point3D {
x: BigInt::from_str("31403115364582379550907050631875140702418979177549974706941498652441512470458").unwrap(),
y: BigInt::from_str("101818303961377311447148617614805458780290001799451860481930108618473055802284").unwrap(),
z: BigInt::zero(),
};
let q = Point3D {
x: BigInt::from_str("101690386697888660822536733613325166154267525726772224824634387558104511424840").unwrap(),
y: BigInt::from_str("-47036664861243869909569076480358479904318913594830847776653065859528326656316").unwrap(),
z: BigInt::zero(),
};
let a = BigInt::from_str("115792089210356248762697446949407573530086143415290314195533631308867097853948").unwrap();
let prime = BigInt::from_str("115792089210356248762697446949407573530086143415290314195533631308867097853951").unwrap();
let expected = Some(Point3D {
x: BigInt::from_str("7679932563960414347091205306595575529033945270189659289643076129390605281494").unwrap(),
y: BigInt::from_str("-107375252532095138741597567516714532302943937430093290901277802845815571422141").unwrap(),
z: BigInt::zero(),
});
assert_eq!(add(&p, &q, &a, &prime), expected);
}
#[pg_test]
fn test_contains() {
let p = Point3D {
x: BigInt::from_str("115106164243905984849100475305234630054675646194978645692811434710714363667279").unwrap(),
y: BigInt::from_str("19706235080398884982913350654710526234719564240780022609690651720634760609570").unwrap(),
z: BigInt::zero(),
};
let a = BigInt::zero();
let b = BigInt::from(7);
let prime = BigInt::from_str("115792089237316195423570985008687907853269984665640564039457584007908834671663").unwrap();
assert!(contains(&p, &a, &b, &prime));
}
#[pg_test]
fn test_verify_secp256k1() {
let message_hash = hex::decode("d94b9ba3e7dd18a7a265b4d619286c0615ccb24817bb6898578c160a0fec4baa").unwrap();
let sig = EcdsaSignature {
r: BigInt::from_str("25620709521037740117758667172134018589525233359318772110869897469209888916545").unwrap(),
s: BigInt::from_str("113898187235606387790617275461401926013993175009609646753506476585129921911557").unwrap(),
};
let public_key = Point3D {
x: BigInt::from_str("115106164243905984849100475305234630054675646194978645692811434710714363667279").unwrap(),
y: BigInt::from_str("19706235080398884982913350654710526234719564240780022609690651720634760609570").unwrap(),
z: BigInt::zero(),
};
let curve = secp256k1();
assert!(verify(&message_hash, sig, public_key, curve));
}
#[pg_test]
fn test_verify_secp256r1() {
let message_hash = hex::decode("48c08394455a5007945a9025c58be18f1795db8a6f8c12e70a00c1cdd6d3df78").unwrap();
let sig = EcdsaSignature {
r: BigInt::from_str("7679932563960414347091205306595575529033945270189659289643076129390605281494").unwrap(),
s: BigInt::from_str("47844299635965077418200610260443789525430653377570372618360888620298576429143").unwrap(),
};
let public_key = Point3D {
x: BigInt::from_str("57742645121064378973436687487225580113493928349340781038880342836084265852815").unwrap(),
y: BigInt::from_str("99327750397910171089097863507426920114029443958399733106031194020330646322282").unwrap(),
z: BigInt::zero(),
};
let curve = secp256r1();
assert!(verify(&message_hash, sig, public_key, curve));
}
#[pg_test]
fn test_invalid_signature() {
let message_hash = hex::decode("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").unwrap();
let sig = EcdsaSignature {
r: BigInt::from_str("1234567890123456789012345678901234567890123456789012345678901234567890123456").unwrap(),
s: BigInt::from_str("9876543210987654321098765432109876543210987654321098765432109876543210987654").unwrap(),
};
let public_key = Point3D {
x: BigInt::from_str("1234567890123456789012345678901234567890123456789012345678901234567890123456").unwrap(),
y: BigInt::from_str("9876543210987654321098765432109876543210987654321098765432109876543210987654").unwrap(),
z: BigInt::zero(),
};
let curve = secp256r1();
assert!(!verify(&message_hash, sig, public_key, curve));
}
#[pg_test]
fn test_ecdsa_verify() {
let public_key = hex::decode("7fa92dd0666eee7c13ddb7b6249b0c8f9fba4360857c4e15d2fc634a2b5a1f8fdb9983b319469d35e719a3b93e1ac292854cd3ff2ad50898681b0a32ffbcbc6a").unwrap();
let input_data = hex::decode("49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763010000000117bd119a942a38b92bfc3b90a21f7eaa37fe1a7fa0abe27fd15dd20683b14d54").unwrap();
let signature = hex::decode("10fab01307f3eed59bc11601265efaab524b50d017bd9cdfeec4f61b01caa8d669c6e9f8d9bcbdba4e5478cb75b084332d51b0be2c21701b157c7c87abb98057").unwrap();
let hash_func = "sha256";
let curve_name = "secp256r1";
assert!(ecdsa_verify(&public_key, &input_data, &signature, hash_func, curve_name));
}
#[pg_test]
fn test_invalid_signature_ecdsa_verify() {
let public_key = hex::decode("7fa92dd0666eee7c13ddb7b6249b0c8f9fba4360857c4e15d2fc634a2b5a1f8fdb9983b319469d35e719a3b93e1ac292854cd3ff2ad50898681b0a32ffbcbc6a").unwrap();
let input_data = hex::decode("49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763010000000117bd119a942a38b92bfc3b90a21f7eaa37fe1a7fa0abe27fd15dd20683b14d54").unwrap();
let signature = hex::decode("10fab01307f3eed59bc11601265efaab524b50d017bd9cdfeec4f61b01caa8d669c6e9f8d9bcbdba4e5478cb75b084332d51b0be2c21701b157c7c87abb98056").unwrap();
let hash_func = "sha256";
let curve_name = "secp256r1";
assert!(!ecdsa_verify(&public_key, &input_data, &signature, hash_func, curve_name));
}
}
#[cfg(test)]
pub mod pg_test {
pub fn setup(_options: Vec<&str>) {
}
pub fn postgresql_conf_options() -> Vec<&'static str> {
vec![]
}
}