diff --git a/.github/workflows/crypto-bigint.yml b/.github/workflows/crypto-bigint.yml index b319809fe..40893a68b 100644 --- a/.github/workflows/crypto-bigint.yml +++ b/.github/workflows/crypto-bigint.yml @@ -5,7 +5,8 @@ on: paths-ignore: - README.md push: - branches: master + branches: + - master paths-ignore: - README.md @@ -20,7 +21,7 @@ jobs: strategy: matrix: rust: - - 1.81.0 # MSRV + - 1.83.0 # MSRV - stable target: - thumbv7em-none-eabi @@ -48,7 +49,7 @@ jobs: include: # 32-bit Linux - target: i686-unknown-linux-gnu - rust: 1.81.0 # MSRV + rust: 1.83.0 # MSRV deps: sudo apt update && sudo apt install gcc-multilib - target: i686-unknown-linux-gnu rust: stable @@ -56,7 +57,7 @@ jobs: # 64-bit Linux - target: x86_64-unknown-linux-gnu - rust: 1.81.0 # MSRV + rust: 1.83.0 # MSRV - target: x86_64-unknown-linux-gnu rust: stable steps: @@ -105,6 +106,9 @@ jobs: with: toolchain: nightly - run: cargo update -Z minimal-versions + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable - run: cargo +stable build --release --all-features miri: @@ -136,7 +140,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: stable components: clippy - run: cargo clippy --all --all-features -- -D warnings @@ -156,7 +160,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.81.0 + toolchain: stable - run: cargo build --benches - run: cargo build --all-features --benches diff --git a/.github/workflows/dudect.yml b/.github/workflows/dudect.yml index b112184c1..a54d6a44e 100644 --- a/.github/workflows/dudect.yml +++ b/.github/workflows/dudect.yml @@ -3,7 +3,8 @@ name: dudect on: push: - branches: master + branches: + - master paths-ignore: - README.md diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index a932bafe9..015e13179 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -1,10 +1,13 @@ name: Security Audit on: pull_request: - paths: Cargo.lock + paths: + - Cargo.lock + - .github/workflows/security-audit.yml push: branches: master - paths: Cargo.lock + paths: + - Cargo.lock schedule: - cron: "0 0 * * *" @@ -18,7 +21,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.cargo/bin - key: ${{ runner.os }}-cargo-audit-v0.13.0 - - uses: actions-rs/audit-check@v1 + key: ${{ runner.os }}-cargo-audit-v0.21.0 + - uses: rustsec/audit-check@v2 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index f043d6801..e91564cb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -19,15 +19,15 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base16ct" @@ -46,24 +46,24 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bumpalo" @@ -71,11 +71,17 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cast" @@ -118,18 +124,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.8" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstyle", "clap_lex", @@ -137,9 +143,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "criterion" @@ -179,9 +185,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -198,9 +204,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -238,25 +244,25 @@ checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" @@ -287,9 +293,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex-literal" @@ -299,22 +305,22 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hybrid-array" -version = "0.2.0-rc.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a41e5b0754cae5aaf7915f1df1147ba8d316fc6e019cfcc00fbaba96d5e030" +checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" dependencies = [ "typenum", ] [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -328,16 +334,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -349,15 +356,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libm" -version = "0.2.8" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" @@ -367,9 +368,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -379,9 +380,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -414,26 +415,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -444,39 +444,42 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", @@ -500,9 +503,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -568,9 +571,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -580,9 +583,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -591,9 +594,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rlp" @@ -613,15 +616,15 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -653,18 +656,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -673,20 +676,21 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serdect" -version = "0.3.0-rc.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a504c8ee181e3e594d84052f983d60afe023f4d94d050900be18062bbbf7b58" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" dependencies = [ "base16ct", "serde", @@ -700,9 +704,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" dependencies = [ "proc-macro2", "quote", @@ -711,14 +715,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", + "once_cell", "rustix", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -745,9 +751,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "wait-timeout" @@ -776,23 +782,23 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -801,9 +807,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -811,9 +817,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -824,15 +830,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -840,11 +846,11 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -856,11 +862,20 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -874,51 +889,72 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[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 = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index 9caf1377c..b5181e5c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,18 +14,18 @@ keywords = ["arbitrary", "crypto", "bignum", "integer", "precision"] readme = "README.md" resolver = "2" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" [dependencies] subtle = { version = "2.6", default-features = false } # optional dependencies der = { version = "0.8.0-rc.1", optional = true, default-features = false } -hybrid-array = { version = "0.2.0-rc.11", optional = true } +hybrid-array = { version = "0.2", optional = true } num-traits = { version = "0.2.19", default-features = false } rand_core = { version = "0.6.4", optional = true } rlp = { version = "0.6", optional = true, default-features = false } -serdect = { version = "0.3.0-rc.0", optional = true, default-features = false } +serdect = { version = "0.3", optional = true, default-features = false } zeroize = { version = "1", optional = true, default-features = false } [dev-dependencies] @@ -35,7 +35,7 @@ hex-literal = "0.4" num-bigint = "0.4" num-integer = "0.1" num-modular = { version = "0.6", features = ["num-bigint", "num-integer", "num-traits"] } -proptest = "1" +proptest = "1.5" rand_core = { version = "0.6", features = ["std"] } rand_chacha = "0.3" @@ -76,3 +76,7 @@ harness = false [[bench]] name = "uint" harness = false + +[[bench]] +name = "int" +harness = false diff --git a/README.md b/README.md index 1da82ab71..321c49b10 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ microcontrollers). ## Minimum Supported Rust Version -This crate requires **Rust 1.81** at a minimum. +This crate requires **Rust 1.83** at a minimum. We may change the MSRV in the future, but it will be accompanied by a minor version bump. @@ -68,7 +68,7 @@ dual licensed as above, without any additional terms or conditions. [build-image]: https://github.com/RustCrypto/crypto-bigint/actions/workflows/crypto-bigint.yml/badge.svg [build-link]: https://github.com/RustCrypto/crypto-bigint/actions/workflows/crypto-bigint.yml [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.83+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300602-crypto-bigint diff --git a/benches/int.rs b/benches/int.rs new file mode 100644 index 000000000..466eb6b70 --- /dev/null +++ b/benches/int.rs @@ -0,0 +1,344 @@ +use std::ops::Div; + +use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; +use num_traits::WrappingSub; +use rand_core::OsRng; + +use crypto_bigint::{NonZero, Random, I1024, I128, I2048, I256, I4096, I512}; + +fn bench_mul(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("split_mul, I128xI128", |b| { + b.iter_batched( + || (I256::random(&mut OsRng), I256::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I256xI256", |b| { + b.iter_batched( + || (I256::random(&mut OsRng), I256::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I512xI512", |b| { + b.iter_batched( + || (I512::random(&mut OsRng), I512::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I1024xI1024", |b| { + b.iter_batched( + || (I1024::random(&mut OsRng), I1024::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I2048xI2048", |b| { + b.iter_batched( + || (I2048::random(&mut OsRng), I2048::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I4096xI4096", |b| { + b.iter_batched( + || (I4096::random(&mut OsRng), I4096::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); +} + +fn bench_widening_mul(c: &mut Criterion) { + let mut group = c.benchmark_group("widening ops"); + + group.bench_function("widening_mul, I128xI128", |b| { + b.iter_batched( + || (I128::random(&mut OsRng), I128::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I256xI256", |b| { + b.iter_batched( + || (I256::random(&mut OsRng), I256::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I512xI512", |b| { + b.iter_batched( + || (I512::random(&mut OsRng), I512::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I1024xI1024", |b| { + b.iter_batched( + || (I1024::random(&mut OsRng), I1024::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I2048xI2048", |b| { + b.iter_batched( + || (I2048::random(&mut OsRng), I2048::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I4096xI4096", |b| { + b.iter_batched( + || (I4096::random(&mut OsRng), I4096::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); +} + +fn bench_div(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("div, I256/I128, full size", |b| { + b.iter_batched( + || { + let x = I256::random(&mut OsRng); + let y = I128::random(&mut OsRng).resize::<{ I256::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I512/I256, full size", |b| { + b.iter_batched( + || { + let x = I512::random(&mut OsRng); + let y = I256::random(&mut OsRng).resize::<{ I512::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I1024/I512, full size", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I512::random(&mut OsRng).resize::<{ I1024::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I2048/I1024, full size", |b| { + b.iter_batched( + || { + let x = I2048::random(&mut OsRng); + let y = I1024::random(&mut OsRng).resize::<{ I2048::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I4096/I2048, full size", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I2048::random(&mut OsRng).resize::<{ I4096::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +fn bench_add(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("add, I128+I128", |b| { + b.iter_batched( + || { + let x = I128::random(&mut OsRng); + let y = I128::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I256+I256", |b| { + b.iter_batched( + || { + let x = I256::random(&mut OsRng); + let y = I256::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I512+I512", |b| { + b.iter_batched( + || { + let x = I512::random(&mut OsRng); + let y = I512::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I1024+I1024", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I1024::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I2048+I2048", |b| { + b.iter_batched( + || { + let x = I2048::random(&mut OsRng); + let y = I2048::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I4096+I4096", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I4096::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +fn bench_sub(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("sub, I128-I128", |b| { + b.iter_batched( + || { + let x = I128::random(&mut OsRng); + let y = I128::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I256-I256", |b| { + b.iter_batched( + || { + let x = I256::random(&mut OsRng); + let y = I256::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I512-I512", |b| { + b.iter_batched( + || { + let x = I512::random(&mut OsRng); + let y = I512::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I1024-I1024", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I1024::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I2048-I2048", |b| { + b.iter_batched( + || { + let x = I2048::random(&mut OsRng); + let y = I2048::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I4096-I4096", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I4096::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +criterion_group!( + benches, + bench_mul, + bench_widening_mul, + bench_div, + bench_add, + bench_sub, +); + +criterion_main!(benches); diff --git a/benches/uint.rs b/benches/uint.rs index 96972a030..3bb2a9616 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -1,8 +1,125 @@ use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use crypto_bigint::{ - Limb, NonZero, Odd, Random, RandomMod, Reciprocal, Uint, U128, U2048, U256, U4096, + Limb, NonZero, Odd, Random, RandomBits, RandomMod, Reciprocal, Uint, U1024, U128, U2048, U256, + U4096, U512, }; -use rand_core::OsRng; +use rand_chacha::ChaCha8Rng; +use rand_core::{OsRng, RngCore, SeedableRng}; + +fn make_rng() -> ChaCha8Rng { + ChaCha8Rng::from_seed(*b"01234567890123456789012345678901") +} + +fn bench_random(c: &mut Criterion) { + let mut group = c.benchmark_group("bounded random"); + + let mut rng = make_rng(); + group.bench_function("random_mod, U1024", |b| { + let bound = U1024::random(&mut rng); + let bound_nz = NonZero::new(bound).unwrap(); + b.iter(|| black_box(U1024::random_mod(&mut rng, &bound_nz))); + }); + + let mut rng = make_rng(); + group.bench_function("random_bits, U1024", |b| { + let bound = U1024::random(&mut rng); + let bound_bits = bound.bits_vartime(); + b.iter(|| { + let mut r = U1024::random_bits(&mut rng, bound_bits); + while r >= bound { + r = U1024::random_bits(&mut rng, bound_bits); + } + black_box(r) + }); + }); + + let mut rng = make_rng(); + group.bench_function("random_mod, U1024, small bound", |b| { + let bound = U1024::from_u64(rng.next_u64()); + let bound_nz = NonZero::new(bound).unwrap(); + b.iter(|| black_box(U1024::random_mod(&mut rng, &bound_nz))); + }); + + let mut rng = make_rng(); + group.bench_function("random_bits, U1024, small bound", |b| { + let bound = U1024::from_u64(rng.next_u64()); + let bound_bits = bound.bits_vartime(); + b.iter(|| { + let mut r = U1024::random_bits(&mut rng, bound_bits); + while r >= bound { + r = U1024::random_bits(&mut rng, bound_bits); + } + black_box(r) + }); + }); + + let mut rng = make_rng(); + group.bench_function("random_mod, U1024, 512 bit bound low", |b| { + let bound = U512::random(&mut rng); + let bound = U1024::from((bound, U512::ZERO)); + let bound_nz = NonZero::new(bound).unwrap(); + b.iter(|| black_box(U1024::random_mod(&mut rng, &bound_nz))); + }); + + let mut rng = make_rng(); + group.bench_function("random_bits, U1024, 512 bit bound low", |b| { + let bound = U512::random(&mut rng); + let bound = U1024::from((bound, U512::ZERO)); + let bound_bits = bound.bits_vartime(); + b.iter(|| { + let mut r = U1024::random_bits(&mut rng, bound_bits); + while r >= bound { + r = U1024::random_bits(&mut rng, bound_bits); + } + black_box(r) + }); + }); + + let mut rng = make_rng(); + group.bench_function("random_mod, U1024, 512 bit bound hi", |b| { + let bound = U512::random(&mut rng); + let bound = U1024::from((U512::ZERO, bound)); + let bound_nz = NonZero::new(bound).unwrap(); + b.iter(|| black_box(U1024::random_mod(&mut rng, &bound_nz))); + }); + + let mut rng = make_rng(); + group.bench_function("random_bits, U1024, 512 bit bound hi", |b| { + let bound = U512::random(&mut rng); + let bound = U1024::from((U512::ZERO, bound)); + let bound_bits = bound.bits_vartime(); + b.iter(|| { + let mut r = U1024::random_bits(&mut rng, bound_bits); + while r >= bound { + r = U1024::random_bits(&mut rng, bound_bits); + } + black_box(r) + }); + }); + + // Slow case: the hi limb is just `2` + let mut rng = make_rng(); + group.bench_function("random_mod, U1024, tiny high limb", |b| { + let hex_1024 = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000291A6B42D1C7D2A7184D13E36F65773BBEFB4FA7996101300D49F09962A361F00"; + let modulus = U1024::from_be_hex(hex_1024); + let modulus_nz = NonZero::new(modulus).unwrap(); + b.iter(|| black_box(U1024::random_mod(&mut rng, &modulus_nz))); + }); + + // Slow case: the hi limb is just `2` + let mut rng = make_rng(); + group.bench_function("random_bits, U1024, tiny high limb", |b| { + let hex_1024 = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000291A6B42D1C7D2A7184D13E36F65773BBEFB4FA7996101300D49F09962A361F00"; + let bound = U1024::from_be_hex(hex_1024); + let bound_bits = bound.bits_vartime(); + b.iter(|| { + let mut r = U1024::random_bits(&mut rng, bound_bits); + while r >= bound { + r = U1024::random_bits(&mut rng, bound_bits); + } + }); + }); +} fn bench_mul(c: &mut Criterion) { let mut group = c.benchmark_group("wrapping ops"); @@ -370,6 +487,7 @@ fn bench_sqrt(c: &mut Criterion) { criterion_group!( benches, + bench_random, bench_mul, bench_division, bench_gcd, diff --git a/src/const_choice.rs b/src/const_choice.rs index 524a3525c..2d9f99966 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -1,6 +1,6 @@ use subtle::{Choice, CtOption}; -use crate::{modular::SafeGcdInverter, Limb, NonZero, Odd, Uint, WideWord, Word}; +use crate::{modular::SafeGcdInverter, Int, Limb, NonZero, Odd, Uint, WideWord, Word}; /// A boolean value returned by constant-time `const fn`s. // TODO: should be replaced by `subtle::Choice` or `CtOption` @@ -49,6 +49,13 @@ impl ConstChoice { Self(value.wrapping_neg()) } + /// Returns the truthy value if the most significant bit of `value` is `1`, + /// and the falsy value if it equals `0`. + #[inline] + pub(crate) const fn from_word_msb(value: Word) -> Self { + Self::from_word_lsb(value >> (Word::BITS - 1)) + } + /// Returns the truthy value if `value == 1`, and the falsy value if `value == 0`. /// Panics for other values. #[inline] @@ -187,6 +194,16 @@ impl ConstChoice { Self(self.0 ^ other.0) } + #[inline] + pub(crate) const fn ne(&self, other: Self) -> Self { + Self::xor(self, other) + } + + #[inline] + pub(crate) const fn eq(&self, other: Self) -> Self { + Self::ne(self, other).not() + } + /// Return `b` if `self` is truthy, otherwise return `a`. #[inline] pub(crate) const fn select_word(&self, a: Word, b: Word) -> Word { @@ -374,6 +391,12 @@ impl ConstCtOption> { assert!(self.is_some.is_true_vartime(), "{}", msg); self.value } + + /// Returns the contained value, interpreting the underlying [`Uint`] value as an [`Int`]. + #[inline] + pub const fn as_int(&self) -> ConstCtOption> { + ConstCtOption::new(Int::from_bits(self.value), self.is_some) + } } impl ConstCtOption<(Uint, Uint)> { @@ -418,6 +441,26 @@ impl ConstCtOption>> { } } +impl ConstCtOption> { + /// This returns the underlying value if it is `Some` or the provided value otherwise. + #[inline] + pub const fn unwrap_or(self, def: Int) -> Int { + Int::select(&def, &self.value, self.is_some) + } + + /// Returns the contained value, consuming the `self` value. + /// + /// # Panics + /// + /// Panics if the value is none with a custom panic message provided by + /// `msg`. + #[inline] + pub const fn expect(self, msg: &str) -> Int { + assert!(self.is_some.is_true_vartime(), "{}", msg); + self.value + } +} + impl ConstCtOption> { /// Returns the contained value, consuming the `self` value. /// @@ -450,9 +493,10 @@ impl #[cfg(test)] mod tests { - use super::ConstChoice; use crate::{WideWord, Word}; + use super::ConstChoice; + #[test] fn from_u64_lsb() { assert_eq!(ConstChoice::from_u64_lsb(0), ConstChoice::FALSE); diff --git a/src/int.rs b/src/int.rs new file mode 100644 index 000000000..66a8d3e8a --- /dev/null +++ b/src/int.rs @@ -0,0 +1,386 @@ +//! Stack-allocated big signed integers. + +use core::fmt; + +use num_traits::ConstZero; +#[cfg(feature = "serde")] +use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +#[cfg(feature = "serde")] +use crate::Encoding; +use crate::{Bounded, ConstChoice, ConstCtOption, Constants, Limb, NonZero, Odd, Uint, Word}; + +mod add; +mod bit_and; +mod bit_not; +mod bit_or; +mod bit_xor; +mod cmp; +mod div; +mod encoding; +mod from; +mod mul; +mod neg; +mod resize; +mod shl; +mod shr; +mod sign; +mod sub; +pub(crate) mod types; + +#[cfg(feature = "rand_core")] +mod rand; + +/// Stack-allocated big _signed_ integer. +/// See [`Uint`] for _unsigned_ integers. +/// +/// Created as a [`Uint`] newtype. +#[allow(clippy::derived_hash_with_manual_eq)] +#[derive(Copy, Clone, Hash)] +pub struct Int(Uint); + +impl Int { + /// The value `0`. + pub const ZERO: Self = Self(Uint::ZERO); // Bit sequence (be): 0000....0000 + + /// The value `1`. + pub const ONE: Self = Self(Uint::ONE); // Bit sequence (be): 0000....0001 + + /// The value `-1` + pub const MINUS_ONE: Self = Self::FULL_MASK; // Bit sequence (be): 1111....1111 + + /// Smallest value this [`Int`] can express. + pub const MIN: Self = Self(Uint::MAX.bitxor(&Uint::MAX.shr(1u32))); // Bit sequence (be): 1000....0000 + + /// Maximum value this [`Int`] can express. + pub const MAX: Self = Self(Uint::MAX.shr(1u32)); // Bit sequence (be): 0111....1111 + + /// Bit mask for the sign bit of this [`Int`]. + pub const SIGN_MASK: Self = Self::MIN; // Bit sequence (be): 1000....0000 + + /// All-one bit mask. + pub const FULL_MASK: Self = Self(Uint::MAX); // Bit sequence (be): 1111...1111 + + /// Total size of the represented integer in bits. + pub const BITS: u32 = Uint::::BITS; + + /// Total size of the represented integer in bytes. + pub const BYTES: usize = Uint::::BYTES; + + /// The number of limbs used on this platform. + pub const LIMBS: usize = LIMBS; + + /// Const-friendly [`Int`] constructor. + pub const fn new(limbs: [Limb; LIMBS]) -> Self { + Self(Uint::new(limbs)) + } + + /// Const-friendly [`Int`] constructor. + /// + /// Reinterprets the bits of a value of type [`Uint`] as an [`Int`]. + /// For a proper conversion, see [`Int::new_from_abs_sign`]; + /// the public interface of this function is available at [`Uint::as_int`]. + pub(crate) const fn from_bits(value: Uint) -> Self { + Self(value) + } + + /// Create an [`Int`] from an array of [`Word`]s (i.e. word-sized unsigned + /// integers). + #[inline] + pub const fn from_words(arr: [Word; LIMBS]) -> Self { + Self(Uint::from_words(arr)) + } + + /// Create an array of [`Word`]s (i.e. word-sized unsigned integers) from + /// an [`Int`]. + #[inline] + pub const fn to_words(self) -> [Word; LIMBS] { + self.0.to_words() + } + + /// Borrow the inner limbs as an array of [`Word`]s. + pub const fn as_words(&self) -> &[Word; LIMBS] { + self.0.as_words() + } + + /// Borrow the inner limbs as a mutable array of [`Word`]s. + pub fn as_words_mut(&mut self) -> &mut [Word; LIMBS] { + self.0.as_words_mut() + } + + /// Borrow the limbs of this [`Int`]. + pub const fn as_limbs(&self) -> &[Limb; LIMBS] { + self.0.as_limbs() + } + + /// Borrow the limbs of this [`Int`] mutably. + pub fn as_limbs_mut(&mut self) -> &mut [Limb; LIMBS] { + self.0.as_limbs_mut() + } + + /// Convert this [`Int`] into its inner limbs. + pub const fn to_limbs(self) -> [Limb; LIMBS] { + self.0.to_limbs() + } + + /// Convert to a [`NonZero>`]. + /// + /// Returns some if the original value is non-zero, and false otherwise. + pub const fn to_nz(self) -> ConstCtOption> { + ConstCtOption::new(NonZero(self), self.0.is_nonzero()) + } + + /// Convert to a [`Odd>`]. + /// + /// Returns some if the original value is odd, and false otherwise. + pub const fn to_odd(self) -> ConstCtOption> { + ConstCtOption::new(Odd(self), self.0.is_odd()) + } + + /// Interpret the data in this type as a [`Uint`] instead. + pub const fn as_uint(&self) -> &Uint { + &self.0 + } + + /// Whether this [`Int`] is equal to `Self::MIN`. + pub const fn is_min(&self) -> ConstChoice { + Self::eq(self, &Self::MIN) + } + + /// Whether this [`Int`] is equal to `Self::MAX`. + pub fn is_max(&self) -> ConstChoice { + Self::eq(self, &Self::MAX) + } + + /// Invert the most significant bit (msb) of this [`Int`] + const fn invert_msb(&self) -> Self { + Self(self.0.bitxor(&Self::SIGN_MASK.0)) + } +} + +impl AsRef<[Word; LIMBS]> for Int { + fn as_ref(&self) -> &[Word; LIMBS] { + self.as_words() + } +} + +impl AsMut<[Word; LIMBS]> for Int { + fn as_mut(&mut self) -> &mut [Word; LIMBS] { + self.as_words_mut() + } +} + +impl AsRef<[Limb]> for Int { + fn as_ref(&self) -> &[Limb] { + self.as_limbs() + } +} + +impl AsMut<[Limb]> for Int { + fn as_mut(&mut self) -> &mut [Limb] { + self.as_limbs_mut() + } +} + +impl ConditionallySelectable for Int { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self(Uint::conditional_select(&a.0, &b.0, choice)) + } +} + +impl Bounded for Int { + const BITS: u32 = Self::BITS; + const BYTES: usize = Self::BYTES; +} + +impl Constants for Int { + const ONE: Self = Self::ONE; + const MAX: Self = Self::MAX; +} + +impl Default for Int { + fn default() -> Self { + Self::ZERO + } +} + +// TODO: impl FixedInteger + +// TODO: impl Integer + +impl ConstZero for Int { + const ZERO: Self = Self::ZERO; +} + +impl num_traits::Zero for Int { + fn zero() -> Self { + Self::ZERO + } + + fn is_zero(&self) -> bool { + self.0.ct_eq(&Self::ZERO.0).into() + } +} + +impl num_traits::One for Int { + fn one() -> Self { + Self::ONE + } + + fn is_one(&self) -> bool { + self.0.ct_eq(&Self::ONE.0).into() + } +} + +impl fmt::Debug for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Int(0x{self:X})") + } +} + +impl fmt::Binary for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Binary::fmt(&self.0, f) + } +} + +impl fmt::Display for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::UpperHex::fmt(self, f) + } +} + +impl fmt::LowerHex for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} + +impl fmt::UpperHex for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::UpperHex::fmt(&self.0, f) + } +} + +#[cfg(feature = "serde")] +impl<'de, const LIMBS: usize> Deserialize<'de> for Int +where + Int: Encoding, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut buffer = Self::ZERO.to_le_bytes(); + serdect::array::deserialize_hex_or_bin(buffer.as_mut(), deserializer)?; + Ok(Self::from_le_bytes(buffer)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Int +where + Int: Encoding, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serdect::array::serialize_hex_lower_or_bin(&Encoding::to_le_bytes(self), serializer) + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use subtle::ConditionallySelectable; + + use crate::{ConstChoice, I128, U128}; + + #[cfg(target_pointer_width = "64")] + #[test] + fn as_words() { + let n = I128::from_be_hex("AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"); + assert_eq!(n.as_words(), &[0xCCCCCCCCDDDDDDDD, 0xAAAAAAAABBBBBBBB]); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn as_words_mut() { + let mut n = I128::from_be_hex("AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"); + assert_eq!(n.as_words_mut(), &[0xCCCCCCCCDDDDDDDD, 0xAAAAAAAABBBBBBBB]); + } + + #[cfg(feature = "alloc")] + #[test] + fn debug() { + let n = I128::from_be_hex("AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"); + + assert_eq!( + format!("{:?}", n), + "Int(0xAAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD)" + ); + } + + #[cfg(feature = "alloc")] + #[test] + fn display() { + let hex = "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"; + let n = I128::from_be_hex(hex); + + use alloc::string::ToString; + assert_eq!(hex, n.to_string()); + + let hex = "AAAAAAAABBBBBBBB0000000000000000"; + let n = I128::from_be_hex(hex); + assert_eq!(hex, n.to_string()); + + let hex = "AAAAAAAABBBBBBBB00000000DDDDDDDD"; + let n = I128::from_be_hex(hex); + assert_eq!(hex, n.to_string()); + + let hex = "AAAAAAAABBBBBBBB0CCCCCCCDDDDDDDD"; + let n = I128::from_be_hex(hex); + assert_eq!(hex, n.to_string()); + } + + #[test] + fn conditional_select() { + let a = I128::from_be_hex("00002222444466668888AAAACCCCEEEE"); + let b = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + + let select_0 = I128::conditional_select(&a, &b, 0.into()); + assert_eq!(a, select_0); + + let select_1 = I128::conditional_select(&a, &b, 1.into()); + assert_eq!(b, select_1); + } + + #[test] + fn is_minimal() { + let min = I128::from_be_hex("80000000000000000000000000000000"); + assert_eq!(min.is_min(), ConstChoice::TRUE); + + let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + assert_eq!(random.is_min(), ConstChoice::FALSE); + } + + #[test] + fn is_maximal() { + let max = I128::from_be_hex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + assert_eq!(max.is_max(), ConstChoice::TRUE); + + let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + assert_eq!(random.is_max(), ConstChoice::FALSE); + } + + #[test] + fn as_uint() { + assert_eq!(*I128::MIN.as_uint(), U128::ONE << 127); + assert_eq!(*I128::MINUS_ONE.as_uint(), U128::MAX); + assert_eq!(*I128::ZERO.as_uint(), U128::ZERO); + assert_eq!(*I128::ONE.as_uint(), U128::ONE); + assert_eq!(*I128::MAX.as_uint(), U128::MAX >> 1); + } +} diff --git a/src/int/add.rs b/src/int/add.rs new file mode 100644 index 000000000..6212c3192 --- /dev/null +++ b/src/int/add.rs @@ -0,0 +1,212 @@ +//! [`Int`] addition operations. + +use core::ops::{Add, AddAssign}; + +use num_traits::WrappingAdd; +use subtle::CtOption; + +use crate::{Checked, CheckedAdd, ConstChoice, ConstCtOption, Int, Wrapping}; + +impl Int { + /// Perform checked addition. Returns `none` when the addition overflowed. + pub const fn checked_add(&self, rhs: &Self) -> ConstCtOption { + let (value, overflow) = self.overflowing_add(rhs); + ConstCtOption::new(value, overflow.not()) + } + + /// Perform `self + rhs`, returns the result, as well as a flag indicating whether the + /// addition overflowed. + pub const fn overflowing_add(&self, rhs: &Self) -> (Self, ConstChoice) { + // Step 1. add operands + let res = Self(self.0.wrapping_add(&rhs.0)); + + // Step 2. determine whether overflow happened. + // Note: + // - overflow can only happen when the inputs have the same sign, and then + // - overflow occurs if and only if the result has the opposite sign of both inputs. + // + // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) + let self_msb = self.is_negative(); + let overflow = self_msb + .eq(rhs.is_negative()) + .and(self_msb.ne(res.is_negative())); + + (res, overflow) + } + + /// Perform wrapping addition, discarding overflow. + pub const fn wrapping_add(&self, rhs: &Self) -> Self { + Self(self.0.wrapping_add(&rhs.0)) + } +} + +impl Add for Int { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + self.add(&rhs) + } +} + +impl Add<&Int> for Int { + type Output = Self; + + fn add(self, rhs: &Self) -> Self { + CtOption::from(self.checked_add(rhs)).expect("attempted to add with overflow") + } +} + +impl AddAssign for Int { + fn add_assign(&mut self, other: Self) { + *self += &other; + } +} + +impl AddAssign<&Int> for Int { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl AddAssign for Wrapping> { + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} + +impl AddAssign<&Wrapping>> for Wrapping> { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl AddAssign for Checked> { + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} + +impl AddAssign<&Checked>> for Checked> { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl CheckedAdd for Int { + fn checked_add(&self, rhs: &Self) -> CtOption { + self.checked_add(rhs).into() + } +} + +impl WrappingAdd for Int { + fn wrapping_add(&self, v: &Self) -> Self { + self.wrapping_add(v) + } +} + +#[cfg(test)] +mod tests { + + #[cfg(test)] + mod tests { + use crate::{I128, U128}; + + #[test] + fn checked_add() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + let max_minus_one = I128 { + 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), + }; + let two = I128 { + 0: U128::from(2u32), + }; + + // lhs = MIN + + let result = I128::MIN.checked_add(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_add(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MIN.checked_add(&I128::MAX); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_add(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MINUS_ONE.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), two.checked_neg().unwrap()); + + let result = I128::MINUS_ONE.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_add(&I128::MAX); + assert_eq!(result.unwrap(), max_minus_one); + + // lhs = 0 + + let result = I128::ZERO.checked_add(&I128::MIN); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::ZERO.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ZERO.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ZERO.checked_add(&I128::MAX); + assert_eq!(result.unwrap(), I128::MAX); + + // lhs = 1 + + let result = I128::ONE.checked_add(&I128::MIN); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::ONE.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), two); + + let result = I128::ONE.checked_add(&I128::MAX); + assert!(bool::from(result.is_none())); + + // lhs = MAX + + let result = I128::MAX.checked_add(&I128::MIN); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MAX.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), max_minus_one); + + let result = I128::MAX.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_add(&I128::ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_add(&I128::MAX); + assert!(bool::from(result.is_none())); + } + } +} diff --git a/src/int/bit_and.rs b/src/int/bit_and.rs new file mode 100644 index 000000000..ff6c750d9 --- /dev/null +++ b/src/int/bit_and.rs @@ -0,0 +1,140 @@ +//! [`Int`] bitwise AND operations. + +use core::ops::{BitAnd, BitAndAssign}; + +use crate::{ConstCtOption, Int, Limb, Uint, Wrapping}; + +impl Int { + /// Computes bitwise `a & b`. + #[inline(always)] + pub const fn bitand(&self, rhs: &Self) -> Self { + Self(Uint::bitand(&self.0, &rhs.0)) + } + + /// Perform bitwise `AND` between `self` and the given [`Limb`], performing the `AND` operation + /// on every limb of `self`. + pub const fn bitand_limb(&self, rhs: Limb) -> Self { + Self(Uint::bitand_limb(&self.0, rhs)) + } + + /// Perform wrapping bitwise `AND`. + /// + /// There's no way wrapping could ever happen. + /// This function exists so that all operations are accounted for in the wrapping operations + pub const fn wrapping_and(&self, rhs: &Self) -> Self { + self.bitand(rhs) + } + + /// Perform checked bitwise `AND`, returning a [`ConstCtOption`] which `is_some` always + pub const fn checked_and(&self, rhs: &Self) -> ConstCtOption { + ConstCtOption::some(self.bitand(rhs)) + } +} + +impl BitAnd for Int { + type Output = Self; + + fn bitand(self, rhs: Self) -> Int { + self.bitand(&rhs) + } +} + +impl BitAnd<&Int> for Int { + type Output = Int; + + #[allow(clippy::needless_borrow)] + fn bitand(self, rhs: &Int) -> Int { + (&self).bitand(rhs) + } +} + +impl BitAnd> for &Int { + type Output = Int; + + fn bitand(self, rhs: Int) -> Int { + self.bitand(&rhs) + } +} + +impl BitAnd<&Int> for &Int { + type Output = Int; + + fn bitand(self, rhs: &Int) -> Int { + self.bitand(rhs) + } +} + +impl BitAndAssign for Int { + #[allow(clippy::assign_op_pattern)] + fn bitand_assign(&mut self, other: Self) { + *self = *self & other; + } +} + +impl BitAndAssign<&Int> for Int { + #[allow(clippy::assign_op_pattern)] + fn bitand_assign(&mut self, other: &Self) { + *self = *self & other; + } +} + +impl BitAnd for Wrapping> { + type Output = Self; + + fn bitand(self, rhs: Self) -> Wrapping> { + Wrapping(self.0.bitand(&rhs.0)) + } +} + +impl BitAnd<&Wrapping>> for Wrapping> { + type Output = Wrapping>; + + fn bitand(self, rhs: &Wrapping>) -> Wrapping> { + Wrapping(self.0.bitand(&rhs.0)) + } +} + +impl BitAnd>> for &Wrapping> { + type Output = Wrapping>; + + fn bitand(self, rhs: Wrapping>) -> Wrapping> { + Wrapping(self.0.bitand(&rhs.0)) + } +} + +impl BitAnd<&Wrapping>> for &Wrapping> { + type Output = Wrapping>; + + fn bitand(self, rhs: &Wrapping>) -> Wrapping> { + Wrapping(self.0.bitand(&rhs.0)) + } +} + +impl BitAndAssign for Wrapping> { + #[allow(clippy::assign_op_pattern)] + fn bitand_assign(&mut self, other: Self) { + *self = *self & other; + } +} + +impl BitAndAssign<&Wrapping>> for Wrapping> { + #[allow(clippy::assign_op_pattern)] + fn bitand_assign(&mut self, other: &Self) { + *self = *self & other; + } +} + +#[cfg(test)] +mod tests { + use crate::I128; + + #[test] + fn checked_and_ok() { + assert_eq!(I128::ZERO.checked_and(&I128::ONE).unwrap(), I128::ZERO); + } + + #[test] + fn overlapping_and_ok() { + assert_eq!(I128::MAX.wrapping_and(&I128::ONE), I128::ONE); + } +} diff --git a/src/int/bit_not.rs b/src/int/bit_not.rs new file mode 100644 index 000000000..f74d9df41 --- /dev/null +++ b/src/int/bit_not.rs @@ -0,0 +1,42 @@ +//! [`Int`] bitwise NOT operations. + +use core::ops::Not; + +use crate::{Uint, Wrapping}; + +use super::Int; + +impl Int { + /// Computes bitwise `!a`. + #[inline(always)] + pub const fn not(&self) -> Self { + Self(Uint::not(&self.0)) + } +} + +impl Not for Int { + type Output = Self; + + fn not(self) -> Self { + Self::not(&self) + } +} + +impl Not for Wrapping> { + type Output = Self; + + fn not(self) -> ::Output { + Wrapping(self.0.not()) + } +} + +#[cfg(test)] +mod tests { + use crate::I128; + + #[test] + fn bitnot_ok() { + assert_eq!(I128::ZERO.not(), I128::MINUS_ONE); + assert_eq!(I128::MAX.not(), I128::MIN); + } +} diff --git a/src/int/bit_or.rs b/src/int/bit_or.rs new file mode 100644 index 000000000..baccb3b85 --- /dev/null +++ b/src/int/bit_or.rs @@ -0,0 +1,132 @@ +//! [`Int`] bitwise OR operations. + +use core::ops::{BitOr, BitOrAssign}; + +use crate::{ConstCtOption, Uint, Wrapping}; + +use super::Int; + +impl Int { + /// Computes bitwise `a & b`. + #[inline(always)] + pub const fn bitor(&self, rhs: &Self) -> Self { + Self(Uint::bitor(&self.0, &rhs.0)) + } + + /// Perform wrapping bitwise `OR`. + /// + /// There's no way wrapping could ever happen. + /// This function exists so that all operations are accounted for in the wrapping operations + pub const fn wrapping_or(&self, rhs: &Self) -> Self { + self.bitor(rhs) + } + + /// Perform checked bitwise `OR`, returning a [`ConstCtOption`] which `is_some` always + pub const fn checked_or(&self, rhs: &Self) -> ConstCtOption { + ConstCtOption::some(self.bitor(rhs)) + } +} + +impl BitOr for Int { + type Output = Self; + + fn bitor(self, rhs: Self) -> Int { + self.bitor(&rhs) + } +} + +impl BitOr<&Int> for Int { + type Output = Int; + + #[allow(clippy::needless_borrow)] + fn bitor(self, rhs: &Int) -> Int { + (&self).bitor(rhs) + } +} + +impl BitOr> for &Int { + type Output = Int; + + fn bitor(self, rhs: Int) -> Int { + self.bitor(&rhs) + } +} + +impl BitOr<&Int> for &Int { + type Output = Int; + + fn bitor(self, rhs: &Int) -> Int { + self.bitor(rhs) + } +} + +impl BitOrAssign for Int { + fn bitor_assign(&mut self, other: Self) { + *self = *self | other; + } +} + +impl BitOrAssign<&Int> for Int { + fn bitor_assign(&mut self, other: &Self) { + *self = *self | other; + } +} + +impl BitOr for Wrapping> { + type Output = Self; + + fn bitor(self, rhs: Self) -> Wrapping> { + Wrapping(self.0.bitor(&rhs.0)) + } +} + +impl BitOr<&Wrapping>> for Wrapping> { + type Output = Wrapping>; + + fn bitor(self, rhs: &Wrapping>) -> Wrapping> { + Wrapping(self.0.bitor(&rhs.0)) + } +} + +impl BitOr>> for &Wrapping> { + type Output = Wrapping>; + + fn bitor(self, rhs: Wrapping>) -> Wrapping> { + Wrapping(self.0.bitor(&rhs.0)) + } +} + +impl BitOr<&Wrapping>> for &Wrapping> { + type Output = Wrapping>; + + fn bitor(self, rhs: &Wrapping>) -> Wrapping> { + Wrapping(self.0.bitor(&rhs.0)) + } +} + +impl BitOrAssign for Wrapping> { + fn bitor_assign(&mut self, other: Self) { + *self = *self | other; + } +} + +impl BitOrAssign<&Wrapping>> for Wrapping> { + fn bitor_assign(&mut self, other: &Self) { + *self = *self | other; + } +} + +#[cfg(test)] +mod tests { + use crate::I128; + + #[test] + fn checked_or_ok() { + assert_eq!(I128::ZERO.checked_or(&I128::ONE).unwrap(), I128::ONE); + } + + #[test] + fn overlapping_or_ok() { + assert_eq!(I128::MAX.wrapping_or(&I128::ONE), I128::MAX); + } +} diff --git a/src/int/bit_xor.rs b/src/int/bit_xor.rs new file mode 100644 index 000000000..eae4e593e --- /dev/null +++ b/src/int/bit_xor.rs @@ -0,0 +1,132 @@ +//! [`Int`] bitwise XOR operations. + +use core::ops::{BitXor, BitXorAssign}; + +use crate::{ConstCtOption, Uint, Wrapping}; + +use super::Int; + +impl Int { + /// Computes bitwise `a ^ b`. + #[inline(always)] + pub const fn bitxor(&self, rhs: &Self) -> Self { + Self(Uint::bitxor(&self.0, &rhs.0)) + } + + /// Perform wrapping bitwise `XOR`. + /// + /// There's no way wrapping could ever happen. + /// This function exists so that all operations are accounted for in the wrapping operations + pub const fn wrapping_xor(&self, rhs: &Self) -> Self { + self.bitxor(rhs) + } + + /// Perform checked bitwise `XOR`, returning a [`ConstCtOption`] which `is_some` always + pub fn checked_xor(&self, rhs: &Self) -> ConstCtOption { + ConstCtOption::some(self.bitxor(rhs)) + } +} + +impl BitXor for Int { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Int { + self.bitxor(&rhs) + } +} + +impl BitXor<&Int> for Int { + type Output = Int; + + #[allow(clippy::needless_borrow)] + fn bitxor(self, rhs: &Int) -> Int { + (&self).bitxor(rhs) + } +} + +impl BitXor> for &Int { + type Output = Int; + + fn bitxor(self, rhs: Int) -> Int { + self.bitxor(&rhs) + } +} + +impl BitXor<&Int> for &Int { + type Output = Int; + + fn bitxor(self, rhs: &Int) -> Int { + self.bitxor(rhs) + } +} + +impl BitXorAssign for Int { + fn bitxor_assign(&mut self, other: Self) { + *self = *self ^ other; + } +} + +impl BitXorAssign<&Int> for Int { + fn bitxor_assign(&mut self, other: &Self) { + *self = *self ^ other; + } +} + +impl BitXor for Wrapping> { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Wrapping> { + Wrapping(self.0.bitxor(&rhs.0)) + } +} + +impl BitXor<&Wrapping>> for Wrapping> { + type Output = Wrapping>; + + fn bitxor(self, rhs: &Wrapping>) -> Wrapping> { + Wrapping(self.0.bitxor(&rhs.0)) + } +} + +impl BitXor>> for &Wrapping> { + type Output = Wrapping>; + + fn bitxor(self, rhs: Wrapping>) -> Wrapping> { + Wrapping(self.0.bitxor(&rhs.0)) + } +} + +impl BitXor<&Wrapping>> for &Wrapping> { + type Output = Wrapping>; + + fn bitxor(self, rhs: &Wrapping>) -> Wrapping> { + Wrapping(self.0.bitxor(&rhs.0)) + } +} + +impl BitXorAssign for Wrapping> { + fn bitxor_assign(&mut self, other: Self) { + *self = *self ^ other; + } +} + +impl BitXorAssign<&Wrapping>> for Wrapping> { + fn bitxor_assign(&mut self, other: &Self) { + *self = *self ^ other; + } +} + +#[cfg(test)] +mod tests { + use crate::I128; + + #[test] + fn checked_xor_ok() { + assert_eq!(I128::ZERO.checked_xor(&I128::ONE).unwrap(), I128::ONE); + } + + #[test] + fn overlapping_xor_ok() { + assert_eq!(I128::ZERO.wrapping_xor(&I128::ONE), I128::ONE); + } +} diff --git a/src/int/cmp.rs b/src/int/cmp.rs new file mode 100644 index 000000000..63a8b2201 --- /dev/null +++ b/src/int/cmp.rs @@ -0,0 +1,197 @@ +//! [`Int`] comparisons. +//! +//! By default, these are all constant-time. + +use core::cmp::Ordering; + +use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; + +use crate::{ConstChoice, Int, Uint}; + +impl Int { + /// Return `b` if `c` is truthy, otherwise return `a`. + #[inline] + pub(crate) const fn select(a: &Self, b: &Self, c: ConstChoice) -> Self { + Self(Uint::select(&a.0, &b.0, c)) + } + + /// Returns the truthy value if `self`!=0 or the falsy value otherwise. + #[inline] + pub(crate) const fn is_nonzero(&self) -> ConstChoice { + Uint::is_nonzero(&self.0) + } + + /// Returns the truthy value if `self == rhs` or the falsy value otherwise. + #[inline] + pub(crate) const fn eq(lhs: &Self, rhs: &Self) -> ConstChoice { + Uint::eq(&lhs.0, &rhs.0) + } + + /// Returns the truthy value if `self < rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn lt(lhs: &Self, rhs: &Self) -> ConstChoice { + Uint::lt(&lhs.invert_msb().0, &rhs.invert_msb().0) + } + + /// Returns the truthy value if `self > rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn gt(lhs: &Self, rhs: &Self) -> ConstChoice { + Uint::gt(&lhs.invert_msb().0, &rhs.invert_msb().0) + } + + /// Returns the ordering between `self` and `rhs` as an i8. + /// Values correspond to the Ordering enum: + /// -1 is Less + /// 0 is Equal + /// 1 is Greater + #[inline] + pub(crate) const fn cmp(lhs: &Self, rhs: &Self) -> i8 { + Uint::cmp(&lhs.invert_msb().0, &rhs.invert_msb().0) + } + + /// Returns the Ordering between `self` and `rhs` in variable time. + pub const fn cmp_vartime(&self, rhs: &Self) -> Ordering { + self.invert_msb().0.cmp_vartime(&rhs.invert_msb().0) + } +} + +impl ConstantTimeEq for Int { + #[inline] + fn ct_eq(&self, other: &Self) -> Choice { + Int::eq(self, other).into() + } +} + +impl ConstantTimeGreater for Int { + #[inline] + fn ct_gt(&self, other: &Self) -> Choice { + Int::gt(self, other).into() + } +} + +impl ConstantTimeLess for Int { + #[inline] + fn ct_lt(&self, other: &Self) -> Choice { + Int::lt(self, other).into() + } +} + +impl Eq for Int {} + +impl Ord for Int { + fn cmp(&self, other: &Self) -> Ordering { + let c = Self::cmp(self, other); + match c { + -1 => Ordering::Less, + 0 => Ordering::Equal, + _ => Ordering::Greater, + } + } +} + +impl PartialOrd for Int { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for Int { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +#[cfg(test)] +mod tests { + use subtle::{ConstantTimeGreater, ConstantTimeLess}; + + use crate::I128; + + #[test] + fn test_is_nonzero() { + assert_eq!(I128::MAX.is_nonzero().to_u8(), 1u8); + assert_eq!(I128::ONE.is_nonzero().to_u8(), 1u8); + assert_eq!(I128::ZERO.is_nonzero().to_u8(), 0u8); + assert_eq!(I128::MINUS_ONE.is_nonzero().to_u8(), 1u8); + assert_eq!(I128::MAX.is_nonzero().to_u8(), 1u8); + } + + #[test] + fn test_eq() { + assert_eq!(I128::ZERO, I128::ONE.wrapping_add(&I128::MINUS_ONE)); + assert_eq!(I128::ONE, I128::ZERO.wrapping_add(&I128::ONE)); + assert_ne!(I128::ONE, I128::ZERO); + } + + #[test] + fn test_gt() { + // x > y + assert!(I128::MAX > I128::ONE); + assert!(I128::ONE > I128::ZERO); + assert!(I128::ZERO > I128::MINUS_ONE); + assert!(I128::MINUS_ONE > I128::MIN); + assert!(I128::MAX > I128::MIN); + + // a !> a + assert_ne!(true, I128::MAX > I128::MAX); + assert_ne!(true, I128::ONE > I128::ONE); + assert_ne!(true, I128::ZERO > I128::ZERO); + assert_ne!(true, I128::MINUS_ONE > I128::MINUS_ONE); + assert_ne!(true, I128::MIN > I128::MIN); + } + + #[test] + fn test_lt() { + // x < y + assert!(I128::ONE < I128::MAX); + assert!(I128::ZERO < I128::ONE); + assert!(I128::MINUS_ONE < I128::ZERO); + assert!(I128::MIN < I128::MINUS_ONE); + assert!(I128::MIN < I128::MAX); + + // a !< a + assert_ne!(true, I128::MAX < I128::MAX); + assert_ne!(true, I128::ONE < I128::ONE); + assert_ne!(true, I128::ZERO < I128::ZERO); + assert_ne!(true, I128::MINUS_ONE < I128::MINUS_ONE); + assert_ne!(true, I128::MIN < I128::MIN); + } + + #[test] + fn ct_gt() { + let a = I128::MIN; + let b = I128::ZERO; + let c = I128::MAX; + + assert!(bool::from(b.ct_gt(&a))); + assert!(bool::from(c.ct_gt(&a))); + assert!(bool::from(c.ct_gt(&b))); + + assert!(!bool::from(a.ct_gt(&a))); + assert!(!bool::from(b.ct_gt(&b))); + assert!(!bool::from(c.ct_gt(&c))); + + assert!(!bool::from(a.ct_gt(&b))); + assert!(!bool::from(a.ct_gt(&c))); + assert!(!bool::from(b.ct_gt(&c))); + } + + #[test] + fn ct_lt() { + let a = I128::ZERO; + let b = I128::ONE; + let c = I128::MAX; + + assert!(bool::from(a.ct_lt(&b))); + assert!(bool::from(a.ct_lt(&c))); + assert!(bool::from(b.ct_lt(&c))); + + assert!(!bool::from(a.ct_lt(&a))); + assert!(!bool::from(b.ct_lt(&b))); + assert!(!bool::from(c.ct_lt(&c))); + + assert!(!bool::from(b.ct_lt(&a))); + assert!(!bool::from(c.ct_lt(&a))); + assert!(!bool::from(c.ct_lt(&b))); + } +} diff --git a/src/int/div.rs b/src/int/div.rs new file mode 100644 index 000000000..86aec7830 --- /dev/null +++ b/src/int/div.rs @@ -0,0 +1,336 @@ +//! [`Int`] division operations. + +use core::ops::Div; + +use subtle::{Choice, CtOption}; + +use crate::{CheckedDiv, ConstChoice, ConstCtOption, Int, NonZero, Uint}; + +impl Int { + #[inline] + /// Base div_rem operation. + /// + /// Given `(a, b)`, computes the quotient and remainder of their absolute values. Furthermore, + /// returns the signs of `a` and `b`. + const fn div_rem_base( + &self, + rhs: &NonZero, + ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice, ConstChoice) { + // Step 1: split operands into signs and magnitudes. + let (lhs_mag, lhs_sgn) = self.abs_sign(); + let (rhs_mag, rhs_sgn) = rhs.abs_sign(); + + // Step 2. Divide magnitudes + // safe to unwrap since rhs is NonZero. + let (quotient, remainder) = lhs_mag.div_rem(&rhs_mag); + + (quotient, remainder, lhs_sgn, rhs_sgn) + } + + /// + /// Example: + /// ``` + /// use crypto_bigint::{I128, NonZero}; + /// let (quotient, remainder) = I128::from(8).checked_div_rem(&I128::from(3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(2)); + /// assert_eq!(remainder.unwrap(), I128::from(2)); + /// + /// let (quotient, remainder) = I128::from(-8).checked_div_rem(&I128::from(3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(-2)); + /// assert_eq!(remainder.unwrap(), I128::from(-2)); + /// + /// let (quotient, remainder) = I128::from(8).checked_div_rem(&I128::from(-3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(-2)); + /// assert_eq!(remainder.unwrap(), I128::from(2)); + /// + /// let (quotient, remainder) = I128::from(-8).checked_div_rem(&I128::from(-3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(2)); + /// assert_eq!(remainder.unwrap(), I128::from(-2)); + /// ``` + pub const fn checked_div_rem( + &self, + rhs: &NonZero, + ) -> (ConstCtOption, ConstCtOption) { + let (quotient, remainder, lhs_sgn, rhs_sgn) = self.div_rem_base(rhs); + let opposing_signs = lhs_sgn.ne(rhs_sgn); + ( + Self::new_from_abs_sign(quotient, opposing_signs), + Self::new_from_abs_sign(remainder, lhs_sgn), + ) + } + + /// Perform checked division, returning a [`CtOption`] which `is_some` only if the rhs != 0. + /// Note: this operation rounds towards zero, truncating any fractional part of the exact result. + pub fn checked_div(&self, rhs: &Self) -> CtOption { + NonZero::new(*rhs).and_then(|rhs| self.checked_div_rem(&rhs).0.into()) + } + + /// Perform checked division, returning a [`CtOption`] which `is_some` only if the rhs != 0. + /// Note: this operation rounds down. + /// + /// Example: + /// ``` + /// use crypto_bigint::I128; + /// assert_eq!( + /// I128::from(8).checked_div_floor(&I128::from(3)).unwrap(), + /// I128::from(2) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_floor(&I128::from(3)).unwrap(), + /// I128::from(-3) + /// ); + /// assert_eq!( + /// I128::from(8).checked_div_floor(&I128::from(-3)).unwrap(), + /// I128::from(-3) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_floor(&I128::from(-3)).unwrap(), + /// I128::from(2) + /// ) + /// ``` + pub fn checked_div_floor(&self, rhs: &Self) -> CtOption { + NonZero::new(*rhs).and_then(|rhs| { + let (quotient, remainder, lhs_sgn, rhs_sgn) = self.div_rem_base(&rhs); + + // Increment the quotient when + // - lhs and rhs have opposing signs, and + // - there is a non-zero remainder. + let opposing_signs = lhs_sgn.ne(rhs_sgn); + let increment_quotient = remainder.is_nonzero().and(opposing_signs); + let quotient_sub_one = quotient.wrapping_add(&Uint::ONE); + let quotient = Uint::select("ient, "ient_sub_one, increment_quotient); + + Self::new_from_abs_sign(quotient, opposing_signs).into() + }) + } + + /// Perform checked division and mod, returning a [`CtOption`] which `is_some` only + /// if the rhs != 0. + /// Note: this operation rounds down. + /// + /// Example: + /// ``` + /// use crypto_bigint::I128; + /// assert_eq!( + /// I128::from(8).checked_div_mod_floor(&I128::from(3)).unwrap(), + /// (I128::from(2), I128::from(2)) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_mod_floor(&I128::from(3)).unwrap(), + /// (I128::from(-3), I128::from(-1)) + /// ); + /// assert_eq!( + /// I128::from(8).checked_div_mod_floor(&I128::from(-3)).unwrap(), + /// (I128::from(-3), I128::from(-1)) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_mod_floor(&I128::from(-3)).unwrap(), + /// (I128::from(2), I128::from(2)) + /// ); + /// ``` + pub fn checked_div_mod_floor(&self, rhs: &Self) -> CtOption<(Self, Self)> { + let (lhs_mag, lhs_sgn) = self.abs_sign(); + let (rhs_mag, rhs_sgn) = rhs.abs_sign(); + let opposing_signs = lhs_sgn.xor(rhs_sgn); + NonZero::new(rhs_mag).and_then(|rhs_mag| { + let (quotient, remainder) = lhs_mag.div_rem(&rhs_mag); + + // Increase the quotient by one when lhs and rhs have opposing signs and there + // is a non-zero remainder. + let modify = remainder.is_nonzero().and(opposing_signs); + let quotient_sub_one = quotient.wrapping_add(&Uint::ONE); + let quotient = Uint::select("ient, "ient_sub_one, modify); + + // Invert the remainder and add one to remainder when lhs and rhs have opposing signs + // and the remainder is non-zero. + let inv_remainder = rhs_mag.wrapping_sub(&remainder); + let remainder = Uint::select(&remainder, &inv_remainder, modify); + + CtOption::from(Int::new_from_abs_sign(quotient, opposing_signs)).and_then(|quotient| { + CtOption::from(Int::new_from_abs_sign(remainder, opposing_signs)) + .and_then(|remainder| CtOption::new((quotient, remainder), Choice::from(1u8))) + }) + }) + } +} + +impl CheckedDiv for Int { + fn checked_div(&self, rhs: &Int) -> CtOption { + self.checked_div(rhs) + } +} + +impl Div<&NonZero>> for &Int { + type Output = CtOption>; + + fn div(self, rhs: &NonZero>) -> Self::Output { + *self / *rhs + } +} + +impl Div<&NonZero>> for Int { + type Output = CtOption>; + + fn div(self, rhs: &NonZero>) -> Self::Output { + self / *rhs + } +} + +impl Div>> for &Int { + type Output = CtOption>; + + fn div(self, rhs: NonZero>) -> Self::Output { + *self / rhs + } +} + +impl Div>> for Int { + type Output = CtOption>; + + fn div(self, rhs: NonZero>) -> Self::Output { + self.checked_div(&rhs) + } +} + +#[cfg(test)] +mod tests { + use crate::{Int, I128}; + + #[test] + fn test_checked_div() { + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + // lhs = min + + let result = I128::MIN.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::MIN.checked_div(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::MINUS_ONE.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::MINUS_ONE.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = 0 + + let result = I128::ZERO.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::ZERO.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = 1 + + let result = I128::ONE.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ONE.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::ONE.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = max + + let result = I128::MAX.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MAX.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MAX.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ONE); + } + + #[test] + fn test_checked_div_floor() { + assert_eq!( + I128::from(8).checked_div_floor(&I128::from(3)).unwrap(), + I128::from(2) + ); + assert_eq!( + I128::from(-8).checked_div_floor(&I128::from(3)).unwrap(), + I128::from(-3) + ); + assert_eq!( + I128::from(8).checked_div_floor(&I128::from(-3)).unwrap(), + I128::from(-3) + ); + assert_eq!( + I128::from(-8).checked_div_floor(&I128::from(-3)).unwrap(), + I128::from(2) + ); + } + + #[test] + fn test_checked_div_mod_floor() { + assert_eq!( + I128::from(8).checked_div_mod_floor(&I128::from(3)).unwrap(), + (I128::from(2), I128::from(2)) + ); + assert_eq!( + I128::from(-8) + .checked_div_mod_floor(&I128::from(3)) + .unwrap(), + (I128::from(-3), I128::from(-1)) + ); + assert_eq!( + I128::from(8) + .checked_div_mod_floor(&I128::from(-3)) + .unwrap(), + (I128::from(-3), I128::from(-1)) + ); + assert_eq!( + I128::from(-8) + .checked_div_mod_floor(&I128::from(-3)) + .unwrap(), + (I128::from(2), I128::from(2)) + ); + } +} diff --git a/src/int/encoding.rs b/src/int/encoding.rs new file mode 100644 index 000000000..1bbe30c88 --- /dev/null +++ b/src/int/encoding.rs @@ -0,0 +1,14 @@ +//! Const-friendly decoding/encoding operations for [`Int`]. + +use crate::{Int, Uint}; + +impl Int { + /// Create a new [`Int`] from the provided big endian hex string. + /// + /// Panics if the hex is malformed or not zero-padded accordingly for the size. + /// + /// See [`Uint::from_be_hex`] for more details. + pub const fn from_be_hex(hex: &str) -> Self { + Self(Uint::from_be_hex(hex)) + } +} diff --git a/src/int/from.rs b/src/int/from.rs new file mode 100644 index 000000000..63dddfcef --- /dev/null +++ b/src/int/from.rs @@ -0,0 +1,158 @@ +//! `From`-like conversions for [`Int`]. + +use crate::{Int, Limb, Uint, Word, I128, I64}; + +impl Int { + /// Create a [`Int`] from an `i8` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i8(n: i8) -> Self { + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Uint::new([Limb(n as Word)]).as_int().resize() + } + + /// Create a [`Int`] from an `i16` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i16(n: i16) -> Self { + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Uint::new([Limb(n as Word)]).as_int().resize() + } + + /// Create a [`Int`] from an `i32` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i32(n: i32) -> Self { + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Uint::new([Limb(n as Word)]).as_int().resize() + } + + /// Create a [`Int`] from an `i64` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + #[cfg(target_pointer_width = "32")] + pub const fn from_i64(n: i64) -> Self { + Uint::<{ I64::LIMBS }>::from_u64(n as u64).as_int().resize() + } + + /// Create a [`Int`] from an `i64` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + #[cfg(target_pointer_width = "64")] + pub const fn from_i64(n: i64) -> Self { + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Uint::new([Limb(n as Word)]).as_int().resize() + } + + /// Create a [`Int`] from an `i128` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i128(n: i128) -> Self { + Uint::<{ I128::LIMBS }>::from_u128(n as u128) + .as_int() + .resize() + } +} + +impl From for Int { + fn from(n: i8) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS > 0, "limbs must be non-zero"); + Self::from_i8(n) + } +} + +impl From for Int { + fn from(n: i16) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS > 0, "limbs must be non-zero"); + Self::from_i16(n) + } +} + +impl From for Int { + fn from(n: i32) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS > 0, "limbs must be non-zero"); + Self::from_i32(n) + } +} + +impl From for Int { + fn from(n: i64) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS >= 8 / Limb::BYTES, "not enough limbs"); + Self::from_i64(n) + } +} + +impl From for Int { + fn from(n: i128) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS >= 16 / Limb::BYTES, "not enough limbs"); + Self::from_i128(n) + } +} + +impl From for i64 { + fn from(n: I64) -> i64 { + u64::from(n.0) as i64 + } +} + +impl From for i128 { + fn from(n: I128) -> i128 { + u128::from(n.0) as i128 + } +} + +impl From<&Int> for Int { + fn from(num: &Int) -> Int { + num.resize() + } +} + +#[cfg(test)] +mod tests { + #[cfg(target_pointer_width = "64")] + use crate::I128 as IntEx; + #[cfg(target_pointer_width = "32")] + use crate::I64 as IntEx; + use crate::{Limb, I128}; + + #[test] + fn from_i8() { + let n = IntEx::from(42i8); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i8); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); + } + + #[test] + fn from_i16() { + let n = IntEx::from(42i16); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i16); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); + } + + #[test] + fn from_i32() { + let n = IntEx::from(42i32); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i32); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); + } + + #[test] + fn from_i64() { + let n = IntEx::from(42i64); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i64); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); + } + + #[test] + fn from_i128() { + let n = I128::from(42i128); + assert_eq!(&n.as_limbs()[..2], &[Limb(42), Limb(0)]); + assert_eq!(i128::from(n), 42i128); + let n = I128::from(-42i128); + assert_eq!(&n.as_limbs()[..2], &[Limb::MAX - Limb(41), Limb::MAX]); + assert_eq!(i128::from(n), -42i128); + } +} diff --git a/src/int/mul.rs b/src/int/mul.rs new file mode 100644 index 000000000..a36944b77 --- /dev/null +++ b/src/int/mul.rs @@ -0,0 +1,285 @@ +//! [`Int`] multiplication operations. + +use core::ops::{Mul, MulAssign}; + +use subtle::CtOption; + +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, Int, Uint, Zero}; + +impl Int { + /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. + /// The `(lo, hi)` components contain the _magnitude of the product_, with sizes + /// corresponding to the sizes of the operands; `negate` indicates whether the result should be + /// negated when converted from `Uint` to `Int`. Note: even if `negate` is truthy, the magnitude + /// might be zero! + pub const fn split_mul( + &self, + rhs: &Int, + ) -> (Uint<{ LIMBS }>, Uint<{ RHS_LIMBS }>, ConstChoice) { + // Step 1: split operands into their signs and magnitudes. + let (lhs_abs, lhs_sgn) = self.abs_sign(); + let (rhs_abs, rhs_sgn) = rhs.abs_sign(); + + // Step 2: multiply the magnitudes + let (lo, hi) = lhs_abs.split_mul(&rhs_abs); + + // Step 3. Determine if the result should be negated. + // This should be done if and only if lhs and rhs have opposing signs. + // Note: if either operand is zero, the resulting magnitude will also be zero. Negating + // zero, however, still yields zero, so having a truthy `negate` in that scenario is OK. + let negate = lhs_sgn.xor(rhs_sgn); + + (lo, hi, negate) + } + + /// Multiply `self` by `rhs`, returning a concatenated "wide" result. + pub const fn widening_mul( + &self, + rhs: &Int, + ) -> Int + where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let (lhs_abs, lhs_sign) = self.abs_sign(); + let (rhs_abs, rhs_sign) = rhs.abs_sign(); + let product_abs = lhs_abs.widening_mul(&rhs_abs); + let product_sign = lhs_sign.xor(rhs_sign); + + // always fits + Int::from_bits(product_abs.wrapping_neg_if(product_sign)) + } +} + +impl CheckedMul> for Int { + #[inline] + fn checked_mul(&self, rhs: &Int) -> CtOption { + let (lo, hi, is_negative) = self.split_mul(rhs); + let val = Self::new_from_abs_sign(lo, is_negative); + CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) + } +} + +impl Mul> for Int { + type Output = Int; + + fn mul(self, rhs: Int) -> Self { + self.mul(&rhs) + } +} + +impl Mul<&Int> for Int { + type Output = Int; + + fn mul(self, rhs: &Int) -> Self { + (&self).mul(rhs) + } +} + +impl Mul> for &Int { + type Output = Int; + + fn mul(self, rhs: Int) -> Self::Output { + self.mul(&rhs) + } +} + +impl Mul<&Int> for &Int { + type Output = Int; + + fn mul(self, rhs: &Int) -> Self::Output { + self.checked_mul(rhs) + .expect("attempted to multiply with overflow") + } +} + +impl MulAssign>> for Checked> { + fn mul_assign(&mut self, other: Checked>) { + *self = *self * other; + } +} + +impl MulAssign<&Checked>> for Checked> { + fn mul_assign(&mut self, other: &Checked>) { + *self = *self * other; + } +} + +// TODO(lleoha): unfortunately we cannot satisfy this (yet!). +// impl +// WideningMul> for Int +// where +// Uint: ConcatMixed, MixedOutput = Uint>, +// { +// type Output = Int; +// +// #[inline] +// fn widening_mul(&self, rhs: Int) -> Self::Output { +// self.widening_mul(&rhs) +// } +// } + +#[cfg(test)] +mod tests { + use crate::{CheckedMul, Int, I128, I256}; + + #[test] + fn test_checked_mul() { + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + // lhs = min + + let result = I128::MIN.checked_mul(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_mul(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MIN.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_mul(&I128::MAX); + assert!(bool::from(result.is_none())); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_mul(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MINUS_ONE.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::MINUS_ONE.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_mul(&I128::MAX); + assert_eq!(result.unwrap(), min_plus_one); + + // lhs = 0 + + let result = I128::ZERO.checked_mul(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = 1 + + let result = I128::ONE.checked_mul(&I128::MIN); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::ONE.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ONE.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_mul(&I128::MAX); + assert_eq!(result.unwrap(), I128::MAX); + + // lhs = max + + let result = I128::MAX.checked_mul(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MAX.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MAX.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_mul(&I128::MAX); + assert!(bool::from(result.is_none())); + } + + #[test] + fn test_widening_mul() { + assert_eq!( + I128::MIN.widening_mul(&I128::MIN), + I256::from_be_hex("4000000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!( + I128::MIN.widening_mul(&I128::MINUS_ONE), + I256::from_be_hex("0000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!(I128::MIN.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!( + I128::MIN.widening_mul(&I128::ONE), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000000") + ); + assert_eq!( + I128::MIN.widening_mul(&I128::MAX), + I256::from_be_hex("C000000000000000000000000000000080000000000000000000000000000000") + ); + + assert_eq!( + I128::MINUS_ONE.widening_mul(&I128::MIN), + I256::from_be_hex("0000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::MINUS_ONE), I256::ONE); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::ONE), I256::MINUS_ONE); + assert_eq!( + I128::MINUS_ONE.widening_mul(&I128::MAX), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000001") + ); + + assert_eq!(I128::ZERO.widening_mul(&I128::MIN), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::MINUS_ONE), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::ONE), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::MAX), I256::ZERO); + + assert_eq!( + I128::ONE.widening_mul(&I128::MIN), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000000") + ); + assert_eq!(I128::ONE.widening_mul(&I128::MINUS_ONE), I256::MINUS_ONE); + assert_eq!(I128::ONE.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::ONE.widening_mul(&I128::ONE), I256::ONE); + assert_eq!( + I128::ONE.widening_mul(&I128::MAX), + I256::from_be_hex("000000000000000000000000000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + + assert_eq!( + I128::MAX.widening_mul(&I128::MIN), + I256::from_be_hex("C000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!( + I128::MAX.widening_mul(&I128::MINUS_ONE), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000001") + ); + assert_eq!(I128::MAX.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!( + I128::MAX.widening_mul(&I128::ONE), + I256::from_be_hex("000000000000000000000000000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + assert_eq!( + I128::MAX.widening_mul(&I128::MAX), + I256::from_be_hex("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000001") + ); + } +} diff --git a/src/int/neg.rs b/src/int/neg.rs new file mode 100644 index 000000000..0556a2823 --- /dev/null +++ b/src/int/neg.rs @@ -0,0 +1,117 @@ +//! [`Int`] negation-related operations. + +use crate::{ConstChoice, ConstCtOption, Int, Uint}; + +impl Int { + /// Map this [`Int`] to its two's-complement negation: + /// map `self` to `(self ^ 1111...1111) + 0000...0001`. + /// + /// Returns the negation, as well as whether the operation overflowed. + /// The operation overflows when attempting to negate [`Int::MIN`]; the positive counterpart + /// of this value cannot be represented. + pub const fn overflowing_neg(&self) -> (Self, ConstChoice) { + Self(self.0.bitxor(&Uint::MAX)).overflowing_add(&Int::ONE) + } + + /// Wrapping negate this [`Int`]. + /// + /// Warning: this operation maps [`Int::MIN`] to itself, since the positive counterpart of this + /// value cannot be represented. + pub const fn wrapping_neg(&self) -> Self { + self.overflowing_neg().0 + } + + /// Wrapping negate this [`Int`] if `negate` is truthy; otherwise do nothing. + /// + /// Warning: this operation maps [`Int::MIN`] to itself, since the positive counterpart of this + /// value cannot be represented. + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Int { + Self(self.0.wrapping_neg_if(negate)) + } + + /// Negate this [`Int`]. + /// + /// Yields `None` when `self == Self::MIN`, since the positive counterpart of this value cannot + /// be represented. + pub const fn checked_neg(&self) -> ConstCtOption { + let (value, overflow) = self.overflowing_neg(); + ConstCtOption::new(value, overflow.not()) + } +} + +#[cfg(test)] +mod tests { + use crate::{ConstChoice, I128}; + + #[test] + fn overflowing_neg() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + let (res, overflow) = I128::MIN.overflowing_neg(); + assert_eq!(res, I128::MIN); + assert_eq!(overflow, ConstChoice::TRUE); + + let (res, overflow) = I128::MINUS_ONE.overflowing_neg(); + assert_eq!(res, I128::ONE); + assert_eq!(overflow, ConstChoice::FALSE); + + let (res, overflow) = I128::ZERO.overflowing_neg(); + assert_eq!(res, I128::ZERO); + assert_eq!(overflow, ConstChoice::FALSE); + + let (res, overflow) = I128::ONE.overflowing_neg(); + assert_eq!(res, I128::MINUS_ONE); + assert_eq!(overflow, ConstChoice::FALSE); + + let (res, overflow) = I128::MAX.overflowing_neg(); + assert_eq!(res, min_plus_one); + assert_eq!(overflow, ConstChoice::FALSE); + } + + #[test] + fn wrapping_neg_if() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + let do_negate = ConstChoice::TRUE; + assert_eq!(I128::MIN.wrapping_neg_if(do_negate), I128::MIN); + assert_eq!(I128::MINUS_ONE.wrapping_neg_if(do_negate), I128::ONE); + assert_eq!(I128::ZERO.wrapping_neg_if(do_negate), I128::ZERO); + assert_eq!(I128::ONE.wrapping_neg_if(do_negate), I128::MINUS_ONE); + assert_eq!(I128::MAX.wrapping_neg_if(do_negate), min_plus_one); + + let do_not_negate = ConstChoice::FALSE; + assert_eq!(I128::MIN.wrapping_neg_if(do_not_negate), I128::MIN); + assert_eq!( + I128::MINUS_ONE.wrapping_neg_if(do_not_negate), + I128::MINUS_ONE + ); + assert_eq!(I128::ZERO.wrapping_neg_if(do_not_negate), I128::ZERO); + assert_eq!(I128::ONE.wrapping_neg_if(do_not_negate), I128::ONE); + assert_eq!(I128::MAX.wrapping_neg_if(do_not_negate), I128::MAX); + } + + #[test] + fn neg() { + assert_eq!(I128::MIN.checked_neg().is_none(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.checked_neg().unwrap(), I128::ONE); + assert_eq!(I128::ZERO.checked_neg().unwrap(), I128::ZERO); + assert_eq!(I128::ONE.checked_neg().unwrap(), I128::MINUS_ONE); + assert_eq!( + I128::MAX.checked_neg().unwrap(), + I128::from_be_hex("80000000000000000000000000000001") + ); + + let negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + let positive = I128::from_be_hex("6EEECCCCAAAA88886666444422220001"); + assert_eq!(negative.checked_neg().unwrap(), positive); + assert_eq!(positive.checked_neg().unwrap(), negative); + assert_eq!( + positive.checked_neg().unwrap().checked_neg().unwrap(), + positive + ); + } +} diff --git a/src/int/rand.rs b/src/int/rand.rs new file mode 100644 index 000000000..37e25f40b --- /dev/null +++ b/src/int/rand.rs @@ -0,0 +1,31 @@ +//! Random number generator support + +use rand_core::CryptoRngCore; + +use crate::{Int, Random, RandomBits, RandomBitsError}; + +use super::Uint; + +impl Random for Int { + /// Generate a cryptographically secure random [`Int`]. + fn random(rng: &mut impl CryptoRngCore) -> Self { + Self(Uint::random(rng)) + } +} + +impl RandomBits for Int { + fn try_random_bits( + rng: &mut impl CryptoRngCore, + bit_length: u32, + ) -> Result { + Self::try_random_bits_with_precision(rng, bit_length, Self::BITS) + } + + fn try_random_bits_with_precision( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, + ) -> Result { + Uint::try_random_bits_with_precision(rng, bit_length, bits_precision).map(Self) + } +} diff --git a/src/int/resize.rs b/src/int/resize.rs new file mode 100644 index 000000000..4aed28991 --- /dev/null +++ b/src/int/resize.rs @@ -0,0 +1,50 @@ +use crate::{Int, Limb, Uint}; + +impl Int { + /// Resize the representation of `self` to an `Int`. + /// Warning: this operation may lead to loss of information. + #[inline(always)] + pub const fn resize(&self) -> Int { + let mut limbs = [Limb::select(Limb::ZERO, Limb::MAX, self.is_negative()); T]; + let mut i = 0; + let dim = if T < LIMBS { T } else { LIMBS }; + while i < dim { + limbs[i] = self.0.limbs[i]; + i += 1; + } + Uint { limbs }.as_int() + } +} + +#[cfg(test)] +mod tests { + use num_traits::WrappingSub; + + use crate::{I128, I256}; + + #[test] + fn scale_down() { + assert_eq!(I256::MIN.resize::<{ I128::LIMBS }>(), I128::ZERO); + assert_eq!(I256::MINUS_ONE.resize::<{ I128::LIMBS }>(), I128::MINUS_ONE); + assert_eq!(I256::ZERO.resize::<{ I128::LIMBS }>(), I128::ZERO); + assert_eq!(I256::ONE.resize::<{ I128::LIMBS }>(), I128::ONE); + assert_eq!(I256::MAX.resize::<{ I128::LIMBS }>(), I128::MINUS_ONE); + } + + #[test] + fn scale_up() { + assert_eq!( + I128::MIN.resize::<{ I256::LIMBS }>(), + I256::ZERO.wrapping_sub(&I256 { + 0: I128::MIN.0.resize() + }) + ); + assert_eq!(I128::MINUS_ONE.resize::<{ I256::LIMBS }>(), I256::MINUS_ONE); + assert_eq!(I128::ZERO.resize::<{ I256::LIMBS }>(), I256::ZERO); + assert_eq!(I128::ONE.resize::<{ I256::LIMBS }>(), I256::ONE); + assert_eq!( + I128::MAX.resize::<{ I256::LIMBS }>(), + I128::MAX.0.resize().as_int() + ); + } +} diff --git a/src/int/shl.rs b/src/int/shl.rs new file mode 100644 index 000000000..25d869310 --- /dev/null +++ b/src/int/shl.rs @@ -0,0 +1,170 @@ +//! [`Int`] bitwise left shift operations. + +use core::ops::{Shl, ShlAssign}; + +use subtle::CtOption; + +use crate::{ConstCtOption, Int, ShlVartime, Uint, WrappingShl}; + +impl Int { + /// Computes `self << shift`. + /// + /// Panics if `shift >= Self::BITS`. + pub const fn shl(&self, shift: u32) -> Self { + Self(Uint::shl(&self.0, shift)) + } + + /// Computes `self << shift` in variable time. + /// + /// Panics if `shift >= Self::BITS`. + pub const fn shl_vartime(&self, shift: u32) -> Self { + Self(Uint::shl_vartime(&self.0, shift)) + } + + /// Computes `self << shift`. + /// + /// Returns `None` if `shift >= Self::BITS`. + pub const fn overflowing_shl(&self, shift: u32) -> ConstCtOption { + self.0.overflowing_shl(shift).as_int() + } + + /// Computes `self << shift`. + /// + /// Returns `None` if `shift >= Self::BITS`. + /// + /// NOTE: this operation is variable time with respect to `shift` *ONLY*. + /// + /// When used with a fixed `shift`, this function is constant-time with respect + /// to `self`. + #[inline(always)] + pub const fn overflowing_shl_vartime(&self, shift: u32) -> ConstCtOption { + self.0.overflowing_shl_vartime(shift).as_int() + } + + /// Computes `self << shift` in a panic-free manner, returning zero if the shift exceeds the + /// precision. + pub const fn wrapping_shl(&self, shift: u32) -> Self { + Self(self.0.wrapping_shl(shift)) + } + + /// Computes `self << shift` in variable-time in a panic-free manner, returning zero if the + /// shift exceeds the precision. + pub const fn wrapping_shl_vartime(&self, shift: u32) -> Self { + Self(self.0.wrapping_shl_vartime(shift)) + } +} + +macro_rules! impl_shl { + ($($shift:ty),+) => { + $( + impl Shl<$shift> for Int { + type Output = Int; + + #[inline] + fn shl(self, shift: $shift) -> Int { + <&Self>::shl(&self, shift) + } + } + + impl Shl<$shift> for &Int { + type Output = Int; + + #[inline] + fn shl(self, shift: $shift) -> Int { + Int::::shl(self, u32::try_from(shift).expect("invalid shift")) + } + } + + impl ShlAssign<$shift> for Int { + fn shl_assign(&mut self, shift: $shift) { + *self = self.shl(shift) + } + } + )+ + }; +} + +impl_shl!(i32, u32, usize); + +impl WrappingShl for Int { + fn wrapping_shl(&self, shift: u32) -> Int { + self.wrapping_shl(shift) + } +} + +impl ShlVartime for Int { + fn overflowing_shl_vartime(&self, shift: u32) -> CtOption { + self.overflowing_shl(shift).into() + } + fn wrapping_shl_vartime(&self, shift: u32) -> Self { + self.wrapping_shl(shift) + } +} + +#[cfg(test)] +mod tests { + use crate::I256; + + const N: I256 = + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); + + const TWO_N: I256 = + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD755DB9CD5E9140777FA4BD19A06C8282"); + + const FOUR_N: I256 = + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAEABB739ABD2280EEFF497A3340D90504"); + + const SIXTY_FIVE: I256 = + I256::from_be_hex("FFFFFFFFFFFFFFFD755DB9CD5E9140777FA4BD19A06C82820000000000000000"); + + const EIGHTY_EIGHT: I256 = + I256::from_be_hex("FFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD03641410000000000000000000000"); + + const SIXTY_FOUR: I256 = + I256::from_be_hex("FFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD03641410000000000000000"); + + #[test] + fn shl_simple() { + let mut t = I256::from(1i8); + assert_eq!(t << 1, I256::from(2i8)); + t = I256::from(3i8); + assert_eq!(t << 8, I256::from(0x300i16)); + } + + #[test] + fn shl1() { + assert_eq!(N << 1, TWO_N); + } + + #[test] + fn shl2() { + assert_eq!(N << 2, FOUR_N); + } + + #[test] + fn shl65() { + assert_eq!(N << 65, SIXTY_FIVE); + } + + #[test] + fn shl88() { + assert_eq!(N << 88, EIGHTY_EIGHT); + } + + #[test] + fn shl256_const() { + assert!(N.overflowing_shl(256).is_none().is_true_vartime()); + assert!(N.overflowing_shl_vartime(256).is_none().is_true_vartime()); + } + + #[test] + #[should_panic(expected = "`shift` within the bit size of the integer")] + fn shl256() { + let _ = N << 256; + } + + #[test] + fn shl64() { + assert_eq!(N << 64, SIXTY_FOUR); + } +} diff --git a/src/int/shr.rs b/src/int/shr.rs new file mode 100644 index 000000000..562da63e5 --- /dev/null +++ b/src/int/shr.rs @@ -0,0 +1,243 @@ +//! [`Int`] bitwise right shift operations. + +use core::ops::{Shr, ShrAssign}; + +use subtle::CtOption; + +use crate::{ConstChoice, ConstCtOption, Int, Limb, ShrVartime, Uint, WrappingShr}; + +impl Int { + /// Computes `self >> shift`. + /// + /// Note, this is _signed_ shift right, i.e., the value shifted in on the left is equal to + /// the most significant bit. + /// + /// Panics if `shift >= Self::BITS`. + pub const fn shr(&self, shift: u32) -> Self { + self.overflowing_shr(shift) + .expect("`shift` within the bit size of the integer") + } + + /// Computes `self >> shift` in variable time. + /// + /// Note, this is _signed_ shift right, i.e., the value shifted in on the left is equal to + /// the most significant bit. + /// + /// Panics if `shift >= Self::BITS`. + pub const fn shr_vartime(&self, shift: u32) -> Self { + self.overflowing_shr_vartime(shift) + .expect("`shift` within the bit size of the integer") + } + + /// Computes `self >> shift`. + /// + /// Note, this is _signed_ shift right, i.e., the value shifted in on the left is equal to + /// the most significant bit. + /// + /// Returns `None` if `shift >= Self::BITS`. + pub const fn overflowing_shr(&self, shift: u32) -> ConstCtOption { + // `floor(log2(BITS - 1))` is the number of bits in the representation of `shift` + // (which lies in range `0 <= shift < BITS`). + let shift_bits = u32::BITS - (Self::BITS - 1).leading_zeros(); + let overflow = ConstChoice::from_u32_lt(shift, Self::BITS).not(); + let shift = shift % Self::BITS; + let mut result = *self; + let mut i = 0; + while i < shift_bits { + let bit = ConstChoice::from_u32_lsb((shift >> i) & 1); + result = Int::select( + &result, + &result + .overflowing_shr_vartime(1 << i) + .expect("shift within range"), + bit, + ); + i += 1; + } + + ConstCtOption::new(result, overflow.not()) + } + + /// Computes `self >> shift`. + /// + /// NOTE: this is _signed_ shift right, i.e., the value shifted in on the left is equal to + /// the most significant bit. + /// + /// Returns `None` if `shift >= Self::BITS`. + /// + /// NOTE: this operation is variable time with respect to `shift` *ONLY*. + /// + /// When used with a fixed `shift`, this function is constant-time with respect + /// to `self`. + #[inline(always)] + pub const fn overflowing_shr_vartime(&self, shift: u32) -> ConstCtOption { + let is_negative = self.is_negative(); + + if shift >= Self::BITS { + return ConstCtOption::none(Self::select(&Self::ZERO, &Self::MINUS_ONE, is_negative)); + } + + // Select the base limb, based on the sign of this value. + let base = Limb::select(Limb::ZERO, Limb::MAX, is_negative); + let mut limbs = [base; LIMBS]; + + let shift_num = (shift / Limb::BITS) as usize; + let rem = shift % Limb::BITS; + + let mut i = 0; + while i < LIMBS - shift_num { + limbs[i] = self.0.limbs[i + shift_num]; + i += 1; + } + + if rem == 0 { + return ConstCtOption::some(Self(Uint::new(limbs))); + } + + // construct the carry s.t. the `rem`-most significant bits of `carry` are 1 when self + // is negative, i.e., shift in 1s when the msb is 1. + let mut carry = Limb::select(Limb::ZERO, Limb::MAX, is_negative); + carry = carry.bitxor(carry.shr(rem)); // logical shift right; shifts in zeroes. + + while i > 0 { + i -= 1; + let shifted = limbs[i].shr(rem); + let new_carry = limbs[i].shl(Limb::BITS - rem); + limbs[i] = shifted.bitor(carry); + carry = new_carry; + } + + ConstCtOption::some(Self(Uint::new(limbs))) + } + + /// Computes `self >> shift` in a panic-free manner. + /// + /// If the shift exceeds the precision, returns + /// - `0` when `self` is non-negative, and + /// - `-1` when `self` is negative. + pub const fn wrapping_shr(&self, shift: u32) -> Self { + let default = Self::select(&Self::ZERO, &Self::MINUS_ONE, self.is_negative()); + self.overflowing_shr(shift).unwrap_or(default) + } + + /// Computes `self >> shift` in variable-time in a panic-free manner. + /// + /// If the shift exceeds the precision, returns + /// - `0` when `self` is non-negative, and + /// - `-1` when `self` is negative. + pub const fn wrapping_shr_vartime(&self, shift: u32) -> Self { + let default = Self::select(&Self::ZERO, &Self::MINUS_ONE, self.is_negative()); + self.overflowing_shr_vartime(shift).unwrap_or(default) + } +} + +macro_rules! impl_shr { + ($($shift:ty),+) => { + $( + impl Shr<$shift> for Int { + type Output = Int; + + #[inline] + fn shr(self, shift: $shift) -> Int { + <&Self>::shr(&self, shift) + } + } + + impl Shr<$shift> for &Int { + type Output = Int; + + #[inline] + fn shr(self, shift: $shift) -> Int { + Int::::shr(self, u32::try_from(shift).expect("invalid shift")) + } + } + + impl ShrAssign<$shift> for Int { + fn shr_assign(&mut self, shift: $shift) { + *self = self.shr(shift) + } + } + )+ + }; +} + +impl_shr!(i32, u32, usize); + +impl WrappingShr for Int { + fn wrapping_shr(&self, shift: u32) -> Int { + self.wrapping_shr(shift) + } +} + +impl ShrVartime for Int { + fn overflowing_shr_vartime(&self, shift: u32) -> CtOption { + self.overflowing_shr(shift).into() + } + fn wrapping_shr_vartime(&self, shift: u32) -> Self { + self.wrapping_shr(shift) + } +} + +#[cfg(test)] +mod tests { + use crate::I256; + + const N: I256 = + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); + + const N_2: I256 = + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0"); + + #[test] + fn shr0() { + assert_eq!(I256::MAX >> 0, I256::MAX); + assert_eq!(I256::MIN >> 0, I256::MIN); + } + + #[test] + fn shr1() { + assert_eq!(N >> 1, N_2); + } + + #[test] + fn shr5() { + assert_eq!( + I256::MAX >> 5, + I256::from_be_hex("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + assert_eq!( + I256::MIN >> 5, + I256::from_be_hex("FC00000000000000000000000000000000000000000000000000000000000000") + ); + } + + #[test] + fn shr7_vartime() { + assert_eq!( + I256::MAX.shr_vartime(7), + I256::from_be_hex("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + assert_eq!( + I256::MIN.shr_vartime(7), + I256::from_be_hex("FF00000000000000000000000000000000000000000000000000000000000000") + ); + } + + #[test] + fn shr256_const() { + assert!(N.overflowing_shr(256).is_none().is_true_vartime()); + assert!(N.overflowing_shr_vartime(256).is_none().is_true_vartime()); + } + + #[test] + #[should_panic(expected = "`shift` within the bit size of the integer")] + fn shr256() { + let _ = N >> 256; + } + + #[test] + fn wrapping_shr() { + assert_eq!(I256::MAX.wrapping_shr(257), I256::ZERO); + assert_eq!(I256::MIN.wrapping_shr(257), I256::MINUS_ONE); + } +} diff --git a/src/int/sign.rs b/src/int/sign.rs new file mode 100644 index 000000000..e8bd3ed0d --- /dev/null +++ b/src/int/sign.rs @@ -0,0 +1,86 @@ +use crate::{ConstChoice, ConstCtOption, Int, Uint, Word}; +use num_traits::ConstZero; + +impl Int { + /// Returns the word of most significant [`Limb`]. + /// For the generative case where the number of limbs is zero, + /// zeroed word is returned (which is semantically correct). + /// This method leaks the limb length of the value, which is also OK. + const fn most_significant_word(&self) -> Word { + if Self::LIMBS == 0 { + Word::ZERO + } else { + self.0.to_words()[LIMBS - 1] + } + } + + /// Construct new [`Int`] from an absolute value and sign. + /// Returns `None` when the absolute value does not fit in an [`Int`]. + pub const fn new_from_abs_sign( + abs: Uint, + is_negative: ConstChoice, + ) -> ConstCtOption { + let magnitude = Self(abs).wrapping_neg_if(is_negative); + let fits = Uint::lte(&abs, &Int::MAX.0).or(is_negative.and(Uint::eq(&abs, &Int::MIN.0))); + ConstCtOption::new(magnitude, fits) + } + + /// Whether this [`Int`] is negative, as a `ConstChoice`. + pub const fn is_negative(&self) -> ConstChoice { + ConstChoice::from_word_msb(self.most_significant_word()) + } + + /// Whether this [`Int`] is positive, as a `ConstChoice`. + pub const fn is_positive(&self) -> ConstChoice { + self.is_negative().not().and(self.is_nonzero()) + } + + /// The sign and magnitude of this [`Int`]. + pub const fn abs_sign(&self) -> (Uint, ConstChoice) { + let sign = self.is_negative(); + // Note: this negate_if is safe to use, since we are negating based on self.is_negative() + let abs = self.wrapping_neg_if(sign); + (abs.0, sign) + } + + /// The magnitude of this [`Int`]. + pub const fn abs(&self) -> Uint { + self.abs_sign().0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::I128; + + #[test] + fn is_negative() { + assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); + + let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); + + let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); + } + + #[test] + fn is_positive() { + assert_eq!(I128::MIN.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::MINUS_ONE.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::ZERO.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_positive(), ConstChoice::TRUE); + assert_eq!(I128::MAX.is_positive(), ConstChoice::TRUE); + + let random_negative = I128::from_be_hex("deadbeefcafebabedeadbeefcafebabe"); + assert_eq!(random_negative.is_positive(), ConstChoice::FALSE); + + let random_positive = I128::from_be_hex("0badc0dedeadc0decafebabedeadcafe"); + assert_eq!(random_positive.is_positive(), ConstChoice::TRUE); + } +} diff --git a/src/int/sub.rs b/src/int/sub.rs new file mode 100644 index 000000000..1cb1d73bc --- /dev/null +++ b/src/int/sub.rs @@ -0,0 +1,212 @@ +//! [`Int`] subtraction operations. + +use core::ops::{Sub, SubAssign}; + +use num_traits::WrappingSub; +use subtle::{Choice, ConstantTimeEq, CtOption}; + +use crate::{Checked, CheckedSub, Int, Wrapping}; + +impl CheckedSub for Int { + fn checked_sub(&self, rhs: &Self) -> CtOption { + // Step 1. subtract operands + let res = Self(self.0.wrapping_sub(&rhs.0)); + + // Step 2. check whether underflow happened. + // Note: + // - underflow can only happen when the inputs have opposing signs, and then + // - underflow occurs if and only if the result and the lhs have opposing signs. + // + // We can thus express the overflow flag as: (self.msb != rhs.msb) & (self.msb != res.msb) + let self_msb: Choice = self.is_negative().into(); + let underflow = + self_msb.ct_ne(&rhs.is_negative().into()) & self_msb.ct_ne(&res.is_negative().into()); + + // Step 3. Construct result + CtOption::new(res, !underflow) + } +} + +impl Sub for Int { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + self.sub(&rhs) + } +} + +impl Sub<&Int> for Int { + type Output = Self; + + fn sub(self, rhs: &Self) -> Self { + self.checked_sub(rhs) + .expect("attempted to subtract with underflow") + } +} + +impl SubAssign for Wrapping> { + fn sub_assign(&mut self, other: Self) { + *self = *self - other; + } +} + +impl SubAssign<&Wrapping>> for Wrapping> { + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } +} + +impl SubAssign for Checked> { + fn sub_assign(&mut self, other: Self) { + *self = *self - other; + } +} + +impl SubAssign<&Checked>> for Checked> { + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } +} + +impl WrappingSub for Int { + fn wrapping_sub(&self, v: &Self) -> Self { + Self(self.0.wrapping_sub(&v.0)) + } +} + +#[cfg(test)] +mod tests { + + #[cfg(test)] + mod tests { + use num_traits::WrappingSub; + + use crate::{CheckedSub, Int, I128, U128}; + + #[test] + fn checked_sub() { + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + let max_minus_one = Int { + 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), + }; + let two = Int { + 0: U128::from(2u32), + }; + let min_plus_two = Int { + 0: I128::MIN.0.wrapping_add(&two.0), + }; + + // lhs = MIN + + let result = I128::MIN.checked_sub(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MIN.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MIN.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_sub(&I128::ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_sub(&I128::MAX); + assert!(bool::from(result.is_none())); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_sub(&I128::MIN); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MINUS_ONE.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), two.checked_neg().unwrap()); + + let result = I128::MINUS_ONE.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), I128::MIN); + + // lhs = 0 + + let result = I128::ZERO.checked_sub(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::ZERO.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ZERO.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ZERO.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), min_plus_one); + + // lhs = 1 + + let result = I128::ONE.checked_sub(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::ONE.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), two); + + let result = I128::ONE.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), min_plus_two); + + // lhs = MAX + + let result = I128::MAX.checked_sub(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_sub(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), max_minus_one); + + let result = I128::MAX.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + } + + #[test] + fn wrapping_sub() { + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + let two = Int { + 0: U128::from(2u32), + }; + let max_minus_one = Int { + 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), + }; + + // + sub - + let result = I128::ONE.wrapping_sub(&I128::MIN); + assert_eq!(result, min_plus_one); + + // 0 sub - + let result = I128::ZERO.wrapping_sub(&I128::MIN); + assert_eq!(result, I128::MIN); + + // - sub + + let result = I128::MIN.wrapping_sub(&two); + assert_eq!(result, max_minus_one); + } + } +} diff --git a/src/int/types.rs b/src/int/types.rs new file mode 100644 index 000000000..0488dd494 --- /dev/null +++ b/src/int/types.rs @@ -0,0 +1,60 @@ +//! Selection of [`Int`] types. +//! todo: replace with macro implementation once serde is set up. + +use crate::Int; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I64 = Int<1>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I128 = Int<2>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I256 = Int<4>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I512 = Int<8>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I1024 = Int<16>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I2048 = Int<32>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I4096 = Int<64>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I64 = Int<2>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I128 = Int<4>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I256 = Int<8>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I512 = Int<16>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I1024 = Int<32>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I2048 = Int<64>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I4096 = Int<128>; diff --git a/src/lib.rs b/src/lib.rs index cdfab0abb..28a52163a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,7 @@ //! ### Random number generation //! //! When the `rand_core` or `rand` features of this crate are enabled, it's -//! possible to generate random numbers using any CSRNG by using the +//! possible to generate random numbers using any RNG by using the //! [`Random`] trait: //! //! ``` @@ -155,26 +155,27 @@ #[macro_use] extern crate alloc; -#[macro_use] -mod macros; - -pub mod modular; +#[cfg(feature = "rand_core")] +pub use rand_core; +#[cfg(feature = "rlp")] +pub use rlp; +pub use subtle; +#[cfg(feature = "zeroize")] +pub use zeroize; #[cfg(feature = "hybrid-array")] -mod array; -mod checked; -mod const_choice; -mod limb; -mod non_zero; -mod odd; -mod primitives; -mod traits; -mod uint; -mod wrapping; +pub use { + crate::array::{ArrayDecoding, ArrayEncoding, ByteArray}, + hybrid_array::{self, typenum::consts}, +}; +#[cfg(feature = "alloc")] +pub use crate::uint::boxed::BoxedUint; pub use crate::{ checked::Checked, const_choice::{ConstChoice, ConstCtOption}, + int::types::*, + int::*, limb::{Limb, WideWord, Word}, non_zero::NonZero, odd::Odd, @@ -183,30 +184,28 @@ pub use crate::{ uint::*, wrapping::Wrapping, }; -pub use subtle; - -#[cfg(feature = "alloc")] -pub use crate::uint::boxed::BoxedUint; - -#[cfg(feature = "hybrid-array")] -pub use { - crate::array::{ArrayDecoding, ArrayEncoding, ByteArray}, - hybrid_array::{self, typenum::consts}, -}; -#[cfg(feature = "rand_core")] -pub use rand_core; +#[macro_use] +mod macros; -#[cfg(feature = "rlp")] -pub use rlp; +pub mod modular; -#[cfg(feature = "zeroize")] -pub use zeroize; +#[cfg(feature = "hybrid-array")] +mod array; +mod checked; +mod const_choice; +mod int; +mod limb; +mod non_zero; +mod odd; +mod primitives; +mod traits; +mod uint; +mod wrapping; /// Import prelude for this crate: includes important traits. pub mod prelude { - pub use crate::traits::*; - #[cfg(feature = "hybrid-array")] pub use crate::array::{ArrayDecoding, ArrayEncoding}; + pub use crate::traits::*; } diff --git a/src/limb/rand.rs b/src/limb/rand.rs index bfaf62ebe..96f384789 100644 --- a/src/limb/rand.rs +++ b/src/limb/rand.rs @@ -2,23 +2,23 @@ use super::Limb; use crate::{Encoding, NonZero, Random, RandomMod}; -use rand_core::CryptoRngCore; +use rand_core::RngCore; use subtle::ConstantTimeLess; impl Random for Limb { #[cfg(target_pointer_width = "32")] - fn random(rng: &mut impl CryptoRngCore) -> Self { + fn random(rng: &mut impl RngCore) -> Self { Self(rng.next_u32()) } #[cfg(target_pointer_width = "64")] - fn random(rng: &mut impl CryptoRngCore) -> Self { + fn random(rng: &mut impl RngCore) -> Self { Self(rng.next_u64()) } } impl RandomMod for Limb { - fn random_mod(rng: &mut impl CryptoRngCore, modulus: &NonZero) -> Self { + fn random_mod(rng: &mut impl RngCore, modulus: &NonZero) -> Self { let mut bytes = ::Repr::default(); let n_bits = modulus.bits() as usize; diff --git a/src/modular/boxed_monty_form.rs b/src/modular/boxed_monty_form.rs index 0049c18ab..78d5774a0 100644 --- a/src/modular/boxed_monty_form.rs +++ b/src/modular/boxed_monty_form.rs @@ -97,7 +97,7 @@ impl BoxedMontyParams { let mod_neg_inv = Limb(Word::MIN.wrapping_sub(inv_mod_limb.limbs[0].0)); - let mod_leading_zeros = modulus.as_ref().leading_zeros().max(Word::BITS - 1); + let mod_leading_zeros = modulus.as_ref().leading_zeros().min(Word::BITS - 1); let r3 = montgomery_reduction_boxed(&mut r2.square(), &modulus, mod_neg_inv); @@ -307,6 +307,14 @@ impl Monty for BoxedMontyForm { } } +/// NOTE: This zeroizes the value, but _not_ the associated parameters! +#[cfg(feature = "zeroize")] +impl Zeroize for BoxedMontyForm { + fn zeroize(&mut self) { + self.montgomery_form.zeroize(); + } +} + /// Convert the given integer into the Montgomery domain. #[inline] fn convert_to_montgomery(integer: &mut BoxedUint, params: &BoxedMontyParams) { @@ -319,12 +327,14 @@ fn convert_to_montgomery(integer: &mut BoxedUint, params: &BoxedMontyParams) { #[cfg(test)] mod tests { - use super::{BoxedMontyForm, BoxedMontyParams, BoxedUint, Odd}; + use super::{BoxedMontyForm, BoxedMontyParams, BoxedUint, Limb, Odd}; #[test] fn new_params_with_valid_modulus() { let modulus = Odd::new(BoxedUint::from(3u8)).unwrap(); - BoxedMontyParams::new(modulus); + let params = BoxedMontyParams::new(modulus); + + assert_eq!(params.mod_leading_zeros, Limb::BITS - 2); } #[test] diff --git a/src/modular/const_monty_form.rs b/src/modular/const_monty_form.rs index ab0efbc26..8ad6d40e8 100644 --- a/src/modular/const_monty_form.rs +++ b/src/modular/const_monty_form.rs @@ -15,7 +15,7 @@ use core::{fmt::Debug, marker::PhantomData}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; #[cfg(feature = "rand_core")] -use crate::{rand_core::CryptoRngCore, Random, RandomMod}; +use crate::{rand_core::RngCore, Random, RandomMod}; #[cfg(feature = "serde")] use { @@ -207,7 +207,7 @@ where MOD: ConstMontyParams, { #[inline] - fn random(rng: &mut impl CryptoRngCore) -> Self { + fn random(rng: &mut impl RngCore) -> Self { Self::new(&Uint::random_mod(rng, MOD::MODULUS.as_nz_ref())) } } diff --git a/src/modular/const_monty_form/macros.rs b/src/modular/const_monty_form/macros.rs index 6b7f096d2..99d22b324 100644 --- a/src/modular/const_monty_form/macros.rs +++ b/src/modular/const_monty_form/macros.rs @@ -84,3 +84,16 @@ macro_rules! const_monty_form { $crate::modular::ConstMontyForm::<$modulus, { $modulus::LIMBS }>::new(&$variable) }; } + +#[cfg(test)] +mod tests { + use crate::modular::ConstMontyParams; + use crate::{Limb, U64}; + + #[test] + fn new_params_with_valid_modulus() { + impl_modulus!(Mod, U64, "0000000000000003"); + + assert_eq!(Mod::MOD_LEADING_ZEROS, core::cmp::min(Limb::BITS - 1, 62)); + } +} diff --git a/src/modular/monty_form.rs b/src/modular/monty_form.rs index 945a8c277..a0d90d938 100644 --- a/src/modular/monty_form.rs +++ b/src/modular/monty_form.rs @@ -60,7 +60,7 @@ where let mod_neg_inv = Limb(Word::MIN.wrapping_sub(inv_mod.limbs[0].0)); - let mod_leading_zeros = modulus.as_ref().leading_zeros().max(Word::BITS - 1); + let mod_leading_zeros = modulus.as_ref().leading_zeros().min(Word::BITS - 1); // `R^3 mod modulus`, used for inversion in Montgomery form. let r3 = montgomery_reduction(&r2.square_wide(), &modulus, mod_neg_inv); @@ -95,7 +95,7 @@ impl MontyParams { let mod_neg_inv = Limb(Word::MIN.wrapping_sub(inv_mod.limbs[0].0)); - let mod_leading_zeros = modulus.as_ref().leading_zeros().max(Word::BITS - 1); + let mod_leading_zeros = modulus.as_ref().leading_zeros_vartime().min(Word::BITS - 1); // `R^3 mod modulus`, used for inversion in Montgomery form. let r3 = montgomery_reduction(&r2.square_wide(), &modulus, mod_neg_inv); @@ -158,6 +158,18 @@ impl ConstantTimeEq for MontyParams { } } +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for MontyParams { + fn zeroize(&mut self) { + self.modulus.zeroize(); + self.one.zeroize(); + self.r2.zeroize(); + self.r3.zeroize(); + self.mod_neg_inv.zeroize(); + self.mod_leading_zeros.zeroize(); + } +} + /// An integer in Montgomery form represented using `LIMBS` limbs. /// The odd modulus is set at runtime. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -318,10 +330,23 @@ impl ConstantTimeEq for MontyForm { } } -/// NOTE: this does _not_ zeroize the parameters, in order to maintain some form of type consistency #[cfg(feature = "zeroize")] impl zeroize::Zeroize for MontyForm { fn zeroize(&mut self) { - self.montgomery_form.zeroize() + self.montgomery_form.zeroize(); + self.params.zeroize(); + } +} + +#[cfg(test)] +mod tests { + use super::{Limb, MontyParams, Odd, Uint}; + + #[test] + fn new_params_with_valid_modulus() { + let modulus = Odd::new(Uint::from(3u8)).unwrap(); + let params = MontyParams::<1>::new(modulus); + + assert_eq!(params.mod_leading_zeros, Limb::BITS - 2); } } diff --git a/src/modular/monty_form/inv.rs b/src/modular/monty_form/inv.rs index 0238131f2..f3c835a4a 100644 --- a/src/modular/monty_form/inv.rs +++ b/src/modular/monty_form/inv.rs @@ -124,10 +124,10 @@ mod tests { let params = params(); let x = U256::from_be_hex("77117F1273373C26C700D076B3F780074D03339F56DD0EFB60E7F58441FD3685"); - let x_mod = MontyForm::new(&x, params); + let x_monty = MontyForm::new(&x, params); - let inv = x_mod.invert().unwrap(); - let res = x_mod * inv; + let inv = x_monty.invert().unwrap(); + let res = x_monty * inv; assert_eq!(res.retrieve(), U256::ONE); } @@ -137,11 +137,11 @@ mod tests { let params = params(); let x = U256::from_be_hex("77117F1273373C26C700D076B3F780074D03339F56DD0EFB60E7F58441FD3685"); - let x_mod = MontyForm::new(&x, params); + let x_monty = MontyForm::new(&x, params); let inverter = params.precompute_inverter(); - let inv = inverter.invert(&x_mod).unwrap(); - let res = x_mod * inv; + let inv = inverter.invert(&x_monty).unwrap(); + let res = x_monty * inv; assert_eq!(res.retrieve(), U256::ONE); } diff --git a/src/modular/monty_form/lincomb.rs b/src/modular/monty_form/lincomb.rs index d7c7db1b2..0debf719a 100644 --- a/src/modular/monty_form/lincomb.rs +++ b/src/modular/monty_form/lincomb.rs @@ -39,7 +39,7 @@ mod tests { use rand_core::SeedableRng; let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); - for n in 0..1000 { + for n in 0..1500 { let modulus = Odd::::random(&mut rng); let params = MontyParams::new_vartime(modulus); let m = modulus.as_nz_ref(); diff --git a/src/modular/reduction.rs b/src/modular/reduction.rs index b53bc8a05..688389fe3 100644 --- a/src/modular/reduction.rs +++ b/src/modular/reduction.rs @@ -5,43 +5,46 @@ use crate::{Limb, Odd, Uint}; #[cfg(feature = "alloc")] use {crate::BoxedUint, subtle::Choice}; -/// Implement the Montgomery reduction algorithm. -/// -/// This is implemented as a macro to abstract over `const fn` and boxed use cases, since the latter -/// needs mutable references and thus the unstable `const_mut_refs` feature (rust-lang/rust#57349). -// TODO(tarcieri): change this into a `const fn` when `const_mut_refs` is stable -macro_rules! impl_montgomery_reduction { - ($upper:expr, $lower:expr, $modulus:expr, $mod_neg_inv:expr, $limbs:expr) => {{ - let mut meta_carry = Limb::ZERO; - let mut new_sum; - - let mut i = 0; - while i < $limbs { - let u = $lower[i].wrapping_mul($mod_neg_inv); - - let (_, mut carry) = $lower[i].mac(u, $modulus[0], Limb::ZERO); - let mut new_limb; - - let mut j = 1; - while j < ($limbs - i) { - (new_limb, carry) = $lower[i + j].mac(u, $modulus[j], carry); - $lower[i + j] = new_limb; - j += 1; - } - while j < $limbs { - (new_limb, carry) = $upper[i + j - $limbs].mac(u, $modulus[j], carry); - $upper[i + j - $limbs] = new_limb; - j += 1; - } - - (new_sum, meta_carry) = $upper[i].adc(carry, meta_carry); - $upper[i] = new_sum; - - i += 1; +/// Algorithm 14.32 in Handbook of Applied Cryptography +const fn montgomery_reduction_inner( + upper: &mut [Limb], + lower: &mut [Limb], + modulus: &[Limb], + mod_neg_inv: Limb, +) -> Limb { + let nlimbs = modulus.len(); + debug_assert!(nlimbs == upper.len()); + debug_assert!(nlimbs == lower.len()); + + let mut meta_carry = Limb::ZERO; + let mut new_sum; + + let mut i = 0; + while i < nlimbs { + let u = lower[i].wrapping_mul(mod_neg_inv); + + let (_, mut carry) = lower[i].mac(u, modulus[0], Limb::ZERO); + let mut new_limb; + + let mut j = 1; + while j < (nlimbs - i) { + (new_limb, carry) = lower[i + j].mac(u, modulus[j], carry); + lower[i + j] = new_limb; + j += 1; } + while j < nlimbs { + (new_limb, carry) = upper[i + j - nlimbs].mac(u, modulus[j], carry); + upper[i + j - nlimbs] = new_limb; + j += 1; + } + + (new_sum, meta_carry) = upper[i].adc(carry, meta_carry); + upper[i] = new_sum; + + i += 1; + } - meta_carry - }}; + meta_carry } /// Algorithm 14.32 in Handbook of Applied Cryptography @@ -51,12 +54,11 @@ pub const fn montgomery_reduction( mod_neg_inv: Limb, ) -> Uint { let (mut lower, mut upper) = *lower_upper; - let meta_carry = impl_montgomery_reduction!( - upper.limbs, - lower.limbs, + let meta_carry = montgomery_reduction_inner( + &mut upper.limbs, + &mut lower.limbs, &modulus.0.limbs, mod_neg_inv, - LIMBS ); // Division is simply taking the upper half of the limbs @@ -79,8 +81,7 @@ pub(crate) fn montgomery_reduction_boxed_mut( debug_assert_eq!(out.nlimbs(), modulus.nlimbs()); let (lower, upper) = x.limbs.split_at_mut(modulus.nlimbs()); - let meta_carry = - impl_montgomery_reduction!(upper, lower, &modulus.limbs, mod_neg_inv, modulus.nlimbs()); + let meta_carry = montgomery_reduction_inner(upper, lower, &modulus.limbs, mod_neg_inv); out.limbs.copy_from_slice(upper); let borrow = out.sbb_assign(modulus, Limb::ZERO); diff --git a/src/non_zero.rs b/src/non_zero.rs index 3f280b301..efe24b0f3 100644 --- a/src/non_zero.rs +++ b/src/non_zero.rs @@ -1,6 +1,6 @@ //! Wrapper type for non-zero integers. -use crate::{Bounded, Constants, Encoding, Limb, Uint, Zero}; +use crate::{Bounded, Constants, ConstChoice, Encoding, Int, Limb, Uint, Zero}; use core::{ fmt, num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8}, @@ -12,7 +12,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use crate::{ArrayEncoding, ByteArray}; #[cfg(feature = "rand_core")] -use {crate::Random, rand_core::CryptoRngCore}; +use {crate::Random, rand_core::RngCore}; #[cfg(feature = "serde")] use serdect::serde::{ @@ -170,6 +170,15 @@ impl NonZero> { } } +impl NonZero> { + /// Convert a [`NonZero`] to its sign and [`NonZero`] magnitude. + pub const fn abs_sign(&self) -> (NonZero>, ConstChoice) { + let (abs, sign) = self.0.abs_sign(); + // Note: a NonZero always has a non-zero magnitude, so it is safe to unwrap. + (NonZero::>::new_unwrap(abs), sign) + } +} + #[cfg(feature = "hybrid-array")] impl NonZero where @@ -232,11 +241,12 @@ impl Random for NonZero where T: Random + Zero, { - /// Generate a random `NonZero`. - fn random(mut rng: &mut impl CryptoRngCore) -> Self { - // Use rejection sampling to eliminate zero values. - // While this method isn't constant-time, the attacker shouldn't learn - // anything about unrelated outputs so long as `rng` is a CSRNG. + /// This uses rejection sampling to avoid zero. + /// + /// As a result, it runs in variable time. If the generator `rng` is + /// cryptographically secure (for example, it implements `CryptoRng`), + /// then this is guaranteed not to leak anything about the output value. + fn random(mut rng: &mut impl RngCore) -> Self { loop { if let Some(result) = Self::new(T::random(&mut rng)).into() { break result; @@ -381,12 +391,26 @@ impl zeroize::Zeroize for NonZero { } } +#[cfg(test)] +mod tests { + use crate::{ConstChoice, I128, U128}; + + #[test] + fn int_abs_sign() { + let x = I128::from(-55).to_nz().unwrap(); + let (abs, sgn) = x.abs_sign(); + assert_eq!(abs, U128::from(55u32).to_nz().unwrap()); + assert_eq!(sgn, ConstChoice::TRUE); + } +} + #[cfg(all(test, feature = "serde"))] #[allow(clippy::unwrap_used)] -mod tests { - use crate::{NonZero, U64}; +mod tests_serde { use bincode::ErrorKind; + use crate::{NonZero, U64}; + #[test] fn serde() { let test = diff --git a/src/odd.rs b/src/odd.rs index c8a11a712..a7995d027 100644 --- a/src/odd.rs +++ b/src/odd.rs @@ -8,7 +8,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use crate::BoxedUint; #[cfg(feature = "rand_core")] -use {crate::Random, rand_core::CryptoRngCore}; +use {crate::Random, rand_core::RngCore}; #[cfg(all(feature = "alloc", feature = "rand_core"))] use crate::RandomBits; @@ -153,7 +153,7 @@ impl PartialOrd> for BoxedUint { #[cfg(feature = "rand_core")] impl Random for Odd> { /// Generate a random `Odd>`. - fn random(rng: &mut impl CryptoRngCore) -> Self { + fn random(rng: &mut impl RngCore) -> Self { let mut ret = Uint::random(rng); ret.limbs[0] |= Limb::ONE; Odd(ret) @@ -163,7 +163,7 @@ impl Random for Odd> { #[cfg(all(feature = "alloc", feature = "rand_core"))] impl Odd { /// Generate a random `Odd>`. - pub fn random(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { + pub fn random(rng: &mut impl RngCore, bit_length: u32) -> Self { let mut ret = BoxedUint::random_bits(rng, bit_length); ret.limbs[0] |= Limb::ONE; Odd(ret) diff --git a/src/traits.rs b/src/traits.rs index 54bfff62b..01f477695 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -12,7 +12,7 @@ use crate::{Limb, NonZero, Odd, Reciprocal}; use core::fmt::{self, Debug}; use core::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign, - Mul, MulAssign, Neg, Not, Rem, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, + Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, }; use subtle::{ Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess, @@ -20,7 +20,7 @@ use subtle::{ }; #[cfg(feature = "rand_core")] -use rand_core::CryptoRngCore; +use rand_core::RngCore; /// Integers whose representation takes a bounded amount of space. pub trait Bounded { @@ -82,6 +82,8 @@ pub trait Integer: 'static + Add + for<'a> Add<&'a Self, Output = Self> + + AddAssign + + for<'a> AddAssign<&'a Self> + AddMod + AsRef<[Limb]> + BitAnd @@ -121,12 +123,16 @@ pub trait Integer: + From + Mul + for<'a> Mul<&'a Self, Output = Self> + + MulAssign + + for<'a> MulAssign<&'a Self> + MulMod + NegMod + Not + Ord + Rem, Output = Self> + for<'a> Rem<&'a NonZero, Output = Self> + + RemAssign> + + for<'a> RemAssign<&'a NonZero> + RemLimb + Send + Sized @@ -138,6 +144,8 @@ pub trait Integer: + ShrVartime + Sub + for<'a> Sub<&'a Self, Output = Self> + + SubAssign + + for<'a> SubAssign<&'a Self> + SubMod + Sync + SquareRoot @@ -282,8 +290,10 @@ pub trait Constants: ConstZero { /// Random number generation support. #[cfg(feature = "rand_core")] pub trait Random: Sized { - /// Generate a cryptographically secure random value. - fn random(rng: &mut impl CryptoRngCore) -> Self; + /// Generate a random value. + /// + /// If `rng` is a CSRNG, the generation is cryptographically secure as well. + fn random(rng: &mut impl RngCore) -> Self; } /// Possible errors of the methods in [`RandomBits`] trait. @@ -343,28 +353,27 @@ impl core::error::Error for RandomBitsError {} /// Random bits generation support. #[cfg(feature = "rand_core")] pub trait RandomBits: Sized { - /// Generate a cryptographically secure random value in range `[0, 2^bit_length)`. + /// Generate a random value in range `[0, 2^bit_length)`. /// /// A wrapper for [`RandomBits::try_random_bits`] that panics on error. - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { + fn random_bits(rng: &mut impl RngCore, bit_length: u32) -> Self { Self::try_random_bits(rng, bit_length).expect("try_random_bits() failed") } - /// Generate a cryptographically secure random value in range `[0, 2^bit_length)`. + /// Generate a random value in range `[0, 2^bit_length)`. /// /// This method is variable time wrt `bit_length`. - fn try_random_bits( - rng: &mut impl CryptoRngCore, - bit_length: u32, - ) -> Result; + /// + /// If `rng` is a CSRNG, the generation is cryptographically secure as well. + fn try_random_bits(rng: &mut impl RngCore, bit_length: u32) -> Result; - /// Generate a cryptographically secure random value in range `[0, 2^bit_length)`, + /// Generate a random value in range `[0, 2^bit_length)`, /// returning an integer with the closest available size to `bits_precision` /// (if the implementing type supports runtime sizing). /// /// A wrapper for [`RandomBits::try_random_bits_with_precision`] that panics on error. fn random_bits_with_precision( - rng: &mut impl CryptoRngCore, + rng: &mut impl RngCore, bit_length: u32, bits_precision: u32, ) -> Self { @@ -372,13 +381,15 @@ pub trait RandomBits: Sized { .expect("try_random_bits_with_precision() failed") } - /// Generate a cryptographically secure random value in range `[0, 2^bit_length)`, + /// Generate a random value in range `[0, 2^bit_length)`, /// returning an integer with the closest available size to `bits_precision` /// (if the implementing type supports runtime sizing). /// /// This method is variable time wrt `bit_length`. + /// + /// If `rng` is a CSRNG, the generation is cryptographically secure as well. fn try_random_bits_with_precision( - rng: &mut impl CryptoRngCore, + rng: &mut impl RngCore, bit_length: u32, bits_precision: u32, ) -> Result; @@ -387,18 +398,16 @@ pub trait RandomBits: Sized { /// Modular random number generation support. #[cfg(feature = "rand_core")] pub trait RandomMod: Sized + Zero { - /// Generate a cryptographically secure random number which is less than - /// a given `modulus`. + /// Generate a random number which is less than a given `modulus`. /// - /// This function uses rejection sampling, a method which produces an - /// unbiased distribution of in-range values provided the underlying - /// CSRNG is unbiased, but runs in variable-time. + /// This uses rejection sampling. /// - /// The variable-time nature of the algorithm should not pose a security - /// issue so long as the underlying random number generator is truly a - /// CSRNG, where previous outputs are unrelated to subsequent - /// outputs and do not reveal information about the RNG's internal state. - fn random_mod(rng: &mut impl CryptoRngCore, modulus: &NonZero) -> Self; + /// As a result, it runs in variable time that depends in part on + /// `modulus`. If the generator `rng` is cryptographically secure (for + /// example, it implements `CryptoRng`), then this is guaranteed not to + /// leak anything about the output value aside from it being less than + /// `modulus`. + fn random_mod(rng: &mut impl RngCore, modulus: &NonZero) -> Self; } /// Compute `self + rhs mod p`. diff --git a/src/uint.rs b/src/uint.rs index 44a1853d5..3746b1788 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -2,6 +2,23 @@ #![allow(clippy::needless_range_loop, clippy::many_single_char_names)] +use core::fmt; + +#[cfg(feature = "serde")] +use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +#[cfg(feature = "zeroize")] +use zeroize::DefaultIsZeroes; + +#[cfg(feature = "extra-sizes")] +pub use extra_sizes::*; + +use crate::{ + modular::{MontyForm, SafeGcdInverter}, + Bounded, ConstCtOption, ConstZero, Constants, Encoding, FixedInteger, Int, Integer, Limb, + NonZero, Odd, PrecomputeInverter, PrecomputeInverterWithAdjuster, Word, +}; + #[macro_use] mod macros; @@ -16,7 +33,7 @@ mod cmp; mod concat; mod div; pub(crate) mod div_limb; -mod encoding; +pub(crate) mod encoding; mod from; mod gcd; mod inv_mod; @@ -39,20 +56,6 @@ pub(crate) mod boxed; #[cfg(feature = "rand_core")] mod rand; -use crate::{ - modular::{MontyForm, SafeGcdInverter}, - Bounded, ConstCtOption, ConstZero, Constants, Encoding, FixedInteger, Integer, Limb, NonZero, - Odd, PrecomputeInverter, PrecomputeInverterWithAdjuster, Word, -}; -use core::fmt; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; - -#[cfg(feature = "serde")] -use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; - -#[cfg(feature = "zeroize")] -use zeroize::DefaultIsZeroes; - /// Stack-allocated big unsigned integer. /// /// Generic over the given number of `LIMBS` @@ -184,6 +187,11 @@ impl Uint { pub const fn to_odd(self) -> ConstCtOption> { ConstCtOption::new(Odd(self), self.is_odd()) } + + /// Interpret the data in this type as an [`Int`] instead. + pub const fn as_int(&self) -> Int { + Int::from_bits(*self) + } } impl AsRef<[Word; LIMBS]> for Uint { @@ -258,6 +266,15 @@ impl Integer for Uint { } } +impl num_traits::Num for Uint { + type FromStrRadixErr = crate::DecodeError; + + /// ⚠️ WARNING: `from_str_radix` impl operates in variable-time with respect to the input. + fn from_str_radix(str: &str, radix: u32) -> Result { + Self::from_str_radix_vartime(str, radix) + } +} + impl ConstZero for Uint { const ZERO: Self = Self::ZERO; } @@ -447,20 +464,17 @@ impl_uint_concat_split_mixed! { #[cfg(feature = "extra-sizes")] mod extra_sizes; -#[cfg(feature = "extra-sizes")] -pub use extra_sizes::*; - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use crate::{Encoding, U128}; - use subtle::ConditionallySelectable; - #[cfg(feature = "alloc")] use alloc::format; + use subtle::ConditionallySelectable; + #[cfg(feature = "serde")] use crate::U64; + use crate::{Encoding, U128}; #[cfg(target_pointer_width = "64")] #[test] diff --git a/src/uint/boxed/add.rs b/src/uint/boxed/add.rs index 991c8f530..2fe8badd9 100644 --- a/src/uint/boxed/add.rs +++ b/src/uint/boxed/add.rs @@ -66,6 +66,14 @@ impl Add<&BoxedUint> for BoxedUint { } } +impl Add for &BoxedUint { + type Output = BoxedUint; + + fn add(self, rhs: BoxedUint) -> BoxedUint { + Add::add(self, &rhs) + } +} + impl Add<&BoxedUint> for &BoxedUint { type Output = BoxedUint; @@ -75,6 +83,23 @@ impl Add<&BoxedUint> for &BoxedUint { } } +impl AddAssign for BoxedUint { + fn add_assign(&mut self, rhs: BoxedUint) { + self.add_assign(&rhs) + } +} + +impl AddAssign<&BoxedUint> for BoxedUint { + fn add_assign(&mut self, rhs: &BoxedUint) { + let carry = self.adc_assign(rhs, Limb::ZERO); + assert_eq!( + carry.is_zero().unwrap_u8(), + 1, + "attempted to add with overflow" + ); + } +} + impl Add> for BoxedUint { type Output = BoxedUint; diff --git a/src/uint/boxed/mul.rs b/src/uint/boxed/mul.rs index 089d28365..90a3809ac 100644 --- a/src/uint/boxed/mul.rs +++ b/src/uint/boxed/mul.rs @@ -102,6 +102,18 @@ impl Mul<&BoxedUint> for &BoxedUint { } } +impl MulAssign for BoxedUint { + fn mul_assign(&mut self, rhs: BoxedUint) { + self.mul_assign(&rhs) + } +} + +impl MulAssign<&BoxedUint> for BoxedUint { + fn mul_assign(&mut self, rhs: &BoxedUint) { + *self = self.clone().mul(rhs) + } +} + impl MulAssign> for Wrapping { fn mul_assign(&mut self, other: Wrapping) { *self = Wrapping(self.0.wrapping_mul(&other.0)); diff --git a/src/uint/boxed/rand.rs b/src/uint/boxed/rand.rs index 6a2e73966..e00afa410 100644 --- a/src/uint/boxed/rand.rs +++ b/src/uint/boxed/rand.rs @@ -5,18 +5,15 @@ use crate::{ uint::rand::{random_bits_core, random_mod_core}, NonZero, RandomBits, RandomBitsError, RandomMod, }; -use rand_core::CryptoRngCore; +use rand_core::RngCore; impl RandomBits for BoxedUint { - fn try_random_bits( - rng: &mut impl CryptoRngCore, - bit_length: u32, - ) -> Result { + fn try_random_bits(rng: &mut impl RngCore, bit_length: u32) -> Result { Self::try_random_bits_with_precision(rng, bit_length, bit_length) } fn try_random_bits_with_precision( - rng: &mut impl CryptoRngCore, + rng: &mut impl RngCore, bit_length: u32, bits_precision: u32, ) -> Result { @@ -34,16 +31,7 @@ impl RandomBits for BoxedUint { } impl RandomMod for BoxedUint { - /// Generate a cryptographically secure random [`BoxedUint`] which is less than a given - /// `modulus`. - /// - /// This function uses rejection sampling, a method which produces an unbiased distribution of - /// in-range values provided the underlying CSRNG is unbiased, but runs in variable-time. - /// - /// The variable-time nature of the algorithm should not pose a security issue so long as the - /// underlying random number generator is truly a CSRNG, where previous outputs are unrelated to - /// subsequent outputs and do not reveal information about the RNG's internal state. - fn random_mod(rng: &mut impl CryptoRngCore, modulus: &NonZero) -> Self { + fn random_mod(rng: &mut impl RngCore, modulus: &NonZero) -> Self { let mut n = BoxedUint::zero_with_precision(modulus.bits_precision()); random_mod_core(rng, &mut n, modulus, modulus.bits()); n diff --git a/src/uint/boxed/sub.rs b/src/uint/boxed/sub.rs index 346227370..4256809ac 100644 --- a/src/uint/boxed/sub.rs +++ b/src/uint/boxed/sub.rs @@ -73,6 +73,14 @@ impl Sub<&BoxedUint> for BoxedUint { } } +impl Sub for &BoxedUint { + type Output = BoxedUint; + + fn sub(self, rhs: BoxedUint) -> BoxedUint { + Sub::sub(self, &rhs) + } +} + impl Sub<&BoxedUint> for &BoxedUint { type Output = BoxedUint; @@ -82,6 +90,23 @@ impl Sub<&BoxedUint> for &BoxedUint { } } +impl SubAssign for BoxedUint { + fn sub_assign(&mut self, rhs: BoxedUint) { + self.sub_assign(&rhs) + } +} + +impl SubAssign<&BoxedUint> for BoxedUint { + fn sub_assign(&mut self, rhs: &BoxedUint) { + let carry = self.sbb_assign(rhs, Limb::ZERO); + assert_eq!( + carry.is_zero().unwrap_u8(), + 1, + "attempted to subtract with underflow" + ); + } +} + impl Sub> for BoxedUint { type Output = BoxedUint; diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index eeb9a5545..453f8d119 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -1,12 +1,15 @@ //! [`Uint`] comparisons. //! -//! By default these are all constant-time and use the `subtle` crate. +//! By default, these are all constant-time. -use super::Uint; -use crate::{ConstChoice, Limb}; use core::cmp::Ordering; + use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; +use crate::{ConstChoice, Limb}; + +use super::Uint; + impl Uint { /// Return `b` if `c` is truthy, otherwise return `a`. #[inline] @@ -64,6 +67,12 @@ impl Uint { ConstChoice::from_word_mask(borrow.0) } + /// Returns the truthy value if `self <= rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn lte(lhs: &Self, rhs: &Self) -> ConstChoice { + Self::gt(lhs, rhs).not() + } + /// Returns the truthy value if `self > rhs` and the falsy value otherwise. #[inline] pub(crate) const fn gt(lhs: &Self, rhs: &Self) -> ConstChoice { @@ -160,10 +169,12 @@ impl PartialEq for Uint { #[cfg(test)] mod tests { - use crate::{Integer, Zero, U128}; use core::cmp::Ordering; + use subtle::{ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; + use crate::{Integer, Uint, Zero, U128}; + #[test] fn is_zero() { assert!(bool::from(U128::ZERO.is_zero())); @@ -233,6 +244,25 @@ mod tests { assert!(!bool::from(c.ct_lt(&b))); } + #[test] + fn lte() { + let a = U128::ZERO; + let b = U128::ONE; + let c = U128::MAX; + + assert!(bool::from(Uint::lte(&a, &b))); + assert!(bool::from(Uint::lte(&a, &c))); + assert!(bool::from(Uint::lte(&b, &c))); + + assert!(bool::from(Uint::lte(&a, &a))); + assert!(bool::from(Uint::lte(&b, &b))); + assert!(bool::from(Uint::lte(&c, &c))); + + assert!(!bool::from(Uint::lte(&b, &a))); + assert!(!bool::from(Uint::lte(&c, &a))); + assert!(!bool::from(Uint::lte(&c, &b))); + } + #[test] fn cmp() { let a = U128::ZERO; diff --git a/src/uint/div.rs b/src/uint/div.rs index f39c3255f..956680882 100644 --- a/src/uint/div.rs +++ b/src/uint/div.rs @@ -803,6 +803,24 @@ impl Div<&NonZero>> for Wrapping> { } } +impl Div> for &Uint { + type Output = Uint; + + #[inline] + fn div(self, rhs: Uint) -> Self::Output { + self / NonZero::new(rhs).expect("attempt to divide with a divisor of zero") + } +} + +impl Div> for Uint { + type Output = Uint; + + #[inline] + fn div(self, rhs: Uint) -> Self::Output { + &self / rhs + } +} + impl DivAssign<&NonZero>> for Wrapping> { fn div_assign(&mut self, rhs: &NonZero>) { *self = Wrapping(self.0 / rhs); @@ -847,6 +865,24 @@ impl Rem>> for Uint { } } +impl Rem> for &Uint { + type Output = Uint; + + #[inline] + fn rem(self, rhs: Uint) -> Self::Output { + self % NonZero::new(rhs).expect("attempt to calculate the remainder with a divisor of zero") + } +} + +impl Rem> for Uint { + type Output = Uint; + + #[inline] + fn rem(self, rhs: Uint) -> Self::Output { + &self % rhs + } +} + impl RemAssign<&NonZero>> for Uint { fn rem_assign(&mut self, rhs: &NonZero>) { *self %= *rhs diff --git a/src/uint/mul.rs b/src/uint/mul.rs index 4a122e963..025c58934 100644 --- a/src/uint/mul.rs +++ b/src/uint/mul.rs @@ -9,125 +9,118 @@ use subtle::CtOption; pub(crate) mod karatsuba; -/// Implement the core schoolbook multiplication algorithm. +/// Schoolbook multiplication a.k.a. long multiplication, i.e. the traditional method taught in +/// schools. /// -/// This is implemented as a macro to abstract over `const fn` and boxed use cases, since the latter -/// needs mutable references and thus the unstable `const_mut_refs` feature (rust-lang/rust#57349). -/// -/// It allows us to have a single place (this module) to improve the multiplication implementation -/// which will also be reused for `BoxedUint`. -// TODO(tarcieri): change this into a `const fn` when `const_mut_refs` is stable -macro_rules! impl_schoolbook_multiplication { - ($lhs:expr, $rhs:expr, $lo:expr, $hi:expr) => {{ - if $lhs.len() != $lo.len() || $rhs.len() != $hi.len() { - panic!("schoolbook multiplication length mismatch"); - } - - let mut i = 0; - while i < $lhs.len() { - let mut j = 0; - let mut carry = Limb::ZERO; - let xi = $lhs[i]; - - while j < $rhs.len() { - let k = i + j; +/// The most efficient method for small numbers. +const fn schoolbook_multiplication(lhs: &[Limb], rhs: &[Limb], lo: &mut [Limb], hi: &mut [Limb]) { + if lhs.len() != lo.len() || rhs.len() != hi.len() { + panic!("schoolbook multiplication length mismatch"); + } - if k >= $lhs.len() { - ($hi[k - $lhs.len()], carry) = $hi[k - $lhs.len()].mac(xi, $rhs[j], carry); - } else { - ($lo[k], carry) = $lo[k].mac(xi, $rhs[j], carry); - } + let mut i = 0; + while i < lhs.len() { + let mut j = 0; + let mut carry = Limb::ZERO; + let xi = lhs[i]; - j += 1; - } + while j < rhs.len() { + let k = i + j; - if i + j >= $lhs.len() { - $hi[i + j - $lhs.len()] = carry; + if k >= lhs.len() { + (hi[k - lhs.len()], carry) = hi[k - lhs.len()].mac(xi, rhs[j], carry); } else { - $lo[i + j] = carry; + (lo[k], carry) = lo[k].mac(xi, rhs[j], carry); } - i += 1; + + j += 1; + } + + if i + j >= lhs.len() { + hi[i + j - lhs.len()] = carry; + } else { + lo[i + j] = carry; } - }}; + i += 1; + } } -/// Implement the schoolbook method for squaring. +/// Schoolbook method of squaring. /// /// Like schoolbook multiplication, but only considering half of the multiplication grid. -// TODO: change this into a `const fn` when `const_mut_refs` is stable. -macro_rules! impl_schoolbook_squaring { - ($limbs:expr, $lo:expr, $hi:expr) => {{ - // Translated from https://github.com/ucbrise/jedi-pairing/blob/c4bf151/include/core/bigint.hpp#L410 - // - // Permission to relicense the resulting translation as Apache 2.0 + MIT was given - // by the original author Sam Kumar: https://github.com/RustCrypto/crypto-bigint/pull/133#discussion_r1056870411 - - if $limbs.len() != $lo.len() || $lo.len() != $hi.len() { - panic!("schoolbook squaring length mismatch"); - } - - let mut i = 1; - while i < $limbs.len() { - let mut j = 0; - let mut carry = Limb::ZERO; - let xi = $limbs[i]; - - while j < i { - let k = i + j; +pub(crate) const fn schoolbook_squaring(limbs: &[Limb], lo: &mut [Limb], hi: &mut [Limb]) { + // Translated from https://github.com/ucbrise/jedi-pairing/blob/c4bf151/include/core/bigint.hpp#L410 + // + // Permission to relicense the resulting translation as Apache 2.0 + MIT was given + // by the original author Sam Kumar: https://github.com/RustCrypto/crypto-bigint/pull/133#discussion_r1056870411 + + if limbs.len() != lo.len() || lo.len() != hi.len() { + panic!("schoolbook squaring length mismatch"); + } - if k >= $limbs.len() { - ($hi[k - $limbs.len()], carry) = $hi[k - $limbs.len()].mac(xi, $limbs[j], carry); - } else { - ($lo[k], carry) = $lo[k].mac(xi, $limbs[j], carry); - } + let mut i = 1; + while i < limbs.len() { + let mut j = 0; + let mut carry = Limb::ZERO; + let xi = limbs[i]; - j += 1; - } + while j < i { + let k = i + j; - if (2 * i) < $limbs.len() { - $lo[2 * i] = carry; + if k >= limbs.len() { + (hi[k - limbs.len()], carry) = hi[k - limbs.len()].mac(xi, limbs[j], carry); } else { - $hi[2 * i - $limbs.len()] = carry; + (lo[k], carry) = lo[k].mac(xi, limbs[j], carry); } - i += 1; + j += 1; } - // Double the current result, this accounts for the other half of the multiplication grid. - // The top word is empty, so we use a special purpose shl. - let mut carry = Limb::ZERO; - let mut i = 0; - while i < $limbs.len() { - ($lo[i].0, carry) = ($lo[i].0 << 1 | carry.0, $lo[i].shr(Limb::BITS - 1)); - i += 1; - } - i = 0; - while i < $limbs.len() - 1 { - ($hi[i].0, carry) = ($hi[i].0 << 1 | carry.0, $hi[i].shr(Limb::BITS - 1)); - i += 1; + if (2 * i) < limbs.len() { + lo[2 * i] = carry; + } else { + hi[2 * i - limbs.len()] = carry; } - $hi[$limbs.len() - 1] = carry; - // Handle the diagonal of the multiplication grid, which finishes the multiplication grid. - let mut carry = Limb::ZERO; - let mut i = 0; - while i < $limbs.len() { - let xi = $limbs[i]; - if (i * 2) < $limbs.len() { - ($lo[i * 2], carry) = $lo[i * 2].mac(xi, xi, carry); - } else { - ($hi[i * 2 - $limbs.len()], carry) = $hi[i * 2 - $limbs.len()].mac(xi, xi, carry); - } + i += 1; + } - if (i * 2 + 1) < $limbs.len() { - ($lo[i * 2 + 1], carry) = $lo[i * 2 + 1].overflowing_add(carry); - } else { - ($hi[i * 2 + 1 - $limbs.len()], carry) = $hi[i * 2 + 1 - $limbs.len()].overflowing_add(carry); - } + // Double the current result, this accounts for the other half of the multiplication grid. + // The top word is empty, so we use a special purpose shl. + let mut carry = Limb::ZERO; + let mut i = 0; + while i < limbs.len() { + (lo[i].0, carry) = (lo[i].0 << 1 | carry.0, lo[i].shr(Limb::BITS - 1)); + i += 1; + } + + let mut i = 0; + while i < limbs.len() - 1 { + (hi[i].0, carry) = (hi[i].0 << 1 | carry.0, hi[i].shr(Limb::BITS - 1)); + i += 1; + } + hi[limbs.len() - 1] = carry; + + // Handle the diagonal of the multiplication grid, which finishes the multiplication grid. + let mut carry = Limb::ZERO; + let mut i = 0; + while i < limbs.len() { + let xi = limbs[i]; + if (i * 2) < limbs.len() { + (lo[i * 2], carry) = lo[i * 2].mac(xi, xi, carry); + } else { + (hi[i * 2 - limbs.len()], carry) = hi[i * 2 - limbs.len()].mac(xi, xi, carry); + } - i += 1; + if (i * 2 + 1) < limbs.len() { + (lo[i * 2 + 1], carry) = lo[i * 2 + 1].overflowing_add(carry); + } else { + (hi[i * 2 + 1 - limbs.len()], carry) = + hi[i * 2 + 1 - limbs.len()].overflowing_add(carry); } - }}; + + i += 1; + } } impl Uint { @@ -251,6 +244,18 @@ impl Mul<&Uint> for &Uint } } +impl MulAssign> for Uint { + fn mul_assign(&mut self, rhs: Uint) { + *self = self.mul(&rhs) + } +} + +impl MulAssign<&Uint> for Uint { + fn mul_assign(&mut self, rhs: &Uint) { + *self = self.mul(rhs) + } +} + impl MulAssign>> for Wrapping> { fn mul_assign(&mut self, other: Wrapping>) { *self = *self * other; @@ -316,7 +321,7 @@ pub(crate) const fn uint_mul_limbs( debug_assert!(lhs.len() == LIMBS && rhs.len() == RHS_LIMBS); let mut lo: Uint = Uint::::ZERO; let mut hi = Uint::::ZERO; - impl_schoolbook_multiplication!(lhs, rhs, lo.limbs, hi.limbs); + schoolbook_multiplication(lhs, rhs, &mut lo.limbs, &mut hi.limbs); (lo, hi) } @@ -327,7 +332,7 @@ pub(crate) const fn uint_square_limbs( ) -> (Uint, Uint) { let mut lo = Uint::::ZERO; let mut hi = Uint::::ZERO; - impl_schoolbook_squaring!(limbs, lo.limbs, hi.limbs); + schoolbook_squaring(limbs, &mut lo.limbs, &mut hi.limbs); (lo, hi) } @@ -336,7 +341,7 @@ pub(crate) const fn uint_square_limbs( pub(crate) fn mul_limbs(lhs: &[Limb], rhs: &[Limb], out: &mut [Limb]) { debug_assert_eq!(lhs.len() + rhs.len(), out.len()); let (lo, hi) = out.split_at_mut(lhs.len()); - impl_schoolbook_multiplication!(lhs, rhs, lo, hi); + schoolbook_multiplication(lhs, rhs, lo, hi); } /// Wrapper function used by `BoxedUint` @@ -344,7 +349,7 @@ pub(crate) fn mul_limbs(lhs: &[Limb], rhs: &[Limb], out: &mut [Limb]) { pub(crate) fn square_limbs(limbs: &[Limb], out: &mut [Limb]) { debug_assert_eq!(limbs.len() * 2, out.len()); let (lo, hi) = out.split_at_mut(limbs.len()); - impl_schoolbook_squaring!(limbs, lo, hi); + schoolbook_squaring(limbs, lo, hi); } #[cfg(test)] diff --git a/src/uint/neg.rs b/src/uint/neg.rs index e734f1fb8..2852ce8fc 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -1,8 +1,16 @@ -use crate::{Limb, Uint, WideWord, Word, WrappingNeg}; +use crate::{ConstChoice, Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { /// Perform wrapping negation. pub const fn wrapping_neg(&self) -> Self { + self.carrying_neg().0 + } + + /// Perform negation: map `self` to `(self ^ 1111...1111) + 0000...0001`. + /// Additionally return whether the carry flag is set on the addition. + /// + /// Note: the carry is set if and only if `self == Self::ZERO`. + pub const fn carrying_neg(&self) -> (Self, ConstChoice) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; let mut i = 0; @@ -12,7 +20,12 @@ impl Uint { carry = r >> Limb::BITS; i += 1; } - Uint::new(ret) + (Uint::new(ret), ConstChoice::from_word_lsb(carry as Word)) + } + + /// Perform wrapping negation, if `negate` is truthy. Otherwise, return `self`. + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { + Uint::select(self, &self.wrapping_neg(), negate) } } @@ -25,7 +38,7 @@ impl WrappingNeg for Uint { #[cfg(test)] mod tests { - use crate::U256; + use crate::{ConstChoice, U256}; #[test] fn wrapping_neg() { @@ -40,4 +53,24 @@ mod tests { U256::from_u64(42).saturating_sub(&U256::ONE).not() ); } + + #[test] + fn carrying_neg() { + assert_eq!(U256::ZERO.carrying_neg(), (U256::ZERO, ConstChoice::TRUE)); + assert_eq!(U256::ONE.carrying_neg(), (U256::MAX, ConstChoice::FALSE)); + assert_eq!(U256::MAX.carrying_neg(), (U256::ONE, ConstChoice::FALSE)); + } + + #[test] + fn wrapping_neg_if() { + let negate = ConstChoice::TRUE; + assert_eq!(U256::ZERO.wrapping_neg_if(negate), U256::ZERO); + assert_eq!(U256::ONE.wrapping_neg_if(negate), U256::MAX); + assert_eq!(U256::MAX.wrapping_neg_if(negate), U256::ONE); + + let do_not_negate = ConstChoice::FALSE; + assert_eq!(U256::ZERO.wrapping_neg_if(do_not_negate), U256::ZERO); + assert_eq!(U256::ONE.wrapping_neg_if(do_not_negate), U256::ONE); + assert_eq!(U256::MAX.wrapping_neg_if(do_not_negate), U256::MAX); + } } diff --git a/src/uint/rand.rs b/src/uint/rand.rs index ce6b41de8..4c7be2b52 100644 --- a/src/uint/rand.rs +++ b/src/uint/rand.rs @@ -2,12 +2,11 @@ use super::{Uint, Word}; use crate::{Encoding, Limb, NonZero, Random, RandomBits, RandomBitsError, RandomMod, Zero}; -use rand_core::CryptoRngCore; +use rand_core::RngCore; use subtle::ConstantTimeLess; impl Random for Uint { - /// Generate a cryptographically secure random [`Uint`]. - fn random(mut rng: &mut impl CryptoRngCore) -> Self { + fn random(mut rng: &mut impl RngCore) -> Self { let mut limbs = [Limb::ZERO; LIMBS]; for limb in &mut limbs { @@ -22,7 +21,7 @@ impl Random for Uint { /// /// NOTE: Assumes that the limbs in the given slice are zeroed! pub(crate) fn random_bits_core( - rng: &mut impl CryptoRngCore, + rng: &mut impl RngCore, zeroed_limbs: &mut [Limb], bit_length: u32, ) -> Result<(), RandomBitsError> { @@ -51,15 +50,12 @@ pub(crate) fn random_bits_core( } impl RandomBits for Uint { - fn try_random_bits( - rng: &mut impl CryptoRngCore, - bit_length: u32, - ) -> Result { + fn try_random_bits(rng: &mut impl RngCore, bit_length: u32) -> Result { Self::try_random_bits_with_precision(rng, bit_length, Self::BITS) } fn try_random_bits_with_precision( - rng: &mut impl CryptoRngCore, + rng: &mut impl RngCore, bit_length: u32, bits_precision: u32, ) -> Result { @@ -82,18 +78,7 @@ impl RandomBits for Uint { } impl RandomMod for Uint { - /// Generate a cryptographically secure random [`Uint`] which is less than - /// a given `modulus`. - /// - /// This function uses rejection sampling, a method which produces an - /// unbiased distribution of in-range values provided the underlying - /// CSRNG is unbiased, but runs in variable-time. - /// - /// The variable-time nature of the algorithm should not pose a security - /// issue so long as the underlying random number generator is truly a - /// CSRNG, where previous outputs are unrelated to subsequent - /// outputs and do not reveal information about the RNG's internal state. - fn random_mod(rng: &mut impl CryptoRngCore, modulus: &NonZero) -> Self { + fn random_mod(rng: &mut impl RngCore, modulus: &NonZero) -> Self { let mut n = Self::ZERO; random_mod_core(rng, &mut n, modulus, modulus.bits_vartime()); n @@ -103,47 +88,55 @@ impl RandomMod for Uint { /// Generic implementation of `random_mod` which can be shared with `BoxedUint`. // TODO(tarcieri): obtain `n_bits` via a trait like `Integer` pub(super) fn random_mod_core( - rng: &mut impl CryptoRngCore, + rng: &mut impl RngCore, n: &mut T, modulus: &NonZero, n_bits: u32, ) where - T: AsMut<[Limb]> + ConstantTimeLess + Zero, + T: AsMut<[Limb]> + AsRef<[Limb]> + ConstantTimeLess + Zero, { - let n_bytes = ((n_bits + 7) / 8) as usize; + #[cfg(target_pointer_width = "64")] + let mut next_word = || rng.next_u64(); + #[cfg(target_pointer_width = "32")] + let mut next_word = || rng.next_u32(); + let n_limbs = n_bits.div_ceil(Limb::BITS) as usize; - let hi_bytes = n_bytes - (n_limbs - 1) * Limb::BYTES; - let mut bytes = Limb::ZERO.to_le_bytes(); + let hi_word_modulus = modulus.as_ref().as_ref()[n_limbs - 1].0; + let mask = !0 >> hi_word_modulus.leading_zeros(); + let mut hi_word = next_word() & mask; loop { + while hi_word > hi_word_modulus { + hi_word = next_word() & mask; + } + // Set high limb + n.as_mut()[n_limbs - 1] = Limb::from_le_bytes(hi_word.to_le_bytes()); + // Set low limbs for i in 0..n_limbs - 1 { - rng.fill_bytes(bytes.as_mut()); // Need to deserialize from little-endian to make sure that two 32-bit limbs // deserialized sequentially are equal to one 64-bit limb produced from the same // byte stream. - n.as_mut()[i] = Limb::from_le_bytes(bytes); + n.as_mut()[i] = Limb::from_le_bytes(next_word().to_le_bytes()); } - - // Generate the high limb which may need to only be filled partially. - bytes = Limb::ZERO.to_le_bytes(); - rng.fill_bytes(&mut bytes[..hi_bytes]); - n.as_mut()[n_limbs - 1] = Limb::from_le_bytes(bytes); - + // If the high limb is equal to the modulus' high limb, it's still possible + // that the full uint is too big so we check and repeat if it is. if n.ct_lt(modulus).into() { break; } + hi_word = next_word() & mask; } } #[cfg(test)] mod tests { use crate::{Limb, NonZero, RandomBits, RandomMod, U256}; + use rand_chacha::ChaCha8Rng; use rand_core::SeedableRng; #[test] fn random_mod() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); + let mut rng = ChaCha8Rng::seed_from_u64(1); // Ensure `random_mod` runs in a reasonable amount of time let modulus = NonZero::new(U256::from(42u8)).unwrap(); @@ -163,7 +156,7 @@ mod tests { #[test] fn random_bits() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); + let mut rng = ChaCha8Rng::seed_from_u64(1); let lower_bound = 16; diff --git a/src/uint/sub.rs b/src/uint/sub.rs index a6ac2bf28..5d9f7df77 100644 --- a/src/uint/sub.rs +++ b/src/uint/sub.rs @@ -1,4 +1,4 @@ -//! [`Uint`] addition operations. +//! [`Uint`] subtraction operations. use super::Uint; use crate::{Checked, CheckedSub, ConstChoice, Limb, Wrapping, WrappingSub, Zero}; @@ -59,6 +59,18 @@ impl Sub<&Uint> for Uint { } } +impl SubAssign> for Uint { + fn sub_assign(&mut self, rhs: Uint) { + *self = self.sub(&rhs) + } +} + +impl SubAssign<&Uint> for Uint { + fn sub_assign(&mut self, rhs: &Uint) { + *self = self.sub(rhs) + } +} + impl SubAssign for Wrapping> { fn sub_assign(&mut self, other: Self) { *self = *self - other; diff --git a/src/wrapping.rs b/src/wrapping.rs index 86640246c..c49378b0c 100644 --- a/src/wrapping.rs +++ b/src/wrapping.rs @@ -8,7 +8,7 @@ use core::{ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; #[cfg(feature = "rand_core")] -use {crate::Random, rand_core::CryptoRngCore}; +use {crate::Random, rand_core::RngCore}; #[cfg(feature = "serde")] use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -259,7 +259,7 @@ impl fmt::UpperHex for Wrapping { #[cfg(feature = "rand_core")] impl Random for Wrapping { - fn random(rng: &mut impl CryptoRngCore) -> Self { + fn random(rng: &mut impl RngCore) -> Self { Wrapping(Random::random(rng)) } } diff --git a/tests/monty_form.rs b/tests/monty_form.rs index b21c6352e..2cb3f05bc 100644 --- a/tests/monty_form.rs +++ b/tests/monty_form.rs @@ -3,21 +3,27 @@ mod common; use common::to_biguint; -use crypto_bigint::{Integer, Invert, Inverter, Odd, PrecomputeInverter, U256}; +use crypto_bigint::{ + modular::MontyForm, Bounded, Constants, Encoding, Integer, Invert, Inverter, Monty, NonZero, + Odd, PrecomputeInverter, U1024, U128, U2048, U256, U4096, U512, +}; use num_bigint::BigUint; use num_modular::ModularUnaryOps; +use num_traits::FromBytes; +use prop::test_runner::TestRng; use proptest::prelude::*; +use subtle::CtOption; -type MontyForm = crypto_bigint::modular::MontyForm<{ U256::LIMBS }>; -type MontyParams = crypto_bigint::modular::MontyParams<{ U256::LIMBS }>; +type MontyForm256 = crypto_bigint::modular::MontyForm<{ U256::LIMBS }>; +type MontyParams256 = crypto_bigint::modular::MontyParams<{ U256::LIMBS }>; -fn retrieve_biguint(monty_form: &MontyForm) -> BigUint { +fn retrieve_biguint(monty_form: &MontyForm256) -> BigUint { to_biguint(&monty_form.retrieve()) } -fn reduce(n: &U256, p: MontyParams) -> MontyForm { +fn reduce(n: &U256, p: MontyParams256) -> MontyForm256 { let n_reduced = n.rem_vartime(p.modulus().as_nz_ref()); - MontyForm::new(&n_reduced, p) + MontyForm256::new(&n_reduced, p) } prop_compose! { @@ -27,26 +33,271 @@ prop_compose! { } prop_compose! { /// Generate a random odd modulus. - fn modulus()(mut n in uint()) -> MontyParams { + fn modulus()(mut n in uint()) -> MontyParams256 { if n.is_even().into() { - n = n.wrapping_add(&U256::one()); + n = n.wrapping_add(&U256::ONE); } - MontyParams::new_vartime(Odd::new(n).expect("modulus ensured odd")) + MontyParams256::new_vartime(Odd::new(n).expect("modulus ensured odd")) } } +// Generates a random `T` and returns it as a tuple in: normal form, montgomery form, +// inverted montgomery form and the normal form inverse from the num_modular crate. +fn random_invertible_uint( + bytes: T::Repr, + monty_params: <::Monty as Monty>::Params, + modulus: T, +) -> Result<(T, ::Monty, ::Monty, BigUint), TestCaseError> +where + T: Integer + Bounded + Encoding, + ::Monty: Invert>, +{ + let r = T::from_be_bytes(bytes); + let rm = ::Monty::new(r.clone(), monty_params); + let rm_inv = rm.invert(); + prop_assume!(bool::from(rm_inv.is_some()), "r={:?} is not invertible", r); + let num_modular_modulus = BigUint::from_be_bytes(modulus.to_be_bytes().as_ref()); + let num_modular_inverse = BigUint::from_be_bytes(bytes.as_ref()) + .clone() + .invm(&num_modular_modulus) + .unwrap(); + return Ok((r, rm, rm_inv.unwrap(), num_modular_inverse)); +} + +// Given two bytes, presumed to be random, construct edge-case MontyParams with the first, last or +// middle bytes set to the edge case bytes. +fn monty_params_from_edge( + edge_bytes: [u8; 2], + rng: &mut TestRng, +) -> <::Monty as Monty>::Params +where + T: Integer + Constants + Encoding, +{ + let mut bytes = T::MAX.to_be_bytes(); + let len = bytes.as_ref().len(); + let edge = match rng.gen_range(0..3) { + // Hi + 0 => (len - 2)..len, + // Mid + 1 => len / 2 - 1..len / 2 + 1, + // Lo + 2 => 0..2, + _ => unimplemented!("shut up rust"), + }; + bytes.as_mut()[edge].copy_from_slice(&edge_bytes); + let mut modulus = T::from_be_bytes(bytes); + modulus.set_bit_vartime(0, true); + ::Monty::new_params_vartime(Odd::new(modulus).unwrap()) +} + +prop_compose! { + fn random_invertible_u128()( + bytes in any::<[u8; U128::BYTES]>(), + monty_params in any::<[u8;2]>().prop_perturb(|edge_bytes, mut rng|{ + monty_params_from_edge::(edge_bytes, &mut rng) + }) + ) -> Result<(U128, ::Monty , ::Monty, BigUint),TestCaseError> { + random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + } +} +prop_compose! { + fn random_invertible_u256()( + bytes in any::<[u8; U256::BYTES]>(), + monty_params in any::<[u8;2]>().prop_perturb(|edge_bytes, mut rng|{ + monty_params_from_edge::(edge_bytes, &mut rng) + }) + ) -> Result<(U256, ::Monty , ::Monty, BigUint),TestCaseError> { + random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + } +} +prop_compose! { + fn random_invertible_u2048()( + bytes in any::<[u8; U2048::BYTES]>(), + monty_params in any::<[u8;2]>().prop_perturb(|edge_bytes, mut rng|{ + monty_params_from_edge::(edge_bytes, &mut rng) + }) + ) -> Result<(U2048, ::Monty , ::Monty, BigUint),TestCaseError> { + random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + } +} +prop_compose! { + fn random_invertible_u1024()( + bytes in any::<[u8; U1024::BYTES]>(), + monty_params in any::<[u8;2]>().prop_perturb(|edge_bytes, mut rng|{ + monty_params_from_edge::(edge_bytes, &mut rng) + }) + ) -> Result<(U1024, ::Monty, ::Monty, BigUint),TestCaseError> { + random_invertible_uint(bytes, monty_params, monty_params.modulus().get()) + } +} proptest! { + #[test] + fn inversion_u128( + inputs in random_invertible_u128(), + ) { + let (r,r_monty, r_monty_inv, r_num_modular_inv) = inputs?; + let monty_params = r_monty.params().clone(); + let one_monty = r_monty * r_monty_inv; + // Inversion works in monty form + assert_eq!( + one_monty, + MontyForm::one(monty_params), + "a*a⁻¹ ≠ 1 (monty form)\nmodulus: {:0128b}", + monty_params.modulus() + ); + // …and in normal form + assert_eq!(one_monty.retrieve(), U128::ONE, "a*a⁻¹ ≠ 1 (normal form)"); + // …and when converted back to normal form and used in a widening operation + let wide_modulus = NonZero::new(Into::::into(&monty_params.modulus().get())).unwrap(); + let one = r_monty_inv.retrieve().widening_mul(&r); + assert_eq!( + one % wide_modulus, + U256::ONE, + "a*a⁻¹ ≠ 1 (normal form, wide)" + ); + // …and agrees with normal form inversion + let normal_form_inv = r.inv_mod(&monty_params.modulus()).unwrap(); + assert_eq!( + normal_form_inv, + r_monty_inv.retrieve(), + "a*a⁻¹ ≠ 1 (normal form inverted)" + ); + // …and agrees with the num_modular crate + assert_eq!( + BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + r_num_modular_inv, + "num_modular ≠ crypto_bigint" + ) + } + + #[test] + fn inversion_u256( + inputs in random_invertible_u256(), + ) { + let (r,r_monty, r_monty_inv, r_num_modular_inv) = inputs?; + let monty_params = r_monty.params().clone(); + let one_monty = r_monty * r_monty_inv; + // Inversion works in monty form + assert_eq!( + one_monty, + MontyForm::one(monty_params), + "a*a⁻¹ ≠ 1 (monty form)\nmodulus: {:0256b}", + monty_params.modulus() + ); + // …and in normal form + assert_eq!(one_monty.retrieve(), U256::ONE, "a*a⁻¹ ≠ 1 (normal form)"); + // …and when converted back to normal form and used in a widening operation + let wide_modulus = NonZero::new(Into::::into(&monty_params.modulus().get())).unwrap(); + let one = r_monty_inv.retrieve().widening_mul(&r); + assert_eq!( + one % wide_modulus, + U512::ONE, + "a*a⁻¹ ≠ 1 (normal form, wide)" + ); + // …and agrees with normal form inversion + let normal_form_inv = r.inv_mod(&monty_params.modulus()).unwrap(); + assert_eq!( + normal_form_inv, + r_monty_inv.retrieve(), + "a*a⁻¹ ≠ 1 (normal form inverted)" + ); + // …and agrees with the num_modular crate + assert_eq!( + BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + r_num_modular_inv, + "num_modular ≠ crypto_bigint" + ) + } + + #[test] + fn inversion_u1024( + inputs in random_invertible_u1024(), + ) { + let (r,r_monty, r_monty_inv, r_num_modular_inv) = inputs?; + let monty_params = r_monty.params().clone(); + let one_monty = r_monty * r_monty_inv; + // Inversion works in monty form + assert_eq!( + one_monty, + MontyForm::one(monty_params), + "a*a⁻¹ ≠ 1 (monty form)\nmodulus: {:01024b}", + monty_params.modulus() + ); + // …and in normal form + assert_eq!(one_monty.retrieve(), U1024::ONE, "a*a⁻¹ ≠ 1 (normal form)"); + // …and when converted back to normal form and used in a widening operation + let wide_modulus = NonZero::new(Into::::into(&monty_params.modulus().get())).unwrap(); + let one = r_monty_inv.retrieve().widening_mul(&r); + assert_eq!( + one % wide_modulus, + U2048::ONE, + "a*a⁻¹ ≠ 1 (normal form, wide)" + ); + // …and agrees with normal form inversion + let normal_form_inv = r.inv_mod(&monty_params.modulus()).unwrap(); + assert_eq!( + normal_form_inv, + r_monty_inv.retrieve(), + "a*a⁻¹ ≠ 1 (normal form inverted)" + ); + // …and agrees with the num_modular crate + assert_eq!( + BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + r_num_modular_inv, + "num_modular ≠ crypto_bigint" + ) + } + + #[test] + fn inversion_u2048( + inputs in random_invertible_u2048(), + ) { + let (r,r_monty, r_monty_inv, r_num_modular_inv) = inputs?; + let monty_params = r_monty.params().clone(); + let one_monty = r_monty * r_monty_inv; + // Inversion works in monty form + assert_eq!( + one_monty, + MontyForm::one(monty_params), + "a*a⁻¹ ≠ 1 (monty form)\nmodulus: {:02048b}", + monty_params.modulus() + ); + // …and in normal form + assert_eq!(one_monty.retrieve(), U2048::ONE, "a*a⁻¹ ≠ 1 (normal form)"); + // …and when converted back to normal form and used in a widening operation + let wide_modulus = NonZero::new(Into::::into(&monty_params.modulus().get())).unwrap(); + let one = r_monty_inv.retrieve().widening_mul(&r); + assert_eq!( + one % wide_modulus, + U4096::ONE, + "a*a⁻¹ ≠ 1 (normal form, wide)" + ); + // …and agrees with normal form inversion + let normal_form_inv = r.inv_mod(&monty_params.modulus()).unwrap(); + assert_eq!( + normal_form_inv, + r_monty_inv.retrieve(), + "a*a⁻¹ ≠ 1 (normal form inverted)" + ); + // …and agrees with the num_modular crate + assert_eq!( + BigUint::from_be_bytes(&normal_form_inv.to_be_bytes()), + r_num_modular_inv, + "num_modular ≠ crypto_bigint" + ) + } + #[test] fn new(n in modulus()) { - let n2 = MontyParams::new(*n.modulus()); + let n2 = MontyParams256::new(*n.modulus()); prop_assert_eq!(n, n2); } #[test] fn inv(x in uint(), n in modulus()) { let x = reduce(&x, n); - let actual = Option::::from(x.invert()); + let actual = Option::::from(x.invert()); let x_bi = retrieve_biguint(&x); let n_bi = to_biguint(n.modulus()); @@ -67,7 +318,7 @@ proptest! { fn precomputed_inv(x in uint(), n in modulus()) { let x = reduce(&x, n); let inverter = x.params().precompute_inverter(); - let actual = Option::::from(inverter.invert(&x)); + let actual = Option::::from(inverter.invert(&x)); let x_bi = retrieve_biguint(&x); let n_bi = to_biguint(n.modulus());