dignifiedquire / num-bigint

Big integer types for Rust
Apache License 2.0
12 stars 26 forks source link

Use case study. #16

Open cheako opened 4 years ago

cheako commented 4 years ago

This is an implementation of an EC library over brainpoolP256r1. Goals ECDSA and key derivations borrowing from Bitcoin HD wallets/bip-0032.

https://gitlab.com/cheako/jtm-rs/-/blob/8de6da4c648b9bca3351991673831feb111aa3dc/src/lib.rs

I found some things a little overwhelming. mod_inv() in particular, I ended up copying a Python implementation.

This is an example of how things come out if you just toss equations up into the air and see where they land. There is a lot to learn from here and I'm in a position to learn new things from a review as well.

Thanks!

dignifiedquire commented 4 years ago

hey, happy to hear this is being useful for others :)

Did you find anything missing in particular?

cheako commented 4 years ago

I latched onto BigInt::from_biguint() and I think a section of documentation describing the other methods for converting to BigInt from BigUint would be worthwhile. I think into_bigint/to_bigint may be simpler ways covering what's needed here. For the storage of Point information it became critical that these were always positive, but that meant that a conversion was needed prior to doing the math.

I don't know if it's missing, but ModInverse didn't behave the way I needed. I ended up using the following(taken from Python's tinyec) and I have no idea why this worked for me. I can dig a little more here to see what it is I need. For example I can say that the EDIT(divisor) will always be a "positive" prime number, but the dividend can be either positive or negative and I believe it's the negative case where ModInverse wasn't returning the correct values.

use num_bigint_dig::{BigInt, BigUint, Sign::Plus};
fn egcd(a: BigInt, b: BigInt) -> (BigInt, BigInt, BigInt) {
    if a == 0.into() {
        (b, 0.into(), 1.into())
    } else {
        use num_integer::Integer;
        let (g, y, x) = egcd(b.mod_floor(&a), a.clone());
        (g, x - (b / a) * y.clone(), y)
    }
}

fn mod_inv(a: BigInt, p: BigUint) -> BigUint {
    if a < 0.into() {
        p.clone() - mod_inv(-a, p.clone())
    } else {
        let p = BigInt::from_biguint(Plus, p);
        let (g, x, _y) = egcd(a, p.clone());
        if g == 1.into() {
            use num_integer::Integer;
            x.mod_floor(&p).to_biguint().unwrap()
        } else {
            panic!()
        }
    }
}
cheako commented 4 years ago

There are some in the crypto field that think generic_array is the path forward, but I don't see any math support there.

I think there is a use case for optimisations over/given a finite field. Though any implementation would share a lot of code with bigint, so I feel it would be best to work in cooperation.

I don't know how well the trick used in generic_array would scale to say implement a generic_finite_field type... If there can just be vast numbers of generics. I know I'm not explaining well, but for generic_finite_field the number of generics would literally extend a generic_array many times past a size in exabytes.