diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ada7e8..07cea02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.37.0 + toolchain: 1.39.0 override: true # cargo fmt does not build the code, and running it in a fresh clone of @@ -30,6 +30,13 @@ jobs: command: fmt args: --all -- --check --color always + # Build benchmarks to prevent bitrot + - name: Build benchmarks + uses: actions-rs/cargo@v1 + with: + command: build + args: --all --benches + test: name: Test on ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -41,7 +48,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.37.0 + toolchain: 1.39.0 override: true - name: cargo fetch uses: actions-rs/cargo@v1 @@ -111,10 +118,3 @@ jobs: with: command: doc args: --all --document-private-items - - # Build benchmarks to prevent bitrot - - name: Build benchmarks - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --all --benches diff --git a/.travis.yml b/.travis.yml index 059d454..c72494c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.37.0 + - 1.39.0 cache: cargo diff --git a/Cargo.lock b/Cargo.lock index 735a285..dcbed1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,11 +55,26 @@ name = "assert_matches" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "autocfg" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bech32" version = "0.7.1" @@ -84,6 +99,7 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -152,6 +168,30 @@ dependencies = [ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bls12_381" +version = "0.1.0" +dependencies = [ + "criterion 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bstr" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bumpalo" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byte-tools" version = "0.3.1" @@ -171,6 +211,14 @@ dependencies = [ "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cc" version = "1.0.45" @@ -181,6 +229,16 @@ name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -194,6 +252,39 @@ name = "constant_time_eq" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "criterion" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion-plot 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "oorandom 11.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "plotters 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "criterion-plot" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam" version = "0.7.2" @@ -201,7 +292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -217,11 +308,12 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -237,6 +329,20 @@ dependencies = [ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-queue" version = "0.1.2" @@ -245,6 +351,15 @@ dependencies = [ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-queue" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-utils" version = "0.6.6" @@ -254,6 +369,16 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crunchy" version = "0.1.6" @@ -272,6 +397,26 @@ dependencies = [ "crypto_api 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "csv" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.8.1" @@ -289,6 +434,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "env_logger" version = "0.6.2" @@ -310,6 +460,7 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "ff_derive 0.6.0", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -382,6 +533,14 @@ dependencies = [ "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hermit-abi" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hex" version = "0.3.2" @@ -404,6 +563,38 @@ dependencies = [ "proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "js-sys" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wasm-bindgen 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "jubjub" +version = "0.3.0" +dependencies = [ + "bls12_381 0.1.0", + "criterion 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -439,6 +630,11 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memchr" version = "2.3.3" @@ -492,6 +688,11 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "oorandom" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "opaque-debug" version = "0.2.3" @@ -502,10 +703,23 @@ name = "pairing" version = "0.16.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "ff 0.6.0", "group 0.6.0", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "plotters" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "js-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -713,6 +927,28 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rayon" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -732,6 +968,14 @@ dependencies = [ "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex-syntax" version = "0.6.16" @@ -755,6 +999,19 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ryu" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scopeguard" version = "1.0.0" @@ -781,6 +1038,31 @@ name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha2" version = "0.8.0" @@ -807,6 +1089,14 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -815,21 +1105,103 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tinytemplate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "typenum" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasi" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasm-bindgen" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bumpalo 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "web-sys" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "js-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.3.8" @@ -844,6 +1216,14 @@ name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -884,6 +1264,7 @@ dependencies = [ "blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "blake2s_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "crypto_api_chachapoly 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ff 0.6.0", "fpe 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -898,6 +1279,7 @@ dependencies = [ "ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "secp256k1 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -923,7 +1305,9 @@ dependencies = [ "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" "checksum assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" +"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0089c35ab7c6f2bc55ab23f769913f0ac65b1023e7e74638a1f43128dd5df2" "checksum bigint 4.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebecac13b3c745150d7b6c3ea7572d372f09d627c2077e893bf26c5c7f70d282" "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" @@ -933,24 +1317,36 @@ dependencies = [ "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" "checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" +"checksum bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" +"checksum bumpalo 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" +"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" "checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" +"checksum criterion 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1" +"checksum criterion-plot 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a01e15e0ea58e8234f96146b1f91fa9d0e4dd7a38da93ff7a75d42c0b9d3a545" "checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c" "checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" -"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" +"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" "checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" +"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" "checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" "checksum crypto_api 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f855e87e75a4799e18b8529178adcde6fd4f97c1449ff4821e747ff728bb102" "checksum crypto_api_chachapoly 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95b2ad7cab08fd71addba81df5077c49df208effdfb3118a1519f9cdeac5aaf2" +"checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +"checksum csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72d337a64190607d4fcca2cb78982c5dd57f4916e19696b48a575fa746b6cb0f" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fpe 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "21988a326139165b75e3196bc6962ca638e5fb0c95102fbf152a3743174b01e4" @@ -959,12 +1355,17 @@ dependencies = [ "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" +"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum hex-literal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" "checksum hex-literal-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d4c5c844e2fee0bf673d54c2c177f1713b3d2af2ff6e666b49cb7572e6cf42d" +"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +"checksum js-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" @@ -972,7 +1373,9 @@ dependencies = [ "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum oorandom 11.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +"checksum plotters 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "4e3bb8da247d27ae212529352020f3e5ee16e83c0c258061d27b08ab92675eeb" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8" "checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8" @@ -996,22 +1399,41 @@ dependencies = [ "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" +"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" "checksum regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" "checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum secp256k1 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e0344a794ff109f85547039536028e12f313178ac1545e49fdf16a530d900a7b" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +"checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +"checksum tinytemplate 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" +"checksum wasm-bindgen 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d" +"checksum wasm-bindgen-backend 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8" +"checksum wasm-bindgen-macro 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205" +"checksum wasm-bindgen-macro-support 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d" +"checksum wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8" +"checksum web-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)" = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index a147250..9681c58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ members = [ "zcash_history", "zcash_primitives", "zcash_proofs", + "jubjub", + "bls12_381", ] [profile.release] diff --git a/bellman/Cargo.toml b/bellman/Cargo.toml index f329609..b056b6c 100644 --- a/bellman/Cargo.toml +++ b/bellman/Cargo.toml @@ -21,6 +21,7 @@ crossbeam = { version = "0.7", optional = true } pairing = { version = "0.16", path = "../pairing", optional = true } rand_core = "0.5" byteorder = "1" +subtle = "2.2.1" [dev-dependencies] hex-literal = "0.2" diff --git a/bellman/src/domain.rs b/bellman/src/domain.rs index ddba4f4..0e9192e 100644 --- a/bellman/src/domain.rs +++ b/bellman/src/domain.rs @@ -13,6 +13,7 @@ use ff::{Field, PrimeField, ScalarEngine}; use group::CurveProjective; +use std::ops::{AddAssign, MulAssign, SubAssign}; use super::SynthesisError; @@ -62,7 +63,7 @@ impl> EvaluationDomain { // Compute omega, the 2^exp primitive root of unity let mut omega = E::Fr::root_of_unity(); for _ in exp..E::Fr::S { - omega.square(); + omega = omega.square(); } // Extend the coeffs vector with zeroes if necessary @@ -72,11 +73,11 @@ impl> EvaluationDomain { coeffs, exp, omega, - omegainv: omega.inverse().unwrap(), - geninv: E::Fr::multiplicative_generator().inverse().unwrap(), + omegainv: omega.invert().unwrap(), + geninv: E::Fr::multiplicative_generator().invert().unwrap(), minv: E::Fr::from_str(&format!("{}", m)) .unwrap() - .inverse() + .invert() .unwrap(), }) } @@ -105,7 +106,7 @@ impl> EvaluationDomain { worker.scope(self.coeffs.len(), |scope, chunk| { for (i, v) in self.coeffs.chunks_mut(chunk).enumerate() { scope.spawn(move |_scope| { - let mut u = g.pow(&[(i * chunk) as u64]); + let mut u = g.pow_vartime(&[(i * chunk) as u64]); for v in v.iter_mut() { v.group_mul_assign(&u); u.mul_assign(&g); @@ -130,7 +131,7 @@ impl> EvaluationDomain { /// This evaluates t(tau) for this domain, which is /// tau^m - 1 for these radix-2 domains. pub fn z(&self, tau: &E::Fr) -> E::Fr { - let mut tmp = tau.pow(&[self.coeffs.len() as u64]); + let mut tmp = tau.pow_vartime(&[self.coeffs.len() as u64]); tmp.sub_assign(&E::Fr::one()); tmp @@ -140,10 +141,7 @@ impl> EvaluationDomain { /// evaluation domain, so we must perform division over /// a coset. pub fn divide_by_z_on_coset(&mut self, worker: &Worker) { - let i = self - .z(&E::Fr::multiplicative_generator()) - .inverse() - .unwrap(); + let i = self.z(&E::Fr::multiplicative_generator()).invert().unwrap(); worker.scope(self.coeffs.len(), |scope, chunk| { for v in self.coeffs.chunks_mut(chunk) { @@ -296,7 +294,7 @@ fn serial_fft>(a: &mut [T], omega: &E::Fr, log_n: u let mut m = 1; for _ in 0..log_n { - let w_m = omega.pow(&[u64::from(n / (2 * m))]); + let w_m = omega.pow_vartime(&[u64::from(n / (2 * m))]); let mut k = 0; while k < n { @@ -330,7 +328,7 @@ fn parallel_fft>( let num_cpus = 1 << log_cpus; let log_new_n = log_n - log_cpus; let mut tmp = vec![vec![T::group_zero(); 1 << log_new_n]; num_cpus]; - let new_omega = omega.pow(&[num_cpus as u64]); + let new_omega = omega.pow_vartime(&[num_cpus as u64]); worker.scope(0, |scope, _| { let a = &*a; @@ -338,8 +336,8 @@ fn parallel_fft>( for (j, tmp) in tmp.iter_mut().enumerate() { scope.spawn(move |_scope| { // Shuffle into a sub-FFT - let omega_j = omega.pow(&[j as u64]); - let omega_step = omega.pow(&[(j as u64) << log_new_n]); + let omega_j = omega.pow_vartime(&[j as u64]); + let omega_step = omega.pow_vartime(&[(j as u64) << log_new_n]); let mut elt = E::Fr::one(); for (i, tmp) in tmp.iter_mut().enumerate() { diff --git a/bellman/src/gadgets/lookup.rs b/bellman/src/gadgets/lookup.rs index b83844d..bde86e2 100644 --- a/bellman/src/gadgets/lookup.rs +++ b/bellman/src/gadgets/lookup.rs @@ -1,6 +1,7 @@ //! Window table lookup gadgets. use ff::{Field, ScalarEngine}; +use std::ops::{AddAssign, Neg}; use super::boolean::Boolean; use super::num::{AllocatedNum, Num}; @@ -15,8 +16,7 @@ where assert_eq!(assignment.len(), 1 << window_size); for (i, constant) in constants.into_iter().enumerate() { - let mut cur = assignment[i]; - cur.negate(); + let mut cur = assignment[i].neg(); cur.add_assign(constant); assignment[i] = cur; for (j, eval) in assignment.iter_mut().enumerate().skip(i + 1) { @@ -150,7 +150,7 @@ where let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { let mut tmp = coords[*i.get()?].1; if *bits[2].get_value().get()? { - tmp.negate(); + tmp = tmp.neg(); } Ok(tmp) })?; @@ -280,7 +280,7 @@ mod test { assert_eq!(res.0.get_value().unwrap(), points[index].0); let mut tmp = points[index].1; if c_val { - tmp.negate() + tmp = tmp.neg() } assert_eq!(res.1.get_value().unwrap(), tmp); } diff --git a/bellman/src/gadgets/multieq.rs b/bellman/src/gadgets/multieq.rs index d052822..37b2d94 100644 --- a/bellman/src/gadgets/multieq.rs +++ b/bellman/src/gadgets/multieq.rs @@ -50,7 +50,9 @@ impl> MultiEq { assert!((E::Fr::CAPACITY as usize) > (self.bits_used + num_bits)); - let coeff = E::Fr::from_str("2").unwrap().pow(&[self.bits_used as u64]); + let coeff = E::Fr::from_str("2") + .unwrap() + .pow_vartime(&[self.bits_used as u64]); self.lhs = self.lhs.clone() + (coeff, lhs); self.rhs = self.rhs.clone() + (coeff, rhs); self.bits_used += num_bits; diff --git a/bellman/src/gadgets/multipack.rs b/bellman/src/gadgets/multipack.rs index c0dc50e..8ee6a15 100644 --- a/bellman/src/gadgets/multipack.rs +++ b/bellman/src/gadgets/multipack.rs @@ -5,6 +5,7 @@ use super::num::Num; use super::Assignment; use crate::{ConstraintSystem, SynthesisError}; use ff::{Field, PrimeField, ScalarEngine}; +use std::ops::AddAssign; /// Takes a sequence of booleans and exposes them as compact /// public inputs @@ -19,7 +20,7 @@ where for bit in bits { num = num.add_bool_with_coeff(CS::one(), bit, coeff); - coeff.double(); + coeff = coeff.double(); } let input = cs.alloc_input(|| format!("input {}", i), || Ok(*num.get_value().get()?))?; @@ -62,7 +63,7 @@ pub fn compute_multipacking(bits: &[bool]) -> Vec { cur.add_assign(&coeff); } - coeff.double(); + coeff = coeff.double(); } result.push(cur); diff --git a/bellman/src/gadgets/num.rs b/bellman/src/gadgets/num.rs index 8be5448..e460d20 100644 --- a/bellman/src/gadgets/num.rs +++ b/bellman/src/gadgets/num.rs @@ -1,6 +1,7 @@ //! Gadgets representing numbers in the scalar field of the underlying curve. use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, ScalarEngine}; +use std::ops::{AddAssign, MulAssign}; use crate::{ConstraintSystem, LinearCombination, SynthesisError, Variable}; @@ -176,7 +177,7 @@ impl AllocatedNum { for bit in result.iter().rev() { lc = lc + (coeff, bit.get_variable()); - coeff.double(); + coeff = coeff.double(); } lc = lc - self.variable; @@ -202,7 +203,7 @@ impl AllocatedNum { for bit in bits.iter() { lc = lc + (coeff, bit.get_variable()); - coeff.double(); + coeff = coeff.double(); } lc = lc - self.variable; @@ -253,8 +254,7 @@ impl AllocatedNum { let var = cs.alloc( || "squared num", || { - let mut tmp = *self.value.get()?; - tmp.square(); + let tmp = self.value.get()?.square(); value = Some(tmp); @@ -288,7 +288,7 @@ impl AllocatedNum { if tmp.is_zero() { Err(SynthesisError::DivisionByZero) } else { - Ok(tmp.inverse().unwrap()) + Ok(tmp.invert().unwrap()) } }, )?; @@ -416,6 +416,7 @@ mod test { use pairing::bls12_381::{Bls12, Fr}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; + use std::ops::{Neg, SubAssign}; use super::{AllocatedNum, Boolean}; use crate::gadgets::test::*; @@ -517,8 +518,7 @@ mod test { #[test] fn test_into_bits_strict() { - let mut negone = Fr::one(); - negone.negate(); + let negone = Fr::one().neg(); let mut cs = TestConstraintSystem::::new(); diff --git a/bellman/src/gadgets/test/mod.rs b/bellman/src/gadgets/test/mod.rs index 47392f1..0a37cd1 100644 --- a/bellman/src/gadgets/test/mod.rs +++ b/bellman/src/gadgets/test/mod.rs @@ -6,6 +6,7 @@ use crate::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable use std::collections::HashMap; use std::fmt::Write; +use std::ops::{AddAssign, MulAssign, Neg}; use byteorder::{BigEndian, ByteOrder}; use std::cmp::Ordering; @@ -151,14 +152,10 @@ impl TestConstraintSystem { pub fn pretty_print(&self) -> String { let mut s = String::new(); - let negone = { - let mut tmp = E::Fr::one(); - tmp.negate(); - tmp - }; + let negone = E::Fr::one().neg(); let powers_of_two = (0..E::Fr::NUM_BITS) - .map(|i| E::Fr::from_str("2").unwrap().pow(&[u64::from(i)])) + .map(|i| E::Fr::from_str("2").unwrap().pow_vartime(&[u64::from(i)])) .collect::>(); let pp = |s: &mut String, lc: &LinearCombination| { diff --git a/bellman/src/gadgets/uint32.rs b/bellman/src/gadgets/uint32.rs index 8bf4558..16bb651 100644 --- a/bellman/src/gadgets/uint32.rs +++ b/bellman/src/gadgets/uint32.rs @@ -330,7 +330,7 @@ impl UInt32 { all_constants &= bit.is_constant(); - coeff.double(); + coeff = coeff.double(); } } @@ -368,7 +368,7 @@ impl UInt32 { max_value >>= 1; i += 1; - coeff.double(); + coeff = coeff.double(); } // Enforce equality between the sum and result diff --git a/bellman/src/groth16/generator.rs b/bellman/src/groth16/generator.rs index 767eddd..d993835 100644 --- a/bellman/src/groth16/generator.rs +++ b/bellman/src/groth16/generator.rs @@ -1,5 +1,5 @@ use rand_core::RngCore; - +use std::ops::{AddAssign, MulAssign}; use std::sync::Arc; use ff::{Field, PrimeField}; @@ -215,8 +215,22 @@ where assembly.num_inputs + assembly.num_aux }); - let gamma_inverse = gamma.inverse().ok_or(SynthesisError::UnexpectedIdentity)?; - let delta_inverse = delta.inverse().ok_or(SynthesisError::UnexpectedIdentity)?; + let gamma_inverse = { + let inverse = gamma.invert(); + if bool::from(inverse.is_some()) { + Ok(inverse.unwrap()) + } else { + Err(SynthesisError::UnexpectedIdentity) + } + }?; + let delta_inverse = { + let inverse = delta.invert(); + if bool::from(inverse.is_some()) { + Ok(inverse.unwrap()) + } else { + Err(SynthesisError::UnexpectedIdentity) + } + }?; let worker = Worker::new(); @@ -228,7 +242,7 @@ where worker.scope(powers_of_tau.len(), |scope, chunk| { for (i, powers_of_tau) in powers_of_tau.chunks_mut(chunk).enumerate() { scope.spawn(move |_scope| { - let mut current_tau_power = tau.pow(&[(i * chunk) as u64]); + let mut current_tau_power = tau.pow_vartime(&[(i * chunk) as u64]); for p in powers_of_tau { p.0 = current_tau_power; diff --git a/bellman/src/groth16/mod.rs b/bellman/src/groth16/mod.rs index 1ff152d..6f5af85 100644 --- a/bellman/src/groth16/mod.rs +++ b/bellman/src/groth16/mod.rs @@ -474,6 +474,7 @@ mod test_with_bls12_381 { use ff::Field; use pairing::bls12_381::{Bls12, Fr}; use rand::thread_rng; + use std::ops::MulAssign; #[test] fn serialization() { diff --git a/bellman/src/groth16/prover.rs b/bellman/src/groth16/prover.rs index 7fe282f..c31b4db 100644 --- a/bellman/src/groth16/prover.rs +++ b/bellman/src/groth16/prover.rs @@ -1,5 +1,5 @@ use rand_core::RngCore; - +use std::ops::{AddAssign, MulAssign}; use std::sync::Arc; use futures::Future; @@ -314,34 +314,34 @@ where } let mut g_a = vk.delta_g1.mul(r); - g_a.add_assign_mixed(&vk.alpha_g1); + AddAssign::<&E::G1Affine>::add_assign(&mut g_a, &vk.alpha_g1); let mut g_b = vk.delta_g2.mul(s); - g_b.add_assign_mixed(&vk.beta_g2); + AddAssign::<&E::G2Affine>::add_assign(&mut g_b, &vk.beta_g2); let mut g_c; { let mut rs = r; rs.mul_assign(&s); g_c = vk.delta_g1.mul(rs); - g_c.add_assign(&vk.alpha_g1.mul(s)); - g_c.add_assign(&vk.beta_g1.mul(r)); + AddAssign::<&E::G1>::add_assign(&mut g_c, &vk.alpha_g1.mul(s)); + AddAssign::<&E::G1>::add_assign(&mut g_c, &vk.beta_g1.mul(r)); } let mut a_answer = a_inputs.wait()?; - a_answer.add_assign(&a_aux.wait()?); - g_a.add_assign(&a_answer); + AddAssign::<&E::G1>::add_assign(&mut a_answer, &a_aux.wait()?); + AddAssign::<&E::G1>::add_assign(&mut g_a, &a_answer); a_answer.mul_assign(s); - g_c.add_assign(&a_answer); + AddAssign::<&E::G1>::add_assign(&mut g_c, &a_answer); - let mut b1_answer = b_g1_inputs.wait()?; - b1_answer.add_assign(&b_g1_aux.wait()?); + let mut b1_answer: E::G1 = b_g1_inputs.wait()?; + AddAssign::<&E::G1>::add_assign(&mut b1_answer, &b_g1_aux.wait()?); let mut b2_answer = b_g2_inputs.wait()?; - b2_answer.add_assign(&b_g2_aux.wait()?); + AddAssign::<&E::G2>::add_assign(&mut b2_answer, &b_g2_aux.wait()?); - g_b.add_assign(&b2_answer); + AddAssign::<&E::G2>::add_assign(&mut g_b, &b2_answer); b1_answer.mul_assign(r); - g_c.add_assign(&b1_answer); - g_c.add_assign(&h.wait()?); - g_c.add_assign(&l.wait()?); + AddAssign::<&E::G1>::add_assign(&mut g_c, &b1_answer); + AddAssign::<&E::G1>::add_assign(&mut g_c, &h.wait()?); + AddAssign::<&E::G1>::add_assign(&mut g_c, &l.wait()?); Ok(Proof { a: g_a.into_affine(), diff --git a/bellman/src/groth16/tests/dummy_engine.rs b/bellman/src/groth16/tests/dummy_engine.rs index 7311545..6c011c3 100644 --- a/bellman/src/groth16/tests/dummy_engine.rs +++ b/bellman/src/groth16/tests/dummy_engine.rs @@ -1,7 +1,4 @@ -use ff::{ - Field, LegendreSymbol, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, ScalarEngine, - SqrtField, -}; +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, ScalarEngine, SqrtField}; use group::{CurveAffine, CurveProjective, EncodedPoint, GroupDecodingError}; use pairing::{Engine, PairingCurveAffine}; @@ -9,18 +6,143 @@ use rand_core::RngCore; use std::cmp::Ordering; use std::fmt; use std::num::Wrapping; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; const MODULUS_R: Wrapping = Wrapping(64513); #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Fr(Wrapping); +impl Default for Fr { + fn default() -> Self { + ::zero() + } +} + +impl ConstantTimeEq for Fr { + fn ct_eq(&self, other: &Fr) -> Choice { + (self.0).0.ct_eq(&(other.0).0) + } +} + impl fmt::Display for Fr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", (self.0).0) } } +impl ConditionallySelectable for Fr { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fr(Wrapping(u32::conditional_select( + &(a.0).0, + &(b.0).0, + choice, + ))) + } +} + +impl Neg for Fr { + type Output = Self; + + fn neg(mut self) -> Self { + if !::is_zero(&self) { + self.0 = MODULUS_R - self.0; + } + self + } +} + +impl<'r> Add<&'r Fr> for Fr { + type Output = Self; + + fn add(self, other: &Self) -> Self { + let mut ret = self; + AddAssign::add_assign(&mut ret, other); + ret + } +} + +impl Add for Fr { + type Output = Self; + + fn add(self, other: Self) -> Self { + self + &other + } +} + +impl<'r> AddAssign<&'r Fr> for Fr { + fn add_assign(&mut self, other: &Self) { + self.0 = (self.0 + other.0) % MODULUS_R; + } +} + +impl AddAssign for Fr { + fn add_assign(&mut self, other: Self) { + AddAssign::add_assign(self, &other); + } +} + +impl<'r> Sub<&'r Fr> for Fr { + type Output = Self; + + fn sub(self, other: &Self) -> Self { + let mut ret = self; + SubAssign::sub_assign(&mut ret, other); + ret + } +} + +impl Sub for Fr { + type Output = Self; + + fn sub(self, other: Self) -> Self { + self - &other + } +} + +impl<'r> SubAssign<&'r Fr> for Fr { + fn sub_assign(&mut self, other: &Self) { + self.0 = ((MODULUS_R + self.0) - other.0) % MODULUS_R; + } +} + +impl SubAssign for Fr { + fn sub_assign(&mut self, other: Self) { + SubAssign::sub_assign(self, &other); + } +} + +impl<'r> Mul<&'r Fr> for Fr { + type Output = Self; + + fn mul(self, other: &Self) -> Self { + let mut ret = self; + MulAssign::mul_assign(&mut ret, other); + ret + } +} + +impl Mul for Fr { + type Output = Self; + + fn mul(self, other: Self) -> Self { + self * &other + } +} + +impl<'r> MulAssign<&'r Fr> for Fr { + fn mul_assign(&mut self, other: &Self) { + self.0 = (self.0 * other.0) % MODULUS_R; + } +} + +impl MulAssign for Fr { + fn mul_assign(&mut self, other: Self) { + MulAssign::mul_assign(self, &other); + } +} + impl Field for Fr { fn random(rng: &mut R) -> Self { Fr(Wrapping(rng.next_u32()) % MODULUS_R) @@ -38,37 +160,22 @@ impl Field for Fr { (self.0).0 == 0 } - fn square(&mut self) { - self.0 = (self.0 * self.0) % MODULUS_R; + fn square(&self) -> Self { + Fr((self.0 * self.0) % MODULUS_R) } - fn double(&mut self) { - self.0 = (self.0 << 1) % MODULUS_R; + fn double(&self) -> Self { + Fr((self.0 << 1) % MODULUS_R) } - fn negate(&mut self) { - if !::is_zero(self) { - self.0 = MODULUS_R - self.0; - } - } - - fn add_assign(&mut self, other: &Self) { - self.0 = (self.0 + other.0) % MODULUS_R; - } - - fn sub_assign(&mut self, other: &Self) { - self.0 = ((MODULUS_R + self.0) - other.0) % MODULUS_R; - } - - fn mul_assign(&mut self, other: &Self) { - self.0 = (self.0 * other.0) % MODULUS_R; - } - - fn inverse(&self) -> Option { + fn invert(&self) -> CtOption { if ::is_zero(self) { - None + CtOption::new(::zero(), Choice::from(0)) } else { - Some(self.pow(&[(MODULUS_R.0 as u64) - 2])) + CtOption::new( + self.pow_vartime(&[(MODULUS_R.0 as u64) - 2]), + Choice::from(1), + ) } } @@ -78,58 +185,39 @@ impl Field for Fr { } impl SqrtField for Fr { - fn legendre(&self) -> LegendreSymbol { - // s = self^((r - 1) // 2) - let s = self.pow([32256]); - if s == ::zero() { - LegendreSymbol::Zero - } else if s == ::one() { - LegendreSymbol::QuadraticResidue - } else { - LegendreSymbol::QuadraticNonResidue - } - } - - fn sqrt(&self) -> Option { + fn sqrt(&self) -> CtOption { // Tonelli-Shank's algorithm for q mod 16 = 1 // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) - match self.legendre() { - LegendreSymbol::Zero => Some(*self), - LegendreSymbol::QuadraticNonResidue => None, - LegendreSymbol::QuadraticResidue => { - let mut c = Fr::root_of_unity(); - // r = self^((t + 1) // 2) - let mut r = self.pow([32]); - // t = self^t - let mut t = self.pow([63]); - let mut m = Fr::S; + let mut c = Fr::root_of_unity(); + // r = self^((t + 1) // 2) + let mut r = self.pow_vartime([32]); + // t = self^t + let mut t = self.pow_vartime([63]); + let mut m = Fr::S; - while t != ::one() { - let mut i = 1; - { - let mut t2i = t; - t2i.square(); - loop { - if t2i == ::one() { - break; - } - t2i.square(); - i += 1; - } + while t != ::one() { + let mut i = 1; + { + let mut t2i = t.square(); + loop { + if t2i == ::one() { + break; } - - for _ in 0..(m - i - 1) { - c.square(); - } - ::mul_assign(&mut r, &c); - c.square(); - ::mul_assign(&mut t, &c); - m = i; + t2i = t2i.square(); + i += 1; } - - Some(r) } + + for _ in 0..(m - i - 1) { + c = c.square(); + } + MulAssign::mul_assign(&mut r, &c); + c = c.square(); + MulAssign::mul_assign(&mut t, &c); + m = i; } + + CtOption::new(r, (r * r).ct_eq(self)) } } @@ -226,7 +314,7 @@ impl PrimeField for Fr { fn from_repr(repr: FrRepr) -> Result { if repr.0[0] >= (MODULUS_R.0 as u64) { - Err(PrimeFieldDecodingError::NotInField(format!("{}", repr))) + Err(PrimeFieldDecodingError::NotInField) } else { Ok(Fr(Wrapping(repr.0[0] as u32))) } @@ -280,16 +368,16 @@ impl Engine for DummyEngine { for &(a, b) in i { let mut tmp = *a; - ::mul_assign(&mut tmp, b); - ::add_assign(&mut acc, &tmp); + MulAssign::mul_assign(&mut tmp, b); + AddAssign::add_assign(&mut acc, &tmp); } acc } /// Perform final exponentiation of the result of a miller loop. - fn final_exponentiation(this: &Self::Fqk) -> Option { - Some(*this) + fn final_exponentiation(this: &Self::Fqk) -> CtOption { + CtOption::new(*this, Choice::from(1)) } } @@ -322,25 +410,13 @@ impl CurveProjective for Fr { } fn double(&mut self) { - ::double(self); - } - - fn add_assign(&mut self, other: &Self) { - ::add_assign(self, other); - } - - fn add_assign_mixed(&mut self, other: &Self) { - ::add_assign(self, other); - } - - fn negate(&mut self) { - ::negate(self); + self.0 = ::double(self).0; } fn mul_assign::Repr>>(&mut self, other: S) { let tmp = Fr::from_repr(other.into()).unwrap(); - ::mul_assign(self, &tmp); + MulAssign::mul_assign(self, &tmp); } fn into_affine(&self) -> Fr { @@ -415,15 +491,11 @@ impl CurveAffine for Fr { ::is_zero(self) } - fn negate(&mut self) { - ::negate(self); - } - fn mul::Repr>>(&self, other: S) -> Self::Projective { let mut res = *self; let tmp = Fr::from_repr(other.into()).unwrap(); - ::mul_assign(&mut res, &tmp); + MulAssign::mul_assign(&mut res, &tmp); res } diff --git a/bellman/src/groth16/tests/mod.rs b/bellman/src/groth16/tests/mod.rs index d8be98e..5c2f02d 100644 --- a/bellman/src/groth16/tests/mod.rs +++ b/bellman/src/groth16/tests/mod.rs @@ -5,6 +5,7 @@ mod dummy_engine; use self::dummy_engine::*; use std::marker::PhantomData; +use std::ops::{AddAssign, MulAssign, SubAssign}; use crate::{Circuit, ConstraintSystem, SynthesisError}; @@ -126,22 +127,22 @@ fn test_xordemo() { let mut root_of_unity = Fr::root_of_unity(); // We expect this to be a 2^10 root of unity - assert_eq!(Fr::one(), root_of_unity.pow(&[1 << 10])); + assert_eq!(Fr::one(), root_of_unity.pow_vartime(&[1 << 10])); // Let's turn it into a 2^3 root of unity. - root_of_unity = root_of_unity.pow(&[1 << 7]); - assert_eq!(Fr::one(), root_of_unity.pow(&[1 << 3])); + root_of_unity = root_of_unity.pow_vartime(&[1 << 7]); + assert_eq!(Fr::one(), root_of_unity.pow_vartime(&[1 << 3])); assert_eq!(Fr::from_str("20201").unwrap(), root_of_unity); // Let's compute all the points in our evaluation domain. let mut points = Vec::with_capacity(8); for i in 0..8 { - points.push(root_of_unity.pow(&[i])); + points.push(root_of_unity.pow_vartime(&[i])); } // Let's compute t(tau) = (tau - p_0)(tau - p_1)... // = tau^8 - 1 - let mut t_at_tau = tau.pow(&[8]); + let mut t_at_tau = tau.pow_vartime(&[8]); t_at_tau.sub_assign(&Fr::one()); { let mut tmp = Fr::one(); @@ -155,8 +156,8 @@ fn test_xordemo() { // We expect our H query to be 7 elements of the form... // {tau^i t(tau) / delta} - let delta_inverse = delta.inverse().unwrap(); - let gamma_inverse = gamma.inverse().unwrap(); + let delta_inverse = delta.invert().unwrap(); + let gamma_inverse = gamma.invert().unwrap(); { let mut coeff = delta_inverse; coeff.mul_assign(&t_at_tau); diff --git a/bellman/src/groth16/verifier.rs b/bellman/src/groth16/verifier.rs index 5bc0581..5983667 100644 --- a/bellman/src/groth16/verifier.rs +++ b/bellman/src/groth16/verifier.rs @@ -1,16 +1,15 @@ use ff::PrimeField; use group::{CurveAffine, CurveProjective}; use pairing::{Engine, PairingCurveAffine}; +use std::ops::{AddAssign, Neg}; use super::{PreparedVerifyingKey, Proof, VerifyingKey}; use crate::SynthesisError; pub fn prepare_verifying_key(vk: &VerifyingKey) -> PreparedVerifyingKey { - let mut gamma = vk.gamma_g2; - gamma.negate(); - let mut delta = vk.delta_g2; - delta.negate(); + let gamma = vk.gamma_g2.neg(); + let delta = vk.delta_g2.neg(); PreparedVerifyingKey { alpha_g1_beta_g2: E::pairing(vk.alpha_g1, vk.beta_g2), @@ -32,7 +31,7 @@ pub fn verify_proof<'a, E: Engine>( let mut acc = pvk.ic[0].into_projective(); for (i, b) in public_inputs.iter().zip(pvk.ic.iter().skip(1)) { - acc.add_assign(&b.mul(i.into_repr())); + AddAssign::<&E::G1>::add_assign(&mut acc, &b.mul(i.into_repr())); } // The original verification equation is: diff --git a/bellman/src/lib.rs b/bellman/src/lib.rs index ef13a83..1e48b0c 100644 --- a/bellman/src/lib.rs +++ b/bellman/src/lib.rs @@ -148,7 +148,7 @@ use std::error::Error; use std::fmt; use std::io; use std::marker::PhantomData; -use std::ops::{Add, Sub}; +use std::ops::{Add, MulAssign, Neg, Sub}; /// Computations are expressed in terms of arithmetic circuits, in particular /// rank-1 quadratic constraint systems. The `Circuit` trait represents a @@ -216,10 +216,8 @@ impl Sub<(E::Fr, Variable)> for LinearCombination { type Output = LinearCombination; #[allow(clippy::suspicious_arithmetic_impl)] - fn sub(self, (mut coeff, var): (E::Fr, Variable)) -> LinearCombination { - coeff.negate(); - - self + (coeff, var) + fn sub(self, (coeff, var): (E::Fr, Variable)) -> LinearCombination { + self + (coeff.neg(), var) } } diff --git a/bellman/src/multiexp.rs b/bellman/src/multiexp.rs index b7729a8..0bc61ba 100644 --- a/bellman/src/multiexp.rs +++ b/bellman/src/multiexp.rs @@ -5,6 +5,7 @@ use futures::Future; use group::{CurveAffine, CurveProjective}; use std::io; use std::iter; +use std::ops::AddAssign; use std::sync::Arc; use super::SynthesisError; @@ -18,16 +19,24 @@ pub trait SourceBuilder: Send + Sync + 'static + Clone { /// A source of bases, like an iterator. pub trait Source { - /// Parses the element from the source. Fails if the point is at infinity. - fn add_assign_mixed( - &mut self, - to: &mut ::Projective, - ) -> Result<(), SynthesisError>; + fn next(&mut self) -> Result<&G, SynthesisError>; /// Skips `amt` elements from the source, avoiding deserialization. fn skip(&mut self, amt: usize) -> Result<(), SynthesisError>; } +pub trait AddAssignFromSource: CurveProjective { + /// Parses the element from the source. Fails if the point is at infinity. + fn add_assign_from_source::Affine>>( + &mut self, + source: &mut S, + ) -> Result<(), SynthesisError> { + AddAssign::<&::Affine>::add_assign(self, source.next()?); + Ok(()) + } +} +impl AddAssignFromSource for G where G: CurveProjective {} + impl SourceBuilder for (Arc>, usize) { type Source = (Arc>, usize); @@ -37,10 +46,7 @@ impl SourceBuilder for (Arc>, usize) { } impl Source for (Arc>, usize) { - fn add_assign_mixed( - &mut self, - to: &mut ::Projective, - ) -> Result<(), SynthesisError> { + fn next(&mut self) -> Result<&G, SynthesisError> { if self.0.len() <= self.1 { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, @@ -53,11 +59,10 @@ impl Source for (Arc>, usize) { return Err(SynthesisError::UnexpectedIdentity); } - to.add_assign_mixed(&self.0[self.1]); - + let ret = &self.0[self.1]; self.1 += 1; - Ok(()) + Ok(ret) } fn skip(&mut self, amt: usize) -> Result<(), SynthesisError> { @@ -153,12 +158,12 @@ fn multiexp_inner( mut skip: u32, c: u32, handle_trivial: bool, -) -> Box::Projective, Error = SynthesisError>> +) -> Box> where for<'a> &'a Q: QueryDensity, D: Send + Sync + 'static + Clone + AsRef, - G: CurveAffine, - S: SourceBuilder, + G: CurveProjective, + S: SourceBuilder<::Affine>, { // Perform this region of the multiexp let this = { @@ -168,13 +173,13 @@ where pool.compute(move || { // Accumulate the result - let mut acc = G::Projective::zero(); + let mut acc = G::zero(); // Build a source for the bases let mut bases = bases.new(); // Create space for the buckets - let mut buckets = vec![::Projective::zero(); (1 << c) - 1]; + let mut buckets = vec![G::zero(); (1 << c) - 1]; let zero = ::Fr::zero().into_repr(); let one = ::Fr::one().into_repr(); @@ -186,7 +191,7 @@ where bases.skip(1)?; } else if exp == one { if handle_trivial { - bases.add_assign_mixed(&mut acc)?; + acc.add_assign_from_source(&mut bases)?; } else { bases.skip(1)?; } @@ -196,7 +201,8 @@ where let exp = exp.as_ref()[0] % (1 << c); if exp != 0 { - bases.add_assign_mixed(&mut buckets[(exp - 1) as usize])?; + (&mut buckets[(exp - 1) as usize]) + .add_assign_from_source(&mut bases)?; } else { bases.skip(1)?; } @@ -208,7 +214,7 @@ where // e.g. 3a + 2b + 1c = a + // (a) + b + // ((a) + b) + c - let mut running_sum = G::Projective::zero(); + let mut running_sum = G::zero(); for exp in buckets.into_iter().rev() { running_sum.add_assign(&exp); acc.add_assign(&running_sum); @@ -236,7 +242,7 @@ where c, false, )) - .map(move |(this, mut higher)| { + .map(move |(this, mut higher): (_, G)| { for _ in 0..c { higher.double(); } @@ -256,12 +262,12 @@ pub fn multiexp( bases: S, density_map: D, exponents: Arc::Fr as PrimeField>::Repr>>, -) -> Box::Projective, Error = SynthesisError>> +) -> Box> where for<'a> &'a Q: QueryDensity, D: Send + Sync + 'static + Clone + AsRef, - G: CurveAffine, - S: SourceBuilder, + G: CurveProjective, + S: SourceBuilder<::Affine>, { let c = if exponents.len() < 32 { 3u32 @@ -282,16 +288,16 @@ where #[cfg(feature = "pairing")] #[test] fn test_with_bls12() { - fn naive_multiexp( - bases: Arc>, + fn naive_multiexp( + bases: Arc::Affine>>, exponents: Arc::Repr>>, - ) -> G::Projective { + ) -> G { assert_eq!(bases.len(), exponents.len()); - let mut acc = G::Projective::zero(); + let mut acc = G::zero(); for (base, exp) in bases.iter().zip(exponents.iter()) { - acc.add_assign(&base.mul(*exp)); + AddAssign::<&G>::add_assign(&mut acc, &base.mul(*exp)); } acc @@ -314,7 +320,7 @@ fn test_with_bls12() { .collect::>(), ); - let naive = naive_multiexp(g.clone(), v.clone()); + let naive: ::G1 = naive_multiexp(g.clone(), v.clone()); let pool = Worker::new(); diff --git a/bellman/tests/mimc.rs b/bellman/tests/mimc.rs index e9a4c7c..a1de0f1 100644 --- a/bellman/tests/mimc.rs +++ b/bellman/tests/mimc.rs @@ -7,6 +7,7 @@ use std::time::{Duration, Instant}; // Bring in some tools for using pairing-friendly curves use ff::{Field, ScalarEngine}; use pairing::Engine; +use std::ops::{AddAssign, MulAssign}; // We're going to use the BLS12-381 pairing-friendly elliptic curve. use pairing::bls12_381::Bls12; @@ -40,8 +41,7 @@ fn mimc(mut xl: E::Fr, mut xr: E::Fr, constants: &[E::Fr]) -> E::Fr { for i in 0..MIMC_ROUNDS { let mut tmp1 = xl; tmp1.add_assign(&constants[i]); - let mut tmp2 = tmp1; - tmp2.square(); + let mut tmp2 = tmp1.square(); tmp2.mul_assign(&tmp1); tmp2.add_assign(&xr); xr = xl; @@ -87,8 +87,7 @@ impl<'a, E: Engine> Circuit for MiMCDemo<'a, E> { // tmp = (xL + Ci)^2 let tmp_value = xl_value.map(|mut e| { e.add_assign(&self.constants[i]); - e.square(); - e + e.square() }); let tmp = cs.alloc( || "tmp", diff --git a/bls12_381/.github/workflows/ci.yml b/bls12_381/.github/workflows/ci.yml new file mode 100644 index 0000000..39066db --- /dev/null +++ b/bls12_381/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +name: CI checks + +on: [push, pull_request] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + + # Ensure all code has been formatted with rustfmt + - run: rustup component add rustfmt + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check --color always + + test: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build tests + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --tests + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release + + no-std: + name: Check no-std compatibility + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + - run: rustup target add thumbv6m-none-eabi + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --target thumbv6m-none-eabi --no-default-features --features groups,pairings + + doc-links: + name: Nightly lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + + # Ensure intra-documentation links all resolve correctly + # Requires #![deny(intra_doc_link_resolution_failure)] in crate. + - name: Check intra-doc links + uses: actions-rs/cargo@v1 + with: + command: doc + args: --document-private-items diff --git a/bls12_381/.gitignore b/bls12_381/.gitignore new file mode 100644 index 0000000..2f88dba --- /dev/null +++ b/bls12_381/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock \ No newline at end of file diff --git a/bls12_381/COPYRIGHT b/bls12_381/COPYRIGHT new file mode 100644 index 0000000..7764b86 --- /dev/null +++ b/bls12_381/COPYRIGHT @@ -0,0 +1,14 @@ +Copyrights in the "bls12_381" library are retained by their contributors. No +copyright assignment is required to contribute to the "bls12_381" library. + +The "bls12_381" library is licensed under either of + + * Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/bls12_381/Cargo.toml b/bls12_381/Cargo.toml new file mode 100644 index 0000000..6e77fb5 --- /dev/null +++ b/bls12_381/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Sean Bowe "] +description = "Implementation of the BLS12-381 pairing-friendly elliptic curve construction" +documentation = "https://docs.rs/bls12_381/" +homepage = "https://github.com/zkcrypto/bls12_381" +license = "MIT/Apache-2.0" +name = "bls12_381" +repository = "https://github.com/zkcrypto/bls12_381" +version = "0.1.0" +edition = "2018" + +[package.metadata.docs.rs] +rustdoc-args = [ "--html-in-header", "katex-header.html" ] + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "groups" +harness = false +required-features = ["groups"] + +[dependencies.subtle] +version = "2.2.1" +default-features = false + +[features] +default = ["groups", "pairings", "alloc"] +groups = [] +pairings = ["groups"] +alloc = [] +nightly = ["subtle/nightly"] diff --git a/bls12_381/LICENSE-APACHE b/bls12_381/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/bls12_381/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/bls12_381/LICENSE-MIT b/bls12_381/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/bls12_381/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/bls12_381/README.md b/bls12_381/README.md new file mode 100644 index 0000000..ba61f30 --- /dev/null +++ b/bls12_381/README.md @@ -0,0 +1,63 @@ +# bls12_381 [![Crates.io](https://img.shields.io/crates/v/bls12_381.svg)](https://crates.io/crates/bls12_381) # + +This crate provides an implementation of the BLS12-381 pairing-friendly elliptic curve construction. + +* **This implementation has not been reviewed or audited. Use at your own risk.** +* This implementation targets Rust `1.36` or later. +* This implementation does not require the Rust standard library. +* All operations are constant time unless explicitly noted. + +## Features + +* `groups` (on by default): Enables APIs for performing group arithmetic with G1, G2, and GT. +* `pairings` (on by default): Enables some APIs for performing pairings. +* `alloc` (on by default): Enables APIs that require an allocator; these include pairing optimizations. +* `nightly`: Enables `subtle/nightly` which tries to prevent compiler optimizations that could jeopardize constant time operations. Requires the nightly Rust compiler. + +## [Documentation](https://docs.rs/bls12_381) + +## Curve Description + +BLS12-381 is a pairing-friendly elliptic curve construction from the [BLS family](https://eprint.iacr.org/2002/088), with embedding degree 12. It is built over a 381-bit prime field `GF(p)` with... + +* z = `-0xd201000000010000` +* p = (z - 1)2(z4 - z2 + 1) / 3 + z + * = `0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab` +* q = z4 - z2 + 1 + * = `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` + +... yielding two **source groups** G1 and G2, each of 255-bit prime order `q`, such that an efficiently computable non-degenerate bilinear pairing function `e` exists into a third **target group** GT. Specifically, G1 is the `q`-order subgroup of E(Fp) : y2 = x3 + 4 and G2 is the `q`-order subgroup of E'(Fp2) : y2 = x3 + 4(u + 1) where the extention field Fp2 is defined as Fp(u) / (u2 + 1). + +BLS12-381 is chosen so that `z` has small Hamming weight (to improve pairing performance) and also so that `GF(q)` has a large 232 primitive root of unity for performing radix-2 fast Fourier transforms for efficient multi-point evaluation and interpolation. It is also chosen so that it exists in a particularly efficient and rigid subfamily of BLS12 curves. + +### Curve Security + +Pairing-friendly elliptic curve constructions are (necessarily) less secure than conventional elliptic curves due to their small "embedding degree". Given a small enough embedding degree, the pairing function itself would allow for a break in DLP hardness if it projected into a weak target group, as weaknesses in this target group are immediately translated into weaknesses in the source group. + +In order to achieve reasonable security without an unreasonably expensive pairing function, a careful choice of embedding degree, base field characteristic and prime subgroup order must be made. BLS12-381 uses an embedding degree of 12 to ensure fast pairing performance but a choice of a 381-bit base field characteristic to yeild a 255-bit subgroup order (for protection against [Pollard's rho algorithm](https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm)) while reaching close to a 128-bit security level. + +There are [known optimizations](https://ellipticnews.wordpress.com/2016/05/02/kim-barbulescu-variant-of-the-number-field-sieve-to-compute-discrete-logarithms-in-finite-fields/) of the [Number Field Sieve algorithm](https://en.wikipedia.org/wiki/General_number_field_sieve) which could be used to weaken DLP security in the target group by taking advantage of its structure, as it is a multiplicative subgroup of a low-degree extension field. However, these attacks require an (as of yet unknown) efficient algorithm for scanning a large space of polynomials. Even if the attack were practical it would only reduce security to roughly 117 to 120 bits. (This contrasts with 254-bit BN curves which usually have less than 100 bits of security in the same situation.) + +### Alternative Curves + +Applications may wish to exchange pairing performance and/or G2 performance by using BLS24 or KSS16 curves which conservatively target 128-bit security. In applications that need cycles of elliptic curves for e.g. arbitrary proof composition, MNT6/MNT4 curve cycles are known that target the 128-bit security level. In applications that only need fixed-depth proof composition, curves of this form have been constructed as part of Zexe. + +## Acknowledgements + +Please see `Cargo.toml` for a list of primary authors of this codebase. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/bls12_381/RELEASES.md b/bls12_381/RELEASES.md new file mode 100644 index 0000000..69afd52 --- /dev/null +++ b/bls12_381/RELEASES.md @@ -0,0 +1,3 @@ +# 0.1.0 + +Initial release. diff --git a/bls12_381/benches/groups.rs b/bls12_381/benches/groups.rs new file mode 100644 index 0000000..87c80d0 --- /dev/null +++ b/bls12_381/benches/groups.rs @@ -0,0 +1,170 @@ +#[macro_use] +extern crate criterion; + +extern crate bls12_381; +use bls12_381::*; + +use criterion::{black_box, Criterion}; + +fn criterion_benchmark(c: &mut Criterion) { + // Pairings + { + let g = G1Affine::generator(); + let h = G2Affine::generator(); + c.bench_function("full pairing", move |b| { + b.iter(|| pairing(black_box(&g), black_box(&h))) + }); + c.bench_function("G2 preparation for pairing", move |b| { + b.iter(|| G2Prepared::from(h)) + }); + let prep = G2Prepared::from(h); + c.bench_function("miller loop for pairing", move |b| { + b.iter(|| multi_miller_loop(&[(&g, &prep)])) + }); + let prep = G2Prepared::from(h); + let r = multi_miller_loop(&[(&g, &prep)]); + c.bench_function("final exponentiation for pairing", move |b| { + b.iter(|| r.final_exponentiation()) + }); + } + // G1Affine + { + let name = "G1Affine"; + let a = G1Affine::generator(); + let s = Scalar::from_raw([1, 2, 3, 4]); + let compressed = [0u8; 48]; + let uncompressed = [0u8; 96]; + c.bench_function(&format!("{} check on curve", name), move |b| { + b.iter(|| black_box(a).is_on_curve()) + }); + c.bench_function(&format!("{} check equality", name), move |b| { + b.iter(|| black_box(a) == black_box(a)) + }); + c.bench_function(&format!("{} scalar multiplication", name), move |b| { + b.iter(|| black_box(a) * black_box(s)) + }); + c.bench_function(&format!("{} subgroup check", name), move |b| { + b.iter(|| black_box(a).is_torsion_free()) + }); + c.bench_function( + &format!("{} deserialize compressed point", name), + move |b| b.iter(|| G1Affine::from_compressed(black_box(&compressed))), + ); + c.bench_function( + &format!("{} deserialize uncompressed point", name), + move |b| b.iter(|| G1Affine::from_uncompressed(black_box(&uncompressed))), + ); + } + + // G1Projective + { + let name = "G1Projective"; + let a = G1Projective::generator(); + let a_affine = G1Affine::generator(); + let s = Scalar::from_raw([1, 2, 3, 4]); + + const N: usize = 10000; + let v = vec![G1Projective::generator(); N]; + let mut q = vec![G1Affine::identity(); N]; + + c.bench_function(&format!("{} check on curve", name), move |b| { + b.iter(|| black_box(a).is_on_curve()) + }); + c.bench_function(&format!("{} check equality", name), move |b| { + b.iter(|| black_box(a) == black_box(a)) + }); + c.bench_function(&format!("{} to affine", name), move |b| { + b.iter(|| G1Affine::from(black_box(a))) + }); + c.bench_function(&format!("{} doubling", name), move |b| { + b.iter(|| black_box(a).double()) + }); + c.bench_function(&format!("{} addition", name), move |b| { + b.iter(|| black_box(a).add(&a)) + }); + c.bench_function(&format!("{} mixed addition", name), move |b| { + b.iter(|| black_box(a).add_mixed(&a_affine)) + }); + c.bench_function(&format!("{} scalar multiplication", name), move |b| { + b.iter(|| black_box(a) * black_box(s)) + }); + c.bench_function(&format!("{} batch to affine n={}", name, N), move |b| { + b.iter(|| { + G1Projective::batch_normalize(black_box(&v), black_box(&mut q)); + black_box(&q)[0] + }) + }); + } + + // G2Affine + { + let name = "G2Affine"; + let a = G2Affine::generator(); + let s = Scalar::from_raw([1, 2, 3, 4]); + let compressed = [0u8; 96]; + let uncompressed = [0u8; 192]; + c.bench_function(&format!("{} check on curve", name), move |b| { + b.iter(|| black_box(a).is_on_curve()) + }); + c.bench_function(&format!("{} check equality", name), move |b| { + b.iter(|| black_box(a) == black_box(a)) + }); + c.bench_function(&format!("{} scalar multiplication", name), move |b| { + b.iter(|| black_box(a) * black_box(s)) + }); + c.bench_function(&format!("{} subgroup check", name), move |b| { + b.iter(|| black_box(a).is_torsion_free()) + }); + c.bench_function( + &format!("{} deserialize compressed point", name), + move |b| b.iter(|| G2Affine::from_compressed(black_box(&compressed))), + ); + c.bench_function( + &format!("{} deserialize uncompressed point", name), + move |b| b.iter(|| G2Affine::from_uncompressed(black_box(&uncompressed))), + ); + } + + // G2Projective + { + let name = "G2Projective"; + let a = G2Projective::generator(); + let a_affine = G2Affine::generator(); + let s = Scalar::from_raw([1, 2, 3, 4]); + + const N: usize = 10000; + let v = vec![G2Projective::generator(); N]; + let mut q = vec![G2Affine::identity(); N]; + + c.bench_function(&format!("{} check on curve", name), move |b| { + b.iter(|| black_box(a).is_on_curve()) + }); + c.bench_function(&format!("{} check equality", name), move |b| { + b.iter(|| black_box(a) == black_box(a)) + }); + c.bench_function(&format!("{} to affine", name), move |b| { + b.iter(|| G2Affine::from(black_box(a))) + }); + c.bench_function(&format!("{} doubling", name), move |b| { + b.iter(|| black_box(a).double()) + }); + c.bench_function(&format!("{} addition", name), move |b| { + b.iter(|| black_box(a).add(&a)) + }); + c.bench_function(&format!("{} mixed addition", name), move |b| { + b.iter(|| black_box(a).add_mixed(&a_affine)) + }); + c.bench_function(&format!("{} scalar multiplication", name), move |b| { + b.iter(|| black_box(a) * black_box(s)) + }); + c.bench_function(&format!("{} batch to affine n={}", name, N), move |b| { + b.iter(|| { + G2Projective::batch_normalize(black_box(&v), black_box(&mut q)); + black_box(&q)[0] + }) + }); + } +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/bls12_381/katex-header.html b/bls12_381/katex-header.html new file mode 100644 index 0000000..98e8590 --- /dev/null +++ b/bls12_381/katex-header.html @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/bls12_381/rust-toolchain b/bls12_381/rust-toolchain new file mode 100644 index 0000000..d70132e --- /dev/null +++ b/bls12_381/rust-toolchain @@ -0,0 +1 @@ +1.36.0 \ No newline at end of file diff --git a/bls12_381/src/fp.rs b/bls12_381/src/fp.rs new file mode 100644 index 0000000..28aa24b --- /dev/null +++ b/bls12_381/src/fp.rs @@ -0,0 +1,866 @@ +//! This module provides an implementation of the BLS12-381 base field `GF(p)` +//! where `p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab` + +use core::convert::TryFrom; +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use crate::util::{adc, mac, sbb}; + +// The internal representation of this type is six 64-bit unsigned +// integers in little-endian order. `Fp` values are always in +// Montgomery form; i.e., Scalar(a) = aR mod p, with R = 2^384. +#[derive(Copy, Clone)] +pub struct Fp([u64; 6]); + +impl fmt::Debug for Fp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let tmp = self.to_bytes(); + write!(f, "0x")?; + for &b in tmp.iter() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl Default for Fp { + fn default() -> Self { + Fp::zero() + } +} + +impl ConstantTimeEq for Fp { + fn ct_eq(&self, other: &Self) -> Choice { + self.0[0].ct_eq(&other.0[0]) + & self.0[1].ct_eq(&other.0[1]) + & self.0[2].ct_eq(&other.0[2]) + & self.0[3].ct_eq(&other.0[3]) + & self.0[4].ct_eq(&other.0[4]) + & self.0[5].ct_eq(&other.0[5]) + } +} + +impl Eq for Fp {} +impl PartialEq for Fp { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl ConditionallySelectable for Fp { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fp([ + u64::conditional_select(&a.0[0], &b.0[0], choice), + u64::conditional_select(&a.0[1], &b.0[1], choice), + u64::conditional_select(&a.0[2], &b.0[2], choice), + u64::conditional_select(&a.0[3], &b.0[3], choice), + u64::conditional_select(&a.0[4], &b.0[4], choice), + u64::conditional_select(&a.0[5], &b.0[5], choice), + ]) + } +} + +/// p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 +const MODULUS: [u64; 6] = [ + 0xb9fe_ffff_ffff_aaab, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a, +]; + +/// INV = -(p^{-1} mod 2^64) mod 2^64 +const INV: u64 = 0x89f3_fffc_fffc_fffd; + +/// R = 2^384 mod p +const R: Fp = Fp([ + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493, +]); + +/// R2 = 2^(384*2) mod p +const R2: Fp = Fp([ + 0xf4df_1f34_1c34_1746, + 0x0a76_e6a6_09d1_04f1, + 0x8de5_476c_4c95_b6d5, + 0x67eb_88a9_939d_83c0, + 0x9a79_3e85_b519_952d, + 0x1198_8fe5_92ca_e3aa, +]); + +impl<'a> Neg for &'a Fp { + type Output = Fp; + + #[inline] + fn neg(self) -> Fp { + self.neg() + } +} + +impl Neg for Fp { + type Output = Fp; + + #[inline] + fn neg(self) -> Fp { + -&self + } +} + +impl<'a, 'b> Sub<&'b Fp> for &'a Fp { + type Output = Fp; + + #[inline] + fn sub(self, rhs: &'b Fp) -> Fp { + self.sub(rhs) + } +} + +impl<'a, 'b> Add<&'b Fp> for &'a Fp { + type Output = Fp; + + #[inline] + fn add(self, rhs: &'b Fp) -> Fp { + self.add(rhs) + } +} + +impl<'a, 'b> Mul<&'b Fp> for &'a Fp { + type Output = Fp; + + #[inline] + fn mul(self, rhs: &'b Fp) -> Fp { + self.mul(rhs) + } +} + +impl_binops_additive!(Fp, Fp); +impl_binops_multiplicative!(Fp, Fp); + +impl Fp { + /// Returns zero, the additive identity. + #[inline] + pub const fn zero() -> Fp { + Fp([0, 0, 0, 0, 0, 0]) + } + + /// Returns one, the multiplicative identity. + #[inline] + pub const fn one() -> Fp { + R + } + + pub fn is_zero(&self) -> Choice { + self.ct_eq(&Fp::zero()) + } + + /// Attempts to convert a little-endian byte representation of + /// a scalar into an `Fp`, failing if the input is not canonical. + pub fn from_bytes(bytes: &[u8; 48]) -> CtOption { + let mut tmp = Fp([0, 0, 0, 0, 0, 0]); + + tmp.0[5] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap()); + tmp.0[4] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap()); + tmp.0[3] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap()); + tmp.0[2] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap()); + tmp.0[1] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap()); + tmp.0[0] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap()); + + // Try to subtract the modulus + let (_, borrow) = sbb(tmp.0[0], MODULUS[0], 0); + let (_, borrow) = sbb(tmp.0[1], MODULUS[1], borrow); + let (_, borrow) = sbb(tmp.0[2], MODULUS[2], borrow); + let (_, borrow) = sbb(tmp.0[3], MODULUS[3], borrow); + let (_, borrow) = sbb(tmp.0[4], MODULUS[4], borrow); + let (_, borrow) = sbb(tmp.0[5], MODULUS[5], borrow); + + // If the element is smaller than MODULUS then the + // subtraction will underflow, producing a borrow value + // of 0xffff...ffff. Otherwise, it'll be zero. + let is_some = (borrow as u8) & 1; + + // Convert to Montgomery form by computing + // (a.R^0 * R^2) / R = a.R + tmp *= &R2; + + CtOption::new(tmp, Choice::from(is_some)) + } + + /// Converts an element of `Fp` into a byte representation in + /// big-endian byte order. + pub fn to_bytes(&self) -> [u8; 48] { + // Turn into canonical form by computing + // (a.R) / R = a + let tmp = Fp::montgomery_reduce( + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], 0, 0, 0, 0, 0, 0, + ); + + let mut res = [0; 48]; + res[0..8].copy_from_slice(&tmp.0[5].to_be_bytes()); + res[8..16].copy_from_slice(&tmp.0[4].to_be_bytes()); + res[16..24].copy_from_slice(&tmp.0[3].to_be_bytes()); + res[24..32].copy_from_slice(&tmp.0[2].to_be_bytes()); + res[32..40].copy_from_slice(&tmp.0[1].to_be_bytes()); + res[40..48].copy_from_slice(&tmp.0[0].to_be_bytes()); + + res + } + + /// Returns whether or not this element is strictly lexicographically + /// larger than its negation. + pub fn lexicographically_largest(&self) -> Choice { + // This can be determined by checking to see if the element is + // larger than (p - 1) // 2. If we subtract by ((p - 1) // 2) + 1 + // and there is no underflow, then the element must be larger than + // (p - 1) // 2. + + // First, because self is in Montgomery form we need to reduce it + let tmp = Fp::montgomery_reduce( + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], 0, 0, 0, 0, 0, 0, + ); + + let (_, borrow) = sbb(tmp.0[0], 0xdcff_7fff_ffff_d556, 0); + let (_, borrow) = sbb(tmp.0[1], 0x0f55_ffff_58a9_ffff, borrow); + let (_, borrow) = sbb(tmp.0[2], 0xb398_6950_7b58_7b12, borrow); + let (_, borrow) = sbb(tmp.0[3], 0xb23b_a5c2_79c2_895f, borrow); + let (_, borrow) = sbb(tmp.0[4], 0x258d_d3db_21a5_d66b, borrow); + let (_, borrow) = sbb(tmp.0[5], 0x0d00_88f5_1cbf_f34d, borrow); + + // If the element was smaller, the subtraction will underflow + // producing a borrow value of 0xffff...ffff, otherwise it will + // be zero. We create a Choice representing true if there was + // overflow (and so this element is not lexicographically larger + // than its negation) and then negate it. + + !Choice::from((borrow as u8) & 1) + } + + /// Constructs an element of `Fp` without checking that it is + /// canonical. + pub const fn from_raw_unchecked(v: [u64; 6]) -> Fp { + Fp(v) + } + + /// Although this is labeled "vartime", it is only + /// variable time with respect to the exponent. It + /// is also not exposed in the public API. + pub fn pow_vartime(&self, by: &[u64; 6]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + + if ((*e >> i) & 1) == 1 { + res *= self; + } + } + } + res + } + + #[inline] + pub fn sqrt(&self) -> CtOption { + // We use Shank's method, as p = 3 (mod 4). This means + // we only need to exponentiate by (p+1)/4. This only + // works for elements that are actually quadratic residue, + // so we check that we got the correct result at the end. + + let sqrt = self.pow_vartime(&[ + 0xee7f_bfff_ffff_eaab, + 0x07aa_ffff_ac54_ffff, + 0xd9cc_34a8_3dac_3d89, + 0xd91d_d2e1_3ce1_44af, + 0x92c6_e9ed_90d2_eb35, + 0x0680_447a_8e5f_f9a6, + ]); + + CtOption::new(sqrt, sqrt.square().ct_eq(self)) + } + + #[inline] + /// Computes the multiplicative inverse of this field + /// element, returning None in the case that this element + /// is zero. + pub fn invert(&self) -> CtOption { + // Exponentiate by p - 2 + let t = self.pow_vartime(&[ + 0xb9fe_ffff_ffff_aaa9, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a, + ]); + + CtOption::new(t, !self.is_zero()) + } + + #[inline] + const fn subtract_p(&self) -> Fp { + let (r0, borrow) = sbb(self.0[0], MODULUS[0], 0); + let (r1, borrow) = sbb(self.0[1], MODULUS[1], borrow); + let (r2, borrow) = sbb(self.0[2], MODULUS[2], borrow); + let (r3, borrow) = sbb(self.0[3], MODULUS[3], borrow); + let (r4, borrow) = sbb(self.0[4], MODULUS[4], borrow); + let (r5, borrow) = sbb(self.0[5], MODULUS[5], borrow); + + // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise + // borrow = 0x000...000. Thus, we use it as a mask! + let r0 = (self.0[0] & borrow) | (r0 & !borrow); + let r1 = (self.0[1] & borrow) | (r1 & !borrow); + let r2 = (self.0[2] & borrow) | (r2 & !borrow); + let r3 = (self.0[3] & borrow) | (r3 & !borrow); + let r4 = (self.0[4] & borrow) | (r4 & !borrow); + let r5 = (self.0[5] & borrow) | (r5 & !borrow); + + Fp([r0, r1, r2, r3, r4, r5]) + } + + #[inline] + pub const fn add(&self, rhs: &Fp) -> Fp { + let (d0, carry) = adc(self.0[0], rhs.0[0], 0); + let (d1, carry) = adc(self.0[1], rhs.0[1], carry); + let (d2, carry) = adc(self.0[2], rhs.0[2], carry); + let (d3, carry) = adc(self.0[3], rhs.0[3], carry); + let (d4, carry) = adc(self.0[4], rhs.0[4], carry); + let (d5, _) = adc(self.0[5], rhs.0[5], carry); + + // Attempt to subtract the modulus, to ensure the value + // is smaller than the modulus. + (&Fp([d0, d1, d2, d3, d4, d5])).subtract_p() + } + + #[inline] + pub const fn neg(&self) -> Fp { + let (d0, borrow) = sbb(MODULUS[0], self.0[0], 0); + let (d1, borrow) = sbb(MODULUS[1], self.0[1], borrow); + let (d2, borrow) = sbb(MODULUS[2], self.0[2], borrow); + let (d3, borrow) = sbb(MODULUS[3], self.0[3], borrow); + let (d4, borrow) = sbb(MODULUS[4], self.0[4], borrow); + let (d5, _) = sbb(MODULUS[5], self.0[5], borrow); + + // Let's use a mask if `self` was zero, which would mean + // the result of the subtraction is p. + let mask = (((self.0[0] | self.0[1] | self.0[2] | self.0[3] | self.0[4] | self.0[5]) == 0) + as u64) + .wrapping_sub(1); + + Fp([ + d0 & mask, + d1 & mask, + d2 & mask, + d3 & mask, + d4 & mask, + d5 & mask, + ]) + } + + #[inline] + pub const fn sub(&self, rhs: &Fp) -> Fp { + (&rhs.neg()).add(self) + } + + #[inline(always)] + const fn montgomery_reduce( + t0: u64, + t1: u64, + t2: u64, + t3: u64, + t4: u64, + t5: u64, + t6: u64, + t7: u64, + t8: u64, + t9: u64, + t10: u64, + t11: u64, + ) -> Self { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = t0.wrapping_mul(INV); + let (_, carry) = mac(t0, k, MODULUS[0], 0); + let (r1, carry) = mac(t1, k, MODULUS[1], carry); + let (r2, carry) = mac(t2, k, MODULUS[2], carry); + let (r3, carry) = mac(t3, k, MODULUS[3], carry); + let (r4, carry) = mac(t4, k, MODULUS[4], carry); + let (r5, carry) = mac(t5, k, MODULUS[5], carry); + let (r6, r7) = adc(t6, 0, carry); + + let k = r1.wrapping_mul(INV); + let (_, carry) = mac(r1, k, MODULUS[0], 0); + let (r2, carry) = mac(r2, k, MODULUS[1], carry); + let (r3, carry) = mac(r3, k, MODULUS[2], carry); + let (r4, carry) = mac(r4, k, MODULUS[3], carry); + let (r5, carry) = mac(r5, k, MODULUS[4], carry); + let (r6, carry) = mac(r6, k, MODULUS[5], carry); + let (r7, r8) = adc(t7, r7, carry); + + let k = r2.wrapping_mul(INV); + let (_, carry) = mac(r2, k, MODULUS[0], 0); + let (r3, carry) = mac(r3, k, MODULUS[1], carry); + let (r4, carry) = mac(r4, k, MODULUS[2], carry); + let (r5, carry) = mac(r5, k, MODULUS[3], carry); + let (r6, carry) = mac(r6, k, MODULUS[4], carry); + let (r7, carry) = mac(r7, k, MODULUS[5], carry); + let (r8, r9) = adc(t8, r8, carry); + + let k = r3.wrapping_mul(INV); + let (_, carry) = mac(r3, k, MODULUS[0], 0); + let (r4, carry) = mac(r4, k, MODULUS[1], carry); + let (r5, carry) = mac(r5, k, MODULUS[2], carry); + let (r6, carry) = mac(r6, k, MODULUS[3], carry); + let (r7, carry) = mac(r7, k, MODULUS[4], carry); + let (r8, carry) = mac(r8, k, MODULUS[5], carry); + let (r9, r10) = adc(t9, r9, carry); + + let k = r4.wrapping_mul(INV); + let (_, carry) = mac(r4, k, MODULUS[0], 0); + let (r5, carry) = mac(r5, k, MODULUS[1], carry); + let (r6, carry) = mac(r6, k, MODULUS[2], carry); + let (r7, carry) = mac(r7, k, MODULUS[3], carry); + let (r8, carry) = mac(r8, k, MODULUS[4], carry); + let (r9, carry) = mac(r9, k, MODULUS[5], carry); + let (r10, r11) = adc(t10, r10, carry); + + let k = r5.wrapping_mul(INV); + let (_, carry) = mac(r5, k, MODULUS[0], 0); + let (r6, carry) = mac(r6, k, MODULUS[1], carry); + let (r7, carry) = mac(r7, k, MODULUS[2], carry); + let (r8, carry) = mac(r8, k, MODULUS[3], carry); + let (r9, carry) = mac(r9, k, MODULUS[4], carry); + let (r10, carry) = mac(r10, k, MODULUS[5], carry); + let (r11, _) = adc(t11, r11, carry); + + // Attempt to subtract the modulus, to ensure the value + // is smaller than the modulus. + (&Fp([r6, r7, r8, r9, r10, r11])).subtract_p() + } + + #[inline] + pub const fn mul(&self, rhs: &Fp) -> Fp { + let (t0, carry) = mac(0, self.0[0], rhs.0[0], 0); + let (t1, carry) = mac(0, self.0[0], rhs.0[1], carry); + let (t2, carry) = mac(0, self.0[0], rhs.0[2], carry); + let (t3, carry) = mac(0, self.0[0], rhs.0[3], carry); + let (t4, carry) = mac(0, self.0[0], rhs.0[4], carry); + let (t5, t6) = mac(0, self.0[0], rhs.0[5], carry); + + let (t1, carry) = mac(t1, self.0[1], rhs.0[0], 0); + let (t2, carry) = mac(t2, self.0[1], rhs.0[1], carry); + let (t3, carry) = mac(t3, self.0[1], rhs.0[2], carry); + let (t4, carry) = mac(t4, self.0[1], rhs.0[3], carry); + let (t5, carry) = mac(t5, self.0[1], rhs.0[4], carry); + let (t6, t7) = mac(t6, self.0[1], rhs.0[5], carry); + + let (t2, carry) = mac(t2, self.0[2], rhs.0[0], 0); + let (t3, carry) = mac(t3, self.0[2], rhs.0[1], carry); + let (t4, carry) = mac(t4, self.0[2], rhs.0[2], carry); + let (t5, carry) = mac(t5, self.0[2], rhs.0[3], carry); + let (t6, carry) = mac(t6, self.0[2], rhs.0[4], carry); + let (t7, t8) = mac(t7, self.0[2], rhs.0[5], carry); + + let (t3, carry) = mac(t3, self.0[3], rhs.0[0], 0); + let (t4, carry) = mac(t4, self.0[3], rhs.0[1], carry); + let (t5, carry) = mac(t5, self.0[3], rhs.0[2], carry); + let (t6, carry) = mac(t6, self.0[3], rhs.0[3], carry); + let (t7, carry) = mac(t7, self.0[3], rhs.0[4], carry); + let (t8, t9) = mac(t8, self.0[3], rhs.0[5], carry); + + let (t4, carry) = mac(t4, self.0[4], rhs.0[0], 0); + let (t5, carry) = mac(t5, self.0[4], rhs.0[1], carry); + let (t6, carry) = mac(t6, self.0[4], rhs.0[2], carry); + let (t7, carry) = mac(t7, self.0[4], rhs.0[3], carry); + let (t8, carry) = mac(t8, self.0[4], rhs.0[4], carry); + let (t9, t10) = mac(t9, self.0[4], rhs.0[5], carry); + + let (t5, carry) = mac(t5, self.0[5], rhs.0[0], 0); + let (t6, carry) = mac(t6, self.0[5], rhs.0[1], carry); + let (t7, carry) = mac(t7, self.0[5], rhs.0[2], carry); + let (t8, carry) = mac(t8, self.0[5], rhs.0[3], carry); + let (t9, carry) = mac(t9, self.0[5], rhs.0[4], carry); + let (t10, t11) = mac(t10, self.0[5], rhs.0[5], carry); + + Self::montgomery_reduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) + } + + /// Squares this element. + #[inline] + pub const fn square(&self) -> Self { + let (t1, carry) = mac(0, self.0[0], self.0[1], 0); + let (t2, carry) = mac(0, self.0[0], self.0[2], carry); + let (t3, carry) = mac(0, self.0[0], self.0[3], carry); + let (t4, carry) = mac(0, self.0[0], self.0[4], carry); + let (t5, t6) = mac(0, self.0[0], self.0[5], carry); + + let (t3, carry) = mac(t3, self.0[1], self.0[2], 0); + let (t4, carry) = mac(t4, self.0[1], self.0[3], carry); + let (t5, carry) = mac(t5, self.0[1], self.0[4], carry); + let (t6, t7) = mac(t6, self.0[1], self.0[5], carry); + + let (t5, carry) = mac(t5, self.0[2], self.0[3], 0); + let (t6, carry) = mac(t6, self.0[2], self.0[4], carry); + let (t7, t8) = mac(t7, self.0[2], self.0[5], carry); + + let (t7, carry) = mac(t7, self.0[3], self.0[4], 0); + let (t8, t9) = mac(t8, self.0[3], self.0[5], carry); + + let (t9, t10) = mac(t9, self.0[4], self.0[5], 0); + + let t11 = t10 >> 63; + let t10 = (t10 << 1) | (t9 >> 63); + let t9 = (t9 << 1) | (t8 >> 63); + let t8 = (t8 << 1) | (t7 >> 63); + let t7 = (t7 << 1) | (t6 >> 63); + let t6 = (t6 << 1) | (t5 >> 63); + let t5 = (t5 << 1) | (t4 >> 63); + let t4 = (t4 << 1) | (t3 >> 63); + let t3 = (t3 << 1) | (t2 >> 63); + let t2 = (t2 << 1) | (t1 >> 63); + let t1 = t1 << 1; + + let (t0, carry) = mac(0, self.0[0], self.0[0], 0); + let (t1, carry) = adc(t1, 0, carry); + let (t2, carry) = mac(t2, self.0[1], self.0[1], carry); + let (t3, carry) = adc(t3, 0, carry); + let (t4, carry) = mac(t4, self.0[2], self.0[2], carry); + let (t5, carry) = adc(t5, 0, carry); + let (t6, carry) = mac(t6, self.0[3], self.0[3], carry); + let (t7, carry) = adc(t7, 0, carry); + let (t8, carry) = mac(t8, self.0[4], self.0[4], carry); + let (t9, carry) = adc(t9, 0, carry); + let (t10, carry) = mac(t10, self.0[5], self.0[5], carry); + let (t11, _) = adc(t11, 0, carry); + + Self::montgomery_reduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) + } +} + +#[test] +fn test_conditional_selection() { + let a = Fp([1, 2, 3, 4, 5, 6]); + let b = Fp([7, 8, 9, 10, 11, 12]); + + assert_eq!( + ConditionallySelectable::conditional_select(&a, &b, Choice::from(0u8)), + a + ); + assert_eq!( + ConditionallySelectable::conditional_select(&a, &b, Choice::from(1u8)), + b + ); +} + +#[test] +fn test_equality() { + fn is_equal(a: &Fp, b: &Fp) -> bool { + let eq = a == b; + let ct_eq = a.ct_eq(&b); + + assert_eq!(eq, ct_eq.unwrap_u8() == 1); + + eq + } + + assert!(is_equal(&Fp([1, 2, 3, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6]))); + + assert!(!is_equal(&Fp([7, 2, 3, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6]))); + assert!(!is_equal(&Fp([1, 7, 3, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6]))); + assert!(!is_equal(&Fp([1, 2, 7, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6]))); + assert!(!is_equal(&Fp([1, 2, 3, 7, 5, 6]), &Fp([1, 2, 3, 4, 5, 6]))); + assert!(!is_equal(&Fp([1, 2, 3, 4, 7, 6]), &Fp([1, 2, 3, 4, 5, 6]))); + assert!(!is_equal(&Fp([1, 2, 3, 4, 5, 7]), &Fp([1, 2, 3, 4, 5, 6]))); +} + +#[test] +fn test_squaring() { + let a = Fp([ + 0xd215_d276_8e83_191b, + 0x5085_d80f_8fb2_8261, + 0xce9a_032d_df39_3a56, + 0x3e9c_4fff_2ca0_c4bb, + 0x6436_b6f7_f4d9_5dfb, + 0x1060_6628_ad4a_4d90, + ]); + let b = Fp([ + 0x33d9_c42a_3cb3_e235, + 0xdad1_1a09_4c4c_d455, + 0xa2f1_44bd_729a_aeba, + 0xd415_0932_be9f_feac, + 0xe27b_c7c4_7d44_ee50, + 0x14b6_a78d_3ec7_a560, + ]); + + assert_eq!(a.square(), b); +} + +#[test] +fn test_multiplication() { + let a = Fp([ + 0x0397_a383_2017_0cd4, + 0x734c_1b2c_9e76_1d30, + 0x5ed2_55ad_9a48_beb5, + 0x095a_3c6b_22a7_fcfc, + 0x2294_ce75_d4e2_6a27, + 0x1333_8bd8_7001_1ebb, + ]); + let b = Fp([ + 0xb9c3_c7c5_b119_6af7, + 0x2580_e208_6ce3_35c1, + 0xf49a_ed3d_8a57_ef42, + 0x41f2_81e4_9846_e878, + 0xe076_2346_c384_52ce, + 0x0652_e893_26e5_7dc0, + ]); + let c = Fp([ + 0xf96e_f3d7_11ab_5355, + 0xe8d4_59ea_00f1_48dd, + 0x53f7_354a_5f00_fa78, + 0x9e34_a4f3_125c_5f83, + 0x3fbe_0c47_ca74_c19e, + 0x01b0_6a8b_bd4a_dfe4, + ]); + + assert_eq!(a * b, c); +} + +#[test] +fn test_addition() { + let a = Fp([ + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b, + ]); + let b = Fp([ + 0x9fd2_8773_3d23_dda0, + 0xb16b_f2af_738b_3554, + 0x3e57_a75b_d3cc_6d1d, + 0x900b_c0bd_627f_d6d6, + 0xd319_a080_efb2_45fe, + 0x15fd_caa4_e4bb_2091, + ]); + let c = Fp([ + 0x3934_42cc_b58b_b327, + 0x1092_685f_3bd5_47e3, + 0x3382_252c_ab6a_c4c9, + 0xf946_94cb_7688_7f55, + 0x4b21_5e90_93a5_e071, + 0x0d56_e30f_34f5_f853, + ]); + + assert_eq!(a + b, c); +} + +#[test] +fn test_subtraction() { + let a = Fp([ + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b, + ]); + let b = Fp([ + 0x9fd2_8773_3d23_dda0, + 0xb16b_f2af_738b_3554, + 0x3e57_a75b_d3cc_6d1d, + 0x900b_c0bd_627f_d6d6, + 0xd319_a080_efb2_45fe, + 0x15fd_caa4_e4bb_2091, + ]); + let c = Fp([ + 0x6d8d_33e6_3b43_4d3d, + 0xeb12_82fd_b766_dd39, + 0x8534_7bb6_f133_d6d5, + 0xa21d_aa5a_9892_f727, + 0x3b25_6cfb_3ad8_ae23, + 0x155d_7199_de7f_8464, + ]); + + assert_eq!(a - b, c); +} + +#[test] +fn test_negation() { + let a = Fp([ + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b, + ]); + let b = Fp([ + 0x669e_44a6_8798_2a79, + 0xa0d9_8a50_37b5_ed71, + 0x0ad5_822f_2861_a854, + 0x96c5_2bf1_ebf7_5781, + 0x87f8_41f0_5c0c_658c, + 0x08a6_e795_afc5_283e, + ]); + + assert_eq!(-a, b); +} + +#[test] +fn test_debug() { + assert_eq!( + format!( + "{:?}", + Fp([ + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b, + ]) + ), + "0x104bf052ad3bc99bcb176c24a06a6c3aad4eaf2308fc4d282e106c84a757d061052630515305e59bdddf8111bfdeb704" + ); +} + +#[test] +fn test_from_bytes() { + let mut a = Fp([ + 0xdc90_6d9b_e3f9_5dc8, + 0x8755_caf7_4596_91a1, + 0xcff1_a7f4_e958_3ab3, + 0x9b43_821f_849e_2284, + 0xf575_54f3_a297_4f3f, + 0x085d_bea8_4ed4_7f79, + ]); + + for _ in 0..100 { + a = a.square(); + let tmp = a.to_bytes(); + let b = Fp::from_bytes(&tmp).unwrap(); + + assert_eq!(a, b); + } + + assert_eq!( + -Fp::one(), + Fp::from_bytes(&[ + 26, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75, + 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177, + 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170 + ]) + .unwrap() + ); + + assert!( + Fp::from_bytes(&[ + 27, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75, + 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177, + 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + assert!(Fp::from_bytes(&[0xff; 48]).is_none().unwrap_u8() == 1); +} + +#[test] +fn test_sqrt() { + // a = 4 + let a = Fp::from_raw_unchecked([ + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e, + ]); + + assert_eq!( + // sqrt(4) = -2 + -a.sqrt().unwrap(), + // 2 + Fp::from_raw_unchecked([ + 0x3213_0000_0006_554f, + 0xb93c_0018_d6c4_0005, + 0x5760_5e0d_b0dd_bb51, + 0x8b25_6521_ed1f_9bcb, + 0x6cf2_8d79_0162_2c03, + 0x11eb_ab9d_bb81_e28c, + ]) + ); +} + +#[test] +fn test_inversion() { + let a = Fp([ + 0x43b4_3a50_78ac_2076, + 0x1ce0_7630_46f8_962b, + 0x724a_5276_486d_735c, + 0x6f05_c2a6_282d_48fd, + 0x2095_bd5b_b4ca_9331, + 0x03b3_5b38_94b0_f7da, + ]); + let b = Fp([ + 0x69ec_d704_0952_148f, + 0x985c_cc20_2219_0f55, + 0xe19b_ba36_a9ad_2f41, + 0x19bb_16c9_5219_dbd8, + 0x14dc_acfd_fb47_8693, + 0x115f_f58a_fff9_a8e1, + ]); + + assert_eq!(a.invert().unwrap(), b); + assert!(Fp::zero().invert().is_none().unwrap_u8() == 1); +} + +#[test] +fn test_lexicographic_largest() { + assert!(!bool::from(Fp::zero().lexicographically_largest())); + assert!(!bool::from(Fp::one().lexicographically_largest())); + assert!(!bool::from( + Fp::from_raw_unchecked([ + 0xa1fa_ffff_fffe_5557, + 0x995b_fff9_76a3_fffe, + 0x03f4_1d24_d174_ceb4, + 0xf654_7998_c199_5dbd, + 0x778a_468f_507a_6034, + 0x0205_5993_1f7f_8103 + ]) + .lexicographically_largest() + )); + assert!(bool::from( + Fp::from_raw_unchecked([ + 0x1804_0000_0001_5554, + 0x8550_0005_3ab0_0001, + 0x633c_b57c_253c_276f, + 0x6e22_d1ec_31eb_b502, + 0xd391_6126_f2d1_4ca2, + 0x17fb_b857_1a00_6596, + ]) + .lexicographically_largest() + )); + assert!(bool::from( + Fp::from_raw_unchecked([ + 0x43f5_ffff_fffc_aaae, + 0x32b7_fff2_ed47_fffd, + 0x07e8_3a49_a2e9_9d69, + 0xeca8_f331_8332_bb7a, + 0xef14_8d1e_a0f4_c069, + 0x040a_b326_3eff_0206, + ]) + .lexicographically_largest() + )); +} diff --git a/bls12_381/src/fp12.rs b/bls12_381/src/fp12.rs new file mode 100644 index 0000000..735f91e --- /dev/null +++ b/bls12_381/src/fp12.rs @@ -0,0 +1,635 @@ +use crate::fp::*; +use crate::fp2::*; +use crate::fp6::*; + +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +/// This represents an element $c_0 + c_1 w$ of $\mathbb{F}_{p^12} = \mathbb{F}_{p^6} / w^2 - v$. +pub struct Fp12 { + pub c0: Fp6, + pub c1: Fp6, +} + +impl From for Fp12 { + fn from(f: Fp) -> Fp12 { + Fp12 { + c0: Fp6::from(f), + c1: Fp6::zero(), + } + } +} + +impl From for Fp12 { + fn from(f: Fp2) -> Fp12 { + Fp12 { + c0: Fp6::from(f), + c1: Fp6::zero(), + } + } +} + +impl From for Fp12 { + fn from(f: Fp6) -> Fp12 { + Fp12 { + c0: f, + c1: Fp6::zero(), + } + } +} + +impl PartialEq for Fp12 { + fn eq(&self, other: &Fp12) -> bool { + self.ct_eq(other).into() + } +} + +impl Copy for Fp12 {} +impl Clone for Fp12 { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl Default for Fp12 { + fn default() -> Self { + Fp12::zero() + } +} + +impl fmt::Debug for Fp12 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?} + ({:?})*w", self.c0, self.c1) + } +} + +impl ConditionallySelectable for Fp12 { + #[inline(always)] + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fp12 { + c0: Fp6::conditional_select(&a.c0, &b.c0, choice), + c1: Fp6::conditional_select(&a.c1, &b.c1, choice), + } + } +} + +impl ConstantTimeEq for Fp12 { + #[inline(always)] + fn ct_eq(&self, other: &Self) -> Choice { + self.c0.ct_eq(&other.c0) & self.c1.ct_eq(&other.c1) + } +} + +impl Fp12 { + #[inline] + pub fn zero() -> Self { + Fp12 { + c0: Fp6::zero(), + c1: Fp6::zero(), + } + } + + #[inline] + pub fn one() -> Self { + Fp12 { + c0: Fp6::one(), + c1: Fp6::zero(), + } + } + + pub fn mul_by_014(&self, c0: &Fp2, c1: &Fp2, c4: &Fp2) -> Fp12 { + let aa = self.c0.mul_by_01(c0, c1); + let bb = self.c1.mul_by_1(c4); + let o = c1 + c4; + let c1 = self.c1 + self.c0; + let c1 = c1.mul_by_01(c0, &o); + let c1 = c1 - aa - bb; + let c0 = bb; + let c0 = c0.mul_by_nonresidue(); + let c0 = c0 + aa; + + Fp12 { c0, c1 } + } + + #[inline(always)] + pub fn is_zero(&self) -> Choice { + self.c0.is_zero() & self.c1.is_zero() + } + + #[inline(always)] + pub fn conjugate(&self) -> Self { + Fp12 { + c0: self.c0, + c1: -self.c1, + } + } + + /// Raises this element to p. + #[inline(always)] + pub fn frobenius_map(&self) -> Self { + let c0 = self.c0.frobenius_map(); + let c1 = self.c1.frobenius_map(); + + // c1 = c1 * (u + 1)^((p - 1) / 6) + let c1 = c1 + * Fp6::from(Fp2 { + c0: Fp::from_raw_unchecked([ + 0x0708_9552_b319_d465, + 0xc669_5f92_b50a_8313, + 0x97e8_3ccc_d117_228f, + 0xa35b_aeca_b2dc_29ee, + 0x1ce3_93ea_5daa_ce4d, + 0x08f2_220f_b0fb_66eb, + ]), + c1: Fp::from_raw_unchecked([ + 0xb2f6_6aad_4ce5_d646, + 0x5842_a06b_fc49_7cec, + 0xcf48_95d4_2599_d394, + 0xc11b_9cba_40a8_e8d0, + 0x2e38_13cb_e5a0_de89, + 0x110e_efda_8884_7faf, + ]), + }); + + Fp12 { c0, c1 } + } + + #[inline] + pub fn square(&self) -> Self { + let ab = self.c0 * self.c1; + let c0c1 = self.c0 + self.c1; + let c0 = self.c1.mul_by_nonresidue(); + let c0 = c0 + self.c0; + let c0 = c0 * c0c1; + let c0 = c0 - ab; + let c1 = ab + ab; + let c0 = c0 - ab.mul_by_nonresidue(); + + Fp12 { c0, c1 } + } + + pub fn invert(&self) -> CtOption { + (self.c0.square() - self.c1.square().mul_by_nonresidue()) + .invert() + .map(|t| Fp12 { + c0: self.c0 * t, + c1: self.c1 * -t, + }) + } +} + +impl<'a, 'b> Mul<&'b Fp12> for &'a Fp12 { + type Output = Fp12; + + #[inline] + fn mul(self, other: &'b Fp12) -> Self::Output { + let aa = self.c0 * other.c0; + let bb = self.c1 * other.c1; + let o = other.c0 + other.c1; + let c1 = self.c1 + self.c0; + let c1 = c1 * o; + let c1 = c1 - aa; + let c1 = c1 - bb; + let c0 = bb.mul_by_nonresidue(); + let c0 = c0 + aa; + + Fp12 { c0, c1 } + } +} + +impl<'a, 'b> Add<&'b Fp12> for &'a Fp12 { + type Output = Fp12; + + #[inline] + fn add(self, rhs: &'b Fp12) -> Self::Output { + Fp12 { + c0: self.c0 + rhs.c0, + c1: self.c1 + rhs.c1, + } + } +} + +impl<'a> Neg for &'a Fp12 { + type Output = Fp12; + + #[inline] + fn neg(self) -> Self::Output { + Fp12 { + c0: -self.c0, + c1: -self.c1, + } + } +} + +impl Neg for Fp12 { + type Output = Fp12; + + #[inline] + fn neg(self) -> Self::Output { + -&self + } +} + +impl<'a, 'b> Sub<&'b Fp12> for &'a Fp12 { + type Output = Fp12; + + #[inline] + fn sub(self, rhs: &'b Fp12) -> Self::Output { + Fp12 { + c0: self.c0 - rhs.c0, + c1: self.c1 - rhs.c1, + } + } +} + +impl_binops_additive!(Fp12, Fp12); +impl_binops_multiplicative!(Fp12, Fp12); + +#[test] +fn test_arithmetic() { + use crate::fp::*; + use crate::fp2::*; + + let a = Fp12 { + c0: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ]), + c1: Fp::from_raw_unchecked([ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ]), + c1: Fp::from_raw_unchecked([ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ]), + c1: Fp::from_raw_unchecked([ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040, + ]), + }, + }, + c1: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ]), + c1: Fp::from_raw_unchecked([ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ]), + c1: Fp::from_raw_unchecked([ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ]), + c1: Fp::from_raw_unchecked([ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040, + ]), + }, + }, + }; + + let b = Fp12 { + c0: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d272_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ]), + c1: Fp::from_raw_unchecked([ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e348, + ]), + c1: Fp::from_raw_unchecked([ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ]), + c1: Fp::from_raw_unchecked([ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040, + ]), + }, + }, + c1: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd2_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ]), + c1: Fp::from_raw_unchecked([ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ]), + c1: Fp::from_raw_unchecked([ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a117_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ]), + c1: Fp::from_raw_unchecked([ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040, + ]), + }, + }, + }; + + let c = Fp12 { + c0: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x47f9_cb98_71b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ]), + c1: Fp::from_raw_unchecked([ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0x7791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ]), + c1: Fp::from_raw_unchecked([ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_133c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_40e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ]), + c1: Fp::from_raw_unchecked([ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_1744_c040, + ]), + }, + }, + c1: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ]), + c1: Fp::from_raw_unchecked([ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d3_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ]), + c1: Fp::from_raw_unchecked([ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ]), + c1: Fp::from_raw_unchecked([ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_1040, + ]), + }, + }, + }; + + // because a and b and c are similar to each other and + // I was lazy, this is just some arbitrary way to make + // them a little more different + let a = a.square().invert().unwrap().square() + c; + let b = b.square().invert().unwrap().square() + a; + let c = c.square().invert().unwrap().square() + b; + + assert_eq!(a.square(), a * a); + assert_eq!(b.square(), b * b); + assert_eq!(c.square(), c * c); + + assert_eq!((a + b) * c.square(), (c * c * a) + (c * c * b)); + + assert_eq!( + a.invert().unwrap() * b.invert().unwrap(), + (a * b).invert().unwrap() + ); + assert_eq!(a.invert().unwrap() * a, Fp12::one()); + + assert!(a != a.frobenius_map()); + assert_eq!( + a, + a.frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + ); +} diff --git a/bls12_381/src/fp2.rs b/bls12_381/src/fp2.rs new file mode 100644 index 0000000..3890d31 --- /dev/null +++ b/bls12_381/src/fp2.rs @@ -0,0 +1,868 @@ +//! This module implements arithmetic over the quadratic extension field Fp2. + +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use crate::fp::Fp; + +#[derive(Copy, Clone)] +pub struct Fp2 { + pub c0: Fp, + pub c1: Fp, +} + +impl fmt::Debug for Fp2 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?} + {:?}*u", self.c0, self.c1) + } +} + +impl Default for Fp2 { + fn default() -> Self { + Fp2::zero() + } +} + +impl From for Fp2 { + fn from(f: Fp) -> Fp2 { + Fp2 { + c0: f, + c1: Fp::zero(), + } + } +} + +impl ConstantTimeEq for Fp2 { + fn ct_eq(&self, other: &Self) -> Choice { + self.c0.ct_eq(&other.c0) & self.c1.ct_eq(&other.c1) + } +} + +impl Eq for Fp2 {} +impl PartialEq for Fp2 { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl ConditionallySelectable for Fp2 { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fp2 { + c0: Fp::conditional_select(&a.c0, &b.c0, choice), + c1: Fp::conditional_select(&a.c1, &b.c1, choice), + } + } +} + +impl<'a> Neg for &'a Fp2 { + type Output = Fp2; + + #[inline] + fn neg(self) -> Fp2 { + self.neg() + } +} + +impl Neg for Fp2 { + type Output = Fp2; + + #[inline] + fn neg(self) -> Fp2 { + -&self + } +} + +impl<'a, 'b> Sub<&'b Fp2> for &'a Fp2 { + type Output = Fp2; + + #[inline] + fn sub(self, rhs: &'b Fp2) -> Fp2 { + self.sub(rhs) + } +} + +impl<'a, 'b> Add<&'b Fp2> for &'a Fp2 { + type Output = Fp2; + + #[inline] + fn add(self, rhs: &'b Fp2) -> Fp2 { + self.add(rhs) + } +} + +impl<'a, 'b> Mul<&'b Fp2> for &'a Fp2 { + type Output = Fp2; + + #[inline] + fn mul(self, rhs: &'b Fp2) -> Fp2 { + self.mul(rhs) + } +} + +impl_binops_additive!(Fp2, Fp2); +impl_binops_multiplicative!(Fp2, Fp2); + +impl Fp2 { + #[inline] + pub const fn zero() -> Fp2 { + Fp2 { + c0: Fp::zero(), + c1: Fp::zero(), + } + } + + #[inline] + pub const fn one() -> Fp2 { + Fp2 { + c0: Fp::one(), + c1: Fp::zero(), + } + } + + pub fn is_zero(&self) -> Choice { + self.c0.is_zero() & self.c1.is_zero() + } + + /// Raises this element to p. + #[inline(always)] + pub fn frobenius_map(&self) -> Self { + // This is always just a conjugation. If you're curious why, here's + // an article about it: https://alicebob.cryptoland.net/the-frobenius-endomorphism-with-finite-fields/ + self.conjugate() + } + + #[inline(always)] + pub fn conjugate(&self) -> Self { + Fp2 { + c0: self.c0, + c1: -self.c1, + } + } + + #[inline(always)] + pub fn mul_by_nonresidue(&self) -> Fp2 { + // Multiply a + bu by u + 1, getting + // au + a + bu^2 + bu + // and because u^2 = -1, we get + // (a - b) + (a + b)u + + Fp2 { + c0: self.c0 - self.c1, + c1: self.c0 + self.c1, + } + } + + /// Returns whether or not this element is strictly lexicographically + /// larger than its negation. + #[inline] + pub fn lexicographically_largest(&self) -> Choice { + // If this element's c1 coefficient is lexicographically largest + // then it is lexicographically largest. Otherwise, in the event + // the c1 coefficient is zero and the c0 coefficient is + // lexicographically largest, then this element is lexicographically + // largest. + + self.c1.lexicographically_largest() + | (self.c1.is_zero() & self.c0.lexicographically_largest()) + } + + pub const fn square(&self) -> Fp2 { + // Complex squaring: + // + // v0 = c0 * c1 + // c0' = (c0 + c1) * (c0 + \beta*c1) - v0 - \beta * v0 + // c1' = 2 * v0 + // + // In BLS12-381's F_{p^2}, our \beta is -1 so we + // can modify this formula: + // + // c0' = (c0 + c1) * (c0 - c1) + // c1' = 2 * c0 * c1 + + let a = (&self.c0).add(&self.c1); + let b = (&self.c0).sub(&self.c1); + let c = (&self.c0).add(&self.c0); + + Fp2 { + c0: (&a).mul(&b), + c1: (&c).mul(&self.c1), + } + } + + pub const fn mul(&self, rhs: &Fp2) -> Fp2 { + // Karatsuba multiplication: + // + // v0 = a0 * b0 + // v1 = a1 * b1 + // c0 = v0 + \beta * v1 + // c1 = (a0 + a1) * (b0 + b1) - v0 - v1 + // + // In BLS12-381's F_{p^2}, our \beta is -1 so we + // can modify this formula. (Also, since we always + // subtract v1, we can compute v1 = -a1 * b1.) + // + // v0 = a0 * b0 + // v1 = (-a1) * b1 + // c0 = v0 + v1 + // c1 = (a0 + a1) * (b0 + b1) - v0 + v1 + + let v0 = (&self.c0).mul(&rhs.c0); + let v1 = (&(&self.c1).neg()).mul(&rhs.c1); + let c0 = (&v0).add(&v1); + let c1 = (&(&self.c0).add(&self.c1)).mul(&(&rhs.c0).add(&rhs.c1)); + let c1 = (&c1).sub(&v0); + let c1 = (&c1).add(&v1); + + Fp2 { c0, c1 } + } + + pub const fn add(&self, rhs: &Fp2) -> Fp2 { + Fp2 { + c0: (&self.c0).add(&rhs.c0), + c1: (&self.c1).add(&rhs.c1), + } + } + + pub const fn sub(&self, rhs: &Fp2) -> Fp2 { + Fp2 { + c0: (&self.c0).sub(&rhs.c0), + c1: (&self.c1).sub(&rhs.c1), + } + } + + pub const fn neg(&self) -> Fp2 { + Fp2 { + c0: (&self.c0).neg(), + c1: (&self.c1).neg(), + } + } + + pub fn sqrt(&self) -> CtOption { + // Algorithm 9, https://eprint.iacr.org/2012/685.pdf + // with constant time modifications. + + CtOption::new(Fp2::zero(), self.is_zero()).or_else(|| { + // a1 = self^((p - 3) / 4) + let a1 = self.pow_vartime(&[ + 0xee7f_bfff_ffff_eaaa, + 0x07aa_ffff_ac54_ffff, + 0xd9cc_34a8_3dac_3d89, + 0xd91d_d2e1_3ce1_44af, + 0x92c6_e9ed_90d2_eb35, + 0x0680_447a_8e5f_f9a6, + ]); + + // alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2) + let alpha = a1.square() * self; + + // x0 = self^((p + 1) / 4) + let x0 = a1 * self; + + // In the event that alpha = -1, the element is order p - 1 and so + // we're just trying to get the square of an element of the subfield + // Fp. This is given by x0 * u, since u = sqrt(-1). Since the element + // x0 = a + bu has b = 0, the solution is therefore au. + CtOption::new( + Fp2 { + c0: -x0.c1, + c1: x0.c0, + }, + alpha.ct_eq(&(&Fp2::one()).neg()), + ) + // Otherwise, the correct solution is (1 + alpha)^((q - 1) // 2) * x0 + .or_else(|| { + CtOption::new( + (alpha + Fp2::one()).pow_vartime(&[ + 0xdcff_7fff_ffff_d555, + 0x0f55_ffff_58a9_ffff, + 0xb398_6950_7b58_7b12, + 0xb23b_a5c2_79c2_895f, + 0x258d_d3db_21a5_d66b, + 0x0d00_88f5_1cbf_f34d, + ]) * x0, + Choice::from(1), + ) + }) + // Only return the result if it's really the square root (and so + // self is actually quadratic nonresidue) + .and_then(|sqrt| CtOption::new(sqrt, sqrt.square().ct_eq(self))) + }) + } + + /// Computes the multiplicative inverse of this field + /// element, returning None in the case that this element + /// is zero. + pub fn invert(&self) -> CtOption { + // We wish to find the multiplicative inverse of a nonzero + // element a + bu in Fp2. We leverage an identity + // + // (a + bu)(a - bu) = a^2 + b^2 + // + // which holds because u^2 = -1. This can be rewritten as + // + // (a + bu)(a - bu)/(a^2 + b^2) = 1 + // + // because a^2 + b^2 = 0 has no nonzero solutions for (a, b). + // This gives that (a - bu)/(a^2 + b^2) is the inverse + // of (a + bu). Importantly, this can be computing using + // only a single inversion in Fp. + + (self.c0.square() + self.c1.square()).invert().map(|t| Fp2 { + c0: self.c0 * t, + c1: self.c1 * -t, + }) + } + + /// Although this is labeled "vartime", it is only + /// variable time with respect to the exponent. It + /// is also not exposed in the public API. + pub fn pow_vartime(&self, by: &[u64; 6]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + + if ((*e >> i) & 1) == 1 { + res *= self; + } + } + } + res + } +} + +#[test] +fn test_conditional_selection() { + let a = Fp2 { + c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]), + c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]), + }; + let b = Fp2 { + c0: Fp::from_raw_unchecked([13, 14, 15, 16, 17, 18]), + c1: Fp::from_raw_unchecked([19, 20, 21, 22, 23, 24]), + }; + + assert_eq!( + ConditionallySelectable::conditional_select(&a, &b, Choice::from(0u8)), + a + ); + assert_eq!( + ConditionallySelectable::conditional_select(&a, &b, Choice::from(1u8)), + b + ); +} + +#[test] +fn test_equality() { + fn is_equal(a: &Fp2, b: &Fp2) -> bool { + let eq = a == b; + let ct_eq = a.ct_eq(&b); + + assert_eq!(eq, ct_eq.unwrap_u8() == 1); + + eq + } + + assert!(is_equal( + &Fp2 { + c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]), + c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]), + }, + &Fp2 { + c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]), + c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]), + } + )); + + assert!(!is_equal( + &Fp2 { + c0: Fp::from_raw_unchecked([2, 2, 3, 4, 5, 6]), + c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]), + }, + &Fp2 { + c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]), + c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]), + } + )); + + assert!(!is_equal( + &Fp2 { + c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]), + c1: Fp::from_raw_unchecked([2, 8, 9, 10, 11, 12]), + }, + &Fp2 { + c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]), + c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]), + } + )); +} + +#[test] +fn test_squaring() { + let a = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ]), + c1: Fp::from_raw_unchecked([ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ]), + }; + let b = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ]), + c1: Fp::from_raw_unchecked([ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ]), + }; + + assert_eq!(a.square(), b); +} + +#[test] +fn test_multiplication() { + let a = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ]), + c1: Fp::from_raw_unchecked([ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ]), + }; + let b = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ]), + c1: Fp::from_raw_unchecked([ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ]), + }; + let c = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xf597_483e_27b4_e0f7, + 0x610f_badf_811d_ae5f, + 0x8432_af91_7714_327a, + 0x6a9a_9603_cf88_f09e, + 0xf05a_7bf8_bad0_eb01, + 0x0954_9131_c003_ffae, + ]), + c1: Fp::from_raw_unchecked([ + 0x963b_02d0_f93d_37cd, + 0xc95c_e1cd_b30a_73d4, + 0x3087_25fa_3126_f9b8, + 0x56da_3c16_7fab_0d50, + 0x6b50_86b5_f4b6_d6af, + 0x09c3_9f06_2f18_e9f2, + ]), + }; + + assert_eq!(a * b, c); +} + +#[test] +fn test_addition() { + let a = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ]), + c1: Fp::from_raw_unchecked([ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ]), + }; + let b = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ]), + c1: Fp::from_raw_unchecked([ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ]), + }; + let c = Fp2 { + c0: Fp::from_raw_unchecked([ + 0x6b82_a9a7_08c1_32d2, + 0x476b_1da3_39ba_5ba4, + 0x848c_0e62_4b91_cd87, + 0x11f9_5955_295a_99ec, + 0xf337_6fce_2255_9f06, + 0x0c3f_e3fa_ce8c_8f43, + ]), + c1: Fp::from_raw_unchecked([ + 0x6f99_2c12_73ab_5bc5, + 0x3355_1366_17a1_df33, + 0x8b0e_f74c_0aed_aff9, + 0x062f_9246_8ad2_ca12, + 0xe146_9770_738f_d584, + 0x12c3_c3dd_84bc_a26d, + ]), + }; + + assert_eq!(a + b, c); +} + +#[test] +fn test_subtraction() { + let a = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ]), + c1: Fp::from_raw_unchecked([ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ]), + }; + let b = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + ]), + c1: Fp::from_raw_unchecked([ + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + ]), + }; + let c = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xe1c0_86bb_bf1b_5981, + 0x4faf_c3a9_aa70_5d7e, + 0x2734_b5c1_0bb7_e726, + 0xb2bd_7776_af03_7a3e, + 0x1b89_5fb3_98a8_4164, + 0x1730_4aef_6f11_3cec, + ]), + c1: Fp::from_raw_unchecked([ + 0x74c3_1c79_9519_1204, + 0x3271_aa54_79fd_ad2b, + 0xc9b4_7157_4915_a30f, + 0x65e4_0313_ec44_b8be, + 0x7487_b238_5b70_67cb, + 0x0952_3b26_d0ad_19a4, + ]), + }; + + assert_eq!(a - b, c); +} + +#[test] +fn test_negation() { + let a = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + ]), + c1: Fp::from_raw_unchecked([ + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + ]), + }; + let b = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xf05c_e7ce_9c11_39d7, + 0x6274_8f57_97e8_a36d, + 0xc4e8_d9df_c664_96df, + 0xb457_88e1_8118_9209, + 0x6949_13d0_8772_930d, + 0x1549_836a_3770_f3cf, + ]), + c1: Fp::from_raw_unchecked([ + 0x24d0_5bb9_fb9d_491c, + 0xfb1e_a120_c12e_39d0, + 0x7067_879f_c807_c7b1, + 0x60a9_269a_31bb_dab6, + 0x45c2_56bc_fd71_649b, + 0x18f6_9b5d_2b8a_fbde, + ]), + }; + + assert_eq!(-a, b); +} + +#[test] +fn test_sqrt() { + // a = 1488924004771393321054797166853618474668089414631333405711627789629391903630694737978065425271543178763948256226639*u + 784063022264861764559335808165825052288770346101304131934508881646553551234697082295473567906267937225174620141295 + let a = Fp2 { + c0: Fp::from_raw_unchecked([ + 0x2bee_d146_27d7_f9e9, + 0xb661_4e06_660e_5dce, + 0x06c4_cc7c_2f91_d42c, + 0x996d_7847_4b7a_63cc, + 0xebae_bc4c_820d_574e, + 0x1886_5e12_d93f_d845, + ]), + c1: Fp::from_raw_unchecked([ + 0x7d82_8664_baf4_f566, + 0xd17e_6639_96ec_7339, + 0x679e_ad55_cb40_78d0, + 0xfe3b_2260_e001_ec28, + 0x3059_93d0_43d9_1b68, + 0x0626_f03c_0489_b72d, + ]), + }; + + assert_eq!(a.sqrt().unwrap().square(), a); + + // b = 5, which is a generator of the p - 1 order + // multiplicative subgroup + let b = Fp2 { + c0: Fp::from_raw_unchecked([ + 0x6631_0000_0010_5545, + 0x2114_0040_0eec_000d, + 0x3fa7_af30_c820_e316, + 0xc52a_8b8d_6387_695d, + 0x9fb4_e61d_1e83_eac5, + 0x005c_b922_afe8_4dc7, + ]), + c1: Fp::zero(), + }; + + assert_eq!(b.sqrt().unwrap().square(), b); + + // c = 25, which is a generator of the (p - 1) / 2 order + // multiplicative subgroup + let c = Fp2 { + c0: Fp::from_raw_unchecked([ + 0x44f6_0000_0051_ffae, + 0x86b8_0141_9948_0043, + 0xd715_9952_f1f3_794a, + 0x755d_6e3d_fe1f_fc12, + 0xd36c_d6db_5547_e905, + 0x02f8_c8ec_bf18_67bb, + ]), + c1: Fp::zero(), + }; + + assert_eq!(c.sqrt().unwrap().square(), c); + + // 2155129644831861015726826462986972654175647013268275306775721078997042729172900466542651176384766902407257452753362*u + 2796889544896299244102912275102369318775038861758288697415827248356648685135290329705805931514906495247464901062529 + // is nonsquare. + assert!(bool::from( + Fp2 { + c0: Fp::from_raw_unchecked([ + 0xc5fa_1bc8_fd00_d7f6, + 0x3830_ca45_4606_003b, + 0x2b28_7f11_04b1_02da, + 0xa7fb_30f2_8230_f23e, + 0x339c_db9e_e953_dbf0, + 0x0d78_ec51_d989_fc57, + ]), + c1: Fp::from_raw_unchecked([ + 0x27ec_4898_cf87_f613, + 0x9de1_394e_1abb_05a5, + 0x0947_f85d_c170_fc14, + 0x586f_bc69_6b61_14b7, + 0x2b34_75a4_077d_7169, + 0x13e1_c895_cc4b_6c22, + ]) + } + .sqrt() + .is_none() + )); +} + +#[test] +fn test_inversion() { + let a = Fp2 { + c0: Fp::from_raw_unchecked([ + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + ]), + c1: Fp::from_raw_unchecked([ + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + ]), + }; + + let b = Fp2 { + c0: Fp::from_raw_unchecked([ + 0x0581_a133_3d4f_48a6, + 0x5824_2f6e_f074_8500, + 0x0292_c955_349e_6da5, + 0xba37_721d_dd95_fcd0, + 0x70d1_6790_3aa5_dfc5, + 0x1189_5e11_8b58_a9d5, + ]), + c1: Fp::from_raw_unchecked([ + 0x0eda_09d2_d7a8_5d17, + 0x8808_e137_a7d1_a2cf, + 0x43ae_2625_c1ff_21db, + 0xf85a_c9fd_f7a7_4c64, + 0x8fcc_dda5_b8da_9738, + 0x08e8_4f0c_b32c_d17d, + ]), + }; + + assert_eq!(a.invert().unwrap(), b); + + assert!(Fp2::zero().invert().is_none().unwrap_u8() == 1); +} + +#[test] +fn test_lexicographic_largest() { + assert!(!bool::from(Fp2::zero().lexicographically_largest())); + assert!(!bool::from(Fp2::one().lexicographically_largest())); + assert!(bool::from( + Fp2 { + c0: Fp::from_raw_unchecked([ + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + ]), + c1: Fp::from_raw_unchecked([ + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + ]), + } + .lexicographically_largest() + )); + assert!(!bool::from( + Fp2 { + c0: -Fp::from_raw_unchecked([ + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + ]), + c1: -Fp::from_raw_unchecked([ + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + ]), + } + .lexicographically_largest() + )); + assert!(!bool::from( + Fp2 { + c0: Fp::from_raw_unchecked([ + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + ]), + c1: Fp::zero(), + } + .lexicographically_largest() + )); + assert!(bool::from( + Fp2 { + c0: -Fp::from_raw_unchecked([ + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + ]), + c1: Fp::zero(), + } + .lexicographically_largest() + )); +} diff --git a/bls12_381/src/fp6.rs b/bls12_381/src/fp6.rs new file mode 100644 index 0000000..3f310dc --- /dev/null +++ b/bls12_381/src/fp6.rs @@ -0,0 +1,504 @@ +use crate::fp::*; +use crate::fp2::*; + +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +/// This represents an element $c_0 + c_1 v + c_2 v^2$ of $\mathbb{F}_{p^6} = \mathbb{F}_{p^2} / v^3 - u - 1$. +pub struct Fp6 { + pub c0: Fp2, + pub c1: Fp2, + pub c2: Fp2, +} + +impl From for Fp6 { + fn from(f: Fp) -> Fp6 { + Fp6 { + c0: Fp2::from(f), + c1: Fp2::zero(), + c2: Fp2::zero(), + } + } +} + +impl From for Fp6 { + fn from(f: Fp2) -> Fp6 { + Fp6 { + c0: f, + c1: Fp2::zero(), + c2: Fp2::zero(), + } + } +} + +impl PartialEq for Fp6 { + fn eq(&self, other: &Fp6) -> bool { + self.ct_eq(other).into() + } +} + +impl Copy for Fp6 {} +impl Clone for Fp6 { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl Default for Fp6 { + fn default() -> Self { + Fp6::zero() + } +} + +impl fmt::Debug for Fp6 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?} + ({:?})*v + ({:?})*v^2", self.c0, self.c1, self.c2) + } +} + +impl ConditionallySelectable for Fp6 { + #[inline(always)] + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fp6 { + c0: Fp2::conditional_select(&a.c0, &b.c0, choice), + c1: Fp2::conditional_select(&a.c1, &b.c1, choice), + c2: Fp2::conditional_select(&a.c2, &b.c2, choice), + } + } +} + +impl ConstantTimeEq for Fp6 { + #[inline(always)] + fn ct_eq(&self, other: &Self) -> Choice { + self.c0.ct_eq(&other.c0) & self.c1.ct_eq(&other.c1) & self.c2.ct_eq(&other.c2) + } +} + +impl Fp6 { + #[inline] + pub fn zero() -> Self { + Fp6 { + c0: Fp2::zero(), + c1: Fp2::zero(), + c2: Fp2::zero(), + } + } + + #[inline] + pub fn one() -> Self { + Fp6 { + c0: Fp2::one(), + c1: Fp2::zero(), + c2: Fp2::zero(), + } + } + + pub fn mul_by_1(&self, c1: &Fp2) -> Fp6 { + let b_b = self.c1 * c1; + + let t1 = (self.c1 + self.c2) * c1 - b_b; + let t1 = t1.mul_by_nonresidue(); + + let t2 = (self.c0 + self.c1) * c1 - b_b; + + Fp6 { + c0: t1, + c1: t2, + c2: b_b, + } + } + + pub fn mul_by_01(&self, c0: &Fp2, c1: &Fp2) -> Fp6 { + let a_a = self.c0 * c0; + let b_b = self.c1 * c1; + + let t1 = (self.c1 + self.c2) * c1 - b_b; + let t1 = t1.mul_by_nonresidue() + a_a; + + let t2 = (c0 + c1) * (self.c0 + self.c1) - a_a - b_b; + + let t3 = (self.c0 + self.c2) * c0 - a_a + b_b; + + Fp6 { + c0: t1, + c1: t2, + c2: t3, + } + } + + /// Multiply by quadratic nonresidue v. + pub fn mul_by_nonresidue(&self) -> Self { + // Given a + bv + cv^2, this produces + // av + bv^2 + cv^3 + // but because v^3 = u + 1, we have + // c(u + 1) + av + v^2 + + Fp6 { + c0: self.c2.mul_by_nonresidue(), + c1: self.c0, + c2: self.c1, + } + } + + /// Raises this element to p. + #[inline(always)] + pub fn frobenius_map(&self) -> Self { + let c0 = self.c0.frobenius_map(); + let c1 = self.c1.frobenius_map(); + let c2 = self.c2.frobenius_map(); + + // c1 = c1 * (u + 1)^((p - 1) / 3) + let c1 = c1 + * Fp2 { + c0: Fp::zero(), + c1: Fp::from_raw_unchecked([ + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741, + ]), + }; + + // c2 = c2 * (u + 1)^((2p - 2) / 3) + let c2 = c2 + * Fp2 { + c0: Fp::from_raw_unchecked([ + 0x890d_c9e4_8675_45c3, + 0x2af3_2253_3285_a5d5, + 0x5088_0866_309b_7e2c, + 0xa20d_1b8c_7e88_1024, + 0x14e4_f04f_e2db_9068, + 0x14e5_6d3f_1564_853a, + ]), + c1: Fp::zero(), + }; + + Fp6 { c0, c1, c2 } + } + + #[inline(always)] + pub fn is_zero(&self) -> Choice { + self.c0.is_zero() & self.c1.is_zero() & self.c2.is_zero() + } + + #[inline] + pub fn square(&self) -> Self { + let s0 = self.c0.square(); + let ab = self.c0 * self.c1; + let s1 = ab + ab; + let s2 = (self.c0 - self.c1 + self.c2).square(); + let bc = self.c1 * self.c2; + let s3 = bc + bc; + let s4 = self.c2.square(); + + Fp6 { + c0: s3.mul_by_nonresidue() + s0, + c1: s4.mul_by_nonresidue() + s1, + c2: s1 + s2 + s3 - s0 - s4, + } + } + + #[inline] + pub fn invert(&self) -> CtOption { + let c0 = (self.c1 * self.c2).mul_by_nonresidue(); + let c0 = self.c0.square() - c0; + + let c1 = self.c2.square().mul_by_nonresidue(); + let c1 = c1 - (self.c0 * self.c1); + + let c2 = self.c1.square(); + let c2 = c2 - (self.c0 * self.c2); + + let tmp = ((self.c1 * c2) + (self.c2 * c1)).mul_by_nonresidue(); + let tmp = tmp + (self.c0 * c0); + + tmp.invert().map(|t| Fp6 { + c0: t * c0, + c1: t * c1, + c2: t * c2, + }) + } +} + +impl<'a, 'b> Mul<&'b Fp6> for &'a Fp6 { + type Output = Fp6; + + #[inline] + fn mul(self, other: &'b Fp6) -> Self::Output { + let aa = self.c0 * other.c0; + let bb = self.c1 * other.c1; + let cc = self.c2 * other.c2; + + let t1 = other.c1 + other.c2; + let tmp = self.c1 + self.c2; + let t1 = t1 * tmp; + let t1 = t1 - bb; + let t1 = t1 - cc; + let t1 = t1.mul_by_nonresidue(); + let t1 = t1 + aa; + + let t3 = other.c0 + other.c2; + let tmp = self.c0 + self.c2; + let t3 = t3 * tmp; + let t3 = t3 - aa; + let t3 = t3 + bb; + let t3 = t3 - cc; + + let t2 = other.c0 + other.c1; + let tmp = self.c0 + self.c1; + let t2 = t2 * tmp; + let t2 = t2 - aa; + let t2 = t2 - bb; + let cc = cc.mul_by_nonresidue(); + let t2 = t2 + cc; + + Fp6 { + c0: t1, + c1: t2, + c2: t3, + } + } +} + +impl<'a, 'b> Add<&'b Fp6> for &'a Fp6 { + type Output = Fp6; + + #[inline] + fn add(self, rhs: &'b Fp6) -> Self::Output { + Fp6 { + c0: self.c0 + rhs.c0, + c1: self.c1 + rhs.c1, + c2: self.c2 + rhs.c2, + } + } +} + +impl<'a> Neg for &'a Fp6 { + type Output = Fp6; + + #[inline] + fn neg(self) -> Self::Output { + Fp6 { + c0: -self.c0, + c1: -self.c1, + c2: -self.c2, + } + } +} + +impl Neg for Fp6 { + type Output = Fp6; + + #[inline] + fn neg(self) -> Self::Output { + -&self + } +} + +impl<'a, 'b> Sub<&'b Fp6> for &'a Fp6 { + type Output = Fp6; + + #[inline] + fn sub(self, rhs: &'b Fp6) -> Self::Output { + Fp6 { + c0: self.c0 - rhs.c0, + c1: self.c1 - rhs.c1, + c2: self.c2 - rhs.c2, + } + } +} + +impl_binops_additive!(Fp6, Fp6); +impl_binops_multiplicative!(Fp6, Fp6); + +#[test] +fn test_arithmetic() { + use crate::fp::*; + + let a = Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d, + ]), + c1: Fp::from_raw_unchecked([ + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8, + ]), + c1: Fp::from_raw_unchecked([ + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6, + ]), + c1: Fp::from_raw_unchecked([ + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040, + ]), + }, + }; + + let b = Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xf120_cb98_b16f_d84b, + 0x5fb5_10cf_f3de_1d61, + 0x0f21_a5d0_69d8_c251, + 0xaa1f_d62f_34f2_839a, + 0x5a13_3515_7f89_913f, + 0x14a3_fe32_9643_c247, + ]), + c1: Fp::from_raw_unchecked([ + 0x3516_cb98_b16c_82f9, + 0x926d_10c2_e126_1d5f, + 0x1709_e01a_0cc2_5fba, + 0x96c8_c960_b825_3f14, + 0x4927_c234_207e_51a9, + 0x18ae_b158_d542_c44e, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xbf0d_cb98_b169_82fc, + 0xa679_10b7_1d1a_1d5c, + 0xb7c1_47c2_b8fb_06ff, + 0x1efa_710d_47d2_e7ce, + 0xed20_a79c_7e27_653c, + 0x02b8_5294_dac1_dfba, + ]), + c1: Fp::from_raw_unchecked([ + 0x9d52_cb98_b180_82e5, + 0x621d_1111_5176_1d6f, + 0xe798_8260_3b48_af43, + 0x0ad3_1637_a4f4_da37, + 0xaeac_737c_5ac1_cf2e, + 0x006e_7e73_5b48_b824, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xe148_cb98_b17d_2d93, + 0x94d5_1104_3ebe_1d6c, + 0xef80_bca9_de32_4cac, + 0xf77c_0969_2827_95b1, + 0x9dc1_009a_fbb6_8f97, + 0x0479_3199_9a47_ba2b, + ]), + c1: Fp::from_raw_unchecked([ + 0x253e_cb98_b179_d841, + 0xc78d_10f7_2c06_1d6a, + 0xf768_f6f3_811b_ea15, + 0xe424_fc9a_ab5a_512b, + 0x8cd5_8db9_9cab_5001, + 0x0883_e4bf_d946_bc32, + ]), + }, + }; + + let c = Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x6934_cb98_b176_82ef, + 0xfa45_10ea_194e_1d67, + 0xff51_313d_2405_877e, + 0xd0cd_efcc_2e8d_0ca5, + 0x7bea_1ad8_3da0_106b, + 0x0c8e_97e6_1845_be39, + ]), + c1: Fp::from_raw_unchecked([ + 0x4779_cb98_b18d_82d8, + 0xb5e9_1144_4daa_1d7a, + 0x2f28_6bda_a653_2fc2, + 0xbca6_94f6_8bae_ff0f, + 0x3d75_e6b8_1a3a_7a5d, + 0x0a44_c3c4_98cc_96a3, + ]), + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x8b6f_cb98_b18a_2d86, + 0xe8a1_1137_3af2_1d77, + 0x3710_a624_493c_cd2b, + 0xa94f_8828_0ee1_ba89, + 0x2c8a_73d6_bb2f_3ac7, + 0x0e4f_76ea_d7cb_98aa, + ]), + c1: Fp::from_raw_unchecked([ + 0xcf65_cb98_b186_d834, + 0x1b59_112a_283a_1d74, + 0x3ef8_e06d_ec26_6a95, + 0x95f8_7b59_9214_7603, + 0x1b9f_00f5_5c23_fb31, + 0x125a_2a11_16ca_9ab1, + ]), + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x135b_cb98_b183_82e2, + 0x4e11_111d_1582_1d72, + 0x46e1_1ab7_8f10_07fe, + 0x82a1_6e8b_1547_317d, + 0x0ab3_8e13_fd18_bb9b, + 0x1664_dd37_55c9_9cb8, + ]), + c1: Fp::from_raw_unchecked([ + 0xce65_cb98_b131_8334, + 0xc759_0fdb_7c3a_1d2e, + 0x6fcb_8164_9d1c_8eb3, + 0x0d44_004d_1727_356a, + 0x3746_b738_a7d0_d296, + 0x136c_144a_96b1_34fc, + ]), + }, + }; + + assert_eq!(a.square(), a * a); + assert_eq!(b.square(), b * b); + assert_eq!(c.square(), c * c); + + assert_eq!((a + b) * c.square(), (c * c * a) + (c * c * b)); + + assert_eq!( + a.invert().unwrap() * b.invert().unwrap(), + (a * b).invert().unwrap() + ); + assert_eq!(a.invert().unwrap() * a, Fp6::one()); +} diff --git a/bls12_381/src/g1.rs b/bls12_381/src/g1.rs new file mode 100644 index 0000000..5469fd6 --- /dev/null +++ b/bls12_381/src/g1.rs @@ -0,0 +1,1347 @@ +//! This module provides an implementation of the $\mathbb{G}_1$ group of BLS12-381. + +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use crate::fp::Fp; +use crate::Scalar; + +/// This is an element of $\mathbb{G}_1$ represented in the affine coordinate space. +/// It is ideal to keep elements in this representation to reduce memory usage and +/// improve performance through the use of mixed curve model arithmetic. +/// +/// Values of `G1Affine` are guaranteed to be in the $q$-order subgroup unless an +/// "unchecked" API was misused. +#[derive(Copy, Clone, Debug)] +pub struct G1Affine { + pub(crate) x: Fp, + pub(crate) y: Fp, + infinity: Choice, +} + +impl Default for G1Affine { + fn default() -> G1Affine { + G1Affine::identity() + } +} + +impl<'a> From<&'a G1Projective> for G1Affine { + fn from(p: &'a G1Projective) -> G1Affine { + let zinv = p.z.invert().unwrap_or(Fp::zero()); + let zinv2 = zinv.square(); + let x = p.x * zinv2; + let zinv3 = zinv2 * zinv; + let y = p.y * zinv3; + + let tmp = G1Affine { + x, + y, + infinity: Choice::from(0u8), + }; + + G1Affine::conditional_select(&tmp, &G1Affine::identity(), zinv.is_zero()) + } +} + +impl From for G1Affine { + fn from(p: G1Projective) -> G1Affine { + G1Affine::from(&p) + } +} + +impl ConstantTimeEq for G1Affine { + fn ct_eq(&self, other: &Self) -> Choice { + // The only cases in which two points are equal are + // 1. infinity is set on both + // 2. infinity is not set on both, and their coordinates are equal + + (self.infinity & other.infinity) + | ((!self.infinity) + & (!other.infinity) + & self.x.ct_eq(&other.x) + & self.y.ct_eq(&other.y)) + } +} + +impl ConditionallySelectable for G1Affine { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + G1Affine { + x: Fp::conditional_select(&a.x, &b.x, choice), + y: Fp::conditional_select(&a.y, &b.y, choice), + infinity: Choice::conditional_select(&a.infinity, &b.infinity, choice), + } + } +} + +impl Eq for G1Affine {} +impl PartialEq for G1Affine { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +impl<'a> Neg for &'a G1Affine { + type Output = G1Affine; + + #[inline] + fn neg(self) -> G1Affine { + G1Affine { + x: self.x, + y: Fp::conditional_select(&-self.y, &Fp::one(), self.infinity), + infinity: self.infinity, + } + } +} + +impl Neg for G1Affine { + type Output = G1Affine; + + #[inline] + fn neg(self) -> G1Affine { + -&self + } +} + +impl<'a, 'b> Add<&'b G1Projective> for &'a G1Affine { + type Output = G1Projective; + + #[inline] + fn add(self, rhs: &'b G1Projective) -> G1Projective { + rhs.add_mixed(self) + } +} + +impl<'a, 'b> Add<&'b G1Affine> for &'a G1Projective { + type Output = G1Projective; + + #[inline] + fn add(self, rhs: &'b G1Affine) -> G1Projective { + self.add_mixed(rhs) + } +} + +impl<'a, 'b> Sub<&'b G1Projective> for &'a G1Affine { + type Output = G1Projective; + + #[inline] + fn sub(self, rhs: &'b G1Projective) -> G1Projective { + self + (-rhs) + } +} + +impl<'a, 'b> Sub<&'b G1Affine> for &'a G1Projective { + type Output = G1Projective; + + #[inline] + fn sub(self, rhs: &'b G1Affine) -> G1Projective { + self + (-rhs) + } +} + +impl_binops_additive!(G1Projective, G1Affine); +impl_binops_additive_specify_output!(G1Affine, G1Projective, G1Projective); + +const B: Fp = Fp::from_raw_unchecked([ + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e, +]); + +impl G1Affine { + /// Returns the identity of the group: the point at infinity. + pub fn identity() -> G1Affine { + G1Affine { + x: Fp::zero(), + y: Fp::one(), + infinity: Choice::from(1u8), + } + } + + /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators) + /// for how this generator is chosen. + pub fn generator() -> G1Affine { + G1Affine { + x: Fp::from_raw_unchecked([ + 0x5cb3_8790_fd53_0c16, + 0x7817_fc67_9976_fff5, + 0x154f_95c7_143b_a1c1, + 0xf0ae_6acd_f3d0_e747, + 0xedce_6ecc_21db_f440, + 0x1201_7741_9e0b_fb75, + ]), + y: Fp::from_raw_unchecked([ + 0xbaac_93d5_0ce7_2271, + 0x8c22_631a_7918_fd8e, + 0xdd59_5f13_5707_25ce, + 0x51ac_5829_5040_5194, + 0x0e1c_8c3f_ad00_59c0, + 0x0bbc_3efc_5008_a26a, + ]), + infinity: Choice::from(0u8), + } + } + + /// Serializes this element into compressed form. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn to_compressed(&self) -> [u8; 48] { + // Strictly speaking, self.x is zero already when self.infinity is true, but + // to guard against implementation mistakes we do not assume this. + let mut res = Fp::conditional_select(&self.x, &Fp::zero(), self.infinity).to_bytes(); + + // This point is in compressed form, so we set the most significant bit. + res[0] |= 1u8 << 7; + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity); + + // Is the y-coordinate the lexicographically largest of the two associated with the + // x-coordinate? If so, set the third-most significant bit so long as this is not + // the point at infinity. + res[0] |= u8::conditional_select( + &0u8, + &(1u8 << 5), + (!self.infinity) & self.y.lexicographically_largest(), + ); + + res + } + + /// Serializes this element into uncompressed form. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn to_uncompressed(&self) -> [u8; 96] { + let mut res = [0; 96]; + + res[0..48].copy_from_slice( + &Fp::conditional_select(&self.x, &Fp::zero(), self.infinity).to_bytes()[..], + ); + res[48..96].copy_from_slice( + &Fp::conditional_select(&self.y, &Fp::zero(), self.infinity).to_bytes()[..], + ); + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity); + + res + } + + /// Attempts to deserialize an uncompressed element. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn from_uncompressed(bytes: &[u8; 96]) -> CtOption { + Self::from_uncompressed_unchecked(bytes) + .and_then(|p| CtOption::new(p, p.is_on_curve() & p.is_torsion_free())) + } + + /// Attempts to deserialize an uncompressed element, not checking if the + /// element is on the curve and not checking if it is in the correct subgroup. + /// **This is dangerous to call unless you trust the bytes you are reading; otherwise, + /// API invariants may be broken.** Please consider using `from_uncompressed()` instead. + pub fn from_uncompressed_unchecked(bytes: &[u8; 96]) -> CtOption { + // Obtain the three flags from the start of the byte sequence + let compression_flag_set = Choice::from((bytes[0] >> 7) & 1); + let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1); + let sort_flag_set = Choice::from((bytes[0] >> 5) & 1); + + // Attempt to obtain the x-coordinate + let x = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[0..48]); + + // Mask away the flag bits + tmp[0] &= 0b0001_1111; + + Fp::from_bytes(&tmp) + }; + + // Attempt to obtain the y-coordinate + let y = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[48..96]); + + Fp::from_bytes(&tmp) + }; + + x.and_then(|x| { + y.and_then(|y| { + // Create a point representing this value + let p = G1Affine::conditional_select( + &G1Affine { + x, + y, + infinity: infinity_flag_set, + }, + &G1Affine::identity(), + infinity_flag_set, + ); + + CtOption::new( + p, + // If the infinity flag is set, the x and y coordinates should have been zero. + ((!infinity_flag_set) | (infinity_flag_set & x.is_zero() & y.is_zero())) & + // The compression flag should not have been set, as this is an uncompressed element + (!compression_flag_set) & + // The sort flag should not have been set, as this is an uncompressed element + (!sort_flag_set), + ) + }) + }) + } + + /// Attempts to deserialize a compressed element. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn from_compressed(bytes: &[u8; 48]) -> CtOption { + // We already know the point is on the curve because this is established + // by the y-coordinate recovery procedure in from_compressed_unchecked(). + + Self::from_compressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_torsion_free())) + } + + /// Attempts to deserialize an uncompressed element, not checking if the + /// element is in the correct subgroup. + /// **This is dangerous to call unless you trust the bytes you are reading; otherwise, + /// API invariants may be broken.** Please consider using `from_compressed()` instead. + pub fn from_compressed_unchecked(bytes: &[u8; 48]) -> CtOption { + // Obtain the three flags from the start of the byte sequence + let compression_flag_set = Choice::from((bytes[0] >> 7) & 1); + let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1); + let sort_flag_set = Choice::from((bytes[0] >> 5) & 1); + + // Attempt to obtain the x-coordinate + let x = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[0..48]); + + // Mask away the flag bits + tmp[0] &= 0b0001_1111; + + Fp::from_bytes(&tmp) + }; + + x.and_then(|x| { + // If the infinity flag is set, return the value assuming + // the x-coordinate is zero and the sort bit is not set. + // + // Otherwise, return a recovered point (assuming the correct + // y-coordinate can be found) so long as the infinity flag + // was not set. + CtOption::new( + G1Affine::identity(), + infinity_flag_set & // Infinity flag should be set + compression_flag_set & // Compression flag should be set + (!sort_flag_set) & // Sort flag should not be set + x.is_zero(), // The x-coordinate should be zero + ) + .or_else(|| { + // Recover a y-coordinate given x by y = sqrt(x^3 + 4) + ((x.square() * x) + B).sqrt().and_then(|y| { + // Switch to the correct y-coordinate if necessary. + let y = Fp::conditional_select( + &y, + &-y, + y.lexicographically_largest() ^ sort_flag_set, + ); + + CtOption::new( + G1Affine { + x, + y, + infinity: infinity_flag_set, + }, + (!infinity_flag_set) & // Infinity flag should not be set + compression_flag_set, // Compression flag should be set + ) + }) + }) + }) + } + + /// Returns true if this element is the identity (the point at infinity). + #[inline] + pub fn is_identity(&self) -> Choice { + self.infinity + } + + /// Returns true if this point is free of an $h$-torsion component, and so it + /// exists within the $q$-order subgroup $\mathbb{G}_1$. This should always return true + /// unless an "unchecked" API was used. + pub fn is_torsion_free(&self) -> Choice { + const FQ_MODULUS_BYTES: [u8; 32] = [ + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115, + ]; + + // Clear the r-torsion from the point and check if it is the identity + G1Projective::from(*self) + .multiply(&FQ_MODULUS_BYTES) + .is_identity() + } + + /// Returns true if this point is on the curve. This should always return + /// true unless an "unchecked" API was used. + pub fn is_on_curve(&self) -> Choice { + // y^2 - x^3 ?= 4 + (self.y.square() - (self.x.square() * self.x)).ct_eq(&B) | self.infinity + } +} + +/// This is an element of $\mathbb{G}_1$ represented in the projective coordinate space. +#[derive(Copy, Clone, Debug)] +pub struct G1Projective { + x: Fp, + y: Fp, + z: Fp, +} + +impl<'a> From<&'a G1Affine> for G1Projective { + fn from(p: &'a G1Affine) -> G1Projective { + G1Projective { + x: p.x, + y: p.y, + z: Fp::conditional_select(&Fp::one(), &Fp::zero(), p.infinity), + } + } +} + +impl From for G1Projective { + fn from(p: G1Affine) -> G1Projective { + G1Projective::from(&p) + } +} + +impl ConstantTimeEq for G1Projective { + fn ct_eq(&self, other: &Self) -> Choice { + // Is (xz^2, yz^3, z) equal to (x'z'^2, yz'^3, z') when converted to affine? + + let z = other.z.square(); + let x1 = self.x * z; + let z = z * other.z; + let y1 = self.y * z; + let z = self.z.square(); + let x2 = other.x * z; + let z = z * self.z; + let y2 = other.y * z; + + let self_is_zero = self.z.is_zero(); + let other_is_zero = other.z.is_zero(); + + (self_is_zero & other_is_zero) // Both point at infinity + | ((!self_is_zero) & (!other_is_zero) & x1.ct_eq(&x2) & y1.ct_eq(&y2)) + // Neither point at infinity, coordinates are the same + } +} + +impl ConditionallySelectable for G1Projective { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + G1Projective { + x: Fp::conditional_select(&a.x, &b.x, choice), + y: Fp::conditional_select(&a.y, &b.y, choice), + z: Fp::conditional_select(&a.z, &b.z, choice), + } + } +} + +impl Eq for G1Projective {} +impl PartialEq for G1Projective { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +impl<'a> Neg for &'a G1Projective { + type Output = G1Projective; + + #[inline] + fn neg(self) -> G1Projective { + G1Projective { + x: self.x, + y: -self.y, + z: self.z, + } + } +} + +impl Neg for G1Projective { + type Output = G1Projective; + + #[inline] + fn neg(self) -> G1Projective { + -&self + } +} + +impl<'a, 'b> Add<&'b G1Projective> for &'a G1Projective { + type Output = G1Projective; + + #[inline] + fn add(self, rhs: &'b G1Projective) -> G1Projective { + self.add(rhs) + } +} + +impl<'a, 'b> Sub<&'b G1Projective> for &'a G1Projective { + type Output = G1Projective; + + #[inline] + fn sub(self, rhs: &'b G1Projective) -> G1Projective { + self + (-rhs) + } +} + +impl<'a, 'b> Mul<&'b Scalar> for &'a G1Projective { + type Output = G1Projective; + + fn mul(self, other: &'b Scalar) -> Self::Output { + self.multiply(&other.to_bytes()) + } +} + +impl<'a, 'b> Mul<&'b Scalar> for &'a G1Affine { + type Output = G1Projective; + + fn mul(self, other: &'b Scalar) -> Self::Output { + G1Projective::from(self).multiply(&other.to_bytes()) + } +} + +impl_binops_additive!(G1Projective, G1Projective); +impl_binops_multiplicative!(G1Projective, Scalar); +impl_binops_multiplicative_mixed!(G1Affine, Scalar, G1Projective); + +impl G1Projective { + /// Returns the identity of the group: the point at infinity. + pub fn identity() -> G1Projective { + G1Projective { + x: Fp::zero(), + y: Fp::one(), + z: Fp::zero(), + } + } + + /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators) + /// for how this generator is chosen. + pub fn generator() -> G1Projective { + G1Projective { + x: Fp::from_raw_unchecked([ + 0x5cb3_8790_fd53_0c16, + 0x7817_fc67_9976_fff5, + 0x154f_95c7_143b_a1c1, + 0xf0ae_6acd_f3d0_e747, + 0xedce_6ecc_21db_f440, + 0x1201_7741_9e0b_fb75, + ]), + y: Fp::from_raw_unchecked([ + 0xbaac_93d5_0ce7_2271, + 0x8c22_631a_7918_fd8e, + 0xdd59_5f13_5707_25ce, + 0x51ac_5829_5040_5194, + 0x0e1c_8c3f_ad00_59c0, + 0x0bbc_3efc_5008_a26a, + ]), + z: Fp::one(), + } + } + + /// Computes the doubling of this point. + pub fn double(&self) -> G1Projective { + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + // + // There are no points of order 2. + + let a = self.x.square(); + let b = self.y.square(); + let c = b.square(); + let d = self.x + b; + let d = d.square(); + let d = d - a - c; + let d = d + d; + let e = a + a + a; + let f = e.square(); + let z3 = self.z * self.y; + let z3 = z3 + z3; + let x3 = f - (d + d); + let c = c + c; + let c = c + c; + let c = c + c; + let y3 = e * (d - x3) - c; + + let tmp = G1Projective { + x: x3, + y: y3, + z: z3, + }; + + G1Projective::conditional_select(&tmp, &G1Projective::identity(), self.is_identity()) + } + + /// Adds this point to another point. + pub fn add(&self, rhs: &G1Projective) -> G1Projective { + // This Jacobian point addition technique is based on the implementation in libsecp256k1, + // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally. + + // If self is the identity, return rhs. Otherwise, return self. The other cases will be + // predicated on neither self nor rhs being the identity. + let f1 = self.is_identity(); + let res = G1Projective::conditional_select(self, rhs, f1); + let f2 = rhs.is_identity(); + + // If neither are the identity but x1 = x2 and y1 != y2, then return the identity + let z = rhs.z.square(); + let u1 = self.x * z; + let z = z * rhs.z; + let s1 = self.y * z; + let z = self.z.square(); + let u2 = rhs.x * z; + let z = z * self.z; + let s2 = rhs.y * z; + let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2)); + let res = + G1Projective::conditional_select(&res, &G1Projective::identity(), (!f1) & (!f2) & f3); + + let t = u1 + u2; + let m = s1 + s2; + let rr = t.square(); + let m_alt = -u2; + let tt = u1 * m_alt; + let rr = rr + tt; + + // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3. + // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda. + let degenerate = m.is_zero() & rr.is_zero(); + let rr_alt = s1 + s1; + let m_alt = m_alt + u1; + let rr_alt = Fp::conditional_select(&rr_alt, &rr, !degenerate); + let m_alt = Fp::conditional_select(&m_alt, &m, !degenerate); + + let n = m_alt.square(); + let q = n * t; + + let n = n.square(); + let n = Fp::conditional_select(&n, &m, degenerate); + let t = rr_alt.square(); + let z3 = m_alt * self.z * rhs.z; // We allow rhs.z != 1, so we must account for this. + let z3 = z3 + z3; + let q = -q; + let t = t + q; + let x3 = t; + let t = t + t; + let t = t + q; + let t = t * rr_alt; + let t = t + n; + let y3 = -t; + let x3 = x3 + x3; + let x3 = x3 + x3; + let y3 = y3 + y3; + let y3 = y3 + y3; + + let tmp = G1Projective { + x: x3, + y: y3, + z: z3, + }; + + G1Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3)) + } + + /// Adds this point to another point in the affine model. + pub fn add_mixed(&self, rhs: &G1Affine) -> G1Projective { + // This Jacobian point addition technique is based on the implementation in libsecp256k1, + // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally. + + // If self is the identity, return rhs. Otherwise, return self. The other cases will be + // predicated on neither self nor rhs being the identity. + let f1 = self.is_identity(); + let res = G1Projective::conditional_select(self, &G1Projective::from(rhs), f1); + let f2 = rhs.is_identity(); + + // If neither are the identity but x1 = x2 and y1 != y2, then return the identity + let u1 = self.x; + let s1 = self.y; + let z = self.z.square(); + let u2 = rhs.x * z; + let z = z * self.z; + let s2 = rhs.y * z; + let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2)); + let res = + G1Projective::conditional_select(&res, &G1Projective::identity(), (!f1) & (!f2) & f3); + + let t = u1 + u2; + let m = s1 + s2; + let rr = t.square(); + let m_alt = -u2; + let tt = u1 * m_alt; + let rr = rr + tt; + + // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3. + // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda. + let degenerate = m.is_zero() & rr.is_zero(); + let rr_alt = s1 + s1; + let m_alt = m_alt + u1; + let rr_alt = Fp::conditional_select(&rr_alt, &rr, !degenerate); + let m_alt = Fp::conditional_select(&m_alt, &m, !degenerate); + + let n = m_alt.square(); + let q = n * t; + + let n = n.square(); + let n = Fp::conditional_select(&n, &m, degenerate); + let t = rr_alt.square(); + let z3 = m_alt * self.z; + let z3 = z3 + z3; + let q = -q; + let t = t + q; + let x3 = t; + let t = t + t; + let t = t + q; + let t = t * rr_alt; + let t = t + n; + let y3 = -t; + let x3 = x3 + x3; + let x3 = x3 + x3; + let y3 = y3 + y3; + let y3 = y3 + y3; + + let tmp = G1Projective { + x: x3, + y: y3, + z: z3, + }; + + G1Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3)) + } + + fn multiply(&self, by: &[u8; 32]) -> G1Projective { + let mut acc = G1Projective::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading bit because it's always unset for Fq + // elements. + for bit in by + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(1) + { + acc = acc.double(); + acc = G1Projective::conditional_select(&acc, &(acc + self), bit); + } + + acc + } + + /// Converts a batch of `G1Projective` elements into `G1Affine` elements. This + /// function will panic if `p.len() != q.len()`. + pub fn batch_normalize(p: &[Self], q: &mut [G1Affine]) { + assert_eq!(p.len(), q.len()); + + let mut acc = Fp::one(); + for (p, q) in p.iter().zip(q.iter_mut()) { + // We use the `x` field of `G1Affine` to store the product + // of previous z-coordinates seen. + q.x = acc; + + // We will end up skipping all identities in p + acc = Fp::conditional_select(&(acc * p.z), &acc, p.is_identity()); + } + + // This is the inverse, as all z-coordinates are nonzero and the ones + // that are not are skipped. + acc = acc.invert().unwrap(); + + for (p, q) in p.iter().rev().zip(q.iter_mut().rev()) { + let skip = p.is_identity(); + + // Compute tmp = 1/z + let tmp = q.x * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc = Fp::conditional_select(&(acc * p.z), &acc, skip); + + // Set the coordinates to the correct value + let tmp2 = tmp.square(); + let tmp3 = tmp2 * tmp; + + q.x = p.x * tmp2; + q.y = p.y * tmp3; + q.infinity = Choice::from(0u8); + + *q = G1Affine::conditional_select(&q, &G1Affine::identity(), skip); + } + } + + /// Returns true if this element is the identity (the point at infinity). + #[inline] + pub fn is_identity(&self) -> Choice { + self.z.is_zero() + } + + /// Returns true if this point is on the curve. This should always return + /// true unless an "unchecked" API was used. + pub fn is_on_curve(&self) -> Choice { + // Y^2 - X^3 = 4(Z^6) + + (self.y.square() - (self.x.square() * self.x)) + .ct_eq(&((self.z.square() * self.z).square() * B)) + | self.z.is_zero() + } +} + +#[test] +fn test_is_on_curve() { + assert!(bool::from(G1Affine::identity().is_on_curve())); + assert!(bool::from(G1Affine::generator().is_on_curve())); + assert!(bool::from(G1Projective::identity().is_on_curve())); + assert!(bool::from(G1Projective::generator().is_on_curve())); + + let z = Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]); + + let gen = G1Affine::generator(); + let mut test = G1Projective { + x: gen.x * (z.square()), + y: gen.y * (z.square() * z), + z, + }; + + assert!(bool::from(test.is_on_curve())); + + test.x = z; + assert!(!bool::from(test.is_on_curve())); +} + +#[test] +#[allow(clippy::eq_op)] +fn test_affine_point_equality() { + let a = G1Affine::generator(); + let b = G1Affine::identity(); + + assert!(a == a); + assert!(b == b); + assert!(a != b); + assert!(b != a); +} + +#[test] +#[allow(clippy::eq_op)] +fn test_projective_point_equality() { + let a = G1Projective::generator(); + let b = G1Projective::identity(); + + assert!(a == a); + assert!(b == b); + assert!(a != b); + assert!(b != a); + + let z = Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]); + + let mut c = G1Projective { + x: a.x * (z.square()), + y: a.y * (z.square() * z), + z, + }; + assert!(bool::from(c.is_on_curve())); + + assert!(a == c); + assert!(b != c); + assert!(c == a); + assert!(c != b); + + c.y = -c.y; + assert!(bool::from(c.is_on_curve())); + + assert!(a != c); + assert!(b != c); + assert!(c != a); + assert!(c != b); + + c.y = -c.y; + c.x = z; + assert!(!bool::from(c.is_on_curve())); + assert!(a != b); + assert!(a != c); + assert!(b != c); +} + +#[test] +fn test_conditionally_select_affine() { + let a = G1Affine::generator(); + let b = G1Affine::identity(); + + assert_eq!(G1Affine::conditional_select(&a, &b, Choice::from(0u8)), a); + assert_eq!(G1Affine::conditional_select(&a, &b, Choice::from(1u8)), b); +} + +#[test] +fn test_conditionally_select_projective() { + let a = G1Projective::generator(); + let b = G1Projective::identity(); + + assert_eq!( + G1Projective::conditional_select(&a, &b, Choice::from(0u8)), + a + ); + assert_eq!( + G1Projective::conditional_select(&a, &b, Choice::from(1u8)), + b + ); +} + +#[test] +fn test_projective_to_affine() { + let a = G1Projective::generator(); + let b = G1Projective::identity(); + + assert!(bool::from(G1Affine::from(a).is_on_curve())); + assert!(!bool::from(G1Affine::from(a).is_identity())); + assert!(bool::from(G1Affine::from(b).is_on_curve())); + assert!(bool::from(G1Affine::from(b).is_identity())); + + let z = Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]); + + let c = G1Projective { + x: a.x * (z.square()), + y: a.y * (z.square() * z), + z, + }; + + assert_eq!(G1Affine::from(c), G1Affine::generator()); +} + +#[test] +fn test_affine_to_projective() { + let a = G1Affine::generator(); + let b = G1Affine::identity(); + + assert!(bool::from(G1Projective::from(a).is_on_curve())); + assert!(!bool::from(G1Projective::from(a).is_identity())); + assert!(bool::from(G1Projective::from(b).is_on_curve())); + assert!(bool::from(G1Projective::from(b).is_identity())); +} + +#[test] +fn test_doubling() { + { + let tmp = G1Projective::identity().double(); + assert!(bool::from(tmp.is_identity())); + assert!(bool::from(tmp.is_on_curve())); + } + { + let tmp = G1Projective::generator().double(); + assert!(!bool::from(tmp.is_identity())); + assert!(bool::from(tmp.is_on_curve())); + + assert_eq!( + G1Affine::from(tmp), + G1Affine { + x: Fp::from_raw_unchecked([ + 0x53e9_78ce_58a9_ba3c, + 0x3ea0_583c_4f3d_65f9, + 0x4d20_bb47_f001_2960, + 0xa54c_664a_e5b2_b5d9, + 0x26b5_52a3_9d7e_b21f, + 0x0008_895d_26e6_8785, + ]), + y: Fp::from_raw_unchecked([ + 0x7011_0b32_9829_3940, + 0xda33_c539_3f1f_6afc, + 0xb86e_dfd1_6a5a_a785, + 0xaec6_d1c9_e7b1_c895, + 0x25cf_c2b5_22d1_1720, + 0x0636_1c83_f8d0_9b15, + ]), + infinity: Choice::from(0u8) + } + ); + } +} + +#[test] +fn test_projective_addition() { + { + let a = G1Projective::identity(); + let b = G1Projective::identity(); + let c = a + b; + assert!(bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } + { + let a = G1Projective::identity(); + let mut b = G1Projective::generator(); + { + let z = Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]); + + b = G1Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = a + b; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G1Projective::generator()); + } + { + let a = G1Projective::identity(); + let mut b = G1Projective::generator(); + { + let z = Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]); + + b = G1Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = b + a; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G1Projective::generator()); + } + { + let a = G1Projective::generator().double().double(); // 4P + let b = G1Projective::generator().double(); // 2P + let c = a + b; + + let mut d = G1Projective::generator(); + for _ in 0..5 { + d += G1Projective::generator(); + } + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(!bool::from(d.is_identity())); + assert!(bool::from(d.is_on_curve())); + assert_eq!(c, d); + } + + // Degenerate case + { + let beta = Fp::from_raw_unchecked([ + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741, + ]); + let beta = beta.square(); + let a = G1Projective::generator().double().double(); + let b = G1Projective { + x: a.x * beta, + y: -a.y, + z: a.z, + }; + assert!(bool::from(a.is_on_curve())); + assert!(bool::from(b.is_on_curve())); + + let c = a + b; + assert_eq!( + G1Affine::from(c), + G1Affine::from(G1Projective { + x: Fp::from_raw_unchecked([ + 0x29e1_e987_ef68_f2d0, + 0xc5f3_ec53_1db0_3233, + 0xacd6_c4b6_ca19_730f, + 0x18ad_9e82_7bc2_bab7, + 0x46e3_b2c5_785c_c7a9, + 0x07e5_71d4_2d22_ddd6, + ]), + y: Fp::from_raw_unchecked([ + 0x94d1_17a7_e5a5_39e7, + 0x8e17_ef67_3d4b_5d22, + 0x9d74_6aaf_508a_33ea, + 0x8c6d_883d_2516_c9a2, + 0x0bc3_b8d5_fb04_47f7, + 0x07bf_a4c7_210f_4f44, + ]), + z: Fp::one() + }) + ); + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } +} + +#[test] +fn test_mixed_addition() { + { + let a = G1Affine::identity(); + let b = G1Projective::identity(); + let c = a + b; + assert!(bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } + { + let a = G1Affine::identity(); + let mut b = G1Projective::generator(); + { + let z = Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]); + + b = G1Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = a + b; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G1Projective::generator()); + } + { + let a = G1Affine::identity(); + let mut b = G1Projective::generator(); + { + let z = Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]); + + b = G1Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = b + a; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G1Projective::generator()); + } + { + let a = G1Projective::generator().double().double(); // 4P + let b = G1Projective::generator().double(); // 2P + let c = a + b; + + let mut d = G1Projective::generator(); + for _ in 0..5 { + d += G1Affine::generator(); + } + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(!bool::from(d.is_identity())); + assert!(bool::from(d.is_on_curve())); + assert_eq!(c, d); + } + + // Degenerate case + { + let beta = Fp::from_raw_unchecked([ + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741, + ]); + let beta = beta.square(); + let a = G1Projective::generator().double().double(); + let b = G1Projective { + x: a.x * beta, + y: -a.y, + z: a.z, + }; + let a = G1Affine::from(a); + assert!(bool::from(a.is_on_curve())); + assert!(bool::from(b.is_on_curve())); + + let c = a + b; + assert_eq!( + G1Affine::from(c), + G1Affine::from(G1Projective { + x: Fp::from_raw_unchecked([ + 0x29e1_e987_ef68_f2d0, + 0xc5f3_ec53_1db0_3233, + 0xacd6_c4b6_ca19_730f, + 0x18ad_9e82_7bc2_bab7, + 0x46e3_b2c5_785c_c7a9, + 0x07e5_71d4_2d22_ddd6, + ]), + y: Fp::from_raw_unchecked([ + 0x94d1_17a7_e5a5_39e7, + 0x8e17_ef67_3d4b_5d22, + 0x9d74_6aaf_508a_33ea, + 0x8c6d_883d_2516_c9a2, + 0x0bc3_b8d5_fb04_47f7, + 0x07bf_a4c7_210f_4f44, + ]), + z: Fp::one() + }) + ); + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } +} + +#[test] +#[allow(clippy::eq_op)] +fn test_projective_negation_and_subtraction() { + let a = G1Projective::generator().double(); + assert_eq!(a + (-a), G1Projective::identity()); + assert_eq!(a + (-a), a - a); +} + +#[test] +fn test_affine_negation_and_subtraction() { + let a = G1Affine::generator(); + assert_eq!(G1Projective::from(a) + (-a), G1Projective::identity()); + assert_eq!(G1Projective::from(a) + (-a), G1Projective::from(a) - a); +} + +#[test] +fn test_projective_scalar_multiplication() { + let g = G1Projective::generator(); + let a = Scalar::from_raw([ + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2, + ]); + let b = Scalar::from_raw([ + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20, + ]); + let c = a * b; + + assert_eq!((g * a) * b, g * c); +} + +#[test] +fn test_affine_scalar_multiplication() { + let g = G1Affine::generator(); + let a = Scalar::from_raw([ + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2, + ]); + let b = Scalar::from_raw([ + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20, + ]); + let c = a * b; + + assert_eq!(G1Affine::from(g * a) * b, g * c); +} + +#[test] +fn test_is_torsion_free() { + let a = G1Affine { + x: Fp::from_raw_unchecked([ + 0x0aba_f895_b97e_43c8, + 0xba4c_6432_eb9b_61b0, + 0x1250_6f52_adfe_307f, + 0x7502_8c34_3933_6b72, + 0x8474_4f05_b8e9_bd71, + 0x113d_554f_b095_54f7, + ]), + y: Fp::from_raw_unchecked([ + 0x73e9_0e88_f5cf_01c0, + 0x3700_7b65_dd31_97e2, + 0x5cf9_a199_2f0d_7c78, + 0x4f83_c10b_9eb3_330d, + 0xf6a6_3f6f_07f6_0961, + 0x0c53_b5b9_7e63_4df3, + ]), + infinity: Choice::from(0u8), + }; + assert!(!bool::from(a.is_torsion_free())); + + assert!(bool::from(G1Affine::identity().is_torsion_free())); + assert!(bool::from(G1Affine::generator().is_torsion_free())); +} + +#[test] +fn test_batch_normalize() { + let a = G1Projective::generator().double(); + let b = a.double(); + let c = b.double(); + + for a_identity in (0..1).map(|n| n == 1) { + for b_identity in (0..1).map(|n| n == 1) { + for c_identity in (0..1).map(|n| n == 1) { + let mut v = [a, b, c]; + if a_identity { + v[0] = G1Projective::identity() + } + if b_identity { + v[1] = G1Projective::identity() + } + if c_identity { + v[2] = G1Projective::identity() + } + + let mut t = [ + G1Affine::identity(), + G1Affine::identity(), + G1Affine::identity(), + ]; + let expected = [ + G1Affine::from(v[0]), + G1Affine::from(v[1]), + G1Affine::from(v[2]), + ]; + + G1Projective::batch_normalize(&v[..], &mut t[..]); + + assert_eq!(&t[..], &expected[..]); + } + } + } +} diff --git a/bls12_381/src/g2.rs b/bls12_381/src/g2.rs new file mode 100644 index 0000000..d3f505b --- /dev/null +++ b/bls12_381/src/g2.rs @@ -0,0 +1,1595 @@ +//! This module provides an implementation of the $\mathbb{G}_2$ group of BLS12-381. + +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use crate::fp::Fp; +use crate::fp2::Fp2; +use crate::Scalar; + +/// This is an element of $\mathbb{G}_2$ represented in the affine coordinate space. +/// It is ideal to keep elements in this representation to reduce memory usage and +/// improve performance through the use of mixed curve model arithmetic. +/// +/// Values of `G2Affine` are guaranteed to be in the $q$-order subgroup unless an +/// "unchecked" API was misused. +#[derive(Copy, Clone, Debug)] +pub struct G2Affine { + pub(crate) x: Fp2, + pub(crate) y: Fp2, + infinity: Choice, +} + +impl Default for G2Affine { + fn default() -> G2Affine { + G2Affine::identity() + } +} + +impl<'a> From<&'a G2Projective> for G2Affine { + fn from(p: &'a G2Projective) -> G2Affine { + let zinv = p.z.invert().unwrap_or(Fp2::zero()); + let zinv2 = zinv.square(); + let x = p.x * zinv2; + let zinv3 = zinv2 * zinv; + let y = p.y * zinv3; + + let tmp = G2Affine { + x, + y, + infinity: Choice::from(0u8), + }; + + G2Affine::conditional_select(&tmp, &G2Affine::identity(), zinv.is_zero()) + } +} + +impl From for G2Affine { + fn from(p: G2Projective) -> G2Affine { + G2Affine::from(&p) + } +} + +impl ConstantTimeEq for G2Affine { + fn ct_eq(&self, other: &Self) -> Choice { + // The only cases in which two points are equal are + // 1. infinity is set on both + // 2. infinity is not set on both, and their coordinates are equal + + (self.infinity & other.infinity) + | ((!self.infinity) + & (!other.infinity) + & self.x.ct_eq(&other.x) + & self.y.ct_eq(&other.y)) + } +} + +impl ConditionallySelectable for G2Affine { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + G2Affine { + x: Fp2::conditional_select(&a.x, &b.x, choice), + y: Fp2::conditional_select(&a.y, &b.y, choice), + infinity: Choice::conditional_select(&a.infinity, &b.infinity, choice), + } + } +} + +impl Eq for G2Affine {} +impl PartialEq for G2Affine { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +impl<'a> Neg for &'a G2Affine { + type Output = G2Affine; + + #[inline] + fn neg(self) -> G2Affine { + G2Affine { + x: self.x, + y: Fp2::conditional_select(&-self.y, &Fp2::one(), self.infinity), + infinity: self.infinity, + } + } +} + +impl Neg for G2Affine { + type Output = G2Affine; + + #[inline] + fn neg(self) -> G2Affine { + -&self + } +} + +impl<'a, 'b> Add<&'b G2Projective> for &'a G2Affine { + type Output = G2Projective; + + #[inline] + fn add(self, rhs: &'b G2Projective) -> G2Projective { + rhs.add_mixed(self) + } +} + +impl<'a, 'b> Add<&'b G2Affine> for &'a G2Projective { + type Output = G2Projective; + + #[inline] + fn add(self, rhs: &'b G2Affine) -> G2Projective { + self.add_mixed(rhs) + } +} + +impl<'a, 'b> Sub<&'b G2Projective> for &'a G2Affine { + type Output = G2Projective; + + #[inline] + fn sub(self, rhs: &'b G2Projective) -> G2Projective { + self + (-rhs) + } +} + +impl<'a, 'b> Sub<&'b G2Affine> for &'a G2Projective { + type Output = G2Projective; + + #[inline] + fn sub(self, rhs: &'b G2Affine) -> G2Projective { + self + (-rhs) + } +} + +impl_binops_additive!(G2Projective, G2Affine); +impl_binops_additive_specify_output!(G2Affine, G2Projective, G2Projective); + +const B: Fp2 = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e, + ]), + c1: Fp::from_raw_unchecked([ + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e, + ]), +}; + +impl G2Affine { + /// Returns the identity of the group: the point at infinity. + pub fn identity() -> G2Affine { + G2Affine { + x: Fp2::zero(), + y: Fp2::one(), + infinity: Choice::from(1u8), + } + } + + /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators) + /// for how this generator is chosen. + pub fn generator() -> G2Affine { + G2Affine { + x: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xf5f2_8fa2_0294_0a10, + 0xb3f5_fb26_87b4_961a, + 0xa1a8_93b5_3e2a_e580, + 0x9894_999d_1a3c_aee9, + 0x6f67_b763_1863_366b, + 0x0581_9192_4350_bcd7, + ]), + c1: Fp::from_raw_unchecked([ + 0xa5a9_c075_9e23_f606, + 0xaaa0_c59d_bccd_60c3, + 0x3bb1_7e18_e286_7806, + 0x1b1a_b6cc_8541_b367, + 0xc2b6_ed0e_f215_8547, + 0x1192_2a09_7360_edf3, + ]), + }, + y: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x4c73_0af8_6049_4c4a, + 0x597c_fa1f_5e36_9c5a, + 0xe7e6_856c_aa0a_635a, + 0xbbef_b5e9_6e0d_495f, + 0x07d3_a975_f0ef_25a2, + 0x0083_fd8e_7e80_dae5, + ]), + c1: Fp::from_raw_unchecked([ + 0xadc0_fc92_df64_b05d, + 0x18aa_270a_2b14_61dc, + 0x86ad_ac6a_3be4_eba0, + 0x7949_5c4e_c93d_a33a, + 0xe717_5850_a43c_caed, + 0x0b2b_c2a1_63de_1bf2, + ]), + }, + infinity: Choice::from(0u8), + } + } + + /// Serializes this element into compressed form. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn to_compressed(&self) -> [u8; 96] { + // Strictly speaking, self.x is zero already when self.infinity is true, but + // to guard against implementation mistakes we do not assume this. + let x = Fp2::conditional_select(&self.x, &Fp2::zero(), self.infinity); + + let mut res = [0; 96]; + + (&mut res[0..48]).copy_from_slice(&x.c1.to_bytes()[..]); + (&mut res[48..96]).copy_from_slice(&x.c0.to_bytes()[..]); + + // This point is in compressed form, so we set the most significant bit. + res[0] |= 1u8 << 7; + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity); + + // Is the y-coordinate the lexicographically largest of the two associated with the + // x-coordinate? If so, set the third-most significant bit so long as this is not + // the point at infinity. + res[0] |= u8::conditional_select( + &0u8, + &(1u8 << 5), + (!self.infinity) & self.y.lexicographically_largest(), + ); + + res + } + + /// Serializes this element into uncompressed form. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn to_uncompressed(&self) -> [u8; 192] { + let mut res = [0; 192]; + + let x = Fp2::conditional_select(&self.x, &Fp2::zero(), self.infinity); + let y = Fp2::conditional_select(&self.y, &Fp2::zero(), self.infinity); + + res[0..48].copy_from_slice(&x.c1.to_bytes()[..]); + res[48..96].copy_from_slice(&x.c0.to_bytes()[..]); + res[96..144].copy_from_slice(&y.c1.to_bytes()[..]); + res[144..192].copy_from_slice(&y.c0.to_bytes()[..]); + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity); + + res + } + + /// Attempts to deserialize an uncompressed element. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn from_uncompressed(bytes: &[u8; 192]) -> CtOption { + Self::from_uncompressed_unchecked(bytes) + .and_then(|p| CtOption::new(p, p.is_on_curve() & p.is_torsion_free())) + } + + /// Attempts to deserialize an uncompressed element, not checking if the + /// element is on the curve and not checking if it is in the correct subgroup. + /// **This is dangerous to call unless you trust the bytes you are reading; otherwise, + /// API invariants may be broken.** Please consider using `from_uncompressed()` instead. + pub fn from_uncompressed_unchecked(bytes: &[u8; 192]) -> CtOption { + // Obtain the three flags from the start of the byte sequence + let compression_flag_set = Choice::from((bytes[0] >> 7) & 1); + let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1); + let sort_flag_set = Choice::from((bytes[0] >> 5) & 1); + + // Attempt to obtain the x-coordinate + let xc1 = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[0..48]); + + // Mask away the flag bits + tmp[0] &= 0b0001_1111; + + Fp::from_bytes(&tmp) + }; + let xc0 = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[48..96]); + + Fp::from_bytes(&tmp) + }; + + // Attempt to obtain the y-coordinate + let yc1 = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[96..144]); + + Fp::from_bytes(&tmp) + }; + let yc0 = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[144..192]); + + Fp::from_bytes(&tmp) + }; + + xc1.and_then(|xc1| { + xc0.and_then(|xc0| { + yc1.and_then(|yc1| { + yc0.and_then(|yc0| { + let x = Fp2 { + c0: xc0, + c1: xc1 + }; + let y = Fp2 { + c0: yc0, + c1: yc1 + }; + + // Create a point representing this value + let p = G2Affine::conditional_select( + &G2Affine { + x, + y, + infinity: infinity_flag_set, + }, + &G2Affine::identity(), + infinity_flag_set, + ); + + CtOption::new( + p, + // If the infinity flag is set, the x and y coordinates should have been zero. + ((!infinity_flag_set) | (infinity_flag_set & x.is_zero() & y.is_zero())) & + // The compression flag should not have been set, as this is an uncompressed element + (!compression_flag_set) & + // The sort flag should not have been set, as this is an uncompressed element + (!sort_flag_set), + ) + }) + }) + }) + }) + } + + /// Attempts to deserialize a compressed element. See [`notes::serialization`](crate::notes::serialization) + /// for details about how group elements are serialized. + pub fn from_compressed(bytes: &[u8; 96]) -> CtOption { + // We already know the point is on the curve because this is established + // by the y-coordinate recovery procedure in from_compressed_unchecked(). + + Self::from_compressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_torsion_free())) + } + + /// Attempts to deserialize an uncompressed element, not checking if the + /// element is in the correct subgroup. + /// **This is dangerous to call unless you trust the bytes you are reading; otherwise, + /// API invariants may be broken.** Please consider using `from_compressed()` instead. + pub fn from_compressed_unchecked(bytes: &[u8; 96]) -> CtOption { + // Obtain the three flags from the start of the byte sequence + let compression_flag_set = Choice::from((bytes[0] >> 7) & 1); + let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1); + let sort_flag_set = Choice::from((bytes[0] >> 5) & 1); + + // Attempt to obtain the x-coordinate + let xc1 = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[0..48]); + + // Mask away the flag bits + tmp[0] &= 0b0001_1111; + + Fp::from_bytes(&tmp) + }; + let xc0 = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[48..96]); + + Fp::from_bytes(&tmp) + }; + + xc1.and_then(|xc1| { + xc0.and_then(|xc0| { + let x = Fp2 { c0: xc0, c1: xc1 }; + + // If the infinity flag is set, return the value assuming + // the x-coordinate is zero and the sort bit is not set. + // + // Otherwise, return a recovered point (assuming the correct + // y-coordinate can be found) so long as the infinity flag + // was not set. + CtOption::new( + G2Affine::identity(), + infinity_flag_set & // Infinity flag should be set + compression_flag_set & // Compression flag should be set + (!sort_flag_set) & // Sort flag should not be set + x.is_zero(), // The x-coordinate should be zero + ) + .or_else(|| { + // Recover a y-coordinate given x by y = sqrt(x^3 + 4) + ((x.square() * x) + B).sqrt().and_then(|y| { + // Switch to the correct y-coordinate if necessary. + let y = Fp2::conditional_select( + &y, + &-y, + y.lexicographically_largest() ^ sort_flag_set, + ); + + CtOption::new( + G2Affine { + x, + y, + infinity: infinity_flag_set, + }, + (!infinity_flag_set) & // Infinity flag should not be set + compression_flag_set, // Compression flag should be set + ) + }) + }) + }) + }) + } + + /// Returns true if this element is the identity (the point at infinity). + #[inline] + pub fn is_identity(&self) -> Choice { + self.infinity + } + + /// Returns true if this point is free of an $h$-torsion component, and so it + /// exists within the $q$-order subgroup $\mathbb{G}_2$. This should always return true + /// unless an "unchecked" API was used. + pub fn is_torsion_free(&self) -> Choice { + const FQ_MODULUS_BYTES: [u8; 32] = [ + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115, + ]; + + // Clear the r-torsion from the point and check if it is the identity + G2Projective::from(*self) + .multiply(&FQ_MODULUS_BYTES) + .is_identity() + } + + /// Returns true if this point is on the curve. This should always return + /// true unless an "unchecked" API was used. + pub fn is_on_curve(&self) -> Choice { + // y^2 - x^3 ?= 4(u + 1) + (self.y.square() - (self.x.square() * self.x)).ct_eq(&B) | self.infinity + } +} + +/// This is an element of $\mathbb{G}_2$ represented in the projective coordinate space. +#[derive(Copy, Clone, Debug)] +pub struct G2Projective { + pub(crate) x: Fp2, + pub(crate) y: Fp2, + pub(crate) z: Fp2, +} + +impl<'a> From<&'a G2Affine> for G2Projective { + fn from(p: &'a G2Affine) -> G2Projective { + G2Projective { + x: p.x, + y: p.y, + z: Fp2::conditional_select(&Fp2::one(), &Fp2::zero(), p.infinity), + } + } +} + +impl From for G2Projective { + fn from(p: G2Affine) -> G2Projective { + G2Projective::from(&p) + } +} + +impl ConstantTimeEq for G2Projective { + fn ct_eq(&self, other: &Self) -> Choice { + // Is (xz^2, yz^3, z) equal to (x'z'^2, yz'^3, z') when converted to affine? + + let z = other.z.square(); + let x1 = self.x * z; + let z = z * other.z; + let y1 = self.y * z; + let z = self.z.square(); + let x2 = other.x * z; + let z = z * self.z; + let y2 = other.y * z; + + let self_is_zero = self.z.is_zero(); + let other_is_zero = other.z.is_zero(); + + (self_is_zero & other_is_zero) // Both point at infinity + | ((!self_is_zero) & (!other_is_zero) & x1.ct_eq(&x2) & y1.ct_eq(&y2)) + // Neither point at infinity, coordinates are the same + } +} + +impl ConditionallySelectable for G2Projective { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + G2Projective { + x: Fp2::conditional_select(&a.x, &b.x, choice), + y: Fp2::conditional_select(&a.y, &b.y, choice), + z: Fp2::conditional_select(&a.z, &b.z, choice), + } + } +} + +impl Eq for G2Projective {} +impl PartialEq for G2Projective { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +impl<'a> Neg for &'a G2Projective { + type Output = G2Projective; + + #[inline] + fn neg(self) -> G2Projective { + G2Projective { + x: self.x, + y: -self.y, + z: self.z, + } + } +} + +impl Neg for G2Projective { + type Output = G2Projective; + + #[inline] + fn neg(self) -> G2Projective { + -&self + } +} + +impl<'a, 'b> Add<&'b G2Projective> for &'a G2Projective { + type Output = G2Projective; + + #[inline] + fn add(self, rhs: &'b G2Projective) -> G2Projective { + self.add(rhs) + } +} + +impl<'a, 'b> Sub<&'b G2Projective> for &'a G2Projective { + type Output = G2Projective; + + #[inline] + fn sub(self, rhs: &'b G2Projective) -> G2Projective { + self + (-rhs) + } +} + +impl<'a, 'b> Mul<&'b Scalar> for &'a G2Projective { + type Output = G2Projective; + + fn mul(self, other: &'b Scalar) -> Self::Output { + self.multiply(&other.to_bytes()) + } +} + +impl<'a, 'b> Mul<&'b Scalar> for &'a G2Affine { + type Output = G2Projective; + + fn mul(self, other: &'b Scalar) -> Self::Output { + G2Projective::from(self).multiply(&other.to_bytes()) + } +} + +impl_binops_additive!(G2Projective, G2Projective); +impl_binops_multiplicative!(G2Projective, Scalar); +impl_binops_multiplicative_mixed!(G2Affine, Scalar, G2Projective); + +impl G2Projective { + /// Returns the identity of the group: the point at infinity. + pub fn identity() -> G2Projective { + G2Projective { + x: Fp2::zero(), + y: Fp2::one(), + z: Fp2::zero(), + } + } + + /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators) + /// for how this generator is chosen. + pub fn generator() -> G2Projective { + G2Projective { + x: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xf5f2_8fa2_0294_0a10, + 0xb3f5_fb26_87b4_961a, + 0xa1a8_93b5_3e2a_e580, + 0x9894_999d_1a3c_aee9, + 0x6f67_b763_1863_366b, + 0x0581_9192_4350_bcd7, + ]), + c1: Fp::from_raw_unchecked([ + 0xa5a9_c075_9e23_f606, + 0xaaa0_c59d_bccd_60c3, + 0x3bb1_7e18_e286_7806, + 0x1b1a_b6cc_8541_b367, + 0xc2b6_ed0e_f215_8547, + 0x1192_2a09_7360_edf3, + ]), + }, + y: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x4c73_0af8_6049_4c4a, + 0x597c_fa1f_5e36_9c5a, + 0xe7e6_856c_aa0a_635a, + 0xbbef_b5e9_6e0d_495f, + 0x07d3_a975_f0ef_25a2, + 0x0083_fd8e_7e80_dae5, + ]), + c1: Fp::from_raw_unchecked([ + 0xadc0_fc92_df64_b05d, + 0x18aa_270a_2b14_61dc, + 0x86ad_ac6a_3be4_eba0, + 0x7949_5c4e_c93d_a33a, + 0xe717_5850_a43c_caed, + 0x0b2b_c2a1_63de_1bf2, + ]), + }, + z: Fp2::one(), + } + } + + /// Computes the doubling of this point. + pub fn double(&self) -> G2Projective { + // http://www.hyperelliptic.org/EFD/g2p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + // + // There are no points of order 2. + + let a = self.x.square(); + let b = self.y.square(); + let c = b.square(); + let d = self.x + b; + let d = d.square(); + let d = d - a - c; + let d = d + d; + let e = a + a + a; + let f = e.square(); + let z3 = self.z * self.y; + let z3 = z3 + z3; + let x3 = f - (d + d); + let c = c + c; + let c = c + c; + let c = c + c; + let y3 = e * (d - x3) - c; + + let tmp = G2Projective { + x: x3, + y: y3, + z: z3, + }; + + G2Projective::conditional_select(&tmp, &G2Projective::identity(), self.is_identity()) + } + + /// Adds this point to another point. + pub fn add(&self, rhs: &G2Projective) -> G2Projective { + // This Jacobian point addition technique is based on the implementation in libsecp256k1, + // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally. + + // If self is the identity, return rhs. Otherwise, return self. The other cases will be + // predicated on neither self nor rhs being the identity. + let f1 = self.is_identity(); + let res = G2Projective::conditional_select(self, rhs, f1); + let f2 = rhs.is_identity(); + + // If neither are the identity but x1 = x2 and y1 != y2, then return the identity + let z = rhs.z.square(); + let u1 = self.x * z; + let z = z * rhs.z; + let s1 = self.y * z; + let z = self.z.square(); + let u2 = rhs.x * z; + let z = z * self.z; + let s2 = rhs.y * z; + let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2)); + let res = + G2Projective::conditional_select(&res, &G2Projective::identity(), (!f1) & (!f2) & f3); + + let t = u1 + u2; + let m = s1 + s2; + let rr = t.square(); + let m_alt = -u2; + let tt = u1 * m_alt; + let rr = rr + tt; + + // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3. + // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda. + let degenerate = m.is_zero() & rr.is_zero(); + let rr_alt = s1 + s1; + let m_alt = m_alt + u1; + let rr_alt = Fp2::conditional_select(&rr_alt, &rr, !degenerate); + let m_alt = Fp2::conditional_select(&m_alt, &m, !degenerate); + + let n = m_alt.square(); + let q = n * t; + + let n = n.square(); + let n = Fp2::conditional_select(&n, &m, degenerate); + let t = rr_alt.square(); + let z3 = m_alt * self.z * rhs.z; // We allow rhs.z != 1, so we must account for this. + let z3 = z3 + z3; + let q = -q; + let t = t + q; + let x3 = t; + let t = t + t; + let t = t + q; + let t = t * rr_alt; + let t = t + n; + let y3 = -t; + let x3 = x3 + x3; + let x3 = x3 + x3; + let y3 = y3 + y3; + let y3 = y3 + y3; + + let tmp = G2Projective { + x: x3, + y: y3, + z: z3, + }; + + G2Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3)) + } + + /// Adds this point to another point in the affine model. + pub fn add_mixed(&self, rhs: &G2Affine) -> G2Projective { + // This Jacobian point addition technique is based on the implementation in libsecp256k1, + // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally. + + // If self is the identity, return rhs. Otherwise, return self. The other cases will be + // predicated on neither self nor rhs being the identity. + let f1 = self.is_identity(); + let res = G2Projective::conditional_select(self, &G2Projective::from(rhs), f1); + let f2 = rhs.is_identity(); + + // If neither are the identity but x1 = x2 and y1 != y2, then return the identity + let u1 = self.x; + let s1 = self.y; + let z = self.z.square(); + let u2 = rhs.x * z; + let z = z * self.z; + let s2 = rhs.y * z; + let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2)); + let res = + G2Projective::conditional_select(&res, &G2Projective::identity(), (!f1) & (!f2) & f3); + + let t = u1 + u2; + let m = s1 + s2; + let rr = t.square(); + let m_alt = -u2; + let tt = u1 * m_alt; + let rr = rr + tt; + + // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3. + // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda. + let degenerate = m.is_zero() & rr.is_zero(); + let rr_alt = s1 + s1; + let m_alt = m_alt + u1; + let rr_alt = Fp2::conditional_select(&rr_alt, &rr, !degenerate); + let m_alt = Fp2::conditional_select(&m_alt, &m, !degenerate); + + let n = m_alt.square(); + let q = n * t; + + let n = n.square(); + let n = Fp2::conditional_select(&n, &m, degenerate); + let t = rr_alt.square(); + let z3 = m_alt * self.z; + let z3 = z3 + z3; + let q = -q; + let t = t + q; + let x3 = t; + let t = t + t; + let t = t + q; + let t = t * rr_alt; + let t = t + n; + let y3 = -t; + let x3 = x3 + x3; + let x3 = x3 + x3; + let y3 = y3 + y3; + let y3 = y3 + y3; + + let tmp = G2Projective { + x: x3, + y: y3, + z: z3, + }; + + G2Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3)) + } + + fn multiply(&self, by: &[u8; 32]) -> G2Projective { + let mut acc = G2Projective::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading bit because it's always unset for Fq + // elements. + for bit in by + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(1) + { + acc = acc.double(); + acc = G2Projective::conditional_select(&acc, &(acc + self), bit); + } + + acc + } + + /// Converts a batch of `G2Projective` elements into `G2Affine` elements. This + /// function will panic if `p.len() != q.len()`. + pub fn batch_normalize(p: &[Self], q: &mut [G2Affine]) { + assert_eq!(p.len(), q.len()); + + let mut acc = Fp2::one(); + for (p, q) in p.iter().zip(q.iter_mut()) { + // We use the `x` field of `G2Affine` to store the product + // of previous z-coordinates seen. + q.x = acc; + + // We will end up skipping all identities in p + acc = Fp2::conditional_select(&(acc * p.z), &acc, p.is_identity()); + } + + // This is the inverse, as all z-coordinates are nonzero and the ones + // that are not are skipped. + acc = acc.invert().unwrap(); + + for (p, q) in p.iter().rev().zip(q.iter_mut().rev()) { + let skip = p.is_identity(); + + // Compute tmp = 1/z + let tmp = q.x * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc = Fp2::conditional_select(&(acc * p.z), &acc, skip); + + // Set the coordinates to the correct value + let tmp2 = tmp.square(); + let tmp3 = tmp2 * tmp; + + q.x = p.x * tmp2; + q.y = p.y * tmp3; + q.infinity = Choice::from(0u8); + + *q = G2Affine::conditional_select(&q, &G2Affine::identity(), skip); + } + } + + /// Returns true if this element is the identity (the point at infinity). + #[inline] + pub fn is_identity(&self) -> Choice { + self.z.is_zero() + } + + /// Returns true if this point is on the curve. This should always return + /// true unless an "unchecked" API was used. + pub fn is_on_curve(&self) -> Choice { + // Y^2 - X^3 = 4(u + 1)(Z^6) + + (self.y.square() - (self.x.square() * self.x)) + .ct_eq(&((self.z.square() * self.z).square() * B)) + | self.z.is_zero() + } +} + +#[test] +fn test_is_on_curve() { + assert!(bool::from(G2Affine::identity().is_on_curve())); + assert!(bool::from(G2Affine::generator().is_on_curve())); + assert!(bool::from(G2Projective::identity().is_on_curve())); + assert!(bool::from(G2Projective::generator().is_on_curve())); + + let z = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]), + c1: Fp::from_raw_unchecked([ + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844, + ]), + }; + + let gen = G2Affine::generator(); + let mut test = G2Projective { + x: gen.x * (z.square()), + y: gen.y * (z.square() * z), + z, + }; + + assert!(bool::from(test.is_on_curve())); + + test.x = z; + assert!(!bool::from(test.is_on_curve())); +} + +#[test] +#[allow(clippy::eq_op)] +fn test_affine_point_equality() { + let a = G2Affine::generator(); + let b = G2Affine::identity(); + + assert!(a == a); + assert!(b == b); + assert!(a != b); + assert!(b != a); +} + +#[test] +#[allow(clippy::eq_op)] +fn test_projective_point_equality() { + let a = G2Projective::generator(); + let b = G2Projective::identity(); + + assert!(a == a); + assert!(b == b); + assert!(a != b); + assert!(b != a); + + let z = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]), + c1: Fp::from_raw_unchecked([ + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844, + ]), + }; + + let mut c = G2Projective { + x: a.x * (z.square()), + y: a.y * (z.square() * z), + z, + }; + assert!(bool::from(c.is_on_curve())); + + assert!(a == c); + assert!(b != c); + assert!(c == a); + assert!(c != b); + + c.y = -c.y; + assert!(bool::from(c.is_on_curve())); + + assert!(a != c); + assert!(b != c); + assert!(c != a); + assert!(c != b); + + c.y = -c.y; + c.x = z; + assert!(!bool::from(c.is_on_curve())); + assert!(a != b); + assert!(a != c); + assert!(b != c); +} + +#[test] +fn test_conditionally_select_affine() { + let a = G2Affine::generator(); + let b = G2Affine::identity(); + + assert_eq!(G2Affine::conditional_select(&a, &b, Choice::from(0u8)), a); + assert_eq!(G2Affine::conditional_select(&a, &b, Choice::from(1u8)), b); +} + +#[test] +fn test_conditionally_select_projective() { + let a = G2Projective::generator(); + let b = G2Projective::identity(); + + assert_eq!( + G2Projective::conditional_select(&a, &b, Choice::from(0u8)), + a + ); + assert_eq!( + G2Projective::conditional_select(&a, &b, Choice::from(1u8)), + b + ); +} + +#[test] +fn test_projective_to_affine() { + let a = G2Projective::generator(); + let b = G2Projective::identity(); + + assert!(bool::from(G2Affine::from(a).is_on_curve())); + assert!(!bool::from(G2Affine::from(a).is_identity())); + assert!(bool::from(G2Affine::from(b).is_on_curve())); + assert!(bool::from(G2Affine::from(b).is_identity())); + + let z = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]), + c1: Fp::from_raw_unchecked([ + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844, + ]), + }; + + let c = G2Projective { + x: a.x * (z.square()), + y: a.y * (z.square() * z), + z, + }; + + assert_eq!(G2Affine::from(c), G2Affine::generator()); +} + +#[test] +fn test_affine_to_projective() { + let a = G2Affine::generator(); + let b = G2Affine::identity(); + + assert!(bool::from(G2Projective::from(a).is_on_curve())); + assert!(!bool::from(G2Projective::from(a).is_identity())); + assert!(bool::from(G2Projective::from(b).is_on_curve())); + assert!(bool::from(G2Projective::from(b).is_identity())); +} + +#[test] +fn test_doubling() { + { + let tmp = G2Projective::identity().double(); + assert!(bool::from(tmp.is_identity())); + assert!(bool::from(tmp.is_on_curve())); + } + { + let tmp = G2Projective::generator().double(); + assert!(!bool::from(tmp.is_identity())); + assert!(bool::from(tmp.is_on_curve())); + + assert_eq!( + G2Affine::from(tmp), + G2Affine { + x: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xe9d9_e2da_9620_f98b, + 0x54f1_1993_46b9_7f36, + 0x3db3_b820_376b_ed27, + 0xcfdb_31c9_b0b6_4f4c, + 0x41d7_c127_8635_4493, + 0x0571_0794_c255_c064, + ]), + c1: Fp::from_raw_unchecked([ + 0xd6c1_d3ca_6ea0_d06e, + 0xda0c_bd90_5595_489f, + 0x4f53_52d4_3479_221d, + 0x8ade_5d73_6f8c_97e0, + 0x48cc_8433_925e_f70e, + 0x08d7_ea71_ea91_ef81, + ]), + }, + y: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x15ba_26eb_4b0d_186f, + 0x0d08_6d64_b7e9_e01e, + 0xc8b8_48dd_652f_4c78, + 0xeecf_46a6_123b_ae4f, + 0x255e_8dd8_b6dc_812a, + 0x1641_42af_21dc_f93f, + ]), + c1: Fp::from_raw_unchecked([ + 0xf9b4_a1a8_9598_4db4, + 0xd417_b114_cccf_f748, + 0x6856_301f_c89f_086e, + 0x41c7_7787_8931_e3da, + 0x3556_b155_066a_2105, + 0x00ac_f7d3_25cb_89cf, + ]), + }, + infinity: Choice::from(0u8) + } + ); + } +} + +#[test] +fn test_projective_addition() { + { + let a = G2Projective::identity(); + let b = G2Projective::identity(); + let c = a + b; + assert!(bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } + { + let a = G2Projective::identity(); + let mut b = G2Projective::generator(); + { + let z = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]), + c1: Fp::from_raw_unchecked([ + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844, + ]), + }; + + b = G2Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = a + b; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G2Projective::generator()); + } + { + let a = G2Projective::identity(); + let mut b = G2Projective::generator(); + { + let z = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]), + c1: Fp::from_raw_unchecked([ + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844, + ]), + }; + + b = G2Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = b + a; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G2Projective::generator()); + } + { + let a = G2Projective::generator().double().double(); // 4P + let b = G2Projective::generator().double(); // 2P + let c = a + b; + + let mut d = G2Projective::generator(); + for _ in 0..5 { + d += G2Projective::generator(); + } + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(!bool::from(d.is_identity())); + assert!(bool::from(d.is_on_curve())); + assert_eq!(c, d); + } + + // Degenerate case + { + let beta = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741, + ]), + c1: Fp::zero(), + }; + let beta = beta.square(); + let a = G2Projective::generator().double().double(); + let b = G2Projective { + x: a.x * beta, + y: -a.y, + z: a.z, + }; + assert!(bool::from(a.is_on_curve())); + assert!(bool::from(b.is_on_curve())); + + let c = a + b; + assert_eq!( + G2Affine::from(c), + G2Affine::from(G2Projective { + x: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x705a_bc79_9ca7_73d3, + 0xfe13_2292_c1d4_bf08, + 0xf37e_ce3e_07b2_b466, + 0x887e_1c43_f447_e301, + 0x1e09_70d0_33bc_77e8, + 0x1985_c81e_20a6_93f2, + ]), + c1: Fp::from_raw_unchecked([ + 0x1d79_b25d_b36a_b924, + 0x2394_8e4d_5296_39d3, + 0x471b_a7fb_0d00_6297, + 0x2c36_d4b4_465d_c4c0, + 0x82bb_c3cf_ec67_f538, + 0x051d_2728_b67b_f952, + ]) + }, + y: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x41b1_bbf6_576c_0abf, + 0xb6cc_9371_3f7a_0f9a, + 0x6b65_b43e_48f3_f01f, + 0xfb7a_4cfc_af81_be4f, + 0x3e32_dadc_6ec2_2cb6, + 0x0bb0_fc49_d798_07e3, + ]), + c1: Fp::from_raw_unchecked([ + 0x7d13_9778_8f5f_2ddf, + 0xab29_0714_4ff0_d8e8, + 0x5b75_73e0_cdb9_1f92, + 0x4cb8_932d_d31d_af28, + 0x62bb_fac6_db05_2a54, + 0x11f9_5c16_d14c_3bbe, + ]) + }, + z: Fp2::one() + }) + ); + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } +} + +#[test] +fn test_mixed_addition() { + { + let a = G2Affine::identity(); + let b = G2Projective::identity(); + let c = a + b; + assert!(bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } + { + let a = G2Affine::identity(); + let mut b = G2Projective::generator(); + { + let z = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]), + c1: Fp::from_raw_unchecked([ + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844, + ]), + }; + + b = G2Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = a + b; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G2Projective::generator()); + } + { + let a = G2Affine::identity(); + let mut b = G2Projective::generator(); + { + let z = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e, + ]), + c1: Fp::from_raw_unchecked([ + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844, + ]), + }; + + b = G2Projective { + x: b.x * (z.square()), + y: b.y * (z.square() * z), + z, + }; + } + let c = b + a; + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(c == G2Projective::generator()); + } + { + let a = G2Projective::generator().double().double(); // 4P + let b = G2Projective::generator().double(); // 2P + let c = a + b; + + let mut d = G2Projective::generator(); + for _ in 0..5 { + d += G2Affine::generator(); + } + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + assert!(!bool::from(d.is_identity())); + assert!(bool::from(d.is_on_curve())); + assert_eq!(c, d); + } + + // Degenerate case + { + let beta = Fp2 { + c0: Fp::from_raw_unchecked([ + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741, + ]), + c1: Fp::zero(), + }; + let beta = beta.square(); + let a = G2Projective::generator().double().double(); + let b = G2Projective { + x: a.x * beta, + y: -a.y, + z: a.z, + }; + let a = G2Affine::from(a); + assert!(bool::from(a.is_on_curve())); + assert!(bool::from(b.is_on_curve())); + + let c = a + b; + assert_eq!( + G2Affine::from(c), + G2Affine::from(G2Projective { + x: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x705a_bc79_9ca7_73d3, + 0xfe13_2292_c1d4_bf08, + 0xf37e_ce3e_07b2_b466, + 0x887e_1c43_f447_e301, + 0x1e09_70d0_33bc_77e8, + 0x1985_c81e_20a6_93f2, + ]), + c1: Fp::from_raw_unchecked([ + 0x1d79_b25d_b36a_b924, + 0x2394_8e4d_5296_39d3, + 0x471b_a7fb_0d00_6297, + 0x2c36_d4b4_465d_c4c0, + 0x82bb_c3cf_ec67_f538, + 0x051d_2728_b67b_f952, + ]) + }, + y: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x41b1_bbf6_576c_0abf, + 0xb6cc_9371_3f7a_0f9a, + 0x6b65_b43e_48f3_f01f, + 0xfb7a_4cfc_af81_be4f, + 0x3e32_dadc_6ec2_2cb6, + 0x0bb0_fc49_d798_07e3, + ]), + c1: Fp::from_raw_unchecked([ + 0x7d13_9778_8f5f_2ddf, + 0xab29_0714_4ff0_d8e8, + 0x5b75_73e0_cdb9_1f92, + 0x4cb8_932d_d31d_af28, + 0x62bb_fac6_db05_2a54, + 0x11f9_5c16_d14c_3bbe, + ]) + }, + z: Fp2::one() + }) + ); + assert!(!bool::from(c.is_identity())); + assert!(bool::from(c.is_on_curve())); + } +} + +#[test] +#[allow(clippy::eq_op)] +fn test_projective_negation_and_subtraction() { + let a = G2Projective::generator().double(); + assert_eq!(a + (-a), G2Projective::identity()); + assert_eq!(a + (-a), a - a); +} + +#[test] +fn test_affine_negation_and_subtraction() { + let a = G2Affine::generator(); + assert_eq!(G2Projective::from(a) + (-a), G2Projective::identity()); + assert_eq!(G2Projective::from(a) + (-a), G2Projective::from(a) - a); +} + +#[test] +fn test_projective_scalar_multiplication() { + let g = G2Projective::generator(); + let a = Scalar::from_raw([ + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2, + ]); + let b = Scalar::from_raw([ + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20, + ]); + let c = a * b; + + assert_eq!((g * a) * b, g * c); +} + +#[test] +fn test_affine_scalar_multiplication() { + let g = G2Affine::generator(); + let a = Scalar::from_raw([ + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2, + ]); + let b = Scalar::from_raw([ + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20, + ]); + let c = a * b; + + assert_eq!(G2Affine::from(g * a) * b, g * c); +} + +#[test] +fn test_is_torsion_free() { + let a = G2Affine { + x: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x89f5_50c8_13db_6431, + 0xa50b_e8c4_56cd_8a1a, + 0xa45b_3741_14ca_e851, + 0xbb61_90f5_bf7f_ff63, + 0x970c_a02c_3ba8_0bc7, + 0x02b8_5d24_e840_fbac, + ]), + c1: Fp::from_raw_unchecked([ + 0x6888_bc53_d707_16dc, + 0x3dea_6b41_1768_2d70, + 0xd8f5_f930_500c_a354, + 0x6b5e_cb65_56f5_c155, + 0xc96b_ef04_3477_8ab0, + 0x0508_1505_5150_06ad, + ]), + }, + y: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x3cf1_ea0d_434b_0f40, + 0x1a0d_c610_e603_e333, + 0x7f89_9561_60c7_2fa0, + 0x25ee_03de_cf64_31c5, + 0xeee8_e206_ec0f_e137, + 0x0975_92b2_26df_ef28, + ]), + c1: Fp::from_raw_unchecked([ + 0x71e8_bb5f_2924_7367, + 0xa5fe_049e_2118_31ce, + 0x0ce6_b354_502a_3896, + 0x93b0_1200_0997_314e, + 0x6759_f3b6_aa5b_42ac, + 0x1569_44c4_dfe9_2bbb, + ]), + }, + infinity: Choice::from(0u8), + }; + assert!(!bool::from(a.is_torsion_free())); + + assert!(bool::from(G2Affine::identity().is_torsion_free())); + assert!(bool::from(G2Affine::generator().is_torsion_free())); +} + +#[test] +fn test_batch_normalize() { + let a = G2Projective::generator().double(); + let b = a.double(); + let c = b.double(); + + for a_identity in (0..1).map(|n| n == 1) { + for b_identity in (0..1).map(|n| n == 1) { + for c_identity in (0..1).map(|n| n == 1) { + let mut v = [a, b, c]; + if a_identity { + v[0] = G2Projective::identity() + } + if b_identity { + v[1] = G2Projective::identity() + } + if c_identity { + v[2] = G2Projective::identity() + } + + let mut t = [ + G2Affine::identity(), + G2Affine::identity(), + G2Affine::identity(), + ]; + let expected = [ + G2Affine::from(v[0]), + G2Affine::from(v[1]), + G2Affine::from(v[2]), + ]; + + G2Projective::batch_normalize(&v[..], &mut t[..]); + + assert_eq!(&t[..], &expected[..]); + } + } + } +} diff --git a/bls12_381/src/lib.rs b/bls12_381/src/lib.rs new file mode 100644 index 0000000..d5b4d51 --- /dev/null +++ b/bls12_381/src/lib.rs @@ -0,0 +1,80 @@ +//! # `bls12_381` +//! +//! This crate provides an implementation of the BLS12-381 pairing-friendly elliptic +//! curve construction. +//! +//! * **This implementation has not been reviewed or audited. Use at your own risk.** +//! * This implementation targets Rust `1.36` or later. +//! * This implementation does not require the Rust standard library. +//! * All operations are constant time unless explicitly noted. + +#![no_std] +// Catch documentation errors caused by code changes. +#![deny(intra_doc_link_resolution_failure)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![deny(unsafe_code)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::many_single_char_names)] +// This lint is described at +// https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl +// In our library, some of the arithmetic involving extension fields will necessarily +// involve various binary operators, and so this lint is triggered unnecessarily. +#![allow(clippy::suspicious_arithmetic_impl)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(test)] +#[macro_use] +extern crate std; + +#[cfg(test)] +#[cfg(feature = "groups")] +mod tests; + +#[macro_use] +mod util; + +/// Notes about how the BLS12-381 elliptic curve is designed, specified +/// and implemented by this library. +pub mod notes { + pub mod design; + pub mod serialization; +} + +mod scalar; + +pub use scalar::Scalar; + +#[cfg(feature = "groups")] +mod fp; +#[cfg(feature = "groups")] +mod fp2; +#[cfg(feature = "groups")] +mod g1; +#[cfg(feature = "groups")] +mod g2; + +#[cfg(feature = "groups")] +pub use g1::{G1Affine, G1Projective}; +#[cfg(feature = "groups")] +pub use g2::{G2Affine, G2Projective}; + +#[cfg(feature = "groups")] +mod fp12; +#[cfg(feature = "groups")] +mod fp6; + +// The BLS parameter x for BLS12-381 is -0xd201000000010000 +const BLS_X: u64 = 0xd201_0000_0001_0000; +const BLS_X_IS_NEGATIVE: bool = true; + +#[cfg(feature = "pairings")] +mod pairings; + +#[cfg(feature = "pairings")] +pub use pairings::{pairing, Gt, MillerLoopResult}; + +#[cfg(all(feature = "pairings", feature = "alloc"))] +pub use pairings::{multi_miller_loop, G2Prepared}; diff --git a/bls12_381/src/notes/design.rs b/bls12_381/src/notes/design.rs new file mode 100644 index 0000000..d245260 --- /dev/null +++ b/bls12_381/src/notes/design.rs @@ -0,0 +1,63 @@ +//! # Design of BLS12-381 +//! ## Fixed Generators +//! +//! Although any generator produced by hashing to $\mathbb{G}_1$ or $\mathbb{G}_2$ is +//! safe to use in a cryptographic protocol, we specify some simple, fixed generators. +//! +//! In order to derive these generators, we select the lexicographically smallest +//! valid $x$-coordinate and the lexicographically smallest corresponding $y$-coordinate, +//! and then scale the resulting point by the cofactor, such that the result is not the +//! identity. This results in the following fixed generators: +//! +//! 1. $\mathbb{G}_1$ +//! * $x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507$ +//! * $y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569$ +//! 2. $\mathbb{G}_2$ +//! * $x = 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 + 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758 u$ +//! * $y = 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 + 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582 u$ +//! +//! This can be derived using the following sage script: +//! +//! ```norun +//! param = -0xd201000000010000 +//! def r(x): +//! return (x**4) - (x**2) + 1 +//! def q(x): +//! return (((x - 1) ** 2) * ((x**4) - (x**2) + 1) // 3) + x +//! def g1_h(x): +//! return ((x-1)**2) // 3 +//! def g2_h(x): +//! return ((x**8) - (4 * (x**7)) + (5 * (x**6)) - (4 * (x**4)) + (6 * (x**3)) - (4 * (x**2)) - (4*x) + 13) // 9 +//! q = q(param) +//! r = r(param) +//! Fq = GF(q) +//! ec = EllipticCurve(Fq, [0, 4]) +//! def psqrt(v): +//! assert(not v.is_zero()) +//! a = sqrt(v) +//! b = -a +//! if a < b: +//! return a +//! else: +//! return b +//! for x in range(0,100): +//! rhs = Fq(x)^3 + 4 +//! if rhs.is_square(): +//! y = psqrt(rhs) +//! p = ec(x, y) * g1_h(param) +//! if (not p.is_zero()) and (p * r).is_zero(): +//! print "g1 generator: %s" % p +//! break +//! Fqx. = PolynomialRing(Fq, 'j') +//! Fq2. = GF(q^2, modulus=j^2 + 1) +//! ec2 = EllipticCurve(Fq2, [0, (4 * (1 + i))]) +//! assert(ec2.order() == (r * g2_h(param))) +//! for x in range(0,100): +//! rhs = (Fq2(x))^3 + (4 * (1 + i)) +//! if rhs.is_square(): +//! y = psqrt(rhs) +//! p = ec2(Fq2(x), y) * g2_h(param) +//! if (not p.is_zero()) and (p * r).is_zero(): +//! print "g2 generator: %s" % p +//! break +//! ``` diff --git a/bls12_381/src/notes/serialization.rs b/bls12_381/src/notes/serialization.rs new file mode 100644 index 0000000..ded752e --- /dev/null +++ b/bls12_381/src/notes/serialization.rs @@ -0,0 +1,29 @@ +//! # BLS12-381 serialization +//! +//! * $\mathbb{F}\_p$ elements are encoded in big-endian form. They occupy 48 +//! bytes in this form. +//! * $\mathbb{F}\_{p^2}$ elements are encoded in big-endian form, meaning that +//! the $\mathbb{F}\_{p^2}$ element $c\_0 + c\_1 \cdot u$ is represented by the +//! $\mathbb{F}\_p$ element $c\_1$ followed by the $\mathbb{F}\_p$ element $c\_0$. +//! This means $\mathbb{F}_{p^2}$ elements occupy 96 bytes in this form. +//! * The group $\mathbb{G}\_1$ uses $\mathbb{F}\_p$ elements for coordinates. The +//! group $\mathbb{G}\_2$ uses $\mathbb{F}_{p^2}$ elements for coordinates. +//! * $\mathbb{G}\_1$ and $\mathbb{G}\_2$ elements can be encoded in uncompressed +//! form (the x-coordinate followed by the y-coordinate) or in compressed form +//! (just the x-coordinate). $\mathbb{G}\_1$ elements occupy 96 bytes in +//! uncompressed form, and 48 bytes in compressed form. $\mathbb{G}\_2$ +//! elements occupy 192 bytes in uncompressed form, and 96 bytes in compressed +//! form. +//! +//! The most-significant three bits of a $\mathbb{G}\_1$ or $\mathbb{G}\_2$ +//! encoding should be masked away before the coordinate(s) are interpreted. +//! These bits are used to unambiguously represent the underlying element: +//! * The most significant bit, when set, indicates that the point is in +//! compressed form. Otherwise, the point is in uncompressed form. +//! * The second-most significant bit indicates that the point is at infinity. +//! If this bit is set, the remaining bits of the group element's encoding +//! should be set to zero. +//! * The third-most significant bit is set if (and only if) this point is in +//! compressed form _and_ it is not the point at infinity _and_ its +//! y-coordinate is the lexicographically largest of the two associated with +//! the encoded x-coordinate. diff --git a/bls12_381/src/pairings.rs b/bls12_381/src/pairings.rs new file mode 100644 index 0000000..ef7180a --- /dev/null +++ b/bls12_381/src/pairings.rs @@ -0,0 +1,648 @@ +use crate::fp12::Fp12; +use crate::fp2::Fp2; +use crate::fp6::Fp6; +use crate::{G1Affine, G2Affine, G2Projective, Scalar, BLS_X, BLS_X_IS_NEGATIVE}; + +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// Represents results of a Miller loop, one of the most expensive portions +/// of the pairing function. `MillerLoopResult`s cannot be compared with each +/// other until `.final_exponentiation()` is called, which is also expensive. +#[derive(Copy, Clone, Debug)] +pub struct MillerLoopResult(pub(crate) Fp12); + +impl ConditionallySelectable for MillerLoopResult { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + MillerLoopResult(Fp12::conditional_select(&a.0, &b.0, choice)) + } +} + +impl MillerLoopResult { + /// This performs a "final exponentiation" routine to convert the result + /// of a Miller loop into an element of `Gt` with help of efficient squaring + /// operation in the so-called `cyclotomic subgroup` of `Fq6` so that + /// it can be compared with other elements of `Gt`. + pub fn final_exponentiation(&self) -> Gt { + #[must_use] + fn fp4_square(a: Fp2, b: Fp2) -> (Fp2, Fp2) { + let t0 = a.square(); + let t1 = b.square(); + let mut t2 = t1.mul_by_nonresidue(); + let c0 = t2 + t0; + t2 = a + b; + t2 = t2.square(); + t2 -= t0; + let c1 = t2 - t1; + + (c0, c1) + } + // Adaptation of Algorithm 5.5.4, Guide to Pairing-Based Cryptography + // Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions + // https://eprint.iacr.org/2009/565.pdf + #[must_use] + fn cyclotomic_square(f: Fp12) -> Fp12 { + let mut z0 = f.c0.c0; + let mut z4 = f.c0.c1; + let mut z3 = f.c0.c2; + let mut z2 = f.c1.c0; + let mut z1 = f.c1.c1; + let mut z5 = f.c1.c2; + + let (t0, t1) = fp4_square(z0, z1); + + // For A + z0 = t0 - z0; + z0 = z0 + z0 + t0; + + z1 = t1 + z1; + z1 = z1 + z1 + t1; + + let (mut t0, t1) = fp4_square(z2, z3); + let (t2, t3) = fp4_square(z4, z5); + + // For C + z4 = t0 - z4; + z4 = z4 + z4 + t0; + + z5 = t1 + z5; + z5 = z5 + z5 + t1; + + // For B + t0 = t3.mul_by_nonresidue(); + z2 = t0 + z2; + z2 = z2 + z2 + t0; + + z3 = t2 - z3; + z3 = z3 + z3 + t2; + + Fp12 { + c0: Fp6 { + c0: z0, + c1: z4, + c2: z3, + }, + c1: Fp6 { + c0: z2, + c1: z1, + c2: z5, + }, + } + } + #[must_use] + fn cycolotomic_exp(f: Fp12) -> Fp12 { + let x = BLS_X; + let mut tmp = Fp12::one(); + let mut found_one = false; + for i in (0..64).rev().map(|b| ((x >> b) & 1) == 1) { + if found_one { + tmp = cyclotomic_square(tmp) + } else { + found_one = i; + } + + if i { + tmp *= f; + } + } + + tmp.conjugate() + } + + let mut f = self.0; + let mut t0 = f + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map(); + Gt(f.invert() + .map(|mut t1| { + let mut t2 = t0 * t1; + t1 = t2; + t2 = t2.frobenius_map().frobenius_map(); + t2 *= t1; + t1 = cyclotomic_square(t2).conjugate(); + let mut t3 = cycolotomic_exp(t2); + let mut t4 = cyclotomic_square(t3); + let mut t5 = t1 * t3; + t1 = cycolotomic_exp(t5); + t0 = cycolotomic_exp(t1); + let mut t6 = cycolotomic_exp(t0); + t6 *= t4; + t4 = cycolotomic_exp(t6); + t5 = t5.conjugate(); + t4 *= t5 * t2; + t5 = t2.conjugate(); + t1 *= t2; + t1 = t1.frobenius_map().frobenius_map().frobenius_map(); + t6 *= t5; + t6 = t6.frobenius_map(); + t3 *= t0; + t3 = t3.frobenius_map().frobenius_map(); + t3 *= t1; + t3 *= t6; + f = t3 * t4; + + f + }) + // We unwrap() because `MillerLoopResult` can only be constructed + // by a function within this crate, and we uphold the invariant + // that the enclosed value is nonzero. + .unwrap()) + } +} + +impl<'a, 'b> Add<&'b MillerLoopResult> for &'a MillerLoopResult { + type Output = MillerLoopResult; + + #[inline] + fn add(self, rhs: &'b MillerLoopResult) -> MillerLoopResult { + MillerLoopResult(self.0 * rhs.0) + } +} + +impl_add_binop_specify_output!(MillerLoopResult, MillerLoopResult, MillerLoopResult); + +/// This is an element of $\mathbb{G}_T$, the target group of the pairing function. As with +/// $\mathbb{G}_1$ and $\mathbb{G}_2$ this group has order $q$. +/// +/// Typically, $\mathbb{G}_T$ is written multiplicatively but we will write it additively to +/// keep code and abstractions consistent. +#[derive(Copy, Clone, Debug)] +pub struct Gt(pub(crate) Fp12); + +impl ConstantTimeEq for Gt { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl ConditionallySelectable for Gt { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Gt(Fp12::conditional_select(&a.0, &b.0, choice)) + } +} + +impl Eq for Gt {} +impl PartialEq for Gt { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +impl Gt { + /// Returns the group identity, which is $1$. + pub fn identity() -> Gt { + Gt(Fp12::one()) + } + + /// Doubles this group element. + pub fn double(&self) -> Gt { + Gt(self.0.square()) + } +} + +impl<'a> Neg for &'a Gt { + type Output = Gt; + + #[inline] + fn neg(self) -> Gt { + // The element is unitary, so we just conjugate. + Gt(self.0.conjugate()) + } +} + +impl Neg for Gt { + type Output = Gt; + + #[inline] + fn neg(self) -> Gt { + -&self + } +} + +impl<'a, 'b> Add<&'b Gt> for &'a Gt { + type Output = Gt; + + #[inline] + fn add(self, rhs: &'b Gt) -> Gt { + Gt(self.0 * rhs.0) + } +} + +impl<'a, 'b> Sub<&'b Gt> for &'a Gt { + type Output = Gt; + + #[inline] + fn sub(self, rhs: &'b Gt) -> Gt { + self + (-rhs) + } +} + +impl<'a, 'b> Mul<&'b Scalar> for &'a Gt { + type Output = Gt; + + fn mul(self, other: &'b Scalar) -> Self::Output { + let mut acc = Gt::identity(); + + // This is a simple double-and-add implementation of group element + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading bit because it's always unset for Fq + // elements. + for bit in other + .to_bytes() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(1) + { + acc = acc.double(); + acc = Gt::conditional_select(&acc, &(acc + self), bit); + } + + acc + } +} + +impl_binops_additive!(Gt, Gt); +impl_binops_multiplicative!(Gt, Scalar); + +#[cfg(feature = "alloc")] +#[derive(Clone, Debug)] +/// This structure contains cached computations pertaining to a $\mathbb{G}_2$ +/// element as part of the pairing function (specifically, the Miller loop) and +/// so should be computed whenever a $\mathbb{G}_2$ element is being used in +/// multiple pairings or is otherwise known in advance. This should be used in +/// conjunction with the [`multi_miller_loop`](crate::multi_miller_loop) +/// function provided by this crate. +/// +/// Requires the `alloc` and `pairing` crate features to be enabled. +pub struct G2Prepared { + infinity: Choice, + coeffs: Vec<(Fp2, Fp2, Fp2)>, +} + +#[cfg(feature = "alloc")] +impl From for G2Prepared { + fn from(q: G2Affine) -> G2Prepared { + struct Adder { + cur: G2Projective, + base: G2Affine, + coeffs: Vec<(Fp2, Fp2, Fp2)>, + } + + impl MillerLoopDriver for Adder { + type Output = (); + + fn doubling_step(&mut self, _: Self::Output) -> Self::Output { + let coeffs = doubling_step(&mut self.cur); + self.coeffs.push(coeffs); + } + fn addition_step(&mut self, _: Self::Output) -> Self::Output { + let coeffs = addition_step(&mut self.cur, &self.base); + self.coeffs.push(coeffs); + } + fn square_output(_: Self::Output) -> Self::Output {} + fn conjugate(_: Self::Output) -> Self::Output {} + fn one() -> Self::Output {} + } + + let is_identity = q.is_identity(); + let q = G2Affine::conditional_select(&q, &G2Affine::generator(), is_identity); + + let mut adder = Adder { + cur: G2Projective::from(q), + base: q, + coeffs: Vec::with_capacity(68), + }; + + miller_loop(&mut adder); + + assert_eq!(adder.coeffs.len(), 68); + + G2Prepared { + infinity: is_identity, + coeffs: adder.coeffs, + } + } +} + +#[cfg(feature = "alloc")] +/// Computes $$\sum_{i=1}^n \textbf{ML}(a_i, b_i)$$ given a series of terms +/// $$(a_1, b_1), (a_2, b_2), ..., (a_n, b_n).$$ +/// +/// Requires the `alloc` and `pairing` crate features to be enabled. +pub fn multi_miller_loop(terms: &[(&G1Affine, &G2Prepared)]) -> MillerLoopResult { + struct Adder<'a, 'b, 'c> { + terms: &'c [(&'a G1Affine, &'b G2Prepared)], + index: usize, + } + + impl<'a, 'b, 'c> MillerLoopDriver for Adder<'a, 'b, 'c> { + type Output = Fp12; + + fn doubling_step(&mut self, mut f: Self::Output) -> Self::Output { + let index = self.index; + for term in self.terms { + let either_identity = term.0.is_identity() | term.1.infinity; + + let new_f = ell(f, &term.1.coeffs[index], term.0); + f = Fp12::conditional_select(&new_f, &f, either_identity); + } + self.index += 1; + + f + } + fn addition_step(&mut self, mut f: Self::Output) -> Self::Output { + let index = self.index; + for term in self.terms { + let either_identity = term.0.is_identity() | term.1.infinity; + + let new_f = ell(f, &term.1.coeffs[index], term.0); + f = Fp12::conditional_select(&new_f, &f, either_identity); + } + self.index += 1; + + f + } + fn square_output(f: Self::Output) -> Self::Output { + f.square() + } + fn conjugate(f: Self::Output) -> Self::Output { + f.conjugate() + } + fn one() -> Self::Output { + Fp12::one() + } + } + + let mut adder = Adder { terms, index: 0 }; + + let tmp = miller_loop(&mut adder); + + MillerLoopResult(tmp) +} + +/// Invoke the pairing function without the use of precomputation and other optimizations. +pub fn pairing(p: &G1Affine, q: &G2Affine) -> Gt { + struct Adder { + cur: G2Projective, + base: G2Affine, + p: G1Affine, + } + + impl MillerLoopDriver for Adder { + type Output = Fp12; + + fn doubling_step(&mut self, f: Self::Output) -> Self::Output { + let coeffs = doubling_step(&mut self.cur); + ell(f, &coeffs, &self.p) + } + fn addition_step(&mut self, f: Self::Output) -> Self::Output { + let coeffs = addition_step(&mut self.cur, &self.base); + ell(f, &coeffs, &self.p) + } + fn square_output(f: Self::Output) -> Self::Output { + f.square() + } + fn conjugate(f: Self::Output) -> Self::Output { + f.conjugate() + } + fn one() -> Self::Output { + Fp12::one() + } + } + + let either_identity = p.is_identity() | q.is_identity(); + let p = G1Affine::conditional_select(&p, &G1Affine::generator(), either_identity); + let q = G2Affine::conditional_select(&q, &G2Affine::generator(), either_identity); + + let mut adder = Adder { + cur: G2Projective::from(q), + base: q, + p, + }; + + let tmp = miller_loop(&mut adder); + let tmp = MillerLoopResult(Fp12::conditional_select( + &tmp, + &Fp12::one(), + either_identity, + )); + tmp.final_exponentiation() +} + +trait MillerLoopDriver { + type Output; + + fn doubling_step(&mut self, f: Self::Output) -> Self::Output; + fn addition_step(&mut self, f: Self::Output) -> Self::Output; + fn square_output(f: Self::Output) -> Self::Output; + fn conjugate(f: Self::Output) -> Self::Output; + fn one() -> Self::Output; +} + +/// This is a "generic" implementation of the Miller loop to avoid duplicating code +/// structure elsewhere; instead, we'll write concrete instantiations of +/// `MillerLoopDriver` for whatever purposes we need (such as caching modes). +fn miller_loop(driver: &mut D) -> D::Output { + let mut f = D::one(); + + let mut found_one = false; + for i in (0..64).rev().map(|b| (((BLS_X >> 1) >> b) & 1) == 1) { + if !found_one { + found_one = i; + continue; + } + + f = driver.doubling_step(f); + + if i { + f = driver.addition_step(f); + } + + f = D::square_output(f); + } + + f = driver.doubling_step(f); + + if BLS_X_IS_NEGATIVE { + f = D::conjugate(f); + } + + f +} + +fn ell(f: Fp12, coeffs: &(Fp2, Fp2, Fp2), p: &G1Affine) -> Fp12 { + let mut c0 = coeffs.0; + let mut c1 = coeffs.1; + + c0.c0 *= p.y; + c0.c1 *= p.y; + + c1.c0 *= p.x; + c1.c1 *= p.x; + + f.mul_by_014(&coeffs.2, &c1, &c0) +} + +fn doubling_step(r: &mut G2Projective) -> (Fp2, Fp2, Fp2) { + // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf + let tmp0 = r.x.square(); + let tmp1 = r.y.square(); + let tmp2 = tmp1.square(); + let tmp3 = (tmp1 + r.x).square() - tmp0 - tmp2; + let tmp3 = tmp3 + tmp3; + let tmp4 = tmp0 + tmp0 + tmp0; + let tmp6 = r.x + tmp4; + let tmp5 = tmp4.square(); + let zsquared = r.z.square(); + r.x = tmp5 - tmp3 - tmp3; + r.z = (r.z + r.y).square() - tmp1 - zsquared; + r.y = (tmp3 - r.x) * tmp4; + let tmp2 = tmp2 + tmp2; + let tmp2 = tmp2 + tmp2; + let tmp2 = tmp2 + tmp2; + r.y -= tmp2; + let tmp3 = tmp4 * zsquared; + let tmp3 = tmp3 + tmp3; + let tmp3 = -tmp3; + let tmp6 = tmp6.square() - tmp0 - tmp5; + let tmp1 = tmp1 + tmp1; + let tmp1 = tmp1 + tmp1; + let tmp6 = tmp6 - tmp1; + let tmp0 = r.z * zsquared; + let tmp0 = tmp0 + tmp0; + + (tmp0, tmp3, tmp6) +} + +fn addition_step(r: &mut G2Projective, q: &G2Affine) -> (Fp2, Fp2, Fp2) { + // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf + let zsquared = r.z.square(); + let ysquared = q.y.square(); + let t0 = zsquared * q.x; + let t1 = ((q.y + r.z).square() - ysquared - zsquared) * zsquared; + let t2 = t0 - r.x; + let t3 = t2.square(); + let t4 = t3 + t3; + let t4 = t4 + t4; + let t5 = t4 * t2; + let t6 = t1 - r.y - r.y; + let t9 = t6 * q.x; + let t7 = t4 * r.x; + r.x = t6.square() - t5 - t7 - t7; + r.z = (r.z + t2).square() - zsquared - t3; + let t10 = q.y + r.z; + let t8 = (t7 - r.x) * t6; + let t0 = r.y * t5; + let t0 = t0 + t0; + r.y = t8 - t0; + let t10 = t10.square() - ysquared; + let ztsquared = r.z.square(); + let t10 = t10 - ztsquared; + let t9 = t9 + t9 - t10; + let t10 = r.z + r.z; + let t6 = -t6; + let t1 = t6 + t6; + + (t10, t1, t9) +} + +#[test] +fn test_bilinearity() { + use crate::Scalar; + + let a = Scalar::from_raw([1, 2, 3, 4]).invert().unwrap().square(); + let b = Scalar::from_raw([5, 6, 7, 8]).invert().unwrap().square(); + let c = a * b; + + let g = G1Affine::from(G1Affine::generator() * a); + let h = G2Affine::from(G2Affine::generator() * b); + let p = pairing(&g, &h); + + assert!(p != Gt::identity()); + + let expected = G1Affine::from(G1Affine::generator() * c); + + assert_eq!(p, pairing(&expected, &G2Affine::generator())); + assert_eq!( + p, + pairing(&G1Affine::generator(), &G2Affine::generator()) * c + ); +} + +#[test] +fn test_unitary() { + let g = G1Affine::generator(); + let h = G2Affine::generator(); + let p = -pairing(&g, &h); + let q = pairing(&g, &-h); + let r = pairing(&-g, &h); + + assert_eq!(p, q); + assert_eq!(q, r); +} + +#[cfg(feature = "alloc")] +#[test] +fn test_multi_miller_loop() { + let a1 = G1Affine::generator(); + let b1 = G2Affine::generator(); + + let a2 = G1Affine::from( + G1Affine::generator() * Scalar::from_raw([1, 2, 3, 4]).invert().unwrap().square(), + ); + let b2 = G2Affine::from( + G2Affine::generator() * Scalar::from_raw([4, 2, 2, 4]).invert().unwrap().square(), + ); + + let a3 = G1Affine::identity(); + let b3 = G2Affine::from( + G2Affine::generator() * Scalar::from_raw([9, 2, 2, 4]).invert().unwrap().square(), + ); + + let a4 = G1Affine::from( + G1Affine::generator() * Scalar::from_raw([5, 5, 5, 5]).invert().unwrap().square(), + ); + let b4 = G2Affine::identity(); + + let a5 = G1Affine::from( + G1Affine::generator() * Scalar::from_raw([323, 32, 3, 1]).invert().unwrap().square(), + ); + let b5 = G2Affine::from( + G2Affine::generator() * Scalar::from_raw([4, 2, 2, 9099]).invert().unwrap().square(), + ); + + let b1_prepared = G2Prepared::from(b1); + let b2_prepared = G2Prepared::from(b2); + let b3_prepared = G2Prepared::from(b3); + let b4_prepared = G2Prepared::from(b4); + let b5_prepared = G2Prepared::from(b5); + + let expected = pairing(&a1, &b1) + + pairing(&a2, &b2) + + pairing(&a3, &b3) + + pairing(&a4, &b4) + + pairing(&a5, &b5); + + let test = multi_miller_loop(&[ + (&a1, &b1_prepared), + (&a2, &b2_prepared), + (&a3, &b3_prepared), + (&a4, &b4_prepared), + (&a5, &b5_prepared), + ]) + .final_exponentiation(); + + assert_eq!(expected, test); +} diff --git a/bls12_381/src/scalar.rs b/bls12_381/src/scalar.rs new file mode 100644 index 0000000..91f88a4 --- /dev/null +++ b/bls12_381/src/scalar.rs @@ -0,0 +1,1076 @@ +//! This module provides an implementation of the BLS12-381 scalar field $\mathbb{F}_q$ +//! where `q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` + +use core::convert::TryFrom; +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use crate::util::{adc, mac, sbb}; + +/// Represents an element of the scalar field $\mathbb{F}_q$ of the BLS12-381 elliptic +/// curve construction. +// The internal representation of this type is four 64-bit unsigned +// integers in little-endian order. `Scalar` values are always in +// Montgomery form; i.e., Scalar(a) = aR mod q, with R = 2^256. +#[derive(Clone, Copy, Eq)] +pub struct Scalar(pub(crate) [u64; 4]); + +impl fmt::Debug for Scalar { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let tmp = self.to_bytes(); + write!(f, "0x")?; + for &b in tmp.iter().rev() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl From for Scalar { + fn from(val: u64) -> Scalar { + Scalar([val, 0, 0, 0]) * R2 + } +} + +impl ConstantTimeEq for Scalar { + fn ct_eq(&self, other: &Self) -> Choice { + self.0[0].ct_eq(&other.0[0]) + & self.0[1].ct_eq(&other.0[1]) + & self.0[2].ct_eq(&other.0[2]) + & self.0[3].ct_eq(&other.0[3]) + } +} + +impl PartialEq for Scalar { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl ConditionallySelectable for Scalar { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Scalar([ + u64::conditional_select(&a.0[0], &b.0[0], choice), + u64::conditional_select(&a.0[1], &b.0[1], choice), + u64::conditional_select(&a.0[2], &b.0[2], choice), + u64::conditional_select(&a.0[3], &b.0[3], choice), + ]) + } +} + +/// Constant representing the modulus +/// q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +const MODULUS: Scalar = Scalar([ + 0xffff_ffff_0000_0001, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, +]); + +impl<'a> Neg for &'a Scalar { + type Output = Scalar; + + #[inline] + fn neg(self) -> Scalar { + self.neg() + } +} + +impl Neg for Scalar { + type Output = Scalar; + + #[inline] + fn neg(self) -> Scalar { + -&self + } +} + +impl<'a, 'b> Sub<&'b Scalar> for &'a Scalar { + type Output = Scalar; + + #[inline] + fn sub(self, rhs: &'b Scalar) -> Scalar { + self.sub(rhs) + } +} + +impl<'a, 'b> Add<&'b Scalar> for &'a Scalar { + type Output = Scalar; + + #[inline] + fn add(self, rhs: &'b Scalar) -> Scalar { + self.add(rhs) + } +} + +impl<'a, 'b> Mul<&'b Scalar> for &'a Scalar { + type Output = Scalar; + + #[inline] + fn mul(self, rhs: &'b Scalar) -> Scalar { + self.mul(rhs) + } +} + +impl_binops_additive!(Scalar, Scalar); +impl_binops_multiplicative!(Scalar, Scalar); + +/// INV = -(q^{-1} mod 2^64) mod 2^64 +const INV: u64 = 0xffff_fffe_ffff_ffff; + +/// R = 2^256 mod q +const R: Scalar = Scalar([ + 0x0000_0001_ffff_fffe, + 0x5884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff5, + 0x1824_b159_acc5_056f, +]); + +/// R^2 = 2^512 mod q +const R2: Scalar = Scalar([ + 0xc999_e990_f3f2_9c6d, + 0x2b6c_edcb_8792_5c23, + 0x05d3_1496_7254_398f, + 0x0748_d9d9_9f59_ff11, +]); + +/// R^3 = 2^768 mod q +const R3: Scalar = Scalar([ + 0xc62c_1807_439b_73af, + 0x1b3e_0d18_8cf0_6990, + 0x73d1_3c71_c7b5_f418, + 0x6e2a_5bb9_c8db_33e9, +]); + +const S: u32 = 32; + +/// GENERATOR^t where t * 2^s + 1 = q +/// with t odd. In other words, this +/// is a 2^s root of unity. +/// +/// `GENERATOR = 7 mod q` is a generator +/// of the q - 1 order multiplicative +/// subgroup. +const ROOT_OF_UNITY: Scalar = Scalar([ + 0xb9b5_8d8c_5f0e_466a, + 0x5b1b_4c80_1819_d7ec, + 0x0af5_3ae3_52a3_1e64, + 0x5bf3_adda_19e9_b27b, +]); + +impl Default for Scalar { + #[inline] + fn default() -> Self { + Self::zero() + } +} + +impl Scalar { + /// Returns zero, the additive identity. + #[inline] + pub const fn zero() -> Scalar { + Scalar([0, 0, 0, 0]) + } + + /// Returns one, the multiplicative identity. + #[inline] + pub const fn one() -> Scalar { + R + } + + /// Doubles this field element. + #[inline] + pub const fn double(&self) -> Scalar { + // TODO: This can be achieved more efficiently with a bitshift. + self.add(self) + } + + /// Attempts to convert a little-endian byte representation of + /// a scalar into a `Scalar`, failing if the input is not canonical. + pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { + let mut tmp = Scalar([0, 0, 0, 0]); + + tmp.0[0] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap()); + tmp.0[1] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap()); + tmp.0[2] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap()); + tmp.0[3] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap()); + + // Try to subtract the modulus + let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); + let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); + let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); + let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); + + // If the element is smaller than MODULUS then the + // subtraction will underflow, producing a borrow value + // of 0xffff...ffff. Otherwise, it'll be zero. + let is_some = (borrow as u8) & 1; + + // Convert to Montgomery form by computing + // (a.R^0 * R^2) / R = a.R + tmp *= &R2; + + CtOption::new(tmp, Choice::from(is_some)) + } + + /// Converts an element of `Scalar` into a byte representation in + /// little-endian byte order. + pub fn to_bytes(&self) -> [u8; 32] { + // Turn into canonical form by computing + // (a.R) / R = a + let tmp = Scalar::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); + + let mut res = [0; 32]; + res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + + res + } + + /// Converts a 512-bit little endian integer into + /// a `Scalar` by reducing by the modulus. + pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar { + Scalar::from_u512([ + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap()), + ]) + } + + fn from_u512(limbs: [u64; 8]) -> Scalar { + // We reduce an arbitrary 512-bit number by decomposing it into two 256-bit digits + // with the higher bits multiplied by 2^256. Thus, we perform two reductions + // + // 1. the lower bits are multiplied by R^2, as normal + // 2. the upper bits are multiplied by R^2 * 2^256 = R^3 + // + // and computing their sum in the field. It remains to see that arbitrary 256-bit + // numbers can be placed into Montgomery form safely using the reduction. The + // reduction works so long as the product is less than R=2^256 multipled by + // the modulus. This holds because for any `c` smaller than the modulus, we have + // that (2^256 - 1)*c is an acceptable product for the reduction. Therefore, the + // reduction always works so long as `c` is in the field; in this case it is either the + // constant `R2` or `R3`. + let d0 = Scalar([limbs[0], limbs[1], limbs[2], limbs[3]]); + let d1 = Scalar([limbs[4], limbs[5], limbs[6], limbs[7]]); + // Convert to Montgomery form + d0 * R2 + d1 * R3 + } + + /// Converts from an integer represented in little endian + /// into its (congruent) `Scalar` representation. + pub const fn from_raw(val: [u64; 4]) -> Self { + (&Scalar(val)).mul(&R2) + } + + /// Squares this element. + #[inline] + pub const fn square(&self) -> Scalar { + let (r1, carry) = mac(0, self.0[0], self.0[1], 0); + let (r2, carry) = mac(0, self.0[0], self.0[2], carry); + let (r3, r4) = mac(0, self.0[0], self.0[3], carry); + + let (r3, carry) = mac(r3, self.0[1], self.0[2], 0); + let (r4, r5) = mac(r4, self.0[1], self.0[3], carry); + + let (r5, r6) = mac(r5, self.0[2], self.0[3], 0); + + let r7 = r6 >> 63; + let r6 = (r6 << 1) | (r5 >> 63); + let r5 = (r5 << 1) | (r4 >> 63); + let r4 = (r4 << 1) | (r3 >> 63); + let r3 = (r3 << 1) | (r2 >> 63); + let r2 = (r2 << 1) | (r1 >> 63); + let r1 = r1 << 1; + + let (r0, carry) = mac(0, self.0[0], self.0[0], 0); + let (r1, carry) = adc(0, r1, carry); + let (r2, carry) = mac(r2, self.0[1], self.0[1], carry); + let (r3, carry) = adc(0, r3, carry); + let (r4, carry) = mac(r4, self.0[2], self.0[2], carry); + let (r5, carry) = adc(0, r5, carry); + let (r6, carry) = mac(r6, self.0[3], self.0[3], carry); + let (r7, _) = adc(0, r7, carry); + + Scalar::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + /// Computes the square root of this element, if it exists. + pub fn sqrt(&self) -> CtOption { + // Tonelli-Shank's algorithm for q mod 16 = 1 + // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + + // w = self^((t - 1) // 2) + // = self^6104339283789297388802252303364915521546564123189034618274734669823 + let w = self.pow_vartime(&[ + 0x7fff_2dff_7fff_ffff, + 0x04d0_ec02_a9de_d201, + 0x94ce_bea4_199c_ec04, + 0x0000_0000_39f6_d3a9, + ]); + + let mut v = S; + let mut x = self * w; + let mut b = x * w; + + // Initialize z as the 2^S root of unity. + let mut z = ROOT_OF_UNITY; + + for max_v in (1..=S).rev() { + let mut k = 1; + let mut tmp = b.square(); + let mut j_less_than_v: Choice = 1.into(); + + for j in 2..max_v { + let tmp_is_one = tmp.ct_eq(&Scalar::one()); + let squared = Scalar::conditional_select(&tmp, &z, tmp_is_one).square(); + tmp = Scalar::conditional_select(&squared, &tmp, tmp_is_one); + let new_z = Scalar::conditional_select(&z, &squared, tmp_is_one); + j_less_than_v &= !j.ct_eq(&v); + k = u32::conditional_select(&j, &k, tmp_is_one); + z = Scalar::conditional_select(&z, &new_z, j_less_than_v); + } + + let result = x * z; + x = Scalar::conditional_select(&result, &x, b.ct_eq(&Scalar::one())); + z = z.square(); + b *= z; + v = k; + } + + CtOption::new( + x, + (x * x).ct_eq(self), // Only return Some if it's the square root. + ) + } + + /// Exponentiates `self` by `by`, where `by` is a + /// little-endian order integer exponent. + pub fn pow(&self, by: &[u64; 4]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + let mut tmp = res; + tmp *= self; + res.conditional_assign(&tmp, (((*e >> i) & 0x1) as u8).into()); + } + } + res + } + + /// Exponentiates `self` by `by`, where `by` is a + /// little-endian order integer exponent. + /// + /// **This operation is variable time with respect + /// to the exponent.** If the exponent is fixed, + /// this operation is effectively constant time. + pub fn pow_vartime(&self, by: &[u64; 4]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + + if ((*e >> i) & 1) == 1 { + res.mul_assign(self); + } + } + } + res + } + + /// Computes the multiplicative inverse of this element, + /// failing if the element is zero. + pub fn invert(&self) -> CtOption { + #[inline(always)] + fn square_assign_multi(n: &mut Scalar, num_times: usize) { + for _ in 0..num_times { + *n = n.square(); + } + } + // found using https://github.com/kwantam/addchain + let mut t0 = self.square(); + let mut t1 = t0 * self; + let mut t16 = t0.square(); + let mut t6 = t16.square(); + let mut t5 = t6 * t0; + t0 = t6 * t16; + let mut t12 = t5 * t16; + let mut t2 = t6.square(); + let mut t7 = t5 * t6; + let mut t15 = t0 * t5; + let mut t17 = t12.square(); + t1 *= t17; + let mut t3 = t7 * t2; + let t8 = t1 * t17; + let t4 = t8 * t2; + let t9 = t8 * t7; + t7 = t4 * t5; + let t11 = t4 * t17; + t5 = t9 * t17; + let t14 = t7 * t15; + let t13 = t11 * t12; + t12 = t11 * t17; + t15 *= &t12; + t16 *= &t15; + t3 *= &t16; + t17 *= &t3; + t0 *= &t17; + t6 *= &t0; + t2 *= &t6; + square_assign_multi(&mut t0, 8); + t0 *= &t17; + square_assign_multi(&mut t0, 9); + t0 *= &t16; + square_assign_multi(&mut t0, 9); + t0 *= &t15; + square_assign_multi(&mut t0, 9); + t0 *= &t15; + square_assign_multi(&mut t0, 7); + t0 *= &t14; + square_assign_multi(&mut t0, 7); + t0 *= &t13; + square_assign_multi(&mut t0, 10); + t0 *= &t12; + square_assign_multi(&mut t0, 9); + t0 *= &t11; + square_assign_multi(&mut t0, 8); + t0 *= &t8; + square_assign_multi(&mut t0, 8); + t0 *= self; + square_assign_multi(&mut t0, 14); + t0 *= &t9; + square_assign_multi(&mut t0, 10); + t0 *= &t8; + square_assign_multi(&mut t0, 15); + t0 *= &t7; + square_assign_multi(&mut t0, 10); + t0 *= &t6; + square_assign_multi(&mut t0, 8); + t0 *= &t5; + square_assign_multi(&mut t0, 16); + t0 *= &t3; + square_assign_multi(&mut t0, 8); + t0 *= &t2; + square_assign_multi(&mut t0, 7); + t0 *= &t4; + square_assign_multi(&mut t0, 9); + t0 *= &t2; + square_assign_multi(&mut t0, 8); + t0 *= &t3; + square_assign_multi(&mut t0, 8); + t0 *= &t2; + square_assign_multi(&mut t0, 8); + t0 *= &t2; + square_assign_multi(&mut t0, 8); + t0 *= &t2; + square_assign_multi(&mut t0, 8); + t0 *= &t3; + square_assign_multi(&mut t0, 8); + t0 *= &t2; + square_assign_multi(&mut t0, 8); + t0 *= &t2; + square_assign_multi(&mut t0, 5); + t0 *= &t1; + square_assign_multi(&mut t0, 5); + t0 *= &t1; + + CtOption::new(t0, !self.ct_eq(&Self::zero())) + } + + #[inline(always)] + const fn montgomery_reduce( + r0: u64, + r1: u64, + r2: u64, + r3: u64, + r4: u64, + r5: u64, + r6: u64, + r7: u64, + ) -> Self { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r0.wrapping_mul(INV); + let (_, carry) = mac(r0, k, MODULUS.0[0], 0); + let (r1, carry) = mac(r1, k, MODULUS.0[1], carry); + let (r2, carry) = mac(r2, k, MODULUS.0[2], carry); + let (r3, carry) = mac(r3, k, MODULUS.0[3], carry); + let (r4, carry2) = adc(r4, 0, carry); + + let k = r1.wrapping_mul(INV); + let (_, carry) = mac(r1, k, MODULUS.0[0], 0); + let (r2, carry) = mac(r2, k, MODULUS.0[1], carry); + let (r3, carry) = mac(r3, k, MODULUS.0[2], carry); + let (r4, carry) = mac(r4, k, MODULUS.0[3], carry); + let (r5, carry2) = adc(r5, carry2, carry); + + let k = r2.wrapping_mul(INV); + let (_, carry) = mac(r2, k, MODULUS.0[0], 0); + let (r3, carry) = mac(r3, k, MODULUS.0[1], carry); + let (r4, carry) = mac(r4, k, MODULUS.0[2], carry); + let (r5, carry) = mac(r5, k, MODULUS.0[3], carry); + let (r6, carry2) = adc(r6, carry2, carry); + + let k = r3.wrapping_mul(INV); + let (_, carry) = mac(r3, k, MODULUS.0[0], 0); + let (r4, carry) = mac(r4, k, MODULUS.0[1], carry); + let (r5, carry) = mac(r5, k, MODULUS.0[2], carry); + let (r6, carry) = mac(r6, k, MODULUS.0[3], carry); + let (r7, _) = adc(r7, carry2, carry); + + // Result may be within MODULUS of the correct value + (&Scalar([r4, r5, r6, r7])).sub(&MODULUS) + } + + /// Multiplies `rhs` by `self`, returning the result. + #[inline] + pub const fn mul(&self, rhs: &Self) -> Self { + // Schoolbook multiplication + + let (r0, carry) = mac(0, self.0[0], rhs.0[0], 0); + let (r1, carry) = mac(0, self.0[0], rhs.0[1], carry); + let (r2, carry) = mac(0, self.0[0], rhs.0[2], carry); + let (r3, r4) = mac(0, self.0[0], rhs.0[3], carry); + + let (r1, carry) = mac(r1, self.0[1], rhs.0[0], 0); + let (r2, carry) = mac(r2, self.0[1], rhs.0[1], carry); + let (r3, carry) = mac(r3, self.0[1], rhs.0[2], carry); + let (r4, r5) = mac(r4, self.0[1], rhs.0[3], carry); + + let (r2, carry) = mac(r2, self.0[2], rhs.0[0], 0); + let (r3, carry) = mac(r3, self.0[2], rhs.0[1], carry); + let (r4, carry) = mac(r4, self.0[2], rhs.0[2], carry); + let (r5, r6) = mac(r5, self.0[2], rhs.0[3], carry); + + let (r3, carry) = mac(r3, self.0[3], rhs.0[0], 0); + let (r4, carry) = mac(r4, self.0[3], rhs.0[1], carry); + let (r5, carry) = mac(r5, self.0[3], rhs.0[2], carry); + let (r6, r7) = mac(r6, self.0[3], rhs.0[3], carry); + + Scalar::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + /// Subtracts `rhs` from `self`, returning the result. + #[inline] + pub const fn sub(&self, rhs: &Self) -> Self { + let (d0, borrow) = sbb(self.0[0], rhs.0[0], 0); + let (d1, borrow) = sbb(self.0[1], rhs.0[1], borrow); + let (d2, borrow) = sbb(self.0[2], rhs.0[2], borrow); + let (d3, borrow) = sbb(self.0[3], rhs.0[3], borrow); + + // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise + // borrow = 0x000...000. Thus, we use it as a mask to conditionally add the modulus. + let (d0, carry) = adc(d0, MODULUS.0[0] & borrow, 0); + let (d1, carry) = adc(d1, MODULUS.0[1] & borrow, carry); + let (d2, carry) = adc(d2, MODULUS.0[2] & borrow, carry); + let (d3, _) = adc(d3, MODULUS.0[3] & borrow, carry); + + Scalar([d0, d1, d2, d3]) + } + + /// Adds `rhs` to `self`, returning the result. + #[inline] + pub const fn add(&self, rhs: &Self) -> Self { + let (d0, carry) = adc(self.0[0], rhs.0[0], 0); + let (d1, carry) = adc(self.0[1], rhs.0[1], carry); + let (d2, carry) = adc(self.0[2], rhs.0[2], carry); + let (d3, _) = adc(self.0[3], rhs.0[3], carry); + + // Attempt to subtract the modulus, to ensure the value + // is smaller than the modulus. + (&Scalar([d0, d1, d2, d3])).sub(&MODULUS) + } + + /// Negates `self`. + #[inline] + pub const fn neg(&self) -> Self { + // Subtract `self` from `MODULUS` to negate. Ignore the final + // borrow because it cannot underflow; self is guaranteed to + // be in the field. + let (d0, borrow) = sbb(MODULUS.0[0], self.0[0], 0); + let (d1, borrow) = sbb(MODULUS.0[1], self.0[1], borrow); + let (d2, borrow) = sbb(MODULUS.0[2], self.0[2], borrow); + let (d3, _) = sbb(MODULUS.0[3], self.0[3], borrow); + + // `tmp` could be `MODULUS` if `self` was zero. Create a mask that is + // zero if `self` was zero, and `u64::max_value()` if self was nonzero. + let mask = (((self.0[0] | self.0[1] | self.0[2] | self.0[3]) == 0) as u64).wrapping_sub(1); + + Scalar([d0 & mask, d1 & mask, d2 & mask, d3 & mask]) + } +} + +impl<'a> From<&'a Scalar> for [u8; 32] { + fn from(value: &'a Scalar) -> [u8; 32] { + value.to_bytes() + } +} + +#[test] +fn test_inv() { + // Compute -(q^{-1} mod 2^64) mod 2^64 by exponentiating + // by totient(2**64) - 1 + + let mut inv = 1u64; + for _ in 0..63 { + inv = inv.wrapping_mul(inv); + inv = inv.wrapping_mul(MODULUS.0[0]); + } + inv = inv.wrapping_neg(); + + assert_eq!(inv, INV); +} + +#[cfg(feature = "std")] +#[test] +fn test_debug() { + assert_eq!( + format!("{:?}", Scalar::zero()), + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + assert_eq!( + format!("{:?}", Scalar::one()), + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert_eq!( + format!("{:?}", R2), + "0x1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe" + ); +} + +#[test] +fn test_equality() { + assert_eq!(Scalar::zero(), Scalar::zero()); + assert_eq!(Scalar::one(), Scalar::one()); + assert_eq!(R2, R2); + + assert!(Scalar::zero() != Scalar::one()); + assert!(Scalar::one() != R2); +} + +#[test] +fn test_to_bytes() { + assert_eq!( + Scalar::zero().to_bytes(), + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ] + ); + + assert_eq!( + Scalar::one().to_bytes(), + [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ] + ); + + assert_eq!( + R2.to_bytes(), + [ + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24 + ] + ); + + assert_eq!( + (-&Scalar::one()).to_bytes(), + [ + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + ] + ); +} + +#[test] +fn test_from_bytes() { + assert_eq!( + Scalar::from_bytes(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ]) + .unwrap(), + Scalar::zero() + ); + + assert_eq!( + Scalar::from_bytes(&[ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ]) + .unwrap(), + Scalar::one() + ); + + assert_eq!( + Scalar::from_bytes(&[ + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24 + ]) + .unwrap(), + R2 + ); + + // -1 should work + assert!( + Scalar::from_bytes(&[ + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + ]) + .is_some() + .unwrap_u8() + == 1 + ); + + // modulus is invalid + assert!( + Scalar::from_bytes(&[ + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + // Anything larger than the modulus is invalid + assert!( + Scalar::from_bytes(&[ + 2, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + assert!( + Scalar::from_bytes(&[ + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 58, 51, 72, 125, 157, 41, 83, 167, 237, 115 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + assert!( + Scalar::from_bytes(&[ + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 116 + ]) + .is_none() + .unwrap_u8() + == 1 + ); +} + +#[test] +fn test_from_u512_zero() { + assert_eq!( + Scalar::zero(), + Scalar::from_u512([ + MODULUS.0[0], + MODULUS.0[1], + MODULUS.0[2], + MODULUS.0[3], + 0, + 0, + 0, + 0 + ]) + ); +} + +#[test] +fn test_from_u512_r() { + assert_eq!(R, Scalar::from_u512([1, 0, 0, 0, 0, 0, 0, 0])); +} + +#[test] +fn test_from_u512_r2() { + assert_eq!(R2, Scalar::from_u512([0, 0, 0, 0, 1, 0, 0, 0])); +} + +#[test] +fn test_from_u512_max() { + let max_u64 = 0xffff_ffff_ffff_ffff; + assert_eq!( + R3 - R, + Scalar::from_u512([max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64]) + ); +} + +#[test] +fn test_from_bytes_wide_r2() { + assert_eq!( + R2, + Scalar::from_bytes_wide(&[ + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]) + ); +} + +#[test] +fn test_from_bytes_wide_negative_one() { + assert_eq!( + -&Scalar::one(), + Scalar::from_bytes_wide(&[ + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]) + ); +} + +#[test] +fn test_from_bytes_wide_maximum() { + assert_eq!( + Scalar([ + 0xc62c_1805_439b_73b1, + 0xc2b9_551e_8ced_218e, + 0xda44_ec81_daf9_a422, + 0x5605_aa60_1c16_2e79, + ]), + Scalar::from_bytes_wide(&[0xff; 64]) + ); +} + +#[test] +fn test_zero() { + assert_eq!(Scalar::zero(), -&Scalar::zero()); + assert_eq!(Scalar::zero(), Scalar::zero() + Scalar::zero()); + assert_eq!(Scalar::zero(), Scalar::zero() - Scalar::zero()); + assert_eq!(Scalar::zero(), Scalar::zero() * Scalar::zero()); +} + +#[cfg(test)] +const LARGEST: Scalar = Scalar([ + 0xffff_ffff_0000_0000, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, +]); + +#[test] +fn test_addition() { + let mut tmp = LARGEST; + tmp += &LARGEST; + + assert_eq!( + tmp, + Scalar([ + 0xffff_fffe_ffff_ffff, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, + ]) + ); + + let mut tmp = LARGEST; + tmp += &Scalar([1, 0, 0, 0]); + + assert_eq!(tmp, Scalar::zero()); +} + +#[test] +fn test_negation() { + let tmp = -&LARGEST; + + assert_eq!(tmp, Scalar([1, 0, 0, 0])); + + let tmp = -&Scalar::zero(); + assert_eq!(tmp, Scalar::zero()); + let tmp = -&Scalar([1, 0, 0, 0]); + assert_eq!(tmp, LARGEST); +} + +#[test] +fn test_subtraction() { + let mut tmp = LARGEST; + tmp -= &LARGEST; + + assert_eq!(tmp, Scalar::zero()); + + let mut tmp = Scalar::zero(); + tmp -= &LARGEST; + + let mut tmp2 = MODULUS; + tmp2 -= &LARGEST; + + assert_eq!(tmp, tmp2); +} + +#[test] +fn test_multiplication() { + let mut cur = LARGEST; + + for _ in 0..100 { + let mut tmp = cur; + tmp *= &cur; + + let mut tmp2 = Scalar::zero(); + for b in cur + .to_bytes() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8)) + { + let tmp3 = tmp2; + tmp2.add_assign(&tmp3); + + if b { + tmp2.add_assign(&cur); + } + } + + assert_eq!(tmp, tmp2); + + cur.add_assign(&LARGEST); + } +} + +#[test] +fn test_squaring() { + let mut cur = LARGEST; + + for _ in 0..100 { + let mut tmp = cur; + tmp = tmp.square(); + + let mut tmp2 = Scalar::zero(); + for b in cur + .to_bytes() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8)) + { + let tmp3 = tmp2; + tmp2.add_assign(&tmp3); + + if b { + tmp2.add_assign(&cur); + } + } + + assert_eq!(tmp, tmp2); + + cur.add_assign(&LARGEST); + } +} + +#[test] +fn test_inversion() { + assert_eq!(Scalar::zero().invert().is_none().unwrap_u8(), 1); + assert_eq!(Scalar::one().invert().unwrap(), Scalar::one()); + assert_eq!((-&Scalar::one()).invert().unwrap(), -&Scalar::one()); + + let mut tmp = R2; + + for _ in 0..100 { + let mut tmp2 = tmp.invert().unwrap(); + tmp2.mul_assign(&tmp); + + assert_eq!(tmp2, Scalar::one()); + + tmp.add_assign(&R2); + } +} + +#[test] +fn test_invert_is_pow() { + let q_minus_2 = [ + 0xffff_fffe_ffff_ffff, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, + ]; + + let mut r1 = R; + let mut r2 = R; + let mut r3 = R; + + for _ in 0..100 { + r1 = r1.invert().unwrap(); + r2 = r2.pow_vartime(&q_minus_2); + r3 = r3.pow(&q_minus_2); + + assert_eq!(r1, r2); + assert_eq!(r2, r3); + // Add R so we check something different next time around + r1.add_assign(&R); + r2 = r1; + r3 = r1; + } +} + +#[test] +fn test_sqrt() { + { + assert_eq!(Scalar::zero().sqrt().unwrap(), Scalar::zero()); + } + + let mut square = Scalar([ + 0x46cd_85a5_f273_077e, + 0x1d30_c47d_d68f_c735, + 0x77f6_56f6_0bec_a0eb, + 0x494a_a01b_df32_468d, + ]); + + let mut none_count = 0; + + for _ in 0..100 { + let square_root = square.sqrt(); + if square_root.is_none().unwrap_u8() == 1 { + none_count += 1; + } else { + assert_eq!(square_root.unwrap() * square_root.unwrap(), square); + } + square -= Scalar::one(); + } + + assert_eq!(49, none_count); +} + +#[test] +fn test_from_raw() { + assert_eq!( + Scalar::from_raw([ + 0x0001_ffff_fffd, + 0x5884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff5, + 0x1824_b159_acc5_056f, + ]), + Scalar::from_raw([0xffff_ffff_ffff_ffff; 4]) + ); + + assert_eq!(Scalar::from_raw(MODULUS.0), Scalar::zero()); + + assert_eq!(Scalar::from_raw([1, 0, 0, 0]), R); +} + +#[test] +fn test_double() { + let a = Scalar::from_raw([ + 0x1fff_3231_233f_fffd, + 0x4884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff3, + 0x1824_b159_acc5_0562, + ]); + + assert_eq!(a.double(), a + a); +} diff --git a/bls12_381/src/tests/g1_compressed_valid_test_vectors.dat b/bls12_381/src/tests/g1_compressed_valid_test_vectors.dat new file mode 100644 index 0000000..ea8cd67 Binary files /dev/null and b/bls12_381/src/tests/g1_compressed_valid_test_vectors.dat differ diff --git a/bls12_381/src/tests/g1_uncompressed_valid_test_vectors.dat b/bls12_381/src/tests/g1_uncompressed_valid_test_vectors.dat new file mode 100644 index 0000000..86abfba Binary files /dev/null and b/bls12_381/src/tests/g1_uncompressed_valid_test_vectors.dat differ diff --git a/bls12_381/src/tests/g2_compressed_valid_test_vectors.dat b/bls12_381/src/tests/g2_compressed_valid_test_vectors.dat new file mode 100644 index 0000000..a40bbe2 Binary files /dev/null and b/bls12_381/src/tests/g2_compressed_valid_test_vectors.dat differ diff --git a/bls12_381/src/tests/g2_uncompressed_valid_test_vectors.dat b/bls12_381/src/tests/g2_uncompressed_valid_test_vectors.dat new file mode 100644 index 0000000..92e4bc5 Binary files /dev/null and b/bls12_381/src/tests/g2_uncompressed_valid_test_vectors.dat differ diff --git a/bls12_381/src/tests/mod.rs b/bls12_381/src/tests/mod.rs new file mode 100644 index 0000000..d6ab86d --- /dev/null +++ b/bls12_381/src/tests/mod.rs @@ -0,0 +1,230 @@ +use super::*; + +macro_rules! test_vectors { + ($projective:ident, $affine:ident, $serialize:ident, $deserialize:ident, $expected:ident) => { + let mut e = $projective::identity(); + + let mut v = vec![]; + { + let mut expected = $expected; + for _ in 0..1000 { + let e_affine = $affine::from(e); + let encoded = e_affine.$serialize(); + v.extend_from_slice(&encoded[..]); + + let mut decoded = encoded; + let len_of_encoding = decoded.len(); + (&mut decoded[..]).copy_from_slice(&expected[0..len_of_encoding]); + expected = &expected[len_of_encoding..]; + let decoded = $affine::$deserialize(&decoded).unwrap(); + assert_eq!(e_affine, decoded); + + e = &e + &$projective::generator(); + } + } + + assert_eq!(&v[..], $expected); + }; +} + +#[test] +fn g1_uncompressed_valid_test_vectors() { + let bytes: &'static [u8] = include_bytes!("g1_uncompressed_valid_test_vectors.dat"); + test_vectors!( + G1Projective, + G1Affine, + to_uncompressed, + from_uncompressed, + bytes + ); +} + +#[test] +fn g1_compressed_valid_test_vectors() { + let bytes: &'static [u8] = include_bytes!("g1_compressed_valid_test_vectors.dat"); + test_vectors!( + G1Projective, + G1Affine, + to_compressed, + from_compressed, + bytes + ); +} + +#[test] +fn g2_uncompressed_valid_test_vectors() { + let bytes: &'static [u8] = include_bytes!("g2_uncompressed_valid_test_vectors.dat"); + test_vectors!( + G2Projective, + G2Affine, + to_uncompressed, + from_uncompressed, + bytes + ); +} + +#[test] +fn g2_compressed_valid_test_vectors() { + let bytes: &'static [u8] = include_bytes!("g2_compressed_valid_test_vectors.dat"); + test_vectors!( + G2Projective, + G2Affine, + to_compressed, + from_compressed, + bytes + ); +} + +#[test] +fn test_pairing_result_against_relic() { + /* + Sent to me from Diego Aranha (author of RELIC library): + 1250EBD871FC0A92 A7B2D83168D0D727 272D441BEFA15C50 3DD8E90CE98DB3E7 B6D194F60839C508 A84305AACA1789B6 + 089A1C5B46E5110B 86750EC6A5323488 68A84045483C92B7 AF5AF689452EAFAB F1A8943E50439F1D 59882A98EAA0170F + 1368BB445C7C2D20 9703F239689CE34C 0378A68E72A6B3B2 16DA0E22A5031B54 DDFF57309396B38C 881C4C849EC23E87 + 193502B86EDB8857 C273FA075A505129 37E0794E1E65A761 7C90D8BD66065B1F FFE51D7A579973B1 315021EC3C19934F + 01B2F522473D1713 91125BA84DC4007C FBF2F8DA752F7C74 185203FCCA589AC7 19C34DFFBBAAD843 1DAD1C1FB597AAA5 + 018107154F25A764 BD3C79937A45B845 46DA634B8F6BE14A 8061E55CCEBA478B 23F7DACAA35C8CA7 8BEAE9624045B4B6 + 19F26337D205FB46 9CD6BD15C3D5A04D C88784FBB3D0B2DB DEA54D43B2B73F2C BB12D58386A8703E 0F948226E47EE89D + 06FBA23EB7C5AF0D 9F80940CA771B6FF D5857BAAF222EB95 A7D2809D61BFE02E 1BFD1B68FF02F0B8 102AE1C2D5D5AB1A + 11B8B424CD48BF38 FCEF68083B0B0EC5 C81A93B330EE1A67 7D0D15FF7B984E89 78EF48881E32FAC9 1B93B47333E2BA57 + 03350F55A7AEFCD3 C31B4FCB6CE5771C C6A0E9786AB59733 20C806AD36082910 7BA810C5A09FFDD9 BE2291A0C25A99A2 + 04C581234D086A99 02249B64728FFD21 A189E87935A95405 1C7CDBA7B3872629 A4FAFC05066245CB 9108F0242D0FE3EF + 0F41E58663BF08CF 068672CBD01A7EC7 3BACA4D72CA93544 DEFF686BFD6DF543 D48EAA24AFE47E1E FDE449383B676631 + */ + + let a = G1Affine::generator(); + let b = G2Affine::generator(); + + use super::fp::Fp; + use super::fp12::Fp12; + use super::fp2::Fp2; + use super::fp6::Fp6; + + let res = pairing(&a, &b); + + let prep = G2Prepared::from(b); + + assert_eq!( + res, + multi_miller_loop(&[(&a, &prep)]).final_exponentiation() + ); + + assert_eq!( + res.0, + Fp12 { + c0: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x1972_e433_a01f_85c5, + 0x97d3_2b76_fd77_2538, + 0xc8ce_546f_c96b_cdf9, + 0xcef6_3e73_66d4_0614, + 0xa611_3427_8184_3780, + 0x13f3_448a_3fc6_d825, + ]), + c1: Fp::from_raw_unchecked([ + 0xd263_31b0_2e9d_6995, + 0x9d68_a482_f779_7e7d, + 0x9c9b_2924_8d39_ea92, + 0xf480_1ca2_e131_07aa, + 0xa16c_0732_bdbc_b066, + 0x083c_a4af_ba36_0478, + ]) + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x59e2_61db_0916_b641, + 0x2716_b6f4_b23e_960d, + 0xc8e5_5b10_a0bd_9c45, + 0x0bdb_0bd9_9c4d_eda8, + 0x8cf8_9ebf_57fd_aac5, + 0x12d6_b792_9e77_7a5e, + ]), + c1: Fp::from_raw_unchecked([ + 0x5fc8_5188_b0e1_5f35, + 0x34a0_6e3a_8f09_6365, + 0xdb31_26a6_e02a_d62c, + 0xfc6f_5aa9_7d9a_990b, + 0xa12f_55f5_eb89_c210, + 0x1723_703a_926f_8889, + ]) + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x9358_8f29_7182_8778, + 0x43f6_5b86_11ab_7585, + 0x3183_aaf5_ec27_9fdf, + 0xfa73_d7e1_8ac9_9df6, + 0x64e1_76a6_a64c_99b0, + 0x179f_a78c_5838_8f1f, + ]), + c1: Fp::from_raw_unchecked([ + 0x672a_0a11_ca2a_ef12, + 0x0d11_b9b5_2aa3_f16b, + 0xa444_12d0_699d_056e, + 0xc01d_0177_221a_5ba5, + 0x66e0_cede_6c73_5529, + 0x05f5_a71e_9fdd_c339, + ]) + } + }, + c1: Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0xd30a_88a1_b062_c679, + 0x5ac5_6a5d_35fc_8304, + 0xd0c8_34a6_a81f_290d, + 0xcd54_30c2_da37_07c7, + 0xf0c2_7ff7_8050_0af0, + 0x0924_5da6_e2d7_2eae, + ]), + c1: Fp::from_raw_unchecked([ + 0x9f2e_0676_791b_5156, + 0xe2d1_c823_4918_fe13, + 0x4c9e_459f_3c56_1bf4, + 0xa3e8_5e53_b9d3_e3c1, + 0x820a_121e_21a7_0020, + 0x15af_6183_41c5_9acc, + ]) + }, + c1: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x7c95_658c_2499_3ab1, + 0x73eb_3872_1ca8_86b9, + 0x5256_d749_4774_34bc, + 0x8ba4_1902_ea50_4a8b, + 0x04a3_d3f8_0c86_ce6d, + 0x18a6_4a87_fb68_6eaa, + ]), + c1: Fp::from_raw_unchecked([ + 0xbb83_e71b_b920_cf26, + 0x2a52_77ac_92a7_3945, + 0xfc0e_e59f_94f0_46a0, + 0x7158_cdf3_7860_58f7, + 0x7cc1_061b_82f9_45f6, + 0x03f8_47aa_9fdb_e567, + ]) + }, + c2: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x8078_dba5_6134_e657, + 0x1cd7_ec9a_4399_8a6e, + 0xb1aa_599a_1a99_3766, + 0xc9a0_f62f_0842_ee44, + 0x8e15_9be3_b605_dffa, + 0x0c86_ba0d_4af1_3fc2, + ]), + c1: Fp::from_raw_unchecked([ + 0xe80f_f2a0_6a52_ffb1, + 0x7694_ca48_721a_906c, + 0x7583_183e_03b0_8514, + 0xf567_afdd_40ce_e4e2, + 0x9a6d_96d2_e526_a5fc, + 0x197e_9f49_861f_2242, + ]) + } + } + } + ); +} diff --git a/bls12_381/src/util.rs b/bls12_381/src/util.rs new file mode 100644 index 0000000..bd25dd0 --- /dev/null +++ b/bls12_381/src/util.rs @@ -0,0 +1,174 @@ +/// Compute a + b + carry, returning the result and the new carry over. +#[inline(always)] +pub const fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) { + let ret = (a as u128) + (b as u128) + (carry as u128); + (ret as u64, (ret >> 64) as u64) +} + +/// Compute a - (b + borrow), returning the result and the new borrow. +#[inline(always)] +pub const fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) { + let ret = (a as u128).wrapping_sub((b as u128) + ((borrow >> 63) as u128)); + (ret as u64, (ret >> 64) as u64) +} + +/// Compute a + (b * c) + carry, returning the result and the new carry over. +#[inline(always)] +pub const fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) { + let ret = (a as u128) + ((b as u128) * (c as u128)) + (carry as u128); + (ret as u64, (ret >> 64) as u64) +} + +macro_rules! impl_add_binop_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Add<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: &'b $rhs) -> $output { + &self + rhs + } + } + + impl<'a> Add<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: $rhs) -> $output { + self + &rhs + } + } + + impl Add<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: $rhs) -> $output { + &self + &rhs + } + } + }; +} + +macro_rules! impl_sub_binop_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Sub<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: &'b $rhs) -> $output { + &self - rhs + } + } + + impl<'a> Sub<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: $rhs) -> $output { + self - &rhs + } + } + + impl Sub<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: $rhs) -> $output { + &self - &rhs + } + } + }; +} + +macro_rules! impl_binops_additive_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl_add_binop_specify_output!($lhs, $rhs, $output); + impl_sub_binop_specify_output!($lhs, $rhs, $output); + }; +} + +macro_rules! impl_binops_multiplicative_mixed { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Mul<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: &'b $rhs) -> $output { + &self * rhs + } + } + + impl<'a> Mul<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: $rhs) -> $output { + self * &rhs + } + } + + impl Mul<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: $rhs) -> $output { + &self * &rhs + } + } + }; +} + +macro_rules! impl_binops_additive { + ($lhs:ident, $rhs:ident) => { + impl_binops_additive_specify_output!($lhs, $rhs, $lhs); + + impl SubAssign<$rhs> for $lhs { + #[inline] + fn sub_assign(&mut self, rhs: $rhs) { + *self = &*self - &rhs; + } + } + + impl AddAssign<$rhs> for $lhs { + #[inline] + fn add_assign(&mut self, rhs: $rhs) { + *self = &*self + &rhs; + } + } + + impl<'b> SubAssign<&'b $rhs> for $lhs { + #[inline] + fn sub_assign(&mut self, rhs: &'b $rhs) { + *self = &*self - rhs; + } + } + + impl<'b> AddAssign<&'b $rhs> for $lhs { + #[inline] + fn add_assign(&mut self, rhs: &'b $rhs) { + *self = &*self + rhs; + } + } + }; +} + +macro_rules! impl_binops_multiplicative { + ($lhs:ident, $rhs:ident) => { + impl_binops_multiplicative_mixed!($lhs, $rhs, $lhs); + + impl MulAssign<$rhs> for $lhs { + #[inline] + fn mul_assign(&mut self, rhs: $rhs) { + *self = &*self * &rhs; + } + } + + impl<'b> MulAssign<&'b $rhs> for $lhs { + #[inline] + fn mul_assign(&mut self, rhs: &'b $rhs) { + *self = &*self * rhs; + } + } + }; +} diff --git a/ff/Cargo.toml b/ff/Cargo.toml index e1cf5e3..9dbf514 100644 --- a/ff/Cargo.toml +++ b/ff/Cargo.toml @@ -11,13 +11,15 @@ repository = "https://github.com/ebfull/ff" edition = "2018" [dependencies] -byteorder = "1" +byteorder = { version = "1", optional = true } ff_derive = { version = "0.6", path = "ff_derive", optional = true } -rand_core = "0.5" +rand_core = { version = "0.5", default-features = false } +subtle = { version = "2.2.1", default-features = false, features = ["i128"] } [features] -default = [] +default = ["std"] derive = ["ff_derive"] +std = ["byteorder"] [badges] maintenance = { status = "actively-developed" } diff --git a/ff/ff_derive/src/lib.rs b/ff/ff_derive/src/lib.rs index 813efe8..a69a2c0 100644 --- a/ff/ff_derive/src/lib.rs +++ b/ff/ff_derive/src/lib.rs @@ -40,18 +40,18 @@ pub fn prime_field(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let mut cur = BigUint::one() << 64; // always 64-bit limbs for now while cur < mod2 { limbs += 1; - cur = cur << 64; + cur <<= 64; } } let mut gen = proc_macro2::TokenStream::new(); let (constants_impl, sqrt_impl) = - prime_field_constants_and_sqrt(&ast.ident, &repr_ident, modulus, limbs, generator); + prime_field_constants_and_sqrt(&ast.ident, &repr_ident, &modulus, limbs, generator); gen.extend(constants_impl); gen.extend(prime_field_repr_impl(&repr_ident, limbs)); - gen.extend(prime_field_impl(&ast.ident, &repr_ident, limbs)); + gen.extend(prime_field_impl(&ast.ident, &repr_ident, &modulus, limbs)); gen.extend(sqrt_impl); // Return the generated impl @@ -60,23 +60,16 @@ pub fn prime_field(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Fetches the ident being wrapped by the type we're deriving. fn fetch_wrapped_ident(body: &syn::Data) -> Option { - match body { - &syn::Data::Struct(ref variant_data) => match variant_data.fields { - syn::Fields::Unnamed(ref fields) => { - if fields.unnamed.len() == 1 { - match fields.unnamed[0].ty { - syn::Type::Path(ref path) => { - if path.path.segments.len() == 1 { - return Some(path.path.segments[0].ident.clone()); - } - } - _ => {} + if let syn::Data::Struct(ref variant_data) = body { + if let syn::Fields::Unnamed(ref fields) = variant_data.fields { + if fields.unnamed.len() == 1 { + if let syn::Type::Path(ref path) = fields.unnamed[0].ty { + if path.path.segments.len() == 1 { + return Some(path.path.segments[0].ident.clone()); } } } - _ => {} - }, - _ => {} + } }; None @@ -113,9 +106,9 @@ fn prime_field_repr_impl(repr: &syn::Ident, limbs: usize) -> proc_macro2::TokenS #[derive(Copy, Clone, PartialEq, Eq, Default)] pub struct #repr(pub [u64; #limbs]); - impl ::std::fmt::Debug for #repr + impl ::core::fmt::Debug for #repr { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "0x")?; for i in self.0.iter().rev() { write!(f, "{:016x}", *i)?; @@ -125,8 +118,8 @@ fn prime_field_repr_impl(repr: &syn::Ident, limbs: usize) -> proc_macro2::TokenS } } - impl ::std::fmt::Display for #repr { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + impl ::core::fmt::Display for #repr { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "0x")?; for i in self.0.iter().rev() { write!(f, "{:016x}", *i)?; @@ -153,7 +146,7 @@ fn prime_field_repr_impl(repr: &syn::Ident, limbs: usize) -> proc_macro2::TokenS impl From for #repr { #[inline(always)] fn from(val: u64) -> #repr { - use std::default::Default; + use core::default::Default; let mut repr = Self::default(); repr.0[0] = val; @@ -163,22 +156,22 @@ fn prime_field_repr_impl(repr: &syn::Ident, limbs: usize) -> proc_macro2::TokenS impl Ord for #repr { #[inline(always)] - fn cmp(&self, other: &#repr) -> ::std::cmp::Ordering { + fn cmp(&self, other: &#repr) -> ::core::cmp::Ordering { for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { if a < b { - return ::std::cmp::Ordering::Less + return ::core::cmp::Ordering::Less } else if a > b { - return ::std::cmp::Ordering::Greater + return ::core::cmp::Ordering::Greater } } - ::std::cmp::Ordering::Equal + ::core::cmp::Ordering::Equal } } impl PartialOrd for #repr { #[inline(always)] - fn partial_cmp(&self, other: &#repr) -> Option<::std::cmp::Ordering> { + fn partial_cmp(&self, other: &#repr) -> Option<::core::cmp::Ordering> { Some(self.cmp(other)) } } @@ -209,7 +202,7 @@ fn prime_field_repr_impl(repr: &syn::Ident, limbs: usize) -> proc_macro2::TokenS while n >= 64 { let mut t = 0; for i in self.0.iter_mut().rev() { - ::std::mem::swap(&mut t, i); + ::core::mem::swap(&mut t, i); } n -= 64; } @@ -257,7 +250,7 @@ fn prime_field_repr_impl(repr: &syn::Ident, limbs: usize) -> proc_macro2::TokenS while n >= 64 { let mut t = 0; for i in &mut self.0 { - ::std::mem::swap(&mut t, i); + ::core::mem::swap(&mut t, i); } n -= 64; } @@ -315,7 +308,7 @@ fn biguint_to_real_u64_vec(mut v: BigUint, limbs: usize) -> Vec { while v > BigUint::zero() { ret.push((&v % &m).to_u64().unwrap()); - v = v >> 64; + v >>= 64; } while ret.len() < limbs { @@ -337,7 +330,7 @@ fn biguint_num_bits(mut v: BigUint) -> u32 { let mut bits = 0; while v != BigUint::zero() { - v = v >> 1; + v >>= 1; bits += 1; } @@ -383,7 +376,7 @@ fn test_exp() { fn prime_field_constants_and_sqrt( name: &syn::Ident, repr: &syn::Ident, - modulus: BigUint, + modulus: &BigUint, limbs: usize, generator: BigUint, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { @@ -396,129 +389,102 @@ fn prime_field_constants_and_sqrt( let repr_shave_bits = (64 * limbs as u32) - biguint_num_bits(modulus.clone()); // Compute R = 2**(64 * limbs) mod m - let r = (BigUint::one() << (limbs * 64)) % &modulus; + let r = (BigUint::one() << (limbs * 64)) % modulus; // modulus - 1 = 2^s * t let mut s: u32 = 0; - let mut t = &modulus - BigUint::from_str("1").unwrap(); + let mut t = modulus - BigUint::from_str("1").unwrap(); while t.is_even() { - t = t >> 1; + t >>= 1; s += 1; } // Compute 2^s root of unity given the generator - let root_of_unity = biguint_to_u64_vec( - (exp(generator.clone(), &t, &modulus) * &r) % &modulus, - limbs, - ); - let generator = biguint_to_u64_vec((generator.clone() * &r) % &modulus, limbs); + let root_of_unity = + biguint_to_u64_vec((exp(generator.clone(), &t, &modulus) * &r) % modulus, limbs); + let generator = biguint_to_u64_vec((generator.clone() * &r) % modulus, limbs); - let mod_minus_1_over_2 = - biguint_to_u64_vec((&modulus - BigUint::from_str("1").unwrap()) >> 1, limbs); - let legendre_impl = quote! { - fn legendre(&self) -> ::ff::LegendreSymbol { - // s = self^((modulus - 1) // 2) - let s = self.pow(#mod_minus_1_over_2); - if s == Self::zero() { - ::ff::LegendreSymbol::Zero - } else if s == Self::one() { - ::ff::LegendreSymbol::QuadraticResidue - } else { - ::ff::LegendreSymbol::QuadraticNonResidue + let sqrt_impl = if (modulus % BigUint::from_str("4").unwrap()) + == BigUint::from_str("3").unwrap() + { + let mod_plus_1_over_4 = + biguint_to_u64_vec((modulus + BigUint::from_str("1").unwrap()) >> 2, limbs); + + quote! { + impl ::ff::SqrtField for #name { + fn sqrt(&self) -> ::subtle::CtOption { + use ::subtle::ConstantTimeEq; + + // Because r = 3 (mod 4) + // sqrt can be done with only one exponentiation, + // via the computation of self^((r + 1) // 4) (mod r) + let sqrt = self.pow_vartime(#mod_plus_1_over_4); + + ::subtle::CtOption::new( + sqrt, + (sqrt * &sqrt).ct_eq(self), // Only return Some if it's the square root. + ) + } } } + } else if (modulus % BigUint::from_str("16").unwrap()) == BigUint::from_str("1").unwrap() { + let t_minus_1_over_2 = biguint_to_u64_vec((&t - BigUint::one()) >> 1, limbs); + + quote! { + impl ::ff::SqrtField for #name { + fn sqrt(&self) -> ::subtle::CtOption { + // Tonelli-Shank's algorithm for q mod 16 = 1 + // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + use ::subtle::{ConditionallySelectable, ConstantTimeEq}; + + // w = self^((t - 1) // 2) + let w = self.pow_vartime(#t_minus_1_over_2); + + let mut v = S; + let mut x = *self * &w; + let mut b = x * &w; + + // Initialize z as the 2^S root of unity. + let mut z = #name(ROOT_OF_UNITY); + + for max_v in (1..=S).rev() { + let mut k = 1; + let mut tmp = b.square(); + let mut j_less_than_v: ::subtle::Choice = 1.into(); + + for j in 2..max_v { + let tmp_is_one = tmp.ct_eq(&#name::one()); + let squared = #name::conditional_select(&tmp, &z, tmp_is_one).square(); + tmp = #name::conditional_select(&squared, &tmp, tmp_is_one); + let new_z = #name::conditional_select(&z, &squared, tmp_is_one); + j_less_than_v &= !j.ct_eq(&v); + k = u32::conditional_select(&j, &k, tmp_is_one); + z = #name::conditional_select(&z, &new_z, j_less_than_v); + } + + let result = x * &z; + x = #name::conditional_select(&result, &x, b.ct_eq(&#name::one())); + z = z.square(); + b *= &z; + v = k; + } + + ::subtle::CtOption::new( + x, + (x * &x).ct_eq(self), // Only return Some if it's the square root. + ) + } + } + } + } else { + quote! {} }; - let sqrt_impl = - if (&modulus % BigUint::from_str("4").unwrap()) == BigUint::from_str("3").unwrap() { - let mod_minus_3_over_4 = - biguint_to_u64_vec((&modulus - BigUint::from_str("3").unwrap()) >> 2, limbs); - - // Compute -R as (m - r) - let rneg = biguint_to_u64_vec(&modulus - &r, limbs); - - quote! { - impl ::ff::SqrtField for #name { - #legendre_impl - - fn sqrt(&self) -> Option { - // Shank's algorithm for q mod 4 = 3 - // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) - - let mut a1 = self.pow(#mod_minus_3_over_4); - - let mut a0 = a1; - a0.square(); - a0.mul_assign(self); - - if a0.0 == #repr(#rneg) { - None - } else { - a1.mul_assign(self); - Some(a1) - } - } - } - } - } else if (&modulus % BigUint::from_str("16").unwrap()) == BigUint::from_str("1").unwrap() { - let t_plus_1_over_2 = biguint_to_u64_vec((&t + BigUint::one()) >> 1, limbs); - let t = biguint_to_u64_vec(t.clone(), limbs); - - quote! { - impl ::ff::SqrtField for #name { - #legendre_impl - - fn sqrt(&self) -> Option { - // Tonelli-Shank's algorithm for q mod 16 = 1 - // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) - - match self.legendre() { - ::ff::LegendreSymbol::Zero => Some(*self), - ::ff::LegendreSymbol::QuadraticNonResidue => None, - ::ff::LegendreSymbol::QuadraticResidue => { - let mut c = #name(ROOT_OF_UNITY); - let mut r = self.pow(#t_plus_1_over_2); - let mut t = self.pow(#t); - let mut m = S; - - while t != Self::one() { - let mut i = 1; - { - let mut t2i = t; - t2i.square(); - loop { - if t2i == Self::one() { - break; - } - t2i.square(); - i += 1; - } - } - - for _ in 0..(m - i - 1) { - c.square(); - } - r.mul_assign(&c); - c.square(); - t.mul_assign(&c); - m = i; - } - - Some(r) - } - } - } - } - } - } else { - quote! {} - }; - // Compute R^2 mod m - let r2 = biguint_to_u64_vec((&r * &r) % &modulus, limbs); + let r2 = biguint_to_u64_vec((&r * &r) % modulus, limbs); let r = biguint_to_u64_vec(r, limbs); - let modulus = biguint_to_real_u64_vec(modulus, limbs); + let modulus = biguint_to_real_u64_vec(modulus.clone(), limbs); // Compute -m^-1 mod 2**64 by exponentiating by totient(2**64) - 1 let mut inv = 1u64; @@ -567,6 +533,7 @@ fn prime_field_constants_and_sqrt( fn prime_field_impl( name: &syn::Ident, repr: &syn::Ident, + modulus: &BigUint, limbs: usize, ) -> proc_macro2::TokenStream { // Returns r{n} as an ident. @@ -710,12 +677,14 @@ fn prime_field_impl( let mut mont_calling = proc_macro2::TokenStream::new(); mont_calling.append_separated( - (0..(limbs * 2)).map(|i| get_temp(i)), + (0..(limbs * 2)).map(get_temp), proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone), ); gen.extend(quote! { - self.mont_reduce(#mont_calling); + let mut ret = *self; + ret.mont_reduce(#mont_calling); + ret }); gen @@ -756,7 +725,7 @@ fn prime_field_impl( let mut mont_calling = proc_macro2::TokenStream::new(); mont_calling.append_separated( - (0..(limbs * 2)).map(|i| get_temp(i)), + (0..(limbs * 2)).map(get_temp), proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone), ); @@ -767,10 +736,44 @@ fn prime_field_impl( gen } + /// Generates an implementation of multiplicative inversion within the target prime + /// field. + fn inv_impl( + a: proc_macro2::TokenStream, + name: &syn::Ident, + modulus: &BigUint, + limbs: usize, + ) -> proc_macro2::TokenStream { + let mod_minus_2 = biguint_to_u64_vec(modulus - BigUint::from(2u64), limbs); + + // TODO: Improve on this by computing an addition chain for mod_minus_two + quote! { + use ::subtle::ConstantTimeEq; + + // By Euler's theorem, if `a` is coprime to `p` (i.e. `gcd(a, p) = 1`), then: + // a^-1 ≡ a^(phi(p) - 1) mod p + // + // `ff_derive` requires that `p` is prime; in this case, `phi(p) = p - 1`, and + // thus: + // a^-1 ≡ a^(p - 2) mod p + let inv = #a.pow_vartime(#mod_minus_2); + + ::subtle::CtOption::new(inv, !#a.ct_eq(&#name::zero())) + } + } + let squaring_impl = sqr_impl(quote! {self}, limbs); let multiply_impl = mul_impl(quote! {self}, quote! {other}, limbs); + let invert_impl = inv_impl(quote! {self}, name, modulus, limbs); let montgomery_impl = mont_impl(limbs); + // (self.0).0[0].ct_eq(&(other.0).0[0]) & (self.0).0[1].ct_eq(&(other.0).0[1]) & ... + let mut ct_eq_impl = proc_macro2::TokenStream::new(); + ct_eq_impl.append_separated( + (0..limbs).map(|i| quote! { (self.0).0[#i].ct_eq(&(other.0).0[#i]) }), + proc_macro2::Punct::new('&', proc_macro2::Spacing::Alone), + ); + // (self.0).0[0], (self.0).0[1], ..., 0, 0, 0, 0, ... let mut into_repr_params = proc_macro2::TokenStream::new(); into_repr_params.append_separated( @@ -783,25 +786,37 @@ fn prime_field_impl( let top_limb_index = limbs - 1; quote! { - impl ::std::marker::Copy for #name { } + impl ::core::marker::Copy for #name { } - impl ::std::clone::Clone for #name { + impl ::core::clone::Clone for #name { fn clone(&self) -> #name { *self } } - impl ::std::cmp::PartialEq for #name { + impl ::core::default::Default for #name { + fn default() -> #name { + #name::zero() + } + } + + impl ::subtle::ConstantTimeEq for #name { + fn ct_eq(&self, other: &#name) -> ::subtle::Choice { + #ct_eq_impl + } + } + + impl ::core::cmp::PartialEq for #name { fn eq(&self, other: &#name) -> bool { self.0 == other.0 } } - impl ::std::cmp::Eq for #name { } + impl ::core::cmp::Eq for #name { } - impl ::std::fmt::Debug for #name + impl ::core::fmt::Debug for #name { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "{}({:?})", stringify!(#name), self.into_repr()) } } @@ -809,20 +824,20 @@ fn prime_field_impl( /// Elements are ordered lexicographically. impl Ord for #name { #[inline(always)] - fn cmp(&self, other: &#name) -> ::std::cmp::Ordering { + fn cmp(&self, other: &#name) -> ::core::cmp::Ordering { self.into_repr().cmp(&other.into_repr()) } } impl PartialOrd for #name { #[inline(always)] - fn partial_cmp(&self, other: &#name) -> Option<::std::cmp::Ordering> { + fn partial_cmp(&self, other: &#name) -> Option<::core::cmp::Ordering> { Some(self.cmp(other)) } } - impl ::std::fmt::Display for #name { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + impl ::core::fmt::Display for #name { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "{}({})", stringify!(#name), self.into_repr()) } } @@ -833,6 +848,144 @@ fn prime_field_impl( } } + impl ::subtle::ConditionallySelectable for #name { + fn conditional_select(a: &#name, b: &#name, choice: ::subtle::Choice) -> #name { + let mut res = [0u64; #limbs]; + for i in 0..#limbs { + res[i] = u64::conditional_select(&(a.0).0[i], &(b.0).0[i], choice); + } + #name(#repr(res)) + } + } + + impl ::core::ops::Neg for #name { + type Output = #name; + + #[inline] + fn neg(self) -> #name { + let mut ret = self; + if !ret.is_zero() { + let mut tmp = MODULUS; + tmp.sub_noborrow(&ret.0); + ret.0 = tmp; + } + ret + } + } + + impl<'r> ::core::ops::Add<&'r #name> for #name { + type Output = #name; + + #[inline] + fn add(self, other: &#name) -> #name { + let mut ret = self; + ret.add_assign(other); + ret + } + } + + impl ::core::ops::Add for #name { + type Output = #name; + + #[inline] + fn add(self, other: #name) -> Self { + self + &other + } + } + + impl<'r> ::core::ops::AddAssign<&'r #name> for #name { + #[inline] + fn add_assign(&mut self, other: &#name) { + // This cannot exceed the backing capacity. + self.0.add_nocarry(&other.0); + + // However, it may need to be reduced. + self.reduce(); + } + } + + impl ::core::ops::AddAssign for #name { + #[inline] + fn add_assign(&mut self, other: #name) { + self.add_assign(&other); + } + } + + impl<'r> ::core::ops::Sub<&'r #name> for #name { + type Output = #name; + + #[inline] + fn sub(self, other: &#name) -> Self { + let mut ret = self; + ret.sub_assign(other); + ret + } + } + + impl ::core::ops::Sub for #name { + type Output = #name; + + #[inline] + fn sub(self, other: #name) -> Self { + self - &other + } + } + + impl<'r> ::core::ops::SubAssign<&'r #name> for #name { + #[inline] + fn sub_assign(&mut self, other: &#name) { + // If `other` is larger than `self`, we'll need to add the modulus to self first. + if other.0 > self.0 { + self.0.add_nocarry(&MODULUS); + } + + self.0.sub_noborrow(&other.0); + } + } + + impl ::core::ops::SubAssign for #name { + #[inline] + fn sub_assign(&mut self, other: #name) { + self.sub_assign(&other); + } + } + + impl<'r> ::core::ops::Mul<&'r #name> for #name { + type Output = #name; + + #[inline] + fn mul(self, other: &#name) -> Self { + let mut ret = self; + ret.mul_assign(other); + ret + } + } + + impl ::core::ops::Mul for #name { + type Output = #name; + + #[inline] + fn mul(self, other: #name) -> Self { + self * &other + } + } + + impl<'r> ::core::ops::MulAssign<&'r #name> for #name { + #[inline] + fn mul_assign(&mut self, other: &#name) + { + #multiply_impl + } + } + + impl ::core::ops::MulAssign for #name { + #[inline] + fn mul_assign(&mut self, other: #name) + { + self.mul_assign(&other); + } + } + impl ::ff::PrimeField for #name { type Repr = #repr; @@ -843,7 +996,7 @@ fn prime_field_impl( Ok(r) } else { - Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) + Err(PrimeFieldDecodingError::NotInField) } } @@ -912,95 +1065,20 @@ fn prime_field_impl( } #[inline] - fn add_assign(&mut self, other: &#name) { + fn double(&self) -> Self { + let mut ret = *self; + // This cannot exceed the backing capacity. - self.0.add_nocarry(&other.0); + ret.0.mul2(); // However, it may need to be reduced. - self.reduce(); + ret.reduce(); + + ret } - #[inline] - fn double(&mut self) { - // This cannot exceed the backing capacity. - self.0.mul2(); - - // However, it may need to be reduced. - self.reduce(); - } - - #[inline] - fn sub_assign(&mut self, other: &#name) { - // If `other` is larger than `self`, we'll need to add the modulus to self first. - if other.0 > self.0 { - self.0.add_nocarry(&MODULUS); - } - - self.0.sub_noborrow(&other.0); - } - - #[inline] - fn negate(&mut self) { - if !self.is_zero() { - let mut tmp = MODULUS; - tmp.sub_noborrow(&self.0); - self.0 = tmp; - } - } - - fn inverse(&self) -> Option { - if self.is_zero() { - None - } else { - // Guajardo Kumar Paar Pelzl - // Efficient Software-Implementation of Finite Fields with Applications to Cryptography - // Algorithm 16 (BEA for Inversion in Fp) - - let one = #repr::from(1); - - let mut u = self.0; - let mut v = MODULUS; - let mut b = #name(R2); // Avoids unnecessary reduction step. - let mut c = Self::zero(); - - while u != one && v != one { - while u.is_even() { - u.div2(); - - if b.0.is_even() { - b.0.div2(); - } else { - b.0.add_nocarry(&MODULUS); - b.0.div2(); - } - } - - while v.is_even() { - v.div2(); - - if c.0.is_even() { - c.0.div2(); - } else { - c.0.add_nocarry(&MODULUS); - c.0.div2(); - } - } - - if v < u { - u.sub_noborrow(&v); - b.sub_assign(&c); - } else { - v.sub_noborrow(&u); - c.sub_assign(&b); - } - } - - if u == one { - Some(b) - } else { - Some(c) - } - } + fn invert(&self) -> ::subtle::CtOption { + #invert_impl } #[inline(always)] @@ -1009,13 +1087,7 @@ fn prime_field_impl( } #[inline] - fn mul_assign(&mut self, other: &#name) - { - #multiply_impl - } - - #[inline] - fn square(&mut self) + fn square(&self) -> Self { #squaring_impl } diff --git a/ff/src/lib.rs b/ff/src/lib.rs index 7662783..65c32b2 100644 --- a/ff/src/lib.rs +++ b/ff/src/lib.rs @@ -1,20 +1,50 @@ //! This crate provides traits for working with finite fields. // Catch documentation errors caused by code changes. +#![no_std] #![deny(intra_doc_link_resolution_failure)] #![allow(unused_imports)] +#[cfg(feature = "std")] +#[macro_use] +extern crate std; + #[cfg(feature = "derive")] pub use ff_derive::*; +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use rand_core::RngCore; -use std::error::Error; -use std::fmt; +#[cfg(feature = "std")] use std::io::{self, Read, Write}; +use subtle::{ConditionallySelectable, CtOption}; /// This trait represents an element of a field. pub trait Field: - Sized + Eq + Copy + Clone + Send + Sync + fmt::Debug + fmt::Display + 'static + Sized + + Eq + + Copy + + Clone + + Default + + Send + + Sync + + fmt::Debug + + fmt::Display + + 'static + + ConditionallySelectable + + Add + + Sub + + Mul + + Neg + + for<'a> Add<&'a Self, Output = Self> + + for<'a> Mul<&'a Self, Output = Self> + + for<'a> Sub<&'a Self, Output = Self> + + MulAssign + + AddAssign + + SubAssign + + for<'a> MulAssign<&'a Self> + + for<'a> AddAssign<&'a Self> + + for<'a> SubAssign<&'a Self> { /// Returns an element chosen uniformly at random using a user-provided RNG. fn random(rng: &mut R) -> Self; @@ -29,46 +59,35 @@ pub trait Field: fn is_zero(&self) -> bool; /// Squares this element. - fn square(&mut self); + #[must_use] + fn square(&self) -> Self; /// Doubles this element. - fn double(&mut self); + #[must_use] + fn double(&self) -> Self; - /// Negates this element. - fn negate(&mut self); - - /// Adds another element to this element. - fn add_assign(&mut self, other: &Self); - - /// Subtracts another element from this element. - fn sub_assign(&mut self, other: &Self); - - /// Multiplies another element by this element. - fn mul_assign(&mut self, other: &Self); - - /// Computes the multiplicative inverse of this element, if nonzero. - fn inverse(&self) -> Option; + /// Computes the multiplicative inverse of this element, + /// failing if the element is zero. + fn invert(&self) -> CtOption; /// Exponentiates this element by a power of the base prime modulus via /// the Frobenius automorphism. fn frobenius_map(&mut self, power: usize); - /// Exponentiates this element by a number represented with `u64` limbs, - /// least significant digit first. - fn pow>(&self, exp: S) -> Self { + /// Exponentiates `self` by `exp`, where `exp` is a little-endian order + /// integer exponent. + /// + /// **This operation is variable time with respect to the exponent.** If the + /// exponent is fixed, this operation is effectively constant time. + fn pow_vartime>(&self, exp: S) -> Self { let mut res = Self::one(); + for e in exp.as_ref().iter().rev() { + for i in (0..64).rev() { + res = res.square(); - let mut found_one = false; - - for i in BitIterator::new(exp) { - if found_one { - res.square(); - } else { - found_one = i; - } - - if i { - res.mul_assign(self); + if ((*e >> i) & 1) == 1 { + res.mul_assign(self); + } } } @@ -78,12 +97,9 @@ pub trait Field: /// This trait represents an element of a field that has a square root operation described for it. pub trait SqrtField: Field { - /// Returns the Legendre symbol of the field element. - fn legendre(&self) -> LegendreSymbol; - /// Returns the square root of the field element, if it is /// quadratic residue. - fn sqrt(&self) -> Option; + fn sqrt(&self) -> CtOption; } /// This trait represents a wrapper around a biginteger which can encode any element of a particular @@ -139,6 +155,7 @@ pub trait PrimeFieldRepr: fn shl(&mut self, amt: u32); /// Writes this `PrimeFieldRepr` as a big endian integer. + #[cfg(feature = "std")] fn write_be(&self, mut writer: W) -> io::Result<()> { use byteorder::{BigEndian, WriteBytesExt}; @@ -150,6 +167,7 @@ pub trait PrimeFieldRepr: } /// Reads a big endian integer into this representation. + #[cfg(feature = "std")] fn read_be(&mut self, mut reader: R) -> io::Result<()> { use byteorder::{BigEndian, ReadBytesExt}; @@ -161,6 +179,7 @@ pub trait PrimeFieldRepr: } /// Writes this `PrimeFieldRepr` as a little endian integer. + #[cfg(feature = "std")] fn write_le(&self, mut writer: W) -> io::Result<()> { use byteorder::{LittleEndian, WriteBytesExt}; @@ -172,6 +191,7 @@ pub trait PrimeFieldRepr: } /// Reads a little endian integer into this representation. + #[cfg(feature = "std")] fn read_le(&mut self, mut reader: R) -> io::Result<()> { use byteorder::{LittleEndian, ReadBytesExt}; @@ -183,25 +203,19 @@ pub trait PrimeFieldRepr: } } -#[derive(Debug, PartialEq)] -pub enum LegendreSymbol { - Zero = 0, - QuadraticResidue = 1, - QuadraticNonResidue = -1, -} - /// An error that may occur when trying to interpret a `PrimeFieldRepr` as a /// `PrimeField` element. #[derive(Debug)] pub enum PrimeFieldDecodingError { /// The encoded value is not in the field - NotInField(String), + NotInField, } -impl Error for PrimeFieldDecodingError { +#[cfg(feature = "std")] +impl std::error::Error for PrimeFieldDecodingError { fn description(&self) -> &str { match *self { - PrimeFieldDecodingError::NotInField(..) => "not an element of the field", + PrimeFieldDecodingError::NotInField => "not an element of the field", } } } @@ -209,9 +223,7 @@ impl Error for PrimeFieldDecodingError { impl fmt::Display for PrimeFieldDecodingError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match *self { - PrimeFieldDecodingError::NotInField(ref repr) => { - write!(f, "{} is not an element of the field", repr) - } + PrimeFieldDecodingError::NotInField => write!(f, "not an element of the field"), } } } @@ -330,7 +342,7 @@ impl> Iterator for BitIterator { #[test] fn test_bit_iterator() { - let mut a = BitIterator::new([0xa953d79b83f6ab59, 0x6dea2059e200bd39]); + let mut a = BitIterator::new([0xa953_d79b_83f6_ab59, 0x6dea_2059_e200_bd39]); let expected = "01101101111010100010000001011001111000100000000010111101001110011010100101010011110101111001101110000011111101101010101101011001"; for e in expected.chars() { @@ -342,10 +354,10 @@ fn test_bit_iterator() { let expected = "1010010101111110101010000101101011101000011101110101001000011001100100100011011010001011011011010001011011101100110100111011010010110001000011110100110001100110011101101000101100011100100100100100001010011101010111110011101011000011101000111011011101011001"; let mut a = BitIterator::new([ - 0x429d5f3ac3a3b759, - 0xb10f4c66768b1c92, - 0x92368b6d16ecd3b4, - 0xa57ea85ae8775219, + 0x429d_5f3a_c3a3_b759, + 0xb10f_4c66_768b_1c92, + 0x9236_8b6d_16ec_d3b4, + 0xa57e_a85a_e877_5219, ]); for e in expected.chars() { diff --git a/group/src/lib.rs b/group/src/lib.rs index 1c5d03e..34c8ac2 100644 --- a/group/src/lib.rs +++ b/group/src/lib.rs @@ -5,16 +5,46 @@ use ff::{PrimeField, PrimeFieldDecodingError, ScalarEngine, SqrtField}; use rand::RngCore; use std::error::Error; use std::fmt; +use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; pub mod tests; mod wnaf; pub use self::wnaf::Wnaf; +/// A helper trait for types implementing group addition. +pub trait CurveOps: + Add + Sub + AddAssign + SubAssign +{ +} + +impl CurveOps for T where + T: Add + Sub + AddAssign + SubAssign +{ +} + +/// A helper trait for references implementing group addition. +pub trait CurveOpsOwned: for<'r> CurveOps<&'r Rhs, Output> {} +impl CurveOpsOwned for T where T: for<'r> CurveOps<&'r Rhs, Output> {} + /// Projective representation of an elliptic curve point guaranteed to be /// in the correct prime order subgroup. pub trait CurveProjective: - PartialEq + Eq + Sized + Copy + Clone + Send + Sync + fmt::Debug + fmt::Display + 'static + PartialEq + + Eq + + Sized + + Copy + + Clone + + Send + + Sync + + fmt::Debug + + fmt::Display + + 'static + + Neg + + CurveOps + + CurveOpsOwned + + CurveOps<::Affine> + + CurveOpsOwned<::Affine> { type Engine: ScalarEngine; type Scalar: PrimeField + SqrtField; @@ -44,22 +74,6 @@ pub trait CurveProjective: /// Doubles this element. fn double(&mut self); - /// Adds another element to this element. - fn add_assign(&mut self, other: &Self); - - /// Subtracts another element from this element. - fn sub_assign(&mut self, other: &Self) { - let mut tmp = *other; - tmp.negate(); - self.add_assign(&tmp); - } - - /// Adds an affine element to this element. - fn add_assign_mixed(&mut self, other: &Self::Affine); - - /// Negates this element. - fn negate(&mut self); - /// Performs scalar multiplication of this element. fn mul_assign::Repr>>(&mut self, other: S); @@ -78,7 +92,17 @@ pub trait CurveProjective: /// Affine representation of an elliptic curve point guaranteed to be /// in the correct prime order subgroup. pub trait CurveAffine: - Copy + Clone + Sized + Send + Sync + fmt::Debug + fmt::Display + PartialEq + Eq + 'static + Copy + + Clone + + Sized + + Send + + Sync + + fmt::Debug + + fmt::Display + + PartialEq + + Eq + + 'static + + Neg { type Engine: ScalarEngine; type Scalar: PrimeField + SqrtField; @@ -97,9 +121,6 @@ pub trait CurveAffine: /// additive identity. fn is_zero(&self) -> bool; - /// Negates this element. - fn negate(&mut self); - /// Performs scalar multiplication of this element with mixed addition. fn mul::Repr>>(&self, other: S) -> Self::Projective; diff --git a/group/src/tests/mod.rs b/group/src/tests/mod.rs index 1970667..c53ba76 100644 --- a/group/src/tests/mod.rs +++ b/group/src/tests/mod.rs @@ -1,6 +1,7 @@ use ff::{Field, PrimeField}; use rand::SeedableRng; use rand_xorshift::XorShiftRng; +use std::ops::Neg; use crate::{CurveAffine, CurveProjective, EncodedPoint}; @@ -12,8 +13,7 @@ pub fn curve_tests() { // Negation edge case with zero. { - let mut z = G::zero(); - z.negate(); + let z = G::zero().neg(); assert!(z.is_zero()); } @@ -30,19 +30,19 @@ pub fn curve_tests() { let rcopy = r; r.add_assign(&G::zero()); assert_eq!(r, rcopy); - r.add_assign_mixed(&G::Affine::zero()); + r.add_assign(&G::Affine::zero()); assert_eq!(r, rcopy); let mut z = G::zero(); z.add_assign(&G::zero()); assert!(z.is_zero()); - z.add_assign_mixed(&G::Affine::zero()); + z.add_assign(&G::Affine::zero()); assert!(z.is_zero()); let mut z2 = z; z2.add_assign(&r); - z.add_assign_mixed(&r.into_affine()); + z.add_assign(&r.into_affine()); assert_eq!(z, z2); assert_eq!(z, r); @@ -67,7 +67,7 @@ pub fn curve_tests() { random_negation_tests::(); random_transformation_tests::(); random_wnaf_tests::(); - random_encoding_tests::(); + random_encoding_tests::(); } fn random_wnaf_tests() { @@ -199,8 +199,7 @@ fn random_negation_tests() { let r = G::random(&mut rng); let s = G::Scalar::random(&mut rng); - let mut sneg = s; - sneg.negate(); + let sneg = s.neg(); let mut t1 = r; t1.mul_assign(s); @@ -213,11 +212,10 @@ fn random_negation_tests() { assert!(t3.is_zero()); let mut t4 = t1; - t4.add_assign_mixed(&t2.into_affine()); + t4.add_assign(&t2.into_affine()); assert!(t4.is_zero()); - t1.negate(); - assert_eq!(t1, t2); + assert_eq!(t1.neg(), t2); } } @@ -244,7 +242,7 @@ fn random_doubling_tests() { tmp2.add_assign(&b); let mut tmp3 = a; - tmp3.add_assign_mixed(&b.into_affine()); + tmp3.add_assign(&b.into_affine()); assert_eq!(tmp1, tmp2); assert_eq!(tmp1, tmp3); @@ -306,7 +304,7 @@ fn random_addition_tests() { aplusa.add_assign(&a); let mut aplusamixed = a; - aplusamixed.add_assign_mixed(&a.into_affine()); + aplusamixed.add_assign(&a.into_affine()); let mut adouble = a; adouble.double(); @@ -336,18 +334,18 @@ fn random_addition_tests() { // (a + b) + c tmp[3] = a_affine.into_projective(); - tmp[3].add_assign_mixed(&b_affine); - tmp[3].add_assign_mixed(&c_affine); + tmp[3].add_assign(&b_affine); + tmp[3].add_assign(&c_affine); // a + (b + c) tmp[4] = b_affine.into_projective(); - tmp[4].add_assign_mixed(&c_affine); - tmp[4].add_assign_mixed(&a_affine); + tmp[4].add_assign(&c_affine); + tmp[4].add_assign(&a_affine); // (a + c) + b tmp[5] = a_affine.into_projective(); - tmp[5].add_assign_mixed(&c_affine); - tmp[5].add_assign_mixed(&b_affine); + tmp[5].add_assign(&c_affine); + tmp[5].add_assign(&b_affine); // Comparisons for i in 0..6 { @@ -413,24 +411,24 @@ fn random_transformation_tests() { } } -fn random_encoding_tests() { +fn random_encoding_tests() { let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, ]); assert_eq!( - G::zero().into_uncompressed().into_affine().unwrap(), - G::zero() + G::Affine::zero().into_uncompressed().into_affine().unwrap(), + G::Affine::zero() ); assert_eq!( - G::zero().into_compressed().into_affine().unwrap(), - G::zero() + G::Affine::zero().into_compressed().into_affine().unwrap(), + G::Affine::zero() ); for _ in 0..1000 { - let mut r = G::Projective::random(&mut rng).into_affine(); + let mut r = G::random(&mut rng).into_affine(); let uncompressed = r.into_uncompressed(); let de_uncompressed = uncompressed.into_affine().unwrap(); @@ -440,7 +438,7 @@ fn random_encoding_tests() { let de_compressed = compressed.into_affine().unwrap(); assert_eq!(de_compressed, r); - r.negate(); + r = r.neg(); let compressed = r.into_compressed(); let de_compressed = compressed.into_affine().unwrap(); diff --git a/jubjub/.github/workflows/ci.yml b/jubjub/.github/workflows/ci.yml new file mode 100644 index 0000000..5d0efb3 --- /dev/null +++ b/jubjub/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +name: CI checks + +on: [push, pull_request] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + + # Ensure all code has been formatted with rustfmt + - run: rustup component add rustfmt + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check --color always + + test: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build tests + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --tests + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release + + no-std: + name: Check no-std compatibility + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + - run: rustup target add thumbv6m-none-eabi + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --target thumbv6m-none-eabi --no-default-features + + doc-links: + name: Nightly lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + + # Ensure intra-documentation links all resolve correctly + # Requires #![deny(intra_doc_link_resolution_failure)] in crate. + - name: Check intra-doc links + uses: actions-rs/cargo@v1 + with: + command: doc + args: --document-private-items diff --git a/jubjub/.gitignore b/jubjub/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/jubjub/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/jubjub/COPYRIGHT b/jubjub/COPYRIGHT new file mode 100644 index 0000000..aaca1cc --- /dev/null +++ b/jubjub/COPYRIGHT @@ -0,0 +1,14 @@ +Copyrights in the "jubjub" library are retained by their contributors. No +copyright assignment is required to contribute to the "jubjub" library. + +The "jubjub" library is licensed under either of + + * Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/jubjub/Cargo.toml b/jubjub/Cargo.toml new file mode 100644 index 0000000..31221ef --- /dev/null +++ b/jubjub/Cargo.toml @@ -0,0 +1,49 @@ +[package] +authors = [ + "Sean Bowe ", + "Eirik Ogilvie-Wigley ", + "Jack Grigg ", +] +description = "Implementation of the Jubjub elliptic curve group" +documentation = "https://docs.rs/jubjub/" +homepage = "https://github.com/zkcrypto/jubjub" +license = "MIT/Apache-2.0" +name = "jubjub" +repository = "https://github.com/zkcrypto/jubjub" +version = "0.3.0" +edition = "2018" + +[dependencies.bls12_381] +path = "../bls12_381" +version = "0.1" +default-features = false + +[dependencies.subtle] +version = "^2.2.1" +default-features = false + +[dev-dependencies] +criterion = "0.3" + +[dev-dependencies.rand_core] +version = "0.5" +default-features = false + +[dev-dependencies.rand_xorshift] +version = "0.2" +default-features = false + +[features] +default = [] + +[[bench]] +name = "fq_bench" +harness = false + +[[bench]] +name = "fr_bench" +harness = false + +[[bench]] +name = "point_bench" +harness = false diff --git a/jubjub/LICENSE-APACHE b/jubjub/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/jubjub/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/jubjub/LICENSE-MIT b/jubjub/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/jubjub/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/jubjub/README.md b/jubjub/README.md new file mode 100644 index 0000000..da5bd53 --- /dev/null +++ b/jubjub/README.md @@ -0,0 +1,53 @@ +# jubjub [![Crates.io](https://img.shields.io/crates/v/jubjub.svg)](https://crates.io/crates/jubjub) # + + + +This is a pure Rust implementation of the Jubjub elliptic curve group and its associated fields. + +* **This implementation has not been reviewed or audited. Use at your own risk.** +* This implementation targets Rust `1.36` or later. +* All operations are constant time unless explicitly noted. +* This implementation does not require the Rust standard library. + +## [Documentation](https://docs.rs/jubjub) + +## Curve Description + +Jubjub is the [twisted Edwards curve](https://en.wikipedia.org/wiki/Twisted_Edwards_curve) `-u^2 + v^2 = 1 + d.u^2.v^2` of rational points over `GF(q)` with a subgroup of prime order `r` and cofactor `8`. + +``` +q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +r = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7 +d = -(10240/10241) +``` + +The choice of `GF(q)` is made to be the scalar field of the BLS12-381 elliptic curve construction. + +Jubjub is birationally equivalent to a [Montgomery curve](https://en.wikipedia.org/wiki/Montgomery_curve) `y^2 = x^3 + Ax^2 + x` over the same field with `A = 40962`. This value of `A` is the smallest integer such that `(A - 2) / 4` is a small integer, `A^2 - 4` is nonsquare in `GF(q)`, and the Montgomery curve and its quadratic twist have small cofactors `8` and `4`, respectively. This is identical to the relationship between Curve25519 and ed25519. + +Please see [./doc/evidence/](./doc/evidence/) for supporting evidence that Jubjub meets the [SafeCurves](https://safecurves.cr.yp.to/index.html) criteria. The tool in [./doc/derive/](./doc/derive/) will derive the curve parameters via the above criteria to demonstrate rigidity. + +## Acknowledgements + +Jubjub was designed by Sean Bowe. Daira Hopwood is responsible for its name and specification. The security evidence in [./doc/evidence/](./doc/evidence/) is the product of Daira Hopwood and based on SafeCurves by Daniel J. Bernstein and Tanja Lange. Peter Newell and Daira Hopwood are responsible for the Jubjub bird image. + +Please see `Cargo.toml` for a list of primary authors of this codebase. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/jubjub/RELEASES.md b/jubjub/RELEASES.md new file mode 100644 index 0000000..45db61c --- /dev/null +++ b/jubjub/RELEASES.md @@ -0,0 +1,24 @@ +# 0.3.0 + +This release now depends on the `bls12_381` crate, which exposes the `Fq` field type that we re-export. + +* The `Fq` and `Fr` field types now have better constant function support for various operations and constructors. +* We no longer depend on the `byteorder` crate. +* We've bumped our `rand_core` dev-dependency up to 0.5. +* We've removed the `std` and `nightly` features. +* We've bumped our dependency of `subtle` up to `^2.2.1`. + +# 0.2.0 + +This release switches to `subtle 2.1` to bring in the `CtOption` type, and also makes a few useful API changes. + +* Implemented `Mul` for `AffineNielsPoint` and `ExtendedNielsPoint` +* Changed `AffinePoint::to_niels()` to be a `const` function so that constant curve points can be constructed without statics. +* Implemented `multiply_bits` for `AffineNielsPoint`, `ExtendedNielsPoint` +* Removed `CtOption` and replaced it with `CtOption` from `subtle` crate. +* Modified receivers of some methods to reduce stack usage +* Changed various `into_bytes` methods into `to_bytes` + +# 0.1.0 + +Initial release. diff --git a/jubjub/benches/fq_bench.rs b/jubjub/benches/fq_bench.rs new file mode 100644 index 0000000..65eceaf --- /dev/null +++ b/jubjub/benches/fq_bench.rs @@ -0,0 +1,58 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use jubjub::*; + +fn bench_add_assign(c: &mut Criterion) { + let mut n = Fq::one(); + let neg_one = -Fq::one(); + c.bench_function("Fq add_assign", |b| { + b.iter(move || { + n += &neg_one; + }) + }); +} + +fn bench_sub_assign(c: &mut Criterion) { + let mut n = Fq::one(); + let neg_one = -Fq::one(); + c.bench_function("Fq sub_assign", |b| { + b.iter(move || { + n -= &neg_one; + }) + }); +} + +fn bench_mul_assign(c: &mut Criterion) { + let mut n = Fq::one(); + let neg_one = -Fq::one(); + c.bench_function("Fq mul_assign", |b| { + b.iter(move || { + n *= &neg_one; + }) + }); +} + +fn bench_square(c: &mut Criterion) { + let n = Fq::one(); + c.bench_function("Fq square", |b| b.iter(move || n.square())); +} + +fn bench_invert(c: &mut Criterion) { + let n = Fq::one(); + c.bench_function("Fq invert", |b| b.iter(move || n.invert())); +} + +fn bench_sqrt(c: &mut Criterion) { + let n = Fq::one().double().double(); + c.bench_function("Fq sqrt", |b| b.iter(move || n.sqrt())); +} + +criterion_group!( + benches, + bench_add_assign, + bench_sub_assign, + bench_mul_assign, + bench_square, + bench_invert, + bench_sqrt, +); +criterion_main!(benches); diff --git a/jubjub/benches/fr_bench.rs b/jubjub/benches/fr_bench.rs new file mode 100644 index 0000000..8dc9ce2 --- /dev/null +++ b/jubjub/benches/fr_bench.rs @@ -0,0 +1,58 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use jubjub::*; + +fn bench_add_assign(c: &mut Criterion) { + let mut n = Fr::one(); + let neg_one = -Fr::one(); + c.bench_function("Fr add_assign", |b| { + b.iter(move || { + n += &neg_one; + }) + }); +} + +fn bench_sub_assign(c: &mut Criterion) { + let mut n = Fr::one(); + let neg_one = -Fr::one(); + c.bench_function("Fr sub_assign", |b| { + b.iter(move || { + n -= &neg_one; + }) + }); +} + +fn bench_mul_assign(c: &mut Criterion) { + let mut n = Fr::one(); + let neg_one = -Fr::one(); + c.bench_function("Fr mul_assign", |b| { + b.iter(move || { + n *= &neg_one; + }) + }); +} + +fn bench_square(c: &mut Criterion) { + let n = Fr::one(); + c.bench_function("Fr square", |b| b.iter(move || n.square())); +} + +fn bench_invert(c: &mut Criterion) { + let n = Fr::one(); + c.bench_function("Fr invert", |b| b.iter(move || n.invert())); +} + +fn bench_sqrt(c: &mut Criterion) { + let n = Fr::one().double().double(); + c.bench_function("Fr sqrt", |b| b.iter(move || n.sqrt())); +} + +criterion_group!( + benches, + bench_add_assign, + bench_sub_assign, + bench_mul_assign, + bench_square, + bench_invert, + bench_sqrt, +); +criterion_main!(benches); diff --git a/jubjub/benches/point_bench.rs b/jubjub/benches/point_bench.rs new file mode 100644 index 0000000..1659ea5 --- /dev/null +++ b/jubjub/benches/point_bench.rs @@ -0,0 +1,73 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use jubjub::*; + +// Non-Niels + +fn bench_point_doubling(c: &mut Criterion) { + let a = ExtendedPoint::identity(); + c.bench_function("Jubjub point doubling", |bencher| { + bencher.iter(move || a.double()) + }); +} + +fn bench_point_addition(c: &mut Criterion) { + let a = ExtendedPoint::identity(); + let b = -ExtendedPoint::identity(); + c.bench_function("Jubjub point addition", |bencher| { + bencher.iter(move || a + b) + }); +} + +fn bench_point_subtraction(c: &mut Criterion) { + let a = ExtendedPoint::identity(); + let b = -ExtendedPoint::identity(); + c.bench_function("Jubjub point subtraction", |bencher| { + bencher.iter(move || a + b) + }); +} + +// Niels + +fn bench_cached_point_addition(c: &mut Criterion) { + let a = ExtendedPoint::identity(); + let b = ExtendedPoint::identity().to_niels(); + c.bench_function("Jubjub cached point addition", |bencher| { + bencher.iter(move || a + b) + }); +} + +fn bench_cached_point_subtraction(c: &mut Criterion) { + let a = ExtendedPoint::identity(); + let b = ExtendedPoint::identity().to_niels(); + c.bench_function("Jubjub cached point subtraction", |bencher| { + bencher.iter(move || a + b) + }); +} + +fn bench_cached_affine_point_addition(c: &mut Criterion) { + let a = ExtendedPoint::identity(); + let b = AffinePoint::identity().to_niels(); + c.bench_function("Jubjub cached affine point addition", |bencher| { + bencher.iter(move || a + b) + }); +} + +fn bench_cached_affine_point_subtraction(c: &mut Criterion) { + let a = ExtendedPoint::identity(); + let b = AffinePoint::identity().to_niels(); + c.bench_function("Jubjub cached affine point subtraction", |bencher| { + bencher.iter(move || a + b) + }); +} + +criterion_group!( + benches, + bench_point_doubling, + bench_point_addition, + bench_point_subtraction, + bench_cached_point_addition, + bench_cached_point_subtraction, + bench_cached_affine_point_addition, + bench_cached_affine_point_subtraction, +); +criterion_main!(benches); diff --git a/jubjub/doc/derive/.gitignore b/jubjub/doc/derive/.gitignore new file mode 100644 index 0000000..7c974cf --- /dev/null +++ b/jubjub/doc/derive/.gitignore @@ -0,0 +1 @@ +*.sage.py diff --git a/jubjub/doc/derive/derive.sage b/jubjub/doc/derive/derive.sage new file mode 100644 index 0000000..c0c5310 --- /dev/null +++ b/jubjub/doc/derive/derive.sage @@ -0,0 +1,32 @@ +q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +Fq = GF(q) + +# We wish to find a Montgomery curve with B = 1 and A the smallest such +# that (A - 2) / 4 is a small integer. +def get_A(n): + return (n * 4) + 2 + +# A = 2 is invalid (singular curve), so we start at i = 1 (A = 6) +i = 1 + +while True: + A = Fq(get_A(i)) + i = i + 1 + + # We also want that A^2 - 4 is nonsquare. + if ((A^2) - 4).is_square(): + continue + + ec = EllipticCurve(Fq, [0, A, 0, 1, 0]) + o = ec.order() + + if (o % 8 == 0): + o = o // 8 + if is_prime(o): + twist = ec.quadratic_twist() + otwist = twist.order() + if (otwist % 4 == 0): + otwist = otwist // 4 + if is_prime(otwist): + print "A = %s" % A + exit(0) diff --git a/jubjub/doc/evidence/.gitignore b/jubjub/doc/evidence/.gitignore new file mode 100644 index 0000000..9a0d287 --- /dev/null +++ b/jubjub/doc/evidence/.gitignore @@ -0,0 +1,102 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + diff --git a/jubjub/doc/evidence/LICENSE b/jubjub/doc/evidence/LICENSE new file mode 100644 index 0000000..9e18163 --- /dev/null +++ b/jubjub/doc/evidence/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 The Zcash developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/jubjub/doc/evidence/README.md b/jubjub/doc/evidence/README.md new file mode 100644 index 0000000..26b2e36 --- /dev/null +++ b/jubjub/doc/evidence/README.md @@ -0,0 +1,28 @@ +Jubjub supporting evidence +-------------------------- + +This repository contains supporting evidence that the twisted Edwards curve +-x^2 + y^2 = 1 - (10240/10241).x^2.y^2 of rational points over +GF(52435875175126190479447740508185965837690552500527637822603658699938581184513), +[also called "Jubjub"](https://z.cash/technology/jubjub.html), +satisfies the [SafeCurves criteria](https://safecurves.cr.yp.to/index.html). + +The script ``verify.sage`` is based on +[this script from the SafeCurves site](https://safecurves.cr.yp.to/verify.html), +modified + +* to support twisted Edwards curves; +* to generate a file 'primes' containing the primes needed for primality proofs, + if it is not already present; +* to change the directory in which Pocklington proof files are generated + (``proof/`` rather than ``../../../proof``), and to create that directory + if it does not exist. + +Prerequisites: + +* apt-get install sagemath +* pip install sortedcontainers + +Run ``sage verify.sage .``, or ``./run.sh`` to also print out the results. + +Note that the "rigidity" criterion cannot be checked automatically. diff --git a/jubjub/doc/evidence/a b/jubjub/doc/evidence/a new file mode 100644 index 0000000..3a2e3f4 --- /dev/null +++ b/jubjub/doc/evidence/a @@ -0,0 +1 @@ +-1 diff --git a/jubjub/doc/evidence/d b/jubjub/doc/evidence/d new file mode 100644 index 0000000..767309a --- /dev/null +++ b/jubjub/doc/evidence/d @@ -0,0 +1 @@ +19257038036680949359750312669786877991949435402254120286184196891950884077233 diff --git a/jubjub/doc/evidence/l b/jubjub/doc/evidence/l new file mode 100644 index 0000000..83f92d5 --- /dev/null +++ b/jubjub/doc/evidence/l @@ -0,0 +1 @@ +6554484396890773809930967563523245729705921265872317281365359162392183254199 diff --git a/jubjub/doc/evidence/p b/jubjub/doc/evidence/p new file mode 100644 index 0000000..1dc0557 --- /dev/null +++ b/jubjub/doc/evidence/p @@ -0,0 +1 @@ +52435875175126190479447740508185965837690552500527637822603658699938581184513 diff --git a/jubjub/doc/evidence/rigid b/jubjub/doc/evidence/rigid new file mode 100644 index 0000000..e560e40 --- /dev/null +++ b/jubjub/doc/evidence/rigid @@ -0,0 +1 @@ +fully rigid diff --git a/jubjub/doc/evidence/run.sh b/jubjub/doc/evidence/run.sh new file mode 100644 index 0000000..817f2fa --- /dev/null +++ b/jubjub/doc/evidence/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh +sage verify.sage . +grep -Rn '.' verify-* |grep '^verify-.*:1:' |sed 's/:1:/ = /' + diff --git a/jubjub/doc/evidence/shape b/jubjub/doc/evidence/shape new file mode 100644 index 0000000..796f74d --- /dev/null +++ b/jubjub/doc/evidence/shape @@ -0,0 +1 @@ +tedwards diff --git a/jubjub/doc/evidence/verify.sage b/jubjub/doc/evidence/verify.sage new file mode 100644 index 0000000..1717c0b --- /dev/null +++ b/jubjub/doc/evidence/verify.sage @@ -0,0 +1,444 @@ +import os +import sys +from errno import ENOENT, EEXIST +from sortedcontainers import SortedSet + + +def readfile(fn): + fd = open(fn,'r') + r = fd.read() + fd.close() + return r + +def writefile(fn,s): + fd = open(fn,'w') + fd.write(s) + fd.close() + +def expand2(n): + s = "" + + while n != 0: + j = 16 + while 2**j < abs(n): j += 1 + if 2**j - abs(n) > abs(n) - 2**(j-1): j -= 1 + + if abs(abs(n) - 2**j) > 2**(j - 10): + if n > 0: + if s != "": s += " + " + s += str(n) + else: + s += " - " + str(-n) + n = 0 + elif n > 0: + if s != "": s += " + " + s += "2^" + str(j) + n -= 2**j + else: + s += " - 2^" + str(j) + n += 2**j + + return s + +def requirement(fn,istrue): + writefile(fn,str(istrue) + '\n') + return istrue + +def verify(): + try: + os.mkdir('proof') + except OSError as e: + if e.errno != EEXIST: raise + + try: + s = set(map(Integer, readfile('primes').split())) + except IOError, e: + if e.errno != ENOENT: raise + s = set() + + needtofactor = SortedSet() + V = SortedSet() # distinct verified primes + verify_primes(V, s, needtofactor) + verify_pass(V, needtofactor) + + old = V + needtofactor.update(V) + while len(needtofactor) > len(old): + k = len(needtofactor) - len(old) + sys.stdout.write('Factoring %d integer%s' % (k, '' if k == 1 else 's')) + sys.stdout.flush() + for x in needtofactor: + if x not in old: + for (y, z) in factor(x): + s.add(y) + sys.stdout.write('.') + sys.stdout.flush() + + print('') + + old = needtofactor.copy() + verify_primes(V, s, needtofactor) + + writefile('primes', '\n'.join(map(str, s)) + '\n') + writefile('verify-primes', '\n' + + ''.join(('2\n' if v == 2 else + '%s\n' % (v,v)) for v in V) + + '\n') + + verify_pass(V, needtofactor) + + +def verify_primes(V, s, needtofactor): + for n in sorted(s): + if not n.is_prime() or n in V: continue + needtofactor.add(n-1) + if n == 2: + V.add(n) + continue + for trybase in primes(2,10000): + base = Integers(n)(trybase) + if base^(n-1) != 1: continue + proof = 'Primality proof for n = %s:\n' % n + proof += '

Take b = %s.\n' % base + proof += '

b^(n-1) mod n = 1.\n' + f = factor(1) + for v in reversed(V): + if f.prod()^2 <= n: + if n % v == 1: + u = base^((n-1)/v)-1 + if u.is_unit(): + if v == 2: + proof += '

2 is prime.\n' + else: + proof += '

%s is prime.\n' % (v,v) + proof += '
b^((n-1)/%s)-1 mod n = %s, which is a unit, inverse %s.\n' % (v,u,1/u) + f *= factor(v)^(n-1).valuation(v) + if f.prod()^2 <= n: continue + if n % f.prod() != 1: continue + proof += '

(%s) divides n-1.\n' % f + proof += '

(%s)^2 > n.\n' % f + proof += "

n is prime by Pocklington's theorem.\n" + proof += '\n' + writefile('proof/%s.html' % n,proof) + V.add(n) + break + + +def verify_pass(V, needtofactor): + p = Integer(readfile('p')) + k = GF(p) + kz. = k[] + l = Integer(readfile('l')) + x0 = Integer(readfile('x0')) + y0 = Integer(readfile('y0')) + x1 = Integer(readfile('x1')) + y1 = Integer(readfile('y1')) + shape = readfile('shape').strip() + rigid = readfile('rigid').strip() + + safefield = True + safeeq = True + safebase = True + saferho = True + safetransfer = True + safedisc = True + saferigid = True + safeladder = True + safetwist = True + safecomplete = True + safeind = True + + pstatus = 'Unverified' + if not p.is_prime(): pstatus = 'False' + needtofactor.add(p) + if p in V: pstatus = 'True' + if pstatus != 'True': safefield = False + writefile('verify-pisprime',pstatus + '\n') + + pstatus = 'Unverified' + if not l.is_prime(): pstatus = 'False' + needtofactor.add(l) + if l in V: pstatus = 'True' + if pstatus != 'True': safebase = False + writefile('verify-lisprime',pstatus + '\n') + + writefile('expand2-p','= %s\n' % expand2(p)) + writefile('expand2-l','
= %s\n' % expand2(l)) + + writefile('hex-p',hex(p) + '\n') + writefile('hex-l',hex(l) + '\n') + writefile('hex-x0',hex(x0) + '\n') + writefile('hex-x1',hex(x1) + '\n') + writefile('hex-y0',hex(y0) + '\n') + writefile('hex-y1',hex(y1) + '\n') + + gcdlpis1 = gcd(l,p) == 1 + safetransfer &= requirement('verify-gcdlp1',gcdlpis1) + + writefile('verify-movsafe','Unverified\n') + writefile('verify-embeddingdegree','Unverified\n') + if gcdlpis1 and l.is_prime(): + u = Integers(l)(p) + d = l-1 + needtofactor.add(d) + for v in V: + while d % v == 0: d /= v + if d == 1: + d = l-1 + for v in V: + while d % v == 0: + if u^(d/v) != 1: break + d /= v + safetransfer &= requirement('verify-movsafe',(l-1)/d <= 100) + writefile('verify-embeddingdegree','%s
= (l-1)/%s\n' % (d,(l-1)/d)) + + t = p+1-l*round((p+1)/l) + if l^2 > 16*p: + writefile('verify-trace','%s\n' % t) + f = factor(1) + d = (p+1-t)/l + needtofactor.add(d) + for v in V: + while d % v == 0: + d //= v + f *= factor(v) + writefile('verify-cofactor','%s\n' % f) + else: + writefile('verify-trace','Unverified\n') + writefile('verify-cofactor','Unverified\n') + + D = t^2-4*p + needtofactor.add(D) + for v in V: + while D % v^2 == 0: D /= v^2 + if prod([v for v in V if D % v == 0]) != -D: + writefile('verify-disc','Unverified\n') + writefile('verify-discisbig','Unverified\n') + safedisc = False + else: + f = -prod([factor(v) for v in V if D % v == 0]) + if D % 4 != 1: + D *= 4 + f = factor(4) * f + Dbits = (log(-D)/log(2)).numerical_approx() + writefile('verify-disc','%s
= %s
≈ -2^%.1f\n' % (D,f,Dbits)) + safedisc &= requirement('verify-discisbig',D < -2^100) + + pi4 = 0.78539816339744830961566084581987572105 + rho = log(pi4*l)/log(4) + writefile('verify-rho','%.1f\n' % rho) + saferho &= requirement('verify-rhoabove100',rho.numerical_approx() >= 100) + + twistl = 'Unverified' + d = p+1+t + needtofactor.add(d) + for v in V: + while d % v == 0: d /= v + if d == 1: + d = p+1+t + for v in V: + if d % v == 0: + if twistl == 'Unverified' or v > twistl: twistl = v + + writefile('verify-twistl','%s\n' % twistl) + writefile('verify-twistembeddingdegree','Unverified\n') + writefile('verify-twistmovsafe','Unverified\n') + if twistl == 'Unverified': + writefile('hex-twistl','Unverified\n') + writefile('expand2-twistl','Unverified\n') + writefile('verify-twistcofactor','Unverified\n') + writefile('verify-gcdtwistlp1','Unverified\n') + writefile('verify-twistrho','Unverified\n') + safetwist = False + else: + writefile('hex-twistl',hex(twistl) + '\n') + writefile('expand2-twistl','
= %s\n' % expand2(twistl)) + f = factor(1) + d = (p+1+t)/twistl + needtofactor.add(d) + for v in V: + while d % v == 0: + d //= v + f *= factor(v) + writefile('verify-twistcofactor','%s\n' % f) + gcdtwistlpis1 = gcd(twistl,p) == 1 + safetwist &= requirement('verify-gcdtwistlp1',gcdtwistlpis1) + + movsafe = 'Unverified' + embeddingdegree = 'Unverified' + if gcdtwistlpis1 and twistl.is_prime(): + u = Integers(twistl)(p) + d = twistl-1 + needtofactor.add(d) + for v in V: + while d % v == 0: d /= v + if d == 1: + d = twistl-1 + for v in V: + while d % v == 0: + if u^(d/v) != 1: break + d /= v + safetwist &= requirement('verify-twistmovsafe',(twistl-1)/d <= 100) + writefile('verify-twistembeddingdegree',"%s
= (l'-1)/%s\n" % (d,(twistl-1)/d)) + + rho = log(pi4*twistl)/log(4) + writefile('verify-twistrho','%.1f\n' % rho) + safetwist &= requirement('verify-twistrhoabove100',rho.numerical_approx() >= 100) + + precomp = 0 + joint = l + needtofactor.add(p+1-t) + needtofactor.add(p+1+t) + for v in V: + d1 = p+1-t + d2 = p+1+t + while d1 % v == 0 or d2 % v == 0: + if d1 % v == 0: d1 //= v + if d2 % v == 0: d2 //= v + # best case for attack: cyclic; each power is usable + # also assume that kangaroo is as efficient as rho + if v + sqrt(pi4*joint/v) < sqrt(pi4*joint): + precomp += v + joint /= v + + rho = log(precomp + sqrt(pi4 * joint))/log(2) + writefile('verify-jointrho','%.1f\n' % rho) + safetwist &= requirement('verify-jointrhoabove100',rho.numerical_approx() >= 100) + + + x0 = k(x0) + y0 = k(y0) + x1 = k(x1) + y1 = k(y1) + + if shape in ('edwards', 'tedwards'): + d = Integer(readfile('d')) + a = 1 + if shape == 'tedwards': + a = Integer(readfile('a')) + + writefile('verify-shape','Twisted Edwards\n') + writefile('verify-equation','%sx^2+y^2 = 1%+dx^2y^2\n' % (a, d)) + if a == 1: + writefile('verify-shape','Edwards\n') + writefile('verify-equation','x^2+y^2 = 1%+dx^2y^2\n' % d) + + a = k(a) + d = k(d) + elliptic = a*d*(a-d) + level0 = a*x0^2+y0^2-1-d*x0^2*y0^2 + level1 = a*x1^2+y1^2-1-d*x1^2*y1^2 + + if shape == 'montgomery': + writefile('verify-shape','Montgomery\n') + A = Integer(readfile('A')) + B = Integer(readfile('B')) + equation = '%sy^2 = x^3%+dx^2+x' % (B,A) + if B == 1: + equation = 'y^2 = x^3%+dx^2+x' % A + writefile('verify-equation',equation + '\n') + + A = k(A) + B = k(B) + elliptic = B*(A^2-4) + level0 = B*y0^2-x0^3-A*x0^2-x0 + level1 = B*y1^2-x1^3-A*x1^2-x1 + + if shape == 'shortw': + writefile('verify-shape','short Weierstrass\n') + a = Integer(readfile('a')) + b = Integer(readfile('b')) + writefile('verify-equation','y^2 = x^3%+dx%+d\n' % (a,b)) + + a = k(a) + b = k(b) + elliptic = 4*a^3+27*b^2 + level0 = y0^2-x0^3-a*x0-b + level1 = y1^2-x1^3-a*x1-b + + writefile('verify-elliptic',str(elliptic) + '\n') + safeeq &= requirement('verify-iselliptic',elliptic != 0) + safebase &= requirement('verify-isoncurve0',level0 == 0) + safebase &= requirement('verify-isoncurve1',level1 == 0) + + if shape in ('edwards', 'tedwards'): + A = 2*(a+d)/(a-d) + B = 4/(a-d) + x0,y0 = (1+y0)/(1-y0),((1+y0)/(1-y0))/x0 + x1,y1 = (1+y1)/(1-y1),((1+y1)/(1-y1))/x1 + shape = 'montgomery' + + if shape == 'montgomery': + a = (3-A^2)/(3*B^2) + b = (2*A^3-9*A)/(27*B^3) + x0,y0 = (x0+A/3)/B,y0/B + x1,y1 = (x1+A/3)/B,y1/B + shape = 'shortw' + + try: + E = EllipticCurve([a,b]) + numorder2 = 0 + numorder4 = 0 + for P in E(0).division_points(4): + if P != 0 and 2*P == 0: + numorder2 += 1 + if 2*P != 0 and 4*P == 0: + numorder4 += 1 + writefile('verify-numorder2',str(numorder2) + '\n') + writefile('verify-numorder4',str(numorder4) + '\n') + completesingle = False + completemulti = False + if numorder4 == 2 and numorder2 == 1: + # complete edwards form, and montgomery with unique point of order 2 + completesingle = True + completemulti = True + # should extend this to allow complete twisted hessian + safecomplete &= requirement('verify-completesingle',completesingle) + safecomplete &= requirement('verify-completemulti',completemulti) + safecomplete &= requirement('verify-ltimesbase1is0',l * E([x1,y1]) == 0) + writefile('verify-ltimesbase1',str(l * E([x1,y1])) + '\n') + writefile('verify-cofactorbase01',str(((p+1-t)//l) * E([x0,y0]) == E([x1,y1])) + '\n') + except: + writefile('verify-numorder2','Unverified\n') + writefile('verify-numorder4','Unverified\n') + writefile('verify-ltimesbase1','Unverified\n') + writefile('verify-cofactorbase01','Unverified\n') + safecomplete = False + + montladder = False + for r,e in (z^3+a*z+b).roots(): + if (3*r^2+a).is_square(): + montladder = True + safeladder &= requirement('verify-montladder',montladder) + + indistinguishability = False + elligator2 = False + if (p+1-t) % 2 == 0: + if b != 0: + indistinguishability = True + elligator2 = True + safeind &= requirement('verify-indistinguishability',indistinguishability) + writefile('verify-ind-notes','Elligator 2: %s.\n' % ['No','Yes'][elligator2]) + + saferigid &= (rigid == 'fully rigid' or rigid == 'somewhat rigid') + + safecurve = True + safecurve &= requirement('verify-safefield',safefield) + safecurve &= requirement('verify-safeeq',safeeq) + safecurve &= requirement('verify-safebase',safebase) + safecurve &= requirement('verify-saferho',saferho) + safecurve &= requirement('verify-safetransfer',safetransfer) + safecurve &= requirement('verify-safedisc',safedisc) + safecurve &= requirement('verify-saferigid',saferigid) + safecurve &= requirement('verify-safeladder',safeladder) + safecurve &= requirement('verify-safetwist',safetwist) + safecurve &= requirement('verify-safecomplete',safecomplete) + safecurve &= requirement('verify-safeind',safeind) + requirement('verify-safecurve',safecurve) + +originaldir = os.open('.',os.O_RDONLY) +for i in range(1,len(sys.argv)): + os.fchdir(originaldir) + os.chdir(sys.argv[i]) + verify() + diff --git a/jubjub/doc/evidence/x0 b/jubjub/doc/evidence/x0 new file mode 100644 index 0000000..3b2097a --- /dev/null +++ b/jubjub/doc/evidence/x0 @@ -0,0 +1 @@ +11076627216317271660298050606127911965867021807910416450833192264015104452986 diff --git a/jubjub/doc/evidence/x1 b/jubjub/doc/evidence/x1 new file mode 100644 index 0000000..c8c8fc3 --- /dev/null +++ b/jubjub/doc/evidence/x1 @@ -0,0 +1 @@ +8076246640662884909881801758704306714034609987455869804520522091855516602923 diff --git a/jubjub/doc/evidence/y0 b/jubjub/doc/evidence/y0 new file mode 100644 index 0000000..b47cd27 --- /dev/null +++ b/jubjub/doc/evidence/y0 @@ -0,0 +1 @@ +44412834903739585386157632289020980010620626017712148233229312325549216099227 diff --git a/jubjub/doc/evidence/y1 b/jubjub/doc/evidence/y1 new file mode 100644 index 0000000..a46479f --- /dev/null +++ b/jubjub/doc/evidence/y1 @@ -0,0 +1 @@ +13262374693698910701929044844600465831413122818447359594527400194675274060458 diff --git a/jubjub/src/fr.rs b/jubjub/src/fr.rs new file mode 100644 index 0000000..e469530 --- /dev/null +++ b/jubjub/src/fr.rs @@ -0,0 +1,1024 @@ +//! This module provides an implementation of the Jubjub scalar field $\mathbb{F}_r$ +//! where `r = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7` + +use core::convert::TryInto; +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use crate::util::{adc, mac, sbb}; + +/// Represents an element of the scalar field $\mathbb{F}_r$ of the Jubjub elliptic +/// curve construction. +// The internal representation of this type is four 64-bit unsigned +// integers in little-endian order. Elements of Fr are always in +// Montgomery form; i.e., Fr(a) = aR mod r, with R = 2^256. +#[derive(Clone, Copy, Eq)] +pub struct Fr(pub(crate) [u64; 4]); + +impl fmt::Debug for Fr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let tmp = self.to_bytes(); + write!(f, "0x")?; + for &b in tmp.iter().rev() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl From for Fr { + fn from(val: u64) -> Fr { + Fr([val, 0, 0, 0]) * R2 + } +} + +impl ConstantTimeEq for Fr { + fn ct_eq(&self, other: &Self) -> Choice { + self.0[0].ct_eq(&other.0[0]) + & self.0[1].ct_eq(&other.0[1]) + & self.0[2].ct_eq(&other.0[2]) + & self.0[3].ct_eq(&other.0[3]) + } +} + +impl PartialEq for Fr { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl ConditionallySelectable for Fr { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fr([ + u64::conditional_select(&a.0[0], &b.0[0], choice), + u64::conditional_select(&a.0[1], &b.0[1], choice), + u64::conditional_select(&a.0[2], &b.0[2], choice), + u64::conditional_select(&a.0[3], &b.0[3], choice), + ]) + } +} + +/// Constant representing the modulus +/// r = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7 +pub const MODULUS: Fr = Fr([ + 0xd097_0e5e_d6f7_2cb7, + 0xa668_2093_ccc8_1082, + 0x0667_3b01_0134_3b00, + 0x0e7d_b4ea_6533_afa9, +]); + +impl<'a> Neg for &'a Fr { + type Output = Fr; + + #[inline] + fn neg(self) -> Fr { + self.neg() + } +} + +impl Neg for Fr { + type Output = Fr; + + #[inline] + fn neg(self) -> Fr { + -&self + } +} + +impl<'a, 'b> Sub<&'b Fr> for &'a Fr { + type Output = Fr; + + #[inline] + fn sub(self, rhs: &'b Fr) -> Fr { + self.sub(rhs) + } +} + +impl<'a, 'b> Add<&'b Fr> for &'a Fr { + type Output = Fr; + + #[inline] + fn add(self, rhs: &'b Fr) -> Fr { + self.add(rhs) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a Fr { + type Output = Fr; + + #[inline] + fn mul(self, rhs: &'b Fr) -> Fr { + // Schoolbook multiplication + + self.mul(rhs) + } +} + +impl_binops_additive!(Fr, Fr); +impl_binops_multiplicative!(Fr, Fr); + +/// INV = -(r^{-1} mod 2^64) mod 2^64 +const INV: u64 = 0x1ba3_a358_ef78_8ef9; + +/// R = 2^256 mod r +const R: Fr = Fr([ + 0x25f8_0bb3_b996_07d9, + 0xf315_d62f_66b6_e750, + 0x9325_14ee_eb88_14f4, + 0x09a6_fc6f_4791_55c6, +]); + +/// R^2 = 2^512 mod r +const R2: Fr = Fr([ + 0x6771_9aa4_95e5_7731, + 0x51b0_cef0_9ce3_fc26, + 0x69da_b7fa_c026_e9a5, + 0x04f6_547b_8d12_7688, +]); + +/// R^2 = 2^768 mod r +const R3: Fr = Fr([ + 0xe0d6_c656_3d83_0544, + 0x323e_3883_598d_0f85, + 0xf0fe_a300_4c2e_2ba8, + 0x0587_4f84_9467_37ec, +]); + +impl Default for Fr { + fn default() -> Self { + Self::zero() + } +} + +impl Fr { + /// Returns zero, the additive identity. + #[inline] + pub const fn zero() -> Fr { + Fr([0, 0, 0, 0]) + } + + /// Returns one, the multiplicative identity. + #[inline] + pub const fn one() -> Fr { + R + } + + /// Doubles this field element. + #[inline] + pub const fn double(&self) -> Fr { + self.add(self) + } + + /// Attempts to convert a little-endian byte representation of + /// a field element into an element of `Fr`, failing if the input + /// is not canonical (is not smaller than r). + pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { + let mut tmp = Fr([0, 0, 0, 0]); + + tmp.0[0] = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); + tmp.0[1] = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); + tmp.0[2] = u64::from_le_bytes(bytes[16..24].try_into().unwrap()); + tmp.0[3] = u64::from_le_bytes(bytes[24..32].try_into().unwrap()); + + // Try to subtract the modulus + let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); + let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); + let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); + let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); + + // If the element is smaller than MODULUS then the + // subtraction will underflow, producing a borrow value + // of 0xffff...ffff. Otherwise, it'll be zero. + let is_some = (borrow as u8) & 1; + + // Convert to Montgomery form by computing + // (a.R^0 * R^2) / R = a.R + tmp *= &R2; + + CtOption::new(tmp, Choice::from(is_some)) + } + + /// Converts an element of `Fr` into a byte representation in + /// little-endian byte order. + pub fn to_bytes(&self) -> [u8; 32] { + // Turn into canonical form by computing + // (a.R) / R = a + let tmp = Fr::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); + + let mut res = [0; 32]; + res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + + res + } + + /// Converts a 512-bit little endian integer into + /// an element of Fr by reducing modulo r. + pub fn from_bytes_wide(bytes: &[u8; 64]) -> Fr { + Fr::from_u512([ + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + u64::from_le_bytes(bytes[16..24].try_into().unwrap()), + u64::from_le_bytes(bytes[24..32].try_into().unwrap()), + u64::from_le_bytes(bytes[32..40].try_into().unwrap()), + u64::from_le_bytes(bytes[40..48].try_into().unwrap()), + u64::from_le_bytes(bytes[48..56].try_into().unwrap()), + u64::from_le_bytes(bytes[56..64].try_into().unwrap()), + ]) + } + + fn from_u512(limbs: [u64; 8]) -> Fr { + // We reduce an arbitrary 512-bit number by decomposing it into two 256-bit digits + // with the higher bits multiplied by 2^256. Thus, we perform two reductions + // + // 1. the lower bits are multiplied by R^2, as normal + // 2. the upper bits are multiplied by R^2 * 2^256 = R^3 + // + // and computing their sum in the field. It remains to see that arbitrary 256-bit + // numbers can be placed into Montgomery form safely using the reduction. The + // reduction works so long as the product is less than R=2^256 multipled by + // the modulus. This holds because for any `c` smaller than the modulus, we have + // that (2^256 - 1)*c is an acceptable product for the reduction. Therefore, the + // reduction always works so long as `c` is in the field; in this case it is either the + // constant `R2` or `R3`. + let d0 = Fr([limbs[0], limbs[1], limbs[2], limbs[3]]); + let d1 = Fr([limbs[4], limbs[5], limbs[6], limbs[7]]); + // Convert to Montgomery form + d0 * R2 + d1 * R3 + } + + /// Converts from an integer represented in little endian + /// into its (congruent) `Fr` representation. + pub const fn from_raw(val: [u64; 4]) -> Self { + (&Fr(val)).mul(&R2) + } + + /// Squares this element. + #[inline] + pub const fn square(&self) -> Fr { + let (r1, carry) = mac(0, self.0[0], self.0[1], 0); + let (r2, carry) = mac(0, self.0[0], self.0[2], carry); + let (r3, r4) = mac(0, self.0[0], self.0[3], carry); + + let (r3, carry) = mac(r3, self.0[1], self.0[2], 0); + let (r4, r5) = mac(r4, self.0[1], self.0[3], carry); + + let (r5, r6) = mac(r5, self.0[2], self.0[3], 0); + + let r7 = r6 >> 63; + let r6 = (r6 << 1) | (r5 >> 63); + let r5 = (r5 << 1) | (r4 >> 63); + let r4 = (r4 << 1) | (r3 >> 63); + let r3 = (r3 << 1) | (r2 >> 63); + let r2 = (r2 << 1) | (r1 >> 63); + let r1 = r1 << 1; + + let (r0, carry) = mac(0, self.0[0], self.0[0], 0); + let (r1, carry) = adc(0, r1, carry); + let (r2, carry) = mac(r2, self.0[1], self.0[1], carry); + let (r3, carry) = adc(0, r3, carry); + let (r4, carry) = mac(r4, self.0[2], self.0[2], carry); + let (r5, carry) = adc(0, r5, carry); + let (r6, carry) = mac(r6, self.0[3], self.0[3], carry); + let (r7, _) = adc(0, r7, carry); + + Fr::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + /// Computes the square root of this element, if it exists. + pub fn sqrt(&self) -> CtOption { + // Because r = 3 (mod 4) + // sqrt can be done with only one exponentiation, + // via the computation of self^((r + 1) // 4) (mod r) + let sqrt = self.pow_vartime(&[ + 0xb425_c397_b5bd_cb2e, + 0x299a_0824_f332_0420, + 0x4199_cec0_404d_0ec0, + 0x039f_6d3a_994c_ebea, + ]); + + CtOption::new( + sqrt, + (sqrt * sqrt).ct_eq(self), // Only return Some if it's the square root. + ) + } + + /// Exponentiates `self` by `by`, where `by` is a + /// little-endian order integer exponent. + pub fn pow(&self, by: &[u64; 4]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + let mut tmp = res; + tmp.mul_assign(self); + res.conditional_assign(&tmp, (((*e >> i) & 0x1) as u8).into()); + } + } + res + } + + /// Exponentiates `self` by `by`, where `by` is a + /// little-endian order integer exponent. + /// + /// **This operation is variable time with respect + /// to the exponent.** If the exponent is fixed, + /// this operation is effectively constant time. + pub fn pow_vartime(&self, by: &[u64; 4]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + + if ((*e >> i) & 1) == 1 { + res.mul_assign(self); + } + } + } + res + } + + /// Computes the multiplicative inverse of this element, + /// failing if the element is zero. + pub fn invert(&self) -> CtOption { + #[inline(always)] + fn square_assign_multi(n: &mut Fr, num_times: usize) { + for _ in 0..num_times { + *n = n.square(); + } + } + // found using https://github.com/kwantam/addchain + let mut t1 = self.square(); + let mut t0 = t1.square(); + let mut t3 = t0 * t1; + let t6 = t3 * self; + let t7 = t6 * t1; + let t12 = t7 * t3; + let t13 = t12 * t0; + let t16 = t12 * t3; + let t2 = t13 * t3; + let t15 = t16 * t3; + let t19 = t2 * t0; + let t9 = t15 * t3; + let t18 = t9 * t3; + let t14 = t18 * t1; + let t4 = t18 * t0; + let t8 = t18 * t3; + let t17 = t14 * t3; + let t11 = t8 * t3; + t1 = t17 * t3; + let t5 = t11 * t3; + t3 = t5 * t0; + t0 = t5.square(); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t3); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t8); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t19); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t13); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t14); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t18); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t17); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t16); + square_assign_multi(&mut t0, 3); + t0.mul_assign(self); + square_assign_multi(&mut t0, 11); + t0.mul_assign(&t11); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t5); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t15); + square_assign_multi(&mut t0, 8); + t0.mul_assign(self); + square_assign_multi(&mut t0, 12); + t0.mul_assign(&t13); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t9); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t15); + square_assign_multi(&mut t0, 14); + t0.mul_assign(&t14); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t13); + square_assign_multi(&mut t0, 2); + t0.mul_assign(self); + square_assign_multi(&mut t0, 6); + t0.mul_assign(self); + square_assign_multi(&mut t0, 9); + t0.mul_assign(&t7); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t12); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t11); + square_assign_multi(&mut t0, 3); + t0.mul_assign(self); + square_assign_multi(&mut t0, 12); + t0.mul_assign(&t9); + square_assign_multi(&mut t0, 11); + t0.mul_assign(&t8); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t7); + square_assign_multi(&mut t0, 4); + t0.mul_assign(&t6); + square_assign_multi(&mut t0, 10); + t0.mul_assign(&t5); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t3); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t4); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t3); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t2); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t2); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t1); + + CtOption::new(t0, !self.ct_eq(&Self::zero())) + } + + #[inline] + #[allow(clippy::too_many_arguments)] + const fn montgomery_reduce( + r0: u64, + r1: u64, + r2: u64, + r3: u64, + r4: u64, + r5: u64, + r6: u64, + r7: u64, + ) -> Self { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r0.wrapping_mul(INV); + let (_, carry) = mac(r0, k, MODULUS.0[0], 0); + let (r1, carry) = mac(r1, k, MODULUS.0[1], carry); + let (r2, carry) = mac(r2, k, MODULUS.0[2], carry); + let (r3, carry) = mac(r3, k, MODULUS.0[3], carry); + let (r4, carry2) = adc(r4, 0, carry); + + let k = r1.wrapping_mul(INV); + let (_, carry) = mac(r1, k, MODULUS.0[0], 0); + let (r2, carry) = mac(r2, k, MODULUS.0[1], carry); + let (r3, carry) = mac(r3, k, MODULUS.0[2], carry); + let (r4, carry) = mac(r4, k, MODULUS.0[3], carry); + let (r5, carry2) = adc(r5, carry2, carry); + + let k = r2.wrapping_mul(INV); + let (_, carry) = mac(r2, k, MODULUS.0[0], 0); + let (r3, carry) = mac(r3, k, MODULUS.0[1], carry); + let (r4, carry) = mac(r4, k, MODULUS.0[2], carry); + let (r5, carry) = mac(r5, k, MODULUS.0[3], carry); + let (r6, carry2) = adc(r6, carry2, carry); + + let k = r3.wrapping_mul(INV); + let (_, carry) = mac(r3, k, MODULUS.0[0], 0); + let (r4, carry) = mac(r4, k, MODULUS.0[1], carry); + let (r5, carry) = mac(r5, k, MODULUS.0[2], carry); + let (r6, carry) = mac(r6, k, MODULUS.0[3], carry); + let (r7, _) = adc(r7, carry2, carry); + + // Result may be within MODULUS of the correct value + (&Fr([r4, r5, r6, r7])).sub(&MODULUS) + } + + /// Multiplies this element by another element + #[inline] + pub const fn mul(&self, rhs: &Self) -> Self { + // Schoolbook multiplication + + let (r0, carry) = mac(0, self.0[0], rhs.0[0], 0); + let (r1, carry) = mac(0, self.0[0], rhs.0[1], carry); + let (r2, carry) = mac(0, self.0[0], rhs.0[2], carry); + let (r3, r4) = mac(0, self.0[0], rhs.0[3], carry); + + let (r1, carry) = mac(r1, self.0[1], rhs.0[0], 0); + let (r2, carry) = mac(r2, self.0[1], rhs.0[1], carry); + let (r3, carry) = mac(r3, self.0[1], rhs.0[2], carry); + let (r4, r5) = mac(r4, self.0[1], rhs.0[3], carry); + + let (r2, carry) = mac(r2, self.0[2], rhs.0[0], 0); + let (r3, carry) = mac(r3, self.0[2], rhs.0[1], carry); + let (r4, carry) = mac(r4, self.0[2], rhs.0[2], carry); + let (r5, r6) = mac(r5, self.0[2], rhs.0[3], carry); + + let (r3, carry) = mac(r3, self.0[3], rhs.0[0], 0); + let (r4, carry) = mac(r4, self.0[3], rhs.0[1], carry); + let (r5, carry) = mac(r5, self.0[3], rhs.0[2], carry); + let (r6, r7) = mac(r6, self.0[3], rhs.0[3], carry); + + Fr::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + /// Subtracts another element from this element. + #[inline] + pub const fn sub(&self, rhs: &Self) -> Self { + let (d0, borrow) = sbb(self.0[0], rhs.0[0], 0); + let (d1, borrow) = sbb(self.0[1], rhs.0[1], borrow); + let (d2, borrow) = sbb(self.0[2], rhs.0[2], borrow); + let (d3, borrow) = sbb(self.0[3], rhs.0[3], borrow); + + // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise + // borrow = 0x000...000. Thus, we use it as a mask to conditionally add the modulus. + let (d0, carry) = adc(d0, MODULUS.0[0] & borrow, 0); + let (d1, carry) = adc(d1, MODULUS.0[1] & borrow, carry); + let (d2, carry) = adc(d2, MODULUS.0[2] & borrow, carry); + let (d3, _) = adc(d3, MODULUS.0[3] & borrow, carry); + + Fr([d0, d1, d2, d3]) + } + + /// Adds this element to another element. + #[inline] + pub const fn add(&self, rhs: &Self) -> Self { + let (d0, carry) = adc(self.0[0], rhs.0[0], 0); + let (d1, carry) = adc(self.0[1], rhs.0[1], carry); + let (d2, carry) = adc(self.0[2], rhs.0[2], carry); + let (d3, _) = adc(self.0[3], rhs.0[3], carry); + + // Attempt to subtract the modulus, to ensure the value + // is smaller than the modulus. + (&Fr([d0, d1, d2, d3])).sub(&MODULUS) + } + + /// Negates this element. + #[inline] + pub const fn neg(&self) -> Self { + // Subtract `self` from `MODULUS` to negate. Ignore the final + // borrow because it cannot underflow; self is guaranteed to + // be in the field. + let (d0, borrow) = sbb(MODULUS.0[0], self.0[0], 0); + let (d1, borrow) = sbb(MODULUS.0[1], self.0[1], borrow); + let (d2, borrow) = sbb(MODULUS.0[2], self.0[2], borrow); + let (d3, _) = sbb(MODULUS.0[3], self.0[3], borrow); + + // `tmp` could be `MODULUS` if `self` was zero. Create a mask that is + // zero if `self` was zero, and `u64::max_value()` if self was nonzero. + let mask = (((self.0[0] | self.0[1] | self.0[2] | self.0[3]) == 0) as u64).wrapping_sub(1); + + Fr([d0 & mask, d1 & mask, d2 & mask, d3 & mask]) + } +} + +impl<'a> From<&'a Fr> for [u8; 32] { + fn from(value: &'a Fr) -> [u8; 32] { + value.to_bytes() + } +} + +#[test] +fn test_inv() { + // Compute -(r^{-1} mod 2^64) mod 2^64 by exponentiating + // by totient(2**64) - 1 + + let mut inv = 1u64; + for _ in 0..63 { + inv = inv.wrapping_mul(inv); + inv = inv.wrapping_mul(MODULUS.0[0]); + } + inv = inv.wrapping_neg(); + + assert_eq!(inv, INV); +} + +#[test] +fn test_debug() { + assert_eq!( + format!("{:?}", Fr::zero()), + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + assert_eq!( + format!("{:?}", Fr::one()), + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert_eq!( + format!("{:?}", R2), + "0x09a6fc6f479155c6932514eeeb8814f4f315d62f66b6e75025f80bb3b99607d9" + ); +} + +#[test] +fn test_equality() { + assert_eq!(Fr::zero(), Fr::zero()); + assert_eq!(Fr::one(), Fr::one()); + assert_eq!(R2, R2); + + assert!(Fr::zero() != Fr::one()); + assert!(Fr::one() != R2); +} + +#[test] +fn test_to_bytes() { + assert_eq!( + Fr::zero().to_bytes(), + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ] + ); + + assert_eq!( + Fr::one().to_bytes(), + [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ] + ); + + assert_eq!( + R2.to_bytes(), + [ + 217, 7, 150, 185, 179, 11, 248, 37, 80, 231, 182, 102, 47, 214, 21, 243, 244, 20, 136, + 235, 238, 20, 37, 147, 198, 85, 145, 71, 111, 252, 166, 9 + ] + ); + + assert_eq!( + (-&Fr::one()).to_bytes(), + [ + 182, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ] + ); +} + +#[test] +fn test_from_bytes() { + assert_eq!( + Fr::from_bytes(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ]) + .unwrap(), + Fr::zero() + ); + + assert_eq!( + Fr::from_bytes(&[ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ]) + .unwrap(), + Fr::one() + ); + + assert_eq!( + Fr::from_bytes(&[ + 217, 7, 150, 185, 179, 11, 248, 37, 80, 231, 182, 102, 47, 214, 21, 243, 244, 20, 136, + 235, 238, 20, 37, 147, 198, 85, 145, 71, 111, 252, 166, 9 + ]) + .unwrap(), + R2 + ); + + // -1 should work + assert!( + Fr::from_bytes(&[ + 182, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_some() + .unwrap_u8() + == 1 + ); + + // modulus is invalid + assert!( + Fr::from_bytes(&[ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + // Anything larger than the modulus is invalid + assert!( + Fr::from_bytes(&[ + 184, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + assert!( + Fr::from_bytes(&[ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 104, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + assert!( + Fr::from_bytes(&[ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 15 + ]) + .is_none() + .unwrap_u8() + == 1 + ); +} + +#[test] +fn test_from_u512_zero() { + assert_eq!( + Fr::zero(), + Fr::from_u512([ + MODULUS.0[0], + MODULUS.0[1], + MODULUS.0[2], + MODULUS.0[3], + 0, + 0, + 0, + 0 + ]) + ); +} + +#[test] +fn test_from_u512_r() { + assert_eq!(R, Fr::from_u512([1, 0, 0, 0, 0, 0, 0, 0])); +} + +#[test] +fn test_from_u512_r2() { + assert_eq!(R2, Fr::from_u512([0, 0, 0, 0, 1, 0, 0, 0])); +} + +#[test] +fn test_from_u512_max() { + let max_u64 = 0xffff_ffff_ffff_ffff; + assert_eq!( + R3 - R, + Fr::from_u512([max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64]) + ); +} + +#[test] +fn test_from_bytes_wide_r2() { + assert_eq!( + R2, + Fr::from_bytes_wide(&[ + 217, 7, 150, 185, 179, 11, 248, 37, 80, 231, 182, 102, 47, 214, 21, 243, 244, 20, 136, + 235, 238, 20, 37, 147, 198, 85, 145, 71, 111, 252, 166, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]) + ); +} + +#[test] +fn test_from_bytes_wide_negative_one() { + assert_eq!( + -&Fr::one(), + Fr::from_bytes_wide(&[ + 182, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]) + ); +} + +#[test] +fn test_from_bytes_wide_maximum() { + assert_eq!( + Fr([ + 0x8b75_c901_5ae4_2a22, + 0xe590_82e7_bf9e_38b8, + 0x6440_c912_61da_51b3, + 0x0a5e_07ff_b209_91cf, + ]), + Fr::from_bytes_wide(&[0xff; 64]) + ); +} + +#[test] +fn test_zero() { + assert_eq!(Fr::zero(), -&Fr::zero()); + assert_eq!(Fr::zero(), Fr::zero() + Fr::zero()); + assert_eq!(Fr::zero(), Fr::zero() - Fr::zero()); + assert_eq!(Fr::zero(), Fr::zero() * Fr::zero()); +} + +#[cfg(test)] +const LARGEST: Fr = Fr([ + 0xd097_0e5e_d6f7_2cb6, + 0xa668_2093_ccc8_1082, + 0x0667_3b01_0134_3b00, + 0x0e7d_b4ea_6533_afa9, +]); + +#[test] +fn test_addition() { + let mut tmp = LARGEST; + tmp += &LARGEST; + + assert_eq!( + tmp, + Fr([ + 0xd097_0e5e_d6f7_2cb5, + 0xa668_2093_ccc8_1082, + 0x0667_3b01_0134_3b00, + 0x0e7d_b4ea_6533_afa9 + ]) + ); + + let mut tmp = LARGEST; + tmp += &Fr([1, 0, 0, 0]); + + assert_eq!(tmp, Fr::zero()); +} + +#[test] +fn test_negation() { + let tmp = -&LARGEST; + + assert_eq!(tmp, Fr([1, 0, 0, 0])); + + let tmp = -&Fr::zero(); + assert_eq!(tmp, Fr::zero()); + let tmp = -&Fr([1, 0, 0, 0]); + assert_eq!(tmp, LARGEST); +} + +#[test] +fn test_subtraction() { + let mut tmp = LARGEST; + tmp -= &LARGEST; + + assert_eq!(tmp, Fr::zero()); + + let mut tmp = Fr::zero(); + tmp -= &LARGEST; + + let mut tmp2 = MODULUS; + tmp2 -= &LARGEST; + + assert_eq!(tmp, tmp2); +} + +#[test] +fn test_multiplication() { + let mut cur = LARGEST; + + for _ in 0..100 { + let mut tmp = cur; + tmp *= &cur; + + let mut tmp2 = Fr::zero(); + for b in cur + .to_bytes() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8)) + { + let tmp3 = tmp2; + tmp2.add_assign(&tmp3); + + if b { + tmp2.add_assign(&cur); + } + } + + assert_eq!(tmp, tmp2); + + cur.add_assign(&LARGEST); + } +} + +#[test] +fn test_squaring() { + let mut cur = LARGEST; + + for _ in 0..100 { + let mut tmp = cur; + tmp = tmp.square(); + + let mut tmp2 = Fr::zero(); + for b in cur + .to_bytes() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8)) + { + let tmp3 = tmp2; + tmp2.add_assign(&tmp3); + + if b { + tmp2.add_assign(&cur); + } + } + + assert_eq!(tmp, tmp2); + + cur.add_assign(&LARGEST); + } +} + +#[test] +fn test_inversion() { + assert_eq!(Fr::zero().invert().is_none().unwrap_u8(), 1); + assert_eq!(Fr::one().invert().unwrap(), Fr::one()); + assert_eq!((-&Fr::one()).invert().unwrap(), -&Fr::one()); + + let mut tmp = R2; + + for _ in 0..100 { + let mut tmp2 = tmp.invert().unwrap(); + tmp2.mul_assign(&tmp); + + assert_eq!(tmp2, Fr::one()); + + tmp.add_assign(&R2); + } +} + +#[test] +fn test_invert_is_pow() { + let r_minus_2 = [ + 0xd097_0e5e_d6f7_2cb5, + 0xa668_2093_ccc8_1082, + 0x0667_3b01_0134_3b00, + 0x0e7d_b4ea_6533_afa9, + ]; + + let mut r1 = R; + let mut r2 = R; + let mut r3 = R; + + for _ in 0..100 { + r1 = r1.invert().unwrap(); + r2 = r2.pow_vartime(&r_minus_2); + r3 = r3.pow(&r_minus_2); + + assert_eq!(r1, r2); + assert_eq!(r2, r3); + // Add R so we check something different next time around + r1.add_assign(&R); + r2 = r1; + r3 = r1; + } +} + +#[test] +fn test_sqrt() { + let mut square = Fr([ + // r - 2 + 0xd097_0e5e_d6f7_2cb5, + 0xa668_2093_ccc8_1082, + 0x0667_3b01_0134_3b00, + 0x0e7d_b4ea_6533_afa9, + ]); + + let mut none_count = 0; + + for _ in 0..100 { + let square_root = square.sqrt(); + if square_root.is_none().unwrap_u8() == 1 { + none_count += 1; + } else { + assert_eq!(square_root.unwrap() * square_root.unwrap(), square); + } + square -= Fr::one(); + } + + assert_eq!(47, none_count); +} + +#[test] +fn test_from_raw() { + assert_eq!( + Fr::from_raw([ + 0x25f8_0bb3_b996_07d8, + 0xf315_d62f_66b6_e750, + 0x9325_14ee_eb88_14f4, + 0x09a6_fc6f_4791_55c6, + ]), + Fr::from_raw([0xffff_ffff_ffff_ffff; 4]) + ); + + assert_eq!(Fr::from_raw(MODULUS.0), Fr::zero()); + + assert_eq!(Fr::from_raw([1, 0, 0, 0]), R); +} diff --git a/jubjub/src/lib.rs b/jubjub/src/lib.rs new file mode 100644 index 0000000..09cb97e --- /dev/null +++ b/jubjub/src/lib.rs @@ -0,0 +1,1328 @@ +//! This crate provides an implementation of the **Jubjub** elliptic curve and its associated +//! field arithmetic. See [`README.md`](https://github.com/zkcrypto/jubjub/blob/master/README.md) for more details about Jubjub. +//! +//! # API +//! +//! * `AffinePoint` / `ExtendedPoint` which are implementations of Jubjub group arithmetic +//! * `AffineNielsPoint` / `ExtendedNielsPoint` which are pre-processed Jubjub points +//! * `Fq`, which is the base field of Jubjub +//! * `Fr`, which is the scalar field of Jubjub +//! * `batch_normalize` for converting many `ExtendedPoint`s into `AffinePoint`s efficiently. +//! +//! # Constant Time +//! +//! All operations are constant time unless explicitly noted; these functions will contain +//! "vartime" in their name and they will be documented as variable time. +//! +//! This crate uses the `subtle` crate to perform constant-time operations. + +#![no_std] +// Catch documentation errors caused by code changes. +#![deny(intra_doc_link_resolution_failure)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![deny(unsafe_code)] +// This lint is described at +// https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl +// In our library, some of the arithmetic will necessarily involve various binary +// operators, and so this lint is triggered unnecessarily. +#![allow(clippy::suspicious_arithmetic_impl)] + +#[cfg(test)] +#[macro_use] +extern crate std; + +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +#[macro_use] +mod util; + +mod fr; +pub use bls12_381::Scalar as Fq; +pub use fr::Fr; + +const FR_MODULUS_BYTES: [u8; 32] = [ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, 1, 1, 59, + 103, 6, 169, 175, 51, 101, 234, 180, 125, 14, +]; + +/// This represents a Jubjub point in the affine `(u, v)` +/// coordinates. +#[derive(Clone, Copy, Debug)] +pub struct AffinePoint { + u: Fq, + v: Fq, +} + +impl Neg for AffinePoint { + type Output = AffinePoint; + + /// This computes the negation of a point `P = (u, v)` + /// as `-P = (-u, v)`. + #[inline] + fn neg(self) -> AffinePoint { + AffinePoint { + u: -self.u, + v: self.v, + } + } +} + +impl ConstantTimeEq for AffinePoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.u.ct_eq(&other.u) & self.v.ct_eq(&other.v) + } +} + +impl PartialEq for AffinePoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl ConditionallySelectable for AffinePoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + AffinePoint { + u: Fq::conditional_select(&a.u, &b.u, choice), + v: Fq::conditional_select(&a.v, &b.v, choice), + } + } +} + +/// This represents an extended point `(U, V, Z, T1, T2)` +/// with `Z` nonzero, corresponding to the affine point +/// `(U/Z, V/Z)`. We always have `T1 * T2 = UV/Z`. +/// +/// You can do the following things with a point in this +/// form: +/// +/// * Convert it into a point in the affine form. +/// * Add it to an `ExtendedPoint`, `AffineNielsPoint` or `ExtendedNielsPoint`. +/// * Double it using `double()`. +/// * Compare it with another extended point using `PartialEq` or `ct_eq()`. +#[derive(Clone, Copy, Debug)] +pub struct ExtendedPoint { + u: Fq, + v: Fq, + z: Fq, + t1: Fq, + t2: Fq, +} + +impl ConstantTimeEq for ExtendedPoint { + fn ct_eq(&self, other: &Self) -> Choice { + // (u/z, v/z) = (u'/z', v'/z') is implied by + // (uz'z = u'z'z) and + // (vz'z = v'z'z) + // as z and z' are always nonzero. + + (self.u * other.z).ct_eq(&(other.u * self.z)) + & (self.v * other.z).ct_eq(&(other.v * self.z)) + } +} + +impl ConditionallySelectable for ExtendedPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + ExtendedPoint { + u: Fq::conditional_select(&a.u, &b.u, choice), + v: Fq::conditional_select(&a.v, &b.v, choice), + z: Fq::conditional_select(&a.z, &b.z, choice), + t1: Fq::conditional_select(&a.t1, &b.t1, choice), + t2: Fq::conditional_select(&a.t2, &b.t2, choice), + } + } +} + +impl PartialEq for ExtendedPoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl Neg for ExtendedPoint { + type Output = ExtendedPoint; + + /// Computes the negation of a point `P = (U, V, Z, T)` + /// as `-P = (-U, V, Z, -T1, T2)`. The choice of `T1` + /// is made without loss of generality. + #[inline] + fn neg(self) -> ExtendedPoint { + ExtendedPoint { + u: -self.u, + v: self.v, + z: self.z, + t1: -self.t1, + t2: self.t2, + } + } +} + +impl From for ExtendedPoint { + /// Constructs an extended point (with `Z = 1`) from + /// an affine point using the map `(u, v) => (u, v, 1, u, v)`. + fn from(affine: AffinePoint) -> ExtendedPoint { + ExtendedPoint { + u: affine.u, + v: affine.v, + z: Fq::one(), + t1: affine.u, + t2: affine.v, + } + } +} + +impl<'a> From<&'a ExtendedPoint> for AffinePoint { + /// Constructs an affine point from an extended point + /// using the map `(U, V, Z, T1, T2) => (U/Z, V/Z)` + /// as Z is always nonzero. **This requires a field inversion + /// and so it is recommended to perform these in a batch + /// using [`batch_normalize`](crate::batch_normalize) instead.** + fn from(extended: &'a ExtendedPoint) -> AffinePoint { + // Z coordinate is always nonzero, so this is + // its inverse. + let zinv = extended.z.invert().unwrap(); + + AffinePoint { + u: extended.u * zinv, + v: extended.v * zinv, + } + } +} + +impl From for AffinePoint { + fn from(extended: ExtendedPoint) -> AffinePoint { + AffinePoint::from(&extended) + } +} + +/// This is a pre-processed version of an affine point `(u, v)` +/// in the form `(v + u, v - u, u * v * 2d)`. This can be added to an +/// [`ExtendedPoint`](crate::ExtendedPoint). +#[derive(Clone, Copy, Debug)] +pub struct AffineNielsPoint { + v_plus_u: Fq, + v_minus_u: Fq, + t2d: Fq, +} + +impl AffineNielsPoint { + /// Constructs this point from the neutral element `(0, 1)`. + pub const fn identity() -> Self { + AffineNielsPoint { + v_plus_u: Fq::one(), + v_minus_u: Fq::one(), + t2d: Fq::zero(), + } + } + + #[inline] + fn multiply(&self, by: &[u8; 32]) -> ExtendedPoint { + let zero = AffineNielsPoint::identity(); + + let mut acc = ExtendedPoint::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading four bits because they're always + // unset for Fr. + for bit in by + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(4) + { + acc = acc.double(); + acc += AffineNielsPoint::conditional_select(&zero, &self, bit); + } + + acc + } + + /// Multiplies this point by the specific little-endian bit pattern in the + /// given byte array, ignoring the highest four bits. + pub fn multiply_bits(&self, by: &[u8; 32]) -> ExtendedPoint { + self.multiply(by) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a AffineNielsPoint { + type Output = ExtendedPoint; + + fn mul(self, other: &'b Fr) -> ExtendedPoint { + self.multiply(&other.to_bytes()) + } +} + +impl_binops_multiplicative_mixed!(AffineNielsPoint, Fr, ExtendedPoint); + +impl ConditionallySelectable for AffineNielsPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + AffineNielsPoint { + v_plus_u: Fq::conditional_select(&a.v_plus_u, &b.v_plus_u, choice), + v_minus_u: Fq::conditional_select(&a.v_minus_u, &b.v_minus_u, choice), + t2d: Fq::conditional_select(&a.t2d, &b.t2d, choice), + } + } +} + +/// This is a pre-processed version of an extended point `(U, V, Z, T1, T2)` +/// in the form `(V + U, V - U, Z, T1 * T2 * 2d)`. +#[derive(Clone, Copy, Debug)] +pub struct ExtendedNielsPoint { + v_plus_u: Fq, + v_minus_u: Fq, + z: Fq, + t2d: Fq, +} + +impl ConditionallySelectable for ExtendedNielsPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + ExtendedNielsPoint { + v_plus_u: Fq::conditional_select(&a.v_plus_u, &b.v_plus_u, choice), + v_minus_u: Fq::conditional_select(&a.v_minus_u, &b.v_minus_u, choice), + z: Fq::conditional_select(&a.z, &b.z, choice), + t2d: Fq::conditional_select(&a.t2d, &b.t2d, choice), + } + } +} + +impl ExtendedNielsPoint { + /// Constructs this point from the neutral element `(0, 1)`. + pub const fn identity() -> Self { + ExtendedNielsPoint { + v_plus_u: Fq::one(), + v_minus_u: Fq::one(), + z: Fq::one(), + t2d: Fq::zero(), + } + } + + #[inline] + fn multiply(&self, by: &[u8; 32]) -> ExtendedPoint { + let zero = ExtendedNielsPoint::identity(); + + let mut acc = ExtendedPoint::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading four bits because they're always + // unset for Fr. + for bit in by + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(4) + { + acc = acc.double(); + acc += ExtendedNielsPoint::conditional_select(&zero, &self, bit); + } + + acc + } + + /// Multiplies this point by the specific little-endian bit pattern in the + /// given byte array, ignoring the highest four bits. + pub fn multiply_bits(&self, by: &[u8; 32]) -> ExtendedPoint { + self.multiply(by) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a ExtendedNielsPoint { + type Output = ExtendedPoint; + + fn mul(self, other: &'b Fr) -> ExtendedPoint { + self.multiply(&other.to_bytes()) + } +} + +impl_binops_multiplicative_mixed!(ExtendedNielsPoint, Fr, ExtendedPoint); + +// `d = -(10240/10241)` +const EDWARDS_D: Fq = Fq::from_raw([ + 0x0106_5fd6_d634_3eb1, + 0x292d_7f6d_3757_9d26, + 0xf5fd_9207_e6bd_7fd4, + 0x2a93_18e7_4bfa_2b48, +]); + +// `2*d` +const EDWARDS_D2: Fq = Fq::from_raw([ + 0x020c_bfad_ac68_7d62, + 0x525a_feda_6eaf_3a4c, + 0xebfb_240f_cd7a_ffa8, + 0x5526_31ce_97f4_5691, +]); + +impl AffinePoint { + /// Constructs the neutral element `(0, 1)`. + pub const fn identity() -> Self { + AffinePoint { + u: Fq::zero(), + v: Fq::one(), + } + } + + /// Multiplies this point by the cofactor, producing an + /// `ExtendedPoint` + pub fn mul_by_cofactor(&self) -> ExtendedPoint { + ExtendedPoint::from(*self).mul_by_cofactor() + } + + /// Determines if this point is of small order. + pub fn is_small_order(&self) -> Choice { + ExtendedPoint::from(*self).is_small_order() + } + + /// Determines if this point is torsion free and so is + /// in the prime order subgroup. + pub fn is_torsion_free(&self) -> Choice { + ExtendedPoint::from(*self).is_torsion_free() + } + + /// Determines if this point is prime order, or in other words that + /// the smallest scalar multiplied by this point that produces the + /// identity is `r`. This is equivalent to checking that the point + /// is both torsion free and not the identity. + pub fn is_prime_order(&self) -> Choice { + let extended = ExtendedPoint::from(*self); + extended.is_torsion_free() & (!extended.is_identity()) + } + + /// Converts this element into its byte representation. + pub fn to_bytes(&self) -> [u8; 32] { + let mut tmp = self.v.to_bytes(); + let u = self.u.to_bytes(); + + // Encode the sign of the u-coordinate in the most + // significant bit. + tmp[31] |= u[0] << 7; + + tmp + } + + /// Attempts to interpret a byte representation of an + /// affine point, failing if the element is not on + /// the curve or non-canonical. + pub fn from_bytes(mut b: [u8; 32]) -> CtOption { + // Grab the sign bit from the representation + let sign = b[31] >> 7; + + // Mask away the sign bit + b[31] &= 0b0111_1111; + + // Interpret what remains as the v-coordinate + Fq::from_bytes(&b).and_then(|v| { + // -u^2 + v^2 = 1 + d.u^2.v^2 + // -u^2 = 1 + d.u^2.v^2 - v^2 (rearrange) + // -u^2 - d.u^2.v^2 = 1 - v^2 (rearrange) + // u^2 + d.u^2.v^2 = v^2 - 1 (flip signs) + // u^2 (1 + d.v^2) = v^2 - 1 (factor) + // u^2 = (v^2 - 1) / (1 + d.v^2) (isolate u^2) + // We know that (1 + d.v^2) is nonzero for all v: + // (1 + d.v^2) = 0 + // d.v^2 = -1 + // v^2 = -(1 / d) No solutions, as -(1 / d) is not a square + + let v2 = v.square(); + + ((v2 - Fq::one()) * ((Fq::one() + EDWARDS_D * v2).invert().unwrap_or(Fq::zero()))) + .sqrt() + .and_then(|u| { + // Fix the sign of `u` if necessary + let flip_sign = Choice::from((u.to_bytes()[0] ^ sign) & 1); + let u_negated = -u; + let final_u = Fq::conditional_select(&u, &u_negated, flip_sign); + + CtOption::new(AffinePoint { u: final_u, v }, Choice::from(1u8)) + }) + }) + } + + /// Returns the `u`-coordinate of this point. + pub fn get_u(&self) -> Fq { + self.u + } + + /// Returns the `v`-coordinate of this point. + pub fn get_v(&self) -> Fq { + self.v + } + + /// Performs a pre-processing step that produces an `AffineNielsPoint` + /// for use in multiple additions. + pub const fn to_niels(&self) -> AffineNielsPoint { + AffineNielsPoint { + v_plus_u: Fq::add(&self.v, &self.u), + v_minus_u: Fq::sub(&self.v, &self.u), + t2d: Fq::mul(&Fq::mul(&self.u, &self.v), &EDWARDS_D2), + } + } + + /// Constructs an AffinePoint given `u` and `v` without checking + /// that the point is on the curve. + pub const fn from_raw_unchecked(u: Fq, v: Fq) -> AffinePoint { + AffinePoint { u, v } + } + + /// This is only for debugging purposes and not + /// exposed in the public API. Checks that this + /// point is on the curve. + #[cfg(test)] + fn is_on_curve_vartime(&self) -> bool { + let u2 = self.u.square(); + let v2 = self.v.square(); + + v2 - u2 == Fq::one() + EDWARDS_D * u2 * v2 + } +} + +impl ExtendedPoint { + /// Constructs an extended point from the neutral element `(0, 1)`. + pub const fn identity() -> Self { + ExtendedPoint { + u: Fq::zero(), + v: Fq::one(), + z: Fq::one(), + t1: Fq::zero(), + t2: Fq::zero(), + } + } + + /// Determines if this point is the identity. + pub fn is_identity(&self) -> Choice { + // If this point is the identity, then + // u = 0 * z = 0 + // and v = 1 * z = z + self.u.ct_eq(&Fq::zero()) & self.v.ct_eq(&self.z) + } + + /// Determines if this point is of small order. + pub fn is_small_order(&self) -> Choice { + // We only need to perform two doublings, since the 2-torsion + // points are (0, 1) and (0, -1), and so we only need to check + // that the u-coordinate of the result is zero to see if the + // point is small order. + self.double().double().u.ct_eq(&Fq::zero()) + } + + /// Determines if this point is torsion free and so is contained + /// in the prime order subgroup. + pub fn is_torsion_free(&self) -> Choice { + self.multiply(&FR_MODULUS_BYTES).is_identity() + } + + /// Determines if this point is prime order, or in other words that + /// the smallest scalar multiplied by this point that produces the + /// identity is `r`. This is equivalent to checking that the point + /// is both torsion free and not the identity. + pub fn is_prime_order(&self) -> Choice { + self.is_torsion_free() & (!self.is_identity()) + } + + /// Multiplies this element by the cofactor `8`. + pub fn mul_by_cofactor(&self) -> ExtendedPoint { + self.double().double().double() + } + + /// Performs a pre-processing step that produces an `ExtendedNielsPoint` + /// for use in multiple additions. + pub fn to_niels(&self) -> ExtendedNielsPoint { + ExtendedNielsPoint { + v_plus_u: self.v + self.u, + v_minus_u: self.v - self.u, + z: self.z, + t2d: self.t1 * self.t2 * EDWARDS_D2, + } + } + + /// Computes the doubling of a point more efficiently than a point can + /// be added to itself. + pub fn double(&self) -> ExtendedPoint { + // Doubling is more efficient (three multiplications, four squarings) + // when we work within the projective coordinate space (U:Z, V:Z). We + // rely on the most efficient formula, "dbl-2008-bbjlp", as described + // in Section 6 of "Twisted Edwards Curves" by Bernstein et al. + // + // See + // for more information. + // + // We differ from the literature in that we use (u, v) rather than + // (x, y) coordinates. We also have the constant `a = -1` implied. Let + // us rewrite the procedure of doubling (u, v, z) to produce (U, V, Z) + // as follows: + // + // B = (u + v)^2 + // C = u^2 + // D = v^2 + // F = D - C + // H = 2 * z^2 + // J = F - H + // U = (B - C - D) * J + // V = F * (- C - D) + // Z = F * J + // + // If we compute K = D + C, we can rewrite this: + // + // B = (u + v)^2 + // C = u^2 + // D = v^2 + // F = D - C + // K = D + C + // H = 2 * z^2 + // J = F - H + // U = (B - K) * J + // V = F * (-K) + // Z = F * J + // + // In order to avoid the unnecessary negation of K, + // we will negate J, transforming the result into + // an equivalent point with a negated z-coordinate. + // + // B = (u + v)^2 + // C = u^2 + // D = v^2 + // F = D - C + // K = D + C + // H = 2 * z^2 + // J = H - F + // U = (B - K) * J + // V = F * K + // Z = F * J + // + // Let us rename some variables to simplify: + // + // UV2 = (u + v)^2 + // UU = u^2 + // VV = v^2 + // VVmUU = VV - UU + // VVpUU = VV + UU + // ZZ2 = 2 * z^2 + // J = ZZ2 - VVmUU + // U = (UV2 - VVpUU) * J + // V = VVmUU * VVpUU + // Z = VVmUU * J + // + // We wish to obtain two factors of T = UV/Z. + // + // UV/Z = (UV2 - VVpUU) * (ZZ2 - VVmUU) * VVmUU * VVpUU / VVmUU / (ZZ2 - VVmUU) + // = (UV2 - VVpUU) * VVmUU * VVpUU / VVmUU + // = (UV2 - VVpUU) * VVpUU + // + // and so we have that T1 = (UV2 - VVpUU) and T2 = VVpUU. + + let uu = self.u.square(); + let vv = self.v.square(); + let zz2 = self.z.square().double(); + let uv2 = (self.u + self.v).square(); + let vv_plus_uu = vv + uu; + let vv_minus_uu = vv - uu; + + // The remaining arithmetic is exactly the process of converting + // from a completed point to an extended point. + CompletedPoint { + u: uv2 - vv_plus_uu, + v: vv_plus_uu, + z: vv_minus_uu, + t: zz2 - vv_minus_uu, + } + .into_extended() + } + + #[inline] + fn multiply(self, by: &[u8; 32]) -> Self { + self.to_niels().multiply(by) + } + + /// This is only for debugging purposes and not + /// exposed in the public API. Checks that this + /// point is on the curve. + #[cfg(test)] + fn is_on_curve_vartime(&self) -> bool { + let affine = AffinePoint::from(*self); + + self.z != Fq::zero() + && affine.is_on_curve_vartime() + && (affine.u * affine.v * self.z == self.t1 * self.t2) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + fn mul(self, other: &'b Fr) -> ExtendedPoint { + self.multiply(&other.to_bytes()) + } +} + +impl_binops_multiplicative!(ExtendedPoint, Fr); + +impl<'a, 'b> Add<&'b ExtendedNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn add(self, other: &'b ExtendedNielsPoint) -> ExtendedPoint { + // We perform addition in the extended coordinates. Here we use + // a formula presented by Hisil, Wong, Carter and Dawson in + // "Twisted Edward Curves Revisited" which only requires 8M. + // + // A = (V1 - U1) * (V2 - U2) + // B = (V1 + U1) * (V2 + U2) + // C = 2d * T1 * T2 + // D = 2 * Z1 * Z2 + // E = B - A + // F = D - C + // G = D + C + // H = B + A + // U3 = E * F + // Y3 = G * H + // Z3 = F * G + // T3 = E * H + + let a = (self.v - self.u) * other.v_minus_u; + let b = (self.v + self.u) * other.v_plus_u; + let c = self.t1 * self.t2 * other.t2d; + let d = (self.z * other.z).double(); + + // The remaining arithmetic is exactly the process of converting + // from a completed point to an extended point. + CompletedPoint { + u: b - a, + v: b + a, + z: d + c, + t: d - c, + } + .into_extended() + } +} + +impl<'a, 'b> Sub<&'b ExtendedNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, other: &'b ExtendedNielsPoint) -> ExtendedPoint { + let a = (self.v - self.u) * other.v_plus_u; + let b = (self.v + self.u) * other.v_minus_u; + let c = self.t1 * self.t2 * other.t2d; + let d = (self.z * other.z).double(); + + CompletedPoint { + u: b - a, + v: b + a, + z: d - c, + t: d + c, + } + .into_extended() + } +} + +impl_binops_additive!(ExtendedPoint, ExtendedNielsPoint); + +impl<'a, 'b> Add<&'b AffineNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn add(self, other: &'b AffineNielsPoint) -> ExtendedPoint { + // This is identical to the addition formula for `ExtendedNielsPoint`, + // except we can assume that `other.z` is one, so that we perform + // 7 multiplications. + + let a = (self.v - self.u) * other.v_minus_u; + let b = (self.v + self.u) * other.v_plus_u; + let c = self.t1 * self.t2 * other.t2d; + let d = self.z.double(); + + // The remaining arithmetic is exactly the process of converting + // from a completed point to an extended point. + CompletedPoint { + u: b - a, + v: b + a, + z: d + c, + t: d - c, + } + .into_extended() + } +} + +impl<'a, 'b> Sub<&'b AffineNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, other: &'b AffineNielsPoint) -> ExtendedPoint { + let a = (self.v - self.u) * other.v_plus_u; + let b = (self.v + self.u) * other.v_minus_u; + let c = self.t1 * self.t2 * other.t2d; + let d = self.z.double(); + + CompletedPoint { + u: b - a, + v: b + a, + z: d - c, + t: d + c, + } + .into_extended() + } +} + +impl_binops_additive!(ExtendedPoint, AffineNielsPoint); + +impl<'a, 'b> Add<&'b ExtendedPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn add(self, other: &'b ExtendedPoint) -> ExtendedPoint { + self + other.to_niels() + } +} + +impl<'a, 'b> Sub<&'b ExtendedPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn sub(self, other: &'b ExtendedPoint) -> ExtendedPoint { + self - other.to_niels() + } +} + +impl_binops_additive!(ExtendedPoint, ExtendedPoint); + +impl<'a, 'b> Add<&'b AffinePoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn add(self, other: &'b AffinePoint) -> ExtendedPoint { + self + other.to_niels() + } +} + +impl<'a, 'b> Sub<&'b AffinePoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn sub(self, other: &'b AffinePoint) -> ExtendedPoint { + self - other.to_niels() + } +} + +impl_binops_additive!(ExtendedPoint, AffinePoint); + +/// This is a "completed" point produced during a point doubling or +/// addition routine. These points exist in the `(U:Z, V:T)` model +/// of the curve. This is not exposed in the API because it is +/// an implementation detail. +struct CompletedPoint { + u: Fq, + v: Fq, + z: Fq, + t: Fq, +} + +impl CompletedPoint { + /// This converts a completed point into an extended point by + /// homogenizing: + /// + /// (u/z, v/t) = (u/z * t/t, v/t * z/z) = (ut/zt, vz/zt) + /// + /// The resulting T coordinate is utvz/zt = uv, and so + /// T1 = u, T2 = v, without loss of generality. + #[inline] + fn into_extended(self) -> ExtendedPoint { + ExtendedPoint { + u: self.u * self.t, + v: self.v * self.z, + z: self.z * self.t, + t1: self.u, + t2: self.v, + } + } +} + +impl Default for AffinePoint { + /// Returns the identity. + fn default() -> AffinePoint { + AffinePoint::identity() + } +} + +impl Default for ExtendedPoint { + /// Returns the identity. + fn default() -> ExtendedPoint { + ExtendedPoint::identity() + } +} + +/// This takes a mutable slice of `ExtendedPoint`s and "normalizes" them using +/// only a single inversion for the entire batch. This normalization results in +/// all of the points having a Z-coordinate of one. Further, an iterator is +/// returned which can be used to obtain `AffinePoint`s for each element in the +/// slice. +/// +/// This costs 5 multiplications per element, and a field inversion. +pub fn batch_normalize<'a>(v: &'a mut [ExtendedPoint]) -> impl Iterator + 'a { + let mut acc = Fq::one(); + for p in v.iter_mut() { + // We use the `t1` field of `ExtendedPoint` to store the product + // of previous z-coordinates seen. + p.t1 = acc; + acc *= &p.z; + } + + // This is the inverse, as all z-coordinates are nonzero. + acc = acc.invert().unwrap(); + + for p in v.iter_mut().rev() { + let mut q = *p; + + // Compute tmp = 1/z + let tmp = q.t1 * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc *= &q.z; + + // Set the coordinates to the correct value + q.u *= &tmp; // Multiply by 1/z + q.v *= &tmp; // Multiply by 1/z + q.z = Fq::one(); // z-coordinate is now one + q.t1 = q.u; + q.t2 = q.v; + + *p = q; + } + + // All extended points are now normalized, but the type + // doesn't encode this fact. Let us offer affine points + // to the caller. + + v.iter().map(|p| AffinePoint { u: p.u, v: p.v }) +} + +#[test] +fn test_is_on_curve_var() { + assert!(AffinePoint::identity().is_on_curve_vartime()); +} + +#[test] +fn test_d_is_non_quadratic_residue() { + assert!(EDWARDS_D.sqrt().is_none().unwrap_u8() == 1); + assert!((-EDWARDS_D).sqrt().is_none().unwrap_u8() == 1); + assert!((-EDWARDS_D).invert().unwrap().sqrt().is_none().unwrap_u8() == 1); +} + +#[test] +fn test_affine_niels_point_identity() { + assert_eq!( + AffineNielsPoint::identity().v_plus_u, + AffinePoint::identity().to_niels().v_plus_u + ); + assert_eq!( + AffineNielsPoint::identity().v_minus_u, + AffinePoint::identity().to_niels().v_minus_u + ); + assert_eq!( + AffineNielsPoint::identity().t2d, + AffinePoint::identity().to_niels().t2d + ); +} + +#[test] +fn test_extended_niels_point_identity() { + assert_eq!( + ExtendedNielsPoint::identity().v_plus_u, + ExtendedPoint::identity().to_niels().v_plus_u + ); + assert_eq!( + ExtendedNielsPoint::identity().v_minus_u, + ExtendedPoint::identity().to_niels().v_minus_u + ); + assert_eq!( + ExtendedNielsPoint::identity().z, + ExtendedPoint::identity().to_niels().z + ); + assert_eq!( + ExtendedNielsPoint::identity().t2d, + ExtendedPoint::identity().to_niels().t2d + ); +} + +#[test] +fn test_assoc() { + let p = ExtendedPoint::from(AffinePoint { + u: Fq::from_raw([ + 0x81c5_71e5_d883_cfb0, + 0x049f_7a68_6f14_7029, + 0xf539_c860_bc3e_a21f, + 0x4284_715b_7ccc_8162, + ]), + v: Fq::from_raw([ + 0xbf09_6275_684b_b8ca, + 0xc7ba_2458_90af_256d, + 0x5911_9f3e_8638_0eb0, + 0x3793_de18_2f9f_b1d2, + ]), + }) + .mul_by_cofactor(); + assert!(p.is_on_curve_vartime()); + + assert_eq!( + (p * Fr::from(1000u64)) * Fr::from(3938u64), + p * (Fr::from(1000u64) * Fr::from(3938u64)), + ); +} + +#[test] +fn test_batch_normalize() { + let mut p = ExtendedPoint::from(AffinePoint { + u: Fq::from_raw([ + 0x81c5_71e5_d883_cfb0, + 0x049f_7a68_6f14_7029, + 0xf539_c860_bc3e_a21f, + 0x4284_715b_7ccc_8162, + ]), + v: Fq::from_raw([ + 0xbf09_6275_684b_b8ca, + 0xc7ba_2458_90af_256d, + 0x5911_9f3e_8638_0eb0, + 0x3793_de18_2f9f_b1d2, + ]), + }) + .mul_by_cofactor(); + + let mut v = vec![]; + for _ in 0..10 { + v.push(p); + p = p.double(); + } + + for p in &v { + assert!(p.is_on_curve_vartime()); + } + + let expected: std::vec::Vec<_> = v.iter().map(|p| AffinePoint::from(*p)).collect(); + let result1: std::vec::Vec<_> = batch_normalize(&mut v).collect(); + for i in 0..10 { + assert!(expected[i] == result1[i]); + assert!(v[i].is_on_curve_vartime()); + assert!(AffinePoint::from(v[i]) == expected[i]); + } + let result2: std::vec::Vec<_> = batch_normalize(&mut v).collect(); + for i in 0..10 { + assert!(expected[i] == result2[i]); + assert!(v[i].is_on_curve_vartime()); + assert!(AffinePoint::from(v[i]) == expected[i]); + } +} + +#[cfg(test)] +const FULL_GENERATOR: AffinePoint = AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xe4b3_d35d_f1a7_adfe, + 0xcaf5_5d1b_29bf_81af, + 0x8b0f_03dd_d60a_8187, + 0x62ed_cbb8_bf37_87c8, + ]), + Fq::from_raw([0xb, 0x0, 0x0, 0x0]), +); + +#[cfg(test)] +const EIGHT_TORSION: [AffinePoint; 8] = [ + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xd92e_6a79_2720_0d43, + 0x7aa4_1ac4_3dae_8582, + 0xeaaa_e086_a166_18d1, + 0x71d4_df38_ba9e_7973, + ]), + Fq::from_raw([ + 0xff0d_2068_eff4_96dd, + 0x9106_ee90_f384_a4a1, + 0x16a1_3035_ad4d_7266, + 0x4958_bdb2_1966_982e, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xfffe_ffff_0000_0001, + 0x67ba_a400_89fb_5bfe, + 0xa5e8_0b39_939e_d334, + 0x73ed_a753_299d_7d47, + ]), + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xd92e_6a79_2720_0d43, + 0x7aa4_1ac4_3dae_8582, + 0xeaaa_e086_a166_18d1, + 0x71d4_df38_ba9e_7973, + ]), + Fq::from_raw([ + 0x00f2_df96_100b_6924, + 0xc2b6_b572_0c79_b75d, + 0x1c98_a7d2_5c54_659e, + 0x2a94_e9a1_1036_e51a, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + Fq::from_raw([ + 0xffff_ffff_0000_0000, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x26d1_9585_d8df_f2be, + 0xd919_893e_c24f_d67c, + 0x488e_f781_683b_bf33, + 0x0218_c81a_6eff_03d4, + ]), + Fq::from_raw([ + 0x00f2_df96_100b_6924, + 0xc2b6_b572_0c79_b75d, + 0x1c98_a7d2_5c54_659e, + 0x2a94_e9a1_1036_e51a, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x0001_0000_0000_0000, + 0xec03_0002_7603_0000, + 0x8d51_ccce_7603_04d0, + 0x0, + ]), + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x26d1_9585_d8df_f2be, + 0xd919_893e_c24f_d67c, + 0x488e_f781_683b_bf33, + 0x0218_c81a_6eff_03d4, + ]), + Fq::from_raw([ + 0xff0d_2068_eff4_96dd, + 0x9106_ee90_f384_a4a1, + 0x16a1_3035_ad4d_7266, + 0x4958_bdb2_1966_982e, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + Fq::from_raw([0x1, 0x0, 0x0, 0x0]), + ), +]; + +#[test] +fn find_eight_torsion() { + let g = ExtendedPoint::from(FULL_GENERATOR); + assert!(g.is_small_order().unwrap_u8() == 0); + let g = g.multiply(&FR_MODULUS_BYTES); + assert!(g.is_small_order().unwrap_u8() == 1); + + let mut cur = g; + + for (i, point) in EIGHT_TORSION.iter().enumerate() { + let tmp = AffinePoint::from(cur); + if &tmp != point { + panic!("{}th torsion point should be {:?}", i, tmp); + } + + cur += &g; + } +} + +#[test] +fn find_curve_generator() { + let mut trial_bytes = [0; 32]; + for _ in 0..255 { + let a = AffinePoint::from_bytes(trial_bytes); + if a.is_some().unwrap_u8() == 1 { + let a = a.unwrap(); + assert!(a.is_on_curve_vartime()); + let b = ExtendedPoint::from(a); + let b = b.multiply(&FR_MODULUS_BYTES); + assert!(b.is_small_order().unwrap_u8() == 1); + let b = b.double(); + assert!(b.is_small_order().unwrap_u8() == 1); + let b = b.double(); + assert!(b.is_small_order().unwrap_u8() == 1); + if b.is_identity().unwrap_u8() == 0 { + let b = b.double(); + assert!(b.is_small_order().unwrap_u8() == 1); + assert!(b.is_identity().unwrap_u8() == 1); + assert_eq!(FULL_GENERATOR, a); + assert!(a.mul_by_cofactor().is_torsion_free().unwrap_u8() == 1); + return; + } + } + + trial_bytes[0] += 1; + } + + panic!("should have found a generator of the curve"); +} + +#[test] +fn test_small_order() { + for point in EIGHT_TORSION.iter() { + assert!(point.is_small_order().unwrap_u8() == 1); + } +} + +#[test] +fn test_is_identity() { + let a = EIGHT_TORSION[0].mul_by_cofactor(); + let b = EIGHT_TORSION[1].mul_by_cofactor(); + + assert_eq!(a.u, b.u); + assert_eq!(a.v, a.z); + assert_eq!(b.v, b.z); + assert!(a.v != b.v); + assert!(a.z != b.z); + + assert!(a.is_identity().unwrap_u8() == 1); + assert!(b.is_identity().unwrap_u8() == 1); + + for point in EIGHT_TORSION.iter() { + assert!(point.mul_by_cofactor().is_identity().unwrap_u8() == 1); + } +} + +#[test] +fn test_mul_consistency() { + let a = Fr([ + 0x21e6_1211_d993_4f2e, + 0xa52c_058a_693c_3e07, + 0x9ccb_77bf_b12d_6360, + 0x07df_2470_ec94_398e, + ]); + let b = Fr([ + 0x0333_6d1c_be19_dbe0, + 0x0153_618f_6156_a536, + 0x2604_c9e1_fc3c_6b15, + 0x04ae_581c_eb02_8720, + ]); + let c = Fr([ + 0xd7ab_f5bb_2468_3f4c, + 0x9d77_12cc_274b_7c03, + 0x9732_93db_9683_789f, + 0x0b67_7e29_380a_97a7, + ]); + assert_eq!(a * b, c); + let p = ExtendedPoint::from(AffinePoint { + u: Fq::from_raw([ + 0x81c5_71e5_d883_cfb0, + 0x049f_7a68_6f14_7029, + 0xf539_c860_bc3e_a21f, + 0x4284_715b_7ccc_8162, + ]), + v: Fq::from_raw([ + 0xbf09_6275_684b_b8ca, + 0xc7ba_2458_90af_256d, + 0x5911_9f3e_8638_0eb0, + 0x3793_de18_2f9f_b1d2, + ]), + }) + .mul_by_cofactor(); + assert_eq!(p * c, (p * a) * b); + + // Test Mul implemented on ExtendedNielsPoint + assert_eq!(p * c, (p.to_niels() * a) * b); + assert_eq!(p.to_niels() * c, (p * a) * b); + assert_eq!(p.to_niels() * c, (p.to_niels() * a) * b); + + // Test Mul implemented on AffineNielsPoint + let p_affine_niels = AffinePoint::from(p).to_niels(); + assert_eq!(p * c, (p_affine_niels * a) * b); + assert_eq!(p_affine_niels * c, (p * a) * b); + assert_eq!(p_affine_niels * c, (p_affine_niels * a) * b); +} + +#[test] +fn test_serialization_consistency() { + let gen = FULL_GENERATOR.mul_by_cofactor(); + let mut p = gen; + + let v = vec![ + [ + 203, 85, 12, 213, 56, 234, 12, 193, 19, 132, 128, 64, 142, 110, 170, 185, 179, 108, 97, + 63, 13, 211, 247, 120, 79, 219, 110, 234, 131, 123, 19, 215, + ], + [ + 113, 154, 240, 230, 224, 198, 208, 170, 104, 15, 59, 126, 151, 222, 233, 195, 203, 195, + 167, 129, 89, 121, 240, 142, 51, 166, 64, 250, 184, 202, 154, 177, + ], + [ + 197, 41, 93, 209, 203, 55, 164, 174, 88, 0, 90, 199, 1, 156, 149, 141, 240, 29, 14, 82, + 86, 225, 126, 129, 186, 157, 148, 162, 219, 51, 156, 199, + ], + [ + 182, 117, 250, 241, 81, 196, 199, 227, 151, 74, 243, 17, 221, 97, 200, 139, 192, 83, + 231, 35, 214, 14, 95, 69, 130, 201, 4, 116, 177, 19, 179, 0, + ], + [ + 118, 41, 29, 200, 60, 189, 119, 252, 78, 40, 230, 18, 208, 221, 38, 214, 176, 250, 4, + 10, 77, 101, 26, 216, 193, 198, 226, 84, 25, 177, 230, 185, + ], + [ + 226, 189, 227, 208, 112, 117, 136, 98, 72, 38, 211, 167, 254, 82, 174, 113, 112, 166, + 138, 171, 166, 113, 52, 251, 129, 197, 138, 45, 195, 7, 61, 140, + ], + [ + 38, 198, 156, 196, 146, 225, 55, 163, 138, 178, 157, 128, 115, 135, 204, 215, 0, 33, + 171, 20, 60, 32, 142, 209, 33, 233, 125, 146, 207, 12, 16, 24, + ], + [ + 17, 187, 231, 83, 165, 36, 232, 184, 140, 205, 195, 252, 166, 85, 59, 86, 3, 226, 211, + 67, 179, 29, 238, 181, 102, 142, 58, 63, 57, 89, 174, 138, + ], + [ + 210, 159, 80, 16, 181, 39, 221, 204, 224, 144, 145, 79, 54, 231, 8, 140, 142, 216, 93, + 190, 183, 116, 174, 63, 33, 242, 177, 118, 148, 40, 241, 203, + ], + [ + 0, 143, 107, 102, 149, 187, 27, 124, 18, 10, 98, 28, 113, 123, 121, 185, 29, 152, 14, + 130, 149, 28, 87, 35, 135, 135, 153, 54, 112, 53, 54, 68, + ], + [ + 178, 131, 85, 160, 214, 51, 208, 157, 196, 152, 247, 93, 202, 56, 81, 239, 155, 122, + 59, 188, 237, 253, 11, 169, 208, 236, 12, 4, 163, 211, 88, 97, + ], + [ + 246, 194, 231, 195, 159, 101, 180, 133, 80, 21, 185, 220, 195, 115, 144, 12, 90, 150, + 44, 117, 8, 156, 168, 248, 206, 41, 60, 82, 67, 75, 57, 67, + ], + [ + 212, 205, 171, 153, 113, 16, 194, 241, 224, 43, 177, 110, 190, 248, 22, 201, 208, 166, + 2, 83, 134, 130, 85, 129, 166, 136, 185, 191, 163, 38, 54, 10, + ], + [ + 8, 60, 190, 39, 153, 222, 119, 23, 142, 237, 12, 110, 146, 9, 19, 219, 143, 64, 161, + 99, 199, 77, 39, 148, 70, 213, 246, 227, 150, 178, 237, 178, + ], + [ + 11, 114, 217, 160, 101, 37, 100, 220, 56, 114, 42, 31, 138, 33, 84, 157, 214, 167, 73, + 233, 115, 81, 124, 134, 15, 31, 181, 60, 184, 130, 175, 159, + ], + [ + 141, 238, 235, 202, 241, 32, 210, 10, 127, 230, 54, 31, 146, 80, 247, 9, 107, 124, 0, + 26, 203, 16, 237, 34, 214, 147, 133, 15, 29, 236, 37, 88, + ], + ]; + + for expected_serialized in v { + assert!(p.is_on_curve_vartime()); + let affine = AffinePoint::from(p); + let serialized = affine.to_bytes(); + let deserialized = AffinePoint::from_bytes(serialized).unwrap(); + assert_eq!(affine, deserialized); + assert_eq!(expected_serialized, serialized); + p += gen; + } +} diff --git a/jubjub/src/util.rs b/jubjub/src/util.rs new file mode 100644 index 0000000..bd25dd0 --- /dev/null +++ b/jubjub/src/util.rs @@ -0,0 +1,174 @@ +/// Compute a + b + carry, returning the result and the new carry over. +#[inline(always)] +pub const fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) { + let ret = (a as u128) + (b as u128) + (carry as u128); + (ret as u64, (ret >> 64) as u64) +} + +/// Compute a - (b + borrow), returning the result and the new borrow. +#[inline(always)] +pub const fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) { + let ret = (a as u128).wrapping_sub((b as u128) + ((borrow >> 63) as u128)); + (ret as u64, (ret >> 64) as u64) +} + +/// Compute a + (b * c) + carry, returning the result and the new carry over. +#[inline(always)] +pub const fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) { + let ret = (a as u128) + ((b as u128) * (c as u128)) + (carry as u128); + (ret as u64, (ret >> 64) as u64) +} + +macro_rules! impl_add_binop_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Add<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: &'b $rhs) -> $output { + &self + rhs + } + } + + impl<'a> Add<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: $rhs) -> $output { + self + &rhs + } + } + + impl Add<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: $rhs) -> $output { + &self + &rhs + } + } + }; +} + +macro_rules! impl_sub_binop_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Sub<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: &'b $rhs) -> $output { + &self - rhs + } + } + + impl<'a> Sub<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: $rhs) -> $output { + self - &rhs + } + } + + impl Sub<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: $rhs) -> $output { + &self - &rhs + } + } + }; +} + +macro_rules! impl_binops_additive_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl_add_binop_specify_output!($lhs, $rhs, $output); + impl_sub_binop_specify_output!($lhs, $rhs, $output); + }; +} + +macro_rules! impl_binops_multiplicative_mixed { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Mul<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: &'b $rhs) -> $output { + &self * rhs + } + } + + impl<'a> Mul<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: $rhs) -> $output { + self * &rhs + } + } + + impl Mul<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: $rhs) -> $output { + &self * &rhs + } + } + }; +} + +macro_rules! impl_binops_additive { + ($lhs:ident, $rhs:ident) => { + impl_binops_additive_specify_output!($lhs, $rhs, $lhs); + + impl SubAssign<$rhs> for $lhs { + #[inline] + fn sub_assign(&mut self, rhs: $rhs) { + *self = &*self - &rhs; + } + } + + impl AddAssign<$rhs> for $lhs { + #[inline] + fn add_assign(&mut self, rhs: $rhs) { + *self = &*self + &rhs; + } + } + + impl<'b> SubAssign<&'b $rhs> for $lhs { + #[inline] + fn sub_assign(&mut self, rhs: &'b $rhs) { + *self = &*self - rhs; + } + } + + impl<'b> AddAssign<&'b $rhs> for $lhs { + #[inline] + fn add_assign(&mut self, rhs: &'b $rhs) { + *self = &*self + rhs; + } + } + }; +} + +macro_rules! impl_binops_multiplicative { + ($lhs:ident, $rhs:ident) => { + impl_binops_multiplicative_mixed!($lhs, $rhs, $lhs); + + impl MulAssign<$rhs> for $lhs { + #[inline] + fn mul_assign(&mut self, rhs: $rhs) { + *self = &*self * &rhs; + } + } + + impl<'b> MulAssign<&'b $rhs> for $lhs { + #[inline] + fn mul_assign(&mut self, rhs: &'b $rhs) { + *self = &*self * rhs; + } + } + }; +} diff --git a/jubjub/tests/common.rs b/jubjub/tests/common.rs new file mode 100644 index 0000000..a4535ed --- /dev/null +++ b/jubjub/tests/common.rs @@ -0,0 +1,29 @@ +use jubjub::*; +use rand_core::{RngCore, SeedableRng}; +use rand_xorshift::XorShiftRng; + +pub const NUM_BLACK_BOX_CHECKS: u32 = 2000; + +pub fn new_rng() -> XorShiftRng { + XorShiftRng::from_seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) +} + +pub trait MyRandom { + fn new_random(rng: &mut T) -> Self; +} + +impl MyRandom for Fq { + fn new_random(rng: &mut T) -> Self { + let mut random_bytes = [0u8; 64]; + rng.fill_bytes(&mut random_bytes); + Fq::from_bytes_wide(&random_bytes) + } +} + +impl MyRandom for Fr { + fn new_random(rng: &mut T) -> Self { + let mut random_bytes = [0u8; 64]; + rng.fill_bytes(&mut random_bytes); + Fr::from_bytes_wide(&random_bytes) + } +} diff --git a/jubjub/tests/fq_blackbox.rs b/jubjub/tests/fq_blackbox.rs new file mode 100644 index 0000000..a823c9b --- /dev/null +++ b/jubjub/tests/fq_blackbox.rs @@ -0,0 +1,120 @@ +mod common; + +use common::{new_rng, MyRandom, NUM_BLACK_BOX_CHECKS}; +use jubjub::*; + +#[test] +fn test_to_and_from_bytes() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, Fq::from_bytes(&Fq::to_bytes(&a)).unwrap()); + } +} + +#[test] +fn test_additive_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + let c = Fq::new_random(&mut rng); + assert_eq!((a + b) + c, a + (b + c)) + } +} + +#[test] +fn test_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, a + Fq::zero()); + assert_eq!(a, Fq::zero() + a); + } +} + +#[test] +fn test_subtract_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, a - Fq::zero()); + assert_eq!(a, Fq::zero() - -&a); + } +} + +#[test] +fn test_additive_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let a_neg = -&a; + assert_eq!(Fq::zero(), a + a_neg); + assert_eq!(Fq::zero(), a_neg + a); + } +} + +#[test] +fn test_additive_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + assert_eq!(a + b, b + a); + } +} + +#[test] +fn test_multiplicative_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + let c = Fq::new_random(&mut rng); + assert_eq!((a * b) * c, a * (b * c)) + } +} + +#[test] +fn test_multiplicative_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, a * Fq::one()); + assert_eq!(a, Fq::one() * a); + } +} + +#[test] +fn test_multiplicative_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + if a == Fq::zero() { + continue; + } + let a_inv = a.invert().unwrap(); + assert_eq!(Fq::one(), a * a_inv); + assert_eq!(Fq::one(), a_inv * a); + } +} + +#[test] +fn test_multiplicative_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + assert_eq!(a * b, b * a); + } +} + +#[test] +fn test_multiply_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(Fq::zero(), Fq::zero() * a); + assert_eq!(Fq::zero(), a * Fq::zero()); + } +} diff --git a/jubjub/tests/fr_blackbox.rs b/jubjub/tests/fr_blackbox.rs new file mode 100644 index 0000000..6e36d0f --- /dev/null +++ b/jubjub/tests/fr_blackbox.rs @@ -0,0 +1,120 @@ +mod common; + +use common::{new_rng, MyRandom, NUM_BLACK_BOX_CHECKS}; +use jubjub::*; + +#[test] +fn test_to_and_from_bytes() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, Fr::from_bytes(&Fr::to_bytes(&a)).unwrap()); + } +} + +#[test] +fn test_additive_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + let c = Fr::new_random(&mut rng); + assert_eq!((a + b) + c, a + (b + c)) + } +} + +#[test] +fn test_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, a + Fr::zero()); + assert_eq!(a, Fr::zero() + a); + } +} + +#[test] +fn test_subtract_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, a - Fr::zero()); + assert_eq!(a, Fr::zero() - -&a); + } +} + +#[test] +fn test_additive_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let a_neg = -&a; + assert_eq!(Fr::zero(), a + a_neg); + assert_eq!(Fr::zero(), a_neg + a); + } +} + +#[test] +fn test_additive_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + assert_eq!(a + b, b + a); + } +} + +#[test] +fn test_multiplicative_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + let c = Fr::new_random(&mut rng); + assert_eq!((a * b) * c, a * (b * c)) + } +} + +#[test] +fn test_multiplicative_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, a * Fr::one()); + assert_eq!(a, Fr::one() * a); + } +} + +#[test] +fn test_multiplicative_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + if a == Fr::zero() { + continue; + } + let a_inv = a.invert().unwrap(); + assert_eq!(Fr::one(), a * a_inv); + assert_eq!(Fr::one(), a_inv * a); + } +} + +#[test] +fn test_multiplicative_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + assert_eq!(a * b, b * a); + } +} + +#[test] +fn test_multiply_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(Fr::zero(), Fr::zero() * a); + assert_eq!(Fr::zero(), a * Fr::zero()); + } +} diff --git a/librustzcash/src/tests/key_agreement.rs b/librustzcash/src/tests/key_agreement.rs index c4f56a1..909c6fa 100644 --- a/librustzcash/src/tests/key_agreement.rs +++ b/librustzcash/src/tests/key_agreement.rs @@ -24,9 +24,8 @@ fn test_key_agreement() { let addr = loop { let mut d = [0; 11]; rng.fill_bytes(&mut d); - match vk.to_payment_address(Diversifier(d), ¶ms) { - Some(a) => break a, - None => {} + if let Some(a) = vk.to_payment_address(Diversifier(d), ¶ms) { + break a; } }; diff --git a/pairing/Cargo.toml b/pairing/Cargo.toml index daf6018..5bd5e1e 100644 --- a/pairing/Cargo.toml +++ b/pairing/Cargo.toml @@ -21,8 +21,10 @@ byteorder = "1" ff = { version = "0.6", path = "../ff", features = ["derive"] } group = { version = "0.6", path = "../group" } rand_core = "0.5" +subtle = "2.2.1" [dev-dependencies] +criterion = "0.3" rand_xorshift = "0.2" [features] @@ -30,5 +32,9 @@ unstable-features = ["expose-arith"] expose-arith = [] default = [] +[[bench]] +name = "pairing_benches" +harness = false + [badges] maintenance = { status = "actively-developed" } diff --git a/pairing/benches/bls12_381/ec.rs b/pairing/benches/bls12_381/ec.rs index 04bed0d..28ce4bf 100644 --- a/pairing/benches/bls12_381/ec.rs +++ b/pairing/benches/bls12_381/ec.rs @@ -1,13 +1,14 @@ -mod g1 { +pub(crate) mod g1 { + use criterion::{criterion_group, Criterion}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; + use std::ops::AddAssign; use ff::Field; use group::CurveProjective; use pairing::bls12_381::*; - #[bench] - fn bench_g1_mul_assign(b: &mut ::test::Bencher) { + fn bench_g1_mul_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -20,16 +21,17 @@ mod g1 { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G1::mul_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } - #[bench] - fn bench_g1_add_assign(b: &mut ::test::Bencher) { + fn bench_g1_add_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -42,16 +44,17 @@ mod g1 { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G1::add_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } - #[bench] - fn bench_g1_add_assign_mixed(b: &mut ::test::Bencher) { + fn bench_g1_add_assign_mixed(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -64,25 +67,35 @@ mod g1 { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign_mixed(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G1::add_assign_mixed", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } + + criterion_group!( + benches, + bench_g1_add_assign, + bench_g1_add_assign_mixed, + bench_g1_mul_assign, + ); } -mod g2 { +pub(crate) mod g2 { + use criterion::{criterion_group, Criterion}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; + use std::ops::AddAssign; use ff::Field; use group::CurveProjective; use pairing::bls12_381::*; - #[bench] - fn bench_g2_mul_assign(b: &mut ::test::Bencher) { + fn bench_g2_mul_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -95,16 +108,17 @@ mod g2 { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G2::mul_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } - #[bench] - fn bench_g2_add_assign(b: &mut ::test::Bencher) { + fn bench_g2_add_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -117,16 +131,17 @@ mod g2 { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G2::add_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } - #[bench] - fn bench_g2_add_assign_mixed(b: &mut ::test::Bencher) { + fn bench_g2_add_assign_mixed(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -139,11 +154,20 @@ mod g2 { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign_mixed(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G2::add_assign_mixed", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } + + criterion_group!( + benches, + bench_g2_add_assign, + bench_g2_add_assign_mixed, + bench_g2_mul_assign, + ); } diff --git a/pairing/benches/bls12_381/fq.rs b/pairing/benches/bls12_381/fq.rs index b663322..244c161 100644 --- a/pairing/benches/bls12_381/fq.rs +++ b/pairing/benches/bls12_381/fq.rs @@ -1,11 +1,12 @@ +use criterion::{criterion_group, Criterion}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; use pairing::bls12_381::*; -#[bench] -fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { +fn bench_fq_repr_add_nocarry(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -27,16 +28,17 @@ fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_nocarry(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FqRepr::add_nocarry", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_repr_sub_noborrow(b: &mut ::test::Bencher) { +fn bench_fq_repr_sub_noborrow(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -57,16 +59,17 @@ fn bench_fq_repr_sub_noborrow(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_noborrow(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FqRepr::sub_noborrow", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_repr_num_bits(b: &mut ::test::Bencher) { +fn bench_fq_repr_num_bits(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -79,15 +82,16 @@ fn bench_fq_repr_num_bits(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let tmp = v[count].num_bits(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FqRepr::num_bits", |b| { + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_repr_mul2(b: &mut ::test::Bencher) { +fn bench_fq_repr_mul2(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -100,16 +104,17 @@ fn bench_fq_repr_mul2(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.mul2(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FqRepr::mul2", |b| { + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_repr_div2(b: &mut ::test::Bencher) { +fn bench_fq_repr_div2(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -122,16 +127,17 @@ fn bench_fq_repr_div2(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.div2(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FqRepr::div2", |b| { + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_add_assign(b: &mut ::test::Bencher) { +fn bench_fq_add_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -144,16 +150,17 @@ fn bench_fq_add_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq::add_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_sub_assign(b: &mut ::test::Bencher) { +fn bench_fq_sub_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -166,16 +173,17 @@ fn bench_fq_sub_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq::sub_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_mul_assign(b: &mut ::test::Bencher) { +fn bench_fq_mul_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -188,16 +196,17 @@ fn bench_fq_mul_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq::mul_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_square(b: &mut ::test::Bencher) { +fn bench_fq_square(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -208,16 +217,16 @@ fn bench_fq_square(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq::square", |b| { + b.iter(|| { + let tmp = v[count].square(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_inverse(b: &mut ::test::Bencher) { +fn bench_fq_invert(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -228,14 +237,15 @@ fn bench_fq_inverse(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].inverse() + c.bench_function("Fq::invert", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].invert() + }) }); } -#[bench] -fn bench_fq_negate(b: &mut ::test::Bencher) { +fn bench_fq_neg(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -246,16 +256,16 @@ fn bench_fq_negate(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.negate(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq::neg", |b| { + b.iter(|| { + let tmp = v[count].neg(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq_sqrt(b: &mut ::test::Bencher) { +fn bench_fq_sqrt(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -264,22 +274,19 @@ fn bench_fq_sqrt(b: &mut ::test::Bencher) { ]); let v: Vec = (0..SAMPLES) - .map(|_| { - let mut tmp = Fq::random(&mut rng); - tmp.square(); - tmp - }) + .map(|_| Fq::random(&mut rng).square()) .collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].sqrt() + c.bench_function("Fq::sqrt", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }) }); } -#[bench] -fn bench_fq_into_repr(b: &mut ::test::Bencher) { +fn bench_fq_into_repr(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -290,14 +297,15 @@ fn bench_fq_into_repr(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].into_repr() + c.bench_function("Fq::into_repr", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }) }); } -#[bench] -fn bench_fq_from_repr(b: &mut ::test::Bencher) { +fn bench_fq_from_repr(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -310,8 +318,28 @@ fn bench_fq_from_repr(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - Fq::from_repr(v[count]) + c.bench_function("Fq::from_repr", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + Fq::from_repr(v[count]) + }) }); } + +criterion_group!( + benches, + bench_fq_repr_add_nocarry, + bench_fq_repr_sub_noborrow, + bench_fq_repr_num_bits, + bench_fq_repr_mul2, + bench_fq_repr_div2, + bench_fq_add_assign, + bench_fq_sub_assign, + bench_fq_mul_assign, + bench_fq_square, + bench_fq_invert, + bench_fq_neg, + bench_fq_sqrt, + bench_fq_into_repr, + bench_fq_from_repr, +); diff --git a/pairing/benches/bls12_381/fq12.rs b/pairing/benches/bls12_381/fq12.rs index 8bf0392..2d6ed49 100644 --- a/pairing/benches/bls12_381/fq12.rs +++ b/pairing/benches/bls12_381/fq12.rs @@ -1,11 +1,12 @@ +use criterion::{criterion_group, Criterion}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; +use std::ops::{AddAssign, MulAssign, SubAssign}; use ff::Field; use pairing::bls12_381::*; -#[bench] -fn bench_fq12_add_assign(b: &mut ::test::Bencher) { +fn bench_fq12_add_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -18,16 +19,17 @@ fn bench_fq12_add_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq12::add_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq12_sub_assign(b: &mut ::test::Bencher) { +fn bench_fq12_sub_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -40,16 +42,17 @@ fn bench_fq12_sub_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq12::sub_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq12_mul_assign(b: &mut ::test::Bencher) { +fn bench_fq12_mul_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -62,16 +65,17 @@ fn bench_fq12_mul_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq12::mul_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq12_squaring(b: &mut ::test::Bencher) { +fn bench_fq12_squaring(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -82,16 +86,16 @@ fn bench_fq12_squaring(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq12::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq12::square", |b| { + b.iter(|| { + let tmp = v[count].square(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq12_inverse(b: &mut ::test::Bencher) { +fn bench_fq12_invert(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -102,9 +106,20 @@ fn bench_fq12_inverse(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq12::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let tmp = v[count].inverse(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq12::invert", |b| { + b.iter(|| { + let tmp = v[count].invert(); + count = (count + 1) % SAMPLES; + tmp + }) }); } + +criterion_group!( + benches, + bench_fq12_add_assign, + bench_fq12_sub_assign, + bench_fq12_mul_assign, + bench_fq12_squaring, + bench_fq12_invert, +); diff --git a/pairing/benches/bls12_381/fq2.rs b/pairing/benches/bls12_381/fq2.rs index 028c42e..1eebb92 100644 --- a/pairing/benches/bls12_381/fq2.rs +++ b/pairing/benches/bls12_381/fq2.rs @@ -1,11 +1,12 @@ +use criterion::{criterion_group, Criterion}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; +use std::ops::{AddAssign, MulAssign, SubAssign}; use ff::{Field, SqrtField}; use pairing::bls12_381::*; -#[bench] -fn bench_fq2_add_assign(b: &mut ::test::Bencher) { +fn bench_fq2_add_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -18,16 +19,17 @@ fn bench_fq2_add_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq2::add_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq2_sub_assign(b: &mut ::test::Bencher) { +fn bench_fq2_sub_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -40,16 +42,17 @@ fn bench_fq2_sub_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq2::sub_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq2_mul_assign(b: &mut ::test::Bencher) { +fn bench_fq2_mul_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -62,16 +65,17 @@ fn bench_fq2_mul_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq2::mul_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq2_squaring(b: &mut ::test::Bencher) { +fn bench_fq2_squaring(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -82,16 +86,16 @@ fn bench_fq2_squaring(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq2::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq2::square", |b| { + b.iter(|| { + let tmp = v[count].square(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq2_inverse(b: &mut ::test::Bencher) { +fn bench_fq2_invert(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -102,15 +106,16 @@ fn bench_fq2_inverse(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq2::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let tmp = v[count].inverse(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq2::invert", |b| { + b.iter(|| { + let tmp = v[count].invert(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fq2_sqrt(b: &mut ::test::Bencher) { +fn bench_fq2_sqrt(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -121,9 +126,21 @@ fn bench_fq2_sqrt(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fq2::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let tmp = v[count].sqrt(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fq2::sqrt", |b| { + b.iter(|| { + let tmp = v[count].sqrt(); + count = (count + 1) % SAMPLES; + tmp + }) }); } + +criterion_group!( + benches, + bench_fq2_add_assign, + bench_fq2_sub_assign, + bench_fq2_mul_assign, + bench_fq2_squaring, + bench_fq2_invert, + bench_fq2_sqrt, +); diff --git a/pairing/benches/bls12_381/fr.rs b/pairing/benches/bls12_381/fr.rs index 9e767d8..d2dbc4c 100644 --- a/pairing/benches/bls12_381/fr.rs +++ b/pairing/benches/bls12_381/fr.rs @@ -1,11 +1,12 @@ +use criterion::{criterion_group, Criterion}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; use pairing::bls12_381::*; -#[bench] -fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { +fn bench_fr_repr_add_nocarry(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -27,16 +28,17 @@ fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_nocarry(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FrRepr::add_nocarry", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_repr_sub_noborrow(b: &mut ::test::Bencher) { +fn bench_fr_repr_sub_noborrow(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -57,16 +59,17 @@ fn bench_fr_repr_sub_noborrow(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_noborrow(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FrRepr::sub_noborrow", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_repr_num_bits(b: &mut ::test::Bencher) { +fn bench_fr_repr_num_bits(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -79,15 +82,16 @@ fn bench_fr_repr_num_bits(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let tmp = v[count].num_bits(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FrRepr::num_bits", |b| { + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_repr_mul2(b: &mut ::test::Bencher) { +fn bench_fr_repr_mul2(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -100,16 +104,17 @@ fn bench_fr_repr_mul2(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.mul2(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FrRepr::mul2", |b| { + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_repr_div2(b: &mut ::test::Bencher) { +fn bench_fr_repr_div2(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -122,16 +127,17 @@ fn bench_fr_repr_div2(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.div2(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("FrRepr::div2", |b| { + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_add_assign(b: &mut ::test::Bencher) { +fn bench_fr_add_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -144,16 +150,17 @@ fn bench_fr_add_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fr::add_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_sub_assign(b: &mut ::test::Bencher) { +fn bench_fr_sub_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -166,16 +173,17 @@ fn bench_fr_sub_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fr::sub_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_mul_assign(b: &mut ::test::Bencher) { +fn bench_fr_mul_assign(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -188,16 +196,17 @@ fn bench_fr_mul_assign(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fr::mul_assign", |b| { + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_square(b: &mut ::test::Bencher) { +fn bench_fr_square(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -208,16 +217,16 @@ fn bench_fr_square(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fr::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fr::square", |b| { + b.iter(|| { + let tmp = v[count].square(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_inverse(b: &mut ::test::Bencher) { +fn bench_fr_invert(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -228,14 +237,15 @@ fn bench_fr_inverse(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fr::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].inverse() + c.bench_function("Fr::invert", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].invert() + }) }); } -#[bench] -fn bench_fr_negate(b: &mut ::test::Bencher) { +fn bench_fr_neg(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -246,16 +256,16 @@ fn bench_fr_negate(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fr::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.negate(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Fr::neg", |b| { + b.iter(|| { + let tmp = v[count].neg(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_fr_sqrt(b: &mut ::test::Bencher) { +fn bench_fr_sqrt(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -264,22 +274,19 @@ fn bench_fr_sqrt(b: &mut ::test::Bencher) { ]); let v: Vec = (0..SAMPLES) - .map(|_| { - let mut tmp = Fr::random(&mut rng); - tmp.square(); - tmp - }) + .map(|_| Fr::random(&mut rng).square()) .collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].sqrt() + c.bench_function("Fr::sqrt", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }) }); } -#[bench] -fn bench_fr_into_repr(b: &mut ::test::Bencher) { +fn bench_fr_into_repr(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -290,14 +297,15 @@ fn bench_fr_into_repr(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| Fr::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].into_repr() + c.bench_function("Fr::into_repr", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }) }); } -#[bench] -fn bench_fr_from_repr(b: &mut ::test::Bencher) { +fn bench_fr_from_repr(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -310,8 +318,28 @@ fn bench_fr_from_repr(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - Fr::from_repr(v[count]) + c.bench_function("Fr::from_repr", |b| { + b.iter(|| { + count = (count + 1) % SAMPLES; + Fr::from_repr(v[count]) + }) }); } + +criterion_group!( + benches, + bench_fr_repr_add_nocarry, + bench_fr_repr_sub_noborrow, + bench_fr_repr_num_bits, + bench_fr_repr_mul2, + bench_fr_repr_div2, + bench_fr_add_assign, + bench_fr_sub_assign, + bench_fr_mul_assign, + bench_fr_square, + bench_fr_invert, + bench_fr_neg, + bench_fr_sqrt, + bench_fr_into_repr, + bench_fr_from_repr, +); diff --git a/pairing/benches/bls12_381/mod.rs b/pairing/benches/bls12_381/mod.rs index 2c23c2a..aa2e2f0 100644 --- a/pairing/benches/bls12_381/mod.rs +++ b/pairing/benches/bls12_381/mod.rs @@ -1,9 +1,10 @@ -mod ec; -mod fq; -mod fq12; -mod fq2; -mod fr; +pub(crate) mod ec; +pub(crate) mod fq; +pub(crate) mod fq12; +pub(crate) mod fq2; +pub(crate) mod fr; +use criterion::{criterion_group, Criterion}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; @@ -11,8 +12,7 @@ use group::CurveProjective; use pairing::bls12_381::*; use pairing::{Engine, PairingCurveAffine}; -#[bench] -fn bench_pairing_g1_preparation(b: &mut ::test::Bencher) { +fn bench_pairing_g1_preparation(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -23,15 +23,16 @@ fn bench_pairing_g1_preparation(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| G1::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let tmp = G1Affine::from(v[count]).prepare(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G1 preparation", |b| { + b.iter(|| { + let tmp = G1Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_pairing_g2_preparation(b: &mut ::test::Bencher) { +fn bench_pairing_g2_preparation(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -42,15 +43,16 @@ fn bench_pairing_g2_preparation(b: &mut ::test::Bencher) { let v: Vec = (0..SAMPLES).map(|_| G2::random(&mut rng)).collect(); let mut count = 0; - b.iter(|| { - let tmp = G2Affine::from(v[count]).prepare(); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("G2 preparation", |b| { + b.iter(|| { + let tmp = G2Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_pairing_miller_loop(b: &mut ::test::Bencher) { +fn bench_pairing_miller_loop(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -68,15 +70,16 @@ fn bench_pairing_miller_loop(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let tmp = Bls12::miller_loop(&[(&v[count].0, &v[count].1)]); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Miller loop", |b| { + b.iter(|| { + let tmp = Bls12::miller_loop(&[(&v[count].0, &v[count].1)]); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_pairing_final_exponentiation(b: &mut ::test::Bencher) { +fn bench_pairing_final_exponentiation(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -95,15 +98,16 @@ fn bench_pairing_final_exponentiation(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let tmp = Bls12::final_exponentiation(&v[count]); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Final exponentiation", |b| { + b.iter(|| { + let tmp = Bls12::final_exponentiation(&v[count]); + count = (count + 1) % SAMPLES; + tmp + }) }); } -#[bench] -fn bench_pairing_full(b: &mut ::test::Bencher) { +fn bench_pairing_full(c: &mut Criterion) { const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([ @@ -116,9 +120,20 @@ fn bench_pairing_full(b: &mut ::test::Bencher) { .collect(); let mut count = 0; - b.iter(|| { - let tmp = Bls12::pairing(v[count].0, v[count].1); - count = (count + 1) % SAMPLES; - tmp + c.bench_function("Full pairing", |b| { + b.iter(|| { + let tmp = Bls12::pairing(v[count].0, v[count].1); + count = (count + 1) % SAMPLES; + tmp + }) }); } + +criterion_group!( + benches, + bench_pairing_g1_preparation, + bench_pairing_g2_preparation, + bench_pairing_miller_loop, + bench_pairing_final_exponentiation, + bench_pairing_full, +); diff --git a/pairing/benches/pairing_benches.rs b/pairing/benches/pairing_benches.rs index b083b42..7abc824 100644 --- a/pairing/benches/pairing_benches.rs +++ b/pairing/benches/pairing_benches.rs @@ -1,10 +1,12 @@ -#![feature(test)] - -extern crate ff; -extern crate group; -extern crate pairing; -extern crate rand_core; -extern crate rand_xorshift; -extern crate test; - +use criterion::criterion_main; mod bls12_381; + +criterion_main!( + bls12_381::benches, + bls12_381::ec::g1::benches, + bls12_381::ec::g2::benches, + bls12_381::fq::benches, + bls12_381::fq12::benches, + bls12_381::fq2::benches, + bls12_381::fr::benches, +); diff --git a/pairing/src/bls12_381/ec.rs b/pairing/src/bls12_381/ec.rs index 019b19a..a87fe11 100644 --- a/pairing/src/bls12_381/ec.rs +++ b/pairing/src/bls12_381/ec.rs @@ -54,10 +54,8 @@ macro_rules! curve_impl { // are equal when (X * Z^2) = (X' * Z'^2) // and (Y * Z^3) = (Y' * Z'^3). - let mut z1 = self.z; - z1.square(); - let mut z2 = other.z; - z2.square(); + let mut z1 = self.z.square(); + let mut z2 = other.z.square(); let mut tmp1 = self.x; tmp1.mul_assign(&z2); @@ -88,7 +86,7 @@ macro_rules! curve_impl { for i in bits { res.double(); if i { - res.add_assign_mixed(self) + res.add_assign(self) } } res @@ -99,16 +97,14 @@ macro_rules! curve_impl { /// /// If and only if `greatest` is set will the lexicographically /// largest y-coordinate be selected. - fn get_point_from_x(x: $basefield, greatest: bool) -> Option<$affine> { + fn get_point_from_x(x: $basefield, greatest: bool) -> CtOption<$affine> { // Compute x^3 + b - let mut x3b = x; - x3b.square(); + let mut x3b = x.square(); x3b.mul_assign(&x); x3b.add_assign(&$affine::get_coeff_b()); x3b.sqrt().map(|y| { - let mut negy = y; - negy.negate(); + let negy = y.neg(); $affine { x: x, @@ -123,11 +119,9 @@ macro_rules! curve_impl { true } else { // Check that the point is on the curve - let mut y2 = self.y; - y2.square(); + let y2 = self.y.square(); - let mut x3b = self.x; - x3b.square(); + let mut x3b = self.x.square(); x3b.mul_assign(&self.x); x3b.add_assign(&Self::get_coeff_b()); @@ -140,6 +134,19 @@ macro_rules! curve_impl { } } + impl ::std::ops::Neg for $affine { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + let mut ret = self; + if !ret.is_zero() { + ret.y = ret.y.neg(); + } + ret + } + } + impl CurveAffine for $affine { type Engine = Bls12; type Scalar = $scalarfield; @@ -169,12 +176,6 @@ macro_rules! curve_impl { self.mul_bits(bits) } - fn negate(&mut self) { - if !self.is_zero() { - self.y.negate(); - } - } - fn into_projective(&self) -> $projective { (*self).into() } @@ -194,6 +195,309 @@ macro_rules! curve_impl { } } + impl ::std::ops::Neg for $projective { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + let mut ret = self; + if !ret.is_zero() { + ret.y = ret.y.neg(); + } + ret + } + } + + impl<'r> ::std::ops::Add<&'r $projective> for $projective { + type Output = Self; + + #[inline] + fn add(self, other: &Self) -> Self { + let mut ret = self; + ret.add_assign(other); + ret + } + } + + impl ::std::ops::Add for $projective { + type Output = Self; + + #[inline] + fn add(self, other: Self) -> Self { + self + &other + } + } + + impl<'r> ::std::ops::AddAssign<&'r $projective> for $projective { + fn add_assign(&mut self, other: &Self) { + if self.is_zero() { + *self = *other; + return; + } + + if other.is_zero() { + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl + + // Z1Z1 = Z1^2 + let z1z1 = self.z.square(); + + // Z2Z2 = Z2^2 + let z2z2 = other.z.square(); + + // U1 = X1*Z2Z2 + let mut u1 = self.x; + u1.mul_assign(&z2z2); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S1 = Y1*Z2*Z2Z2 + let mut s1 = self.y; + s1.mul_assign(&other.z); + s1.mul_assign(&z2z2); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if u1 == u2 && s1 == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + // H = U2-U1 + let mut h = u2; + h.sub_assign(&u1); + + // I = (2*H)^2 + let i = h.double().square(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-S1) + let mut r = s2; + r.sub_assign(&s1); + r = r.double(); + + // V = U1*I + let mut v = u1; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V - X3) - 2*S1*J + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + s1.mul_assign(&j); // S1 = S1 * J * 2 + s1 = s1.double(); + self.y.sub_assign(&s1); + + // Z3 = ((Z1+Z2)^2 - Z1Z1 - Z2Z2)*H + self.z.add_assign(&other.z); + self.z = self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&z2z2); + self.z.mul_assign(&h); + } + } + } + + impl ::std::ops::AddAssign for $projective { + #[inline] + fn add_assign(&mut self, other: Self) { + self.add_assign(&other); + } + } + + impl<'r> ::std::ops::Sub<&'r $projective> for $projective { + type Output = Self; + + #[inline] + fn sub(self, other: &Self) -> Self { + let mut ret = self; + ret.sub_assign(other); + ret + } + } + + impl ::std::ops::Sub for $projective { + type Output = Self; + + #[inline] + fn sub(self, other: Self) -> Self { + self - &other + } + } + + impl<'r> ::std::ops::SubAssign<&'r $projective> for $projective { + fn sub_assign(&mut self, other: &Self) { + self.add_assign(&other.neg()); + } + } + + impl ::std::ops::SubAssign for $projective { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.sub_assign(&other); + } + } + + impl<'r> ::std::ops::Add<&'r <$projective as CurveProjective>::Affine> for $projective { + type Output = Self; + + #[inline] + fn add(self, other: &<$projective as CurveProjective>::Affine) -> Self { + let mut ret = self; + ret.add_assign(other); + ret + } + } + + impl ::std::ops::Add<<$projective as CurveProjective>::Affine> for $projective { + type Output = Self; + + #[inline] + fn add(self, other: <$projective as CurveProjective>::Affine) -> Self { + self + &other + } + } + + impl<'r> ::std::ops::AddAssign<&'r <$projective as CurveProjective>::Affine> + for $projective + { + fn add_assign(&mut self, other: &<$projective as CurveProjective>::Affine) { + if other.is_zero() { + return; + } + + if self.is_zero() { + self.x = other.x; + self.y = other.y; + self.z = $basefield::one(); + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl + + // Z1Z1 = Z1^2 + let z1z1 = self.z.square(); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if self.x == u2 && self.y == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + // H = U2-X1 + let mut h = u2; + h.sub_assign(&self.x); + + // HH = H^2 + let hh = h.square(); + + // I = 4*HH + let i = hh.double().double(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-Y1) + let mut r = s2; + r.sub_assign(&self.y); + r = r.double(); + + // V = X1*I + let mut v = self.x; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V-X3)-2*Y1*J + j.mul_assign(&self.y); // J = 2*Y1*J + j = j.double(); + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + self.y.sub_assign(&j); + + // Z3 = (Z1+H)^2-Z1Z1-HH + self.z.add_assign(&h); + self.z = self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&hh); + } + } + } + + impl ::std::ops::AddAssign<<$projective as CurveProjective>::Affine> for $projective { + #[inline] + fn add_assign(&mut self, other: <$projective as CurveProjective>::Affine) { + self.add_assign(&other); + } + } + + impl<'r> ::std::ops::Sub<&'r <$projective as CurveProjective>::Affine> for $projective { + type Output = Self; + + #[inline] + fn sub(self, other: &<$projective as CurveProjective>::Affine) -> Self { + let mut ret = self; + ret.sub_assign(other); + ret + } + } + + impl ::std::ops::Sub<<$projective as CurveProjective>::Affine> for $projective { + type Output = Self; + + #[inline] + fn sub(self, other: <$projective as CurveProjective>::Affine) -> Self { + self - &other + } + } + + impl<'r> ::std::ops::SubAssign<&'r <$projective as CurveProjective>::Affine> + for $projective + { + fn sub_assign(&mut self, other: &<$projective as CurveProjective>::Affine) { + self.add_assign(&other.neg()); + } + } + + impl ::std::ops::SubAssign<<$projective as CurveProjective>::Affine> for $projective { + #[inline] + fn sub_assign(&mut self, other: <$projective as CurveProjective>::Affine) { + self.sub_assign(&other); + } + } + impl CurveProjective for $projective { type Engine = Bls12; type Scalar = $scalarfield; @@ -205,8 +509,9 @@ macro_rules! curve_impl { let x = $basefield::random(rng); let greatest = rng.next_u32() % 2 != 0; - if let Some(p) = $affine::get_point_from_x(x, greatest) { - let p = p.scale_by_cofactor(); + let p = $affine::get_point_from_x(x, greatest); + if p.is_some().into() { + let p = p.unwrap().scale_by_cofactor(); if !p.is_zero() { return p; @@ -257,7 +562,7 @@ macro_rules! curve_impl { } // Invert `tmp`. - tmp = tmp.inverse().unwrap(); // Guaranteed to be nonzero. + tmp = tmp.invert().unwrap(); // Guaranteed to be nonzero. // Second pass: iterate backwards to compute inverses for (g, s) in v @@ -284,8 +589,7 @@ macro_rules! curve_impl { // Perform affine transformations for g in v.iter_mut().filter(|g| !g.is_normalized()) { - let mut z = g.z; // 1/z - z.square(); // 1/z^2 + let mut z = g.z.square(); // 1/z^2 g.x.mul_assign(&z); // x/z^2 z.mul_assign(&g.z); // 1/z^3 g.y.mul_assign(&z); // y/z^3 @@ -306,37 +610,32 @@ macro_rules! curve_impl { // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l // A = X1^2 - let mut a = self.x; - a.square(); + let a = self.x.square(); // B = Y1^2 - let mut b = self.y; - b.square(); + let b = self.y.square(); // C = B^2 - let mut c = b; - c.square(); + let mut c = b.square(); // D = 2*((X1+B)2-A-C) let mut d = self.x; d.add_assign(&b); - d.square(); + d = d.square(); d.sub_assign(&a); d.sub_assign(&c); - d.double(); + d = d.double(); // E = 3*A - let mut e = a; - e.double(); + let mut e = a.double(); e.add_assign(&a); // F = E^2 - let mut f = e; - f.square(); + let f = e.square(); // Z3 = 2*Y1*Z1 self.z.mul_assign(&self.y); - self.z.double(); + self.z = self.z.double(); // X3 = F-2*D self.x = f; @@ -347,190 +646,10 @@ macro_rules! curve_impl { self.y = d; self.y.sub_assign(&self.x); self.y.mul_assign(&e); - c.double(); - c.double(); - c.double(); + c = c.double().double().double(); self.y.sub_assign(&c); } - fn add_assign(&mut self, other: &Self) { - if self.is_zero() { - *self = *other; - return; - } - - if other.is_zero() { - return; - } - - // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl - - // Z1Z1 = Z1^2 - let mut z1z1 = self.z; - z1z1.square(); - - // Z2Z2 = Z2^2 - let mut z2z2 = other.z; - z2z2.square(); - - // U1 = X1*Z2Z2 - let mut u1 = self.x; - u1.mul_assign(&z2z2); - - // U2 = X2*Z1Z1 - let mut u2 = other.x; - u2.mul_assign(&z1z1); - - // S1 = Y1*Z2*Z2Z2 - let mut s1 = self.y; - s1.mul_assign(&other.z); - s1.mul_assign(&z2z2); - - // S2 = Y2*Z1*Z1Z1 - let mut s2 = other.y; - s2.mul_assign(&self.z); - s2.mul_assign(&z1z1); - - if u1 == u2 && s1 == s2 { - // The two points are equal, so we double. - self.double(); - } else { - // If we're adding -a and a together, self.z becomes zero as H becomes zero. - - // H = U2-U1 - let mut h = u2; - h.sub_assign(&u1); - - // I = (2*H)^2 - let mut i = h; - i.double(); - i.square(); - - // J = H*I - let mut j = h; - j.mul_assign(&i); - - // r = 2*(S2-S1) - let mut r = s2; - r.sub_assign(&s1); - r.double(); - - // V = U1*I - let mut v = u1; - v.mul_assign(&i); - - // X3 = r^2 - J - 2*V - self.x = r; - self.x.square(); - self.x.sub_assign(&j); - self.x.sub_assign(&v); - self.x.sub_assign(&v); - - // Y3 = r*(V - X3) - 2*S1*J - self.y = v; - self.y.sub_assign(&self.x); - self.y.mul_assign(&r); - s1.mul_assign(&j); // S1 = S1 * J * 2 - s1.double(); - self.y.sub_assign(&s1); - - // Z3 = ((Z1+Z2)^2 - Z1Z1 - Z2Z2)*H - self.z.add_assign(&other.z); - self.z.square(); - self.z.sub_assign(&z1z1); - self.z.sub_assign(&z2z2); - self.z.mul_assign(&h); - } - } - - fn add_assign_mixed(&mut self, other: &Self::Affine) { - if other.is_zero() { - return; - } - - if self.is_zero() { - self.x = other.x; - self.y = other.y; - self.z = $basefield::one(); - return; - } - - // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl - - // Z1Z1 = Z1^2 - let mut z1z1 = self.z; - z1z1.square(); - - // U2 = X2*Z1Z1 - let mut u2 = other.x; - u2.mul_assign(&z1z1); - - // S2 = Y2*Z1*Z1Z1 - let mut s2 = other.y; - s2.mul_assign(&self.z); - s2.mul_assign(&z1z1); - - if self.x == u2 && self.y == s2 { - // The two points are equal, so we double. - self.double(); - } else { - // If we're adding -a and a together, self.z becomes zero as H becomes zero. - - // H = U2-X1 - let mut h = u2; - h.sub_assign(&self.x); - - // HH = H^2 - let mut hh = h; - hh.square(); - - // I = 4*HH - let mut i = hh; - i.double(); - i.double(); - - // J = H*I - let mut j = h; - j.mul_assign(&i); - - // r = 2*(S2-Y1) - let mut r = s2; - r.sub_assign(&self.y); - r.double(); - - // V = X1*I - let mut v = self.x; - v.mul_assign(&i); - - // X3 = r^2 - J - 2*V - self.x = r; - self.x.square(); - self.x.sub_assign(&j); - self.x.sub_assign(&v); - self.x.sub_assign(&v); - - // Y3 = r*(V-X3)-2*Y1*J - j.mul_assign(&self.y); // J = 2*Y1*J - j.double(); - self.y = v; - self.y.sub_assign(&self.x); - self.y.mul_assign(&r); - self.y.sub_assign(&j); - - // Z3 = (Z1+H)^2-Z1Z1-HH - self.z.add_assign(&h); - self.z.square(); - self.z.sub_assign(&z1z1); - self.z.sub_assign(&hh); - } - } - - fn negate(&mut self) { - if !self.is_zero() { - self.y.negate() - } - } - fn mul_assign::Repr>>(&mut self, other: S) { let mut res = Self::zero(); @@ -544,7 +663,7 @@ macro_rules! curve_impl { } if i { - res.add_assign(self); + res.add_assign(&*self); } } @@ -595,9 +714,8 @@ macro_rules! curve_impl { } } else { // Z is nonzero, so it must have an inverse in a field. - let zinv = p.z.inverse().unwrap(); - let mut zinv_powered = zinv; - zinv_powered.square(); + let zinv = p.z.invert().unwrap(); + let mut zinv_powered = zinv.square(); // X/Z^2 let mut x = p.x; @@ -627,6 +745,8 @@ pub mod g1 { use group::{CurveAffine, CurveProjective, EncodedPoint, GroupDecodingError}; use rand_core::RngCore; use std::fmt; + use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; + use subtle::CtOption; curve_impl!( "G1", @@ -831,7 +951,12 @@ pub mod g1 { let x = Fq::from_repr(x) .map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?; - G1Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) + let ret = G1Affine::get_point_from_x(x, greatest); + if ret.is_some().into() { + Ok(ret.unwrap()) + } else { + Err(GroupDecodingError::NotOnCurve) + } } } fn from_affine(affine: G1Affine) -> Self { @@ -848,8 +973,7 @@ pub mod g1 { affine.x.into_repr().write_be(&mut writer).unwrap(); } - let mut negy = affine.y; - negy.negate(); + let negy = affine.y.neg(); // Set the third most significant bit if the correct y-coordinate // is lexicographically largest. @@ -940,15 +1064,15 @@ pub mod g1 { let mut i = 0; loop { // y^2 = x^3 + b - let mut rhs = x; - rhs.square(); + let mut rhs = x.square(); rhs.mul_assign(&x); rhs.add_assign(&G1Affine::get_coeff_b()); - if let Some(y) = rhs.sqrt() { + let y = rhs.sqrt(); + if y.is_some().into() { + let y = y.unwrap(); let yrepr = y.into_repr(); - let mut negy = y; - negy.negate(); + let negy = y.neg(); let negyrepr = negy.into_repr(); let p = G1Affine { @@ -1276,7 +1400,7 @@ pub mod g1 { assert_eq!(tmp1, c.into_projective()); let mut tmp2 = a.into_projective(); - tmp2.add_assign_mixed(&b); + tmp2.add_assign(&b); assert_eq!(tmp2.into_affine(), c); assert_eq!(tmp2, c.into_projective()); } @@ -1296,6 +1420,8 @@ pub mod g2 { use group::{CurveAffine, CurveProjective, EncodedPoint, GroupDecodingError}; use rand_core::RngCore; use std::fmt; + use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; + use subtle::CtOption; curve_impl!( "G2", @@ -1524,7 +1650,12 @@ pub mod g2 { })?, }; - G2Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) + let ret = G2Affine::get_point_from_x(x, greatest); + if ret.is_some().into() { + Ok(ret.unwrap()) + } else { + Err(GroupDecodingError::NotOnCurve) + } } } fn from_affine(affine: G2Affine) -> Self { @@ -1542,8 +1673,7 @@ pub mod g2 { affine.x.c0.into_repr().write_be(&mut writer).unwrap(); } - let mut negy = affine.y; - negy.negate(); + let negy = affine.y.neg(); // Set the third most significant bit if the correct y-coordinate // is lexicographically largest. @@ -1646,14 +1776,14 @@ pub mod g2 { let mut i = 0; loop { // y^2 = x^3 + b - let mut rhs = x; - rhs.square(); + let mut rhs = x.square(); rhs.mul_assign(&x); rhs.add_assign(&G2Affine::get_coeff_b()); - if let Some(y) = rhs.sqrt() { - let mut negy = y; - negy.negate(); + let y = rhs.sqrt(); + if y.is_some().into() { + let y = y.unwrap(); + let negy = y.neg(); let p = G2Affine { x, diff --git a/pairing/src/bls12_381/fq.rs b/pairing/src/bls12_381/fq.rs index 08135e3..57d6532 100644 --- a/pairing/src/bls12_381/fq.rs +++ b/pairing/src/bls12_381/fq.rs @@ -1,5 +1,9 @@ use super::fq2::Fq2; use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; +use std::ops::{AddAssign, MulAssign, SubAssign}; + +#[cfg(test)] +use std::ops::Neg; // B coefficient of BLS12-381 curve, 4. pub const B_COEFF: Fq = Fq(FqRepr([ @@ -454,14 +458,14 @@ fn test_b_coeff() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_frob_coeffs() { - let mut nqr = Fq::one(); - nqr.negate(); + let nqr = Fq::one().neg(); assert_eq!(FROBENIUS_COEFF_FQ2_C1[0], Fq::one()); assert_eq!( FROBENIUS_COEFF_FQ2_C1[1], - nqr.pow([ + nqr.pow_vartime([ 0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, @@ -479,7 +483,7 @@ fn test_frob_coeffs() { assert_eq!(FROBENIUS_COEFF_FQ6_C1[0], Fq2::one()); assert_eq!( FROBENIUS_COEFF_FQ6_C1[1], - nqr.pow([ + nqr.pow_vartime([ 0x9354ffffffffe38e, 0xa395554e5c6aaaa, 0xcd104635a790520c, @@ -490,7 +494,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C1[2], - nqr.pow([ + nqr.pow_vartime([ 0xb78e0000097b2f68, 0xd44f23b47cbd64e3, 0x5cb9668120b069a9, @@ -507,7 +511,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C1[3], - nqr.pow([ + nqr.pow_vartime([ 0xdbc6fcd6f35b9e06, 0x997dead10becd6aa, 0x9dbbd24c17206460, @@ -530,7 +534,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C1[4], - nqr.pow([ + nqr.pow_vartime([ 0x4649add3c71c6d90, 0x43caa6528972a865, 0xcda8445bbaaa0fbb, @@ -559,7 +563,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C1[5], - nqr.pow([ + nqr.pow_vartime([ 0xf896f792732eb2be, 0x49c86a6d1dc593a1, 0xe5b31e94581f91c3, @@ -596,7 +600,7 @@ fn test_frob_coeffs() { assert_eq!(FROBENIUS_COEFF_FQ6_C2[0], Fq2::one()); assert_eq!( FROBENIUS_COEFF_FQ6_C2[1], - nqr.pow([ + nqr.pow_vartime([ 0x26a9ffffffffc71c, 0x1472aaa9cb8d5555, 0x9a208c6b4f20a418, @@ -607,7 +611,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C2[2], - nqr.pow([ + nqr.pow_vartime([ 0x6f1c000012f65ed0, 0xa89e4768f97ac9c7, 0xb972cd024160d353, @@ -624,7 +628,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C2[3], - nqr.pow([ + nqr.pow_vartime([ 0xb78df9ade6b73c0c, 0x32fbd5a217d9ad55, 0x3b77a4982e40c8c1, @@ -647,7 +651,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C2[4], - nqr.pow([ + nqr.pow_vartime([ 0x8c935ba78e38db20, 0x87954ca512e550ca, 0x9b5088b775541f76, @@ -676,7 +680,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ6_C2[5], - nqr.pow([ + nqr.pow_vartime([ 0xf12def24e65d657c, 0x9390d4da3b8b2743, 0xcb663d28b03f2386, @@ -713,7 +717,7 @@ fn test_frob_coeffs() { assert_eq!(FROBENIUS_COEFF_FQ12_C1[0], Fq2::one()); assert_eq!( FROBENIUS_COEFF_FQ12_C1[1], - nqr.pow([ + nqr.pow_vartime([ 0x49aa7ffffffff1c7, 0x51caaaa72e35555, 0xe688231ad3c82906, @@ -724,7 +728,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[2], - nqr.pow([ + nqr.pow_vartime([ 0xdbc7000004bd97b4, 0xea2791da3e5eb271, 0x2e5cb340905834d4, @@ -741,7 +745,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[3], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0x6de37e6b79adcf03, 0x4cbef56885f66b55, 0x4edde9260b903230, @@ -764,7 +768,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[4], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0xa324d6e9e38e36c8, 0xa1e5532944b95432, 0x66d4222ddd5507dd, @@ -793,7 +797,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[5], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0xfc4b7bc93997595f, 0xa4e435368ee2c9d0, 0xf2d98f4a2c0fc8e1, @@ -828,7 +832,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[6], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0x21219610a012ba3c, 0xa5c19ad35375325, 0x4e9df1e497674396, @@ -869,7 +873,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[7], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0x742754a1f22fdb, 0x2a1955c2dec3a702, 0x9747b28c796d134e, @@ -916,7 +920,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[8], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0x802f5720d0b25710, 0x6714f0a258b85c7c, 0x31394c90afdf16e, @@ -969,7 +973,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[9], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0x4af4accf7de0b977, 0x742485e21805b4ee, 0xee388fbc4ac36dec, @@ -1028,7 +1032,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[10], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0xe5953a4f96cdda44, 0x336b2d734cbc32bb, 0x3f79bfe3cd7410e, @@ -1093,7 +1097,7 @@ fn test_frob_coeffs() { ); assert_eq!( FROBENIUS_COEFF_FQ12_C1[11], - nqr.pow(vec![ + nqr.pow_vartime(vec![ 0x107db680942de533, 0x6262b24d2052393b, 0x6136df824159ebc, @@ -1166,8 +1170,7 @@ fn test_frob_coeffs() { #[test] fn test_neg_one() { - let mut o = Fq::one(); - o.negate(); + let o = Fq::one().neg(); assert_eq!(NEGATIVE_ONE, o); } @@ -1928,7 +1931,7 @@ fn test_fq_mul_assign() { #[test] fn test_fq_squaring() { - let mut a = Fq(FqRepr([ + let a = Fq(FqRepr([ 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, @@ -1937,9 +1940,8 @@ fn test_fq_squaring() { 0x19ffffffffffffff, ])); assert!(a.is_valid()); - a.square(); assert_eq!( - a, + a.square(), Fq::from_repr(FqRepr([ 0x1cfb28fe7dfbbb86, 0x24cbe1731577a59, @@ -1959,20 +1961,13 @@ fn test_fq_squaring() { for _ in 0..1000000 { // Ensure that (a * a) = a^2 let a = Fq::random(&mut rng); - - let mut tmp = a; - tmp.square(); - - let mut tmp2 = a; - tmp2.mul_assign(&a); - - assert_eq!(tmp, tmp2); + assert_eq!(a.square(), a * a); } } #[test] -fn test_fq_inverse() { - assert!(Fq::zero().inverse().is_none()); +fn test_fq_invert() { + assert!(bool::from(Fq::zero().invert().is_none())); let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, @@ -1984,7 +1979,7 @@ fn test_fq_inverse() { for _ in 0..1000 { // Ensure that a * a^-1 = 1 let mut a = Fq::random(&mut rng); - let ainv = a.inverse().unwrap(); + let ainv = a.invert().unwrap(); a.mul_assign(&ainv); assert_eq!(a, one); } @@ -1999,19 +1994,15 @@ fn test_fq_double() { for _ in 0..1000 { // Ensure doubling a is equivalent to adding a to itself. - let mut a = Fq::random(&mut rng); - let mut b = a; - b.add_assign(&a); - a.double(); - assert_eq!(a, b); + let a = Fq::random(&mut rng); + assert_eq!(a.double(), a + a); } } #[test] -fn test_fq_negate() { +fn test_fq_neg() { { - let mut a = Fq::zero(); - a.negate(); + let a = Fq::zero().neg(); assert!(a.is_zero()); } @@ -2024,8 +2015,7 @@ fn test_fq_negate() { for _ in 0..1000 { // Ensure (a - (-a)) = 0. let mut a = Fq::random(&mut rng); - let mut b = a; - b.negate(); + let b = a.neg(); a.add_assign(&b); assert!(a.is_zero()); @@ -2043,7 +2033,7 @@ fn test_fq_pow() { // Exponentiate by various small numbers and ensure it consists with repeated // multiplication. let a = Fq::random(&mut rng); - let target = a.pow(&[i]); + let target = a.pow_vartime(&[i]); let mut c = Fq::one(); for _ in 0..i { c.mul_assign(&a); @@ -2055,7 +2045,7 @@ fn test_fq_pow() { // Exponentiating by the modulus should have no effect in a prime field. let a = Fq::random(&mut rng); - assert_eq!(a, a.pow(Fq::char())); + assert_eq!(a, a.pow_vartime(Fq::char())); } } @@ -2073,10 +2063,8 @@ fn test_fq_sqrt() { for _ in 0..1000 { // Ensure sqrt(a^2) = a or -a let a = Fq::random(&mut rng); - let mut nega = a; - nega.negate(); - let mut b = a; - b.square(); + let nega = a.neg(); + let b = a.square(); let b = b.sqrt().unwrap(); @@ -2087,10 +2075,9 @@ fn test_fq_sqrt() { // Ensure sqrt(a)^2 = a for random a let a = Fq::random(&mut rng); - if let Some(mut tmp) = a.sqrt() { - tmp.square(); - - assert_eq!(a, tmp); + let tmp = a.sqrt(); + if tmp.is_some().into() { + assert_eq!(a, tmp.unwrap().square()); } } } @@ -2209,7 +2196,7 @@ fn test_fq_root_of_unity() { Fq::from_repr(FqRepr::from(2)).unwrap() ); assert_eq!( - Fq::multiplicative_generator().pow([ + Fq::multiplicative_generator().pow_vartime([ 0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, @@ -2219,8 +2206,8 @@ fn test_fq_root_of_unity() { ]), Fq::root_of_unity() ); - assert_eq!(Fq::root_of_unity().pow([1 << Fq::S]), Fq::one()); - assert!(Fq::multiplicative_generator().sqrt().is_none()); + assert_eq!(Fq::root_of_unity().pow_vartime([1 << Fq::S]), Fq::one()); + assert!(bool::from(Fq::multiplicative_generator().sqrt().is_none())); } #[test] @@ -2246,40 +2233,3 @@ fn test_fq_ordering() { fn fq_repr_tests() { crate::tests::repr::random_repr_tests::(); } - -#[test] -fn test_fq_legendre() { - use ff::LegendreSymbol::*; - use ff::SqrtField; - - assert_eq!(QuadraticResidue, Fq::one().legendre()); - assert_eq!(Zero, Fq::zero().legendre()); - - assert_eq!( - QuadraticNonResidue, - Fq::from_repr(FqRepr::from(2)).unwrap().legendre() - ); - assert_eq!( - QuadraticResidue, - Fq::from_repr(FqRepr::from(4)).unwrap().legendre() - ); - - let e = FqRepr([ - 0x52a112f249778642, - 0xd0bedb989b7991f, - 0xdad3b6681aa63c05, - 0xf2efc0bb4721b283, - 0x6057a98f18c24733, - 0x1022c2fd122889e4, - ]); - assert_eq!(QuadraticNonResidue, Fq::from_repr(e).unwrap().legendre()); - let e = FqRepr([ - 0x6dae594e53a96c74, - 0x19b16ca9ba64b37b, - 0x5c764661a59bfc68, - 0xaa346e9b31c60a, - 0x346059f9d87a9fa9, - 0x1d61ac6bfd5c88b, - ]); - assert_eq!(QuadraticResidue, Fq::from_repr(e).unwrap().legendre()); -} diff --git a/pairing/src/bls12_381/fq12.rs b/pairing/src/bls12_381/fq12.rs index 17ae390..f8b4853 100644 --- a/pairing/src/bls12_381/fq12.rs +++ b/pairing/src/bls12_381/fq12.rs @@ -3,9 +3,11 @@ use super::fq2::Fq2; use super::fq6::Fq6; use ff::Field; use rand_core::RngCore; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, CtOption}; /// An element of Fq12, represented by c0 + c1 * w. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct Fq12 { pub c0: Fq6, pub c1: Fq6, @@ -19,7 +21,7 @@ impl ::std::fmt::Display for Fq12 { impl Fq12 { pub fn conjugate(&mut self) { - self.c1.negate(); + self.c1 = self.c1.neg(); } pub fn mul_by_014(&mut self, c0: &Fq2, c1: &Fq2, c4: &Fq2) { @@ -39,6 +41,132 @@ impl Fq12 { } } +impl ConditionallySelectable for Fq12 { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fq12 { + c0: Fq6::conditional_select(&a.c0, &b.c0, choice), + c1: Fq6::conditional_select(&a.c1, &b.c1, choice), + } + } +} + +impl Neg for Fq12 { + type Output = Self; + + fn neg(self) -> Self { + Fq12 { + c0: self.c0.neg(), + c1: self.c1.neg(), + } + } +} + +impl<'r> Add<&'r Fq12> for Fq12 { + type Output = Self; + + fn add(self, other: &Self) -> Self { + Fq12 { + c0: self.c0 + other.c0, + c1: self.c1 + other.c1, + } + } +} + +impl Add for Fq12 { + type Output = Self; + + fn add(self, other: Self) -> Self { + self.add(&other) + } +} + +impl<'r> AddAssign<&'r Fq12> for Fq12 { + fn add_assign(&mut self, other: &'r Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + } +} + +impl AddAssign for Fq12 { + fn add_assign(&mut self, other: Self) { + self.add_assign(&other); + } +} + +impl<'r> Sub<&'r Fq12> for Fq12 { + type Output = Self; + + fn sub(self, other: &Self) -> Self { + Fq12 { + c0: self.c0 - other.c0, + c1: self.c1 - other.c1, + } + } +} + +impl Sub for Fq12 { + type Output = Self; + + fn sub(self, other: Self) -> Self { + self.sub(&other) + } +} + +impl<'r> SubAssign<&'r Fq12> for Fq12 { + fn sub_assign(&mut self, other: &'r Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + } +} + +impl SubAssign for Fq12 { + fn sub_assign(&mut self, other: Self) { + self.sub_assign(&other); + } +} + +impl<'r> Mul<&'r Fq12> for Fq12 { + type Output = Self; + + fn mul(self, other: &Self) -> Self { + let mut ret = self; + ret.mul_assign(other); + ret + } +} + +impl Mul for Fq12 { + type Output = Self; + + fn mul(self, other: Self) -> Self { + self.mul(&other) + } +} + +impl<'r> MulAssign<&'r Fq12> for Fq12 { + fn mul_assign(&mut self, other: &Self) { + let mut aa = self.c0; + aa.mul_assign(&other.c0); + let mut bb = self.c1; + bb.mul_assign(&other.c1); + let mut o = other.c0; + o.add_assign(&other.c1); + self.c1.add_assign(&self.c0); + self.c1.mul_assign(&o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = bb; + self.c0.mul_by_nonresidue(); + self.c0.add_assign(&aa); + } +} + +impl MulAssign for Fq12 { + fn mul_assign(&mut self, other: Self) { + self.mul_assign(&other); + } +} + impl Field for Fq12 { fn random(rng: &mut R) -> Self { Fq12 { @@ -65,24 +193,11 @@ impl Field for Fq12 { self.c0.is_zero() && self.c1.is_zero() } - fn double(&mut self) { - self.c0.double(); - self.c1.double(); - } - - fn negate(&mut self) { - self.c0.negate(); - self.c1.negate(); - } - - fn add_assign(&mut self, other: &Self) { - self.c0.add_assign(&other.c0); - self.c1.add_assign(&other.c1); - } - - fn sub_assign(&mut self, other: &Self) { - self.c0.sub_assign(&other.c0); - self.c1.sub_assign(&other.c1); + fn double(&self) -> Self { + Fq12 { + c0: self.c0.double(), + c1: self.c1.double(), + } } fn frobenius_map(&mut self, power: usize) { @@ -94,7 +209,7 @@ impl Field for Fq12 { self.c1.c2.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); } - fn square(&mut self) { + fn square(&self) -> Self { let mut ab = self.c0; ab.mul_assign(&self.c1); let mut c0c1 = self.c0; @@ -104,44 +219,22 @@ impl Field for Fq12 { c0.add_assign(&self.c0); c0.mul_assign(&c0c1); c0.sub_assign(&ab); - self.c1 = ab; - self.c1.add_assign(&ab); + let mut c1 = ab; + c1.add_assign(&ab); ab.mul_by_nonresidue(); c0.sub_assign(&ab); - self.c0 = c0; + Fq12 { c0, c1 } } - fn mul_assign(&mut self, other: &Self) { - let mut aa = self.c0; - aa.mul_assign(&other.c0); - let mut bb = self.c1; - bb.mul_assign(&other.c1); - let mut o = other.c0; - o.add_assign(&other.c1); - self.c1.add_assign(&self.c0); - self.c1.mul_assign(&o); - self.c1.sub_assign(&aa); - self.c1.sub_assign(&bb); - self.c0 = bb; - self.c0.mul_by_nonresidue(); - self.c0.add_assign(&aa); - } - - fn inverse(&self) -> Option { - let mut c0s = self.c0; - c0s.square(); - let mut c1s = self.c1; - c1s.square(); + fn invert(&self) -> CtOption { + let mut c0s = self.c0.square(); + let mut c1s = self.c1.square(); c1s.mul_by_nonresidue(); c0s.sub_assign(&c1s); - c0s.inverse().map(|t| { - let mut tmp = Fq12 { c0: t, c1: t }; - tmp.c0.mul_assign(&self.c0); - tmp.c1.mul_assign(&self.c1); - tmp.c1.negate(); - - tmp + c0s.invert().map(|t| Fq12 { + c0: t.mul(&self.c0), + c1: t.mul(&self.c1).neg(), }) } } diff --git a/pairing/src/bls12_381/fq2.rs b/pairing/src/bls12_381/fq2.rs index 4fd391b..0cd88e7 100644 --- a/pairing/src/bls12_381/fq2.rs +++ b/pairing/src/bls12_381/fq2.rs @@ -1,11 +1,12 @@ use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; use ff::{Field, SqrtField}; use rand_core::RngCore; - use std::cmp::Ordering; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, CtOption}; /// An element of Fq2, represented by c0 + c1 * u. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct Fq2 { pub c0: Fq, pub c1: Fq, @@ -46,16 +47,139 @@ impl Fq2 { /// Norm of Fq2 as extension field in i over Fq pub fn norm(&self) -> Fq { - let mut t0 = self.c0; - let mut t1 = self.c1; - t0.square(); - t1.square(); + let t0 = self.c0.square(); + let mut t1 = self.c1.square(); t1.add_assign(&t0); t1 } } +impl ConditionallySelectable for Fq2 { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fq2 { + c0: Fq::conditional_select(&a.c0, &b.c0, choice), + c1: Fq::conditional_select(&a.c1, &b.c1, choice), + } + } +} + +impl Neg for Fq2 { + type Output = Self; + + fn neg(self) -> Self { + Fq2 { + c0: self.c0.neg(), + c1: self.c1.neg(), + } + } +} + +impl<'r> Add<&'r Fq2> for Fq2 { + type Output = Self; + + fn add(self, other: &Self) -> Self { + Fq2 { + c0: self.c0 + other.c0, + c1: self.c1 + other.c1, + } + } +} + +impl Add for Fq2 { + type Output = Self; + + fn add(self, other: Self) -> Self { + self.add(&other) + } +} + +impl<'r> AddAssign<&'r Fq2> for Fq2 { + fn add_assign(&mut self, other: &'r Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + } +} + +impl AddAssign for Fq2 { + fn add_assign(&mut self, other: Self) { + self.add_assign(&other); + } +} + +impl<'r> Sub<&'r Fq2> for Fq2 { + type Output = Self; + + fn sub(self, other: &Self) -> Self { + Fq2 { + c0: self.c0 - other.c0, + c1: self.c1 - other.c1, + } + } +} + +impl Sub for Fq2 { + type Output = Self; + + fn sub(self, other: Self) -> Self { + self.sub(&other) + } +} + +impl<'r> SubAssign<&'r Fq2> for Fq2 { + fn sub_assign(&mut self, other: &'r Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + } +} + +impl SubAssign for Fq2 { + fn sub_assign(&mut self, other: Self) { + self.sub_assign(&other); + } +} + +impl<'r> Mul<&'r Fq2> for Fq2 { + type Output = Self; + + fn mul(self, other: &Self) -> Self { + let mut ret = self; + ret.mul_assign(other); + ret + } +} + +impl Mul for Fq2 { + type Output = Self; + + fn mul(self, other: Self) -> Self { + self.mul(&other) + } +} + +impl<'r> MulAssign<&'r Fq2> for Fq2 { + fn mul_assign(&mut self, other: &Self) { + let mut aa = self.c0; + aa.mul_assign(&other.c0); + let mut bb = self.c1; + bb.mul_assign(&other.c1); + let mut o = other.c0; + o.add_assign(&other.c1); + self.c1.add_assign(&self.c0); + self.c1.mul_assign(&o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = aa; + self.c0.sub_assign(&bb); + } +} + +impl MulAssign for Fq2 { + fn mul_assign(&mut self, other: Self) { + self.mul_assign(&other); + } +} + impl Field for Fq2 { fn random(rng: &mut R) -> Self { Fq2 { @@ -82,73 +206,35 @@ impl Field for Fq2 { self.c0.is_zero() && self.c1.is_zero() } - fn square(&mut self) { + fn square(&self) -> Self { let mut ab = self.c0; ab.mul_assign(&self.c1); let mut c0c1 = self.c0; c0c1.add_assign(&self.c1); - let mut c0 = self.c1; - c0.negate(); + let mut c0 = self.c1.neg(); c0.add_assign(&self.c0); c0.mul_assign(&c0c1); c0.sub_assign(&ab); - self.c1 = ab; - self.c1.add_assign(&ab); + let mut c1 = ab; + c1.add_assign(&ab); c0.add_assign(&ab); - self.c0 = c0; + Fq2 { c0, c1 } } - fn double(&mut self) { - self.c0.double(); - self.c1.double(); + fn double(&self) -> Self { + Fq2 { + c0: self.c0.double(), + c1: self.c1.double(), + } } - fn negate(&mut self) { - self.c0.negate(); - self.c1.negate(); - } - - fn add_assign(&mut self, other: &Self) { - self.c0.add_assign(&other.c0); - self.c1.add_assign(&other.c1); - } - - fn sub_assign(&mut self, other: &Self) { - self.c0.sub_assign(&other.c0); - self.c1.sub_assign(&other.c1); - } - - fn mul_assign(&mut self, other: &Self) { - let mut aa = self.c0; - aa.mul_assign(&other.c0); - let mut bb = self.c1; - bb.mul_assign(&other.c1); - let mut o = other.c0; - o.add_assign(&other.c1); - self.c1.add_assign(&self.c0); - self.c1.mul_assign(&o); - self.c1.sub_assign(&aa); - self.c1.sub_assign(&bb); - self.c0 = aa; - self.c0.sub_assign(&bb); - } - - fn inverse(&self) -> Option { - let mut t1 = self.c1; - t1.square(); - let mut t0 = self.c0; - t0.square(); + fn invert(&self) -> CtOption { + let t1 = self.c1.square(); + let mut t0 = self.c0.square(); t0.add_assign(&t1); - t0.inverse().map(|t| { - let mut tmp = Fq2 { - c0: self.c0, - c1: self.c1, - }; - tmp.c0.mul_assign(&t); - tmp.c1.mul_assign(&t); - tmp.c1.negate(); - - tmp + t0.invert().map(|t| Fq2 { + c0: self.c0.mul(&t), + c1: self.c1.mul(&t).neg(), }) } @@ -158,18 +244,16 @@ impl Field for Fq2 { } impl SqrtField for Fq2 { - fn legendre(&self) -> ::ff::LegendreSymbol { - self.norm().legendre() - } - - fn sqrt(&self) -> Option { + /// WARNING: THIS IS NOT ACTUALLY CONSTANT TIME YET! + /// THIS WILL BE REPLACED BY THE bls12_381 CRATE, WHICH IS CONSTANT TIME! + fn sqrt(&self) -> CtOption { // Algorithm 9, https://eprint.iacr.org/2012/685.pdf if self.is_zero() { - Some(Self::zero()) + CtOption::new(Self::zero(), Choice::from(1)) } else { // a1 = self^((q - 3) / 4) - let mut a1 = self.pow([ + let mut a1 = self.pow_vartime([ 0xee7fbfffffffeaaa, 0x7aaffffac54ffff, 0xd9cc34a83dac3d89, @@ -177,8 +261,7 @@ impl SqrtField for Fq2 { 0x92c6e9ed90d2eb35, 0x680447a8e5ff9a6, ]); - let mut alpha = a1; - alpha.square(); + let mut alpha = a1.square(); alpha.mul_assign(self); let mut a0 = alpha; a0.frobenius_map(1); @@ -190,7 +273,7 @@ impl SqrtField for Fq2 { }; if a0 == neg1 { - None + CtOption::new(Self::zero(), Choice::from(0)) } else { a1.mul_assign(self); @@ -202,7 +285,7 @@ impl SqrtField for Fq2 { } else { alpha.add_assign(&Fq2::one()); // alpha = alpha^((q - 1) / 2) - alpha = alpha.pow([ + alpha = alpha.pow_vartime([ 0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, @@ -213,7 +296,7 @@ impl SqrtField for Fq2 { a1.mul_assign(&alpha); } - Some(a1) + CtOption::new(a1, Choice::from(1)) } } } @@ -226,7 +309,7 @@ fn test_fq2_ordering() { c1: Fq::zero(), }; - let mut b = a.clone(); + let mut b = a; assert!(a.cmp(&b) == Ordering::Equal); b.c0.add_assign(&Fq::one()); @@ -273,34 +356,30 @@ fn test_fq2_squaring() { use super::fq::FqRepr; use ff::PrimeField; - let mut a = Fq2 { + let a = Fq2 { c0: Fq::one(), c1: Fq::one(), }; // u + 1 - a.square(); assert_eq!( - a, + a.square(), Fq2 { c0: Fq::zero(), c1: Fq::from_repr(FqRepr::from(2)).unwrap(), } ); // 2u - let mut a = Fq2 { + let a = Fq2 { c0: Fq::zero(), c1: Fq::one(), }; // u - a.square(); - assert_eq!(a, { - let mut neg1 = Fq::one(); - neg1.negate(); + assert_eq!(a.square(), { Fq2 { - c0: neg1, + c0: Fq::one().neg(), c1: Fq::zero(), } }); // -1 - let mut a = Fq2 { + let a = Fq2 { c0: Fq::from_repr(FqRepr([ 0x9c2c6309bbf8b598, 0x4eef5c946536f602, @@ -320,9 +399,8 @@ fn test_fq2_squaring() { ])) .unwrap(), }; - a.square(); assert_eq!( - a, + a.square(), Fq2 { c0: Fq::from_repr(FqRepr([ 0xf262c28c538bcf68, @@ -417,11 +495,11 @@ fn test_fq2_mul() { } #[test] -fn test_fq2_inverse() { +fn test_fq2_invert() { use super::fq::FqRepr; use ff::PrimeField; - assert!(Fq2::zero().inverse().is_none()); + assert!(bool::from(Fq2::zero().invert().is_none())); let a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -443,7 +521,7 @@ fn test_fq2_inverse() { ])) .unwrap(), }; - let a = a.inverse().unwrap(); + let a = a.invert().unwrap(); assert_eq!( a, Fq2 { @@ -614,7 +692,7 @@ fn test_fq2_negation() { use super::fq::FqRepr; use ff::PrimeField; - let mut a = Fq2 { + let a = Fq2 { c0: Fq::from_repr(FqRepr([ 0x2d0078036923ffc7, 0x11e59ea221a3b6d2, @@ -633,8 +711,8 @@ fn test_fq2_negation() { 0x12d1137b8a6a837, ])) .unwrap(), - }; - a.negate(); + } + .neg(); assert_eq!( a, Fq2 { @@ -665,7 +743,7 @@ fn test_fq2_doubling() { use super::fq::FqRepr; use ff::PrimeField; - let mut a = Fq2 { + let a = Fq2 { c0: Fq::from_repr(FqRepr([ 0x2d0078036923ffc7, 0x11e59ea221a3b6d2, @@ -685,9 +763,8 @@ fn test_fq2_doubling() { ])) .unwrap(), }; - a.double(); assert_eq!( - a, + a.double(), Fq2 { c0: Fq::from_repr(FqRepr([ 0x5a00f006d247ff8e, @@ -914,19 +991,6 @@ fn test_fq2_sqrt() { ); } -#[test] -fn test_fq2_legendre() { - use ff::LegendreSymbol::*; - - assert_eq!(Zero, Fq2::zero().legendre()); - // i^2 = -1 - let mut m1 = Fq2::one(); - m1.negate(); - assert_eq!(QuadraticResidue, m1.legendre()); - m1.mul_by_nonresidue(); - assert_eq!(QuadraticNonResidue, m1.legendre()); -} - #[cfg(test)] use rand_core::SeedableRng; #[cfg(test)] diff --git a/pairing/src/bls12_381/fq6.rs b/pairing/src/bls12_381/fq6.rs index 92128ca..b8ac627 100644 --- a/pairing/src/bls12_381/fq6.rs +++ b/pairing/src/bls12_381/fq6.rs @@ -2,9 +2,11 @@ use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; use super::fq2::Fq2; use ff::Field; use rand_core::RngCore; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, CtOption}; /// An element of Fq6, represented by c0 + c1 * v + c2 * v^(2). -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct Fq6 { pub c0: Fq2, pub c1: Fq2, @@ -99,101 +101,115 @@ impl Fq6 { } } -impl Field for Fq6 { - fn random(rng: &mut R) -> Self { +impl ConditionallySelectable for Fq6 { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { Fq6 { - c0: Fq2::random(rng), - c1: Fq2::random(rng), - c2: Fq2::random(rng), + c0: Fq2::conditional_select(&a.c0, &b.c0, choice), + c1: Fq2::conditional_select(&a.c1, &b.c1, choice), + c2: Fq2::conditional_select(&a.c2, &b.c2, choice), } } +} - fn zero() -> Self { +impl Neg for Fq6 { + type Output = Self; + + fn neg(self) -> Self { Fq6 { - c0: Fq2::zero(), - c1: Fq2::zero(), - c2: Fq2::zero(), + c0: self.c0.neg(), + c1: self.c1.neg(), + c2: self.c2.neg(), } } +} - fn one() -> Self { +impl<'r> Add<&'r Fq6> for Fq6 { + type Output = Self; + + fn add(self, other: &Self) -> Self { Fq6 { - c0: Fq2::one(), - c1: Fq2::zero(), - c2: Fq2::zero(), + c0: self.c0 + other.c0, + c1: self.c1 + other.c1, + c2: self.c2 + other.c2, } } +} - fn is_zero(&self) -> bool { - self.c0.is_zero() && self.c1.is_zero() && self.c2.is_zero() +impl Add for Fq6 { + type Output = Self; + + fn add(self, other: Self) -> Self { + self.add(&other) } +} - fn double(&mut self) { - self.c0.double(); - self.c1.double(); - self.c2.double(); - } - - fn negate(&mut self) { - self.c0.negate(); - self.c1.negate(); - self.c2.negate(); - } - - fn add_assign(&mut self, other: &Self) { +impl<'r> AddAssign<&'r Fq6> for Fq6 { + fn add_assign(&mut self, other: &'r Self) { self.c0.add_assign(&other.c0); self.c1.add_assign(&other.c1); self.c2.add_assign(&other.c2); } +} - fn sub_assign(&mut self, other: &Self) { +impl AddAssign for Fq6 { + fn add_assign(&mut self, other: Self) { + self.add_assign(&other); + } +} + +impl<'r> Sub<&'r Fq6> for Fq6 { + type Output = Self; + + fn sub(self, other: &Self) -> Self { + Fq6 { + c0: self.c0 - other.c0, + c1: self.c1 - other.c1, + c2: self.c2 - other.c2, + } + } +} + +impl Sub for Fq6 { + type Output = Self; + + fn sub(self, other: Self) -> Self { + self.sub(&other) + } +} + +impl<'r> SubAssign<&'r Fq6> for Fq6 { + fn sub_assign(&mut self, other: &'r Self) { self.c0.sub_assign(&other.c0); self.c1.sub_assign(&other.c1); self.c2.sub_assign(&other.c2); } +} - fn frobenius_map(&mut self, power: usize) { - self.c0.frobenius_map(power); - self.c1.frobenius_map(power); - self.c2.frobenius_map(power); - - self.c1.mul_assign(&FROBENIUS_COEFF_FQ6_C1[power % 6]); - self.c2.mul_assign(&FROBENIUS_COEFF_FQ6_C2[power % 6]); +impl SubAssign for Fq6 { + fn sub_assign(&mut self, other: Self) { + self.sub_assign(&other); } +} - fn square(&mut self) { - let mut s0 = self.c0; - s0.square(); - let mut ab = self.c0; - ab.mul_assign(&self.c1); - let mut s1 = ab; - s1.double(); - let mut s2 = self.c0; - s2.sub_assign(&self.c1); - s2.add_assign(&self.c2); - s2.square(); - let mut bc = self.c1; - bc.mul_assign(&self.c2); - let mut s3 = bc; - s3.double(); - let mut s4 = self.c2; - s4.square(); +impl<'r> Mul<&'r Fq6> for Fq6 { + type Output = Self; - self.c0 = s3; - self.c0.mul_by_nonresidue(); - self.c0.add_assign(&s0); - - self.c1 = s4; - self.c1.mul_by_nonresidue(); - self.c1.add_assign(&s1); - - self.c2 = s1; - self.c2.add_assign(&s2); - self.c2.add_assign(&s3); - self.c2.sub_assign(&s0); - self.c2.sub_assign(&s4); + fn mul(self, other: &Self) -> Self { + let mut ret = self; + ret.mul_assign(other); + ret } +} +impl Mul for Fq6 { + type Output = Self; + + fn mul(self, other: Self) -> Self { + self.mul(&other) + } +} + +impl<'r> MulAssign<&'r Fq6> for Fq6 { fn mul_assign(&mut self, other: &Self) { let mut a_a = self.c0; let mut b_b = self.c1; @@ -244,27 +260,108 @@ impl Field for Fq6 { self.c1 = t2; self.c2 = t3; } +} - fn inverse(&self) -> Option { +impl MulAssign for Fq6 { + fn mul_assign(&mut self, other: Self) { + self.mul_assign(&other); + } +} + +impl Field for Fq6 { + fn random(rng: &mut R) -> Self { + Fq6 { + c0: Fq2::random(rng), + c1: Fq2::random(rng), + c2: Fq2::random(rng), + } + } + + fn zero() -> Self { + Fq6 { + c0: Fq2::zero(), + c1: Fq2::zero(), + c2: Fq2::zero(), + } + } + + fn one() -> Self { + Fq6 { + c0: Fq2::one(), + c1: Fq2::zero(), + c2: Fq2::zero(), + } + } + + fn is_zero(&self) -> bool { + self.c0.is_zero() && self.c1.is_zero() && self.c2.is_zero() + } + + fn double(&self) -> Self { + Fq6 { + c0: self.c0.double(), + c1: self.c1.double(), + c2: self.c2.double(), + } + } + + fn frobenius_map(&mut self, power: usize) { + self.c0.frobenius_map(power); + self.c1.frobenius_map(power); + self.c2.frobenius_map(power); + + self.c1.mul_assign(&FROBENIUS_COEFF_FQ6_C1[power % 6]); + self.c2.mul_assign(&FROBENIUS_COEFF_FQ6_C2[power % 6]); + } + + fn square(&self) -> Self { + let s0 = self.c0.square(); + let mut ab = self.c0; + ab.mul_assign(&self.c1); + let s1 = ab.double(); + let mut s2 = self.c0; + s2.sub_assign(&self.c1); + s2.add_assign(&self.c2); + s2 = s2.square(); + let mut bc = self.c1; + bc.mul_assign(&self.c2); + let s3 = bc.double(); + let s4 = self.c2.square(); + + let mut c0 = s3; + c0.mul_by_nonresidue(); + c0.add_assign(&s0); + + let mut c1 = s4; + c1.mul_by_nonresidue(); + c1.add_assign(&s1); + + let mut c2 = s1; + c2.add_assign(&s2); + c2.add_assign(&s3); + c2.sub_assign(&s0); + c2.sub_assign(&s4); + + Fq6 { c0, c1, c2 } + } + + fn invert(&self) -> CtOption { let mut c0 = self.c2; c0.mul_by_nonresidue(); c0.mul_assign(&self.c1); - c0.negate(); + c0 = c0.neg(); { - let mut c0s = self.c0; - c0s.square(); + let c0s = self.c0.square(); c0.add_assign(&c0s); } - let mut c1 = self.c2; - c1.square(); + let mut c1 = self.c2.square(); c1.mul_by_nonresidue(); { let mut c01 = self.c0; c01.mul_assign(&self.c1); c1.sub_assign(&c01); } - let mut c2 = self.c1; - c2.square(); + let mut c2 = self.c1.square(); { let mut c02 = self.c0; c02.mul_assign(&self.c2); @@ -281,21 +378,18 @@ impl Field for Fq6 { tmp2.mul_assign(&c0); tmp1.add_assign(&tmp2); - match tmp1.inverse() { - Some(t) => { - let mut tmp = Fq6 { - c0: t, - c1: t, - c2: t, - }; - tmp.c0.mul_assign(&c0); - tmp.c1.mul_assign(&c1); - tmp.c2.mul_assign(&c2); + tmp1.invert().map(|t| { + let mut tmp = Fq6 { + c0: t, + c1: t, + c2: t, + }; + tmp.c0.mul_assign(&c0); + tmp.c1.mul_assign(&c1); + tmp.c2.mul_assign(&c2); - Some(tmp) - } - None => None, - } + tmp + }) } } diff --git a/pairing/src/bls12_381/fr.rs b/pairing/src/bls12_381/fr.rs index 76f3ffe..4c30e49 100644 --- a/pairing/src/bls12_381/fr.rs +++ b/pairing/src/bls12_381/fr.rs @@ -1,4 +1,5 @@ use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; +use std::ops::{AddAssign, MulAssign, SubAssign}; #[derive(PrimeField)] #[PrimeFieldModulus = "52435875175126190479447740508185965837690552500527637822603658699938581184513"] @@ -9,6 +10,8 @@ pub struct Fr(FrRepr); use rand_core::SeedableRng; #[cfg(test)] use rand_xorshift::XorShiftRng; +#[cfg(test)] +use std::ops::Neg; #[test] fn test_fr_repr_ordering() { @@ -275,30 +278,6 @@ fn test_fr_repr_sub_noborrow() { ); } -#[test] -fn test_fr_legendre() { - use ff::LegendreSymbol::*; - use ff::SqrtField; - - assert_eq!(QuadraticResidue, Fr::one().legendre()); - assert_eq!(Zero, Fr::zero().legendre()); - - let e = FrRepr([ - 0x0dbc5349cd5664da, - 0x8ac5b6296e3ae29d, - 0x127cb819feceaa3b, - 0x3a6b21fb03867191, - ]); - assert_eq!(QuadraticResidue, Fr::from_repr(e).unwrap().legendre()); - let e = FrRepr([ - 0x96341aefd047c045, - 0x9b5f4254500a4d65, - 0x1ee08223b68ac240, - 0x31d9cd545c0ec7c6, - ]); - assert_eq!(QuadraticNonResidue, Fr::from_repr(e).unwrap().legendre()); -} - #[test] fn test_fr_repr_add_nocarry() { let mut rng = XorShiftRng::from_seed([ @@ -690,16 +669,15 @@ fn test_fr_mul_assign() { #[test] fn test_fr_squaring() { - let mut a = Fr(FrRepr([ + let a = Fr(FrRepr([ 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x73eda753299d7d47, ])); assert!(a.is_valid()); - a.square(); assert_eq!( - a, + a.square(), Fr::from_repr(FrRepr([ 0xc0d698e7bde077b8, 0xb79a310579e76ec2, @@ -717,20 +695,13 @@ fn test_fr_squaring() { for _ in 0..1000000 { // Ensure that (a * a) = a^2 let a = Fr::random(&mut rng); - - let mut tmp = a; - tmp.square(); - - let mut tmp2 = a; - tmp2.mul_assign(&a); - - assert_eq!(tmp, tmp2); + assert_eq!(a.square(), a * a); } } #[test] -fn test_fr_inverse() { - assert!(Fr::zero().inverse().is_none()); +fn test_fr_invert() { + assert!(bool::from(Fr::zero().invert().is_none())); let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, @@ -742,7 +713,7 @@ fn test_fr_inverse() { for _ in 0..1000 { // Ensure that a * a^-1 = 1 let mut a = Fr::random(&mut rng); - let ainv = a.inverse().unwrap(); + let ainv = a.invert().unwrap(); a.mul_assign(&ainv); assert_eq!(a, one); } @@ -757,19 +728,15 @@ fn test_fr_double() { for _ in 0..1000 { // Ensure doubling a is equivalent to adding a to itself. - let mut a = Fr::random(&mut rng); - let mut b = a; - b.add_assign(&a); - a.double(); - assert_eq!(a, b); + let a = Fr::random(&mut rng); + assert_eq!(a.double(), a + a); } } #[test] -fn test_fr_negate() { +fn test_fr_neg() { { - let mut a = Fr::zero(); - a.negate(); + let a = Fr::zero().neg(); assert!(a.is_zero()); } @@ -782,8 +749,7 @@ fn test_fr_negate() { for _ in 0..1000 { // Ensure (a - (-a)) = 0. let mut a = Fr::random(&mut rng); - let mut b = a; - b.negate(); + let b = a.neg(); a.add_assign(&b); assert!(a.is_zero()); @@ -801,7 +767,7 @@ fn test_fr_pow() { // Exponentiate by various small numbers and ensure it consists with repeated // multiplication. let a = Fr::random(&mut rng); - let target = a.pow(&[i]); + let target = a.pow_vartime(&[i]); let mut c = Fr::one(); for _ in 0..i { c.mul_assign(&a); @@ -813,7 +779,7 @@ fn test_fr_pow() { // Exponentiating by the modulus should have no effect in a prime field. let a = Fr::random(&mut rng); - assert_eq!(a, a.pow(Fr::char())); + assert_eq!(a, a.pow_vartime(Fr::char())); } } @@ -831,10 +797,8 @@ fn test_fr_sqrt() { for _ in 0..1000 { // Ensure sqrt(a^2) = a or -a let a = Fr::random(&mut rng); - let mut nega = a; - nega.negate(); - let mut b = a; - b.square(); + let nega = a.neg(); + let b = a.square(); let b = b.sqrt().unwrap(); @@ -845,10 +809,9 @@ fn test_fr_sqrt() { // Ensure sqrt(a)^2 = a for random a let a = Fr::random(&mut rng); - if let Some(mut tmp) = a.sqrt() { - tmp.square(); - - assert_eq!(a, tmp); + let tmp = a.sqrt(); + if tmp.is_some().into() { + assert_eq!(a, tmp.unwrap().square()); } } } @@ -1001,7 +964,7 @@ fn test_fr_root_of_unity() { Fr::from_repr(FrRepr::from(7)).unwrap() ); assert_eq!( - Fr::multiplicative_generator().pow([ + Fr::multiplicative_generator().pow_vartime([ 0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, @@ -1009,8 +972,8 @@ fn test_fr_root_of_unity() { ]), Fr::root_of_unity() ); - assert_eq!(Fr::root_of_unity().pow([1 << Fr::S]), Fr::one()); - assert!(Fr::multiplicative_generator().sqrt().is_none()); + assert_eq!(Fr::root_of_unity().pow_vartime([1 << Fr::S]), Fr::one()); + assert!(bool::from(Fr::multiplicative_generator().sqrt().is_none())); } #[test] diff --git a/pairing/src/bls12_381/mod.rs b/pairing/src/bls12_381/mod.rs index e6e88dd..80848e1 100644 --- a/pairing/src/bls12_381/mod.rs +++ b/pairing/src/bls12_381/mod.rs @@ -25,6 +25,8 @@ use super::{Engine, PairingCurveAffine}; use ff::{BitIterator, Field, ScalarEngine}; use group::CurveAffine; +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; +use subtle::CtOption; // The BLS parameter x for BLS12-381 is -0xd201000000010000 const BLS_X: u64 = 0xd201000000010000; @@ -96,7 +98,7 @@ impl Engine for Bls12 { } } - f.square(); + f = f.square(); } for &mut (p, ref mut coeffs) in &mut pairs { @@ -110,62 +112,58 @@ impl Engine for Bls12 { f } - fn final_exponentiation(r: &Fq12) -> Option { + fn final_exponentiation(r: &Fq12) -> CtOption { let mut f1 = *r; f1.conjugate(); - match r.inverse() { - Some(mut f2) => { - let mut r = f1; - r.mul_assign(&f2); - f2 = r; - r.frobenius_map(2); - r.mul_assign(&f2); + r.invert().map(|mut f2| { + let mut r = f1; + r.mul_assign(&f2); + f2 = r; + r.frobenius_map(2); + r.mul_assign(&f2); - fn exp_by_x(f: &mut Fq12, x: u64) { - *f = f.pow(&[x]); - if BLS_X_IS_NEGATIVE { - f.conjugate(); - } + fn exp_by_x(f: &mut Fq12, x: u64) { + *f = f.pow_vartime(&[x]); + if BLS_X_IS_NEGATIVE { + f.conjugate(); } - - let mut x = BLS_X; - let mut y0 = r; - y0.square(); - let mut y1 = y0; - exp_by_x(&mut y1, x); - x >>= 1; - let mut y2 = y1; - exp_by_x(&mut y2, x); - x <<= 1; - let mut y3 = r; - y3.conjugate(); - y1.mul_assign(&y3); - y1.conjugate(); - y1.mul_assign(&y2); - y2 = y1; - exp_by_x(&mut y2, x); - y3 = y2; - exp_by_x(&mut y3, x); - y1.conjugate(); - y3.mul_assign(&y1); - y1.conjugate(); - y1.frobenius_map(3); - y2.frobenius_map(2); - y1.mul_assign(&y2); - y2 = y3; - exp_by_x(&mut y2, x); - y2.mul_assign(&y0); - y2.mul_assign(&r); - y1.mul_assign(&y2); - y2 = y3; - y2.frobenius_map(1); - y1.mul_assign(&y2); - - Some(y1) } - None => None, - } + + let mut x = BLS_X; + let y0 = r.square(); + let mut y1 = y0; + exp_by_x(&mut y1, x); + x >>= 1; + let mut y2 = y1; + exp_by_x(&mut y2, x); + x <<= 1; + let mut y3 = r; + y3.conjugate(); + y1.mul_assign(&y3); + y1.conjugate(); + y1.mul_assign(&y2); + y2 = y1; + exp_by_x(&mut y2, x); + y3 = y2; + exp_by_x(&mut y3, x); + y1.conjugate(); + y3.mul_assign(&y1); + y1.conjugate(); + y1.frobenius_map(3); + y2.frobenius_map(2); + y1.mul_assign(&y2); + y2 = y3; + exp_by_x(&mut y2, x); + y2.mul_assign(&y0); + y2.mul_assign(&r); + y1.mul_assign(&y2); + y2 = y3; + y2.frobenius_map(1); + y1.mul_assign(&y2); + + y1 + }) } } @@ -184,41 +182,35 @@ impl G2Prepared { fn doubling_step(r: &mut G2) -> (Fq2, Fq2, Fq2) { // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf - let mut tmp0 = r.x; - tmp0.square(); + let mut tmp0 = r.x.square(); - let mut tmp1 = r.y; - tmp1.square(); + let mut tmp1 = r.y.square(); - let mut tmp2 = tmp1; - tmp2.square(); + let mut tmp2 = tmp1.square(); let mut tmp3 = tmp1; tmp3.add_assign(&r.x); - tmp3.square(); + tmp3 = tmp3.square(); tmp3.sub_assign(&tmp0); tmp3.sub_assign(&tmp2); - tmp3.double(); + tmp3 = tmp3.double(); - let mut tmp4 = tmp0; - tmp4.double(); + let mut tmp4 = tmp0.double(); tmp4.add_assign(&tmp0); let mut tmp6 = r.x; tmp6.add_assign(&tmp4); - let mut tmp5 = tmp4; - tmp5.square(); + let tmp5 = tmp4.square(); - let mut zsquared = r.z; - zsquared.square(); + let zsquared = r.z.square(); r.x = tmp5; r.x.sub_assign(&tmp3); r.x.sub_assign(&tmp3); r.z.add_assign(&r.y); - r.z.square(); + r.z = r.z.square(); r.z.sub_assign(&tmp1); r.z.sub_assign(&zsquared); @@ -226,47 +218,41 @@ impl G2Prepared { r.y.sub_assign(&r.x); r.y.mul_assign(&tmp4); - tmp2.double(); - tmp2.double(); - tmp2.double(); + tmp2 = tmp2.double().double().double(); r.y.sub_assign(&tmp2); tmp3 = tmp4; tmp3.mul_assign(&zsquared); - tmp3.double(); - tmp3.negate(); + tmp3 = tmp3.double().neg(); - tmp6.square(); + tmp6 = tmp6.square(); tmp6.sub_assign(&tmp0); tmp6.sub_assign(&tmp5); - tmp1.double(); - tmp1.double(); + tmp1 = tmp1.double().double(); tmp6.sub_assign(&tmp1); tmp0 = r.z; tmp0.mul_assign(&zsquared); - tmp0.double(); + tmp0 = tmp0.double(); (tmp0, tmp3, tmp6) } fn addition_step(r: &mut G2, q: &G2Affine) -> (Fq2, Fq2, Fq2) { // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf - let mut zsquared = r.z; - zsquared.square(); + let zsquared = r.z.square(); - let mut ysquared = q.y; - ysquared.square(); + let ysquared = q.y.square(); let mut t0 = zsquared; t0.mul_assign(&q.x); let mut t1 = q.y; t1.add_assign(&r.z); - t1.square(); + t1 = t1.square(); t1.sub_assign(&ysquared); t1.sub_assign(&zsquared); t1.mul_assign(&zsquared); @@ -274,12 +260,9 @@ impl G2Prepared { let mut t2 = t0; t2.sub_assign(&r.x); - let mut t3 = t2; - t3.square(); + let t3 = t2.square(); - let mut t4 = t3; - t4.double(); - t4.double(); + let t4 = t3.double().double(); let mut t5 = t4; t5.mul_assign(&t2); @@ -294,14 +277,13 @@ impl G2Prepared { let mut t7 = t4; t7.mul_assign(&r.x); - r.x = t6; - r.x.square(); + r.x = t6.square(); r.x.sub_assign(&t5); r.x.sub_assign(&t7); r.x.sub_assign(&t7); r.z.add_assign(&t2); - r.z.square(); + r.z = r.z.square(); r.z.sub_assign(&zsquared); r.z.sub_assign(&t3); @@ -314,29 +296,26 @@ impl G2Prepared { t0 = r.y; t0.mul_assign(&t5); - t0.double(); + t0 = t0.double(); r.y = t8; r.y.sub_assign(&t0); - t10.square(); + t10 = t10.square(); t10.sub_assign(&ysquared); - let mut ztsquared = r.z; - ztsquared.square(); + let ztsquared = r.z.square(); t10.sub_assign(&ztsquared); - t9.double(); + t9 = t9.double(); t9.sub_assign(&t10); - t10 = r.z; - t10.double(); + t10 = r.z.double(); - t6.negate(); + t6 = t6.neg(); - t1 = t6; - t1.double(); + t1 = t6.double(); (t10, t1, t9) } diff --git a/pairing/src/bls12_381/tests/mod.rs b/pairing/src/bls12_381/tests/mod.rs index 636fcfe..9c5b2c9 100644 --- a/pairing/src/bls12_381/tests/mod.rs +++ b/pairing/src/bls12_381/tests/mod.rs @@ -189,12 +189,14 @@ fn test_g1_uncompressed_invalid_vectors() { let mut x = Fq::one(); loop { - let mut x3b = x; - x3b.square(); + let mut x3b = x.square(); x3b.mul_assign(&x); x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API? - if let Some(y) = x3b.sqrt() { + let y = x3b.sqrt(); + if y.is_some().into() { + let y = y.unwrap(); + // We know this is on the curve, but it's likely not going to be in the correct subgroup. x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); y.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); @@ -326,15 +328,17 @@ fn test_g2_uncompressed_invalid_vectors() { let mut x = Fq2::one(); loop { - let mut x3b = x; - x3b.square(); + let mut x3b = x.square(); x3b.mul_assign(&x); x3b.add_assign(&Fq2 { c0: Fq::from_repr(FqRepr::from(4)).unwrap(), c1: Fq::from_repr(FqRepr::from(4)).unwrap(), }); // TODO: perhaps expose coeff_b through API? - if let Some(y) = x3b.sqrt() { + let y = x3b.sqrt(); + if y.is_some().into() { + let y = y.unwrap(); + // We know this is on the curve, but it's likely not going to be in the correct subgroup. x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); x.c0.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); @@ -422,12 +426,11 @@ fn test_g1_compressed_invalid_vectors() { let mut x = Fq::one(); loop { - let mut x3b = x; - x3b.square(); + let mut x3b = x.square(); x3b.mul_assign(&x); x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API? - if let Some(_) = x3b.sqrt() { + if x3b.sqrt().is_some().into() { x.add_assign(&Fq::one()); } else { x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); @@ -447,12 +450,11 @@ fn test_g1_compressed_invalid_vectors() { let mut x = Fq::one(); loop { - let mut x3b = x; - x3b.square(); + let mut x3b = x.square(); x3b.mul_assign(&x); x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API? - if let Some(_) = x3b.sqrt() { + if x3b.sqrt().is_some().into() { // We know this is on the curve, but it's likely not going to be in the correct subgroup. x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); o.as_mut()[0] |= 0b1000_0000; @@ -553,15 +555,14 @@ fn test_g2_compressed_invalid_vectors() { }; loop { - let mut x3b = x; - x3b.square(); + let mut x3b = x.square(); x3b.mul_assign(&x); x3b.add_assign(&Fq2 { c0: Fq::from_repr(FqRepr::from(4)).unwrap(), c1: Fq::from_repr(FqRepr::from(4)).unwrap(), }); // TODO: perhaps expose coeff_b through API? - if let Some(_) = x3b.sqrt() { + if x3b.sqrt().is_some().into() { x.add_assign(&Fq2::one()); } else { x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); @@ -585,15 +586,14 @@ fn test_g2_compressed_invalid_vectors() { }; loop { - let mut x3b = x; - x3b.square(); + let mut x3b = x.square(); x3b.mul_assign(&x); x3b.add_assign(&Fq2 { c0: Fq::from_repr(FqRepr::from(4)).unwrap(), c1: Fq::from_repr(FqRepr::from(4)).unwrap(), }); // TODO: perhaps expose coeff_b through API? - if let Some(_) = x3b.sqrt() { + if x3b.sqrt().is_some().into() { // We know this is on the curve, but it's likely not going to be in the correct subgroup. x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); x.c0.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); diff --git a/pairing/src/lib.rs b/pairing/src/lib.rs index 89e5873..0081d81 100644 --- a/pairing/src/lib.rs +++ b/pairing/src/lib.rs @@ -21,19 +21,18 @@ pub mod tests; pub mod bls12_381; use ff::{Field, PrimeField, ScalarEngine, SqrtField}; -use group::{CurveAffine, CurveProjective}; +use group::{CurveAffine, CurveOps, CurveOpsOwned, CurveProjective}; +use subtle::CtOption; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are /// of prime order `r`, and are equipped with a bilinear pairing function. pub trait Engine: ScalarEngine { /// The projective representation of an element in G1. - type G1: CurveProjective< - Engine = Self, - Base = Self::Fq, - Scalar = Self::Fr, - Affine = Self::G1Affine, - > + From; + type G1: CurveProjective + + From + + CurveOps + + CurveOpsOwned; /// The affine representation of an element in G1. type G1Affine: PairingCurveAffine< @@ -46,12 +45,10 @@ pub trait Engine: ScalarEngine { > + From; /// The projective representation of an element in G2. - type G2: CurveProjective< - Engine = Self, - Base = Self::Fqe, - Scalar = Self::Fr, - Affine = Self::G2Affine, - > + From; + type G2: CurveProjective + + From + + CurveOps + + CurveOpsOwned; /// The affine representation of an element in G2. type G2Affine: PairingCurveAffine< @@ -83,7 +80,7 @@ pub trait Engine: ScalarEngine { >; /// Perform final exponentiation of the result of a miller loop. - fn final_exponentiation(_: &Self::Fqk) -> Option; + fn final_exponentiation(_: &Self::Fqk) -> CtOption; /// Performs a complete pairing operation `(p, q)`. fn pairing(p: G1, q: G2) -> Self::Fqk diff --git a/pairing/src/tests/engine.rs b/pairing/src/tests/engine.rs index b6ae50e..0776e5d 100644 --- a/pairing/src/tests/engine.rs +++ b/pairing/src/tests/engine.rs @@ -1,6 +1,7 @@ use group::{CurveAffine, CurveProjective}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; +use std::ops::MulAssign; use crate::{Engine, Field, PairingCurveAffine, PrimeField}; @@ -129,7 +130,7 @@ fn random_bilinearity_tests() { let mut cd = c; cd.mul_assign(&d); - let abcd = E::pairing(a, b).pow(cd.into_repr()); + let abcd = E::pairing(a, b).pow_vartime(cd.into_repr()); assert_eq!(acbd, adbc); assert_eq!(acbd, abcd); diff --git a/pairing/src/tests/field.rs b/pairing/src/tests/field.rs index 8f3d8d9..7ddb365 100644 --- a/pairing/src/tests/field.rs +++ b/pairing/src/tests/field.rs @@ -1,4 +1,4 @@ -use ff::{Field, LegendreSymbol, PrimeField, SqrtField}; +use ff::{Field, PrimeField, SqrtField}; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -9,12 +9,12 @@ pub fn random_frobenius_tests>(characteristic: C, maxp ]); for _ in 0..100 { - for i in 0..(maxpower + 1) { + for i in 0..=maxpower { let mut a = F::random(&mut rng); let mut b = a; for _ in 0..i { - a = a.pow(&characteristic); + a = a.pow_vartime(&characteristic); } b.frobenius_map(i); @@ -31,27 +31,22 @@ pub fn random_sqrt_tests() { for _ in 0..10000 { let a = F::random(&mut rng); - let mut b = a; - b.square(); - assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + let b = a.square(); let b = b.sqrt().unwrap(); - let mut negb = b; - negb.negate(); + let negb = b.neg(); assert!(a == b || a == negb); } let mut c = F::one(); for _ in 0..10000 { - let mut b = c; - b.square(); - assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + let mut b = c.square(); b = b.sqrt().unwrap(); if b != c { - b.negate(); + b = b.neg(); } assert_eq!(b, c); @@ -77,12 +72,11 @@ pub fn random_field_tests() { assert!(F::zero().is_zero()); { - let mut z = F::zero(); - z.negate(); + let z = F::zero().neg(); assert!(z.is_zero()); } - assert!(F::zero().inverse().is_none()); + assert!(bool::from(F::zero().invert().is_none())); // Multiplication by zero { @@ -204,8 +198,7 @@ fn random_subtraction_tests(rng: &mut R) { fn random_negation_tests(rng: &mut R) { for _ in 0..10000 { let a = F::random(rng); - let mut b = a; - b.negate(); + let mut b = a.neg(); b.add_assign(&a); assert!(b.is_zero()); @@ -214,32 +207,24 @@ fn random_negation_tests(rng: &mut R) { fn random_doubling_tests(rng: &mut R) { for _ in 0..10000 { - let mut a = F::random(rng); - let mut b = a; - a.add_assign(&b); - b.double(); - - assert_eq!(a, b); + let a = F::random(rng); + assert_eq!(a + a, a.double()); } } fn random_squaring_tests(rng: &mut R) { for _ in 0..10000 { - let mut a = F::random(rng); - let mut b = a; - a.mul_assign(&b); - b.square(); - - assert_eq!(a, b); + let a = F::random(rng); + assert_eq!(a * a, a.square()); } } fn random_inversion_tests(rng: &mut R) { - assert!(F::zero().inverse().is_none()); + assert!(bool::from(F::zero().invert().is_none())); for _ in 0..10000 { let mut a = F::random(rng); - let b = a.inverse().unwrap(); // probablistically nonzero + let b = a.invert().unwrap(); // probablistically nonzero a.mul_assign(&b); assert_eq!(a, F::one()); diff --git a/pairing/src/tests/repr.rs b/pairing/src/tests/repr.rs index 67badd8..cde3ab3 100644 --- a/pairing/src/tests/repr.rs +++ b/pairing/src/tests/repr.rs @@ -68,7 +68,7 @@ fn random_shl_tests() { for _ in 0..100 { let r = P::random(&mut rng).into_repr(); - for shift in 0..(r.num_bits() + 1) { + for shift in 0..=r.num_bits() { let mut r1 = r; let mut r2 = r; @@ -92,7 +92,7 @@ fn random_shr_tests() { for _ in 0..100 { let r = P::random(&mut rng).into_repr(); - for shift in 0..(r.num_bits() + 1) { + for shift in 0..=r.num_bits() { let mut r1 = r; let mut r2 = r; diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..5edffce --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +1.39.0 diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index d3fab13..b40744d 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -28,13 +28,19 @@ rand_core = "0.5.1" ripemd160 = { version = "0.8", optional = true } secp256k1 = { version = "=0.15.0", optional = true } sha2 = "0.8" +subtle = "2.2.1" [dev-dependencies] +criterion = "0.3" hex-literal = "0.2" rand_xorshift = "0.2" [features] transparent-inputs = ["ripemd160", "secp256k1"] +[[bench]] +name = "pedersen_hash" +harness = false + [badges] maintenance = { status = "actively-developed" } diff --git a/zcash_primitives/benches/pedersen_hash.rs b/zcash_primitives/benches/pedersen_hash.rs index 3ea652a..6510936 100644 --- a/zcash_primitives/benches/pedersen_hash.rs +++ b/zcash_primitives/benches/pedersen_hash.rs @@ -1,17 +1,10 @@ -#![feature(test)] - -extern crate pairing; -extern crate rand_core; -extern crate test; -extern crate zcash_primitives; - +use criterion::{criterion_group, criterion_main, Criterion}; use pairing::bls12_381::Bls12; use rand_core::{OsRng, RngCore}; use zcash_primitives::jubjub::JubjubBls12; use zcash_primitives::pedersen_hash::{pedersen_hash, Personalization}; -#[bench] -fn bench_pedersen_hash(b: &mut test::Bencher) { +fn bench_pedersen_hash(c: &mut Criterion) { let params = JubjubBls12::new(); let rng = &mut OsRng; let bits = (0..510) @@ -19,5 +12,10 @@ fn bench_pedersen_hash(b: &mut test::Bencher) { .collect::>(); let personalization = Personalization::MerkleTree(31); - b.iter(|| pedersen_hash::(personalization, bits.clone(), ¶ms)); + c.bench_function("Pedersen hash", |b| { + b.iter(|| pedersen_hash::(personalization, bits.clone(), ¶ms)) + }); } + +criterion_group!(benches, bench_pedersen_hash); +criterion_main!(benches); diff --git a/zcash_primitives/src/jubjub/edwards.rs b/zcash_primitives/src/jubjub/edwards.rs index 7e46912..1b3ebc0 100644 --- a/zcash_primitives/src/jubjub/edwards.rs +++ b/zcash_primitives/src/jubjub/edwards.rs @@ -1,4 +1,6 @@ use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; +use subtle::CtOption; use super::{montgomery, JubjubEngine, JubjubParams, PrimeOrder, Unknown}; @@ -89,8 +91,14 @@ impl Point { y_repr.as_mut()[3] &= 0x7fffffffffffffff; match E::Fr::from_repr(y_repr) { - Ok(y) => Self::get_for_y(y, x_sign, params) - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "not on curve")), + Ok(y) => { + let p = Self::get_for_y(y, x_sign, params); + if bool::from(p.is_some()) { + Ok(p.unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::InvalidInput, "not on curve")) + } + } Err(_) => Err(io::Error::new( io::ErrorKind::InvalidInput, "y is not in field", @@ -98,14 +106,13 @@ impl Point { } } - pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option { + pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> CtOption { // Given a y on the curve, x^2 = (y^2 - 1) / (dy^2 + 1) // This is defined for all valid y-coordinates, // as dy^2 + 1 = 0 has no solution in Fr. // tmp1 = y^2 - let mut tmp1 = y; - tmp1.square(); + let mut tmp1 = y.square(); // tmp2 = (y^2 * d) + 1 let mut tmp2 = tmp1; @@ -115,33 +122,27 @@ impl Point { // tmp1 = y^2 - 1 tmp1.sub_assign(&E::Fr::one()); - match tmp2.inverse() { - Some(tmp2) => { - // tmp1 = (y^2 - 1) / (dy^2 + 1) - tmp1.mul_assign(&tmp2); + tmp2.invert().and_then(|tmp2| { + // tmp1 = (y^2 - 1) / (dy^2 + 1) + tmp1.mul_assign(&tmp2); - match tmp1.sqrt() { - Some(mut x) => { - if x.into_repr().is_odd() != sign { - x.negate(); - } - - let mut t = x; - t.mul_assign(&y); - - Some(Point { - x, - y, - t, - z: E::Fr::one(), - _marker: PhantomData, - }) - } - None => None, + tmp1.sqrt().map(|mut x| { + if x.into_repr().is_odd() != sign { + x = x.neg(); } - } - None => None, - } + + let mut t = x; + t.mul_assign(&y); + + Point { + x, + y, + t, + z: E::Fr::one(), + _marker: PhantomData, + } + }) + }) } /// This guarantees the point is in the prime order subgroup @@ -157,8 +158,9 @@ impl Point { let y = E::Fr::random(rng); let sign = rng.next_u32() % 2 != 0; - if let Some(p) = Self::get_for_y(y, sign, params) { - return p; + let p = Self::get_for_y(y, sign, params); + if bool::from(p.is_some()) { + return p.unwrap(); } } } @@ -210,12 +212,9 @@ impl Point { // only point of order 2 that is not the neutral element. if y.is_zero() { // This must be the point (0, 0) as above. - let mut neg1 = E::Fr::one(); - neg1.negate(); - Point { x: E::Fr::zero(), - y: neg1, + y: E::Fr::one().neg(), t: E::Fr::zero(), z: E::Fr::one(), _marker: PhantomData, @@ -306,7 +305,7 @@ impl Point { /// Convert to affine coordinates pub fn to_xy(&self) -> (E::Fr, E::Fr) { - let zinv = self.z.inverse().unwrap(); + let zinv = self.z.invert().unwrap(); let mut x = self.x; x.mul_assign(&zinv); @@ -321,8 +320,8 @@ impl Point { pub fn negate(&self) -> Self { let mut p = self.clone(); - p.x.negate(); - p.t.negate(); + p.x = p.x.neg(); + p.t = p.t.neg(); p } @@ -335,27 +334,22 @@ impl Point { // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd // A = X1^2 - let mut a = self.x; - a.square(); + let a = self.x.square(); // B = Y1^2 - let mut b = self.y; - b.square(); + let b = self.y.square(); // C = 2*Z1^2 - let mut c = self.z; - c.square(); - c.double(); + let c = self.z.square().double(); // D = a*A // = -A - let mut d = a; - d.negate(); + let d = a.neg(); // E = (X1+Y1)^2 - A - B let mut e = self.x; e.add_assign(&self.y); - e.square(); + e = e.square(); e.add_assign(&d); // -A = D e.sub_assign(&b); diff --git a/zcash_primitives/src/jubjub/fs.rs b/zcash_primitives/src/jubjub/fs.rs index 81120e6..ef1ccbe 100644 --- a/zcash_primitives/src/jubjub/fs.rs +++ b/zcash_primitives/src/jubjub/fs.rs @@ -1,10 +1,11 @@ use byteorder::{ByteOrder, LittleEndian}; use ff::{ - adc, mac_with_carry, sbb, BitIterator, Field, - LegendreSymbol::{self, *}, - PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField, + adc, mac_with_carry, sbb, BitIterator, Field, PrimeField, PrimeFieldDecodingError, + PrimeFieldRepr, SqrtField, }; use rand_core::RngCore; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use super::ToUniform; @@ -256,6 +257,21 @@ impl PrimeFieldRepr for FsRepr { #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Fs(FsRepr); +impl Default for Fs { + fn default() -> Self { + Fs::zero() + } +} + +impl ConstantTimeEq for Fs { + fn ct_eq(&self, other: &Fs) -> Choice { + (self.0).0[0].ct_eq(&(other.0).0[0]) + & (self.0).0[1].ct_eq(&(other.0).0[1]) + & (self.0).0[2].ct_eq(&(other.0).0[2]) + & (self.0).0[3].ct_eq(&(other.0).0[3]) + } +} + impl ::std::fmt::Display for Fs { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { write!(f, "Fs({})", self.into_repr()) @@ -268,6 +284,166 @@ impl From for FsRepr { } } +impl ConditionallySelectable for Fs { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fs(FsRepr([ + u64::conditional_select(&(a.0).0[0], &(b.0).0[0], choice), + u64::conditional_select(&(a.0).0[1], &(b.0).0[1], choice), + u64::conditional_select(&(a.0).0[2], &(b.0).0[2], choice), + u64::conditional_select(&(a.0).0[3], &(b.0).0[3], choice), + ])) + } +} + +impl Neg for Fs { + type Output = Self; + + #[inline] + fn neg(mut self) -> Self { + if !self.is_zero() { + let mut tmp = MODULUS; + tmp.sub_noborrow(&self.0); + self.0 = tmp; + } + self + } +} + +impl<'r> Add<&'r Fs> for Fs { + type Output = Self; + + #[inline] + fn add(self, other: &Self) -> Self { + let mut ret = self; + ret.add_assign(other); + ret + } +} + +impl Add for Fs { + type Output = Self; + + #[inline] + fn add(self, other: Self) -> Self { + self + &other + } +} + +impl<'r> AddAssign<&'r Fs> for Fs { + #[inline] + fn add_assign(&mut self, other: &Self) { + // This cannot exceed the backing capacity. + self.0.add_nocarry(&other.0); + + // However, it may need to be reduced. + self.reduce(); + } +} + +impl AddAssign for Fs { + #[inline] + fn add_assign(&mut self, other: Self) { + self.add_assign(&other); + } +} + +impl<'r> Sub<&'r Fs> for Fs { + type Output = Self; + + #[inline] + fn sub(self, other: &Self) -> Self { + let mut ret = self; + ret.sub_assign(other); + ret + } +} + +impl Sub for Fs { + type Output = Self; + + #[inline] + fn sub(self, other: Self) -> Self { + self - &other + } +} + +impl<'r> SubAssign<&'r Fs> for Fs { + #[inline] + fn sub_assign(&mut self, other: &Self) { + // If `other` is larger than `self`, we'll need to add the modulus to self first. + if other.0 > self.0 { + self.0.add_nocarry(&MODULUS); + } + + self.0.sub_noborrow(&other.0); + } +} + +impl SubAssign for Fs { + #[inline] + fn sub_assign(&mut self, other: Self) { + self.sub_assign(&other); + } +} + +impl<'r> Mul<&'r Fs> for Fs { + type Output = Self; + + #[inline] + fn mul(self, other: &Self) -> Self { + let mut ret = self; + ret.mul_assign(other); + ret + } +} + +impl Mul for Fs { + type Output = Self; + + #[inline] + fn mul(self, other: Self) -> Self { + self * &other + } +} + +impl<'r> MulAssign<&'r Fs> for Fs { + #[inline] + fn mul_assign(&mut self, other: &Self) { + let mut carry = 0; + let r0 = mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); + let r1 = mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); + let r2 = mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); + let r3 = mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); + let r4 = carry; + let mut carry = 0; + let r1 = mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); + let r2 = mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); + let r3 = mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); + let r5 = carry; + let mut carry = 0; + let r2 = mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); + let r3 = mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); + let r5 = mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); + let r6 = carry; + let mut carry = 0; + let r3 = mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); + let r5 = mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); + let r6 = mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); + let r7 = carry; + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + } +} + +impl MulAssign for Fs { + #[inline] + fn mul_assign(&mut self, other: Self) { + self.mul_assign(&other); + } +} + impl PrimeField for Fs { type Repr = FsRepr; @@ -278,7 +454,7 @@ impl PrimeField for Fs { Ok(r) } else { - Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) + Err(PrimeFieldDecodingError::NotInField) } } @@ -352,45 +528,23 @@ impl Field for Fs { } #[inline] - fn add_assign(&mut self, other: &Fs) { + fn double(&self) -> Self { + let mut ret = *self; + // This cannot exceed the backing capacity. - self.0.add_nocarry(&other.0); + ret.0.mul2(); // However, it may need to be reduced. - self.reduce(); + ret.reduce(); + + ret } - #[inline] - fn double(&mut self) { - // This cannot exceed the backing capacity. - self.0.mul2(); - - // However, it may need to be reduced. - self.reduce(); - } - - #[inline] - fn sub_assign(&mut self, other: &Fs) { - // If `other` is larger than `self`, we'll need to add the modulus to self first. - if other.0 > self.0 { - self.0.add_nocarry(&MODULUS); - } - - self.0.sub_noborrow(&other.0); - } - - #[inline] - fn negate(&mut self) { - if !self.is_zero() { - let mut tmp = MODULUS; - tmp.sub_noborrow(&self.0); - self.0 = tmp; - } - } - - fn inverse(&self) -> Option { + /// WARNING: THIS IS NOT ACTUALLY CONSTANT TIME YET! + /// THIS WILL BE REPLACED BY THE jubjub CRATE, WHICH IS CONSTANT TIME! + fn invert(&self) -> CtOption { if self.is_zero() { - None + CtOption::new(Self::zero(), Choice::from(0)) } else { // Guajardo Kumar Paar Pelzl // Efficient Software-Implementation of Finite Fields with Applications to Cryptography @@ -436,9 +590,9 @@ impl Field for Fs { } if u == one { - Some(b) + CtOption::new(b, Choice::from(1)) } else { - Some(c) + CtOption::new(c, Choice::from(1)) } } } @@ -449,36 +603,7 @@ impl Field for Fs { } #[inline] - fn mul_assign(&mut self, other: &Fs) { - let mut carry = 0; - let r0 = mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); - let r1 = mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); - let r2 = mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); - let r3 = mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); - let r4 = carry; - let mut carry = 0; - let r1 = mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); - let r2 = mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); - let r3 = mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); - let r4 = mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); - let r5 = carry; - let mut carry = 0; - let r2 = mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); - let r3 = mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); - let r4 = mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); - let r5 = mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); - let r6 = carry; - let mut carry = 0; - let r3 = mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); - let r4 = mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); - let r5 = mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); - let r6 = mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); - let r7 = carry; - self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); - } - - #[inline] - fn square(&mut self) { + fn square(&self) -> Self { let mut carry = 0; let r1 = mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); let r2 = mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); @@ -509,7 +634,10 @@ impl Field for Fs { let r5 = adc(r5, 0, &mut carry); let r6 = mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); let r7 = adc(r7, 0, &mut carry); - self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + + let mut ret = *self; + ret.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + ret } } @@ -587,7 +715,7 @@ impl Fs { fn mul_bits>(&self, bits: BitIterator) -> Self { let mut res = Self::zero(); for bit in bits { - res.double(); + res = res.double(); if bit { res.add_assign(self) @@ -611,51 +739,28 @@ impl ToUniform for Fs { } impl SqrtField for Fs { - fn legendre(&self) -> LegendreSymbol { - // s = self^((s - 1) // 2) - let s = self.pow([ - 0x684b872f6b7b965b, - 0x53341049e6640841, - 0x83339d80809a1d80, - 0x73eda753299d7d4, - ]); - if s == Self::zero() { - Zero - } else if s == Self::one() { - QuadraticResidue - } else { - QuadraticNonResidue - } - } - - fn sqrt(&self) -> Option { + fn sqrt(&self) -> CtOption { // Shank's algorithm for s mod 4 = 3 // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) // a1 = self^((s - 3) // 4) - let mut a1 = self.pow([ + let mut a1 = self.pow_vartime([ 0xb425c397b5bdcb2d, 0x299a0824f3320420, 0x4199cec0404d0ec0, 0x39f6d3a994cebea, ]); - let mut a0 = a1; - a0.square(); + let mut a0 = a1.square(); a0.mul_assign(self); + a1.mul_assign(self); - if a0 == NEGATIVE_ONE { - None - } else { - a1.mul_assign(self); - Some(a1) - } + CtOption::new(a1, !a0.ct_eq(&NEGATIVE_ONE)) } } #[test] fn test_neg_one() { - let mut o = Fs::one(); - o.negate(); + let o = Fs::one().neg(); assert_eq!(NEGATIVE_ONE, o); } @@ -907,27 +1012,6 @@ fn test_fs_repr_sub_noborrow() { } } -#[test] -fn test_fs_legendre() { - assert_eq!(QuadraticResidue, Fs::one().legendre()); - assert_eq!(Zero, Fs::zero().legendre()); - - let e = FsRepr([ - 0x8385eec23df1f88e, - 0x9a01fb412b2dba16, - 0x4c928edcdd6c22f, - 0x9f2df7ef69ecef9, - ]); - assert_eq!(QuadraticResidue, Fs::from_repr(e).unwrap().legendre()); - let e = FsRepr([ - 0xe8ed9f299da78568, - 0x35efdebc88b2209, - 0xc82125cb1f916dbe, - 0x6813d2b38c39bd0, - ]); - assert_eq!(QuadraticNonResidue, Fs::from_repr(e).unwrap().legendre()); -} - #[test] fn test_fr_repr_add_nocarry() { let mut rng = XorShiftRng::from_seed([ @@ -1307,16 +1391,15 @@ fn test_fs_mul_assign() { #[test] fn test_fr_squaring() { - let mut a = Fs(FsRepr([ + let a = Fs(FsRepr([ 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xe7db4ea6533afa8, ])); assert!(a.is_valid()); - a.square(); assert_eq!( - a, + a.square(), Fs::from_repr(FsRepr([ 0x12c7f55cbc52fbaa, 0xdedc98a0b5e6ce9e, @@ -1335,8 +1418,7 @@ fn test_fr_squaring() { // Ensure that (a * a) = a^2 let a = Fs::random(&mut rng); - let mut tmp = a; - tmp.square(); + let tmp = a.square(); let mut tmp2 = a; tmp2.mul_assign(&a); @@ -1346,8 +1428,8 @@ fn test_fr_squaring() { } #[test] -fn test_fs_inverse() { - assert!(Fs::zero().inverse().is_none()); +fn test_fs_invert() { + assert!(bool::from(Fs::zero().invert().is_none())); let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, @@ -1359,7 +1441,7 @@ fn test_fs_inverse() { for _ in 0..1000 { // Ensure that a * a^-1 = 1 let mut a = Fs::random(&mut rng); - let ainv = a.inverse().unwrap(); + let ainv = a.invert().unwrap(); a.mul_assign(&ainv); assert_eq!(a, one); } @@ -1374,19 +1456,15 @@ fn test_fs_double() { for _ in 0..1000 { // Ensure doubling a is equivalent to adding a to itself. - let mut a = Fs::random(&mut rng); - let mut b = a; - b.add_assign(&a); - a.double(); - assert_eq!(a, b); + let a = Fs::random(&mut rng); + assert_eq!(a.double(), a + a); } } #[test] -fn test_fs_negate() { +fn test_fs_neg() { { - let mut a = Fs::zero(); - a.negate(); + let a = Fs::zero().neg(); assert!(a.is_zero()); } @@ -1399,8 +1477,7 @@ fn test_fs_negate() { for _ in 0..1000 { // Ensure (a - (-a)) = 0. let mut a = Fs::random(&mut rng); - let mut b = a; - b.negate(); + let b = a.neg(); a.add_assign(&b); assert!(a.is_zero()); @@ -1418,7 +1495,7 @@ fn test_fs_pow() { // Exponentiate by various small numbers and ensure it consists with repeated // multiplication. let a = Fs::random(&mut rng); - let target = a.pow(&[i]); + let target = a.pow_vartime(&[i]); let mut c = Fs::one(); for _ in 0..i { c.mul_assign(&a); @@ -1430,7 +1507,7 @@ fn test_fs_pow() { // Exponentiating by the modulus should have no effect in a prime field. let a = Fs::random(&mut rng); - assert_eq!(a, a.pow(Fs::char())); + assert_eq!(a, a.pow_vartime(Fs::char())); } } @@ -1446,10 +1523,8 @@ fn test_fs_sqrt() { for _ in 0..1000 { // Ensure sqrt(a^2) = a or -a let a = Fs::random(&mut rng); - let mut nega = a; - nega.negate(); - let mut b = a; - b.square(); + let nega = a.neg(); + let b = a.square(); let b = b.sqrt().unwrap(); @@ -1460,10 +1535,9 @@ fn test_fs_sqrt() { // Ensure sqrt(a)^2 = a for random a let a = Fs::random(&mut rng); - if let Some(mut tmp) = a.sqrt() { - tmp.square(); - - assert_eq!(a, tmp); + let tmp = a.sqrt(); + if tmp.is_some().into() { + assert_eq!(a, tmp.unwrap().square()); } } } @@ -1614,7 +1688,7 @@ fn test_fs_root_of_unity() { Fs::from_repr(FsRepr::from(6)).unwrap() ); assert_eq!( - Fs::multiplicative_generator().pow([ + Fs::multiplicative_generator().pow_vartime([ 0x684b872f6b7b965b, 0x53341049e6640841, 0x83339d80809a1d80, @@ -1622,6 +1696,6 @@ fn test_fs_root_of_unity() { ]), Fs::root_of_unity() ); - assert_eq!(Fs::root_of_unity().pow([1 << Fs::S]), Fs::one()); - assert!(Fs::multiplicative_generator().sqrt().is_none()); + assert_eq!(Fs::root_of_unity().pow_vartime([1 << Fs::S]), Fs::one()); + assert!(bool::from(Fs::multiplicative_generator().sqrt().is_none())); } diff --git a/zcash_primitives/src/jubjub/mod.rs b/zcash_primitives/src/jubjub/mod.rs index 9428600..06a3810 100644 --- a/zcash_primitives/src/jubjub/mod.rs +++ b/zcash_primitives/src/jubjub/mod.rs @@ -195,8 +195,7 @@ impl JubjubParams for JubjubBls12 { impl JubjubBls12 { pub fn new() -> Self { let montgomery_a = Fr::from_str("40962").unwrap(); - let mut montgomery_2a = montgomery_a; - montgomery_2a.double(); + let montgomery_2a = montgomery_a.double(); let mut tmp_params = JubjubBls12 { // d = -(10240/10241) diff --git a/zcash_primitives/src/jubjub/montgomery.rs b/zcash_primitives/src/jubjub/montgomery.rs index 4e6c5e1..9cad803 100644 --- a/zcash_primitives/src/jubjub/montgomery.rs +++ b/zcash_primitives/src/jubjub/montgomery.rs @@ -1,4 +1,6 @@ use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; +use subtle::CtOption; use super::{edwards, JubjubEngine, JubjubParams, PrimeOrder, Unknown}; @@ -46,11 +48,10 @@ impl PartialEq for Point { } impl Point { - pub fn get_for_x(x: E::Fr, sign: bool, params: &E::Params) -> Option { + pub fn get_for_x(x: E::Fr, sign: bool, params: &E::Params) -> CtOption { // Given an x on the curve, y = sqrt(x^3 + A*x^2 + x) - let mut x2 = x; - x2.square(); + let mut x2 = x.square(); let mut rhs = x2; rhs.mul_assign(params.montgomery_a()); @@ -58,21 +59,18 @@ impl Point { x2.mul_assign(&x); rhs.add_assign(&x2); - match rhs.sqrt() { - Some(mut y) => { - if y.into_repr().is_odd() != sign { - y.negate(); - } - - Some(Point { - x, - y, - infinity: false, - _marker: PhantomData, - }) + rhs.sqrt().map(|mut y| { + if y.into_repr().is_odd() != sign { + y = y.neg(); } - None => None, - } + + Point { + x, + y, + infinity: false, + _marker: PhantomData, + } + }) } /// This guarantees the point is in the prime order subgroup @@ -88,8 +86,9 @@ impl Point { let x = E::Fr::random(rng); let sign = rng.next_u32() % 2 != 0; - if let Some(p) = Self::get_for_x(x, sign, params) { - return p; + let p = Self::get_for_x(x, sign, params); + if p.is_some().into() { + return p.unwrap(); } } } @@ -139,11 +138,11 @@ impl Point { { let mut tmp = E::Fr::one(); tmp.sub_assign(&y); - u.mul_assign(&tmp.inverse().unwrap()) + u.mul_assign(&tmp.invert().unwrap()) } let mut v = u; - v.mul_assign(&x.inverse().unwrap()); + v.mul_assign(&x.invert().unwrap()); // Scale it into the correct curve constants v.mul_assign(params.scale()); @@ -189,7 +188,7 @@ impl Point { pub fn negate(&self) -> Self { let mut p = self.clone(); - p.y.negate(); + p.y = p.y.neg(); p } @@ -215,24 +214,22 @@ impl Point { { let mut tmp = *params.montgomery_a(); tmp.mul_assign(&self.x); - tmp.double(); + tmp = tmp.double(); delta.add_assign(&tmp); } { - let mut tmp = self.x; - tmp.square(); + let mut tmp = self.x.square(); delta.add_assign(&tmp); - tmp.double(); + tmp = tmp.double(); delta.add_assign(&tmp); } { - let mut tmp = self.y; - tmp.double(); - delta.mul_assign(&tmp.inverse().expect("y is nonzero so this must be nonzero")); + let tmp = self.y.double(); + // y is nonzero so this must be nonzero + delta.mul_assign(&tmp.invert().unwrap()); } - let mut x3 = delta; - x3.square(); + let mut x3 = delta.square(); x3.sub_assign(params.montgomery_a()); x3.sub_assign(&self.x); x3.sub_assign(&self.x); @@ -241,7 +238,7 @@ impl Point { y3.sub_assign(&self.x); y3.mul_assign(&delta); y3.add_assign(&self.y); - y3.negate(); + y3 = y3.neg(); Point { x: x3, @@ -275,14 +272,11 @@ impl Point { { let mut tmp = other.x; tmp.sub_assign(&self.x); - delta.mul_assign( - &tmp.inverse() - .expect("self.x != other.x, so this must be nonzero"), - ); + // self.x != other.x, so this must be nonzero + delta.mul_assign(&tmp.invert().unwrap()); } - let mut x3 = delta; - x3.square(); + let mut x3 = delta.square(); x3.sub_assign(params.montgomery_a()); x3.sub_assign(&self.x); x3.sub_assign(&other.x); @@ -291,7 +285,7 @@ impl Point { y3.sub_assign(&self.x); y3.mul_assign(&delta); y3.add_assign(&self.y); - y3.negate(); + y3 = y3.neg(); Point { x: x3, diff --git a/zcash_primitives/src/jubjub/tests.rs b/zcash_primitives/src/jubjub/tests.rs index b2c12ae..84b3a96 100644 --- a/zcash_primitives/src/jubjub/tests.rs +++ b/zcash_primitives/src/jubjub/tests.rs @@ -1,6 +1,7 @@ use super::{edwards, montgomery, JubjubEngine, JubjubParams, PrimeOrder}; -use ff::{Field, LegendreSymbol, PrimeField, PrimeFieldRepr, SqrtField}; +use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -19,11 +20,9 @@ pub fn test_suite(params: &E::Params) { } fn is_on_mont_curve>(x: E::Fr, y: E::Fr, params: &P) -> bool { - let mut lhs = y; - lhs.square(); + let lhs = y.square(); - let mut x2 = x; - x2.square(); + let x2 = x.square(); let mut x3 = x2; x3.mul_assign(&x); @@ -41,11 +40,9 @@ fn is_on_twisted_edwards_curve>( y: E::Fr, params: &P, ) -> bool { - let mut x2 = x; - x2.square(); + let x2 = x.square(); - let mut y2 = y; - y2.square(); + let y2 = y.square(); // -x^2 + y^2 let mut lhs = y2; @@ -237,7 +234,9 @@ fn test_get_for(params: &E::Params) { let y = E::Fr::random(rng); let sign = rng.next_u32() % 2 == 1; - if let Some(mut p) = edwards::Point::::get_for_y(y, sign, params) { + let p = edwards::Point::::get_for_y(y, sign, params); + if bool::from(p.is_some()) { + let mut p = p.unwrap(); assert!(p.to_xy().0.into_repr().is_odd() == sign); p = p.negate(); assert!(edwards::Point::::get_for_y(y, !sign, params).unwrap() == p); @@ -309,23 +308,19 @@ fn test_back_and_forth(params: &E::Params) { fn test_jubjub_params(params: &E::Params) { // a = -1 - let mut a = E::Fr::one(); - a.negate(); + let a = E::Fr::one().neg(); { // Check that 2A is consistent with A - let mut tmp = *params.montgomery_a(); - tmp.double(); - - assert_eq!(&tmp, params.montgomery_2a()); + assert_eq!(¶ms.montgomery_a().double(), params.montgomery_2a()); } { // The twisted Edwards addition law is complete when d is nonsquare // and a is square. - assert!(params.edwards_d().legendre() == LegendreSymbol::QuadraticNonResidue); - assert!(a.legendre() == LegendreSymbol::QuadraticResidue); + assert!(bool::from(params.edwards_d().sqrt().is_none())); + assert!(bool::from(a.sqrt().is_some())); } { @@ -335,38 +330,37 @@ fn test_jubjub_params(params: &E::Params) { let mut tmp = *params.edwards_d(); // 1 / d is nonsquare - assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue); + assert!(bool::from(tmp.invert().unwrap().sqrt().is_none())); // tmp = -d - tmp.negate(); + tmp = tmp.neg(); // -d is nonsquare - assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + assert!(bool::from(tmp.sqrt().is_none())); // 1 / -d is nonsquare - assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue); + assert!(bool::from(tmp.invert().unwrap().sqrt().is_none())); } { // Check that A^2 - 4 is nonsquare: - let mut tmp = params.montgomery_a().clone(); - tmp.square(); + let mut tmp = params.montgomery_a().square(); tmp.sub_assign(&E::Fr::from_str("4").unwrap()); - assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + assert!(bool::from(tmp.sqrt().is_none())); } { // Check that A - 2 is nonsquare: let mut tmp = params.montgomery_a().clone(); tmp.sub_assign(&E::Fr::from_str("2").unwrap()); - assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + assert!(bool::from(tmp.sqrt().is_none())); } { // Check the validity of the scaling factor let mut tmp = a; tmp.sub_assign(¶ms.edwards_d()); - tmp = tmp.inverse().unwrap(); + tmp = tmp.invert().unwrap(); tmp.mul_assign(&E::Fr::from_str("4").unwrap()); tmp = tmp.sqrt().unwrap(); assert_eq!(&tmp, params.scale()); diff --git a/zcash_primitives/src/pedersen_hash.rs b/zcash_primitives/src/pedersen_hash.rs index 000d76b..ea182a5 100644 --- a/zcash_primitives/src/pedersen_hash.rs +++ b/zcash_primitives/src/pedersen_hash.rs @@ -2,6 +2,7 @@ use crate::jubjub::*; use ff::{Field, PrimeField, PrimeFieldRepr}; +use std::ops::{AddAssign, Neg}; #[derive(Copy, Clone)] pub enum Personalization { @@ -57,14 +58,14 @@ where if a { tmp.add_assign(&cur); } - cur.double(); // 2^1 * cur + cur = cur.double(); // 2^1 * cur if b { tmp.add_assign(&cur); } // conditionally negate if c { - tmp.negate(); + tmp = tmp.neg(); } acc.add_assign(&tmp); @@ -74,9 +75,7 @@ where if chunks_remaining == 0 { break; } else { - cur.double(); // 2^2 * cur - cur.double(); // 2^3 * cur - cur.double(); // 2^4 * cur + cur = cur.double().double().double(); // 2^4 * cur } } diff --git a/zcash_primitives/src/redjubjub.rs b/zcash_primitives/src/redjubjub.rs index 505d6cc..fcc3900 100644 --- a/zcash_primitives/src/redjubjub.rs +++ b/zcash_primitives/src/redjubjub.rs @@ -7,6 +7,7 @@ use crate::jubjub::{edwards::Point, FixedGenerators, JubjubEngine, JubjubParams, use ff::{Field, PrimeField, PrimeFieldRepr}; use rand_core::RngCore; use std::io::{self, Read, Write}; +use std::ops::{AddAssign, MulAssign, Neg}; use crate::util::hash_to_scalar; @@ -188,7 +189,7 @@ pub fn batch_verify<'a, E: JubjubEngine, R: RngCore>( let z = E::Fs::random(rng); s.mul_assign(&z); - s.negate(); + s = s.neg(); r = r.mul(z, params); diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index f346625..e34767b 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -5,9 +5,9 @@ use aes::Aes256; use blake2b_simd::Params as Blake2bParams; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; -use ff::Field; use fpe::ff1::{BinaryNumeralString, FF1}; use pairing::bls12_381::Bls12; +use std::ops::AddAssign; use crate::{ jubjub::{fs::Fs, FixedGenerators, JubjubEngine, JubjubParams, ToUniform}, diff --git a/zcash_proofs/src/circuit/ecc.rs b/zcash_proofs/src/circuit/ecc.rs index 851f0e5..05baf8b 100644 --- a/zcash_proofs/src/circuit/ecc.rs +++ b/zcash_proofs/src/circuit/ecc.rs @@ -2,6 +2,7 @@ use ff::Field; use pairing::Engine; +use std::ops::{AddAssign, MulAssign, Neg, SubAssign}; use bellman::{ConstraintSystem, SynthesisError}; @@ -322,8 +323,7 @@ impl EdwardsPoint { // Compute C = d*A*A let c = AllocatedNum::alloc(cs.namespace(|| "C"), || { - let mut t0 = *a.get_value().get()?; - t0.square(); + let mut t0 = a.get_value().get()?.square(); t0.mul_assign(params.edwards_d()); Ok(t0) @@ -339,18 +339,16 @@ impl EdwardsPoint { // Compute x3 = (2.A) / (1 + C) let x3 = AllocatedNum::alloc(cs.namespace(|| "x3"), || { let mut t0 = *a.get_value().get()?; - t0.double(); + t0 = t0.double(); let mut t1 = E::Fr::one(); t1.add_assign(c.get_value().get()?); - match t1.inverse() { - Some(t1) => { - t0.mul_assign(&t1); - - Ok(t0) - } - None => Err(SynthesisError::DivisionByZero), + let res = t1.invert().map(|t1| t0 * &t1); + if bool::from(res.is_some()) { + Ok(res.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) } })?; @@ -365,20 +363,17 @@ impl EdwardsPoint { // Compute y3 = (U - 2.A) / (1 - C) let y3 = AllocatedNum::alloc(cs.namespace(|| "y3"), || { let mut t0 = *a.get_value().get()?; - t0.double(); - t0.negate(); + t0 = t0.double().neg(); t0.add_assign(t.get_value().get()?); let mut t1 = E::Fr::one(); t1.sub_assign(c.get_value().get()?); - match t1.inverse() { - Some(t1) => { - t0.mul_assign(&t1); - - Ok(t0) - } - None => Err(SynthesisError::DivisionByZero), + let res = t1.invert().map(|t1| t0 * &t1); + if bool::from(res.is_some()) { + Ok(res.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) } })?; @@ -452,13 +447,11 @@ impl EdwardsPoint { let mut t1 = E::Fr::one(); t1.add_assign(c.get_value().get()?); - match t1.inverse() { - Some(t1) => { - t0.mul_assign(&t1); - - Ok(t0) - } - None => Err(SynthesisError::DivisionByZero), + let ret = t1.invert().map(|t1| t0 * &t1); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) } })?; @@ -479,13 +472,11 @@ impl EdwardsPoint { let mut t1 = E::Fr::one(); t1.sub_assign(c.get_value().get()?); - match t1.inverse() { - Some(t1) => { - t0.mul_assign(&t1); - - Ok(t0) - } - None => Err(SynthesisError::DivisionByZero), + let ret = t1.invert().map(|t1| t0 * &t1); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) } })?; @@ -522,13 +513,11 @@ impl MontgomeryPoint { let mut t0 = *self.x.get_value().get()?; t0.mul_assign(params.scale()); - match self.y.get_value().get()?.inverse() { - Some(invy) => { - t0.mul_assign(&invy); - - Ok(t0) - } - None => Err(SynthesisError::DivisionByZero), + let ret = self.y.get_value().get()?.invert().map(|invy| t0 * &invy); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) } })?; @@ -546,13 +535,11 @@ impl MontgomeryPoint { t0.sub_assign(&E::Fr::one()); t1.add_assign(&E::Fr::one()); - match t1.inverse() { - Some(t1) => { - t0.mul_assign(&t1); - - Ok(t0) - } - None => Err(SynthesisError::DivisionByZero), + let ret = t1.invert().map(|t1| t0 * &t1); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) } })?; @@ -594,12 +581,11 @@ impl MontgomeryPoint { let mut d = *other.x.get_value().get()?; d.sub_assign(self.x.get_value().get()?); - match d.inverse() { - Some(d) => { - n.mul_assign(&d); - Ok(n) - } - None => Err(SynthesisError::DivisionByZero), + let ret = d.invert().map(|d| n * &d); + if bool::from(ret.is_some()) { + Ok(ret.unwrap()) + } else { + Err(SynthesisError::DivisionByZero) } })?; @@ -612,8 +598,7 @@ impl MontgomeryPoint { // Compute x'' = lambda^2 - A - x - x' let xprime = AllocatedNum::alloc(cs.namespace(|| "xprime"), || { - let mut t0 = *lambda.get_value().get()?; - t0.square(); + let mut t0 = lambda.get_value().get()?.square(); t0.sub_assign(params.montgomery_a()); t0.sub_assign(self.x.get_value().get()?); t0.sub_assign(other.x.get_value().get()?); @@ -641,7 +626,7 @@ impl MontgomeryPoint { t0.sub_assign(self.x.get_value().get()?); t0.mul_assign(lambda.get_value().get()?); t0.add_assign(self.y.get_value().get()?); - t0.negate(); + t0 = t0.neg(); Ok(t0) })?; @@ -668,6 +653,7 @@ mod test { use pairing::bls12_381::{Bls12, Fr}; use rand_core::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; + use std::ops::SubAssign; use bellman::gadgets::test::*; use zcash_primitives::jubjub::fs::Fs; @@ -1039,8 +1025,9 @@ mod test { let x = Fr::random(rng); let s: bool = rng.next_u32() % 2 != 0; - if let Some(p) = montgomery::Point::::get_for_x(x, s, params) { - break p; + let p = montgomery::Point::::get_for_x(x, s, params); + if p.is_some().into() { + break p.unwrap(); } }; @@ -1048,8 +1035,9 @@ mod test { let x = Fr::random(rng); let s: bool = rng.next_u32() % 2 != 0; - if let Some(p) = montgomery::Point::::get_for_x(x, s, params) { - break p; + let p = montgomery::Point::::get_for_x(x, s, params); + if p.is_some().into() { + break p.unwrap(); } }; diff --git a/zcash_proofs/src/circuit/sapling.rs b/zcash_proofs/src/circuit/sapling.rs index ddb0f1c..9782a4f 100644 --- a/zcash_proofs/src/circuit/sapling.rs +++ b/zcash_proofs/src/circuit/sapling.rs @@ -245,7 +245,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let mut coeff = E::Fr::one(); for bit in &value_bits { value_num = value_num.add_bool_with_coeff(CS::one(), bit, coeff); - coeff.double(); + coeff = coeff.double(); } // Place the value in the note diff --git a/zcash_proofs/src/circuit/sprout/mod.rs b/zcash_proofs/src/circuit/sprout/mod.rs index 1877047..6afe677 100644 --- a/zcash_proofs/src/circuit/sprout/mod.rs +++ b/zcash_proofs/src/circuit/sprout/mod.rs @@ -268,7 +268,7 @@ impl NoteValue { let mut coeff = E::Fr::one(); for b in &self.bits { tmp = tmp + (coeff, b.get_variable()); - coeff.double(); + coeff = coeff.double(); } tmp diff --git a/zcash_proofs/src/sapling/prover.rs b/zcash_proofs/src/sapling/prover.rs index 32e4229..f7b0373 100644 --- a/zcash_proofs/src/sapling/prover.rs +++ b/zcash_proofs/src/sapling/prover.rs @@ -5,6 +5,7 @@ use bellman::{ use ff::Field; use pairing::bls12_381::{Bls12, Fr}; use rand_core::OsRng; +use std::ops::{AddAssign, Neg}; use zcash_primitives::{ jubjub::{edwards, fs::Fs, FixedGenerators, JubjubBls12, Unknown}, primitives::{Diversifier, Note, PaymentAddress, ProofGenerationKey, ValueCommitment}, @@ -200,8 +201,7 @@ impl SaplingProvingContext { // Accumulate the value commitment randomness in the context { - let mut tmp = rcv; - tmp.negate(); // Outputs subtract from the total. + let mut tmp = rcv.neg(); // Outputs subtract from the total. tmp.add_assign(&self.bsk); // Update the context