Repository: RustCrypto/signatures
Pull request #906: dsa: migrate to `crypto-bigint`
Author: login: baloo
Created: 2025-02-16T08:50:18Z | Merged: 2025-04-23T17:30:27Z

This is a rebase of #784

There are two commits on top that I think should fix tests. There is a bit of cleanup to do still.


Diff:
diff --git a/.github/workflows/dsa.yml b/.github/workflows/dsa.yml
index 1da1e5fa..7322885a 100644
--- a/.github/workflows/dsa.yml
+++ b/.github/workflows/dsa.yml
@@ -24,7 +24,7 @@ jobs:
           - thumbv7em-none-eabi
           - wasm32-unknown-unknown
         toolchain:
-          - 1.81.0 # MSRV
+          - 1.85.0 # MSRV
           - stable
     steps:
       - uses: actions/checkout@v4
@@ -42,7 +42,7 @@ jobs:
           - macos-latest
           - windows-latest
         toolchain:
-          - 1.81.0 # MSRV
+          - 1.85.0 # MSRV
           - stable
     runs-on: ${{ matrix.platform }}
     steps:
diff --git a/Cargo.lock b/Cargo.lock
index 975b7079..05ea658d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -268,12 +268,12 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
 [[package]]
 name = "crypto-bigint"
 version = "0.7.0-pre.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6957fb7344601c8271b03e9d4c7efb46f1dee86553eee20f99e54db0cf53f36e"
+source = "git+https://github.com/RustCrypto/crypto-bigint.git#c7889598ac12d55070399f259f38f1eb25da5b5d"
 dependencies = [
  "hybrid-array",
  "num-traits",
  "rand_core 0.9.2",
+ "serdect",
  "subtle",
  "zeroize",
 ]
@@ -287,6 +287,15 @@ dependencies = [
  "hybrid-array",
 ]
 
+[[package]]
+name = "crypto-primes"
+version = "0.7.0-dev"
+source = "git+https://github.com/entropyxyz/crypto-primes.git#3ce564d4dc230f16213339260803443b846d7ede"
+dependencies = [
+ "crypto-bigint",
+ "rand_core 0.9.2",
+]
+
 [[package]]
 name = "ctr"
 version = "0.10.0-pre.2"
@@ -303,10 +312,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319"
 dependencies = [
  "const-oid",
+ "der_derive",
  "pem-rfc7468",
  "zeroize",
 ]
 
+[[package]]
+name = "der_derive"
+version = "0.8.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "211bea8bb45f5f61bc857104606913ef8ac8b5ec698143aa2aa96a7ffdc94991"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
 [[package]]
 name = "digest"
 version = "0.11.0-pre.10"
@@ -319,6 +340,27 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "dsa"
+version = "0.7.0-pre.1"
+dependencies = [
+ "crypto-bigint",
+ "crypto-primes",
+ "der",
+ "digest",
+ "hex",
+ "hex-literal 1.0.0",
+ "pkcs8",
+ "proptest",
+ "rand 0.9.0",
+ "rand_chacha 0.9.0",
+ "rfc6979",
+ "sha1",
+ "sha2",
+ "signature 3.0.0-pre",
+ "zeroize",
+]
+
 [[package]]
 name = "ecdsa"
 version = "0.17.0-pre.9"
@@ -1057,6 +1099,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "sha1"
+version = "0.11.0-pre.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55f44e40722caefdd99383c25d3ae52a1094a1951215ae76f68837ece4e7f566"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
 [[package]]
 name = "sha2"
 version = "0.11.0-pre.5"
diff --git a/Cargo.toml b/Cargo.toml
index 400348b4..c460a8ef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [workspace]
 resolver = "2"
 members = [
-    #"dsa",
+    "dsa",
     "ecdsa",
     "ed448",
     "ed25519",
@@ -10,9 +10,6 @@ members = [
     "rfc6979",
     "slh-dsa",
 ]
-exclude = [
-    "dsa", # still on rand_core 0.8
-]
 
 [profile.dev]
 opt-level = 2
@@ -20,7 +17,7 @@ opt-level = 2
 [patch.crates-io]
 # A global patch crates-io block is used to avoid duplicate dependencies
 # when pulling a member crate through git
-#dsa             = { path = "./dsa" }
+dsa             = { path = "./dsa" }
 ecdsa           = { path = "./ecdsa" }
 ed448-signature = { path = "./ed448" }
 ed25519         = { path = "./ed25519" }
@@ -35,3 +32,9 @@ slh-dsa         = { path = "./slh-dsa" }
 # https://github.com/RustCrypto/traits/pull/1822
 elliptic-curve = { git = "https://github.com/RustCrypto/traits.git" }
 signature      = { git = "https://github.com/RustCrypto/traits.git" }
+ 
+# https://github.com/RustCrypto/crypto-bigint/pull/762
+# https://github.com/RustCrypto/crypto-bigint/pull/765
+crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint.git" }
+
+crypto-primes = { git = "https://github.com/entropyxyz/crypto-primes.git" }
diff --git a/dsa/Cargo.lock b/dsa/Cargo.lock
deleted file mode 100644
index 600e90b8..00000000
--- a/dsa/Cargo.lock
+++ /dev/null
@@ -1,400 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "autocfg"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
-
-[[package]]
-name = "base64ct"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
-
-[[package]]
-name = "block-buffer"
-version = "0.11.0-rc.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc"
-dependencies = [
- "hybrid-array",
-]
-
-[[package]]
-name = "byteorder"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "const-oid"
-version = "0.10.0-rc.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ff6be19477a1bd5441f382916a89bc2a0b2c35db6d41e0f6e8538bf6d6463f"
-
-[[package]]
-name = "cpufeatures"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "crypto-common"
-version = "0.2.0-rc.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa"
-dependencies = [
- "getrandom",
- "hybrid-array",
- "rand_core",
-]
-
-[[package]]
-name = "der"
-version = "0.8.0-rc.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319"
-dependencies = [
- "const-oid",
- "pem-rfc7468",
- "zeroize",
-]
-
-[[package]]
-name = "digest"
-version = "0.11.0-pre.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379"
-dependencies = [
- "block-buffer",
- "const-oid",
- "crypto-common",
- "subtle",
-]
-
-[[package]]
-name = "dsa"
-version = "0.7.0-pre.1"
-dependencies = [
- "digest",
- "num-bigint-dig",
- "num-traits",
- "pkcs8",
- "rand",
- "rand_chacha",
- "rfc6979",
- "sha1",
- "sha2",
- "signature",
- "zeroize",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "hmac"
-version = "0.13.0-pre.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4b1fb14e4df79f9406b434b60acef9f45c26c50062cccf1346c6103b8c47d58"
-dependencies = [
- "digest",
-]
-
-[[package]]
-name = "hybrid-array"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
-dependencies = [
- "typenum",
-]
-
-[[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-dependencies = [
- "spin",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.169"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
-
-[[package]]
-name = "libm"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
-
-[[package]]
-name = "num-bigint-dig"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
-dependencies = [
- "byteorder",
- "lazy_static",
- "libm",
- "num-integer",
- "num-iter",
- "num-traits",
- "rand",
- "smallvec",
- "zeroize",
-]
-
-[[package]]
-name = "num-integer"
-version = "0.1.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "num-iter"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "pem-rfc7468"
-version = "1.0.0-rc.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2dfbfa5c6f0906884269722c5478e72fd4d6c0e24fe600332c6d62359567ce1"
-dependencies = [
- "base64ct",
-]
-
-[[package]]
-name = "pkcs8"
-version = "0.11.0-rc.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f22636de7c995e997ed3d8d2949b7414d4faba3efa7312a6c0e75d875a14bdd4"
-dependencies = [
- "der",
- "spki",
-]
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
-dependencies = [
- "zerocopy",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.93"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.38"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
-dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom",
-]
-
-[[package]]
-name = "rfc6979"
-version = "0.5.0-pre.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "871ee76a3eee98b0f805e5d1caf26929f4565073c580c053a55f886fc15dea49"
-dependencies = [
- "hmac",
- "subtle",
-]
-
-[[package]]
-name = "sha1"
-version = "0.11.0-pre.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9540978cef7a8498211c1b1c14e5ce920fe5bd524ea84f4a3d72d4602515ae93"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
-[[package]]
-name = "sha2"
-version = "0.11.0-pre.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "540c0893cce56cdbcfebcec191ec8e0f470dd1889b6e7a0b503e310a94a168f5"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
-[[package]]
-name = "signature"
-version = "2.3.0-pre.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "054d71959c7051b9042c26af337f05cc930575ed2604d7d3ced3158383e59734"
-dependencies = [
- "digest",
- "rand_core",
-]
-
-[[package]]
-name = "smallvec"
-version = "1.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
-
-[[package]]
-name = "spin"
-version = "0.9.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
-
-[[package]]
-name = "spki"
-version = "0.8.0-rc.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37ac66481418fd7afdc584adcf3be9aa572cf6c2858814494dc2a01755f050bc"
-dependencies = [
- "base64ct",
- "der",
-]
-
-[[package]]
-name = "subtle"
-version = "2.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
-
-[[package]]
-name = "syn"
-version = "2.0.98"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "typenum"
-version = "1.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
-
-[[package]]
-name = "wasi"
-version = "0.11.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
-
-[[package]]
-name = "zerocopy"
-version = "0.7.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
-dependencies = [
- "byteorder",
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.7.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "zeroize"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/dsa/Cargo.toml b/dsa/Cargo.toml
index 86e11b62..b48e5b6e 100644
--- a/dsa/Cargo.toml
+++ b/dsa/Cargo.toml
@@ -6,30 +6,34 @@ Pure Rust implementation of the Digital Signature Algorithm (DSA) as specified
 in FIPS 186-4 (Digital Signature Standard), providing RFC6979 deterministic
 signatures as well as support for added entropy
 """
-edition = "2021"
+edition = "2024"
 license = "Apache-2.0 OR MIT"
 readme = "README.md"
 homepage = "https://github.com/RustCrypto/signatures/tree/master/dsa"
 repository = "https://github.com/RustCrypto/signatures"
 categories = ["cryptography", "no-std"]
 keywords = ["crypto", "nist", "signature"]
-rust-version = "1.81"
+rust-version = "1.85"
 
 [dependencies]
-digest = "=0.11.0-pre.9"
-num-bigint = { package = "num-bigint-dig", version = "0.8", default-features = false, features = ["prime", "rand", "zeroize"] }
-num-traits = { version = "0.2", default-features = false }
+digest = "=0.11.0-pre.10"
+crypto-bigint = { version = "=0.7.0-pre.1", default-features = false, features = ["alloc", "zeroize"] }
+crypto-primes = { version = "=0.7.0-dev", default-features = false }
 pkcs8 = { version = "0.11.0-rc.1", default-features = false, features = ["alloc"] }
 rfc6979 = { version = "=0.5.0-pre.4" }
-sha2 = { version = "=0.11.0-pre.4", default-features = false }
-signature = { version = "=2.3.0-pre.4", default-features = false, features = ["alloc", "digest", "rand_core"] }
+sha2 = { version = "=0.11.0-pre.5", default-features = false }
+signature = { version = "=3.0.0-pre", default-features = false, features = ["alloc", "digest", "rand_core"] }
 zeroize = { version = "1", default-features = false }
 
 [dev-dependencies]
+hex = "0.4.3"
+hex-literal = "1"
 pkcs8 = { version = "0.11.0-rc.1", default-features = false, features = ["pem"] }
-rand = "0.8"
-rand_chacha = "0.3"
-sha1 = "=0.11.0-pre.4"
+proptest = "1"
+rand = "0.9"
+rand_chacha = "0.9"
+sha1 = "=0.11.0-pre.5"
+der = { version = "0.8.0-rc.1", features = ["derive"] }
 
 [features]
 std = []
diff --git a/dsa/examples/export.rs b/dsa/examples/export.rs
index ebedc32f..4b7e3fa1 100644
--- a/dsa/examples/export.rs
+++ b/dsa/examples/export.rs
@@ -5,7 +5,7 @@ use pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};
 use std::{fs::File, io::Write};
 
 fn main() {
-    let mut rng = rand::thread_rng();
+    let mut rng = rand::rng();
     let components = Components::generate(&mut rng, KeySize::DSA_2048_256);
     let signing_key = SigningKey::generate(&mut rng, components);
     let verifying_key = signing_key.verifying_key();
diff --git a/dsa/examples/generate.rs b/dsa/examples/generate.rs
index 443b4991..14a42da2 100644
--- a/dsa/examples/generate.rs
+++ b/dsa/examples/generate.rs
@@ -3,7 +3,7 @@
 use dsa::{Components, KeySize, SigningKey};
 
 fn main() {
-    let mut rng = rand::thread_rng();
+    let mut rng = rand::rng();
     let components = Components::generate(&mut rng, KeySize::DSA_2048_256);
     let signing_key = SigningKey::generate(&mut rng, components);
     let _verifying_key = signing_key.verifying_key();
diff --git a/dsa/examples/sign.rs b/dsa/examples/sign.rs
index 25be84fd..7ce28fe2 100644
--- a/dsa/examples/sign.rs
+++ b/dsa/examples/sign.rs
@@ -8,15 +8,13 @@ use signature::{RandomizedDigestSigner, SignatureEncoding};
 use std::{fs::File, io::Write};
 
 fn main() {
-    let mut rng = rand::thread_rng();
+    let mut rng = rand::rng();
     let components = Components::generate(&mut rng, KeySize::DSA_2048_256);
     let signing_key = SigningKey::generate(&mut rng, components);
     let verifying_key = signing_key.verifying_key();
 
-    let signature = signing_key.sign_digest_with_rng(
-        &mut rand::thread_rng(),
-        Sha1::new().chain_update(b"hello world"),
-    );
+    let signature = signing_key
+        .sign_digest_with_rng(&mut rand::rng(), Sha1::new().chain_update(b"hello world"));
 
     let signing_key_bytes = signing_key.to_pkcs8_pem(LineEnding::LF).unwrap();
     let verifying_key_bytes = verifying_key.to_public_key_pem(LineEnding::LF).unwrap();
diff --git a/dsa/src/components.rs b/dsa/src/components.rs
index dbc708e9..d1e3fbff 100644
--- a/dsa/src/components.rs
+++ b/dsa/src/components.rs
@@ -3,13 +3,12 @@
 //!
 
 use crate::{size::KeySize, two};
-use num_bigint::BigUint;
-use num_traits::Zero;
+use crypto_bigint::{BoxedUint, NonZero, Odd};
 use pkcs8::der::{
-    self, asn1::UintRef, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, Tag,
-    Writer,
+    self, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, Tag, Writer,
+    asn1::UintRef,
 };
-use signature::rand_core::CryptoRngCore;
+use signature::rand_core::CryptoRng;
 
 /// The common components of an DSA keypair
 ///
@@ -18,46 +17,61 @@ use signature::rand_core::CryptoRngCore;
 #[must_use]
 pub struct Components {
     /// Prime p
-    p: BigUint,
+    p: Odd<BoxedUint>,
 
     /// Quotient q
-    q: BigUint,
+    q: NonZero<BoxedUint>,
 
     /// Generator g
-    g: BigUint,
+    g: NonZero<BoxedUint>,
+
+    pub(crate) key_size: KeySize,
 }
 
 impl Components {
     /// Construct the common components container from its inner values (p, q and g)
-    pub fn from_components(p: BigUint, q: BigUint, g: BigUint) -> signature::Result<Self> {
-        if p < two() || q < two() || g.is_zero() || g > p {
+    pub fn from_components(
+        p: Odd<BoxedUint>,
+        q: NonZero<BoxedUint>,
+        g: NonZero<BoxedUint>,
+    ) -> signature::Result<Self> {
+        if *p < two() || *q < two() || *g > *p {
             return Err(signature::Error::new());
         }
 
-        Ok(Self { p, q, g })
+        let key_size = match (p.bits_precision(), q.bits_precision()) {
+            #[allow(deprecated)]
+            (p, q) if KeySize::DSA_1024_160.matches(p, q) => KeySize::DSA_1024_160,
+            (p, q) if KeySize::DSA_2048_224.matches(p, q) => KeySize::DSA_2048_224,
+            (p, q) if KeySize::DSA_2048_256.matches(p, q) => KeySize::DSA_2048_256,
+            (p, q) if KeySize::DSA_3072_256.matches(p, q) => KeySize::DSA_3072_256,
+            _ => return Err(signature::Error::new()),
+        };
+
+        Ok(Self { p, q, g, key_size })
     }
 
     /// Generate a new pair of common components
-    pub fn generate(rng: &mut impl CryptoRngCore, key_size: KeySize) -> Self {
+    pub fn generate<R: CryptoRng + ?Sized>(rng: &mut R, key_size: KeySize) -> Self {
         let (p, q, g) = crate::generate::common_components(rng, key_size);
         Self::from_components(p, q, g).expect("[Bug] Newly generated components considered invalid")
     }
 
     /// DSA prime p
     #[must_use]
-    pub const fn p(&self) -> &BigUint {
+    pub const fn p(&self) -> &Odd<BoxedUint> {
         &self.p
     }
 
     /// DSA quotient q
     #[must_use]
-    pub const fn q(&self) -> &BigUint {
+    pub const fn q(&self) -> &NonZero<BoxedUint> {
         &self.q
     }
 
     /// DSA generator g
     #[must_use]
-    pub const fn g(&self) -> &BigUint {
+    pub const fn g(&self) -> &NonZero<BoxedUint> {
         &self.g
     }
 }
@@ -70,9 +84,22 @@ impl<'a> DecodeValue<'a> for Components {
         let q = reader.decode::<UintRef<'_>>()?;
         let g = reader.decode::<UintRef<'_>>()?;
 
-        let p = BigUint::from_bytes_be(p.as_bytes());
-        let q = BigUint::from_bytes_be(q.as_bytes());
-        let g = BigUint::from_bytes_be(g.as_bytes());
+        let p = BoxedUint::from_be_slice(p.as_bytes(), (p.as_bytes().len() * 8) as u32)
+            .expect("invariant violation");
+        let q = BoxedUint::from_be_slice(q.as_bytes(), (q.as_bytes().len() * 8) as u32)
+            .expect("invariant violation");
+        let g = BoxedUint::from_be_slice(g.as_bytes(), (g.as_bytes().len() * 8) as u32)
+            .expect("invariant violation");
+
+        let p = Odd::new(p)
+            .into_option()
+            .ok_or(Tag::Integer.value_error())?;
+        let q = NonZero::new(q)
+            .into_option()
+            .ok_or(Tag::Integer.value_error())?;
+        let g = NonZero::new(g)
+            .into_option()
+            .ok_or(Tag::Integer.value_error())?;
 
         Self::from_components(p, q, g).map_err(|_| Tag::Integer.value_error())
     }
@@ -80,17 +107,17 @@ impl<'a> DecodeValue<'a> for Components {
 
 impl EncodeValue for Components {
     fn value_len(&self) -> der::Result<Length> {
-        UintRef::new(&self.p.to_bytes_be())?.encoded_len()?
-            + UintRef::new(&self.q.to_bytes_be())?.encoded_len()?
-            + UintRef::new(&self.g.to_bytes_be())?.encoded_len()?
+        UintRef::new(&self.p.to_be_bytes())?.encoded_len()?
+            + UintRef::new(&self.q.to_be_bytes())?.encoded_len()?
+            + UintRef::new(&self.g.to_be_bytes())?.encoded_len()?
     }
 
     fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> {
-        UintRef::new(&self.p.to_bytes_be())?.encode(writer)?;
-        UintRef::new(&self.q.to_bytes_be())?.encode(writer)?;
-        UintRef::new(&self.g.to_bytes_be())?.encode(writer)?;
+        UintRef::new(&self.p.to_be_bytes())?.encode(writer)?;
+        UintRef::new(&self.q.to_be_bytes())?.encode(writer)?;
+        UintRef::new(&self.g.to_be_bytes())?.encode(writer)?;
         Ok(())
     }
 }
 
-impl<'a> Sequence<'a> for Components {}
+impl Sequence<'_> for Components {}
diff --git a/dsa/src/generate.rs b/dsa/src/generate.rs
index 1f56060e..5a40fbd9 100644
--- a/dsa/src/generate.rs
+++ b/dsa/src/generate.rs
@@ -1,7 +1,6 @@
-use crate::two;
-use num_bigint::{BigUint, RandPrime};
-use num_traits::Pow;
-use signature::rand_core::CryptoRngCore;
+use crypto_bigint::{BoxedUint, NonZero};
+use crypto_primes::{Flavor, random_prime};
+use signature::rand_core::CryptoRng;
 
 mod components;
 mod keypair;
@@ -15,9 +14,12 @@ pub use self::keypair::keypair;
 
 /// Calculate the upper and lower bounds for generating values like p or q
 #[inline]
-fn calculate_bounds(size: u32) -> (BigUint, BigUint) {
-    let lower = two().pow(size - 1);
-    let upper = two().pow(size);
+fn calculate_bounds(size: u32) -> (NonZero<BoxedUint>, NonZero<BoxedUint>) {
+    let lower = BoxedUint::one().widen(size + 1).shl(size - 1);
+    let upper = BoxedUint::one().widen(size + 1).shl(size);
+
+    let lower = NonZero::new(lower).expect("[bug] shl can't go backward");
+    let upper = NonZero::new(upper).expect("[bug] shl can't go backward");
 
     (lower, upper)
 }
@@ -26,6 +28,6 @@ fn calculate_bounds(size: u32) -> (BigUint, BigUint) {
 ///
 /// This wrapper function mainly exists to enforce the [`CryptoRng`](rand::CryptoRng) requirement (I might otherwise forget it)
 #[inline]
-fn generate_prime(bit_length: usize, rng: &mut impl CryptoRngCore) -> BigUint {
-    rng.gen_prime(bit_length)
+fn generate_prime<R: CryptoRng + ?Sized>(bit_length: u32, rng: &mut R) -> BoxedUint {
+    random_prime(rng, Flavor::Any, bit_length)
 }
diff --git a/dsa/src/generate/components.rs b/dsa/src/generate/components.rs
index 101aefab..bb22ffda 100644
--- a/dsa/src/generate/components.rs
+++ b/dsa/src/generate/components.rs
@@ -3,73 +3,95 @@
 //!
 
 use crate::{
+    Components,
     generate::{calculate_bounds, generate_prime},
     size::KeySize,
-    two, Components,
+    two,
 };
-use num_bigint::{prime::probably_prime, BigUint, RandBigInt};
-use num_traits::One;
-use signature::rand_core::CryptoRngCore;
-
-/// Numbers of miller-rabin rounds performed to determine primality
-const MR_ROUNDS: usize = 64;
+use crypto_bigint::{
+    BoxedUint, NonZero, Odd, RandomBits,
+    modular::{BoxedMontyForm, BoxedMontyParams},
+    subtle::CtOption,
+};
+use crypto_primes::{Flavor, is_prime};
+use signature::rand_core::CryptoRng;
 
 /// Generate the common components p, q, and g
 ///
 /// # Returns
 ///
-/// Tuple of three `BigUint`s. Ordered like this `(p, q, g)`
-pub fn common(
-    rng: &mut impl CryptoRngCore,
+/// Tuple of three `BoxedUint`s. Ordered like this `(p, q, g)`
+pub fn common<R: CryptoRng + ?Sized>(
+    rng: &mut R,
     KeySize { l, n }: KeySize,
-) -> (BigUint, BigUint, BigUint) {
+) -> (Odd<BoxedUint>, NonZero<BoxedUint>, NonZero<BoxedUint>) {
     // Calculate the lower and upper bounds of p and q
     let (p_min, p_max) = calculate_bounds(l);
-    let (q_min, q_max) = calculate_bounds(n);
+    let (q_min, q_max): (NonZero<_>, _) = calculate_bounds(n);
 
-    let (p, q) = 'gen_pq: loop {
-        let q = generate_prime(n as usize, rng);
-        if q < q_min || q > q_max {
+    let (p, q): (Odd<_>, _) = 'gen_pq: loop {
+        let q = generate_prime(n, rng);
+        if q < *q_min || q > *q_max {
             continue;
         }
+        let q = NonZero::new(q)
+            .expect("[bug] invariant violation, q is above q_min which itself is NonZero");
 
         // Attempt to find a prime p which has a subgroup of the order q
         for _ in 0..4096 {
             let m = 'gen_m: loop {
-                let m = rng.gen_biguint(l as usize);
-                if m > p_min && m < p_max {
+                let m = BoxedUint::random_bits(rng, l);
+
+                if m > *p_min && m < *p_max {
                     break 'gen_m m;
                 }
             };
-            let mr = &m % (two() * &q);
-            let p = m - mr + BigUint::one();
+            let rem = NonZero::new((two() * &*q).widen(m.bits_precision()))
+                .expect("[bug] 2 * NonZero can't be zero");
+
+            let mr = &m % &rem;
+            let p = m - mr + BoxedUint::one();
 
-            if probably_prime(&p, MR_ROUNDS) {
+            if is_prime(Flavor::Any, &p) {
+                let p = Odd::new(p).expect("[bug] Any even number would be prime. P is at least 2^L and L is at least 1024.");
                 break 'gen_pq (p, q);
             }
         }
     };
 
+    // Q needs to be the same precision as P for the operations below.
+    let q = q.widen(l);
+
     // Generate g using the unverifiable method as defined by Appendix A.2.1
-    let e = (&p - BigUint::one()) / &q;
-    let mut h = BigUint::one();
+    let e = (&*p - &BoxedUint::one()) / &q;
+    let mut h = BoxedUint::one().widen(l);
     let g = loop {
-        let g = h.modpow(&e, &p);
-        if !g.is_one() {
-            break g;
+        let params = BoxedMontyParams::new_vartime(p.clone());
+        let form = BoxedMontyForm::new(h.clone(), params);
+        let g = form.pow(&e).retrieve();
+
+        if !bool::from(g.is_one()) {
+            // TODO(baloo): shouldn't we check e can't be 1 here?
+            //              and g could still be zero right? In which case just loop around?
+            break NonZero::new(g).unwrap();
         }
 
-        h += BigUint::one();
+        h += BoxedUint::one();
     };
 
+    let q = NonZero::new(q.shorten(n)).expect("[bug] q_min(2^N-1) < q < q_max(2^N), Q is non zero");
+
     (p, q, g)
 }
 
 /// Calculate the public component from the common components and the private component
 #[inline]
-pub fn public(components: &Components, x: &BigUint) -> BigUint {
+pub fn public(components: &Components, x: &NonZero<BoxedUint>) -> CtOption<NonZero<BoxedUint>> {
     let p = components.p();
     let g = components.g();
 
-    g.modpow(x, p)
+    let params = BoxedMontyParams::new_vartime(p.clone());
+    let form = BoxedMontyForm::new((**g).clone(), params);
+
+    NonZero::new(form.pow(x).retrieve())
 }
diff --git a/dsa/src/generate/keypair.rs b/dsa/src/generate/keypair.rs
index c1af8325..1e0c70f6 100644
--- a/dsa/src/generate/keypair.rs
+++ b/dsa/src/generate/keypair.rs
@@ -3,16 +3,41 @@
 //! Generate a DSA keypair
 //!
 
-use crate::{generate::components, signing_key::SigningKey, Components, VerifyingKey};
-use num_bigint::{BigUint, RandBigInt};
-use num_traits::One;
-use signature::rand_core::CryptoRngCore;
+use crate::{Components, VerifyingKey, generate::components, signing_key::SigningKey};
+use crypto_bigint::{BoxedUint, NonZero, RandomMod};
+use signature::rand_core::CryptoRng;
 
 /// Generate a new keypair
 #[inline]
-pub fn keypair(rng: &mut impl CryptoRngCore, components: Components) -> SigningKey {
-    let x = rng.gen_biguint_range(&BigUint::one(), components.q());
-    let y = components::public(&components, &x);
+pub fn keypair<R: CryptoRng + ?Sized>(rng: &mut R, components: Components) -> SigningKey {
+    #[inline]
+    fn find_non_zero_x<R: CryptoRng + ?Sized>(
+        rng: &mut R,
+        components: &Components,
+    ) -> NonZero<BoxedUint> {
+        loop {
+            let x = BoxedUint::random_mod(rng, components.q());
+            if let Some(x) = NonZero::new(x).into() {
+                return x;
+            }
+        }
+    }
+
+    #[inline]
+    fn find_components<R: CryptoRng + ?Sized>(
+        rng: &mut R,
+        components: &Components,
+    ) -> (NonZero<BoxedUint>, NonZero<BoxedUint>) {
+        loop {
+            let x = find_non_zero_x(rng, components);
+
+            if let Some(y) = components::public(components, &x).into_option() {
+                return (x, y);
+            }
+        }
+    }
+
+    let (x, y) = find_components(rng, &components);
 
     VerifyingKey::from_components(components, y)
         .and_then(|verifying_key| SigningKey::from_components(verifying_key, x))
diff --git a/dsa/src/generate/secret_number.rs b/dsa/src/generate/secret_number.rs
index 1d315f7a..b852a212 100644
--- a/dsa/src/generate/secret_number.rs
+++ b/dsa/src/generate/secret_number.rs
@@ -2,32 +2,16 @@
 //! Generate a per-message secret number
 //!
 
-use crate::{signing_key::SigningKey, Components};
-use alloc::{vec, vec::Vec};
+use crate::{Components, signing_key::SigningKey};
+use alloc::vec;
 use core::cmp::min;
-use digest::{core_api::BlockSizeUser, Digest, FixedOutputReset};
-use num_bigint::{BigUint, ModInverse, RandBigInt};
-use num_traits::{One, Zero};
-use rfc6979::HmacDrbg;
-use signature::rand_core::CryptoRngCore;
-use zeroize::Zeroize;
+use crypto_bigint::{BoxedUint, NonZero, RandomBits};
+use digest::{Digest, FixedOutputReset, core_api::BlockSizeUser};
+use signature::rand_core::TryCryptoRng;
+use zeroize::Zeroizing;
 
-/// Reduce the hash into an RFC-6979 appropriate form
-fn reduce_hash(q: &BigUint, hash: &[u8]) -> Vec<u8> {
-    // Reduce the hash modulo Q
-    let q_byte_len = q.bits() / 8;
-
-    let hash_len = min(hash.len(), q_byte_len);
-    let hash = &hash[..hash_len];
-
-    let hash = BigUint::from_bytes_be(hash);
-    let mut reduced = (hash % q).to_bytes_be();
-
-    while reduced.len() < q_byte_len {
-        reduced.insert(0, 0);
-    }
-
-    reduced
+fn truncate_hash(hash: &[u8], desired_size: usize) -> &[u8] {
+    &hash[(hash.len() - desired_size)..]
 }
 
 /// Generate a per-message secret number k deterministically using the method described in RFC 6979
@@ -36,28 +20,37 @@ fn reduce_hash(q: &BigUint, hash: &[u8]) -> Vec<u8> {
 ///
 /// Secret number k and its modular multiplicative inverse with q
 #[inline]
-pub fn secret_number_rfc6979<D>(signing_key: &SigningKey, hash: &[u8]) -> (BigUint, BigUint)
+pub fn secret_number_rfc6979<D>(
+    signing_key: &SigningKey,
+    hash: &[u8],
+) -> Result<(BoxedUint, BoxedUint), signature::Error>
 where
     D: Digest + BlockSizeUser + FixedOutputReset,
 {
     let q = signing_key.verifying_key().components().q();
-    let k_size = q.bits() / 8;
-    let hash = reduce_hash(q, hash);
+    let size = (q.bits() / 8) as usize;
+    let hash = BoxedUint::from_be_slice(&hash[..min(size, hash.len())], q.bits_precision())
+        .map_err(|_| signature::Error::new())?;
 
-    let mut x_bytes = signing_key.x().to_bytes_be();
-    let mut hmac = HmacDrbg::<D>::new(&x_bytes, &hash, &[]);
-    x_bytes.zeroize();
+    // Reduce hash mod q
+    let hash = (hash % q).to_be_bytes();
+    let hash = truncate_hash(&hash, size);
 
-    let mut buffer = vec![0; k_size];
-    loop {
-        hmac.fill_bytes(&mut buffer);
+    let q_bytes = q.to_be_bytes();
+    let q_bytes = truncate_hash(&q_bytes, size);
+
+    let x_bytes = Zeroizing::new(signing_key.x().to_be_bytes());
+    let x_bytes = truncate_hash(&x_bytes, size);
 
-        let k = BigUint::from_bytes_be(&buffer);
-        if let Some(inv_k) = (&k).mod_inverse(q) {
-            let inv_k = inv_k.to_biguint().unwrap();
+    let mut buffer = vec![0; size];
+    loop {
+        rfc6979::generate_k_mut::<D>(x_bytes, q_bytes, hash, &[], &mut buffer);
 
-            if k > BigUint::zero() && &k < q {
-                return (k, inv_k);
+        let k = BoxedUint::from_be_slice(&buffer, q.bits_precision())
+            .map_err(|_| signature::Error::new())?;
+        if let Some(inv_k) = k.inv_mod(q).into() {
+            if (bool::from(k.is_nonzero())) && (k < **q) {
+                return Ok((k, inv_k));
             }
         }
     }
@@ -69,28 +62,30 @@ where
 ///
 /// Secret number k and its modular multiplicative inverse with q
 #[inline]
-pub fn secret_number(
-    rng: &mut impl CryptoRngCore,
+pub fn secret_number<R: TryCryptoRng + ?Sized>(
+    rng: &mut R,
     components: &Components,
-) -> Option<(BigUint, BigUint)> {
+) -> Result<Option<(BoxedUint, BoxedUint)>, signature::Error> {
     let q = components.q();
     let n = q.bits();
+    let q = q.widen(n + 64);
+    let q = &q;
 
     // Attempt to try a fitting secret number
     // Give up after 4096 tries
     for _ in 0..4096 {
-        let c = rng.gen_biguint(n + 64);
-        let k = (c % (q - BigUint::one())) + BigUint::one();
-
-        if let Some(inv_k) = (&k).mod_inverse(q) {
-            let inv_k = inv_k.to_biguint().unwrap();
+        let c = BoxedUint::try_random_bits(rng, n + 64).map_err(|_| signature::Error::new())?;
+        let rem = NonZero::new((&**q - &BoxedUint::one()).widen(c.bits_precision()))
+            .expect("[bug] minimum size for q is to 2^(160 - 1)");
+        let k = (c % rem) + BoxedUint::one();
 
+        if let Some(inv_k) = k.inv_mod(q).into() {
             // `k` and `k^-1` both have to be in the range `[1, q-1]`
-            if (inv_k > BigUint::zero() && &inv_k < q) && (k > BigUint::zero() && &k < q) {
-                return Some((k, inv_k));
+            if (inv_k > BoxedUint::zero() && inv_k < **q) && (k > BoxedUint::zero() && k < **q) {
+                return Ok(Some((k, inv_k)));
             }
         }
     }
 
-    None
+    Ok(None)
 }
diff --git a/dsa/src/lib.rs b/dsa/src/lib.rs
index 024c2ae7..abc095dd 100644
--- a/dsa/src/lib.rs
+++ b/dsa/src/lib.rs
@@ -26,11 +26,15 @@
 #![cfg_attr(feature = "hazmat", doc = "```")]
 #![cfg_attr(not(feature = "hazmat"), doc = "```ignore")]
 //! # use dsa::{Components, SigningKey, VerifyingKey};
-//! # use num_bigint::BigUint;
-//! # use num_traits::One;
-//! # let read_common_parameters = || (BigUint::one(), BigUint::one(), BigUint::one());
-//! # let read_public_component = || BigUint::one();
-//! # let read_private_component = || BigUint::one();
+//! # use crypto_bigint::{BoxedUint, NonZero, Odd};
+//! # let read_common_parameters = ||
+//! #     (
+//! #          Odd::new(BoxedUint::one()).unwrap(),
+//! #          NonZero::new(BoxedUint::one()).unwrap(),
+//! #          NonZero::new(BoxedUint::one()).unwrap(),
+//! #     );
+//! # let read_public_component = || NonZero::new(BoxedUint::one()).unwrap();
+//! # let read_private_component = || NonZero::new(BoxedUint::one()).unwrap();
 //! # || -> signature::Result<()> {
 //! let (p, q, g) = read_common_parameters();
 //! let components = Components::from_components(p, q, g)?;
@@ -53,7 +57,7 @@ pub use crate::signing_key::SigningKey;
 
 pub use crate::{components::Components, size::KeySize, verifying_key::VerifyingKey};
 
-pub use num_bigint::BigUint;
+pub use crypto_bigint::{BoxedUint, NonZero, Odd};
 pub use pkcs8;
 pub use signature;
 
@@ -71,10 +75,9 @@ mod verifying_key;
 pub const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10040.4.1");
 
 use alloc::{boxed::Box, vec::Vec};
-use num_traits::Zero;
 use pkcs8::der::{
-    self, asn1::UintRef, Decode, DecodeValue, Encode, EncodeValue, Header, Length, Reader,
-    Sequence, Writer,
+    self, Decode, DecodeValue, Encode, EncodeValue, FixedTag, Header, Length, Reader, Sequence,
+    Writer, asn1::UintRef,
 };
 use signature::SignatureEncoding;
 
@@ -83,31 +86,27 @@ use signature::SignatureEncoding;
 #[must_use]
 pub struct Signature {
     /// Signature part r
-    r: BigUint,
+    r: NonZero<BoxedUint>,
 
     /// Signature part s
-    s: BigUint,
+    s: NonZero<BoxedUint>,
 }
 
 impl Signature {
     /// Create a new Signature container from its components
-    pub fn from_components(r: BigUint, s: BigUint) -> signature::Result<Self> {
-        if r.is_zero() || s.is_zero() {
-            return Err(signature::Error::new());
-        }
-
-        Ok(Self { r, s })
+    pub fn from_components(r: NonZero<BoxedUint>, s: NonZero<BoxedUint>) -> Self {
+        Self { r, s }
     }
 
     /// Signature part r
     #[must_use]
-    pub fn r(&self) -> &BigUint {
+    pub fn r(&self) -> &NonZero<BoxedUint> {
         &self.r
     }
 
     /// Signature part s
     #[must_use]
-    pub fn s(&self) -> &BigUint {
+    pub fn s(&self) -> &NonZero<BoxedUint> {
         &self.s
     }
 }
@@ -120,23 +119,32 @@ impl<'a> DecodeValue<'a> for Signature {
             let r = UintRef::decode(reader)?;
             let s = UintRef::decode(reader)?;
 
-            let r = BigUint::from_bytes_be(r.as_bytes());
-            let s = BigUint::from_bytes_be(s.as_bytes());
+            let r = BoxedUint::from_be_slice(r.as_bytes(), r.as_bytes().len() as u32 * 8)
+                .map_err(|_| UintRef::TAG.value_error())?;
+            let s = BoxedUint::from_be_slice(s.as_bytes(), s.as_bytes().len() as u32 * 8)
+                .map_err(|_| UintRef::TAG.value_error())?;
+
+            let r = NonZero::new(r)
+                .into_option()
+                .ok_or(UintRef::TAG.value_error())?;
+            let s = NonZero::new(s)
+                .into_option()
+                .ok_or(UintRef::TAG.value_error())?;
 
-            Self::from_components(r, s).map_err(|_| der::Tag::Integer.value_error())
+            Ok(Self::from_components(r, s))
         })
     }
 }
 
 impl EncodeValue for Signature {
     fn value_len(&self) -> der::Result<Length> {
-        UintRef::new(&self.r.to_bytes_be())?.encoded_len()?
-            + UintRef::new(&self.s.to_bytes_be())?.encoded_len()?
+        UintRef::new(&self.r.to_be_bytes())?.encoded_len()?
+            + UintRef::new(&self.s.to_be_bytes())?.encoded_len()?
     }
 
     fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> {
-        UintRef::new(&self.r.to_bytes_be())?.encode(writer)?;
-        UintRef::new(&self.s.to_bytes_be())?.encode(writer)?;
+        UintRef::new(&self.r.to_be_bytes())?.encode(writer)?;
+        UintRef::new(&self.s.to_be_bytes())?.encode(writer)?;
         Ok(())
     }
 }
@@ -159,7 +167,7 @@ impl PartialOrd for Signature {
     }
 }
 
-impl<'a> Sequence<'a> for Signature {}
+impl Sequence<'_> for Signature {}
 
 impl SignatureEncoding for Signature {
     type Repr = Box<[u8]>;
@@ -182,8 +190,8 @@ impl TryFrom<&[u8]> for Signature {
     }
 }
 
-/// Returns a `BigUint` with the value 2
+/// Returns a `BoxedUint` with the value 2
 #[inline]
-fn two() -> BigUint {
-    BigUint::from(2_u8)
+fn two() -> BoxedUint {
+    BoxedUint::from(2_u8)
 }
diff --git a/dsa/src/signing_key.rs b/dsa/src/signing_key.rs
index 1204806e..76615971 100644
--- a/dsa/src/signing_key.rs
+++ b/dsa/src/signing_key.rs
@@ -2,25 +2,29 @@
 //! Module containing the definition of the private key container
 //!
 
-use crate::{Components, Signature, VerifyingKey, OID};
+use crate::{Components, OID, Signature, VerifyingKey};
 use core::{
     cmp::min,
     fmt::{self, Debug},
 };
-use digest::{core_api::BlockSizeUser, Digest, FixedOutputReset};
-use num_bigint::BigUint;
-use num_traits::Zero;
+use crypto_bigint::{
+    BoxedUint, NonZero,
+    modular::{BoxedMontyForm, BoxedMontyParams},
+};
+use digest::{Digest, FixedOutputReset, core_api::BlockSizeUser};
 use pkcs8::{
+    AlgorithmIdentifierRef, EncodePrivateKey, PrivateKeyInfoRef, SecretDocument,
     der::{
-        asn1::{OctetStringRef, UintRef},
         AnyRef, Decode, Encode,
+        asn1::{OctetStringRef, UintRef},
     },
-    AlgorithmIdentifierRef, EncodePrivateKey, PrivateKeyInfoRef, SecretDocument,
 };
+#[cfg(feature = "hazmat")]
+use signature::rand_core::CryptoRng;
 use signature::{
-    hazmat::{PrehashSigner, RandomizedPrehashSigner},
-    rand_core::CryptoRngCore,
     DigestSigner, RandomizedDigestSigner, Signer,
+    hazmat::{PrehashSigner, RandomizedPrehashSigner},
+    rand_core::TryCryptoRng,
 };
 use zeroize::{Zeroize, Zeroizing};
 
@@ -35,13 +39,16 @@ pub struct SigningKey {
     verifying_key: VerifyingKey,
 
     /// Private component x
-    x: Zeroizing<BigUint>,
+    x: Zeroizing<NonZero<BoxedUint>>,
 }
 
 impl SigningKey {
     /// Construct a new private key from the public key and private component
-    pub fn from_components(verifying_key: VerifyingKey, x: BigUint) -> signature::Result<Self> {
-        if x.is_zero() || x > *verifying_key.components().q() {
+    pub fn from_components(
+        verifying_key: VerifyingKey,
+        x: NonZero<BoxedUint>,
+    ) -> signature::Result<Self> {
+        if x > *verifying_key.components().q() {
             return Err(signature::Error::new());
         }
 
@@ -54,7 +61,7 @@ impl SigningKey {
     #[cfg(feature = "hazmat")]
     /// Generate a new DSA keypair
     #[inline]
-    pub fn generate(rng: &mut impl CryptoRngCore, components: Components) -> SigningKey {
+    pub fn generate<R: CryptoRng + ?Sized>(rng: &mut R, components: Components) -> SigningKey {
         crate::generate::keypair(rng, components)
     }
 
@@ -67,7 +74,7 @@ impl SigningKey {
     ///
     /// If you decide to clone this value, please consider using [`Zeroize::zeroize`](::zeroize::Zeroize::zeroize()) to zero out the memory after you're done using the clone
     #[must_use]
-    pub fn x(&self) -> &BigUint {
+    pub fn x(&self) -> &NonZero<BoxedUint> {
         &self.x
     }
 
@@ -80,31 +87,55 @@ impl SigningKey {
     where
         D: Digest + BlockSizeUser + FixedOutputReset,
     {
-        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash);
+        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash)?;
         self.sign_prehashed(k_kinv, prehash)
     }
 
     /// Sign some pre-hashed data
     fn sign_prehashed(
         &self,
-        (k, inv_k): (BigUint, BigUint),
+        (k, inv_k): (BoxedUint, BoxedUint),
         hash: &[u8],
     ) -> signature::Result<Signature> {
         let components = self.verifying_key().components();
+        let key_size = &components.key_size;
         let (p, q, g) = (components.p(), components.q(), components.g());
         let x = self.x();
 
-        let r = g.modpow(&k, p) % q;
+        debug_assert_eq!(key_size.n_aligned(), q.bits_precision());
+
+        let x = x.widen(p.bits_precision());
+        let x = &x;
+
+        let k = k.widen(p.bits_precision());
+        let inv_k = inv_k.widen(p.bits_precision());
+
+        let params = BoxedMontyParams::new(p.clone());
+        let form = BoxedMontyForm::new((**g).clone(), params);
+        let r = form.pow(&k).retrieve() % q.widen(p.bits_precision());
+        debug_assert_eq!(key_size.l_aligned(), r.bits_precision());
+
+        let r_short = r.shorten(key_size.n_aligned());
+        let r_short = NonZero::new(r_short)
+            .expect("[bug] invalid value of k used here, the secret number computed was invalid");
+        let r = NonZero::new(r)
+            .expect("[bug] invalid value of k used here, the secret number computed was invalid");
 
         let n = q.bits() / 8;
         let block_size = hash.len(); // Hash function output size
 
-        let z_len = min(n, block_size);
-        let z = BigUint::from_bytes_be(&hash[..z_len]);
+        let z_len = min(n as usize, block_size);
+        let z = BoxedUint::from_be_slice(&hash[..z_len], z_len as u32 * 8)
+            .expect("invariant violation");
 
-        let s = (inv_k * (z + x * &r)) % q;
+        let s = inv_k.mul_mod(&(z + &**x * &*r), &q.widen(key_size.l_aligned()));
+        let s = s.shorten(key_size.n_aligned());
+        let s = NonZero::new(s)
+            .expect("[bug] invalid value of k used here, the secret number computed was invalid");
 
-        let signature = Signature::from_components(r, s)?;
+        debug_assert_eq!(key_size.n_aligned(), r_short.bits_precision());
+        debug_assert_eq!(key_size.n_aligned(), s.bits_precision());
+        let signature = Signature::from_components(r_short, s);
 
         if signature.r() < q && signature.s() < q {
             Ok(signature)
@@ -124,20 +155,20 @@ impl Signer<Signature> for SigningKey {
 impl PrehashSigner<Signature> for SigningKey {
     /// Warning: This uses `sha2::Sha256` as the hash function for the digest. If you need to use a different one, use [`SigningKey::sign_prehashed_rfc6979`].
     fn sign_prehash(&self, prehash: &[u8]) -> Result<Signature, signature::Error> {
-        let k_kinv = crate::generate::secret_number_rfc6979::<sha2::Sha256>(self, prehash);
+        let k_kinv = crate::generate::secret_number_rfc6979::<sha2::Sha256>(self, prehash)?;
         self.sign_prehashed(k_kinv, prehash)
     }
 }
 
 impl RandomizedPrehashSigner<Signature> for SigningKey {
-    fn sign_prehash_with_rng(
+    fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>(
         &self,
-        mut rng: &mut impl CryptoRngCore,
+        rng: &mut R,
         prehash: &[u8],
     ) -> Result<Signature, signature::Error> {
         let components = self.verifying_key.components();
 
-        if let Some(k_kinv) = crate::generate::secret_number(&mut rng, components) {
+        if let Some(k_kinv) = crate::generate::secret_number(rng, components)? {
             self.sign_prehashed(k_kinv, prehash)
         } else {
             Err(signature::Error::new())
@@ -151,7 +182,7 @@ where
 {
     fn try_sign_digest(&self, digest: D) -> Result<Signature, signature::Error> {
         let hash = digest.finalize_fixed();
-        let ks = crate::generate::secret_number_rfc6979::<D>(self, &hash);
+        let ks = crate::generate::secret_number_rfc6979::<D>(self, &hash)?;
 
         self.sign_prehashed(ks, &hash)
     }
@@ -161,12 +192,12 @@ impl<D> RandomizedDigestSigner<D, Signature> for SigningKey
 where
     D: Digest,
 {
-    fn try_sign_digest_with_rng(
+    fn try_sign_digest_with_rng<R: TryCryptoRng + ?Sized>(
         &self,
-        mut rng: &mut impl CryptoRngCore,
+        rng: &mut R,
         digest: D,
     ) -> Result<Signature, signature::Error> {
-        let ks = crate::generate::secret_number(&mut rng, self.verifying_key().components())
+        let ks = crate::generate::secret_number(rng, self.verifying_key().components())?
             .ok_or_else(signature::Error::new)?;
         let hash = digest.finalize();
 
@@ -183,7 +214,7 @@ impl EncodePrivateKey for SigningKey {
             parameters: Some(parameters),
         };
 
-        let mut x_bytes = self.x().to_bytes_be();
+        let mut x_bytes = self.x().to_be_bytes();
         let x = UintRef::new(&x_bytes)?;
         let mut signing_key = x.to_der()?;
 
@@ -207,14 +238,28 @@ impl<'a> TryFrom<PrivateKeyInfoRef<'a>> for SigningKey {
         let parameters = value.algorithm.parameters_any()?;
         let components = parameters.decode_as::<Components>()?;
 
-        let x = UintRef::from_der(value.private_key.as_bytes())?;
-        let x = BigUint::from_bytes_be(x.as_bytes());
+        // Use the precision of `p`. `p` will always have the largest precision.
+        // Every operation is mod `p` anyway, so it will always fit.
+        let precision = components.p().bits_precision();
+
+        let x = UintRef::from_der(value.private_key.into())?;
+        let x = BoxedUint::from_be_slice(x.as_bytes(), precision)
+            .map_err(|_| pkcs8::Error::KeyMalformed)?;
+        let x = NonZero::new(x)
+            .into_option()
+            .ok_or(pkcs8::Error::KeyMalformed)?;
 
         let y = if let Some(y_bytes) = value.public_key.as_ref().and_then(|bs| bs.as_bytes()) {
             let y = UintRef::from_der(y_bytes)?;
-            BigUint::from_bytes_be(y.as_bytes())
+            let y = BoxedUint::from_be_slice(y.as_bytes(), precision)
+                .map_err(|_| pkcs8::Error::KeyMalformed)?;
+            NonZero::new(y)
+                .into_option()
+                .ok_or(pkcs8::Error::KeyMalformed)?
         } else {
             crate::generate::public_component(&components, &x)
+                .into_option()
+                .ok_or(pkcs8::Error::KeyMalformed)?
         };
 
         let verifying_key =
diff --git a/dsa/src/size.rs b/dsa/src/size.rs
index 4c26e27b..a2685702 100644
--- a/dsa/src/size.rs
+++ b/dsa/src/size.rs
@@ -1,4 +1,7 @@
+use crypto_bigint::Limb;
+
 /// DSA key size
+#[derive(Clone, Debug, Copy, PartialEq, PartialOrd)]
 pub struct KeySize {
     /// Bit size of p
     pub(crate) l: u32,
@@ -23,3 +26,17 @@ impl KeySize {
     /// DSA parameter size constant: L = 3072, N = 256
     pub const DSA_3072_256: Self = Self { l: 3072, n: 256 };
 }
+
+impl KeySize {
+    pub(crate) fn l_aligned(&self) -> u32 {
+        self.l.div_ceil(Limb::BITS) * Limb::BITS
+    }
+
+    pub(crate) fn n_aligned(&self) -> u32 {
+        self.n.div_ceil(Limb::BITS) * Limb::BITS
+    }
+
+    pub(crate) fn matches(&self, l: u32, n: u32) -> bool {
+        l == self.l_aligned() && n == self.n_aligned()
+    }
+}
diff --git a/dsa/src/verifying_key.rs b/dsa/src/verifying_key.rs
index e44bebf4..d8b6d3b7 100644
--- a/dsa/src/verifying_key.rs
+++ b/dsa/src/verifying_key.rs
@@ -2,19 +2,22 @@
 //! Module containing the definition of the public key container
 //!
 
-use crate::{two, Components, Signature, OID};
+use crate::{Components, OID, Signature, two};
 use core::cmp::min;
+use crypto_bigint::{
+    BoxedUint, NonZero,
+    modular::{BoxedMontyForm, BoxedMontyParams},
+};
 use digest::Digest;
-use num_bigint::{BigUint, ModInverse};
-use num_traits::One;
 use pkcs8::{
+    AlgorithmIdentifierRef, EncodePublicKey, SubjectPublicKeyInfoRef,
     der::{
-        asn1::{BitStringRef, UintRef},
         AnyRef, Decode, Encode,
+        asn1::{BitStringRef, UintRef},
     },
-    spki, AlgorithmIdentifierRef, EncodePublicKey, SubjectPublicKeyInfoRef,
+    spki,
 };
-use signature::{hazmat::PrehashVerifier, DigestVerifier, Verifier};
+use signature::{DigestVerifier, Verifier, hazmat::PrehashVerifier};
 
 /// DSA public key.
 #[derive(Clone, Debug, PartialEq, PartialOrd)]
@@ -24,13 +27,19 @@ pub struct VerifyingKey {
     components: Components,
 
     /// Public component y
-    y: BigUint,
+    y: NonZero<BoxedUint>,
 }
 
 impl VerifyingKey {
     /// Construct a new public key from the common components and the public component
-    pub fn from_components(components: Components, y: BigUint) -> signature::Result<Self> {
-        if y < two() || y.modpow(components.q(), components.p()) != BigUint::one() {
+    pub fn from_components(
+        components: Components,
+        y: NonZero<BoxedUint>,
+    ) -> signature::Result<Self> {
+        let params = BoxedMontyParams::new_vartime(components.p().clone());
+        let form = BoxedMontyForm::new((*y).clone(), params);
+
+        if *y < two() || form.pow(components.q()).retrieve() != BoxedUint::one() {
             return Err(signature::Error::new());
         }
 
@@ -44,7 +53,7 @@ impl VerifyingKey {
 
     /// DSA public component
     #[must_use]
-    pub const fn y(&self) -> &BigUint {
+    pub const fn y(&self) -> &NonZero<BoxedUint> {
         &self.y
     }
 
@@ -56,23 +65,44 @@ impl VerifyingKey {
         let (r, s) = (signature.r(), signature.s());
         let y = self.y();
 
-        if signature.r() >= q || signature.s() >= q {
+        if r >= q || s >= q {
             return Some(false);
         }
 
-        let w = s.mod_inverse(q)?.to_biguint().unwrap();
+        let q = &q.widen(p.bits_precision());
+        let r = &r.widen(p.bits_precision());
+        let s = &s.widen(p.bits_precision());
+
+        let w: BoxedUint = Option::from(s.inv_mod(q))?;
 
         let n = q.bits() / 8;
         let block_size = hash.len(); // Hash function output size
 
-        let z_len = min(n, block_size);
-        let z = BigUint::from_bytes_be(&hash[..z_len]);
+        let z_len = min(n as usize, block_size);
+        let z = BoxedUint::from_be_slice(&hash[..z_len], z_len as u32 * 8)
+            .expect("invariant violation");
+
+        let z = z.widen(p.bits_precision());
+        let w = w.widen(q.bits_precision());
 
-        let u1 = (&z * &w) % q;
-        let u2 = (r * &w) % q;
-        let v = (g.modpow(&u1, p) * y.modpow(&u2, p) % p) % q;
+        let u1 = (&z * &w) % q.widen(p.bits_precision());
+        let u2 = r.mul_mod(&w, q);
 
-        Some(v == *r)
+        let p1_params = BoxedMontyParams::new(p.clone());
+        let p2_params = BoxedMontyParams::new(p.clone());
+
+        let g_form = BoxedMontyForm::new((**g).clone(), p1_params);
+        let y_form = BoxedMontyForm::new((**y).clone(), p2_params);
+
+        let v1 = g_form.pow(&u1).retrieve();
+        let v2 = y_form.pow(&u2).retrieve();
+        let v3 = v1 * v2;
+        let p = p.widen(v3.bits_precision());
+        let q = q.widen(v3.bits_precision());
+        let v4 = v3 % NonZero::new(p).expect("[bug] p is an odd number and can't be zero");
+        let v = v4 % q;
+
+        Some(v == **r)
     }
 }
 
@@ -124,7 +154,7 @@ impl EncodePublicKey for VerifyingKey {
             parameters: Some(parameters),
         };
 
-        let y_bytes = self.y.to_bytes_be();
+        let y_bytes = self.y.to_be_bytes();
         let y = UintRef::new(&y_bytes)?;
         let public_key = y.to_der()?;
 
@@ -151,7 +181,13 @@ impl<'a> TryFrom<SubjectPublicKeyInfoRef<'a>> for VerifyingKey {
                 .ok_or(spki::Error::KeyMalformed)?,
         )?;
 
-        Self::from_components(components, BigUint::from_bytes_be(y.as_bytes()))
-            .map_err(|_| spki::Error::KeyMalformed)
+        let y = NonZero::new(
+            BoxedUint::from_be_slice(y.as_bytes(), y.as_bytes().len() as u32 * 8)
+                .expect("invariant violation"),
+        )
+        .into_option()
+        .ok_or(spki::Error::KeyMalformed)?;
+
+        Self::from_components(components, y).map_err(|_| spki::Error::KeyMalformed)
     }
 }
diff --git a/dsa/tests/components.rs b/dsa/tests/components.rs
index 22888346..6c25225f 100644
--- a/dsa/tests/components.rs
+++ b/dsa/tests/components.rs
@@ -1,7 +1,7 @@
 use dsa::Components;
 use pkcs8::{
-    der::{Decode, Encode},
     Document,
+    der::{Decode, Encode},
 };
 
 const OPENSSL_PEM_COMPONENTS: &str = include_str!("pems/params.pem");
diff --git a/dsa/tests/deterministic.rs b/dsa/tests/deterministic.rs
index cc426daf..59e6983d 100644
--- a/dsa/tests/deterministic.rs
+++ b/dsa/tests/deterministic.rs
@@ -1,9 +1,7 @@
 #![cfg(feature = "hazmat")]
-
-use digest::{core_api::BlockSizeUser, Digest, FixedOutputReset};
+use crypto_bigint::{BoxedUint, NonZero, Odd};
+use digest::{Digest, FixedOutputReset, core_api::BlockSizeUser};
 use dsa::{Components, Signature, SigningKey, VerifyingKey};
-use num_bigint::BigUint;
-use num_traits::Num;
 use sha1::Sha1;
 use sha2::{Sha224, Sha256, Sha384, Sha512};
 use signature::DigestSigner;
@@ -11,34 +9,43 @@ use signature::DigestSigner;
 const MESSAGE: &[u8] = b"sample";
 const MESSAGE_2: &[u8] = b"test";
 
+fn decode_hex_number(txt: &str, precision: u32) -> BoxedUint {
+    let val = hex::decode(txt).unwrap();
+    BoxedUint::from_be_slice(&val, precision).unwrap()
+}
+
 fn dsa_1024_signing_key() -> SigningKey {
-    let p = BigUint::from_str_radix(
-        "86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447\
+    let p_str = "86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447\
                 E6533B86B18BED6E8A48B784A14C252C5BE0DBF60B86D6385BD2F12FB763ED88\
                 73ABFD3F5BA2E0A8C0A59082EAC056935E529DAF7C610467899C77ADEDFC846C\
-                881870B7B19B2B58F9BE0521A17002E3BDD6B86685EE90B3D9A1B02B782B1779",
-        16,
-    )
-    .unwrap();
-    let q = BigUint::from_str_radix("996F967F6C8E388D9E28D01E205FBA957A5698B1", 16).unwrap();
-    let g = BigUint::from_str_radix(
-        "07B0F92546150B62514BB771E2A0C0CE387F03BDA6C56B505209FF25FD3C133D\
+                881870B7B19B2B58F9BE0521A17002E3BDD6B86685EE90B3D9A1B02B782B1779";
+    let p = decode_hex_number(p_str, 1024);
+
+    let q_str = "996F967F6C8E388D9E28D01E205FBA957A5698B1";
+    let q = decode_hex_number(q_str, 160);
+
+    let g_str = "07B0F92546150B62514BB771E2A0C0CE387F03BDA6C56B505209FF25FD3C133D\
             89BBCD97E904E09114D9A7DEFDEADFC9078EA544D2E401AEECC40BB9FBBF78FD\
             87995A10A1C27CB7789B594BA7EFB5C4326A9FE59A070E136DB77175464ADCA4\
-            17BE5DCE2F40D10A46A3A3943F26AB7FD9C0398FF8C76EE0A56826A8A88F1DBD",
-        16,
-    )
-    .unwrap();
+            17BE5DCE2F40D10A46A3A3943F26AB7FD9C0398FF8C76EE0A56826A8A88F1DBD";
+    let g = decode_hex_number(g_str, 1024);
 
-    let x = BigUint::from_str_radix("411602CB19A6CCC34494D79D98EF1E7ED5AF25F7", 16).unwrap();
-    let y = BigUint::from_str_radix(
-        "5DF5E01DED31D0297E274E1691C192FE5868FEF9E19A84776454B100CF16F653\
+    let x_str = "411602CB19A6CCC34494D79D98EF1E7ED5AF25F7";
+    let x = decode_hex_number(x_str, 160);
+
+    let y_str = "5DF5E01DED31D0297E274E1691C192FE5868FEF9E19A84776454B100CF16F653\
             92195A38B90523E2542EE61871C0440CB87C322FC4B4D2EC5E1E7EC766E1BE8D\
             4CE935437DC11C3C8FD426338933EBFE739CB3465F4D3668C5E473508253B1E6\
-            82F65CBDC4FAE93C2EA212390E54905A86E2223170B44EAA7DA5DD9FFCFB7F3B",
-        16,
-    )
-    .unwrap();
+            82F65CBDC4FAE93C2EA212390E54905A86E2223170B44EAA7DA5DD9FFCFB7F3B";
+    let y = decode_hex_number(y_str, 1024);
+
+    let (p, q, g, y, x) = (
+        Odd::new(p).unwrap(),
+        NonZero::new(q).unwrap(),
+        NonZero::new(g).unwrap(),
+        NonZero::new(y).unwrap(),
+        NonZero::new(x).unwrap(),
+    );
 
     let components = Components::from_components(p, q, g).expect("Invalid components");
     let verifying_key =
@@ -48,7 +55,7 @@ fn dsa_1024_signing_key() -> SigningKey {
 }
 
 fn dsa_2048_signing_key() -> SigningKey {
-    let p = BigUint::from_str_radix(
+    let p = decode_hex_number(
         "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48\
         C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F\
         FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5\
@@ -57,15 +64,13 @@ fn dsa_2048_signing_key() -> SigningKey {
         F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE\
         92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15\
         3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
-        16,
-    )
-    .unwrap();
-    let q = BigUint::from_str_radix(
+        2048,
+    );
+    let q = decode_hex_number(
         "F2C3119374CE76C9356990B465374A17F23F9ED35089BD969F61C6DDE9998C1F",
-        16,
-    )
-    .unwrap();
-    let g = BigUint::from_str_radix(
+        256,
+    );
+    let g = decode_hex_number(
         "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613\
         D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4\
         6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472\
@@ -74,16 +79,14 @@ fn dsa_2048_signing_key() -> SigningKey {
         3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71\
         BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0\
         DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
-        16,
-    )
-    .unwrap();
+        2048,
+    );
 
-    let x = BigUint::from_str_radix(
+    let x = decode_hex_number(
         "69C7548C21D0DFEA6B9A51C9EAD4E27C33D3B3F180316E5BCAB92C933F0E4DBC",
-        16,
-    )
-    .unwrap();
-    let y = BigUint::from_str_radix(
+        2048,
+    );
+    let y = decode_hex_number(
         "667098C654426C78D7F8201EAC6C203EF030D43605032C2F1FA937E5237DBD94\
         9F34A0A2564FE126DC8B715C5141802CE0979C8246463C40E6B6BDAA2513FA61\
         1728716C2E4FD53BC95B89E69949D96512E873B9C8F8DFD499CC312882561ADE\
@@ -92,9 +95,16 @@ fn dsa_2048_signing_key() -> SigningKey {
         687972A2D382599C9BAC4E0ED7998193078913032558134976410B89D2C171D1\
         23AC35FD977219597AA7D15C1A9A428E59194F75C721EBCBCFAE44696A499AFA\
         74E04299F132026601638CB87AB79190D4A0986315DA8EEC6561C938996BEADF",
-        16,
-    )
-    .unwrap();
+        2048,
+    );
+
+    let (p, q, g, y, x) = (
+        Odd::new(p).unwrap(),
+        NonZero::new(q).unwrap(),
+        NonZero::new(g).unwrap(),
+        NonZero::new(y).unwrap(),
+        NonZero::new(x).unwrap(),
+    );
 
     let components = Components::from_components(p, q, g).expect("Invalid components");
     let verifying_key =
@@ -129,11 +139,14 @@ where
 
 /// Create a signature container from the two components in their textual hexadecimal form
 fn from_str_signature(r: &str, s: &str) -> Signature {
+    assert_eq!(r.len(), s.len());
+
+    let precision = (r.len() * 8) as u32;
+
     Signature::from_components(
-        BigUint::from_str_radix(r, 16).unwrap(),
-        BigUint::from_str_radix(s, 16).unwrap(),
+        NonZero::new(decode_hex_number(r, precision)).unwrap(),
+        NonZero::new(decode_hex_number(s, precision)).unwrap(),
     )
-    .unwrap()
 }
 
 /// Return the RFC 6979 test cases
diff --git a/dsa/tests/proptest.rs b/dsa/tests/proptest.rs
new file mode 100644
index 00000000..b6069644
--- /dev/null
+++ b/dsa/tests/proptest.rs
@@ -0,0 +1,47 @@
+#![cfg(feature = "hazmat")]
+//! Property-based tests.
+
+use der::{Decode, Encode, Sequence, asn1::Uint};
+use dsa::{Components, KeySize, Signature, SigningKey, VerifyingKey, signature::Verifier};
+use pkcs8::DecodePublicKey;
+use proptest::prelude::*;
+use rand::SeedableRng;
+use rand_chacha::ChaCha8Rng;
+
+#[derive(Sequence)]
+struct MockSignature {
+    r: Uint,
+    s: Uint,
+}
+
+const OPENSSL_PEM_PUBLIC_KEY: &str = include_str!("pems/public.pem");
+
+prop_compose! {
+    fn private_key()(seed in any::<[u8; 32]>()) -> SigningKey {
+        let mut rng = ChaCha8Rng::from_seed(seed);
+        #[allow(deprecated)]
+        let components = Components::generate(&mut rng, KeySize::DSA_1024_160);
+        SigningKey::generate(&mut rng, components)
+    }
+}
+
+proptest! {
+    #[test]
+    fn dsa_signature_verification( r in any::<Vec<u8>>(), s in any::<Vec<u8>>(),) {
+        let verifying_key = VerifyingKey::from_public_key_pem(OPENSSL_PEM_PUBLIC_KEY)
+            .expect("Failed to decode PEM encoded OpenSSL public key");
+
+        let asn1 = MockSignature {
+            r: Uint::new(&r).unwrap(),
+            s: Uint::new(&s).unwrap(),
+        }
+        .to_der()
+        .expect("Failed to serialize signature");
+
+        let Ok(signature) = Signature::from_der(&asn1) else {
+            return Ok(());
+        };
+
+        prop_assert!(verifying_key.verify(&[], &signature).is_err());
+    }
+}
diff --git a/dsa/tests/signature.rs b/dsa/tests/signature.rs
index 6bceb145..3fa326d6 100644
--- a/dsa/tests/signature.rs
+++ b/dsa/tests/signature.rs
@@ -3,13 +3,14 @@
 
 use digest::Digest;
 use dsa::{Components, KeySize, Signature, SigningKey};
+use hex_literal::hex;
 use pkcs8::der::{Decode, Encode};
 use rand::{CryptoRng, RngCore, SeedableRng};
 use rand_chacha::ChaCha8Rng;
 use sha2::Sha256;
 use signature::{
-    hazmat::{PrehashSigner, PrehashVerifier},
     DigestVerifier, RandomizedDigestSigner, Signer, Verifier,
+    hazmat::{PrehashSigner, PrehashVerifier},
 };
 
 /// Seed used for the ChaCha8 RNG
@@ -22,19 +23,19 @@ const MESSAGE: &[u8] = b"test";
 ///
 /// This signature was generated using the keys generated by this CSPRNG (the per-message `k` component was also generated using the CSPRNG)
 const MESSAGE_SIGNATURE_CRATE_ASN1: &[u8] = &[
-    0x30, 0x2C, 0x02, 0x14, 0x45, 0x1D, 0xE5, 0x76, 0x21, 0xD8, 0xFD, 0x76, 0xC1, 0x6F, 0x45, 0x4E,
-    0xDE, 0x5F, 0x09, 0x79, 0x76, 0x52, 0xF3, 0xA5, 0x02, 0x14, 0x53, 0x60, 0xE6, 0xB7, 0xF0, 0xCF,
-    0xAE, 0x49, 0xB1, 0x58, 0x5C, 0xCF, 0x5F, 0x3F, 0x94, 0x49, 0x21, 0xA0, 0xBF, 0xD2,
+    0x30, 0x2c, 0x2, 0x14, 0x4e, 0x12, 0x27, 0x75, 0x18, 0xf6, 0x40, 0xe3, 0x3a, 0xdb, 0x80, 0x6d,
+    0xe7, 0x98, 0xd3, 0xa3, 0x40, 0xf5, 0x9d, 0xf, 0x2, 0x14, 0x17, 0x78, 0x1e, 0xc8, 0x53, 0x58,
+    0x91, 0xe0, 0x3f, 0x2d, 0x36, 0x27, 0x36, 0x6b, 0xac, 0x8e, 0xd7, 0xf9, 0xa4, 0xcf,
 ];
 
 /// Message signed by OpenSSL using the keys generated by this CSPRNG
 ///
 /// This signature was generated using the SHA-256 digest
-const MESSAGE_SIGNATURE_OPENSSL_ASN1: &[u8] = &[
-    0x30, 0x2C, 0x02, 0x14, 0x6D, 0xB3, 0x8E, 0xAF, 0x97, 0x13, 0x7E, 0x07, 0xFF, 0x24, 0xB8, 0x66,
-    0x97, 0x18, 0xE1, 0x6F, 0xD7, 0x9A, 0x28, 0x2D, 0x02, 0x14, 0x47, 0x8C, 0x0B, 0x96, 0x51, 0x08,
-    0x08, 0xC8, 0x34, 0x9D, 0x0D, 0x41, 0xC7, 0x73, 0x0F, 0xB5, 0x9C, 0xBB, 0x00, 0x34,
-];
+const MESSAGE_SIGNATURE_OPENSSL_ASN1: &[u8] = &hex!(
+    "302c 0214 3a2b 2250 3e2d 21b3 22a1 c834
+     1668 2c0c 98df 2dee 0214 501d 1ffb bbba
+     9925 a1d1 7bb8 c835 ca27 0931 ca6a"
+);
 
 /// Get the seeded CSPRNG
 fn seeded_csprng() -> impl CryptoRng + RngCore {
@@ -87,9 +88,11 @@ fn verify_signature() {
     let signature = Signature::from_der(MESSAGE_SIGNATURE_OPENSSL_ASN1)
         .expect("Failed to parse ASN.1 representation of the test signature");
 
-    assert!(verifying_key
-        .verify_digest(Sha256::new().chain_update(MESSAGE), &signature)
-        .is_ok());
+    assert!(
+        verifying_key
+            .verify_digest(Sha256::new().chain_update(MESSAGE), &signature)
+            .is_ok()
+    );
 }
 
 #[test]
@@ -116,3 +119,46 @@ fn signer_verifier_signature() {
     verifying_key.verify(message, &manual_signature).unwrap();
     verifying_key.verify(message, &signer_signature).unwrap();
 }
+
+/// This test forces the r and s of the signature to a bit precision different to what would
+/// otherwise be expected
+#[test]
+fn verify_signature_precision() {
+    use der::{Sequence, asn1::Uint};
+
+    let signing_key = generate_deterministic_keypair();
+    let verifying_key = signing_key.verifying_key();
+
+    #[derive(Sequence)]
+    struct MockSignature {
+        r: Uint,
+        s: Uint,
+    }
+
+    for value in &[
+        {
+            let mut value = vec![];
+            value.resize(512, 0);
+            value.push(1);
+            value
+        },
+        {
+            let mut value = vec![];
+            value.push(1);
+            value.resize(512, 0);
+            value
+        },
+    ] {
+        let asn1 = MockSignature {
+            r: Uint::new(&value).unwrap(),
+            s: Uint::new(&value).unwrap(),
+        }
+        .to_der()
+        .expect("Failed to serialize signature");
+
+        let signature = Signature::from_der(&asn1)
+            .expect("Failed to parse ASN.1 representation of the test signature");
+
+        let _ = verifying_key.verify_digest(Sha256::new().chain_update(MESSAGE), &signature);
+    }
+}
diff --git a/dsa/tests/signing_key.rs b/dsa/tests/signing_key.rs
index aca82360..5c789517 100644
--- a/dsa/tests/signing_key.rs
+++ b/dsa/tests/signing_key.rs
@@ -3,10 +3,12 @@
 // But we want to use those small key sizes for fast tests
 #![allow(deprecated)]
 
+use crypto_bigint::{
+    BoxedUint, Odd,
+    modular::{BoxedMontyForm, BoxedMontyParams},
+};
 use digest::Digest;
 use dsa::{Components, KeySize, SigningKey};
-use num_bigint::BigUint;
-use num_traits::Zero;
 use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding};
 use sha1::Sha1;
 use signature::{DigestVerifier, RandomizedDigestSigner};
@@ -50,9 +52,11 @@ fn sign_and_verify() {
     let signature =
         signing_key.sign_digest_with_rng(&mut rand::thread_rng(), Sha1::new().chain_update(DATA));
 
-    assert!(verifying_key
-        .verify_digest(Sha1::new().chain_update(DATA), &signature)
-        .is_ok());
+    assert!(
+        verifying_key
+            .verify_digest(Sha1::new().chain_update(DATA), &signature)
+            .is_ok()
+    );
 }
 
 #[test]
@@ -60,13 +64,16 @@ fn verify_validity() {
     let signing_key = generate_keypair();
     let components = signing_key.verifying_key().components();
 
+    let params = BoxedMontyParams::new(Odd::new((**components.p()).clone()).unwrap());
+    let form = BoxedMontyForm::new((**components.g()).clone(), params);
+
     assert!(
-        BigUint::zero() < *signing_key.x() && signing_key.x() < components.q(),
+        BoxedUint::zero() < **signing_key.x() && signing_key.x() < components.q(),
         "Requirement 0<x<q not met"
     );
     assert_eq!(
-        *signing_key.verifying_key().y(),
-        components.g().modpow(signing_key.x(), components.p()),
+        **signing_key.verifying_key().y(),
+        form.pow(signing_key.x()).retrieve(),
         "Requirement y=(g^x)%p not met"
     );
 }
diff --git a/dsa/tests/verifying_key.rs b/dsa/tests/verifying_key.rs
index 145d87e5..2814a005 100644
--- a/dsa/tests/verifying_key.rs
+++ b/dsa/tests/verifying_key.rs
@@ -6,11 +6,13 @@ use dsa::VerifyingKey;
 use pkcs8::{DecodePublicKey, EncodePublicKey, LineEnding};
 
 #[cfg(feature = "hazmat")]
-use dsa::{Components, KeySize, SigningKey};
-#[cfg(feature = "hazmat")]
-use num_bigint::BigUint;
-#[cfg(feature = "hazmat")]
-use num_traits::One;
+use {
+    crypto_bigint::{
+        BoxedUint, Odd,
+        modular::{BoxedMontyForm, BoxedMontyParams},
+    },
+    dsa::{Components, KeySize, SigningKey},
+};
 
 const OPENSSL_PEM_PUBLIC_KEY: &str = include_str!("pems/public.pem");
 
@@ -52,6 +54,9 @@ fn validate_verifying_key() {
     let p = verifying_key.components().p();
     let q = verifying_key.components().q();
 
+    let params = BoxedMontyParams::new(Odd::new((**p).clone()).unwrap());
+    let form = BoxedMontyForm::new((**verifying_key.y()).clone(), params);
+
     // Taken from the parameter validation from bouncy castle
-    assert_eq!(verifying_key.y().modpow(q, p), BigUint::one());
+    assert_eq!(form.pow(q).retrieve(), BoxedUint::one());
 }


Reviews:
user: login: baloo
body: Those two `NonZero` I'm not sure about.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T03:53:22Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T05:09:23Z
user: login: aumetra
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T10:38:29Z
user: login: aumetra
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T15:58:45Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T17:35:19Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T19:35:35Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T19:36:12Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T19:37:59Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T19:42:29Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T20:22:08Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T20:33:05Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T21:48:46Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T21:49:43Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T21:58:14Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-20T22:38:44Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T16:12:23Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T16:26:03Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T17:59:10Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T22:43:59Z
user: login: tarcieri
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T13:14:25Z
user: login: baloo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T16:03:23Z
user: login: tarcieri
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-23T17:10:20Z

Review comments:
diff_hunk: @@ -80,31 +87,57 @@ impl SigningKey {
     where
         D: Digest + BlockSizeUser + FixedOutputReset,
     {
-        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash);
+        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash)?;
         self.sign_prehashed(k_kinv, prehash)
     }
 
     /// Sign some pre-hashed data
     fn sign_prehashed(
         &self,
-        (k, inv_k): (BigUint, BigUint),
+        (k, inv_k): (BoxedUint, BoxedUint),
         hash: &[u8],
     ) -> signature::Result<Signature> {
+        extern crate std;
+
         let components = self.verifying_key().components();
+        let key_size = &components.key_size;
         let (p, q, g) = (components.p(), components.q(), components.g());
         let x = self.x();
 
-        let r = g.modpow(&k, p) % q;
+        debug_assert_eq!(key_size.n_aligned(), q.bits_precision());
+
+        let x = x.widen(p.bits_precision());
+        let x = &x;
+
+        let k = k.widen(p.bits_precision());
+        let inv_k = inv_k.widen(p.bits_precision());
+
+        let params = BoxedMontyParams::new(p.clone());
+        let form = BoxedMontyForm::new((**g).clone(), params);
+        let r = form.pow(&k).retrieve() % q.widen(p.bits_precision());
+        debug_assert_eq!(key_size.l_aligned(), r.bits_precision());
+
+        let r_short = r.shorten(key_size.n_aligned());
+        // TODO(baloo): is there any way R could be zero here? Could it be any reason for K to be
+        //              one?
+        let r_short = NonZero::new(r_short).unwrap();
+        let r = NonZero::new(r).unwrap();
path: dsa/src/signing_key.rs
user: login: baloo
body: I'm not sure about this unwrap, isn't there a way for `r = g.modpow(k, p) % q` to be zero?
created_at: 2025-03-03T03:51:28Z
author_association: MEMBER
diff_hunk: @@ -80,31 +87,57 @@ impl SigningKey {
     where
         D: Digest + BlockSizeUser + FixedOutputReset,
     {
-        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash);
+        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash)?;
         self.sign_prehashed(k_kinv, prehash)
     }
 
     /// Sign some pre-hashed data
     fn sign_prehashed(
         &self,
-        (k, inv_k): (BigUint, BigUint),
+        (k, inv_k): (BoxedUint, BoxedUint),
         hash: &[u8],
     ) -> signature::Result<Signature> {
+        extern crate std;
+
         let components = self.verifying_key().components();
+        let key_size = &components.key_size;
         let (p, q, g) = (components.p(), components.q(), components.g());
         let x = self.x();
 
-        let r = g.modpow(&k, p) % q;
+        debug_assert_eq!(key_size.n_aligned(), q.bits_precision());
+
+        let x = x.widen(p.bits_precision());
+        let x = &x;
+
+        let k = k.widen(p.bits_precision());
+        let inv_k = inv_k.widen(p.bits_precision());
+
+        let params = BoxedMontyParams::new(p.clone());
+        let form = BoxedMontyForm::new((**g).clone(), params);
+        let r = form.pow(&k).retrieve() % q.widen(p.bits_precision());
+        debug_assert_eq!(key_size.l_aligned(), r.bits_precision());
+
+        let r_short = r.shorten(key_size.n_aligned());
+        // TODO(baloo): is there any way R could be zero here? Could it be any reason for K to be
+        //              one?
+        let r_short = NonZero::new(r_short).unwrap();
+        let r = NonZero::new(r).unwrap();
 
         let n = q.bits() / 8;
         let block_size = hash.len(); // Hash function output size
 
-        let z_len = min(n, block_size);
-        let z = BigUint::from_bytes_be(&hash[..z_len]);
+        let z_len = min(n as usize, block_size);
+        let z = BoxedUint::from_be_slice(&hash[..z_len], z_len as u32 * 8)
+            .expect("invariant violation");
 
-        let s = (inv_k * (z + x * &r)) % q;
+        let s = inv_k.mul_mod(&(z + &**x * &*r), &q.widen(key_size.l_aligned()));
+        let s = s.shorten(key_size.n_aligned());
+        // TODO(baloo): is there any way S could be zero here?
+        let s = NonZero::new(s).unwrap();
path: dsa/src/signing_key.rs
user: login: baloo
body: Similarly here, `s` could be zero?
created_at: 2025-03-03T03:52:41Z
author_association: MEMBER
diff_hunk: @@ -22,19 +23,19 @@ const MESSAGE: &[u8] = b"test";
 ///
 /// This signature was generated using the keys generated by this CSPRNG (the per-message `k` component was also generated using the CSPRNG)
 const MESSAGE_SIGNATURE_CRATE_ASN1: &[u8] = &[
-    0x30, 0x2C, 0x02, 0x14, 0x45, 0x1D, 0xE5, 0x76, 0x21, 0xD8, 0xFD, 0x76, 0xC1, 0x6F, 0x45, 0x4E,
-    0xDE, 0x5F, 0x09, 0x79, 0x76, 0x52, 0xF3, 0xA5, 0x02, 0x14, 0x53, 0x60, 0xE6, 0xB7, 0xF0, 0xCF,
-    0xAE, 0x49, 0xB1, 0x58, 0x5C, 0xCF, 0x5F, 0x3F, 0x94, 0x49, 0x21, 0xA0, 0xBF, 0xD2,
+    0x30, 0x2d, 0x2, 0x14, 0x4, 0x23, 0xc3, 0xf1, 0xa5, 0x15, 0x65, 0xe5, 0xcc, 0x30, 0xdd, 0x55,
+    0x96, 0x27, 0x30, 0xfb, 0x42, 0xf9, 0x2a, 0x6b, 0x2, 0x15, 0x0, 0x95, 0xd9, 0x42, 0xc2, 0xab,
+    0xe7, 0x62, 0x49, 0x80, 0x4f, 0x53, 0xf1, 0x37, 0x57, 0x81, 0x7b, 0xea, 0x71, 0xc9, 0x2f,
 ];
 
 /// Message signed by OpenSSL using the keys generated by this CSPRNG
 ///
 /// This signature was generated using the SHA-256 digest
-const MESSAGE_SIGNATURE_OPENSSL_ASN1: &[u8] = &[
-    0x30, 0x2C, 0x02, 0x14, 0x6D, 0xB3, 0x8E, 0xAF, 0x97, 0x13, 0x7E, 0x07, 0xFF, 0x24, 0xB8, 0x66,
-    0x97, 0x18, 0xE1, 0x6F, 0xD7, 0x9A, 0x28, 0x2D, 0x02, 0x14, 0x47, 0x8C, 0x0B, 0x96, 0x51, 0x08,
-    0x08, 0xC8, 0x34, 0x9D, 0x0D, 0x41, 0xC7, 0x73, 0x0F, 0xB5, 0x9C, 0xBB, 0x00, 0x34,
-];
+const MESSAGE_SIGNATURE_OPENSSL_ASN1: &[u8] = &hex!(
+    "302e 0215 0092 4523 9462 f59b 5936 d355
+     8b16 8dbb 0915 7ba1 5302 1500 9d8d 7aa7
+     02a6 2dde 1975 e9f2 a20c 73b4 7dd3 0912"
+);
path: dsa/tests/signature.rs
user: login: baloo
body: The behavior of `Components::generate` changed because of the switch to crypto-bigint (and crypto-primes). This makes the "deterministic" private key used for test change. And as a result, makes the signatures change.

I don't believe we offered any guarantee towards the stability of the `Component::generate`?
created_at: 2025-03-03T05:09:23Z
author_association: MEMBER
diff_hunk: @@ -22,19 +23,19 @@ const MESSAGE: &[u8] = b"test";
 ///
 /// This signature was generated using the keys generated by this CSPRNG (the per-message `k` component was also generated using the CSPRNG)
 const MESSAGE_SIGNATURE_CRATE_ASN1: &[u8] = &[
-    0x30, 0x2C, 0x02, 0x14, 0x45, 0x1D, 0xE5, 0x76, 0x21, 0xD8, 0xFD, 0x76, 0xC1, 0x6F, 0x45, 0x4E,
-    0xDE, 0x5F, 0x09, 0x79, 0x76, 0x52, 0xF3, 0xA5, 0x02, 0x14, 0x53, 0x60, 0xE6, 0xB7, 0xF0, 0xCF,
-    0xAE, 0x49, 0xB1, 0x58, 0x5C, 0xCF, 0x5F, 0x3F, 0x94, 0x49, 0x21, 0xA0, 0xBF, 0xD2,
+    0x30, 0x2d, 0x2, 0x14, 0x4, 0x23, 0xc3, 0xf1, 0xa5, 0x15, 0x65, 0xe5, 0xcc, 0x30, 0xdd, 0x55,
+    0x96, 0x27, 0x30, 0xfb, 0x42, 0xf9, 0x2a, 0x6b, 0x2, 0x15, 0x0, 0x95, 0xd9, 0x42, 0xc2, 0xab,
+    0xe7, 0x62, 0x49, 0x80, 0x4f, 0x53, 0xf1, 0x37, 0x57, 0x81, 0x7b, 0xea, 0x71, 0xc9, 0x2f,
 ];
 
 /// Message signed by OpenSSL using the keys generated by this CSPRNG
 ///
 /// This signature was generated using the SHA-256 digest
-const MESSAGE_SIGNATURE_OPENSSL_ASN1: &[u8] = &[
-    0x30, 0x2C, 0x02, 0x14, 0x6D, 0xB3, 0x8E, 0xAF, 0x97, 0x13, 0x7E, 0x07, 0xFF, 0x24, 0xB8, 0x66,
-    0x97, 0x18, 0xE1, 0x6F, 0xD7, 0x9A, 0x28, 0x2D, 0x02, 0x14, 0x47, 0x8C, 0x0B, 0x96, 0x51, 0x08,
-    0x08, 0xC8, 0x34, 0x9D, 0x0D, 0x41, 0xC7, 0x73, 0x0F, 0xB5, 0x9C, 0xBB, 0x00, 0x34,
-];
+const MESSAGE_SIGNATURE_OPENSSL_ASN1: &[u8] = &hex!(
+    "302e 0215 0092 4523 9462 f59b 5936 d355
+     8b16 8dbb 0915 7ba1 5302 1500 9d8d 7aa7
+     02a6 2dde 1975 e9f2 a20c 73b4 7dd3 0912"
+);
path: dsa/tests/signature.rs
user: login: aumetra
body: No, we didn't make any guarantees. The only guarantee we provided was "it outputs something that works".  

If some code relies on this staying the same it's really fragile anyway, since they'd have to use the same algorithms to generate primes and factors, which can differ between implementations. So yeah, this should be fine.
created_at: 2025-03-03T10:38:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -80,31 +87,57 @@ impl SigningKey {
     where
         D: Digest + BlockSizeUser + FixedOutputReset,
     {
-        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash);
+        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash)?;
         self.sign_prehashed(k_kinv, prehash)
     }
 
     /// Sign some pre-hashed data
     fn sign_prehashed(
         &self,
-        (k, inv_k): (BigUint, BigUint),
+        (k, inv_k): (BoxedUint, BoxedUint),
         hash: &[u8],
     ) -> signature::Result<Signature> {
+        extern crate std;
+
         let components = self.verifying_key().components();
+        let key_size = &components.key_size;
         let (p, q, g) = (components.p(), components.q(), components.g());
         let x = self.x();
 
-        let r = g.modpow(&k, p) % q;
+        debug_assert_eq!(key_size.n_aligned(), q.bits_precision());
+
+        let x = x.widen(p.bits_precision());
+        let x = &x;
+
+        let k = k.widen(p.bits_precision());
+        let inv_k = inv_k.widen(p.bits_precision());
+
+        let params = BoxedMontyParams::new(p.clone());
+        let form = BoxedMontyForm::new((**g).clone(), params);
+        let r = form.pow(&k).retrieve() % q.widen(p.bits_precision());
+        debug_assert_eq!(key_size.l_aligned(), r.bits_precision());
+
+        let r_short = r.shorten(key_size.n_aligned());
+        // TODO(baloo): is there any way R could be zero here? Could it be any reason for K to be
+        //              one?
+        let r_short = NonZero::new(r_short).unwrap();
+        let r = NonZero::new(r).unwrap();
path: dsa/src/signing_key.rs
user: login: aumetra
body: IIRC `r` and `s` should neither be ever zero, and if they are it's an invalid signature. So panicking here is fine since AFAIK there would have had to be a cosmic ray or something hitting the CPU register/memory chip for that to exist.
created_at: 2025-03-03T15:58:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -80,31 +87,57 @@ impl SigningKey {
     where
         D: Digest + BlockSizeUser + FixedOutputReset,
     {
-        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash);
+        let k_kinv = crate::generate::secret_number_rfc6979::<D>(self, prehash)?;
         self.sign_prehashed(k_kinv, prehash)
     }
 
     /// Sign some pre-hashed data
     fn sign_prehashed(
         &self,
-        (k, inv_k): (BigUint, BigUint),
+        (k, inv_k): (BoxedUint, BoxedUint),
         hash: &[u8],
     ) -> signature::Result<Signature> {
+        extern crate std;
+
         let components = self.verifying_key().components();
+        let key_size = &components.key_size;
         let (p, q, g) = (components.p(), components.q(), components.g());
         let x = self.x();
 
-        let r = g.modpow(&k, p) % q;
+        debug_assert_eq!(key_size.n_aligned(), q.bits_precision());
+
+        let x = x.widen(p.bits_precision());
+        let x = &x;
+
+        let k = k.widen(p.bits_precision());
+        let inv_k = inv_k.widen(p.bits_precision());
+
+        let params = BoxedMontyParams::new(p.clone());
+        let form = BoxedMontyForm::new((**g).clone(), params);
+        let r = form.pow(&k).retrieve() % q.widen(p.bits_precision());
+        debug_assert_eq!(key_size.l_aligned(), r.bits_precision());
+
+        let r_short = r.shorten(key_size.n_aligned());
+        // TODO(baloo): is there any way R could be zero here? Could it be any reason for K to be
+        //              one?
+        let r_short = NonZero::new(r_short).unwrap();
+        let r = NonZero::new(r).unwrap();
path: dsa/src/signing_key.rs
user: login: baloo
body: Oh right, that ties to the `k` value computed in the `secret_number` and `secret_number_rfc6979`!

That would then be a bug and a panic might be in order.
created_at: 2025-03-03T17:35:19Z
author_association: MEMBER
diff_hunk: @@ -3,16 +3,25 @@
 //! Generate a DSA keypair
 //!
 
-use crate::{generate::components, signing_key::SigningKey, Components, VerifyingKey};
-use num_bigint::{BigUint, RandBigInt};
-use num_traits::One;
-use signature::rand_core::CryptoRngCore;
+use crate::{Components, VerifyingKey, generate::components, signing_key::SigningKey};
+use crypto_bigint::{BoxedUint, NonZero, RandomMod};
+use signature::rand_core::CryptoRng;
 
 /// Generate a new keypair
 #[inline]
-pub fn keypair(rng: &mut impl CryptoRngCore, components: Components) -> SigningKey {
-    let x = rng.gen_biguint_range(&BigUint::one(), components.q());
-    let y = components::public(&components, &x);
+pub fn keypair<R: CryptoRng + ?Sized>(rng: &mut R, components: Components) -> SigningKey {
+    let (x, y) = loop {
+        let x = 'gen_x: loop {
+            let x = BoxedUint::random_mod(rng, components.q());
+            if let Some(x) = NonZero::new(x).into() {
+                break 'gen_x x;
+            }
+        };
+
+        if let Some(y) = components::public(&components, &x).into_option() {
+            break (x, y);
+        }
+    };
path: dsa/src/generate/keypair.rs
user: login: tarcieri
body: The way this logic is written is a little confusing. Maybe even nested functions would be clearer?
created_at: 2025-04-20T19:35:35Z
author_association: MEMBER
diff_hunk: @@ -2,32 +2,16 @@
 //! Generate a per-message secret number
 //!
 
-use crate::{signing_key::SigningKey, Components};
-use alloc::{vec, vec::Vec};
+use crate::{Components, signing_key::SigningKey};
+use alloc::vec;
 use core::cmp::min;
-use digest::{core_api::BlockSizeUser, Digest, FixedOutputReset};
-use num_bigint::{BigUint, ModInverse, RandBigInt};
-use num_traits::{One, Zero};
-use rfc6979::HmacDrbg;
-use signature::rand_core::CryptoRngCore;
-use zeroize::Zeroize;
+use crypto_bigint::{BoxedUint, NonZero, RandomBits};
+use digest::{Digest, FixedOutputReset, core_api::BlockSizeUser};
+use signature::rand_core::TryCryptoRng;
+use zeroize::Zeroizing;
 
-/// Reduce the hash into an RFC-6979 appropriate form
-fn reduce_hash(q: &BigUint, hash: &[u8]) -> Vec<u8> {
-    // Reduce the hash modulo Q
-    let q_byte_len = q.bits() / 8;
-
-    let hash_len = min(hash.len(), q_byte_len);
-    let hash = &hash[..hash_len];
-
-    let hash = BigUint::from_bytes_be(hash);
-    let mut reduced = (hash % q).to_bytes_be();
-
-    while reduced.len() < q_byte_len {
-        reduced.insert(0, 0);
-    }
-
-    reduced
+fn reduce_hash(hash: &[u8], desired_size: usize) -> &[u8] {
+    &hash[(hash.len() - desired_size)..]
 }
path: dsa/src/generate/secret_number.rs
user: login: tarcieri
body: What happened here?
created_at: 2025-04-20T19:36:12Z
author_association: MEMBER
diff_hunk: @@ -182,8 +190,8 @@ impl TryFrom<&[u8]> for Signature {
     }
 }
 
-/// Returns a `BigUint` with the value 2
+/// Returns a `BoxedUint` with the value 2
 #[inline]
-fn two() -> BigUint {
-    BigUint::from(2_u8)
+fn two() -> BoxedUint {
+    BoxedUint::from(2_u8)
 }
path: dsa/src/lib.rs
user: login: tarcieri
body: A `Uint` constant could possibly work here, depending on usage
created_at: 2025-04-20T19:37:59Z
author_association: MEMBER
diff_hunk: @@ -56,23 +65,47 @@ impl VerifyingKey {
         let (r, s) = (signature.r(), signature.s());
         let y = self.y();
 
-        if signature.r() >= q || signature.s() >= q {
+        if r >= q || s >= q {
             return Some(false);
         }
 
-        let w = s.mod_inverse(q)?.to_biguint().unwrap();
+        let q = q.widen(p.bits_precision());
+        let q = &q;
+        let r = r.widen(p.bits_precision());
+        let r = &r;
+        let s = s.widen(p.bits_precision());
+        let s = &s;
path: dsa/src/verifying_key.rs
user: login: tarcieri
body: This is just to save writing `&q`, `&r`, and `&s` I guess?
created_at: 2025-04-20T19:42:29Z
author_association: MEMBER
diff_hunk: @@ -182,8 +190,8 @@ impl TryFrom<&[u8]> for Signature {
     }
 }
 
-/// Returns a `BigUint` with the value 2
+/// Returns a `BoxedUint` with the value 2
 #[inline]
-fn two() -> BigUint {
-    BigUint::from(2_u8)
+fn two() -> BoxedUint {
+    BoxedUint::from(2_u8)
 }
path: dsa/src/lib.rs
user: login: baloo
body: This is used to compare against components and those are BoxedUint, I would need an `impl PartialOrd<Uint> for BoxedUint` for that.
created_at: 2025-04-20T20:22:07Z
author_association: MEMBER
diff_hunk: @@ -182,8 +190,8 @@ impl TryFrom<&[u8]> for Signature {
     }
 }
 
-/// Returns a `BigUint` with the value 2
+/// Returns a `BoxedUint` with the value 2
 #[inline]
-fn two() -> BigUint {
-    BigUint::from(2_u8)
+fn two() -> BoxedUint {
+    BoxedUint::from(2_u8)
 }
path: dsa/src/lib.rs
user: login: tarcieri
body: Seems doable
created_at: 2025-04-20T20:33:05Z
author_association: MEMBER
diff_hunk: @@ -3,16 +3,25 @@
 //! Generate a DSA keypair
 //!
 
-use crate::{generate::components, signing_key::SigningKey, Components, VerifyingKey};
-use num_bigint::{BigUint, RandBigInt};
-use num_traits::One;
-use signature::rand_core::CryptoRngCore;
+use crate::{Components, VerifyingKey, generate::components, signing_key::SigningKey};
+use crypto_bigint::{BoxedUint, NonZero, RandomMod};
+use signature::rand_core::CryptoRng;
 
 /// Generate a new keypair
 #[inline]
-pub fn keypair(rng: &mut impl CryptoRngCore, components: Components) -> SigningKey {
-    let x = rng.gen_biguint_range(&BigUint::one(), components.q());
-    let y = components::public(&components, &x);
+pub fn keypair<R: CryptoRng + ?Sized>(rng: &mut R, components: Components) -> SigningKey {
+    let (x, y) = loop {
+        let x = 'gen_x: loop {
+            let x = BoxedUint::random_mod(rng, components.q());
+            if let Some(x) = NonZero::new(x).into() {
+                break 'gen_x x;
+            }
+        };
+
+        if let Some(y) = components::public(&components, &x).into_option() {
+            break (x, y);
+        }
+    };
path: dsa/src/generate/keypair.rs
user: login: baloo
body: let me know what you think.
created_at: 2025-04-20T21:48:46Z
author_association: MEMBER
diff_hunk: @@ -56,23 +65,47 @@ impl VerifyingKey {
         let (r, s) = (signature.r(), signature.s());
         let y = self.y();
 
-        if signature.r() >= q || signature.s() >= q {
+        if r >= q || s >= q {
             return Some(false);
         }
 
-        let w = s.mod_inverse(q)?.to_biguint().unwrap();
+        let q = q.widen(p.bits_precision());
+        let q = &q;
+        let r = r.widen(p.bits_precision());
+        let r = &r;
+        let s = s.widen(p.bits_precision());
+        let s = &s;
path: dsa/src/verifying_key.rs
user: login: baloo
body: Just me when I was fiddling with precisions.
created_at: 2025-04-20T21:49:43Z
author_association: MEMBER
diff_hunk: @@ -2,32 +2,16 @@
 //! Generate a per-message secret number
 //!
 
-use crate::{signing_key::SigningKey, Components};
-use alloc::{vec, vec::Vec};
+use crate::{Components, signing_key::SigningKey};
+use alloc::vec;
 use core::cmp::min;
-use digest::{core_api::BlockSizeUser, Digest, FixedOutputReset};
-use num_bigint::{BigUint, ModInverse, RandBigInt};
-use num_traits::{One, Zero};
-use rfc6979::HmacDrbg;
-use signature::rand_core::CryptoRngCore;
-use zeroize::Zeroize;
+use crypto_bigint::{BoxedUint, NonZero, RandomBits};
+use digest::{Digest, FixedOutputReset, core_api::BlockSizeUser};
+use signature::rand_core::TryCryptoRng;
+use zeroize::Zeroizing;
 
-/// Reduce the hash into an RFC-6979 appropriate form
-fn reduce_hash(q: &BigUint, hash: &[u8]) -> Vec<u8> {
-    // Reduce the hash modulo Q
-    let q_byte_len = q.bits() / 8;
-
-    let hash_len = min(hash.len(), q_byte_len);
-    let hash = &hash[..hash_len];
-
-    let hash = BigUint::from_bytes_be(hash);
-    let mut reduced = (hash % q).to_bytes_be();
-
-    while reduced.len() < q_byte_len {
-        reduced.insert(0, 0);
-    }
-
-    reduced
+fn reduce_hash(hash: &[u8], desired_size: usize) -> &[u8] {
+    &hash[(hash.len() - desired_size)..]
 }
path: dsa/src/generate/secret_number.rs
user: login: baloo
body: The logic moved in `secret_number_rfc6979`, the name was incorrect, that should have been `truncate_hash` or something.
created_at: 2025-04-20T21:58:14Z
author_association: MEMBER
diff_hunk: @@ -17,14 +17,15 @@ env:
 
 jobs:
   build:
+    if: false # both thumbv7 and wasm require a getrandom backend to be injected
path: .github/workflows/dsa.yml
user: login: tarcieri
body: Why is `getrandom` a hard dependency now?
created_at: 2025-04-20T22:38:44Z
author_association: MEMBER
diff_hunk: @@ -3,16 +3,25 @@
 //! Generate a DSA keypair
 //!
 
-use crate::{generate::components, signing_key::SigningKey, Components, VerifyingKey};
-use num_bigint::{BigUint, RandBigInt};
-use num_traits::One;
-use signature::rand_core::CryptoRngCore;
+use crate::{Components, VerifyingKey, generate::components, signing_key::SigningKey};
+use crypto_bigint::{BoxedUint, NonZero, RandomMod};
+use signature::rand_core::CryptoRng;
 
 /// Generate a new keypair
 #[inline]
-pub fn keypair(rng: &mut impl CryptoRngCore, components: Components) -> SigningKey {
-    let x = rng.gen_biguint_range(&BigUint::one(), components.q());
-    let y = components::public(&components, &x);
+pub fn keypair<R: CryptoRng + ?Sized>(rng: &mut R, components: Components) -> SigningKey {
+    let (x, y) = loop {
+        let x = 'gen_x: loop {
+            let x = BoxedUint::random_mod(rng, components.q());
+            if let Some(x) = NonZero::new(x).into() {
+                break 'gen_x x;
+            }
+        };
+
+        if let Some(y) = components::public(&components, &x).into_option() {
+            break (x, y);
+        }
+    };
path: dsa/src/generate/keypair.rs
user: login: tarcieri
body: I can't tell what changed
created_at: 2025-04-21T16:12:23Z
author_association: MEMBER
diff_hunk: @@ -3,16 +3,25 @@
 //! Generate a DSA keypair
 //!
 
-use crate::{generate::components, signing_key::SigningKey, Components, VerifyingKey};
-use num_bigint::{BigUint, RandBigInt};
-use num_traits::One;
-use signature::rand_core::CryptoRngCore;
+use crate::{Components, VerifyingKey, generate::components, signing_key::SigningKey};
+use crypto_bigint::{BoxedUint, NonZero, RandomMod};
+use signature::rand_core::CryptoRng;
 
 /// Generate a new keypair
 #[inline]
-pub fn keypair(rng: &mut impl CryptoRngCore, components: Components) -> SigningKey {
-    let x = rng.gen_biguint_range(&BigUint::one(), components.q());
-    let y = components::public(&components, &x);
+pub fn keypair<R: CryptoRng + ?Sized>(rng: &mut R, components: Components) -> SigningKey {
+    let (x, y) = loop {
+        let x = 'gen_x: loop {
+            let x = BoxedUint::random_mod(rng, components.q());
+            if let Some(x) = NonZero::new(x).into() {
+                break 'gen_x x;
+            }
+        };
+
+        if let Some(y) = components::public(&components, &x).into_option() {
+            break (x, y);
+        }
+    };
path: dsa/src/generate/keypair.rs
user: login: baloo
body: https://github.com/RustCrypto/signatures/pull/906/commits/87db89af3996e71fa77491f1e48b8930ea2a3596
created_at: 2025-04-21T16:26:03Z
author_association: MEMBER
diff_hunk: @@ -17,14 +17,15 @@ env:
 
 jobs:
   build:
+    if: false # both thumbv7 and wasm require a getrandom backend to be injected
path: .github/workflows/dsa.yml
user: login: baloo
body: wait? is it?
created_at: 2025-04-21T17:59:09Z
author_association: MEMBER
diff_hunk: @@ -17,14 +17,15 @@ env:
 
 jobs:
   build:
+    if: false # both thumbv7 and wasm require a getrandom backend to be injected
path: .github/workflows/dsa.yml
user: login: tarcieri
body: If it isn't, you should still be able to do this build
created_at: 2025-04-21T22:43:59Z
author_association: MEMBER
diff_hunk: @@ -17,14 +17,15 @@ env:
 
 jobs:
   build:
+    if: false # both thumbv7 and wasm require a getrandom backend to be injected
path: .github/workflows/dsa.yml
user: login: tarcieri
body: Offhand I don't see anything which would make `getrandom` a hard dependency so I guess I'm confused why this is needed
created_at: 2025-04-22T13:14:25Z
author_association: MEMBER
diff_hunk: @@ -17,14 +17,15 @@ env:
 
 jobs:
   build:
+    if: false # both thumbv7 and wasm require a getrandom backend to be injected
path: .github/workflows/dsa.yml
user: login: baloo
body: yeah you're correct, I guess I got confused with the other rand_core upgrade? I don't remember.
created_at: 2025-04-22T16:03:23Z
author_association: MEMBER

PR comments:
user: login: baloo
created_at: 2025-02-16T08:50:41Z
body: cc @aumetra 
author_association: MEMBER
user: login: aumetra
created_at: 2025-02-16T16:52:08Z
body: Apologies, I was a bit absent lately. I'll have a look when I get the time (which should be in like two hours at max)
author_association: CONTRIBUTOR
user: login: baloo
created_at: 2025-02-16T20:45:13Z
body: No need to apologize! Please take care.
That was only to keep you updated on the progress and let you chime in as needed.

If there are specific things you wanted to be taken care of, please let me know I'd be happy to.
author_association: MEMBER
user: login: baloo
created_at: 2025-02-17T06:58:01Z
body: out of the last 3 tests that still fail:
 - sign_message: that one needs the vectors to be adjusted, we're switching the backend and by doing so the primes we get from the RNG are different because their generation is now different.
 - signer_verifier_signature: that one is a precision that needs to be adjusted.
 - verify_signature: I'm not quite sure yet.
 
 Overall having to adjust precisions seems to be ... inefficent? error-prone? I guess there is a way better method for doing that. I need to have a look at RSA how they did it there.
author_association: MEMBER
user: login: aumetra
created_at: 2025-02-19T11:03:12Z
body: > Overall having to adjust precisions seems to be ... inefficent? error-prone? I guess there is a way better method for doing that. I need to have a look at RSA how they did it there.

Seems like even `rsa` did the same thing. Just via the `.widen` method which is, I guess, a newer method that didn't exist when I started this PR a year ago 
author_association: CONTRIBUTOR
user: login: aumetra
created_at: 2025-02-19T11:04:08Z
body: Scratch that, it existed, I just didn't see it at that time
author_association: CONTRIBUTOR
user: login: tarcieri
created_at: 2025-02-19T14:07:07Z
body: @baloo the precision aspect is definitely a bit tricky. `num-bigint` does it automatically, however this leads to variable-time operation depending on the value’s precision which is unsuitable for private key operations
author_association: MEMBER
user: login: baloo
created_at: 2025-02-19T17:43:34Z
body: Oh I understand why it's done! I'm just not sure I know what I'm doing [insert here the picture of a dog].
author_association: MEMBER
user: login: baloo
created_at: 2025-03-02T07:04:57Z
body: okay, it's definitely on the ugly side, but tests do pass \o/

I think I got the hang of it, I think I can clean things up from there, but ... tomorrow.
author_association: MEMBER
user: login: aumetra
created_at: 2025-03-02T09:54:36Z
body: Nice! I mean I have a few hours to kill today, too, so I can go ahead and try to clean up some things, too. I'll PR it into your fork when I get to it 
author_association: CONTRIBUTOR
user: login: tarcieri
created_at: 2025-03-09T18:51:54Z
body: @baloo this one could actually use a rebase
author_association: MEMBER
user: login: baloo
created_at: 2025-03-09T20:53:40Z
body: This probably should include tests stretching the bit precision of a signature to test against panic.
Maybe proptests would be a good fit?
author_association: MEMBER
user: login: tarcieri
created_at: 2025-03-09T20:57:42Z
body: Yes proptests would be good.

I need to pull this down and benchmark it but getting the precisions right has been a challenge in `rsa`. Did performance regress significantly?
author_association: MEMBER
user: login: baloo
created_at: 2025-03-09T22:25:31Z
body: It was pretty bad at some point (tests would take over a minute) but I did not dig why. It works fine now.

I did not run a benchmark.
author_association: MEMBER
user: login: dignifiedquire
created_at: 2025-04-05T11:42:59Z
body: anything missing for merging this? 
author_association: MEMBER
user: login: tarcieri
created_at: 2025-04-05T11:48:10Z
body: cc @baloo
author_association: MEMBER
user: login: baloo
created_at: 2025-04-05T14:20:47Z
body: A review!
author_association: MEMBER
user: login: tarcieri
created_at: 2025-04-07T01:04:26Z
body: Will try to do that soon
author_association: MEMBER
user: login: tarcieri
created_at: 2025-04-20T19:49:08Z
body: As a general comment there's a lot of usage of `expect` in places that it wasn't required with `num-bigint`.

I don't want to make addressing those a blocker for this PR but it sure would be nice to fix eventually, even if it's just converting them to errors internally.
author_association: MEMBER
user: login: tarcieri
created_at: 2025-04-20T19:58:29Z
body: Hmm, what's up with the test failures?
author_association: MEMBER
user: login: baloo
created_at: 2025-04-20T20:10:33Z
body: > Hmm, what's up with the test failures?

Tests are relying on the generation of primes by the inner library (injecting an rng). Whenever the implementation for primes changes, the tests will fail. I just bumped the pin for `crypto-primes` and it got reworked a bit. I'll need to adjust the expected signatures.

Essentially this comment: https://github.com/RustCrypto/signatures/pull/906#discussion_r1976885319
author_association: MEMBER
user: login: baloo
created_at: 2025-04-23T16:57:37Z
body: @tarcieri anything else on this PR?
author_association: MEMBER
user: login: tarcieri
created_at: 2025-04-23T17:09:21Z
body: @baloo there are some merge conflicts
author_association: MEMBER