diff --git a/.github/workflows/ed448.yml b/.github/workflows/ed448.yml
new file mode 100644
index 000000000..a3da1cdcb
--- /dev/null
+++ b/.github/workflows/ed448.yml
@@ -0,0 +1,132 @@
+name: ed448
+
+on:
+ pull_request:
+ paths:
+ - ".github/workflows/ed448.yml"
+ - "ed448/**"
+ - "Cargo.*"
+ push:
+ branches: master
+
+defaults:
+ run:
+ working-directory: ed448
+
+env:
+ CARGO_INCREMENTAL: 0
+ RUSTFLAGS: "-Dwarnings"
+ RUSTDOCFLAGS: "-Dwarnings"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ rust:
+ - 1.85.0 # MSRV
+ - stable
+ target:
+ - thumbv7em-none-eabi
+ - wasm32-unknown-unknown
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ matrix.rust }}
+ targets: ${{ matrix.target }}
+ - run: cargo build --target ${{ matrix.target }} --release --no-default-features
+ - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc
+ - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pkcs8
+ - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features signing
+ - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features serde
+ - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features zeroize
+ - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,pkcs8,signing,serde,zeroize
+
+ benches:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ rust:
+ - 1.85.0 # MSRV
+ - stable
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ matrix.rust }}
+ - run: cargo build --all-features --benches
+
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ # 32-bit Linux
+ - target: i686-unknown-linux-gnu
+ rust: 1.85.0 # MSRV
+ deps: sudo apt update && sudo apt install gcc-multilib
+ - target: i686-unknown-linux-gnu
+ rust: stable
+ deps: sudo apt update && sudo apt install gcc-multilib
+
+ # 64-bit Linux
+ - target: x86_64-unknown-linux-gnu
+ rust: 1.85.0 # MSRV
+ - target: x86_64-unknown-linux-gnu
+ rust: stable
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ matrix.rust }}
+ targets: ${{ matrix.target }}
+ - run: ${{ matrix.deps }}
+ - run: cargo check --target ${{ matrix.target }} --all-features
+ - run: cargo test --release --target ${{ matrix.target }} --no-default-features
+ - run: cargo test --release --target ${{ matrix.target }}
+ - run: cargo test --release --target ${{ matrix.target }} --all-features
+
+ cross:
+ strategy:
+ matrix:
+ include:
+ # ARM32
+ - target: armv7-unknown-linux-gnueabihf
+ rust: 1.85.0 # MSRV (cross)
+ - target: armv7-unknown-linux-gnueabihf
+ rust: stable
+
+ # ARM64
+ - target: aarch64-unknown-linux-gnu
+ rust: 1.85.0 # MSRV (cross)
+ - target: aarch64-unknown-linux-gnu
+ rust: stable
+
+ # PPC32
+ - target: powerpc-unknown-linux-gnu
+ rust: 1.85.0 # MSRV (cross)
+ - target: powerpc-unknown-linux-gnu
+ rust: stable
+
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: ${{ matrix.deps }}
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ matrix.rust }}
+ targets: ${{ matrix.target }}
+ - uses: RustCrypto/actions/cross-install@master
+ - run: cross test --release --target ${{ matrix.target }} --all-features
+
+ doc:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: RustCrypto/actions/cargo-cache@master
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: stable
+ - run: cargo doc --all-features
diff --git a/Cargo.lock b/Cargo.lock
index 367dd339c..cee0fbba6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -74,14 +74,14 @@ version = "0.14.0-pre"
dependencies = [
"belt-hash",
"criterion",
- "der",
+ "der 0.8.0-rc.1",
"digest",
"elliptic-curve",
"hex",
- "hex-literal",
+ "hex-literal 1.0.0",
"hkdf",
"hmac",
- "pkcs8",
+ "pkcs8 0.11.0-rc.2",
"primeorder",
"proptest",
"rand_core 0.9.3",
@@ -135,7 +135,7 @@ version = "0.11.0-rc.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94"
dependencies = [
- "hybrid-array",
+ "hybrid-array 0.3.0",
]
[[package]]
@@ -228,6 +228,12 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
[[package]]
name = "const-oid"
version = "0.10.0"
@@ -316,12 +322,23 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+[[package]]
+name = "crypto-bigint"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96272c2ff28b807e09250b180ad1fb7889a3258f7455759b5c3c58b719467130"
+dependencies = [
+ "hybrid-array 0.2.3",
+ "num-traits",
+ "subtle",
+]
+
[[package]]
name = "crypto-bigint"
version = "0.7.0-pre.0"
-source = "git+https://github.com/RustCrypto/crypto-bigint.git#2734f1852d9a713dc92183bb6e2d4b987f1e38f1"
+source = "git+https://github.com/RustCrypto/crypto-bigint.git#22cfab7cf7080a376596d55cb6029f567a663c11"
dependencies = [
- "hybrid-array",
+ "hybrid-array 0.3.0",
"num-traits",
"rand_core 0.9.3",
"subtle",
@@ -334,7 +351,17 @@ version = "0.2.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "170d71b5b14dec99db7739f6fc7d6ec2db80b78c3acb77db48392ccc3d8a9ea0"
dependencies = [
- "hybrid-array",
+ "hybrid-array 0.3.0",
+]
+
+[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid 0.9.6",
+ "zeroize",
]
[[package]]
@@ -343,7 +370,7 @@ version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319"
dependencies = [
- "const-oid",
+ "const-oid 0.10.0",
"pem-rfc7468",
"zeroize",
]
@@ -355,7 +382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516"
dependencies = [
"block-buffer",
- "const-oid",
+ "const-oid 0.10.0",
"crypto-common",
"subtle",
]
@@ -365,13 +392,33 @@ name = "ecdsa"
version = "0.17.0-pre.9"
source = "git+https://github.com/RustCrypto/signatures.git#8324be36081a8fdf85d3b50bd769ab59f71b0289"
dependencies = [
- "der",
+ "der 0.8.0-rc.1",
"digest",
"elliptic-curve",
"rfc6979",
"serdect",
"signature",
- "spki",
+ "spki 0.8.0-rc.1",
+]
+
+[[package]]
+name = "ed448"
+version = "0.17.0-pre.0"
+dependencies = [
+ "crypto-bigint 0.6.1",
+ "elliptic-curve",
+ "hex",
+ "hex-literal 0.4.1",
+ "pkcs8 0.10.2",
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.3",
+ "serde_bare",
+ "serde_json",
+ "serdect",
+ "sha3",
+ "signature",
+ "subtle",
+ "zeroize",
]
[[package]]
@@ -387,15 +434,15 @@ source = "git+https://github.com/RustCrypto/traits.git#204a4e030fa98863429ccd379
dependencies = [
"base16ct",
"base64ct",
- "crypto-bigint",
+ "crypto-bigint 0.7.0-pre.0",
"digest",
"ff",
"group",
- "hex-literal",
+ "hex-literal 1.0.0",
"hkdf",
- "hybrid-array",
+ "hybrid-array 0.3.0",
"pem-rfc7468",
- "pkcs8",
+ "pkcs8 0.11.0-rc.2",
"rand_core 0.9.3",
"sec1",
"serde_json",
@@ -513,6 +560,12 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
[[package]]
name = "hex-literal"
version = "1.0.0"
@@ -537,6 +590,15 @@ dependencies = [
"digest",
]
+[[package]]
+name = "hybrid-array"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
+dependencies = [
+ "typenum",
+]
+
[[package]]
name = "hybrid-array"
version = "0.3.0"
@@ -593,7 +655,7 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"hex",
- "hex-literal",
+ "hex-literal 1.0.0",
"num-bigint 0.4.6",
"num-traits",
"once_cell",
@@ -705,7 +767,7 @@ version = "0.14.0-pre"
dependencies = [
"ecdsa",
"elliptic-curve",
- "hex-literal",
+ "hex-literal 1.0.0",
"primeorder",
"sec1",
"serdect",
@@ -718,7 +780,7 @@ dependencies = [
"blobby",
"ecdsa",
"elliptic-curve",
- "hex-literal",
+ "hex-literal 1.0.0",
"primeorder",
"rand_core 0.9.3",
"serdect",
@@ -733,7 +795,7 @@ dependencies = [
"criterion",
"ecdsa",
"elliptic-curve",
- "hex-literal",
+ "hex-literal 1.0.0",
"primeorder",
"proptest",
"rand_core 0.9.3",
@@ -749,7 +811,7 @@ dependencies = [
"criterion",
"ecdsa",
"elliptic-curve",
- "hex-literal",
+ "hex-literal 1.0.0",
"primeorder",
"proptest",
"rand_core 0.9.3",
@@ -766,7 +828,7 @@ dependencies = [
"criterion",
"ecdsa",
"elliptic-curve",
- "hex-literal",
+ "hex-literal 1.0.0",
"primefield",
"primeorder",
"proptest",
@@ -784,14 +846,24 @@ dependencies = [
"base64ct",
]
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der 0.7.9",
+ "spki 0.7.3",
+]
+
[[package]]
name = "pkcs8"
version = "0.11.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f22636de7c995e997ed3d8d2949b7414d4faba3efa7312a6c0e75d875a14bdd4"
dependencies = [
- "der",
- "spki",
+ "der 0.8.0-rc.1",
+ "spki 0.8.0-rc.1",
]
[[package]]
@@ -870,7 +942,7 @@ dependencies = [
"lazy_static",
"num-traits",
"rand",
- "rand_chacha",
+ "rand_chacha 0.3.1",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
@@ -906,7 +978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
- "rand_chacha",
+ "rand_chacha 0.3.1",
"rand_core 0.6.4",
]
@@ -920,6 +992,16 @@ dependencies = [
"rand_core 0.6.4",
]
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -1058,9 +1140,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a017a4aa8f0bd51e9d0184d98042dfe9285218fec098493f47d9a8aa0f1a3f27"
dependencies = [
"base16ct",
- "der",
- "hybrid-array",
- "pkcs8",
+ "der 0.8.0-rc.1",
+ "hybrid-array 0.3.0",
+ "pkcs8 0.11.0-rc.2",
"serdect",
"subtle",
"zeroize",
@@ -1075,6 +1157,15 @@ dependencies = [
"serde_derive",
]
+[[package]]
+name = "serde_bare"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51c55386eed0f1ae957b091dc2ca8122f287b60c79c774cbe3d5f2b69fded660"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde_derive"
version = "1.0.219"
@@ -1143,7 +1234,7 @@ name = "sm2"
version = "0.14.0-pre"
dependencies = [
"elliptic-curve",
- "hex-literal",
+ "hex-literal 1.0.0",
"primeorder",
"proptest",
"rand_core 0.9.3",
@@ -1162,6 +1253,16 @@ dependencies = [
"digest",
]
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der 0.7.9",
+]
+
[[package]]
name = "spki"
version = "0.8.0-rc.1"
@@ -1169,7 +1270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ac66481418fd7afdc584adcf3be9aa572cf6c2858814494dc2a01755f050bc"
dependencies = [
"base64ct",
- "der",
+ "der 0.8.0-rc.1",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 19f942acc..8ea985ae5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,7 @@ members = [
"p256",
"p384",
"p521",
+ "ed448",
"primefield",
"primeorder",
"sm2"
diff --git a/ed448/Cargo.toml b/ed448/Cargo.toml
new file mode 100644
index 000000000..7338df5c0
--- /dev/null
+++ b/ed448/Cargo.toml
@@ -0,0 +1,45 @@
+[package]
+authors = ["RustCrypto Developers"]
+categories = ["cryptography"]
+description = """A pure-Rust implementation of Ed448 and Curve448 and Decaf.
+This crate also includes signing and verifying of Ed448 signatures.
+"""
+documentation = "https://docs.rs/ed448-goldilocks"
+exclude = [".gitignore", ".github/*"]
+edition = "2021"
+homepage = "https://docs.rs/ed448-goldilocks/"
+keywords = ["cryptography", "decaf", "ed448", "ed448-goldilocks"]
+license = "BSD-3-Clause"
+name = "ed448"
+readme = "README.md"
+repository = "https://github.com/RustCrypto/elliptic-curves/tree/master/ed448"
+rust-version = "1.85"
+version = "0.17.0-pre.0"
+
+[dependencies]
+crypto-bigint = { version = "0.6.0-rc.3", features = ["hybrid-array"], default-features = false }
+crypto_signature = { version = "2.3.0-pre.6", default-features = false, features = ["digest", "rand_core"], optional = true, package = "signature" }
+elliptic-curve = { version = "0.14.0-rc.0", features = ["arithmetic", "bits", "hash2curve", "jwk", "pkcs8", "pem", "sec1"] }
+pkcs8 = { version = "0.10", features = ["alloc"], optional = true }
+rand_core = { version = "0.9", default-features = false }
+serdect = { version = "0.3.0", optional = true }
+sha3 = { version = "=0.11.0-pre.5", default-features = false }
+subtle = { version = "2.6", default-features = false }
+zeroize = { version = "1.8", default-features = false, optional = true }
+
+[features]
+default = ["std", "signing", "pkcs8"]
+alloc = ["serdect/alloc", "zeroize/alloc"]
+pkcs8 = ["dep:pkcs8"]
+signing = ["dep:crypto_signature", "zeroize"]
+serde = ["dep:serdect"]
+std = ["serdect/default", "zeroize/default", "crypto_signature/std"]
+zeroize = ["dep:zeroize"]
+
+[dev-dependencies]
+hex-literal = "0.4"
+hex = "0.4"
+rand_core = { version = "0.9", features = ["os_rng"] }
+rand_chacha = "0.9"
+serde_bare = "0.5"
+serde_json = "1.0"
diff --git a/ed448/README.md b/ed448/README.md
new file mode 100644
index 000000000..1d808a8f3
--- /dev/null
+++ b/ed448/README.md
@@ -0,0 +1,91 @@
+
+
+
+
+ed448-goldilocks-plus
+
+[![Crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![BSD-3 Licensed][license-image]
+
+THIS CODE HAS NOT BEEN AUDITED OR REVIEWED. USE AT YOUR OWN RISK.
+
+## Field Choice
+
+The field size is a Solinas trinomial prime 2^448 - 2^224 -1. This prime is called the Goldilocks prime.
+
+## Curves
+
+This repository implements three curves explicitly and another curve implicitly.
+
+The three explicitly implemented curves are:
+
+- Ed448-Goldilocks
+
+- Curve448
+
+- Twisted-Goldilocks
+
+
+## Ed448-Goldilocks Curve
+
+- The goldilocks curve is an Edwards curve with affine equation x^2 + y^2 = 1 - 39081x^2y^2 .
+- This curve was defined by Mike Hamburg in https://eprint.iacr.org/2015/625.pdf .
+- The cofactor of this curve over the goldilocks prime is 4.
+
+## Twisted-Goldilocks Curve
+
+- The twisted goldilocks curve is a Twisted Edwards curve with affine equation y^2 - x^2 = 1 - 39082x^2y^2 .
+- This curve is also defined in https://eprint.iacr.org/2015/625.pdf .
+- The cofactor of this curve over the goldilocks prime is 4.
+
+### Isogeny
+
+- This curve is 2-isogenous to Ed448-Goldilocks. Details of the isogeny can be found here: https://www.shiftleft.org/papers/isogeny/isogeny.pdf
+
+## Curve448
+
+This curve is 2-isogenous to Ed448-Goldilocks. Details of Curve448 can be found here: https://tools.ietf.org/html/rfc7748
+
+The main usage of this curve is for X448.
+
+N.B. In that document there is an Edwards curve that is birationally equivalent to Curve448, with a large `d` value. This curve is not implemented and to my knowledge, has no utility.
+
+## Strategy
+
+The main strategy for group arithmetic on Ed448-Goldilocks is to perform the 2-isogeny to map the point to the Twisted-Goldilocks curve, then use the faster Twisted Edwards formulas to perform scalar multiplication. Computing the 2-isogeny then the dual isogeny will pick up a factor of 4 once we map the point back to the Ed448-Goldilocks curve, so the scalar must be adjusted by a factor of 4. Adjusting the scalar is dependent on the point and the scalar. More details can be found in the 2-isogenous paper.
+
+# Decaf
+
+The Decaf strategy [link paper] is used to build a group of prime order from the Twisted Goldilocks curve. The Twisted Goldilocks curve is used as it has faster formulas. We can also use Curve448 or Ed448-Goldilocks. Decaf takes advantage of an isogeny with a Jacobi Quartic curve which is not explicitly defined. Details of this can be found here: https://www.shiftleft.org/papers/decaf/decaf.pdf However, to my knowledge there is no documentation for the Decaf protocol implemented in this repository, which is a tweaked version of the original decaf protocol linked in the paper.
+
+## Completed Point vs Extensible Point
+
+Deviating from Curve25519-Dalek, this library will implement Extensible points instead of Completed Points. Due to the following observation:
+
+- There is a cost of 3/4 Field multiplications to switch from the CompletedPoint. So if we were to perform repeated doubling, this would add an extra cost for each doubling in projective form. More details on the ExtensiblePoint can be found here [3.2]: https://www.shiftleft.org/papers/fff/fff.pdf
+
+## Credits
+
+The library design was taken from Dalek's design of Curve25519. The code for Montgomery curve arithmetic was also taken from Dalek's library.
+
+The golang implementation of Ed448 and libdecaf were used as references.
+
+Special thanks to Mike Hamburg for answering all the questions asked regarding Decaf and goldilocks.
+
+This library adds [hash_to_curve](https://datatracker.ietf.org/doc/rfc9380/) and serialization of structs.
+
+## Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the BSD-3-Clause
+license, shall be dual licensed as above, without any additional terms or
+conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/ed448-goldilocks-plus.svg
+[crate-link]: https://crates.io/crates/ed448-goldilocks-plus
+[docs-image]: https://docs.rs/ed448-goldilocks-plus/badge.svg
+[docs-link]: https://docs.rs/ed448-goldilocks-plus/
+[license-image]: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
\ No newline at end of file
diff --git a/ed448/resources/bear.png b/ed448/resources/bear.png
new file mode 100644
index 000000000..ad0e7b7d9
Binary files /dev/null and b/ed448/resources/bear.png differ
diff --git a/ed448/rustfmt.toml b/ed448/rustfmt.toml
new file mode 100644
index 000000000..25b9f9dcc
--- /dev/null
+++ b/ed448/rustfmt.toml
@@ -0,0 +1,4 @@
+use_field_init_shorthand = true
+reorder_imports = true
+reorder_modules = true
+reorder_impl_items = true
diff --git a/ed448/src/constants.rs b/ed448/src/constants.rs
new file mode 100644
index 000000000..076e9a05c
--- /dev/null
+++ b/ed448/src/constants.rs
@@ -0,0 +1,15 @@
+use crate::*;
+use crate::{decaf::DecafPoint, Scalar};
+
+pub const DECAF_BASEPOINT: DecafPoint = DecafPoint(curve::twedwards::extended::ExtendedPoint {
+ X: TWISTED_EDWARDS_BASE_POINT.X,
+ Y: TWISTED_EDWARDS_BASE_POINT.Y,
+ Z: TWISTED_EDWARDS_BASE_POINT.Z,
+ T: TWISTED_EDWARDS_BASE_POINT.T,
+});
+
+/// `BASEPOINT_ORDER` is the order of the Ed448 basepoint, i.e.,
+/// $$
+/// \ell = 2^\{446\} + 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d.
+/// $$
+pub const BASEPOINT_ORDER: Scalar = Scalar(ORDER);
diff --git a/ed448/src/curve.rs b/ed448/src/curve.rs
new file mode 100644
index 000000000..d883b7ebc
--- /dev/null
+++ b/ed448/src/curve.rs
@@ -0,0 +1,7 @@
+pub mod edwards;
+pub mod montgomery;
+pub(crate) mod scalar_mul;
+pub(crate) mod twedwards;
+
+pub use edwards::{AffinePoint, CompressedEdwardsY, EdwardsPoint};
+pub use montgomery::{MontgomeryPoint, ProjectiveMontgomeryPoint};
diff --git a/ed448/src/curve/edwards.rs b/ed448/src/curve/edwards.rs
new file mode 100644
index 000000000..8a95a782c
--- /dev/null
+++ b/ed448/src/curve/edwards.rs
@@ -0,0 +1,15 @@
+/// This module contains the code for the Goldilocks curve.
+/// The goldilocks curve is the (untwisted) Edwards curve with affine equation x^2 + y^2 = 1 - 39081x^2y^2
+/// Scalar Multiplication for this curve is pre-dominantly delegated to the Twisted Edwards variation using a (doubling) isogeny
+/// Passing the point back to the Goldilocks curve using the dual-isogeny clears the cofactor.
+/// The small remainder of the Scalar Multiplication is computed on the untwisted curve.
+/// See for details
+///
+/// This isogeny strategy does not clear the cofactor on the Goldilocks curve unless the Scalar is a multiple of 4.
+/// or the point is known to be in the q-torsion subgroup.
+/// Hence, one will need to multiply by the cofactor to ensure it is cleared when using the Goldilocks curve.
+/// If this is a problem, one can use a different isogeny strategy (Decaf/Ristretto)
+pub(crate) mod affine;
+pub(crate) mod extended;
+pub use affine::AffinePoint;
+pub use extended::{CompressedEdwardsY, EdwardsPoint};
diff --git a/ed448/src/curve/edwards/affine.rs b/ed448/src/curve/edwards/affine.rs
new file mode 100644
index 000000000..be89f1600
--- /dev/null
+++ b/ed448/src/curve/edwards/affine.rs
@@ -0,0 +1,117 @@
+use crate::curve::edwards::EdwardsPoint;
+use crate::field::FieldElement;
+use crate::*;
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
+
+#[cfg(feature = "zeroize")]
+use zeroize::DefaultIsZeroes;
+
+/// Affine point on untwisted curve
+#[derive(Copy, Clone, Debug)]
+pub struct AffinePoint {
+ pub(crate) x: FieldElement,
+ pub(crate) y: FieldElement,
+}
+
+impl Default for AffinePoint {
+ fn default() -> Self {
+ Self::IDENTITY
+ }
+}
+
+impl ConstantTimeEq for AffinePoint {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y)
+ }
+}
+
+impl ConditionallySelectable for AffinePoint {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ Self {
+ x: FieldElement::conditional_select(&a.x, &b.x, choice),
+ y: FieldElement::conditional_select(&a.y, &b.y, choice),
+ }
+ }
+}
+
+impl PartialEq for AffinePoint {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for AffinePoint {}
+
+impl elliptic_curve::point::AffineCoordinates for AffinePoint {
+ type FieldRepr = Ed448FieldBytes;
+
+ fn x(&self) -> Self::FieldRepr {
+ Ed448FieldBytes::from(self.x.to_bytes_extended())
+ }
+
+ fn y_is_odd(&self) -> Choice {
+ self.y.is_negative()
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl DefaultIsZeroes for AffinePoint {}
+
+impl AffinePoint {
+ /// The identity point
+ pub const IDENTITY: AffinePoint = AffinePoint {
+ x: FieldElement::ZERO,
+ y: FieldElement::ONE,
+ };
+
+ pub(crate) fn isogeny(&self) -> Self {
+ let x = self.x;
+ let y = self.y;
+ let mut t0 = x.square(); // x^2
+ let t1 = t0 + FieldElement::ONE; // x^2+1
+ t0 -= FieldElement::ONE; // x^2-1
+ let mut t2 = y.square(); // y^2
+ t2 = t2.double(); // 2y^2
+ let t3 = x.double(); // 2x
+
+ let mut t4 = t0 * y; // y(x^2-1)
+ t4 = t4.double(); // 2y(x^2-1)
+ let xNum = t4.double(); // xNum = 4y(x^2-1)
+
+ let mut t5 = t0.square(); // x^4-2x^2+1
+ t4 = t5 + t2; // x^4-2x^2+1+2y^2
+ let xDen = t4 + t2; // xDen = x^4-2x^2+1+4y^2
+
+ t5 *= x; // x^5-2x^3+x
+ t4 = t2 * t3; // 4xy^2
+ let yNum = t4 - t5; // yNum = -(x^5-2x^3+x-4xy^2)
+
+ t4 = t1 * t2; // 2x^2y^2+2y^2
+ let yDen = t5 - t4; // yDen = x^5-2x^3+x-2x^2y^2-2y^2
+
+ Self {
+ x: xNum * xDen.invert(),
+ y: yNum * yDen.invert(),
+ }
+ }
+
+ /// Convert to edwards extended point
+ pub fn to_edwards(&self) -> EdwardsPoint {
+ EdwardsPoint {
+ X: self.x,
+ Y: self.y,
+ Z: FieldElement::ONE,
+ T: self.x * self.y,
+ }
+ }
+
+ /// The X coordinate
+ pub fn x(&self) -> [u8; 56] {
+ self.x.to_bytes()
+ }
+
+ /// The Y coordinate
+ pub fn y(&self) -> [u8; 56] {
+ self.y.to_bytes()
+ }
+}
diff --git a/ed448/src/curve/edwards/extended.rs b/ed448/src/curve/edwards/extended.rs
new file mode 100644
index 000000000..4eab9bfeb
--- /dev/null
+++ b/ed448/src/curve/edwards/extended.rs
@@ -0,0 +1,1261 @@
+use core::borrow::Borrow;
+use core::fmt::{Display, Formatter, LowerHex, Result as FmtResult, UpperHex};
+use core::iter::Sum;
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use crate::constants::BASEPOINT_ORDER;
+use crate::curve::edwards::affine::AffinePoint;
+use crate::curve::montgomery::MontgomeryPoint; // XXX: need to fix this path
+use crate::curve::scalar_mul::variable_base;
+use crate::curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint;
+use crate::field::{FieldElement, Scalar};
+use crate::*;
+use elliptic_curve::{
+ array::{
+ typenum::{U57, U84},
+ Array,
+ },
+ group::{cofactor::CofactorGroup, prime::PrimeGroup, Curve, Group, GroupEncoding},
+ hash2curve::{ExpandMsg, ExpandMsgXof, Expander, FromOkm},
+ ops::{LinearCombination, MulByGenerator},
+};
+use rand_core::RngCore;
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+/// The default hash to curve domain separation tag
+pub const DEFAULT_HASH_TO_CURVE_SUITE: &[u8] = b"edwards448_XOF:SHAKE256_ELL2_RO_";
+/// The default encode to curve domain separation tag
+pub const DEFAULT_ENCODE_TO_CURVE_SUITE: &[u8] = b"edwards448_XOF:SHAKE256_ELL2_NU_";
+
+/// The compressed internal representation of a point on the Twisted Edwards Curve
+pub type PointBytes = [u8; 57];
+
+/// Represents a point on the Compressed Twisted Edwards Curve
+/// in little endian format where the most significant bit is the sign bit
+/// and the remaining 448 bits represent the y-coordinate
+#[derive(Copy, Clone, Debug)]
+pub struct CompressedEdwardsY(pub PointBytes);
+
+#[cfg(feature = "zeroize")]
+impl zeroize::Zeroize for CompressedEdwardsY {
+ fn zeroize(&mut self) {
+ self.0.zeroize()
+ }
+}
+
+impl Display for CompressedEdwardsY {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ for b in &self.0[..] {
+ write!(f, "{:02x}", b)?;
+ }
+ Ok(())
+ }
+}
+
+impl LowerHex for CompressedEdwardsY {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ for b in &self.0[..] {
+ write!(f, "{:02x}", b)?;
+ }
+ Ok(())
+ }
+}
+
+impl UpperHex for CompressedEdwardsY {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ for b in &self.0[..] {
+ write!(f, "{:02X}", b)?;
+ }
+ Ok(())
+ }
+}
+
+impl Default for CompressedEdwardsY {
+ fn default() -> Self {
+ Self([0u8; 57])
+ }
+}
+
+impl ConditionallySelectable for CompressedEdwardsY {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ let mut bytes = [0u8; 57];
+ for (i, byte) in bytes.iter_mut().enumerate() {
+ *byte = u8::conditional_select(&a.0[i], &b.0[i], choice);
+ }
+ Self(bytes)
+ }
+}
+
+impl ConstantTimeEq for CompressedEdwardsY {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.0.ct_eq(&other.0)
+ }
+}
+
+impl PartialEq for CompressedEdwardsY {
+ fn eq(&self, other: &CompressedEdwardsY) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for CompressedEdwardsY {}
+
+impl AsRef<[u8]> for CompressedEdwardsY {
+ fn as_ref(&self) -> &[u8] {
+ &self.0[..]
+ }
+}
+
+impl AsRef for CompressedEdwardsY {
+ fn as_ref(&self) -> &PointBytes {
+ &self.0
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From for Vec {
+ fn from(value: CompressedEdwardsY) -> Self {
+ Self::from(&value)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From<&CompressedEdwardsY> for Vec {
+ fn from(value: &CompressedEdwardsY) -> Self {
+ value.0.to_vec()
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for CompressedEdwardsY {
+ type Error = &'static str;
+
+ fn try_from(value: Vec) -> Result {
+ Self::try_from(&value)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom<&Vec> for CompressedEdwardsY {
+ type Error = &'static str;
+
+ fn try_from(value: &Vec) -> Result {
+ Self::try_from(value.as_slice())
+ }
+}
+
+impl TryFrom<&[u8]> for CompressedEdwardsY {
+ type Error = &'static str;
+
+ fn try_from(value: &[u8]) -> Result {
+ let bytes = ::try_from(value).map_err(|_| "Invalid length")?;
+ Self::try_from(&bytes)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for CompressedEdwardsY {
+ type Error = &'static str;
+
+ fn try_from(value: Box<[u8]>) -> Result {
+ Self::try_from(value.as_ref())
+ }
+}
+
+impl From for PointBytes {
+ fn from(value: CompressedEdwardsY) -> Self {
+ value.0
+ }
+}
+
+impl From<&CompressedEdwardsY> for PointBytes {
+ fn from(value: &CompressedEdwardsY) -> Self {
+ Self::from(*value)
+ }
+}
+
+impl TryFrom for CompressedEdwardsY {
+ type Error = &'static str;
+
+ fn try_from(value: PointBytes) -> Result {
+ let pt = CompressedEdwardsY(value);
+ let _ = Option::::from(pt.decompress()).ok_or("Invalid point")?;
+ Ok(pt)
+ }
+}
+
+impl TryFrom<&PointBytes> for CompressedEdwardsY {
+ type Error = &'static str;
+
+ fn try_from(value: &PointBytes) -> Result {
+ Self::try_from(*value)
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serdect::serde::Serialize for CompressedEdwardsY {
+ fn serialize(&self, s: S) -> Result {
+ serdect::array::serialize_hex_lower_or_bin(&self.0, s)
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serdect::serde::Deserialize<'de> for CompressedEdwardsY {
+ fn deserialize(d: D) -> Result
+ where
+ D: serdect::serde::Deserializer<'de>,
+ {
+ let mut arr = [0u8; 57];
+ serdect::array::deserialize_hex_or_bin(&mut arr, d)?;
+ Ok(CompressedEdwardsY(arr))
+ }
+}
+
+impl CompressedEdwardsY {
+ /// The compressed generator point
+ pub const GENERATOR: Self = Self([
+ 20, 250, 48, 242, 91, 121, 8, 152, 173, 200, 215, 78, 44, 19, 189, 253, 196, 57, 124, 230,
+ 28, 255, 211, 58, 215, 194, 160, 5, 30, 156, 120, 135, 64, 152, 163, 108, 115, 115, 234,
+ 75, 98, 199, 201, 86, 55, 32, 118, 136, 36, 188, 182, 110, 113, 70, 63, 105, 0,
+ ]);
+ /// The compressed identity point
+ pub const IDENTITY: Self = Self([0u8; 57]);
+
+ /// Attempt to decompress to an `EdwardsPoint`.
+ ///
+ /// Returns `None` if the input is not the \\(y\\)-coordinate of a
+ /// curve point.
+ pub fn decompress_unchecked(&self) -> CtOption {
+ // Safe to unwrap here as the underlying data structure is a slice
+ let (sign, b) = self.0.split_last().expect("slice is non-empty");
+
+ let mut y_bytes: [u8; 56] = [0; 56];
+ y_bytes.copy_from_slice(b);
+
+ // Recover x using y
+ let y = FieldElement::from_bytes(&y_bytes);
+ let yy = y.square();
+ let dyy = FieldElement::EDWARDS_D * yy;
+ let numerator = FieldElement::ONE - yy;
+ let denominator = FieldElement::ONE - dyy;
+
+ let (mut x, is_res) = FieldElement::sqrt_ratio(&numerator, &denominator);
+
+ // Compute correct sign of x
+ let compressed_sign_bit = Choice::from(sign >> 7);
+ let is_negative = x.is_negative();
+ x.conditional_negate(compressed_sign_bit ^ is_negative);
+
+ CtOption::new(AffinePoint { x, y }.to_edwards(), is_res)
+ }
+
+ /// Attempt to decompress to an `EdwardsPoint`.
+ ///
+ /// Returns `None`:
+ /// - if the input is not the \\(y\\)-coordinate of a curve point.
+ /// - if the input point is not on the curve.
+ /// - if the input point has nonzero torsion component.
+ pub fn decompress(&self) -> CtOption {
+ self.decompress_unchecked()
+ .and_then(|pt| CtOption::new(pt, pt.is_on_curve() & pt.is_torsion_free()))
+ }
+
+ /// View this `CompressedEdwardsY` as an array of bytes.
+ pub const fn as_bytes(&self) -> &PointBytes {
+ &self.0
+ }
+
+ /// Copy this `CompressedEdwardsY` to an array of bytes.
+ pub const fn to_bytes(&self) -> PointBytes {
+ self.0
+ }
+}
+
+/// Represent points on the (untwisted) edwards curve using Extended Homogenous Projective Co-ordinates
+/// (x, y) -> (X/Z, Y/Z, Z, T)
+/// a = 1, d = -39081
+/// XXX: Make this more descriptive
+/// Should this be renamed to EdwardsPoint so that we are consistent with Dalek crypto? Necessary as ExtendedPoint is not regular lingo?
+#[derive(Copy, Clone, Debug)]
+pub struct EdwardsPoint {
+ pub(crate) X: FieldElement,
+ pub(crate) Y: FieldElement,
+ pub(crate) Z: FieldElement,
+ pub(crate) T: FieldElement,
+}
+
+impl Default for EdwardsPoint {
+ fn default() -> Self {
+ Self::IDENTITY
+ }
+}
+
+impl Display for EdwardsPoint {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ write!(
+ f,
+ "{{ X: {}, Y: {}, Z: {}, T: {} }}",
+ self.X, self.Y, self.Z, self.T
+ )
+ }
+}
+
+impl LowerHex for EdwardsPoint {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ write!(
+ f,
+ "{{ X: {:x}, Y: {:x}, Z: {:x}, T: {:x} }}",
+ self.X, self.Y, self.Z, self.T
+ )
+ }
+}
+
+impl UpperHex for EdwardsPoint {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ write!(
+ f,
+ "{{ X: {:X}, Y: {:X}, Z: {:X}, T: {:X} }}",
+ self.X, self.Y, self.Z, self.T
+ )
+ }
+}
+
+impl ConditionallySelectable for EdwardsPoint {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ EdwardsPoint {
+ X: FieldElement::conditional_select(&a.X, &b.X, choice),
+ Y: FieldElement::conditional_select(&a.Y, &b.Y, choice),
+ Z: FieldElement::conditional_select(&a.Z, &b.Z, choice),
+ T: FieldElement::conditional_select(&a.T, &b.T, choice),
+ }
+ }
+}
+
+impl ConstantTimeEq for EdwardsPoint {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ let XZ = self.X * other.Z;
+ let ZX = self.Z * other.X;
+
+ let YZ = self.Y * other.Z;
+ let ZY = self.Z * other.Y;
+
+ (XZ.ct_eq(&ZX)) & (YZ.ct_eq(&ZY))
+ }
+}
+
+impl PartialEq for EdwardsPoint {
+ fn eq(&self, other: &EdwardsPoint) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for EdwardsPoint {}
+
+impl Group for EdwardsPoint {
+ type Scalar = Scalar;
+
+ fn random(mut rng: impl RngCore) -> Self {
+ let mut bytes = [0u8; 32];
+ rng.fill_bytes(&mut bytes);
+ Self::hash_with_defaults(&bytes)
+ }
+
+ fn identity() -> Self {
+ Self::IDENTITY
+ }
+
+ fn generator() -> Self {
+ Self::GENERATOR
+ }
+
+ fn is_identity(&self) -> Choice {
+ self.ct_eq(&Self::IDENTITY)
+ }
+
+ fn double(&self) -> Self {
+ self.double()
+ }
+}
+
+impl GroupEncoding for EdwardsPoint {
+ type Repr = Array;
+
+ fn from_bytes(bytes: &Self::Repr) -> CtOption {
+ let mut value = [0u8; 57];
+ value.copy_from_slice(bytes);
+ CompressedEdwardsY(value).decompress()
+ }
+
+ fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption {
+ let mut value = [0u8; 57];
+ value.copy_from_slice(bytes);
+ CompressedEdwardsY(value).decompress()
+ }
+
+ fn to_bytes(&self) -> Self::Repr {
+ Self::Repr::from(self.compress().0)
+ }
+}
+
+impl CofactorGroup for EdwardsPoint {
+ type Subgroup = EdwardsPoint;
+
+ fn clear_cofactor(&self) -> Self::Subgroup {
+ self.double().double()
+ }
+
+ fn into_subgroup(self) -> CtOption {
+ CtOption::new(self.clear_cofactor(), self.is_torsion_free())
+ }
+
+ fn is_torsion_free(&self) -> Choice {
+ self.is_torsion_free()
+ }
+}
+
+impl PrimeGroup for EdwardsPoint {}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From for Vec {
+ fn from(value: EdwardsPoint) -> Self {
+ Self::from(&value)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From<&EdwardsPoint> for Vec {
+ fn from(value: &EdwardsPoint) -> Self {
+ value.compress().0.to_vec()
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for EdwardsPoint {
+ type Error = &'static str;
+
+ fn try_from(value: Vec) -> Result {
+ Self::try_from(&value)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom<&Vec> for EdwardsPoint {
+ type Error = &'static str;
+
+ fn try_from(value: &Vec) -> Result {
+ Self::try_from(value.as_slice())
+ }
+}
+
+impl TryFrom<&[u8]> for EdwardsPoint {
+ type Error = &'static str;
+
+ fn try_from(value: &[u8]) -> Result {
+ let bytes =
+ ::try_from(value).map_err(|_| "Invalid length, expected 57 bytes")?;
+ Self::try_from(bytes)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for EdwardsPoint {
+ type Error = &'static str;
+
+ fn try_from(value: Box<[u8]>) -> Result {
+ Self::try_from(value.as_ref())
+ }
+}
+
+impl TryFrom for EdwardsPoint {
+ type Error = &'static str;
+
+ fn try_from(value: PointBytes) -> Result {
+ Option::::from(CompressedEdwardsY(value).decompress()).ok_or("Invalid point")
+ }
+}
+
+impl TryFrom<&PointBytes> for EdwardsPoint {
+ type Error = &'static str;
+
+ fn try_from(value: &PointBytes) -> Result {
+ Self::try_from(*value)
+ }
+}
+
+impl From for PointBytes {
+ fn from(value: EdwardsPoint) -> Self {
+ value.compress().into()
+ }
+}
+
+impl From<&EdwardsPoint> for PointBytes {
+ fn from(value: &EdwardsPoint) -> Self {
+ Self::from(*value)
+ }
+}
+
+impl From for AffinePoint {
+ fn from(value: EdwardsPoint) -> Self {
+ value.to_affine()
+ }
+}
+
+impl From<&AffinePoint> for EdwardsPoint {
+ fn from(value: &AffinePoint) -> Self {
+ value.to_edwards()
+ }
+}
+
+impl From for EdwardsPoint {
+ fn from(value: AffinePoint) -> Self {
+ value.to_edwards()
+ }
+}
+
+impl From<&EdwardsPoint> for AffinePoint {
+ fn from(value: &EdwardsPoint) -> Self {
+ value.to_affine()
+ }
+}
+
+impl LinearCombination<[(EdwardsPoint, Scalar); N]> for EdwardsPoint {}
+
+impl LinearCombination<[(EdwardsPoint, Scalar)]> for EdwardsPoint {}
+
+impl MulByGenerator for EdwardsPoint {}
+
+impl Curve for EdwardsPoint {
+ type AffineRepr = AffinePoint;
+
+ fn to_affine(&self) -> AffinePoint {
+ self.to_affine()
+ }
+}
+
+impl EdwardsPoint {
+ /// Generator for the prime subgroup
+ pub const GENERATOR: Self = GOLDILOCKS_BASE_POINT;
+ /// Identity point
+ pub const IDENTITY: Self = Self {
+ X: FieldElement::ZERO,
+ Y: FieldElement::ONE,
+ Z: FieldElement::ONE,
+ T: FieldElement::ZERO,
+ };
+
+ /// Convert this point to [`MontgomeryPoint`]
+ pub fn to_montgomery(&self) -> MontgomeryPoint {
+ // u = y^2 * [(1-dy^2)/(1-y^2)]
+
+ let affine = self.to_affine();
+
+ let yy = affine.y.square();
+ let dyy = FieldElement::EDWARDS_D * yy;
+
+ let u = yy * (FieldElement::ONE - dyy) * (FieldElement::ONE - yy).invert();
+
+ MontgomeryPoint(u.to_bytes())
+ }
+
+ /// Generic scalar multiplication to compute s*P
+ pub fn scalar_mul(&self, scalar: &Scalar) -> Self {
+ // Compute floor(s/4)
+ let mut scalar_div_four = *scalar;
+ scalar_div_four.div_by_four();
+
+ // Use isogeny and dual isogeny to compute phi^-1((s/4) * phi(P))
+ let partial_result = variable_base(&self.to_twisted(), &scalar_div_four).to_untwisted();
+ // Add partial result to (scalar mod 4) * P
+ partial_result.add(&self.scalar_mod_four(scalar))
+ }
+
+ /// Returns (scalar mod 4) * P in constant time
+ pub(crate) fn scalar_mod_four(&self, scalar: &Scalar) -> Self {
+ // Compute compute (scalar mod 4)
+ let s_mod_four = scalar[0] & 3;
+
+ // Compute all possible values of (scalar mod 4) * P
+ let zero_p = EdwardsPoint::IDENTITY;
+ let one_p = self;
+ let two_p = one_p.double();
+ let three_p = two_p.add(self);
+
+ // Under the reasonable assumption that `==` is constant time
+ // Then the whole function is constant time.
+ // This should be cheaper than calling double_and_add or a scalar mul operation
+ // as the number of possibilities are so small.
+ // XXX: This claim has not been tested (although it sounds intuitive to me)
+ let mut result = EdwardsPoint::IDENTITY;
+ result.conditional_assign(&zero_p, Choice::from((s_mod_four == 0) as u8));
+ result.conditional_assign(one_p, Choice::from((s_mod_four == 1) as u8));
+ result.conditional_assign(&two_p, Choice::from((s_mod_four == 2) as u8));
+ result.conditional_assign(&three_p, Choice::from((s_mod_four == 3) as u8));
+
+ result
+ }
+
+ /// Standard compression; store Y and sign of X
+ // XXX: This needs more docs and is `compress` the conventional function name? I think to_bytes/encode is?
+ pub fn compress(&self) -> CompressedEdwardsY {
+ let affine = self.to_affine();
+
+ let affine_x = affine.x;
+ let affine_y = affine.y;
+
+ let mut compressed_bytes = [0u8; 57];
+
+ let sign = affine_x.is_negative().unwrap_u8();
+
+ let y_bytes = affine_y.to_bytes();
+ compressed_bytes[..y_bytes.len()].copy_from_slice(&y_bytes[..]);
+ *compressed_bytes.last_mut().expect("at least one byte") = sign << 7;
+ CompressedEdwardsY(compressed_bytes)
+ }
+
+ /// Add two points
+ //https://iacr.org/archive/asiacrypt2008/53500329/53500329.pdf (3.1)
+ // These formulas are unified, so for now we can use it for doubling. Will refactor later for speed
+ pub fn add(&self, other: &EdwardsPoint) -> Self {
+ let aXX = self.X * other.X; // aX1X2
+ let dTT = FieldElement::EDWARDS_D * self.T * other.T; // dT1T2
+ let ZZ = self.Z * other.Z; // Z1Z2
+ let YY = self.Y * other.Y;
+
+ let X = {
+ let x_1 = (self.X * other.Y) + (self.Y * other.X);
+ let x_2 = ZZ - dTT;
+ x_1 * x_2
+ };
+ let Y = {
+ let y_1 = YY - aXX;
+ let y_2 = ZZ + dTT;
+ y_1 * y_2
+ };
+
+ let T = {
+ let t_1 = YY - aXX;
+ let t_2 = (self.X * other.Y) + (self.Y * other.X);
+ t_1 * t_2
+ };
+
+ let Z = { (ZZ - dTT) * (ZZ + dTT) };
+
+ EdwardsPoint { X, Y, Z, T }
+ }
+
+ /// Double this point
+ // XXX: See comment on addition, the formula is unified, so this will do for now
+ //https://iacr.org/archive/asiacrypt2008/53500329/53500329.pdf (3.1)
+ pub fn double(&self) -> Self {
+ self.add(self)
+ }
+
+ /// Check if this point is on the curve
+ pub fn is_on_curve(&self) -> Choice {
+ let XY = self.X * self.Y;
+ let ZT = self.Z * self.T;
+
+ // Y^2 + X^2 == Z^2 - T^2 * D
+
+ let YY = self.Y.square();
+ let XX = self.X.square();
+ let ZZ = self.Z.square();
+ let TT = self.T.square();
+ let lhs = YY + XX;
+ let rhs = ZZ + TT * FieldElement::EDWARDS_D;
+
+ XY.ct_eq(&ZT) & lhs.ct_eq(&rhs)
+ }
+
+ /// Convert this point to an [`AffinePoint`].
+ pub fn to_affine(&self) -> AffinePoint {
+ let INV_Z = self.Z.invert();
+
+ let x = self.X * INV_Z;
+ let y = self.Y * INV_Z;
+
+ AffinePoint { x, y }
+ }
+
+ /// Edwards_Isogeny is derived from the doubling formula
+ /// XXX: There is a duplicate method in the twisted edwards module to compute the dual isogeny
+ /// XXX: Not much point trying to make it generic I think. So what we can do is optimise each respective isogeny method for a=1 or a = -1 (currently, I just made it really slow and simple)
+ fn edwards_isogeny(&self, a: FieldElement) -> TwistedExtendedPoint {
+ // Convert to affine now, then derive extended version later
+ let affine = self.to_affine();
+ let x = affine.x;
+ let y = affine.y;
+
+ // Compute x
+ let xy = x * y;
+ let x_numerator = xy + xy;
+ let x_denom = y.square() - (a * x.square());
+ let new_x = x_numerator * x_denom.invert();
+
+ // Compute y
+ let y_numerator = y.square() + (a * x.square());
+ let y_denom = (FieldElement::ONE + FieldElement::ONE) - y.square() - (a * x.square());
+ let new_y = y_numerator * y_denom.invert();
+
+ TwistedExtendedPoint {
+ X: new_x,
+ Y: new_y,
+ Z: FieldElement::ONE,
+ T: new_x * new_y,
+ }
+ }
+
+ pub(crate) fn to_twisted(self) -> TwistedExtendedPoint {
+ self.edwards_isogeny(FieldElement::ONE)
+ }
+
+ /// Compute the negation of this point's `x`-coordinate.
+ pub fn negate(&self) -> Self {
+ EdwardsPoint {
+ X: -self.X,
+ Y: self.Y,
+ Z: self.Z,
+ T: -self.T,
+ }
+ }
+
+ /// Compute the negation of this point's `y`-coordinate.
+ pub fn torque(&self) -> Self {
+ EdwardsPoint {
+ X: -self.X,
+ Y: -self.Y,
+ Z: self.Z,
+ T: self.T,
+ }
+ }
+
+ /// Determine if this point is “torsion-free”, i.e., is contained in
+ /// the prime-order subgroup.
+ ///
+ /// # Return
+ ///
+ /// * `true` if `self` has zero torsion component and is in the
+ /// prime-order subgroup;
+ /// * `false` if `self` has a nonzero torsion component and is not
+ /// in the prime-order subgroup.
+ pub fn is_torsion_free(&self) -> Choice {
+ (self * BASEPOINT_ORDER).ct_eq(&Self::IDENTITY)
+ }
+
+ /// Hash a message to a point on the curve
+ ///
+ /// Hash using the default domain separation tag and hash function
+ pub fn hash_with_defaults(msg: &[u8]) -> Self {
+ Self::hash::>(msg, DEFAULT_HASH_TO_CURVE_SUITE)
+ }
+
+ /// Hash a message to a point on the curve
+ ///
+ /// Implements hash to curve according
+ /// see
+ pub fn hash(msg: &[u8], dst: &[u8]) -> Self
+ where
+ X: for<'a> ExpandMsg<'a>,
+ {
+ let mut random_bytes = Array::::default();
+ let dst = [dst];
+ let mut expander =
+ X::expand_message(&[msg], &dst, random_bytes.len() * 2).expect("bad dst");
+ expander.fill_bytes(&mut random_bytes);
+ let u0 = FieldElement::from_okm(&random_bytes);
+ expander.fill_bytes(&mut random_bytes);
+ let u1 = FieldElement::from_okm(&random_bytes);
+ let mut q0 = u0.map_to_curve_elligator2();
+ let mut q1 = u1.map_to_curve_elligator2();
+ q0 = q0.isogeny();
+ q1 = q1.isogeny();
+
+ (q0.to_edwards() + q1.to_edwards()).double().double()
+ }
+
+ /// Encode a message to a point on the curve
+ ///
+ /// Encode using the default domain separation tag and hash function
+ pub fn encode_with_defaults(msg: &[u8]) -> Self {
+ Self::encode::>(msg, DEFAULT_ENCODE_TO_CURVE_SUITE)
+ }
+
+ /// Encode a message to a point on the curve
+ ///
+ /// Implements encode to curve according
+ /// see
+ pub fn encode(msg: &[u8], dst: &[u8]) -> Self
+ where
+ X: for<'a> ExpandMsg<'a>,
+ {
+ let mut random_bytes = Array::::default();
+ let dst = [dst];
+ let mut expander = X::expand_message(&[msg], &dst, random_bytes.len()).expect("bad dst");
+ expander.fill_bytes(&mut random_bytes);
+ let u0 = FieldElement::from_okm(&random_bytes);
+ let mut q0 = u0.map_to_curve_elligator2();
+ q0 = q0.isogeny();
+
+ q0.to_edwards().double().double()
+ }
+}
+
+// ------------------------------------------------------------------------
+// Addition and Subtraction
+// ------------------------------------------------------------------------
+
+impl Add<&EdwardsPoint> for &EdwardsPoint {
+ type Output = EdwardsPoint;
+
+ fn add(self, other: &EdwardsPoint) -> EdwardsPoint {
+ self.add(other)
+ }
+}
+
+define_add_variants!(
+ LHS = EdwardsPoint,
+ RHS = EdwardsPoint,
+ Output = EdwardsPoint
+);
+
+define_add_variants!(LHS = EdwardsPoint, RHS = AffinePoint, Output = EdwardsPoint);
+
+define_add_variants!(LHS = AffinePoint, RHS = EdwardsPoint, Output = EdwardsPoint);
+
+impl Add<&AffinePoint> for &EdwardsPoint {
+ type Output = EdwardsPoint;
+
+ fn add(self, other: &AffinePoint) -> EdwardsPoint {
+ *self + *other
+ }
+}
+
+impl Add<&EdwardsPoint> for &AffinePoint {
+ type Output = EdwardsPoint;
+
+ fn add(self, other: &EdwardsPoint) -> EdwardsPoint {
+ *other + *self
+ }
+}
+
+impl<'b> AddAssign<&'b EdwardsPoint> for EdwardsPoint {
+ fn add_assign(&mut self, _rhs: &'b EdwardsPoint) {
+ *self = *self + _rhs;
+ }
+}
+
+define_add_assign_variants!(LHS = EdwardsPoint, RHS = EdwardsPoint);
+
+impl AddAssign<&AffinePoint> for EdwardsPoint {
+ fn add_assign(&mut self, rhs: &AffinePoint) {
+ *self += rhs.to_edwards();
+ }
+}
+
+define_add_assign_variants!(LHS = EdwardsPoint, RHS = AffinePoint);
+
+impl AddAssign<&EdwardsPoint> for AffinePoint {
+ fn add_assign(&mut self, rhs: &EdwardsPoint) {
+ *self = (self.to_edwards() + rhs).to_affine();
+ }
+}
+
+define_add_assign_variants!(LHS = AffinePoint, RHS = EdwardsPoint);
+
+impl Sub<&EdwardsPoint> for &EdwardsPoint {
+ type Output = EdwardsPoint;
+
+ fn sub(self, other: &EdwardsPoint) -> EdwardsPoint {
+ self.add(&other.negate())
+ }
+}
+
+define_sub_variants!(
+ LHS = EdwardsPoint,
+ RHS = EdwardsPoint,
+ Output = EdwardsPoint
+);
+
+impl Sub<&AffinePoint> for &EdwardsPoint {
+ type Output = EdwardsPoint;
+
+ fn sub(self, other: &AffinePoint) -> EdwardsPoint {
+ *self - other.to_edwards()
+ }
+}
+
+define_sub_variants!(LHS = EdwardsPoint, RHS = AffinePoint, Output = EdwardsPoint);
+
+impl Sub<&EdwardsPoint> for &AffinePoint {
+ type Output = EdwardsPoint;
+
+ fn sub(self, other: &EdwardsPoint) -> EdwardsPoint {
+ *self - other
+ }
+}
+
+define_sub_variants!(LHS = AffinePoint, RHS = EdwardsPoint, Output = EdwardsPoint);
+
+impl<'b> SubAssign<&'b EdwardsPoint> for EdwardsPoint {
+ fn sub_assign(&mut self, _rhs: &'b EdwardsPoint) {
+ *self = *self - _rhs;
+ }
+}
+
+define_sub_assign_variants!(LHS = EdwardsPoint, RHS = EdwardsPoint);
+
+impl SubAssign<&AffinePoint> for EdwardsPoint {
+ fn sub_assign(&mut self, rhs: &AffinePoint) {
+ *self -= rhs.to_edwards();
+ }
+}
+
+define_sub_assign_variants!(LHS = EdwardsPoint, RHS = AffinePoint);
+
+impl SubAssign<&EdwardsPoint> for AffinePoint {
+ fn sub_assign(&mut self, rhs: &EdwardsPoint) {
+ *self = (self.to_edwards() - rhs).to_affine();
+ }
+}
+
+define_sub_assign_variants!(LHS = AffinePoint, RHS = EdwardsPoint);
+
+impl Sum for EdwardsPoint
+where
+ T: Borrow,
+{
+ fn sum(iter: I) -> Self
+ where
+ I: Iterator- ,
+ {
+ iter.fold(Self::IDENTITY, |acc, item| acc + item.borrow())
+ }
+}
+
+// ------------------------------------------------------------------------
+// Negation
+// ------------------------------------------------------------------------
+
+impl Neg for &EdwardsPoint {
+ type Output = EdwardsPoint;
+
+ fn neg(self) -> EdwardsPoint {
+ self.negate()
+ }
+}
+
+impl Neg for EdwardsPoint {
+ type Output = EdwardsPoint;
+
+ fn neg(self) -> EdwardsPoint {
+ -&self
+ }
+}
+
+// ------------------------------------------------------------------------
+// Scalar multiplication
+// ------------------------------------------------------------------------
+
+impl<'b> MulAssign<&'b Scalar> for EdwardsPoint {
+ fn mul_assign(&mut self, scalar: &'b Scalar) {
+ let result = *self * scalar;
+ *self = result;
+ }
+}
+
+define_mul_assign_variants!(LHS = EdwardsPoint, RHS = Scalar);
+
+define_mul_variants!(LHS = EdwardsPoint, RHS = Scalar, Output = EdwardsPoint);
+define_mul_variants!(LHS = Scalar, RHS = EdwardsPoint, Output = EdwardsPoint);
+
+impl Mul<&Scalar> for &EdwardsPoint {
+ type Output = EdwardsPoint;
+
+ /// Scalar multiplication: compute `scalar * self`.
+ fn mul(self, scalar: &Scalar) -> EdwardsPoint {
+ self.scalar_mul(scalar)
+ }
+}
+
+impl Mul<&EdwardsPoint> for &Scalar {
+ type Output = EdwardsPoint;
+
+ /// Scalar multiplication: compute `scalar * self`.
+ fn mul(self, point: &EdwardsPoint) -> EdwardsPoint {
+ point * self
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serdect::serde::Serialize for EdwardsPoint {
+ fn serialize(&self, s: S) -> Result {
+ self.compress().serialize(s)
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serdect::serde::Deserialize<'de> for EdwardsPoint {
+ fn deserialize(d: D) -> Result
+ where
+ D: serdect::serde::Deserializer<'de>,
+ {
+ let compressed = CompressedEdwardsY::deserialize(d)?;
+ Option::::from(compressed.decompress())
+ .ok_or_else(|| serdect::serde::de::Error::custom("invalid point"))
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for EdwardsPoint {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use elliptic_curve::Field;
+ use hex_literal::hex;
+ use rand_core::TryRngCore;
+
+ fn hex_to_field(hex: &'static str) -> FieldElement {
+ assert_eq!(hex.len(), 56 * 2);
+ let mut bytes = hex_literal::decode(&[hex.as_bytes()]);
+ bytes.reverse();
+ FieldElement::from_bytes(&bytes)
+ }
+
+ #[test]
+ fn test_isogeny() {
+ let x = hex_to_field("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa955555555555555555555555555555555555555555555555555555555");
+ let y = hex_to_field("ae05e9634ad7048db359d6205086c2b0036ed7a035884dd7b7e36d728ad8c4b80d6565833a2a3098bbbcb2bed1cda06bdaeafbcdea9386ed");
+ let a = AffinePoint { x, y }.to_edwards();
+ let twist_a = a.to_twisted().to_untwisted();
+ assert!(twist_a == a.double().double())
+ }
+
+ // XXX: Move this to constants folder to test all global constants
+ #[test]
+ fn derive_base_points() {
+ use crate::{GOLDILOCKS_BASE_POINT, TWISTED_EDWARDS_BASE_POINT};
+
+ // This was the original basepoint which had order 2q;
+ let old_x = hex_to_field("4F1970C66BED0DED221D15A622BF36DA9E146570470F1767EA6DE324A3D3A46412AE1AF72AB66511433B80E18B00938E2626A82BC70CC05E");
+ let old_y = hex_to_field("693F46716EB6BC248876203756C9C7624BEA73736CA3984087789C1E05A0C2D73AD3FF1CE67C39C4FDBD132C4ED7C8AD9808795BF230FA14");
+ let old_bp = AffinePoint { x: old_x, y: old_y }.to_edwards();
+
+ // This is the new basepoint, that is in the ed448 paper
+ let new_x = hex_to_field("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa955555555555555555555555555555555555555555555555555555555");
+ let new_y = hex_to_field("ae05e9634ad7048db359d6205086c2b0036ed7a035884dd7b7e36d728ad8c4b80d6565833a2a3098bbbcb2bed1cda06bdaeafbcdea9386ed");
+ let new_bp = AffinePoint { x: new_x, y: new_y }.to_edwards();
+
+ // Doubling the old basepoint, should give us the new basepoint
+ assert_eq!(old_bp.double(), new_bp);
+
+ // XXX: Unfortunately, the test vectors in libdecaf currently use the old basepoint.
+ // We need to update this. But for now, I use the old basepoint so that I can check against libdecaf
+
+ assert_eq!(GOLDILOCKS_BASE_POINT, old_bp);
+
+ // The Twisted basepoint can be derived by using the isogeny
+ assert_eq!(old_bp.to_twisted(), TWISTED_EDWARDS_BASE_POINT)
+ }
+
+ #[test]
+ fn test_is_on_curve() {
+ let x = hex_to_field("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa955555555555555555555555555555555555555555555555555555555");
+ let y = hex_to_field("ae05e9634ad7048db359d6205086c2b0036ed7a035884dd7b7e36d728ad8c4b80d6565833a2a3098bbbcb2bed1cda06bdaeafbcdea9386ed");
+ let gen = AffinePoint { x, y }.to_edwards();
+ assert_eq!(gen.is_on_curve().unwrap_u8(), 1u8);
+ }
+ #[test]
+ fn test_compress_decompress() {
+ let x = hex_to_field("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa955555555555555555555555555555555555555555555555555555555");
+ let y = hex_to_field("ae05e9634ad7048db359d6205086c2b0036ed7a035884dd7b7e36d728ad8c4b80d6565833a2a3098bbbcb2bed1cda06bdaeafbcdea9386ed");
+ let gen = AffinePoint { x, y }.to_edwards();
+
+ let decompressed_point = gen.compress().decompress();
+ assert!(>::into(decompressed_point.is_some()));
+
+ assert!(gen == decompressed_point.unwrap());
+ }
+ #[test]
+ fn test_decompress_compress() {
+ let bytes = hex!("649c6a53b109897d962d033f23d01fd4e1053dddf3746d2ddce9bd66aea38ccfc3df061df03ca399eb806312ab3037c0c31523142956ada780");
+ let compressed = CompressedEdwardsY(bytes);
+ let decompressed = compressed.decompress().unwrap();
+
+ let recompressed = decompressed.compress();
+
+ assert_eq!(bytes, recompressed.0);
+ }
+ #[test]
+ fn test_just_decompress() {
+ let bytes = hex!("649c6a53b109897d962d033f23d01fd4e1053dddf3746d2ddce9bd66aea38ccfc3df061df03ca399eb806312ab3037c0c31523142956ada780");
+ let compressed = CompressedEdwardsY(bytes);
+ let decompressed = compressed.decompress().unwrap();
+
+ assert_eq!(decompressed.X, hex_to_field("39c41cea305d737df00de8223a0d5f4d48c8e098e16e9b4b2f38ac353262e119cb5ff2afd6d02464702d9d01c9921243fc572f9c718e2527"));
+ assert_eq!(decompressed.Y, hex_to_field("a7ad5629142315c3c03730ab126380eb99a33cf01d06dfc3cf8ca3ae66bde9dc2d6d74f3dd3d05e1d41fd0233f032d967d8909b1536a9c64"));
+
+ let bytes = hex!("010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
+ let compressed = CompressedEdwardsY(bytes);
+ let decompressed = compressed.decompress().unwrap();
+
+ assert_eq!(decompressed.X, hex_to_field("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"));
+ assert_eq!(decompressed.Y, hex_to_field("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"));
+ }
+ #[test]
+ fn test_is_torsion_free() {
+ assert_eq!(EdwardsPoint::GENERATOR.is_torsion_free().unwrap_u8(), 1u8);
+ assert_eq!(EdwardsPoint::IDENTITY.is_torsion_free().unwrap_u8(), 1u8);
+
+ let bytes = hex!("13b6714c7a5f53101bbec88f2f17cd30f42e37fae363a5474efb4197ed6005df5861ae178a0c2c16ad378b7befed0d0904b7ced35e9f674180");
+ let compressed = CompressedEdwardsY(bytes);
+ let decompressed = compressed.decompress();
+ assert_eq!(decompressed.is_none().unwrap_u8(), 1u8);
+ }
+
+ #[test]
+ fn hash_with_test_vectors() {
+ const DST: &[u8] = b"QUUX-V01-CS02-with-edwards448_XOF:SHAKE256_ELL2_RO_";
+ const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
+ (b"", hex!("73036d4a88949c032f01507005c133884e2f0d81f9a950826245dda9e844fc78186c39daaa7147ead3e462cff60e9c6340b58134480b4d17"), hex!("94c1d61b43728e5d784ef4fcb1f38e1075f3aef5e99866911de5a234f1aafdc26b554344742e6ba0420b71b298671bbeb2b7736618634610")),
+ (b"abc", hex!("4e0158acacffa545adb818a6ed8e0b870e6abc24dfc1dc45cf9a052e98469275d9ff0c168d6a5ac7ec05b742412ee090581f12aa398f9f8c"), hex!("894d3fa437b2d2e28cdc3bfaade035430f350ec5239b6b406b5501da6f6d6210ff26719cad83b63e97ab26a12df6dec851d6bf38e294af9a")),
+ (b"abcdef0123456789", hex!("2c25b4503fadc94b27391933b557abdecc601c13ed51c5de68389484f93dbd6c22e5f962d9babf7a39f39f994312f8ca23344847e1fbf176"), hex!("d5e6f5350f430e53a110f5ac7fcc82a96cb865aeca982029522d32601e41c042a9dfbdfbefa2b0bdcdc3bc58cca8a7cd546803083d3a8548")),
+ (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("a1861a9464ae31249a0e60bf38791f3663049a3f5378998499a83292e159a2fecff838eb9bc6939e5c6ae76eb074ad4aae39b55b72ca0b9a"), hex!("580a2798c5b904f8adfec5bd29fb49b4633cd9f8c2935eb4a0f12e5dfa0285680880296bb729c6405337525fb5ed3dff930c137314f60401")),
+ (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("987c5ac19dd4b47835466a50b2d9feba7c8491b8885a04edf577e15a9f2c98b203ec2cd3e5390b3d20bba0fa6fc3eecefb5029a317234401"), hex!("5e273fcfff6b007bb6771e90509275a71ff1480c459ded26fc7b10664db0a68aaa98bc7ecb07e49cf05b80ae5ac653fbdd14276bbd35ccbc")),
+ ];
+
+ for (msg, x, y) in MSGS {
+ let p = EdwardsPoint::hash::>(msg, DST);
+ assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
+ let p = p.to_affine();
+ let mut xx = [0u8; 56];
+ xx.copy_from_slice(&x[..]);
+ xx.reverse();
+ let mut yy = [0u8; 56];
+ yy.copy_from_slice(&y[..]);
+ yy.reverse();
+ assert_eq!(p.x.to_bytes(), xx);
+ assert_eq!(p.y.to_bytes(), yy);
+ }
+ }
+
+ #[test]
+ fn hash_fuzzing() {
+ for _ in 0..25 {
+ let mut msg = [0u8; 64];
+ rand_core::OsRng.try_fill_bytes(&mut msg).unwrap();
+ let p = EdwardsPoint::hash_with_defaults(&msg);
+ assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
+ assert_eq!(p.is_torsion_free().unwrap_u8(), 1u8);
+ }
+ }
+
+ #[test]
+ fn encode() {
+ const DST: &[u8] = b"QUUX-V01-CS02-with-edwards448_XOF:SHAKE256_ELL2_NU_";
+ const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
+ (b"", hex!("eb5a1fc376fd73230af2de0f3374087cc7f279f0460114cf0a6c12d6d044c16de34ec2350c34b26bf110377655ab77936869d085406af71e"), hex!("df5dcea6d42e8f494b279a500d09e895d26ac703d75ca6d118e8ca58bf6f608a2a383f292fce1563ff995dce75aede1fdc8e7c0c737ae9ad")),
+ (b"abc", hex!("4623a64bceaba3202df76cd8b6e3daf70164f3fcbda6d6e340f7fab5cdf89140d955f722524f5fe4d968fef6ba2853ff4ea086c2f67d8110"), hex!("abaac321a169761a8802ab5b5d10061fec1a83c670ac6bc95954700317ee5f82870120e0e2c5a21b12a0c7ad17ebd343363604c4bcecafd1")),
+ (b"abcdef0123456789", hex!("e9eb562e76db093baa43a31b7edd04ec4aadcef3389a7b9c58a19cf87f8ae3d154e134b6b3ed45847a741e33df51903da681629a4b8bcc2e"), hex!("0cf6606927ad7eb15dbc193993bc7e4dda744b311a8ec4274c8f738f74f605934582474c79260f60280fe35bd37d4347e59184cbfa12cbc4")),
+ (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("122a3234d34b26c69749f23356452bf9501efa2d94859d5ef741fef024156d9d191a03a2ad24c38186f93e02d05572575968b083d8a39738"), hex!("ddf55e74eb4414c2c1fa4aa6bc37c4ab470a3fed6bb5af1e43570309b162fb61879bb15f9ea49c712efd42d0a71666430f9f0d4a20505050")),
+ (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("221704949b1ce1ab8dd174dc9b8c56fcffa27179569ce9219c0c2fe183d3d23343a4c42a0e2e9d6b9d0feb1df3883ec489b6671d1fa64089"), hex!("ebdecfdc87142d1a919034bf22ecfad934c9a85effff14b594ae2c00943ca62a39d6ee3be9df0bb504ce8a9e1669bc6959c42ad6a1d3b686")),
+ ];
+
+ for (msg, x, y) in MSGS {
+ let p = EdwardsPoint::encode::>(msg, DST);
+ assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
+ let p = p.to_affine();
+ let mut xx = [0u8; 56];
+ xx.copy_from_slice(&x[..]);
+ xx.reverse();
+ let mut yy = [0u8; 56];
+ yy.copy_from_slice(&y[..]);
+ yy.reverse();
+ assert_eq!(p.x.to_bytes(), xx);
+ assert_eq!(p.y.to_bytes(), yy);
+ }
+ }
+
+ // TODO: uncomment once elliptic-curve-tools is updated to match elliptic-curve 0.14
+ // #[test]
+ // fn test_sum_of_products() {
+ // use elliptic_curve_tools::SumOfProducts;
+ // let values = [
+ // (Scalar::from(8u8), EdwardsPoint::GENERATOR),
+ // (Scalar::from(9u8), EdwardsPoint::GENERATOR),
+ // (Scalar::from(10u8), EdwardsPoint::GENERATOR),
+ // (Scalar::from(11u8), EdwardsPoint::GENERATOR),
+ // (Scalar::from(12u8), EdwardsPoint::GENERATOR),
+ // ];
+ //
+ // let expected = EdwardsPoint::GENERATOR * Scalar::from(50u8);
+ // let result = EdwardsPoint::sum_of_products(&values);
+ // assert_eq!(result, expected);
+ // }
+ //
+ // #[test]
+ // fn test_sum_of_products2() {
+ // use elliptic_curve_tools::SumOfProducts;
+ // use rand_core::SeedableRng;
+ //
+ // const TESTS: usize = 5;
+ // const CHUNKS: usize = 10;
+ // let mut rng = rand_chacha::ChaCha8Rng::from_seed([3u8; 32]);
+ //
+ // for _ in 0..TESTS {
+ // let scalars = (0..CHUNKS)
+ // .map(|_| Scalar::random(&mut rng))
+ // .collect::>();
+ // let points = (0..CHUNKS)
+ // .map(|_| EdwardsPoint::random(&mut rng))
+ // .collect::>();
+ //
+ // let input = scalars
+ // .iter()
+ // .zip(points.iter())
+ // .map(|(&s, &p)| (s, p))
+ // .collect::>();
+ // let rhs = EdwardsPoint::sum_of_products(&input);
+ //
+ // let expected = points
+ // .iter()
+ // .zip(scalars.iter())
+ // .fold(EdwardsPoint::IDENTITY, |acc, (&p, &s)| acc + (p * s));
+ //
+ // assert_eq!(rhs, expected);
+ // }
+ // }
+
+ #[test]
+ fn test_pow_add_mul() {
+ use rand_core::SeedableRng;
+
+ let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
+ let x = Scalar::random(&mut rng);
+ let b = Scalar::random(&mut rng);
+
+ let g1 = EdwardsPoint::GENERATOR;
+ let g2 = EdwardsPoint::hash_with_defaults(b"test_pow_add_mul");
+
+ let expected_commitment = g1 * x + g2 * b;
+
+ let shift = Scalar::from(256u16);
+ let x_bytes = x.to_bytes_rfc_8032();
+ let mut sum = Scalar::ZERO;
+ let mut components = [EdwardsPoint::IDENTITY; 57];
+ for i in 1..57 {
+ let r = Scalar::random(&mut rng);
+ sum += r * shift.pow([i as u64]);
+ components[i] = g1 * Scalar::from(x_bytes[i]) + g2 * r;
+ }
+ components[0] = g1 * Scalar::from(x_bytes[0]) + g2 * (b - sum);
+
+ let mut computed_commitment = EdwardsPoint::IDENTITY;
+ for i in (0..57).rev() {
+ computed_commitment *= shift;
+ computed_commitment += components[i];
+ }
+
+ assert_eq!(computed_commitment, expected_commitment);
+ }
+}
diff --git a/ed448/src/curve/montgomery.rs b/ed448/src/curve/montgomery.rs
new file mode 100644
index 000000000..93cb669e3
--- /dev/null
+++ b/ed448/src/curve/montgomery.rs
@@ -0,0 +1,236 @@
+// The original file was a part of curve25519-dalek.
+// Copyright (c) 2016-2019 Isis Lovecruft, Henry de Valence
+// Copyright (c) 2020 Kevaundray Wedderburn
+// See LICENSE for licensing information.
+//
+// Authors:
+// - Isis Agora Lovecruft
+// - Henry de Valence
+// - Kevaundray Wedderburn
+
+#![allow(non_snake_case)]
+
+// use crate::constants::A_PLUS_TWO_OVER_FOUR;
+use crate::curve::edwards::extended::EdwardsPoint;
+use crate::field::{FieldElement, Scalar};
+use core::fmt;
+use core::ops::Mul;
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
+
+// Low order points on Curve448 and it's twist
+const LOW_A: MontgomeryPoint = MontgomeryPoint([
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+]);
+const LOW_B: MontgomeryPoint = MontgomeryPoint([
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+]);
+const LOW_C: MontgomeryPoint = MontgomeryPoint([
+ 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+]);
+
+/// A point in Montgomery form
+#[derive(Copy, Clone)]
+pub struct MontgomeryPoint(pub [u8; 56]);
+
+impl Default for MontgomeryPoint {
+ fn default() -> MontgomeryPoint {
+ Self([0u8; 56])
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for MontgomeryPoint {}
+
+impl fmt::Debug for MontgomeryPoint {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ self.0[..].fmt(formatter)
+ }
+}
+
+impl ConstantTimeEq for MontgomeryPoint {
+ fn ct_eq(&self, other: &MontgomeryPoint) -> Choice {
+ self.0.ct_eq(&other.0)
+ }
+}
+
+impl PartialEq for MontgomeryPoint {
+ fn eq(&self, other: &MontgomeryPoint) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+impl Eq for MontgomeryPoint {}
+
+/// A Projective point in Montgomery form
+#[derive(Copy, Clone, Debug)]
+pub struct ProjectiveMontgomeryPoint {
+ U: FieldElement,
+ W: FieldElement,
+}
+
+impl Mul<&Scalar> for &MontgomeryPoint {
+ type Output = MontgomeryPoint;
+
+ #[allow(clippy::suspicious_arithmetic_impl)]
+ fn mul(self, scalar: &Scalar) -> MontgomeryPoint {
+ // Algorithm 8 of Costello-Smith 2017
+ let affine_u = FieldElement::from_bytes(&self.0);
+ let mut x0 = ProjectiveMontgomeryPoint::identity();
+ let mut x1 = ProjectiveMontgomeryPoint {
+ U: affine_u,
+ W: FieldElement::ONE,
+ };
+
+ let bits = scalar.bits();
+ let mut swap = 0;
+ for s in (0..448).rev() {
+ let bit = bits[s] as u8;
+ let choice: u8 = swap ^ bit;
+
+ ProjectiveMontgomeryPoint::conditional_swap(&mut x0, &mut x1, Choice::from(choice));
+ differential_add_and_double(&mut x0, &mut x1, &affine_u);
+
+ swap = bit;
+ }
+
+ x0.to_affine()
+ }
+}
+
+impl Mul<&MontgomeryPoint> for &Scalar {
+ type Output = MontgomeryPoint;
+
+ fn mul(self, point: &MontgomeryPoint) -> MontgomeryPoint {
+ point * self
+ }
+}
+
+impl MontgomeryPoint {
+ /// Returns the generator specified in RFC7748
+ pub const GENERATOR: Self = Self([
+ 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ]);
+
+ /// Convert this point to an [`EdwardsPoint`]
+ pub fn to_edwards(&self, _sign: u8) -> Option {
+ // We use the 4-isogeny to map to the Ed448.
+ // This is different to Curve25519, where we use a birational map.
+ todo!()
+ }
+
+ /// Returns true if the point is one of the low order points
+ pub fn is_low_order(&self) -> bool {
+ (*self == LOW_A) || (*self == LOW_B) || (*self == LOW_C)
+ }
+
+ /// View the point as a byte slice
+ pub fn as_bytes(&self) -> &[u8; 56] {
+ &self.0
+ }
+
+ /// Convert the point to a ProjectiveMontgomeryPoint
+ pub fn to_projective(&self) -> ProjectiveMontgomeryPoint {
+ ProjectiveMontgomeryPoint {
+ U: FieldElement::from_bytes(&self.0),
+ W: FieldElement::ONE,
+ }
+ }
+}
+
+impl ConditionallySelectable for ProjectiveMontgomeryPoint {
+ fn conditional_select(
+ a: &ProjectiveMontgomeryPoint,
+ b: &ProjectiveMontgomeryPoint,
+ choice: Choice,
+ ) -> ProjectiveMontgomeryPoint {
+ ProjectiveMontgomeryPoint {
+ U: FieldElement::conditional_select(&a.U, &b.U, choice),
+ W: FieldElement::conditional_select(&a.W, &b.W, choice),
+ }
+ }
+}
+
+fn differential_add_and_double(
+ P: &mut ProjectiveMontgomeryPoint,
+ Q: &mut ProjectiveMontgomeryPoint,
+ affine_PmQ: &FieldElement,
+) {
+ let t0 = P.U + P.W;
+ let t1 = P.U - P.W;
+ let t2 = Q.U + Q.W;
+ let t3 = Q.U - Q.W;
+
+ let t4 = t0.square(); // (U_P + W_P)^2 = U_P^2 + 2 U_P W_P + W_P^2
+ let t5 = t1.square(); // (U_P - W_P)^2 = U_P^2 - 2 U_P W_P + W_P^2
+
+ let t6 = t4 - t5; // 4 U_P W_P
+
+ let t7 = t0 * t3; // (U_P + W_P) (U_Q - W_Q) = U_P U_Q + W_P U_Q - U_P W_Q - W_P W_Q
+ let t8 = t1 * t2; // (U_P - W_P) (U_Q + W_Q) = U_P U_Q - W_P U_Q + U_P W_Q - W_P W_Q
+
+ let t9 = t7 + t8; // 2 (U_P U_Q - W_P W_Q)
+ let t10 = t7 - t8; // 2 (W_P U_Q - U_P W_Q)
+
+ let t11 = t9.square(); // 4 (U_P U_Q - W_P W_Q)^2
+ let t12 = t10.square(); // 4 (W_P U_Q - U_P W_Q)^2
+ let t13 = FieldElement::A_PLUS_TWO_OVER_FOUR * t6; // (A + 2) U_P U_Q
+
+ let t14 = t4 * t5; // ((U_P + W_P)(U_P - W_P))^2 = (U_P^2 - W_P^2)^2
+ let t15 = t13 + t5; // (U_P - W_P)^2 + (A + 2) U_P W_P
+
+ let t16 = t6 * t15; // 4 (U_P W_P) ((U_P - W_P)^2 + (A + 2) U_P W_P)
+ let t17 = *affine_PmQ * t12; // U_D * 4 (W_P U_Q - U_P W_Q)^2
+ let t18 = t11; // W_D * 4 (U_P U_Q - W_P W_Q)^2
+
+ P.U = t14; // U_{P'} = (U_P + W_P)^2 (U_P - W_P)^2
+ P.W = t16; // W_{P'} = (4 U_P W_P) ((U_P - W_P)^2 + ((A + 2)/4) 4 U_P W_P)
+ Q.U = t18; // U_{Q'} = W_D * 4 (U_P U_Q - W_P W_Q)^2
+ Q.W = t17; // W_{Q'} = U_D * 4 (W_P U_Q - U_P W_Q)^2
+}
+
+impl ProjectiveMontgomeryPoint {
+ /// The identity element of the group: the point at infinity.
+ pub fn identity() -> ProjectiveMontgomeryPoint {
+ ProjectiveMontgomeryPoint {
+ U: FieldElement::ONE,
+ W: FieldElement::ZERO,
+ }
+ }
+
+ /// Convert the point to affine form
+ pub fn to_affine(&self) -> MontgomeryPoint {
+ let x = self.U * self.W.invert();
+ MontgomeryPoint(x.to_bytes())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ #[test]
+ fn test_montgomery_edwards() {
+ let scalar = Scalar::from(200u32);
+ use crate::GOLDILOCKS_BASE_POINT as bp;
+
+ // Montgomery scalar mul
+ let montgomery_bp = bp.to_montgomery();
+ let montgomery_res = &montgomery_bp * &scalar;
+
+ // Goldilocks scalar mul
+ let goldilocks_point = bp.scalar_mul(&scalar);
+ assert_eq!(goldilocks_point.to_montgomery(), montgomery_res);
+ }
+}
diff --git a/ed448/src/curve/scalar_mul.rs b/ed448/src/curve/scalar_mul.rs
new file mode 100644
index 000000000..b6ff71a1e
--- /dev/null
+++ b/ed448/src/curve/scalar_mul.rs
@@ -0,0 +1,7 @@
+pub(crate) mod double_and_add;
+// pub(crate) mod double_base;
+pub(crate) mod variable_base;
+pub(crate) mod window;
+
+pub(crate) use double_and_add::double_and_add;
+pub(crate) use variable_base::variable_base;
diff --git a/ed448/src/curve/scalar_mul/double_and_add.rs b/ed448/src/curve/scalar_mul/double_and_add.rs
new file mode 100644
index 000000000..5ffd09f81
--- /dev/null
+++ b/ed448/src/curve/scalar_mul/double_and_add.rs
@@ -0,0 +1,20 @@
+use crate::curve::twedwards::extended::ExtendedPoint;
+use crate::field::Scalar;
+use subtle::{Choice, ConditionallySelectable};
+
+/// Traditional double and add algorithm
+pub(crate) fn double_and_add(point: &ExtendedPoint, s: &Scalar) -> ExtendedPoint {
+ let mut result = ExtendedPoint::IDENTITY;
+
+ // NB, we reverse here, so we are going from MSB to LSB
+ // XXX: Would be great if subtle had a From for Choice. But maybe that is not it's purpose?
+ for bit in s.bits().into_iter().rev() {
+ result = result.double();
+
+ let mut p = ExtendedPoint::IDENTITY;
+ p.conditional_assign(point, Choice::from(bit as u8));
+ result = result.add(&p);
+ }
+
+ result
+}
diff --git a/ed448/src/curve/scalar_mul/double_base.rs b/ed448/src/curve/scalar_mul/double_base.rs
new file mode 100644
index 000000000..82290dfc4
--- /dev/null
+++ b/ed448/src/curve/scalar_mul/double_base.rs
@@ -0,0 +1,13 @@
+#![allow(non_snake_case)]
+
+use super::double_and_add;
+use crate::curve::twedwards::extended::ExtendedPoint;
+use crate::field::Scalar;
+/// XXX: Really in-efficient way to do double base scala mul
+/// Replace it with endomorphism from pornin or use naf form
+/// Computes aA + bB where B is the TwistedEdwards basepoint
+pub(crate) fn double_base_scalar_mul(a: &Scalar, A: &ExtendedPoint, b: &Scalar) -> ExtendedPoint {
+ let part_a = double_and_add(A, a);
+ let part_b = double_and_add(&ExtendedPoint::GENERATOR, b);
+ part_a.add(&part_b)
+}
diff --git a/ed448/src/curve/scalar_mul/variable_base.rs b/ed448/src/curve/scalar_mul/variable_base.rs
new file mode 100644
index 000000000..2e9536376
--- /dev/null
+++ b/ed448/src/curve/scalar_mul/variable_base.rs
@@ -0,0 +1,77 @@
+#![allow(non_snake_case)]
+
+use super::window::wnaf::LookupTable;
+use crate::curve::twedwards::{extended::ExtendedPoint, extensible::ExtensiblePoint};
+use crate::field::Scalar;
+use subtle::{Choice, ConditionallyNegatable};
+
+pub fn variable_base(point: &ExtendedPoint, s: &Scalar) -> ExtendedPoint {
+ let mut result = ExtensiblePoint::IDENTITY;
+
+ // Recode Scalar
+ let scalar = s.to_radix_16();
+
+ let lookup = LookupTable::from(point);
+
+ for i in (0..113).rev() {
+ result = result.double();
+ result = result.double();
+ result = result.double();
+ result = result.double();
+
+ // The mask is the top bit, will be 1 for negative numbers, 0 for positive numbers
+ let mask = scalar[i] >> 7;
+ let sign = mask & 0x1;
+ // Use the mask to get the absolute value of scalar
+ let abs_value = ((scalar[i] + mask) ^ mask) as u32;
+
+ let mut neg_P = lookup.select(abs_value);
+ neg_P.conditional_negate(Choice::from((sign) as u8));
+
+ result = result.add_projective_niels(&neg_P);
+ }
+
+ result.to_extended()
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::curve::scalar_mul::double_and_add;
+ use crate::TWISTED_EDWARDS_BASE_POINT;
+ use elliptic_curve::bigint::U448;
+
+ #[test]
+ fn test_scalar_mul() {
+ // XXX: In the future use known multiples from Sage in bytes form?
+ let twisted_point = TWISTED_EDWARDS_BASE_POINT;
+ let scalar = Scalar(U448::from_be_hex(
+ "05ca185aee2e1b73def437f63c003777083f83043fe5bf1aab454c66b64629d1de8026c1307f665ead0b70151533427ce128ae786ee372b7"
+ ));
+
+ let got = variable_base(&twisted_point, &scalar);
+
+ let got2 = double_and_add(&twisted_point, &scalar);
+ assert_eq!(got, got2);
+
+ // Lets see if this is conserved over the isogenies
+ let edwards_point = twisted_point.to_untwisted();
+ let got_untwisted_point = edwards_point.scalar_mul(&scalar);
+ let expected_untwisted_point = got.to_untwisted();
+ assert_eq!(got_untwisted_point, expected_untwisted_point);
+ }
+
+ #[test]
+ fn test_simple_scalar_mul_identities() {
+ let x = TWISTED_EDWARDS_BASE_POINT;
+
+ // Test that 1 * P = P
+ let exp = variable_base(&x, &Scalar::from(1u8));
+ assert!(x == exp);
+ // Test that 2 * (P + P) = 4 * P
+ let x_ext = x.to_extensible();
+ let expected_two_x = x_ext.add_extensible(&x_ext).double();
+ let got = variable_base(&x, &Scalar::from(4u8));
+ assert!(expected_two_x.to_extended() == got);
+ }
+}
diff --git a/ed448/src/curve/scalar_mul/window.rs b/ed448/src/curve/scalar_mul/window.rs
new file mode 100644
index 000000000..80d597f5a
--- /dev/null
+++ b/ed448/src/curve/scalar_mul/window.rs
@@ -0,0 +1 @@
+pub mod wnaf;
diff --git a/ed448/src/curve/scalar_mul/window/wnaf.rs b/ed448/src/curve/scalar_mul/window/wnaf.rs
new file mode 100644
index 000000000..1c804fded
--- /dev/null
+++ b/ed448/src/curve/scalar_mul/window/wnaf.rs
@@ -0,0 +1,52 @@
+use crate::curve::twedwards::extended::ExtendedPoint;
+use crate::curve::twedwards::projective::ProjectiveNielsPoint;
+use subtle::{ConditionallySelectable, ConstantTimeEq};
+
+pub struct LookupTable([ProjectiveNielsPoint; 8]);
+
+/// Precomputes odd multiples of the point passed in
+impl From<&ExtendedPoint> for LookupTable {
+ fn from(point: &ExtendedPoint) -> LookupTable {
+ let P = point.to_extensible();
+
+ let mut table = [P.to_projective_niels(); 8];
+
+ for i in 1..8 {
+ table[i] = P.add_projective_niels(&table[i - 1]).to_projective_niels();
+ }
+
+ LookupTable(table)
+ }
+}
+
+impl LookupTable {
+ /// Selects a projective niels point from a lookup table in constant time
+ pub fn select(&self, index: u32) -> ProjectiveNielsPoint {
+ let mut result = ProjectiveNielsPoint::identity();
+
+ for i in 1..9 {
+ let swap = index.ct_eq(&(i as u32));
+ result.conditional_assign(&self.0[i - 1], swap);
+ }
+ result
+ }
+}
+
+// XXX: Add back tests to ensure that select works correctly
+
+#[test]
+fn test_lookup() {
+ let p = ExtendedPoint::GENERATOR;
+ let points = LookupTable::from(&p);
+
+ let mut expected_point = ExtendedPoint::IDENTITY;
+ for i in 0..8 {
+ let selected_point = points.select(i);
+ assert_eq!(selected_point.to_extended(), expected_point);
+
+ expected_point = expected_point
+ .to_extensible()
+ .add_extended(&p)
+ .to_extended();
+ }
+}
diff --git a/ed448/src/curve/twedwards.rs b/ed448/src/curve/twedwards.rs
new file mode 100644
index 000000000..fa4e4f0ef
--- /dev/null
+++ b/ed448/src/curve/twedwards.rs
@@ -0,0 +1,8 @@
+/// This module will contain the EC arithmetic for the Twisted Edwards form of Goldilocks.
+/// with the following affine equation : -x^2 + y^2 = 1 - 39082x^2y^2
+/// This curve will be used as a backend for the Goldilocks, Ristretto and Decaf through the use of isogenies.
+/// It will not be exposed in the public API.
+pub(crate) mod affine;
+pub(crate) mod extended;
+pub(crate) mod extensible;
+pub(crate) mod projective;
diff --git a/ed448/src/curve/twedwards/affine.rs b/ed448/src/curve/twedwards/affine.rs
new file mode 100644
index 000000000..055e49014
--- /dev/null
+++ b/ed448/src/curve/twedwards/affine.rs
@@ -0,0 +1,150 @@
+#![allow(dead_code)]
+use crate::curve::twedwards::{extended::ExtendedPoint, extensible::ExtensiblePoint};
+use crate::field::FieldElement;
+use subtle::{Choice, ConditionallySelectable};
+
+/// This point representation is not a part of the API.
+///
+/// AffinePoint is mainly used as a convenience struct.
+/// XXX: Initially, I wanted to leave some of these in the library to help
+/// others learn. So if you are scrubbing the commit history. Hopefully they were helpful.
+///
+/// Represents an AffinePoint on the Twisted Edwards Curve
+/// with Equation y^2 - x^2 = 1 - (TWISTED_D) * x^2 * y^2
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct AffinePoint {
+ pub(crate) x: FieldElement,
+ pub(crate) y: FieldElement,
+}
+
+impl Default for AffinePoint {
+ fn default() -> AffinePoint {
+ AffinePoint::IDENTITY
+ }
+}
+
+impl AffinePoint {
+ /// Identity element
+ pub(crate) const IDENTITY: AffinePoint = AffinePoint {
+ x: FieldElement::ZERO,
+ y: FieldElement::ONE,
+ };
+
+ /// Checks if the AffinePoint is on the TwistedEdwards curve
+ fn is_on_curve(&self) -> bool {
+ let xx = self.x.square();
+ let yy = self.y.square();
+
+ yy - xx == FieldElement::ONE + (FieldElement::TWISTED_D * xx * yy)
+ }
+
+ // Negates an AffinePoint
+ pub(crate) fn negate(&self) -> AffinePoint {
+ AffinePoint {
+ x: -self.x,
+ y: self.y,
+ }
+ }
+
+ /// Adds an AffinePoint onto an AffinePoint
+ pub(crate) fn add(&self, other: &AffinePoint) -> AffinePoint {
+ let y_numerator = self.y * other.y + self.x * other.x;
+ let y_denominator =
+ FieldElement::ONE - FieldElement::TWISTED_D * self.x * other.x * self.y * other.y;
+
+ let x_numerator = self.x * other.y + self.y * other.x;
+ let x_denominator =
+ FieldElement::ONE + FieldElement::TWISTED_D * self.x * other.x * self.y * other.y;
+
+ let x = x_numerator * x_denominator.invert();
+ let y = y_numerator * y_denominator.invert();
+ AffinePoint { x, y }
+ }
+
+ /// Converts an AffinePoint to an ExtensiblePoint
+ pub(crate) fn to_extensible(self) -> ExtensiblePoint {
+ ExtensiblePoint {
+ X: self.x,
+ Y: self.y,
+ Z: FieldElement::ONE,
+ T1: self.x,
+ T2: self.y,
+ }
+ }
+
+ // /// Converts an AffinePoint to an AffineNielsPoint
+ // pub(crate) fn to_affine_niels(&self) -> AffineNielsPoint {
+ // AffineNielsPoint {
+ // y_plus_x: self.y + self.x,
+ // y_minus_x: self.y - self.x,
+ // td: self.x * self.y * FieldElement::TWISTED_D,
+ // }
+ // }
+ /// Converts an An AffinePoint to an ExtendedPoint
+ pub(crate) fn to_extended(self) -> ExtendedPoint {
+ self.to_extensible().to_extended()
+ }
+}
+
+/// Represents a PreComputed or Cached AffinePoint
+/// ((y+x)/2, (y-x)/2, dxy)
+#[derive(Copy, Clone)]
+pub struct AffineNielsPoint {
+ pub(crate) y_plus_x: FieldElement,
+ pub(crate) y_minus_x: FieldElement,
+ pub(crate) td: FieldElement,
+}
+
+impl ConditionallySelectable for AffineNielsPoint {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ AffineNielsPoint {
+ y_plus_x: FieldElement::conditional_select(&a.y_plus_x, &b.y_plus_x, choice),
+ y_minus_x: FieldElement::conditional_select(&a.y_minus_x, &b.y_minus_x, choice),
+ td: FieldElement::conditional_select(&a.td, &b.td, choice),
+ }
+ }
+}
+
+impl AffineNielsPoint {
+ /// Returns the identity element for an AffineNielsPoint
+ pub(crate) const IDENTITY: AffineNielsPoint = AffineNielsPoint {
+ y_plus_x: FieldElement::ONE,
+ y_minus_x: FieldElement::ONE,
+ td: FieldElement::ZERO,
+ };
+
+ /// Checks if two AffineNielsPoints are equal
+ /// Returns true if they are
+ pub(crate) fn equals(&self, other: &AffineNielsPoint) -> bool {
+ (self.y_minus_x == other.y_minus_x)
+ && (self.y_plus_x == other.y_plus_x)
+ && (self.td == other.td)
+ }
+
+ /// Converts an AffineNielsPoint to an ExtendedPoint
+ pub(crate) fn to_extended(self) -> ExtendedPoint {
+ ExtendedPoint {
+ X: self.y_plus_x - self.y_minus_x,
+ Y: self.y_minus_x + self.y_plus_x,
+ Z: FieldElement::ONE,
+ T: self.y_plus_x * self.y_minus_x,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ #[test]
+ fn test_negation() {
+ use crate::TWISTED_EDWARDS_BASE_POINT;
+ let a = TWISTED_EDWARDS_BASE_POINT.to_affine();
+ assert!(a.is_on_curve());
+
+ let neg_a = a.negate();
+ let got = neg_a.add(&a);
+ assert!(got == AffinePoint::IDENTITY);
+ }
+}
diff --git a/ed448/src/curve/twedwards/extended.rs b/ed448/src/curve/twedwards/extended.rs
new file mode 100644
index 000000000..108221ec8
--- /dev/null
+++ b/ed448/src/curve/twedwards/extended.rs
@@ -0,0 +1,251 @@
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+use crate::curve::edwards::EdwardsPoint as EdwardsExtendedPoint;
+use crate::curve::twedwards::affine::AffinePoint;
+use crate::curve::twedwards::extensible::ExtensiblePoint;
+use crate::field::FieldElement;
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
+
+#[derive(Copy, Clone, Debug)]
+pub struct ExtendedPoint {
+ pub(crate) X: FieldElement,
+ pub(crate) Y: FieldElement,
+ pub(crate) Z: FieldElement,
+ pub(crate) T: FieldElement,
+}
+
+impl ConstantTimeEq for ExtendedPoint {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ let XZ = self.X * other.Z;
+ let ZX = self.Z * other.X;
+
+ let YZ = self.Y * other.Z;
+ let ZY = self.Z * other.Y;
+
+ (XZ.ct_eq(&ZX)) & (YZ.ct_eq(&ZY))
+ }
+}
+
+impl ConditionallySelectable for ExtendedPoint {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ ExtendedPoint {
+ X: FieldElement::conditional_select(&a.X, &b.X, choice),
+ Y: FieldElement::conditional_select(&a.Y, &b.Y, choice),
+ Z: FieldElement::conditional_select(&a.Z, &b.Z, choice),
+ T: FieldElement::conditional_select(&a.T, &b.T, choice),
+ }
+ }
+}
+
+impl PartialEq for ExtendedPoint {
+ fn eq(&self, other: &ExtendedPoint) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+impl Eq for ExtendedPoint {}
+
+impl Default for ExtendedPoint {
+ fn default() -> ExtendedPoint {
+ ExtendedPoint::IDENTITY
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for ExtendedPoint {}
+
+impl ExtendedPoint {
+ /// Generator for the prime subgroup
+ pub const GENERATOR: ExtendedPoint = ExtendedPoint {
+ X: crate::TWISTED_EDWARDS_BASE_POINT.X,
+ Y: crate::TWISTED_EDWARDS_BASE_POINT.Y,
+ Z: crate::TWISTED_EDWARDS_BASE_POINT.Z,
+ T: crate::TWISTED_EDWARDS_BASE_POINT.T,
+ };
+ /// Identity point
+ pub const IDENTITY: ExtendedPoint = ExtendedPoint {
+ X: FieldElement::ZERO,
+ Y: FieldElement::ONE,
+ Z: FieldElement::ONE,
+ T: FieldElement::ZERO,
+ };
+
+ /// Doubles an extended point
+ pub(crate) fn double(&self) -> ExtendedPoint {
+ self.to_extensible().double().to_extended()
+ }
+
+ /// Adds an extended point to itself
+ pub(crate) fn add(&self, other: &ExtendedPoint) -> ExtendedPoint {
+ self.to_extensible().add_extended(other).to_extended()
+ }
+
+ /// Converts an ExtendedPoint to an ExtensiblePoint
+ pub fn to_extensible(self) -> ExtensiblePoint {
+ ExtensiblePoint {
+ X: self.X,
+ Y: self.Y,
+ Z: self.Z,
+ T1: self.T,
+ T2: FieldElement::ONE,
+ }
+ }
+
+ /// Converts an extended point to Affine co-ordinates
+ pub(crate) fn to_affine(self) -> AffinePoint {
+ // Points to consider:
+ // - All points where Z=0, translate to (0,0)
+ // - The identity point has z=1, so it is not a problem
+
+ let INV_Z = self.Z.invert();
+
+ let x = self.X * INV_Z;
+ let y = self.Y * INV_Z;
+
+ AffinePoint { x, y }
+ }
+
+ /// Edwards_Isogeny is derived from the doubling formula
+ /// XXX: There is a duplicate method in the twisted edwards module to compute the dual isogeny
+ /// XXX: Not much point trying to make it generic I think. So what we can do is optimise each respective isogeny method for a=1 or a = -1 (currently, I just made it really slow and simple)
+ fn edwards_isogeny(&self, a: FieldElement) -> EdwardsExtendedPoint {
+ // Convert to affine now, then derive extended version later
+ let affine = self.to_affine();
+ let x = affine.x;
+ let y = affine.y;
+
+ // Compute x
+ let xy = x * y;
+ let x_numerator = xy + xy;
+ let x_denom = y.square() - (a * x.square());
+ let new_x = x_numerator * x_denom.invert();
+
+ // Compute y
+ let y_numerator = y.square() + (a * x.square());
+ let y_denom = (FieldElement::ONE + FieldElement::ONE) - y.square() - (a * x.square());
+ let new_y = y_numerator * y_denom.invert();
+
+ EdwardsExtendedPoint {
+ X: new_x,
+ Y: new_y,
+ Z: FieldElement::ONE,
+ T: new_x * new_y,
+ }
+ }
+
+ /// Uses a 2-isogeny to map the point to the Ed448-Goldilocks
+ pub fn to_untwisted(self) -> EdwardsExtendedPoint {
+ self.edwards_isogeny(FieldElement::MINUS_ONE)
+ }
+
+ /// Checks if the point is on the curve
+ pub(crate) fn is_on_curve(&self) -> Choice {
+ let XY = self.X * self.Y;
+ let ZT = self.Z * self.T;
+
+ // Y^2 - X^2 == Z^2 + T^2 * (TWISTED_D)
+
+ let YY = self.Y.square();
+ let XX = self.X.square();
+ let ZZ = self.Z.square();
+ let TT = self.T.square();
+ let lhs = YY - XX;
+ let rhs = ZZ + TT * FieldElement::TWISTED_D;
+
+ XY.ct_eq(&ZT) & lhs.ct_eq(&rhs)
+ }
+
+ /// Negates a point
+ pub fn negate(&self) -> ExtendedPoint {
+ ExtendedPoint {
+ X: -self.X,
+ Y: self.Y,
+ Z: self.Z,
+ T: -self.T,
+ }
+ }
+
+ /// Torques a point
+ pub fn torque(&self) -> ExtendedPoint {
+ ExtendedPoint {
+ X: -self.X,
+ Y: -self.Y,
+ Z: self.Z,
+ T: self.T,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{GOLDILOCKS_BASE_POINT, TWISTED_EDWARDS_BASE_POINT};
+
+ fn hex_to_field(hex: &'static str) -> FieldElement {
+ assert_eq!(hex.len(), 56 * 2);
+ let mut bytes = hex_literal::decode(&[hex.as_bytes()]);
+ bytes.reverse();
+ FieldElement::from_bytes(&bytes)
+ }
+
+ #[test]
+ fn test_isogeny() {
+ let x = hex_to_field("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa955555555555555555555555555555555555555555555555555555555");
+ let y = hex_to_field("ae05e9634ad7048db359d6205086c2b0036ed7a035884dd7b7e36d728ad8c4b80d6565833a2a3098bbbcb2bed1cda06bdaeafbcdea9386ed");
+ let a = AffinePoint { x, y }.to_extended();
+ let twist_a = a.to_untwisted().to_twisted();
+ assert_eq!(twist_a, a.double().double())
+ }
+
+ #[test]
+ fn test_is_on_curve() {
+ // The twisted edwards basepoint should be on the curve
+ // twisted edwards curve
+ assert_eq!(TWISTED_EDWARDS_BASE_POINT.is_on_curve().unwrap_u8(), 1u8);
+
+ // The goldilocks basepoint should not be
+ let invalid_point = ExtendedPoint {
+ X: GOLDILOCKS_BASE_POINT.X,
+ Y: GOLDILOCKS_BASE_POINT.Y,
+ Z: GOLDILOCKS_BASE_POINT.Z,
+ T: GOLDILOCKS_BASE_POINT.T,
+ };
+ assert_eq!(invalid_point.is_on_curve().unwrap_u8(), 0u8);
+ }
+
+ #[test]
+ fn test_point_add() {
+ let a = TWISTED_EDWARDS_BASE_POINT;
+ let b = a.double();
+
+ // A + B = B + A = C
+ let c_1 = a.to_extensible().add_extended(&b).to_extended();
+ let c_2 = b.to_extensible().add_extended(&a).to_extended();
+ assert!(c_1 == c_2);
+
+ // Adding identity point should not change result
+ let c = c_1.to_extensible().add_extended(&ExtendedPoint::IDENTITY);
+ assert!(c.to_extended() == c_1);
+ }
+
+ #[test]
+ fn test_point_sub() {
+ let a = TWISTED_EDWARDS_BASE_POINT;
+ let b = a.double();
+
+ // A - B = C
+ let c_1 = a.to_extensible().sub_extended(&b).to_extended();
+
+ // -B + A = C
+ let c_2 = b.negate().to_extensible().add_extended(&a).to_extended();
+ assert!(c_1 == c_2);
+ }
+
+ #[test]
+ fn test_negate() {
+ let a = TWISTED_EDWARDS_BASE_POINT;
+ let neg_a = a.negate();
+
+ assert!(a.to_extensible().add_extended(&neg_a) == ExtensiblePoint::IDENTITY);
+ }
+}
diff --git a/ed448/src/curve/twedwards/extensible.rs b/ed448/src/curve/twedwards/extensible.rs
new file mode 100644
index 000000000..73f3678df
--- /dev/null
+++ b/ed448/src/curve/twedwards/extensible.rs
@@ -0,0 +1,180 @@
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+use crate::curve::twedwards::{
+ affine::AffineNielsPoint, extended::ExtendedPoint, projective::ProjectiveNielsPoint,
+};
+use crate::field::FieldElement;
+use subtle::{Choice, ConstantTimeEq};
+
+/// This is the representation that we will do most of the group operations on.
+// In affine (x,y) is the extensible point (X, Y, Z, T1, T2)
+// Where x = X/Z , y = Y/Z , T1 * T2 = T
+// XXX: I think we have too many point representations,
+// But let's not remove any yet
+pub struct ExtensiblePoint {
+ pub(crate) X: FieldElement,
+ pub(crate) Y: FieldElement,
+ pub(crate) Z: FieldElement,
+ pub(crate) T1: FieldElement,
+ pub(crate) T2: FieldElement,
+}
+
+impl ConstantTimeEq for ExtensiblePoint {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ let XZ = self.X * other.Z;
+ let ZX = self.Z * other.X;
+
+ let YZ = self.Y * other.Z;
+ let ZY = self.Z * other.Y;
+
+ XZ.ct_eq(&ZX) & YZ.ct_eq(&ZY)
+ }
+}
+impl PartialEq for ExtensiblePoint {
+ fn eq(&self, other: &ExtensiblePoint) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+impl Eq for ExtensiblePoint {}
+
+impl ExtensiblePoint {
+ pub const IDENTITY: ExtensiblePoint = ExtensiblePoint {
+ X: FieldElement::ZERO,
+ Y: FieldElement::ONE,
+ Z: FieldElement::ONE,
+ T1: FieldElement::ZERO,
+ T2: FieldElement::ONE,
+ };
+
+ /// Doubles a point
+ /// (3.3) https://iacr.org/archive/asiacrypt2008/53500329/53500329.pdf
+ pub fn double(&self) -> ExtensiblePoint {
+ let A = self.X.square();
+ let B = self.Y.square();
+ let C = self.Z.square() + self.Z.square();
+ let D = -A;
+ let E = (self.X + self.Y).square() - A - B;
+ let G = D + B;
+ let F = G - C;
+ let H = D - B;
+ ExtensiblePoint {
+ X: E * F,
+ Y: G * H,
+ Z: F * G,
+ T1: E,
+ T2: H,
+ }
+ }
+
+ /// Adds two extensible points together by converting the other point to a ExtendedPoint
+ pub fn add_extensible(&self, other: &ExtensiblePoint) -> ExtensiblePoint {
+ self.add_extended(&other.to_extended())
+ }
+
+ /// Adds an extensible point to an extended point
+ /// Returns an extensible point
+ /// (3.1) https://iacr.org/archive/asiacrypt2008/53500329/53500329.pdf
+ pub fn add_extended(&self, other: &ExtendedPoint) -> ExtensiblePoint {
+ let A = self.X * other.X;
+ let B = self.Y * other.Y;
+ let C = self.T1 * self.T2 * other.T * FieldElement::TWISTED_D;
+ let D = self.Z * other.Z;
+ let E = (self.X + self.Y) * (other.X + other.Y) - A - B;
+ let F = D - C;
+ let G = D + C;
+ let H = B + A;
+ ExtensiblePoint {
+ X: E * F,
+ Y: G * H,
+ T1: E,
+ T2: H,
+ Z: F * G,
+ }
+ }
+
+ /// Subtracts an extensible point from an extended point
+ /// Returns an extensible point
+ /// This is a direct modification of the addition formula to the negation of `other`
+ pub fn sub_extended(&self, other: &ExtendedPoint) -> ExtensiblePoint {
+ let A = self.X * other.X;
+ let B = self.Y * other.Y;
+ let C = self.T1 * self.T2 * other.T * FieldElement::TWISTED_D;
+ let D = self.Z * other.Z;
+ let E = (self.X + self.Y) * (other.Y - other.X) + A - B;
+ let F = D + C;
+ let G = D - C;
+ let H = B - A;
+ ExtensiblePoint {
+ X: E * F,
+ Y: G * H,
+ T1: E,
+ T2: H,
+ Z: F * G,
+ }
+ }
+
+ /// Adds an extensible point to an AffineNiels point
+ /// Returns an Extensible point
+ pub fn add_affine_niels(&self, other: AffineNielsPoint) -> ExtensiblePoint {
+ let A = other.y_minus_x * (self.Y - self.X);
+ let B = other.y_plus_x * (self.X + self.Y);
+ let C = other.td * self.T1 * self.T2;
+ let D = B + A;
+ let E = B - A;
+ let F = self.Z - C;
+ let G = self.Z + C;
+ ExtensiblePoint {
+ X: E * F,
+ Y: G * D,
+ Z: F * G,
+ T1: E,
+ T2: D,
+ }
+ }
+
+ /// Adds an extensible point to a ProjectiveNiels point
+ /// Returns an extensible point
+ /// (3.1)[Last set of formulas] https://iacr.org/archive/asiacrypt2008/53500329/53500329.pdf
+ /// This differs from the formula above by a factor of 2. Saving 1 Double
+ /// Cost 8M
+ pub fn add_projective_niels(&self, other: &ProjectiveNielsPoint) -> ExtensiblePoint {
+ // This is the only step which makes it different than adding an AffineNielsPoint
+ let Z = self.Z * other.Z;
+
+ let A = (self.Y - self.X) * other.Y_minus_X;
+ let B = (self.Y + self.X) * other.Y_plus_X;
+ let C = other.Td * self.T1 * self.T2;
+ let D = B + A;
+ let E = B - A;
+ let F = Z - C;
+ let G = Z + C;
+ ExtensiblePoint {
+ X: E * F,
+ Y: G * D,
+ Z: F * G,
+ T1: E,
+ T2: D,
+ }
+ }
+
+ /// Converts an extensible point to an extended point
+ pub fn to_extended(&self) -> ExtendedPoint {
+ ExtendedPoint {
+ X: self.X,
+ Y: self.Y,
+ Z: self.Z,
+ T: self.T1 * self.T2,
+ }
+ }
+
+ /// Converts an Extensible point to a ProjectiveNiels Point
+ pub fn to_projective_niels(&self) -> ProjectiveNielsPoint {
+ ProjectiveNielsPoint {
+ Y_plus_X: self.X + self.Y,
+ Y_minus_X: self.Y - self.X,
+ Z: self.Z + self.Z,
+ Td: self.T1 * self.T2 * FieldElement::TWO_TIMES_TWISTED_D,
+ }
+ }
+}
diff --git a/ed448/src/curve/twedwards/projective.rs b/ed448/src/curve/twedwards/projective.rs
new file mode 100644
index 000000000..d5c9cafb7
--- /dev/null
+++ b/ed448/src/curve/twedwards/projective.rs
@@ -0,0 +1,77 @@
+#![allow(non_snake_case)]
+
+use crate::curve::twedwards::{extended::ExtendedPoint, extensible::ExtensiblePoint};
+use crate::field::FieldElement;
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable};
+
+impl Default for ProjectiveNielsPoint {
+ fn default() -> ProjectiveNielsPoint {
+ ProjectiveNielsPoint::identity()
+ }
+}
+
+// Its a variant of Niels, where a Z coordinate is added for unmixed readdition
+// ((y+x)/2, (y-x)/2, dxy, Z)
+#[derive(Copy, Clone)]
+pub struct ProjectiveNielsPoint {
+ pub(crate) Y_plus_X: FieldElement,
+ pub(crate) Y_minus_X: FieldElement,
+ pub(crate) Td: FieldElement,
+ pub(crate) Z: FieldElement,
+}
+
+impl PartialEq for ProjectiveNielsPoint {
+ fn eq(&self, other: &ProjectiveNielsPoint) -> bool {
+ self.to_extended().eq(&other.to_extended())
+ }
+}
+impl Eq for ProjectiveNielsPoint {}
+
+impl ConditionallySelectable for ProjectiveNielsPoint {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ ProjectiveNielsPoint {
+ Y_plus_X: FieldElement::conditional_select(&a.Y_plus_X, &b.Y_plus_X, choice),
+ Y_minus_X: FieldElement::conditional_select(&a.Y_minus_X, &b.Y_minus_X, choice),
+ Td: FieldElement::conditional_select(&a.Td, &b.Td, choice),
+ Z: FieldElement::conditional_select(&a.Z, &b.Z, choice),
+ }
+ }
+}
+impl ConditionallyNegatable for ProjectiveNielsPoint {
+ fn conditional_negate(&mut self, choice: Choice) {
+ FieldElement::conditional_swap(&mut self.Y_minus_X, &mut self.Y_plus_X, choice);
+ self.Td.conditional_negate(choice);
+ }
+}
+
+impl ProjectiveNielsPoint {
+ pub fn identity() -> ProjectiveNielsPoint {
+ ExtensiblePoint::IDENTITY.to_projective_niels()
+ }
+
+ pub fn to_extended(self) -> ExtendedPoint {
+ let A = self.Y_plus_X - self.Y_minus_X;
+ let B = self.Y_plus_X + self.Y_minus_X;
+ ExtendedPoint {
+ X: self.Z * A,
+ Y: self.Z * B,
+ Z: self.Z.square(),
+ T: B * A,
+ }
+ }
+}
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_conditional_negate() {
+ let bp = ExtendedPoint::GENERATOR;
+
+ let mut bp_neg = bp.to_extensible().to_projective_niels();
+ bp_neg.conditional_negate(1.into());
+
+ let expect_identity = bp_neg.to_extended().add(&bp);
+ assert_eq!(ExtendedPoint::IDENTITY, expect_identity);
+ }
+}
diff --git a/ed448/src/decaf.rs b/ed448/src/decaf.rs
new file mode 100644
index 000000000..8b1cef506
--- /dev/null
+++ b/ed448/src/decaf.rs
@@ -0,0 +1,9 @@
+// This will be the module for Decaf over Ed448
+// This is the newer version of the Decaf strategy, which looks simpler
+
+pub mod affine;
+mod ops;
+pub mod points;
+
+pub use affine::AffinePoint;
+pub use points::{CompressedDecaf, DecafPoint};
diff --git a/ed448/src/decaf/affine.rs b/ed448/src/decaf/affine.rs
new file mode 100644
index 000000000..2902532da
--- /dev/null
+++ b/ed448/src/decaf/affine.rs
@@ -0,0 +1,72 @@
+use crate::curve::twedwards::affine::AffinePoint as InnerAffinePoint;
+use crate::field::FieldElement;
+use crate::{Decaf448FieldBytes, DecafPoint};
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
+
+#[cfg(feature = "zeroize")]
+use zeroize::DefaultIsZeroes;
+
+/// Affine point on the twisted curve
+#[derive(Copy, Clone, Debug, Default)]
+pub struct AffinePoint(pub(crate) InnerAffinePoint);
+
+impl ConstantTimeEq for AffinePoint {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.0.x.ct_eq(&other.0.x) & self.0.y.ct_eq(&other.0.y)
+ }
+}
+
+impl ConditionallySelectable for AffinePoint {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ Self(InnerAffinePoint {
+ x: FieldElement::conditional_select(&a.0.x, &b.0.x, choice),
+ y: FieldElement::conditional_select(&a.0.y, &b.0.y, choice),
+ })
+ }
+}
+
+impl PartialEq for AffinePoint {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for AffinePoint {}
+
+impl elliptic_curve::point::AffineCoordinates for AffinePoint {
+ type FieldRepr = Decaf448FieldBytes;
+
+ fn x(&self) -> Self::FieldRepr {
+ Decaf448FieldBytes::from(self.x())
+ }
+
+ fn y_is_odd(&self) -> Choice {
+ self.0.y.is_negative()
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl DefaultIsZeroes for AffinePoint {}
+
+impl AffinePoint {
+ /// The identity point
+ pub const IDENTITY: Self = Self(InnerAffinePoint::IDENTITY);
+
+ /// Convert to DecafPoint
+ pub fn to_decaf(&self) -> DecafPoint {
+ DecafPoint(self.0.to_extended())
+ }
+
+ /// The X coordinate
+ pub fn x(&self) -> [u8; 57] {
+ // TODO: fix this to be 56 bytes as per
+ // https://datatracker.ietf.org/doc/draft-irtf-cfrg-ristretto255-decaf448
+ // This might require creating a separate DecafScalar
+ self.0.x.to_bytes_extended()
+ }
+
+ /// The Y coordinate
+ pub fn y(&self) -> [u8; 56] {
+ self.0.y.to_bytes()
+ }
+}
diff --git a/ed448/src/decaf/ops.rs b/ed448/src/decaf/ops.rs
new file mode 100644
index 000000000..866a9b8c8
--- /dev/null
+++ b/ed448/src/decaf/ops.rs
@@ -0,0 +1,199 @@
+use crate::{curve::scalar_mul::double_and_add, DecafAffinePoint, Scalar};
+use core::{
+ borrow::Borrow,
+ iter::Sum,
+ ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
+};
+use elliptic_curve::group::Curve;
+
+use super::DecafPoint;
+
+/// Scalar Mul Operations
+impl Mul<&Scalar> for &DecafPoint {
+ type Output = DecafPoint;
+
+ fn mul(self, scalar: &Scalar) -> DecafPoint {
+ // XXX: We can do better than double and add
+ DecafPoint(double_and_add(&self.0, scalar))
+ }
+}
+
+define_mul_variants!(LHS = DecafPoint, RHS = Scalar, Output = DecafPoint);
+
+impl Mul<&DecafPoint> for &Scalar {
+ type Output = DecafPoint;
+
+ fn mul(self, point: &DecafPoint) -> DecafPoint {
+ point * self
+ }
+}
+
+define_mul_variants!(LHS = Scalar, RHS = DecafPoint, Output = DecafPoint);
+
+impl<'s> MulAssign<&'s Scalar> for DecafPoint {
+ fn mul_assign(&mut self, scalar: &'s Scalar) {
+ *self = *self * scalar;
+ }
+}
+impl MulAssign for DecafPoint {
+ fn mul_assign(&mut self, scalar: Scalar) {
+ *self = *self * scalar;
+ }
+}
+
+// Point addition
+
+impl Add<&DecafPoint> for &DecafPoint {
+ type Output = DecafPoint;
+
+ fn add(self, other: &DecafPoint) -> DecafPoint {
+ DecafPoint(self.0.to_extensible().add_extended(&other.0).to_extended())
+ }
+}
+
+impl Add<&DecafAffinePoint> for &DecafPoint {
+ type Output = DecafPoint;
+
+ fn add(self, rhs: &DecafAffinePoint) -> Self::Output {
+ self + DecafPoint(rhs.0.to_extended())
+ }
+}
+
+impl Add<&DecafPoint> for &DecafAffinePoint {
+ type Output = DecafPoint;
+
+ fn add(self, rhs: &DecafPoint) -> Self::Output {
+ DecafPoint(self.0.to_extended()) + rhs
+ }
+}
+
+define_add_variants!(LHS = DecafPoint, RHS = DecafPoint, Output = DecafPoint);
+define_add_variants!(
+ LHS = DecafPoint,
+ RHS = DecafAffinePoint,
+ Output = DecafPoint
+);
+define_add_variants!(
+ LHS = DecafAffinePoint,
+ RHS = DecafPoint,
+ Output = DecafPoint
+);
+
+impl AddAssign<&DecafPoint> for DecafPoint {
+ fn add_assign(&mut self, other: &DecafPoint) {
+ *self = *self + other;
+ }
+}
+impl AddAssign for DecafPoint {
+ fn add_assign(&mut self, other: DecafPoint) {
+ *self = *self + other;
+ }
+}
+
+impl AddAssign<&DecafAffinePoint> for DecafPoint {
+ fn add_assign(&mut self, other: &DecafAffinePoint) {
+ *self = *self + *other;
+ }
+}
+
+impl AddAssign<&DecafPoint> for DecafAffinePoint {
+ fn add_assign(&mut self, rhs: &DecafPoint) {
+ *self = (DecafPoint(self.0.to_extended()) + rhs).to_affine();
+ }
+}
+
+define_add_assign_variants!(LHS = DecafPoint, RHS = DecafAffinePoint);
+define_add_assign_variants!(LHS = DecafAffinePoint, RHS = DecafPoint);
+
+// Point Subtraction
+
+impl Sub<&DecafPoint> for &DecafPoint {
+ type Output = DecafPoint;
+
+ fn sub(self, other: &DecafPoint) -> DecafPoint {
+ DecafPoint(self.0.to_extensible().sub_extended(&other.0).to_extended())
+ }
+}
+
+impl Sub<&DecafAffinePoint> for &DecafPoint {
+ type Output = DecafPoint;
+
+ fn sub(self, rhs: &DecafAffinePoint) -> Self::Output {
+ self - DecafPoint(rhs.0.to_extended())
+ }
+}
+
+impl Sub<&DecafPoint> for &DecafAffinePoint {
+ type Output = DecafPoint;
+
+ fn sub(self, rhs: &DecafPoint) -> Self::Output {
+ DecafPoint(self.0.to_extended()) - rhs
+ }
+}
+
+define_sub_variants!(LHS = DecafPoint, RHS = DecafPoint, Output = DecafPoint);
+define_sub_variants!(
+ LHS = DecafPoint,
+ RHS = DecafAffinePoint,
+ Output = DecafPoint
+);
+define_sub_variants!(
+ LHS = DecafAffinePoint,
+ RHS = DecafPoint,
+ Output = DecafPoint
+);
+
+impl SubAssign<&DecafPoint> for DecafPoint {
+ fn sub_assign(&mut self, other: &DecafPoint) {
+ *self = *self - other;
+ }
+}
+impl SubAssign for DecafPoint {
+ fn sub_assign(&mut self, other: DecafPoint) {
+ *self = *self - other;
+ }
+}
+
+impl SubAssign<&DecafAffinePoint> for DecafPoint {
+ fn sub_assign(&mut self, other: &DecafAffinePoint) {
+ *self = *self - *other;
+ }
+}
+
+impl SubAssign<&DecafPoint> for DecafAffinePoint {
+ fn sub_assign(&mut self, rhs: &DecafPoint) {
+ *self = (DecafPoint(self.0.to_extended()) - rhs).to_affine();
+ }
+}
+
+define_sub_assign_variants!(LHS = DecafPoint, RHS = DecafAffinePoint);
+define_sub_assign_variants!(LHS = DecafAffinePoint, RHS = DecafPoint);
+
+// Point Negation
+
+impl Neg for &DecafPoint {
+ type Output = DecafPoint;
+
+ fn neg(self) -> DecafPoint {
+ DecafPoint(self.0.negate())
+ }
+}
+impl Neg for DecafPoint {
+ type Output = DecafPoint;
+
+ fn neg(self) -> DecafPoint {
+ (&self).neg()
+ }
+}
+
+impl Sum for DecafPoint
+where
+ T: Borrow,
+{
+ fn sum(iter: I) -> Self
+ where
+ I: Iterator
- ,
+ {
+ iter.fold(Self::IDENTITY, |acc, item| acc + item.borrow())
+ }
+}
diff --git a/ed448/src/decaf/points.rs b/ed448/src/decaf/points.rs
new file mode 100644
index 000000000..14329eaff
--- /dev/null
+++ b/ed448/src/decaf/points.rs
@@ -0,0 +1,800 @@
+use crate::constants::{BASEPOINT_ORDER, DECAF_BASEPOINT};
+use crate::curve::twedwards::extended::ExtendedPoint;
+use crate::field::FieldElement;
+use crate::*;
+
+use elliptic_curve::{
+ array::{
+ typenum::{U56, U84},
+ Array,
+ },
+ group::{cofactor::CofactorGroup, prime::PrimeGroup, Curve, GroupEncoding},
+ hash2curve::{ExpandMsg, Expander, FromOkm},
+ ops::{LinearCombination, MulByGenerator},
+ Group,
+};
+
+use core::fmt::{Display, Formatter, LowerHex, Result as FmtResult, UpperHex};
+use rand_core::{CryptoRng, RngCore};
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+/// The bytes representation of a compressed point
+pub type DecafPointBytes = [u8; 56];
+/// The group bytes representation
+pub type DecafPointRepr = Array;
+
+/// A Decaf point in the Twisted Edwards curve
+#[derive(Copy, Clone, Debug)]
+pub struct DecafPoint(pub(crate) ExtendedPoint);
+
+impl Default for DecafPoint {
+ fn default() -> Self {
+ Self::IDENTITY
+ }
+}
+
+impl Display for DecafPoint {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ write!(
+ f,
+ "{{ X: {}, Y: {}, Z: {}, T: {} }}",
+ self.0.X, self.0.Y, self.0.Z, self.0.T
+ )
+ }
+}
+
+impl LowerHex for DecafPoint {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ write!(
+ f,
+ "{{ X: {:x}, Y: {:x}, Z: {:x}, T: {:x} }}",
+ self.0.X, self.0.Y, self.0.Z, self.0.T
+ )
+ }
+}
+
+impl UpperHex for DecafPoint {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ write!(
+ f,
+ "{{ X: {:X}, Y: {:X}, Z: {:X}, T: {:X} }}",
+ self.0.X, self.0.Y, self.0.Z, self.0.T
+ )
+ }
+}
+
+impl ConstantTimeEq for DecafPoint {
+ fn ct_eq(&self, other: &DecafPoint) -> Choice {
+ (self.0.X * other.0.Y).ct_eq(&(self.0.Y * other.0.X))
+ }
+}
+
+impl ConditionallySelectable for DecafPoint {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ DecafPoint(ExtendedPoint {
+ X: FieldElement::conditional_select(&a.0.X, &b.0.X, choice),
+ Y: FieldElement::conditional_select(&a.0.Y, &b.0.Y, choice),
+ Z: FieldElement::conditional_select(&a.0.Z, &b.0.Z, choice),
+ T: FieldElement::conditional_select(&a.0.T, &b.0.T, choice),
+ })
+ }
+}
+
+impl PartialEq for DecafPoint {
+ fn eq(&self, other: &DecafPoint) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for DecafPoint {}
+
+impl From for DecafPointBytes {
+ fn from(point: DecafPoint) -> DecafPointBytes {
+ point.compress().0
+ }
+}
+
+impl From<&DecafPoint> for DecafPointBytes {
+ fn from(compressed: &DecafPoint) -> DecafPointBytes {
+ Self::from(*compressed)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From for Vec {
+ fn from(compressed: DecafPoint) -> Vec {
+ Self::from(&compressed)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From<&DecafPoint> for Vec {
+ fn from(point: &DecafPoint) -> Vec {
+ point.compress().0.to_vec()
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for DecafPoint {
+ type Error = &'static str;
+
+ fn try_from(bytes: Vec) -> Result {
+ Self::try_from(bytes.as_slice())
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom<&Vec> for DecafPoint {
+ type Error = &'static str;
+
+ fn try_from(bytes: &Vec) -> Result {
+ Self::try_from(bytes.as_slice())
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom<&[u8]> for DecafPoint {
+ type Error = &'static str;
+
+ fn try_from(bytes: &[u8]) -> Result {
+ let compressed =
+ ::try_from(bytes).map_err(|_| "bytes is not the correct length")?;
+ Self::try_from(compressed)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for DecafPoint {
+ type Error = &'static str;
+
+ fn try_from(bytes: Box<[u8]>) -> Result {
+ Self::try_from(bytes.as_ref())
+ }
+}
+
+impl TryFrom for DecafPoint {
+ type Error = &'static str;
+
+ fn try_from(bytes: DecafPointBytes) -> Result {
+ let pt = CompressedDecaf(bytes);
+ Option::::from(pt.decompress()).ok_or("Invalid point encoding")
+ }
+}
+
+impl TryFrom<&DecafPointBytes> for DecafPoint {
+ type Error = &'static str;
+
+ fn try_from(bytes: &DecafPointBytes) -> Result {
+ Self::try_from(*bytes)
+ }
+}
+
+impl Group for DecafPoint {
+ type Scalar = Scalar;
+
+ fn random(mut rng: impl RngCore) -> Self {
+ let mut uniform_bytes = [0u8; 112];
+ rng.fill_bytes(&mut uniform_bytes);
+ Self::from_uniform_bytes(&uniform_bytes)
+ }
+
+ fn identity() -> Self {
+ Self::IDENTITY
+ }
+
+ fn generator() -> Self {
+ Self::GENERATOR
+ }
+
+ fn is_identity(&self) -> Choice {
+ self.ct_eq(&Self::IDENTITY)
+ }
+
+ fn double(&self) -> Self {
+ Self(self.0.double())
+ }
+}
+
+impl GroupEncoding for DecafPoint {
+ type Repr = DecafPointRepr;
+
+ fn from_bytes(bytes: &Self::Repr) -> CtOption {
+ let pt = CompressedDecaf(*(bytes.as_ref()));
+ pt.decompress()
+ }
+
+ fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption {
+ let pt = CompressedDecaf(*(bytes.as_ref()));
+ pt.decompress()
+ }
+
+ fn to_bytes(&self) -> Self::Repr {
+ DecafPointRepr::from(self.compress().0)
+ }
+}
+
+impl CofactorGroup for DecafPoint {
+ type Subgroup = DecafPoint;
+
+ fn clear_cofactor(&self) -> Self::Subgroup {
+ self.double().double()
+ }
+
+ fn into_subgroup(self) -> CtOption {
+ CtOption::new(self.clear_cofactor(), self.is_torsion_free())
+ }
+
+ fn is_torsion_free(&self) -> Choice {
+ (self * BASEPOINT_ORDER).ct_eq(&Self::IDENTITY)
+ }
+}
+
+impl PrimeGroup for DecafPoint {}
+
+impl MulByGenerator for DecafPoint {}
+
+impl LinearCombination<[(DecafPoint, Scalar); N]> for DecafPoint {}
+
+impl LinearCombination<[(DecafPoint, Scalar)]> for DecafPoint {}
+
+impl Curve for DecafPoint {
+ type AffineRepr = DecafAffinePoint;
+
+ fn to_affine(&self) -> Self::AffineRepr {
+ DecafAffinePoint(self.0.to_affine())
+ }
+}
+
+impl From for DecafPoint {
+ fn from(point: EdwardsPoint) -> Self {
+ Self(point.to_twisted())
+ }
+}
+
+impl From<&EdwardsPoint> for DecafPoint {
+ fn from(point: &EdwardsPoint) -> Self {
+ Self(point.to_twisted())
+ }
+}
+
+impl From for EdwardsPoint {
+ fn from(point: DecafPoint) -> Self {
+ point.0.to_untwisted()
+ }
+}
+
+impl From<&DecafPoint> for EdwardsPoint {
+ fn from(point: &DecafPoint) -> Self {
+ point.0.to_untwisted()
+ }
+}
+
+impl From for DecafPoint {
+ fn from(point: DecafAffinePoint) -> Self {
+ Self(point.0.to_extended())
+ }
+}
+
+impl From<&DecafAffinePoint> for DecafPoint {
+ fn from(point: &DecafAffinePoint) -> Self {
+ Self(point.0.to_extended())
+ }
+}
+
+impl From for DecafAffinePoint {
+ fn from(point: DecafPoint) -> Self {
+ DecafAffinePoint(point.0.to_affine())
+ }
+}
+
+impl From<&DecafPoint> for DecafAffinePoint {
+ fn from(point: &DecafPoint) -> Self {
+ DecafAffinePoint(point.0.to_affine())
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for DecafPoint {}
+
+impl DecafPoint {
+ /// The generator point
+ pub const GENERATOR: DecafPoint = DECAF_BASEPOINT;
+ /// The identity point
+ pub const IDENTITY: DecafPoint = DecafPoint(ExtendedPoint::IDENTITY);
+
+ /// Check if the point is the identity
+ pub fn is_identity(&self) -> Choice {
+ self.ct_eq(&DecafPoint::IDENTITY)
+ }
+
+ /// Add two points
+ pub fn add(&self, other: &DecafPoint) -> DecafPoint {
+ DecafPoint(self.0.to_extensible().add_extended(&other.0).to_extended())
+ }
+
+ /// Subtract two points
+ pub fn sub(&self, other: &DecafPoint) -> DecafPoint {
+ DecafPoint(self.0.to_extensible().sub_extended(&other.0).to_extended())
+ }
+
+ /// Compress this point
+ pub fn compress(&self) -> CompressedDecaf {
+ let X = self.0.X;
+ // let Y = self.0.Y;
+ let Z = self.0.Z;
+ let T = self.0.T;
+
+ let XX_TT = (X + T) * (X - T);
+
+ let (isr, _) = (X.square() * XX_TT * FieldElement::NEG_EDWARDS_D).inverse_square_root();
+ let mut ratio = isr * XX_TT;
+ let altx = ratio * FieldElement::DECAF_FACTOR; // Sign choice
+ ratio.conditional_negate(altx.is_negative());
+ let k = ratio * Z - T;
+
+ let mut s = k * FieldElement::NEG_EDWARDS_D * isr * X;
+ s.conditional_negate(s.is_negative());
+
+ CompressedDecaf(s.to_bytes())
+ }
+
+ /// Return a `DecafPoint` chosen uniformly at random using a user-provided RNG.
+ ///
+ /// Uses the Decaf448 map, so that the discrete log
+ /// of the output point with respect to any other point
+ /// is unknown.
+ pub fn random(mut rng: impl CryptoRng) -> Self {
+ let mut uniform_bytes = [0u8; 112];
+ rng.fill_bytes(&mut uniform_bytes);
+ Self::from_uniform_bytes(&uniform_bytes)
+ }
+
+ /// Construct a `DecafPoint` using `ExpandMsg`.
+ ///
+ /// This function is similar to `hash_to_curve` in the IETF draft
+ /// where an expand_message function can be chosen and a domain
+ /// separation tag.
+ pub fn hash(msg: &[u8], dst: &[u8]) -> Self
+ where
+ X: for<'a> ExpandMsg<'a>,
+ {
+ let dst = [dst];
+ let mut random_bytes = Array::::default();
+ let mut expander =
+ X::expand_message(&[msg], &dst, random_bytes.len() * 2).expect("bad dst");
+ expander.fill_bytes(&mut random_bytes);
+ let u0 = FieldElement::from_okm(&random_bytes);
+ expander.fill_bytes(&mut random_bytes);
+ let u1 = FieldElement::from_okm(&random_bytes);
+
+ let q0 = u0.map_to_curve_decaf448();
+ let q1 = u1.map_to_curve_decaf448();
+ Self(q0.add(&q1))
+ }
+
+ /// Construct a `DecafPoint` from 112 bytes of data.
+ ///
+ /// If the input bytes are uniformly distributed, the resulting
+ /// point will be uniformly distributed over the group, and its
+ /// discrete log with respect to other points is unknown.
+ ///
+ /// Implements map to curve according
+ /// see
+ /// section 5.3.4 by splitting the input into two 56-byte halves,
+ /// then applies the decaf448_map to each, and adds the results.
+ pub fn from_uniform_bytes(bytes: &[u8; 112]) -> Self {
+ let lo: [u8; 56] = (&bytes[..56])
+ .try_into()
+ .expect("how does the slice have an incorrect length");
+ let hi: [u8; 56] = (&bytes[56..])
+ .try_into()
+ .expect("how does the slice have an incorrect length");
+
+ let u0 = FieldElement::from_bytes(&lo);
+ let u1 = FieldElement::from_bytes(&hi);
+ let q0 = u0.map_to_curve_decaf448();
+ let q1 = u1.map_to_curve_decaf448();
+ Self(q0.add(&q1))
+ }
+}
+
+/// A compressed decaf point
+#[derive(Copy, Clone, Debug)]
+#[repr(transparent)]
+pub struct CompressedDecaf(pub DecafPointBytes);
+
+impl Default for CompressedDecaf {
+ fn default() -> CompressedDecaf {
+ Self::IDENTITY
+ }
+}
+
+impl ConstantTimeEq for CompressedDecaf {
+ fn ct_eq(&self, other: &CompressedDecaf) -> Choice {
+ self.as_bytes().ct_eq(other.as_bytes())
+ }
+}
+
+impl ConditionallySelectable for CompressedDecaf {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ let mut bytes = [0u8; 56];
+ for (i, byte) in bytes.iter_mut().enumerate() {
+ *byte = u8::conditional_select(&a.0[i], &b.0[i], choice);
+ }
+ Self(bytes)
+ }
+}
+
+impl PartialEq for CompressedDecaf {
+ fn eq(&self, other: &CompressedDecaf) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for CompressedDecaf {}
+
+impl From for DecafPointBytes {
+ fn from(compressed: CompressedDecaf) -> DecafPointBytes {
+ compressed.0
+ }
+}
+
+impl From<&CompressedDecaf> for DecafPointBytes {
+ fn from(compressed: &CompressedDecaf) -> DecafPointBytes {
+ Self::from(*compressed)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From for Vec {
+ fn from(compressed: CompressedDecaf) -> Vec {
+ Self::from(&compressed)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From<&CompressedDecaf> for Vec {
+ fn from(compressed: &CompressedDecaf) -> Vec {
+ compressed.0.to_vec()
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for CompressedDecaf {
+ type Error = &'static str;
+
+ fn try_from(bytes: Vec) -> Result {
+ Self::try_from(bytes.as_slice())
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom<&Vec> for CompressedDecaf {
+ type Error = &'static str;
+
+ fn try_from(bytes: &Vec) -> Result {
+ Self::try_from(bytes.as_slice())
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom<&[u8]> for CompressedDecaf {
+ type Error = &'static str;
+
+ fn try_from(bytes: &[u8]) -> Result {
+ let compressed = ::try_from(bytes).map_err(|_| "invalid length")?;
+ Self::try_from(compressed)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for CompressedDecaf {
+ type Error = &'static str;
+
+ fn try_from(bytes: Box<[u8]>) -> Result {
+ Self::try_from(bytes.as_ref())
+ }
+}
+
+impl TryFrom for CompressedDecaf {
+ type Error = &'static str;
+
+ fn try_from(bytes: DecafPointBytes) -> Result {
+ let pt = CompressedDecaf(bytes);
+ let _ = Option::::from(pt.decompress()).ok_or("Invalid point encoding")?;
+ Ok(pt)
+ }
+}
+
+impl TryFrom<&DecafPointBytes> for CompressedDecaf {
+ type Error = &'static str;
+
+ fn try_from(bytes: &DecafPointBytes) -> Result {
+ Self::try_from(*bytes)
+ }
+}
+
+impl AsRef for CompressedDecaf {
+ fn as_ref(&self) -> &DecafPointBytes {
+ &self.0
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serdect::serde::Serialize for CompressedDecaf {
+ fn serialize(&self, s: S) -> Result {
+ serdect::slice::serialize_hex_lower_or_bin(&self.0, s)
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serdect::serde::Deserialize<'de> for CompressedDecaf {
+ fn deserialize(d: D) -> Result
+ where
+ D: serdect::serde::Deserializer<'de>,
+ {
+ let mut bytes = [0u8; 56];
+ serdect::array::deserialize_hex_or_bin(&mut bytes, d)?;
+ Self::try_from(bytes).map_err(serdect::serde::de::Error::custom)
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for CompressedDecaf {}
+
+impl CompressedDecaf {
+ /// The compressed generator point
+ pub const GENERATOR: Self = Self([
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ ]);
+ /// The compressed identity point
+ pub const IDENTITY: Self = Self([0u8; 56]);
+
+ /// Decompress a point if it is valid
+ pub fn decompress(&self) -> CtOption {
+ let s = FieldElement::from_bytes(&self.0);
+ //XX: Check for canonical encoding and sign,
+ // Copied this check from Dalek: The From_bytes function does not throw an error, if the bytes exceed the prime.
+ // However, to_bytes reduces the Field element before serialising
+ // So we can use to_bytes -> from_bytes and if the representations are the same, then the element was already in reduced form
+ let s_bytes_check = s.to_bytes();
+ let s_encoding_is_canonical = s_bytes_check[..].ct_eq(&self.0);
+ let s_is_negative = s.is_negative();
+ // if s_encoding_is_canonical.unwrap_u8() == 0u8 || s.is_negative().unwrap_u8() == 1u8 {
+ // return None;
+ // }
+
+ let ss = s.square();
+ let u1 = FieldElement::ONE - ss;
+ let u2 = FieldElement::ONE + ss;
+ let u1_sqr = u1.square();
+
+ let v = ss * (FieldElement::NEG_FOUR_TIMES_TWISTED_D) + u1_sqr; // XXX: constantify please
+
+ let (I, ok) = (v * u1_sqr).inverse_square_root();
+
+ let Dx = I * u1;
+ let Dxs = (s + s) * Dx;
+
+ let mut X = (Dxs * I) * v;
+ let k = Dxs * FieldElement::DECAF_FACTOR;
+ X.conditional_negate(k.is_negative());
+
+ let Y = Dx * u2;
+ let Z = FieldElement::ONE;
+ let T = X * Y;
+ let pt = ExtendedPoint { X, Y, Z, T };
+
+ CtOption::new(
+ DecafPoint(pt),
+ ok & pt.is_on_curve() & s_encoding_is_canonical & !s_is_negative,
+ )
+ }
+
+ /// Get the bytes of this compressed point
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::TWISTED_EDWARDS_BASE_POINT;
+
+ #[test]
+ fn test_edwards_ristretto_operations() {
+ // Basic test that if P1 + P2 = P3
+ // Then Decaf(P1) + Decaf(P2) = Decaf(P3)
+
+ let P = TWISTED_EDWARDS_BASE_POINT;
+
+ let P2 = P.double();
+ let P3 = P2.to_extensible().add_extended(&P).to_extended();
+
+ // Encode and decode to make them Decaf points
+ let Decaf_P = DecafPoint(P).compress().decompress().unwrap();
+ let Decaf_P2 = DecafPoint(P2).compress().decompress().unwrap();
+ let expected_Decaf_P3 = DecafPoint(P3).compress().decompress().unwrap();
+
+ // Adding the DecafPoint should be the same as adding the Edwards points and encoding the result as Decaf
+ let Decaf_P3 = Decaf_P + Decaf_P2;
+
+ assert_eq!(Decaf_P3, expected_Decaf_P3);
+ }
+
+ #[test]
+ fn test_identity() {
+ // Basic test to check the identity is being encoded properly
+ let compress_identity = DecafPoint::IDENTITY.compress();
+ assert!(compress_identity == CompressedDecaf::IDENTITY)
+ }
+
+ #[test]
+ fn test_vectors_lib_decaf() {
+ // Testing small multiples of basepoint. Taken from reference implementation.
+ let compressed = [
+ // Taken from libdecaf, where they were computed using SAGE script
+ CompressedDecaf([
+ 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ]),
+ CompressedDecaf([
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ 51,
+ ]),
+ CompressedDecaf([
+ 200, 152, 235, 79, 135, 249, 124, 86, 76, 111, 214, 31, 199, 228, 150, 137, 49, 74,
+ 31, 129, 142, 200, 94, 235, 59, 213, 81, 74, 200, 22, 211, 135, 120, 246, 158, 243,
+ 71, 168, 159, 202, 129, 126, 102, 222, 253, 237, 206, 23, 140, 124, 199, 9, 178,
+ 17, 110, 117,
+ ]),
+ CompressedDecaf([
+ 160, 192, 155, 242, 186, 114, 8, 253, 160, 244, 191, 227, 208, 245, 178, 154, 84,
+ 48, 18, 48, 109, 67, 131, 27, 90, 220, 111, 231, 248, 89, 111, 163, 8, 118, 61,
+ 177, 84, 104, 50, 59, 17, 207, 110, 74, 235, 140, 24, 254, 68, 103, 143, 68, 84,
+ 90, 105, 188,
+ ]),
+ CompressedDecaf([
+ 180, 111, 24, 54, 170, 40, 124, 10, 90, 86, 83, 240, 236, 94, 249, 233, 3, 244, 54,
+ 226, 28, 21, 112, 194, 154, 217, 229, 245, 150, 218, 151, 238, 175, 23, 21, 10,
+ 227, 11, 203, 49, 116, 208, 75, 194, 215, 18, 200, 199, 120, 157, 124, 180, 253,
+ 161, 56, 244,
+ ]),
+ CompressedDecaf([
+ 28, 91, 190, 207, 71, 65, 223, 170, 231, 157, 183, 45, 250, 206, 0, 234, 170, 197,
+ 2, 194, 6, 9, 52, 182, 234, 174, 202, 106, 32, 189, 61, 169, 224, 190, 135, 119,
+ 247, 208, 32, 51, 209, 177, 88, 132, 35, 34, 129, 164, 31, 199, 248, 14, 237, 4,
+ 175, 94,
+ ]),
+ CompressedDecaf([
+ 134, 255, 1, 130, 212, 15, 127, 158, 219, 120, 98, 81, 88, 33, 189, 103, 191, 214,
+ 22, 90, 60, 68, 222, 149, 215, 223, 121, 184, 119, 156, 207, 100, 96, 227, 198,
+ 139, 112, 193, 106, 170, 40, 15, 45, 123, 63, 34, 215, 69, 185, 122, 137, 144, 108,
+ 252, 71, 108,
+ ]),
+ CompressedDecaf([
+ 80, 43, 203, 104, 66, 235, 6, 240, 228, 144, 50, 186, 232, 124, 85, 76, 3, 29, 109,
+ 77, 45, 118, 148, 239, 191, 156, 70, 141, 72, 34, 12, 80, 248, 202, 40, 132, 51,
+ 100, 215, 12, 238, 146, 214, 254, 36, 110, 97, 68, 143, 157, 185, 128, 139, 59, 36,
+ 8,
+ ]),
+ CompressedDecaf([
+ 12, 152, 16, 241, 226, 235, 211, 137, 202, 167, 137, 55, 77, 120, 0, 121, 116, 239,
+ 77, 23, 34, 115, 22, 244, 14, 87, 139, 51, 104, 39, 218, 63, 107, 72, 42, 71, 148,
+ 235, 106, 57, 117, 185, 113, 181, 225, 56, 143, 82, 233, 30, 162, 241, 188, 176,
+ 249, 18,
+ ]),
+ CompressedDecaf([
+ 32, 212, 29, 133, 161, 141, 86, 87, 162, 150, 64, 50, 21, 99, 187, 208, 76, 47,
+ 251, 208, 163, 122, 123, 164, 58, 79, 125, 38, 60, 226, 111, 175, 78, 31, 116, 249,
+ 244, 181, 144, 198, 146, 41, 174, 87, 31, 227, 127, 166, 57, 181, 184, 235, 72,
+ 189, 154, 85,
+ ]),
+ CompressedDecaf([
+ 230, 180, 184, 244, 8, 199, 1, 13, 6, 1, 231, 237, 160, 195, 9, 161, 164, 39, 32,
+ 214, 208, 107, 87, 89, 253, 196, 225, 239, 226, 45, 7, 109, 108, 68, 212, 47, 80,
+ 141, 103, 190, 70, 41, 20, 210, 139, 142, 220, 227, 46, 112, 148, 48, 81, 100, 175,
+ 23,
+ ]),
+ CompressedDecaf([
+ 190, 136, 187, 184, 108, 89, 193, 61, 142, 157, 9, 171, 152, 16, 95, 105, 194, 209,
+ 221, 19, 77, 188, 211, 176, 134, 54, 88, 245, 49, 89, 219, 100, 192, 225, 57, 209,
+ 128, 243, 200, 155, 130, 150, 208, 174, 50, 68, 25, 192, 111, 168, 127, 199, 218,
+ 175, 52, 193,
+ ]),
+ CompressedDecaf([
+ 164, 86, 249, 54, 151, 105, 232, 240, 137, 2, 18, 74, 3, 20, 199, 160, 101, 55,
+ 160, 110, 50, 65, 31, 79, 147, 65, 89, 80, 161, 123, 173, 250, 116, 66, 182, 33,
+ 116, 52, 163, 160, 94, 244, 91, 229, 241, 11, 215, 178, 239, 142, 160, 12, 67, 30,
+ 222, 197,
+ ]),
+ CompressedDecaf([
+ 24, 110, 69, 44, 68, 102, 170, 67, 131, 180, 192, 2, 16, 213, 46, 121, 34, 219,
+ 249, 119, 30, 139, 71, 226, 41, 169, 183, 183, 60, 141, 16, 253, 126, 240, 182,
+ 228, 21, 48, 249, 31, 36, 163, 237, 154, 183, 31, 163, 139, 152, 178, 254, 71, 70,
+ 213, 29, 104,
+ ]),
+ CompressedDecaf([
+ 74, 231, 253, 202, 233, 69, 63, 25, 90, 142, 173, 92, 190, 26, 123, 150, 153, 103,
+ 59, 82, 196, 10, 178, 121, 39, 70, 72, 135, 190, 83, 35, 127, 127, 58, 33, 185, 56,
+ 212, 13, 14, 201, 225, 91, 29, 81, 48, 177, 63, 254, 216, 19, 115, 165, 62, 43, 67,
+ ]),
+ CompressedDecaf([
+ 132, 25, 129, 195, 191, 238, 195, 246, 12, 254, 202, 117, 217, 216, 220, 23, 244,
+ 108, 240, 16, 111, 36, 34, 181, 154, 236, 88, 10, 88, 243, 66, 39, 46, 58, 94, 87,
+ 90, 5, 93, 219, 5, 19, 144, 197, 76, 36, 198, 236, 177, 224, 172, 235, 7, 95, 96,
+ 86,
+ ]),
+ ];
+ let mut point = DecafPoint::IDENTITY;
+ let generator = DecafPoint::GENERATOR;
+ for compressed_point in compressed.iter() {
+ assert_eq!(&point.compress(), compressed_point);
+ point = &point + &generator;
+ let decompressed_point = compressed_point.decompress();
+ assert_eq!(decompressed_point.is_some().unwrap_u8(), 1u8);
+ }
+ }
+
+ #[test]
+ fn test_invalid_point() {
+ // Test that the identity point is not on the curve
+ let all_ones = CompressedDecaf([1u8; 56]);
+ assert_eq!(all_ones.decompress().is_none().unwrap_u8(), 1u8);
+ let all_twos = CompressedDecaf([2u8; 56]);
+ assert_eq!(all_twos.decompress().is_none().unwrap_u8(), 1u8);
+ }
+
+ #[test]
+ fn test_hash_to_curve() {
+ use elliptic_curve::hash2curve::ExpandMsgXof;
+
+ let msg = b"Hello, world!";
+ let point = DecafPoint::hash::>(msg, b"test_hash_to_curve");
+ assert_eq!(point.0.is_on_curve().unwrap_u8(), 1u8);
+ assert_ne!(point, DecafPoint::IDENTITY);
+ assert_ne!(point, DecafPoint::GENERATOR);
+ }
+
+ // TODO: uncomment once elliptic-curve-tools is updated to match elliptic-curve 0.14
+ // #[test]
+ // fn test_sum_of_products() { use elliptic_curve_tools::SumOfProducts; let values = [ (Scalar::from(8u8), DecafPoint::GENERATOR), (Scalar::from(9u8), DecafPoint::GENERATOR), (Scalar::from(10u8), DecafPoint::GENERATOR), (Scalar::from(11u8), DecafPoint::GENERATOR), (Scalar::from(12u8), DecafPoint::GENERATOR), ]; let expected = DecafPoint::GENERATOR * Scalar::from(50u8); let result = DecafPoint::sum_of_products(&values); assert_eq!(result, expected); }
+ //
+ // #[test]
+ // fn test_sum_of_products2() {
+ // use elliptic_curve_tools::SumOfProducts;
+ // use rand_core::SeedableRng;
+ //
+ // const TESTS: usize = 5;
+ // const CHUNKS: usize = 10;
+ // let mut rng = rand_chacha::ChaCha8Rng::from_seed([3u8; 32]);
+ //
+ // for _ in 0..TESTS {
+ // let scalars = (0..CHUNKS)
+ // .map(|_| Scalar::random(&mut rng))
+ // .collect::>();
+ // let points = (0..CHUNKS)
+ // .map(|_| DecafPoint::random(&mut rng))
+ // .collect::>();
+ //
+ // let input = scalars
+ // .iter()
+ // .zip(points.iter())
+ // .map(|(&s, &p)| (s, p))
+ // .collect::>();
+ // let rhs = DecafPoint::sum_of_products(&input);
+ //
+ // let expected = points
+ // .iter()
+ // .zip(scalars.iter())
+ // .fold(DecafPoint::IDENTITY, |acc, (&p, &s)| acc + (p * s));
+ //
+ // assert_eq!(rhs, expected);
+ // }
+ // }
+}
diff --git a/ed448/src/field.rs b/ed448/src/field.rs
new file mode 100644
index 000000000..e09c9602d
--- /dev/null
+++ b/ed448/src/field.rs
@@ -0,0 +1,31 @@
+mod element;
+mod scalar;
+
+pub(crate) use element::*;
+pub use scalar::{Scalar, ScalarBytes, WideScalarBytes, MODULUS_LIMBS, ORDER, WIDE_ORDER};
+
+use crate::curve::edwards::EdwardsPoint;
+use crate::curve::twedwards::extended::ExtendedPoint as TwExtendedPoint;
+
+use elliptic_curve::bigint::{
+ impl_modulus,
+ modular::{ConstMontyForm, ConstMontyParams},
+ U448,
+};
+
+impl_modulus!(MODULUS, U448, "fffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
+pub(crate) type ConstMontyType = ConstMontyForm;
+
+pub const GOLDILOCKS_BASE_POINT: EdwardsPoint = EdwardsPoint {
+ X: FieldElement(ConstMontyType::new(&U448::from_be_hex("4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e"))),
+ Y: FieldElement(ConstMontyType::new(&U448::from_be_hex("693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14"))),
+ Z: FieldElement::ONE,
+ T: FieldElement(ConstMontyType::new(&U448::from_be_hex("c75eb58aee221c6ccec39d2d508d91c9c5056a183f8451d260d71667e2356d58f179de90b5b27da1f78fa07d85662d1deb06624e82af95f3"))),
+};
+
+pub const TWISTED_EDWARDS_BASE_POINT: TwExtendedPoint = TwExtendedPoint {
+ X: FieldElement(ConstMontyType::new(&U448::from_be_hex("7ffffffffffffffffffffffffffffffffffffffffffffffffffffffe80000000000000000000000000000000000000000000000000000000"))),
+ Y: FieldElement(ConstMontyType::new(&U448::from_be_hex("8508de14f04286d48d06c13078ca240805264370504c74c393d5242c5045271414181844d73f48e5199b0c1e3ab470a1c86079b4dfdd4a64"))),
+ Z: FieldElement::ONE,
+ T: FieldElement(ConstMontyType::new(&U448::from_be_hex("6d3669e173c6a450e23d5682a9ffe1ddc2b86da60f794be956382384a319b57519c9854dde98e342140362071833f4e093e3c816dc198105"))),
+};
diff --git a/ed448/src/field/element.rs b/ed448/src/field/element.rs
new file mode 100644
index 000000000..f5fcbc7a3
--- /dev/null
+++ b/ed448/src/field/element.rs
@@ -0,0 +1,474 @@
+use core::fmt::{self, Debug, Display, Formatter, LowerHex, UpperHex};
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use elliptic_curve::{
+ array::Array,
+ bigint::{
+ consts::{U84, U88},
+ NonZero, U448, U704,
+ },
+ hash2curve::{FromOkm, MapToCurve},
+};
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
+
+#[cfg(feature = "zeroize")]
+use zeroize::DefaultIsZeroes;
+
+use super::ConstMontyType;
+use crate::curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint;
+use crate::{AffinePoint, EdwardsPoint};
+
+#[derive(Clone, Copy, Default)]
+pub(crate) struct FieldElement(pub(crate) ConstMontyType);
+
+impl Display for FieldElement {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{:x}", self.0.retrieve())
+ }
+}
+
+impl Debug for FieldElement {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "FieldElement({:x})", self.0.retrieve())
+ }
+}
+
+impl LowerHex for FieldElement {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{:x}", self.0.retrieve())
+ }
+}
+
+impl UpperHex for FieldElement {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{:X}", self.0.retrieve())
+ }
+}
+
+impl ConstantTimeEq for FieldElement {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.0.ct_eq(&other.0)
+ }
+}
+
+impl ConditionallySelectable for FieldElement {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ Self(ConstMontyType::conditional_select(&a.0, &b.0, choice))
+ }
+}
+
+impl PartialEq for FieldElement {
+ fn eq(&self, other: &FieldElement) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+impl Eq for FieldElement {}
+
+impl FromOkm for FieldElement {
+ type Length = U84;
+
+ fn from_okm(data: &Array) -> Self {
+ const SEMI_WIDE_MODULUS: NonZero = NonZero::::new_unwrap(U704::from_be_hex("0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
+ let mut tmp = Array::::default();
+ tmp[4..].copy_from_slice(&data[..]);
+
+ let mut num = U704::from_be_slice(&tmp[..]);
+ num %= SEMI_WIDE_MODULUS;
+
+ let bytes =
+ <[u8; 56]>::try_from(&num.to_le_bytes()[..56]).expect("slice is the wrong length");
+ FieldElement(ConstMontyType::new(&U448::from_le_slice(&bytes)))
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl DefaultIsZeroes for FieldElement {}
+
+impl Add<&FieldElement> for &FieldElement {
+ type Output = FieldElement;
+
+ fn add(self, other: &FieldElement) -> FieldElement {
+ FieldElement(self.0.add(&other.0))
+ }
+}
+
+define_add_variants!(
+ LHS = FieldElement,
+ RHS = FieldElement,
+ Output = FieldElement
+);
+
+impl AddAssign for FieldElement {
+ fn add_assign(&mut self, other: FieldElement) {
+ *self = *self + other;
+ }
+}
+
+impl AddAssign<&FieldElement> for FieldElement {
+ fn add_assign(&mut self, other: &FieldElement) {
+ *self = *self + *other;
+ }
+}
+
+impl Sub<&FieldElement> for &FieldElement {
+ type Output = FieldElement;
+
+ fn sub(self, other: &FieldElement) -> FieldElement {
+ FieldElement(self.0.sub(&other.0))
+ }
+}
+
+define_sub_variants!(
+ LHS = FieldElement,
+ RHS = FieldElement,
+ Output = FieldElement
+);
+
+impl SubAssign for FieldElement {
+ fn sub_assign(&mut self, other: FieldElement) {
+ *self = *self - other;
+ }
+}
+
+impl SubAssign<&FieldElement> for FieldElement {
+ fn sub_assign(&mut self, other: &FieldElement) {
+ *self = *self - *other;
+ }
+}
+
+impl Mul<&FieldElement> for &FieldElement {
+ type Output = FieldElement;
+
+ fn mul(self, other: &FieldElement) -> FieldElement {
+ FieldElement(self.0.mul(&other.0))
+ }
+}
+
+define_mul_variants!(
+ LHS = FieldElement,
+ RHS = FieldElement,
+ Output = FieldElement
+);
+
+impl MulAssign<&FieldElement> for FieldElement {
+ fn mul_assign(&mut self, other: &FieldElement) {
+ *self = *self * *other;
+ }
+}
+
+impl MulAssign for FieldElement {
+ fn mul_assign(&mut self, other: FieldElement) {
+ *self = *self * other;
+ }
+}
+
+impl Neg for &FieldElement {
+ type Output = FieldElement;
+
+ fn neg(self) -> FieldElement {
+ -*self
+ }
+}
+
+impl Neg for FieldElement {
+ type Output = FieldElement;
+
+ fn neg(self) -> FieldElement {
+ Self(self.0.neg())
+ }
+}
+
+impl MapToCurve for FieldElement {
+ type Output = EdwardsPoint;
+
+ fn map_to_curve(&self) -> Self::Output {
+ self.map_to_curve_elligator2().to_edwards()
+ }
+}
+
+impl FieldElement {
+ pub const A_PLUS_TWO_OVER_FOUR: Self = Self(ConstMontyType::new(&U448::from_be_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000098aa")));
+ pub const DECAF_FACTOR: Self = Self(ConstMontyType::new(&U448::from_be_hex("22d962fbeb24f7683bf68d722fa26aa0a1f1a7b8a5b8d54b64a2d780968c14ba839a66f4fd6eded260337bf6aa20ce529642ef0f45572736")));
+ pub const EDWARDS_D: Self = Self(ConstMontyType::new(&U448::from_be_hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756")));
+ pub const J: Self = Self(ConstMontyType::new(&U448::from_u64(156326)));
+ pub const MINUS_ONE: Self = Self(ConstMontyType::new(&U448::from_be_hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffe")));
+ pub const NEG_EDWARDS_D: Self = Self(ConstMontyType::new(&U448::from_be_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000098a9")));
+ pub const NEG_FOUR_TIMES_TWISTED_D: Self = Self(ConstMontyType::new(&U448::from_be_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a8")));
+ pub const ONE: Self = Self(ConstMontyType::new(&U448::ONE));
+ pub const TWISTED_D: Self = Self(ConstMontyType::new(&U448::from_be_hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6755")));
+ pub const TWO_TIMES_TWISTED_D: Self = Self(ConstMontyType::new(&U448::from_be_hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffeceab")));
+ pub const Z: Self = Self(ConstMontyType::new(&U448::from_be_hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffe")));
+ pub const ZERO: Self = Self(ConstMontyType::new(&U448::ZERO));
+
+ pub fn is_negative(&self) -> Choice {
+ let bytes = self.to_bytes();
+ (bytes[0] & 1).into()
+ }
+
+ /// Inverts a field element
+ /// Previous chain length: 462, new length 460
+ pub fn invert(&self) -> Self {
+ const INV_EXP: U448 = U448::from_be_hex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffd");
+ Self(self.0.pow(&INV_EXP))
+ }
+
+ pub fn square(&self) -> Self {
+ Self(self.0.square())
+ }
+
+ /// Squares a field element `n` times
+ fn square_n(&self, mut n: u32) -> FieldElement {
+ let mut result = self.square();
+
+ // Decrease value by 1 since we just did a squaring
+ n -= 1;
+
+ for _ in 0..n {
+ result = result.square();
+ }
+
+ result
+ }
+
+ pub fn is_square(&self) -> Choice {
+ const IS_SQUARE_EXP: U448 = U448::from_le_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7f");
+ self.0.pow(&IS_SQUARE_EXP).ct_eq(&FieldElement::ONE.0)
+ }
+
+ pub fn sqrt(&self) -> FieldElement {
+ const SQRT_EXP: U448 = U448::from_be_hex("3fffffffffffffffffffffffffffffffffffffffffffffffffffffffc0000000000000000000000000000000000000000000000000000000");
+ Self(self.0.pow(&SQRT_EXP))
+ }
+
+ pub fn to_bytes(self) -> [u8; 56] {
+ let mut bytes = [0u8; 56];
+ bytes.copy_from_slice(&self.0.retrieve().to_le_bytes()[..56]);
+ bytes
+ }
+
+ pub fn to_bytes_extended(self) -> [u8; 57] {
+ let mut bytes = [0u8; 57];
+ bytes[..56].copy_from_slice(&self.to_bytes());
+ bytes
+ }
+
+ pub fn from_bytes(bytes: &[u8; 56]) -> Self {
+ Self(ConstMontyType::new(&U448::from_le_slice(bytes)))
+ }
+
+ pub fn double(&self) -> Self {
+ Self(self.0.add(&self.0))
+ }
+
+ /// Computes the inverse square root of a field element
+ /// Returns the result and a boolean to indicate whether self
+ /// was a Quadratic residue
+ pub(crate) fn inverse_square_root(&self) -> (FieldElement, Choice) {
+ let (mut l0, mut l1, mut l2);
+
+ l1 = self.square();
+ l2 = l1 * self;
+ l1 = l2.square();
+ l2 = l1 * self;
+ l1 = l2.square_n(3);
+ l0 = l2 * l1;
+ l1 = l0.square_n(3);
+ l0 = l2 * l1;
+ l2 = l0.square_n(9);
+ l1 = l0 * l2;
+ l0 = l1 * l1;
+ l2 = l0 * self;
+ l0 = l2.square_n(18);
+ l2 = l1 * l0;
+ l0 = l2.square_n(37);
+ l1 = l2 * l0;
+ l0 = l1.square_n(37);
+ l1 = l2 * l0;
+ l0 = l1.square_n(111);
+ l2 = l1 * l0;
+ l0 = l2.square();
+ l1 = l0 * self;
+ l0 = l1.square_n(223);
+ l1 = l2 * l0;
+ l2 = l1.square();
+ l0 = l2 * self;
+
+ let is_residue = l0.ct_eq(&FieldElement::ONE);
+ (l1, is_residue)
+ }
+
+ /// Computes the square root ratio of two elements
+ pub(crate) fn sqrt_ratio(u: &FieldElement, v: &FieldElement) -> (FieldElement, Choice) {
+ // Compute sqrt(1/(uv))
+ let x = *u * v;
+ let (inv_sqrt_x, is_res) = x.inverse_square_root();
+ // Return u * sqrt(1/(uv)) == sqrt(u/v). However, since this trick only works
+ // for u != 0, check for that case explicitly (when u == 0 then inv_sqrt_x
+ // will be zero, which is what we want, but is_res will be 0)
+ let zero_u = u.ct_eq(&FieldElement::ZERO);
+ (inv_sqrt_x * u, zero_u | is_res)
+ }
+
+ /// Computes the square root ratio of two elements
+ ///
+ /// The difference between this and `sqrt_ratio` is that
+ /// if the input is non-square, the function returns a result with
+ /// a defined relationship to the inputs.
+ pub(crate) fn sqrt_ratio_i(u: &FieldElement, v: &FieldElement) -> (FieldElement, Choice) {
+ const P_MINUS_THREE_DIV_4: U448 = U448::from_be_hex("3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffffffffffffffffffffffffffffffffffffffffff");
+ let u = u.0;
+ let v = v.0;
+
+ let r = u * (u * v).pow(&P_MINUS_THREE_DIV_4);
+ let check = v * r.square();
+ let was_square = check.ct_eq(&u);
+
+ let mut r = FieldElement(r);
+ r.conditional_negate(r.is_negative());
+ (r, was_square)
+ }
+
+ pub(crate) fn map_to_curve_elligator2(&self) -> AffinePoint {
+ let mut t1 = self.square(); // 1. t1 = u^2
+ t1 *= Self::Z; // 2. t1 = Z * t1 // Z * u^2
+ let e1 = t1.ct_eq(&Self::MINUS_ONE); // 3. e1 = t1 == -1 // exceptional case: Z * u^2 == -1
+ t1.conditional_assign(&Self::ZERO, e1); // 4. t1 = CMOV(t1, 0, e1) // if t1 == -1, set t1 = 0
+ let mut x1 = t1 + Self::ONE; // 5. x1 = t1 + 1
+ x1 = x1.invert(); // 6. x1 = inv0(x1)
+ x1 *= -Self::J; // 7. x1 = -A * x1 // x1 = -A / (1 + Z * u^2)
+ let mut gx1 = x1 + Self::J; // 8. gx1 = x1 + A
+ gx1 *= x1; // 9. gx1 = gx1 * x1
+ gx1 += Self::ONE; // 10. gx1 = gx1 + B
+ gx1 *= x1; // 11. gx1 = gx1 * x1 // gx1 = x1^3 + A * x1^2 + B * x1
+ let x2 = -x1 - Self::J; // 12. x2 = -x1 - A
+ let gx2 = t1 * gx1; // 13. gx2 = t1 * gx1
+ let e2 = gx1.is_square(); // 14. e2 = is_square(gx1)
+ let x = Self::conditional_select(&x2, &x1, e2); // 15. x = CMOV(x2, x1, e2) // If is_square(gx1), x = x1, else x = x2
+ let y2 = Self::conditional_select(&gx2, &gx1, e2); // 16. y2 = CMOV(gx2, gx1, e2) // If is_square(gx1), y2 = gx1, else y2 = gx2
+ let mut y = y2.sqrt(); // 17. y = sqrt(y2)
+ let e3 = y.is_negative(); // 18. e3 = sgn0(y) == 1
+ y.conditional_negate(e2 ^ e3); // y = CMOV(-y, y, e2 xor e3)
+ AffinePoint { x, y }
+ }
+
+ pub(crate) fn map_to_curve_decaf448(&self) -> TwistedExtendedPoint {
+ const ONE_MINUS_TWO_D: FieldElement =
+ FieldElement(ConstMontyType::new(&U448::from_u64(78163)));
+
+ let r = -self.square();
+ let u0 = Self::EDWARDS_D * (r - Self::ONE);
+ let u1 = (u0 + Self::ONE) * (u0 - r);
+
+ let rhs = (r + Self::ONE) * u1;
+ let (v, was_square) = Self::sqrt_ratio_i(&ONE_MINUS_TWO_D, &rhs);
+
+ let mut v_prime = self * v;
+ v_prime.conditional_assign(&v, was_square);
+ let mut sgn = Self::MINUS_ONE;
+ sgn.conditional_negate(was_square);
+
+ let s = v_prime * (r + Self::ONE);
+ let s2 = s.square();
+ let s_abs = Self::conditional_select(&s, &s.neg(), s.is_negative());
+
+ let w0 = s_abs + s_abs;
+ let w1 = s2 + Self::ONE;
+ let w2 = s2 - Self::ONE;
+ let w3 = v_prime * s * (r - Self::ONE) * ONE_MINUS_TWO_D + sgn;
+
+ EdwardsPoint {
+ X: w0 * w3,
+ Y: w2 * w1,
+ Z: w1 * w3,
+ T: w0 * w2,
+ }
+ .to_twisted()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXof, Expander};
+ use hex_literal::hex;
+ use sha3::Shake256;
+
+ #[test]
+ fn from_okm_curve448() {
+ const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_RO_";
+ const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
+ (b"", hex!("c704c7b3d3b36614cf3eedd0324fe6fe7d1402c50efd16cff89ff63f50938506280d3843478c08e24f7842f4e3ef45f6e3c4897f9d976148"), hex!("c25427dc97fff7a5ad0a78654e2c6c27b1c1127b5b53c7950cd1fd6edd2703646b25f341e73deedfebf022d1d3cecd02b93b4d585ead3ed7")),
+ (b"abc", hex!("2dd95593dfee26fe0d218d3d9a0a23d9e1a262fd1d0b602483d08415213e75e2db3c69b0a5bc89e71bcefc8c723d2b6a0cf263f02ad2aa70"), hex!("272e4c79a1290cc6d2bc4f4f9d31bf7fbe956ca303c04518f117d77c0e9d850796fc3e1e2bcb9c75e8eaaded5e150333cae9931868047c9d")),
+ (b"abcdef0123456789", hex!("6aab71a38391639f27e49eae8b1cb6b7172a1f478190ece293957e7cdb2391e7cc1c4261970d9c1bbf9c3915438f74fbd7eb5cd4d4d17ace"), hex!("c80b8380ca47a3bcbf76caa75cef0e09f3d270d5ee8f676cde11aedf41aaca6741bd81a86232bd336ccb42efad39f06542bc06a67b65909e")),
+ (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("cb5c27e51f9c18ee8ffdb6be230f4eb4f2c2481963b2293484f08da2241c1ff59f80978e6defe9d70e34abba2fcbe12dc3a1eb2c5d3d2e4a"), hex!("c895e8afecec5466e126fa70fc4aa784b8009063afb10e3ee06a9b22318256aa8693b0c85b955cf2d6540b8ed71e729af1b8d5ca3b116cd7")),
+ (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("8cba93a007bb2c801b1769e026b1fa1640b14a34cf3029db3c7fd6392745d6fec0f7870b5071d6da4402cedbbde28ae4e50ab30e1049a238"), hex!("4223746145069e4b8a981acc3404259d1a2c3ecfed5d864798a89d45f81a2c59e2d40eb1d5f0fe11478cbb2bb30246dd388cb932ad7bb330")),
+ ];
+
+ for (msg, expected_u0, expected_u1) in MSGS {
+ let mut expander =
+ ExpandMsgXof::::expand_message(&[msg], &[DST], 84 * 2).unwrap();
+ let mut data = Array::::default();
+ expander.fill_bytes(&mut data);
+ let u0 = FieldElement::from_okm(&data);
+ let mut e_u0 = *expected_u0;
+ e_u0.reverse();
+ let mut e_u1 = *expected_u1;
+ e_u1.reverse();
+ assert_eq!(u0.to_bytes(), e_u0);
+ expander.fill_bytes(&mut data);
+ let u1 = FieldElement::from_okm(&data);
+ assert_eq!(u1.to_bytes(), e_u1);
+ }
+ }
+
+ #[test]
+ fn from_okm_edwards448() {
+ const DST: &[u8] = b"QUUX-V01-CS02-with-edwards448_XOF:SHAKE256_ELL2_RO_";
+ const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
+ (b"", hex!("0847c5ebf957d3370b1f98fde499fb3e659996d9fc9b5707176ade785ba72cd84b8a5597c12b1024be5f510fa5ba99642c4cec7f3f69d3e7"), hex!("f8cbd8a7ae8c8deed071f3ac4b93e7cfcb8f1eac1645d699fd6d3881cb295a5d3006d9449ed7cad412a77a1fe61e84a9e41d59ef384d6f9a")),
+ (b"abc", hex!("04d975cd938ab49be3e81703d6a57cca84ed80d2ff6d4756d3f22947fb5b70ab0231f0087cbfb4b7cae73b41b0c9396b356a4831d9a14322"), hex!("2547ca887ac3db7b5fad3a098aa476e90078afe1358af6c63d677d6edfd2100bc004e0f5db94dd2560fc5b308e223241d00488c9ca6b0ef2")),
+ (b"abcdef0123456789", hex!("10659ce25588db4e4be6f7c791a79eb21a7f24aaaca76a6ca3b83b80aaf95aa328fe7d569a1ac99f9cd216edf3915d72632f1a8b990e250c"), hex!("9243e5b6c480683fd533e81f4a778349a309ce00bd163a29eb9fa8dbc8f549242bef33e030db21cffacd408d2c4264b93e476c6a8590e7aa")),
+ (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("c80390020e578f009ead417029eff6cd0926110922db63ab98395e3bdfdd5d8a65b1a2b8d495dc8c5e59b7f3518731f7dfc0f93ace5dee4b"), hex!("1c4dc6653a445bbef2add81d8e90a6c8591a788deb91d0d3f1519a2e4a460313041b77c1b0817f2e80b388e5c3e49f37d787dc1f85e4324a")),
+ (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("163c79ab0210a4b5e4f44fb19437ea965bf5431ab233ef16606f0b03c5f16a3feb7d46a5a675ce8f606e9c2bf74ee5336c54a1e54919f13f"), hex!("f99666bde4995c4088333d6c2734687e815f80a99c6da02c47df4b51f6c9d9ed466b4fecf7d9884990a8e0d0be6907fa437e0b1a27f49265")),
+ ];
+
+ for (msg, expected_u0, expected_u1) in MSGS {
+ let mut expander =
+ ExpandMsgXof::::expand_message(&[msg], &[DST], 84 * 2).unwrap();
+ let mut data = Array::::default();
+ expander.fill_bytes(&mut data);
+ let u0 = FieldElement::from_okm(&data);
+ let mut e_u0 = *expected_u0;
+ e_u0.reverse();
+ let mut e_u1 = *expected_u1;
+ e_u1.reverse();
+ assert_eq!(u0.to_bytes(), e_u0);
+ expander.fill_bytes(&mut data);
+ let u1 = FieldElement::from_okm(&data);
+ assert_eq!(u1.to_bytes(), e_u1);
+ }
+ }
+
+ #[test]
+ fn get_constants() {
+ let m1 = -FieldElement::ONE;
+ assert_eq!(m1, FieldElement::MINUS_ONE);
+ }
+
+ #[test]
+ fn sqrt() {
+ let nine = FieldElement::from_bytes(&[
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ]);
+ let three = FieldElement::from_bytes(&[
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ]);
+ assert_eq!(three, nine.sqrt());
+ }
+}
diff --git a/ed448/src/field/scalar.rs b/ed448/src/field/scalar.rs
new file mode 100644
index 000000000..c0d5fe446
--- /dev/null
+++ b/ed448/src/field/scalar.rs
@@ -0,0 +1,1106 @@
+use crate::*;
+
+use core::fmt::{Display, Formatter, Result as FmtResult};
+use core::iter::{Product, Sum};
+use core::ops::{
+ Add, AddAssign, Index, IndexMut, Mul, MulAssign, Neg, Shr, ShrAssign, Sub, SubAssign,
+};
+use crypto_bigint::Zero;
+use elliptic_curve::{
+ array::{
+ typenum::{U114, U57, U84, U88},
+ Array,
+ },
+ bigint::{Limb, NonZero, U448, U704, U896},
+ ff::{helpers, Field, FieldBits, PrimeFieldBits},
+ hash2curve::{ExpandMsg, Expander, FromOkm},
+ ops::{Invert, Reduce, ReduceNonZero},
+ scalar::{FromUintUnchecked, IsHigh, ScalarPrimitive},
+ PrimeField,
+};
+use rand_core::{CryptoRng, RngCore, TryRngCore};
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption};
+
+/// This is the scalar field
+/// size = 4q = 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
+/// We can therefore use 14 saturated 32-bit limbs
+#[derive(Debug, Copy, Clone, PartialOrd, Ord)]
+pub struct Scalar(pub(crate) U448);
+
+/// The number of bytes needed to represent the scalar field
+pub type ScalarBytes = Array;
+/// The number of bytes needed to represent the safely create a scalar from a random bytes
+pub type WideScalarBytes = Array;
+
+/// The order of the scalar field
+pub const ORDER: U448 = U448::from_be_hex("3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3");
+pub const NZ_ORDER: NonZero = NonZero::::new_unwrap(ORDER);
+const ORDER_MINUS_ONE: U448 = ORDER.wrapping_sub(&U448::ONE);
+const HALF_ORDER: U448 = ORDER.shr_vartime(1);
+/// The wide order of the scalar field
+pub const WIDE_ORDER: U896 = U896::from_be_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3");
+const WIDE_ORDER_MINUS_ONE: U896 = WIDE_ORDER.wrapping_sub(&U896::ONE);
+
+/// The modulus of the scalar field as a sequence of 14 32-bit limbs
+pub const MODULUS_LIMBS: [u32; 14] = [
+ 0xab5844f3, 0x2378c292, 0x8dc58f55, 0x216cc272, 0xaed63690, 0xc44edb49, 0x7cca23e9, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x3fffffff,
+];
+
+impl Display for Scalar {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+ let bytes = self.to_bytes_rfc_8032();
+ for b in &bytes {
+ write!(f, "{:02x}", b)?;
+ }
+ Ok(())
+ }
+}
+
+impl ConstantTimeEq for Scalar {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ self.to_bytes().ct_eq(&other.to_bytes())
+ }
+}
+
+impl ConditionallySelectable for Scalar {
+ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+ Self(U448::conditional_select(&a.0, &b.0, choice))
+ }
+}
+
+impl PartialEq for Scalar {
+ fn eq(&self, other: &Scalar) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for Scalar {}
+
+impl From for Scalar {
+ fn from(a: u8) -> Self {
+ Scalar(U448::from_u8(a))
+ }
+}
+
+impl From for Scalar {
+ fn from(a: u16) -> Self {
+ Scalar(U448::from_u16(a))
+ }
+}
+
+impl From for Scalar {
+ fn from(a: u32) -> Scalar {
+ Scalar(U448::from_u32(a))
+ }
+}
+
+impl From for Scalar {
+ fn from(a: u64) -> Self {
+ Scalar(U448::from_u64(a))
+ }
+}
+
+impl From for Scalar {
+ fn from(a: u128) -> Self {
+ Scalar(U448::from_u128(a))
+ }
+}
+
+impl Index for Scalar {
+ type Output = crypto_bigint::Word;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.0.as_words()[index]
+ }
+}
+
+impl IndexMut for Scalar {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ &mut self.0.as_words_mut()[index]
+ }
+}
+
+// Trait implementations
+impl Add<&Scalar> for &Scalar {
+ type Output = Scalar;
+
+ fn add(self, rhs: &Scalar) -> Self::Output {
+ self.addition(rhs)
+ }
+}
+
+define_add_variants!(LHS = Scalar, RHS = Scalar, Output = Scalar);
+
+impl AddAssign for Scalar {
+ fn add_assign(&mut self, rhs: Self) {
+ *self = *self + rhs
+ }
+}
+
+impl AddAssign<&Scalar> for Scalar {
+ fn add_assign(&mut self, rhs: &Scalar) {
+ *self = *self + rhs
+ }
+}
+
+impl Mul<&Scalar> for &Scalar {
+ type Output = Scalar;
+
+ fn mul(self, rhs: &Scalar) -> Self::Output {
+ self.multiply(rhs)
+ }
+}
+
+define_mul_variants!(LHS = Scalar, RHS = Scalar, Output = Scalar);
+
+impl MulAssign for Scalar {
+ fn mul_assign(&mut self, rhs: Self) {
+ *self = *self * rhs
+ }
+}
+
+impl MulAssign<&Scalar> for Scalar {
+ fn mul_assign(&mut self, rhs: &Scalar) {
+ *self = *self * rhs
+ }
+}
+
+impl Sub<&Scalar> for &Scalar {
+ type Output = Scalar;
+
+ fn sub(self, rhs: &Scalar) -> Self::Output {
+ self.subtract(rhs)
+ }
+}
+
+define_sub_variants!(LHS = Scalar, RHS = Scalar, Output = Scalar);
+
+impl SubAssign for Scalar {
+ fn sub_assign(&mut self, rhs: Self) {
+ *self = *self - rhs
+ }
+}
+
+impl SubAssign<&Scalar> for Scalar {
+ fn sub_assign(&mut self, rhs: &Scalar) {
+ *self = *self - rhs
+ }
+}
+
+impl Neg for Scalar {
+ type Output = Scalar;
+
+ fn neg(self) -> Self::Output {
+ -&self
+ }
+}
+
+impl Neg for &Scalar {
+ type Output = Scalar;
+
+ fn neg(self) -> Self::Output {
+ Scalar::ZERO - self
+ }
+}
+
+impl Default for Scalar {
+ fn default() -> Scalar {
+ Scalar::ZERO
+ }
+}
+
+impl Sum for Scalar {
+ fn sum>(iter: I) -> Self {
+ let mut acc = Scalar::ZERO;
+ for s in iter {
+ acc += s;
+ }
+ acc
+ }
+}
+
+impl<'a> Sum<&'a Scalar> for Scalar {
+ fn sum>(iter: I) -> Self {
+ let mut acc = Scalar::ZERO;
+ for s in iter {
+ acc += s;
+ }
+ acc
+ }
+}
+
+impl Product for Scalar {
+ fn product>(iter: I) -> Self {
+ let mut acc = Scalar::ONE;
+ for s in iter {
+ acc *= s;
+ }
+ acc
+ }
+}
+
+impl<'a> Product<&'a Scalar> for Scalar {
+ fn product>(iter: I) -> Self {
+ let mut acc = Scalar::ONE;
+ for s in iter {
+ acc *= s;
+ }
+ acc
+ }
+}
+
+impl Field for Scalar {
+ const ZERO: Self = Self::ZERO;
+ const ONE: Self = Self::ONE;
+
+ fn try_from_rng(rng: &mut R) -> Result {
+ let mut seed = WideScalarBytes::default();
+ rng.try_fill_bytes(&mut seed)?;
+ Ok(Scalar::from_bytes_mod_order_wide(&seed))
+ }
+
+ fn square(&self) -> Self {
+ self.square()
+ }
+
+ fn double(&self) -> Self {
+ self + self
+ }
+
+ fn invert(&self) -> CtOption {
+ CtOption::new(self.invert(), !self.ct_eq(&Self::ZERO))
+ }
+
+ fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
+ helpers::sqrt_ratio_generic(num, div)
+ }
+}
+
+impl PrimeField for Scalar {
+ type Repr = ScalarBytes;
+
+ fn from_repr(repr: Self::Repr) -> CtOption {
+ Self::from_canonical_bytes(&repr)
+ }
+ fn to_repr(&self) -> Self::Repr {
+ self.to_bytes_rfc_8032()
+ }
+ fn is_odd(&self) -> Choice {
+ Choice::from((self.0.to_words()[0] & 1) as u8)
+ }
+ const MODULUS: &'static str = "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3";
+ const NUM_BITS: u32 = 448;
+ const CAPACITY: u32 = Self::NUM_BITS - 1;
+ const TWO_INV: Self = Self(U448::from_be_hex("1fffffffffffffffffffffffffffffffffffffffffffffffffffffffbe6511f4e2276da4d76b1b4810b6613946e2c7aa91bc614955ac227a"));
+ const MULTIPLICATIVE_GENERATOR: Self = Self(U448::from_u8(7));
+ const S: u32 = 1;
+
+ const ROOT_OF_UNITY: Self = Self(U448::from_be_hex("3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f2"));
+
+ const ROOT_OF_UNITY_INV: Self = Self(U448::from_be_hex("3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f2"));
+
+ const DELTA: Self = Self(U448::from_u8(49));
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From for Vec {
+ fn from(scalar: Scalar) -> Vec {
+ Self::from(&scalar)
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl From<&Scalar> for Vec {
+ fn from(scalar: &Scalar) -> Vec {
+ scalar.to_bytes_rfc_8032().to_vec()
+ }
+}
+
+impl From for ScalarBytes {
+ fn from(scalar: Scalar) -> ScalarBytes {
+ Self::from(&scalar)
+ }
+}
+
+impl From<&Scalar> for ScalarBytes {
+ fn from(scalar: &Scalar) -> ScalarBytes {
+ scalar.to_bytes_rfc_8032()
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for Scalar {
+ type Error = &'static str;
+
+ fn try_from(bytes: Vec) -> Result {
+ Self::try_from(&bytes[..])
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom<&Vec> for Scalar {
+ type Error = &'static str;
+
+ fn try_from(bytes: &Vec) -> Result {
+ Self::try_from(&bytes[..])
+ }
+}
+
+impl TryFrom<&[u8]> for Scalar {
+ type Error = &'static str;
+
+ fn try_from(bytes: &[u8]) -> Result {
+ if bytes.len() != 57 {
+ return Err("invalid byte length");
+ }
+ let scalar_bytes = ScalarBytes::try_from(bytes).expect("invalid scalar bytes");
+ Option::::from(Scalar::from_canonical_bytes(&scalar_bytes))
+ .ok_or("scalar was not canonically encoded")
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std"))]
+impl TryFrom> for Scalar {
+ type Error = &'static str;
+
+ fn try_from(bytes: Box<[u8]>) -> Result {
+ Self::try_from(bytes.as_ref())
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serdect::serde::Serialize for Scalar {
+ fn serialize
(&self, s: S) -> Result
+ where
+ S: serdect::serde::Serializer,
+ {
+ serdect::slice::serialize_hex_lower_or_bin(&self.to_bytes(), s)
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de> serdect::serde::Deserialize<'de> for Scalar {
+ fn deserialize(d: D) -> Result
+ where
+ D: serdect::serde::Deserializer<'de>,
+ {
+ let mut buffer = ScalarBytes::default();
+ serdect::array::deserialize_hex_or_bin(&mut buffer[..56], d)?;
+ Option::from(Self::from_canonical_bytes(&buffer)).ok_or(serdect::serde::de::Error::custom(
+ "scalar was not canonically encoded",
+ ))
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for Scalar {}
+
+impl core::fmt::LowerHex for Scalar {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ let tmp = self.to_bytes_rfc_8032();
+ for &b in tmp.iter() {
+ write!(f, "{:02x}", b)?;
+ }
+ Ok(())
+ }
+}
+
+impl core::fmt::UpperHex for Scalar {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ let tmp = self.to_bytes_rfc_8032();
+ for &b in tmp.iter() {
+ write!(f, "{:02X}", b)?;
+ }
+ Ok(())
+ }
+}
+
+impl FromOkm for Scalar {
+ type Length = U84;
+
+ fn from_okm(data: &Array) -> Self {
+ const SEMI_WIDE_MODULUS: NonZero = NonZero::::new_unwrap(U704::from_be_hex("00000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3"));
+ let mut tmp = Array::::default();
+ tmp[4..].copy_from_slice(&data[..]);
+
+ let mut num = U704::from_be_slice(&tmp[..]);
+ num %= SEMI_WIDE_MODULUS;
+ let mut words = [0; U448::LIMBS];
+ words.copy_from_slice(&num.to_words()[..U448::LIMBS]);
+ Scalar(U448::from_words(words))
+ }
+}
+
+impl Reduce for Scalar {
+ type Bytes = ScalarBytes;
+
+ fn reduce(bytes: U448) -> Self {
+ let (r, underflow) = bytes.sbb(&ORDER, Limb::ZERO);
+ let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8);
+ Self(U448::conditional_select(&bytes, &r, !underflow))
+ }
+
+ fn reduce_bytes(bytes: &Self::Bytes) -> Self {
+ Self::reduce(U448::from_le_slice(bytes))
+ }
+}
+
+impl Reduce for Scalar {
+ type Bytes = WideScalarBytes;
+
+ fn reduce(bytes: U896) -> Self {
+ let (r, underflow) = bytes.sbb(&WIDE_ORDER, Limb::ZERO);
+ let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8);
+ Self(U896::conditional_select(&bytes, &r, !underflow).split().1)
+ }
+
+ fn reduce_bytes(bytes: &Self::Bytes) -> Self {
+ Self::from_bytes_mod_order_wide(bytes)
+ }
+}
+
+impl ReduceNonZero for Scalar {
+ fn reduce_nonzero(bytes: U448) -> Self {
+ let (r, underflow) = bytes.sbb(&ORDER_MINUS_ONE, Limb::ZERO);
+ let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8);
+ Self(U448::conditional_select(&bytes, &r, !underflow).wrapping_add(&U448::ONE))
+ }
+
+ fn reduce_nonzero_bytes(bytes: &Self::Bytes) -> Self {
+ Self::reduce_nonzero(U448::from_le_slice(bytes))
+ }
+}
+
+impl ReduceNonZero for Scalar {
+ fn reduce_nonzero(bytes: U896) -> Self {
+ let (r, underflow) = bytes.sbb(&WIDE_ORDER_MINUS_ONE, Limb::ZERO);
+ let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8);
+
+ Self(
+ U896::conditional_select(&bytes, &r, !underflow)
+ .split()
+ .1
+ .wrapping_add(&U448::ONE),
+ )
+ }
+
+ fn reduce_nonzero_bytes(bytes: &Self::Bytes) -> Self {
+ Self::reduce_nonzero(U896::from_le_slice(bytes))
+ }
+}
+
+impl PrimeFieldBits for Scalar {
+ type ReprBits = [crypto_bigint::Word; U448::LIMBS];
+
+ fn to_le_bits(&self) -> FieldBits {
+ self.0.to_words().into()
+ }
+
+ fn char_le_bits() -> FieldBits {
+ ORDER.to_words().into()
+ }
+}
+
+impl From> for Scalar {
+ fn from(scalar: ScalarPrimitive) -> Self {
+ Self(*scalar.as_uint())
+ }
+}
+
+impl From<&ScalarPrimitive> for Scalar {
+ fn from(scalar: &ScalarPrimitive) -> Self {
+ let uint = *scalar.as_uint();
+ uint.into()
+ }
+}
+
+impl From for ScalarPrimitive {
+ fn from(scalar: Scalar) -> Self {
+ let uint: U448 = scalar.into();
+ Self::from_uint_unchecked(uint)
+ }
+}
+
+impl From<&Scalar> for ScalarPrimitive {
+ fn from(scalar: &Scalar) -> Self {
+ let uint: U448 = scalar.into();
+ ScalarPrimitive::from_uint_unchecked(uint)
+ }
+}
+
+impl From> for Scalar {
+ fn from(scalar: ScalarPrimitive) -> Self {
+ Self(*scalar.as_uint())
+ }
+}
+
+impl From<&ScalarPrimitive> for Scalar {
+ fn from(scalar: &ScalarPrimitive) -> Self {
+ let uint = *scalar.as_uint();
+ uint.into()
+ }
+}
+
+impl From for ScalarPrimitive {
+ fn from(scalar: Scalar) -> Self {
+ let uint: U448 = scalar.into();
+ Self::from_uint_unchecked(uint)
+ }
+}
+
+impl From<&Scalar> for ScalarPrimitive {
+ fn from(scalar: &Scalar) -> Self {
+ let uint: U448 = scalar.into();
+ ScalarPrimitive::from_uint_unchecked(uint)
+ }
+}
+
+impl From for Scalar {
+ fn from(uint: U448) -> Self {
+ >::reduce(uint)
+ }
+}
+
+impl From<&U448> for Scalar {
+ fn from(uint: &U448) -> Self {
+ Self::from(*uint)
+ }
+}
+
+impl From for U448 {
+ fn from(scalar: Scalar) -> Self {
+ scalar.0
+ }
+}
+
+impl From<&Scalar> for U448 {
+ fn from(scalar: &Scalar) -> Self {
+ Self::from(*scalar)
+ }
+}
+
+impl FromUintUnchecked for Scalar {
+ type Uint = U448;
+
+ fn from_uint_unchecked(uint: U448) -> Self {
+ Self(uint)
+ }
+}
+
+impl Invert for Scalar {
+ type Output = CtOption;
+
+ fn invert(&self) -> CtOption {
+ CtOption::new(self.invert(), !self.ct_eq(&Self::ZERO))
+ }
+}
+
+impl IsHigh for Scalar {
+ fn is_high(&self) -> Choice {
+ self.0.ct_gt(&HALF_ORDER)
+ }
+}
+
+impl AsRef for Scalar {
+ fn as_ref(&self) -> &Scalar {
+ self
+ }
+}
+
+impl Shr for Scalar {
+ type Output = Self;
+
+ fn shr(self, rhs: usize) -> Self::Output {
+ let mut cp = self;
+ cp.shr_assign(rhs);
+ cp
+ }
+}
+
+impl Shr for &Scalar {
+ type Output = Scalar;
+
+ fn shr(self, rhs: usize) -> Self::Output {
+ let mut cp = *self;
+ cp.shr_assign(rhs);
+ cp
+ }
+}
+
+impl ShrAssign for Scalar {
+ fn shr_assign(&mut self, shift: usize) {
+ self.0 >>= shift;
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl From<&Scalar> for Ed448ScalarBits {
+ fn from(scalar: &Scalar) -> Self {
+ scalar.0.to_words().into()
+ }
+}
+
+impl Scalar {
+ /// The multiplicative identity element
+ pub const ONE: Scalar = Scalar(U448::ONE);
+ /// Twice the multiplicative identity element
+ pub const TWO: Scalar = Scalar(U448::from_u8(2));
+ /// The additive identity element
+ pub const ZERO: Scalar = Scalar(U448::ZERO);
+
+ /// Compute `self` + `rhs` mod ℓ
+ pub const fn addition(&self, rhs: &Self) -> Self {
+ Self(self.0.add_mod(&rhs.0, &ORDER))
+ }
+
+ /// Compute `self` + `self` mod ℓ
+ pub const fn double(&self) -> Self {
+ self.addition(self)
+ }
+
+ /// Compute `self` - `rhs` mod ℓ
+ pub const fn subtract(&self, rhs: &Self) -> Self {
+ Self(self.0.sub_mod(&rhs.0, &ORDER))
+ }
+
+ /// Compute `self` * `rhs` mod ℓ
+ pub const fn multiply(&self, rhs: &Self) -> Self {
+ let wide_value = self.0.split_mul(&rhs.0);
+ Self(U448::rem_wide_vartime(wide_value, &NZ_ORDER))
+ }
+
+ /// Square this scalar
+ pub const fn square(&self) -> Self {
+ let value = self.0.square_wide();
+ Self(U448::rem_wide_vartime(value, &NZ_ORDER))
+ }
+
+ /// Is this scalar equal to zero?
+ pub fn is_zero(&self) -> Choice {
+ self.0.is_zero()
+ }
+
+ /// Divides a scalar by four without reducing mod p
+ /// This is used in the 2-isogeny when mapping points from Ed448-Goldilocks
+ /// to Twisted-Goldilocks
+ pub(crate) fn div_by_four(&mut self) {
+ self.0 >>= 2;
+ }
+
+ // This method was modified from Curve25519-Dalek codebase. [scalar.rs]
+ // We start with 14 u32s and convert them to 56 u8s.
+ // We then use the code copied from Dalek to convert the 56 u8s to radix-16 and re-center the coefficients to be between [-16,16)
+ // XXX: We can recode the scalar without converting it to bytes, will refactor this method to use this and check which is faster.
+ pub(crate) fn to_radix_16(self) -> [i8; 113] {
+ let bytes = self.to_bytes();
+ let mut output = [0i8; 113];
+
+ // Step 1: change radix.
+ // Convert from radix 256 (bytes) to radix 16 (nibbles)
+ #[inline(always)]
+ fn bot_half(x: u8) -> u8 {
+ x & 15
+ }
+ #[inline(always)]
+ fn top_half(x: u8) -> u8 {
+ (x >> 4) & 15
+ }
+
+ // radix-16
+ for i in 0..56 {
+ output[2 * i] = bot_half(bytes[i]) as i8;
+ output[2 * i + 1] = top_half(bytes[i]) as i8;
+ }
+ // re-center co-efficients to be between [-8, 8)
+ for i in 0..112 {
+ let carry = (output[i] + 8) >> 4;
+ output[i] -= carry << 4;
+ output[i + 1] += carry;
+ }
+
+ output
+ }
+
+ // XXX: Better if this method returns an array of 448 items
+ /// Returns the bits of the scalar in little-endian order.
+ pub fn bits(&self) -> [bool; 448] {
+ let mut bits = [false; 448];
+ let mut i = 0;
+ // We have 56 limbs, each 8 bits
+ // First we iterate each limb
+ for limb in self.to_bytes().iter() {
+ // Then we iterate each bit in the limb
+ for j in 0..8 {
+ bits[i] = limb & (1 << j) != 0;
+ i += 1;
+ }
+ }
+
+ // XXX :We are doing LSB first
+ bits
+ }
+
+ /// Construct a `Scalar` from a little-endian byte representation.
+ pub fn from_bytes(bytes: &[u8; 56]) -> Scalar {
+ Self(U448::from_le_slice(bytes))
+ }
+
+ /// Convert this `Scalar` to a little-endian byte array.
+ pub fn to_bytes(&self) -> [u8; 56] {
+ let bytes = self.0.to_le_bytes();
+ let output: [u8; 56] = core::array::from_fn(|i| bytes[i]);
+ output
+ }
+
+ /// Invert this scalar
+ pub fn invert(&self) -> Self {
+ Self::conditional_select(
+ &self.exp_vartime(&[
+ 0x2378c292ab5844f1,
+ 0x216cc2728dc58f55,
+ 0xc44edb49aed63690,
+ 0xffffffff7cca23e9,
+ 0xffffffffffffffff,
+ 0xffffffffffffffff,
+ 0x3fffffffffffffff,
+ ]),
+ &Self::ZERO,
+ self.is_zero(),
+ )
+ }
+
+ /// Exponentiates `self` by `exp`, where `exp` is a little-endian order integer
+ /// exponent.
+ pub const fn exp_vartime(&self, exp: &[u64]) -> Self {
+ let mut res = Self::ONE;
+
+ let mut i = exp.len();
+ while i > 0 {
+ i -= 1;
+
+ let mut j = 64;
+ while j > 0 {
+ j -= 1;
+ res = res.square();
+
+ if ((exp[i] >> j) & 1) == 1 {
+ res = res.multiply(self);
+ }
+ }
+ }
+
+ res
+ }
+
+ /// Return the square root of this scalar, if it is a quadratic residue.
+ pub fn sqrt(&self) -> CtOption {
+ let ss = self.pow([
+ 0x48de30a4aad6113d,
+ 0x085b309ca37163d5,
+ 0x7113b6d26bb58da4,
+ 0xffffffffdf3288fa,
+ 0xffffffffffffffff,
+ 0xffffffffffffffff,
+ 0x0fffffffffffffff,
+ ]);
+ CtOption::new(ss, ss.square().ct_eq(self))
+ }
+
+ /// Halves a Scalar modulo the prime
+ pub const fn halve(&self) -> Self {
+ Self(self.0.shr_vartime(1))
+ }
+
+ /// Attempt to construct a `Scalar` from a canonical byte representation.
+ ///
+ /// # Return
+ ///
+ /// - `Some(s)`, where `s` is the `Scalar` corresponding to `bytes`,
+ /// if `bytes` is a canonical byte representation;
+ /// - `None` if `bytes` is not a canonical byte representation.
+ pub fn from_canonical_bytes(bytes: &ScalarBytes) -> CtOption {
+ // Check that the 10 high bits are not set
+ let is_valid = is_zero(bytes[56]) | is_zero(bytes[55] >> 6);
+ let bytes: [u8; 56] = core::array::from_fn(|i| bytes[i]);
+ let candidate = Scalar::from_bytes(&bytes);
+
+ // underflow means candidate < ORDER, thus canonical
+ let (_, underflow) = candidate.0.sbb(&ORDER, Limb::ZERO);
+ let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8);
+ CtOption::new(candidate, underflow & is_valid)
+ }
+
+ /// Serialize the scalar into 57 bytes, per RFC 8032.
+ /// Byte 56 will always be zero.
+ pub fn to_bytes_rfc_8032(&self) -> ScalarBytes {
+ let mut bytes = ScalarBytes::default();
+ bytes[..56].copy_from_slice(&self.to_bytes());
+ bytes
+ }
+
+ /// Construct a `Scalar` by reducing a 912-bit little-endian integer
+ /// modulo the group order ℓ.
+ pub fn from_bytes_mod_order_wide(input: &WideScalarBytes) -> Scalar {
+ // top multiplier = 2^896 mod ℓ
+ const TOP_MULTIPLIER: U448 = U448::from_be_hex("3402a939f823b7292052bcb7e4d070af1a9cc14ba3c47c44ae17cf725ee4d8380d66de2388ea18597af32c4bc1b195d9e3539257049b9b60");
+ let value = (
+ U448::from_le_slice(&input[..56]),
+ U448::from_le_slice(&input[56..112]),
+ );
+ let mut top = [0u8; 56];
+ top[..2].copy_from_slice(&input[112..]);
+ let top = U448::from_le_slice(&top).mul_mod(&TOP_MULTIPLIER, &NZ_ORDER);
+ let bottom = U448::rem_wide_vartime(value, &NZ_ORDER);
+ Self(bottom.add_mod(&top, &ORDER))
+ }
+
+ /// Construct a Scalar by reducing a 448-bit little-endian integer modulo the group order ℓ
+ pub fn from_bytes_mod_order(input: &ScalarBytes) -> Scalar {
+ let value = U448::from_le_slice(&input[..56]);
+ Self(value.rem_vartime(&NZ_ORDER))
+ }
+
+ /// Return a `Scalar` chosen uniformly at random using a user-provided RNG.
+ ///
+ /// # Inputs
+ ///
+ /// * `rng`: any RNG which implements the `RngCore + CryptoRng` interface.
+ ///
+ /// # Returns
+ ///
+ /// A random scalar within ℤ/lℤ.
+ pub fn random(rng: &mut R) -> Self {
+ let mut scalar_bytes = WideScalarBytes::default();
+ rng.fill_bytes(&mut scalar_bytes);
+ Scalar::from_bytes_mod_order_wide(&scalar_bytes)
+ }
+
+ /// Computes the hash to field routine according to Section 5
+ ///
+ /// and returns a scalar.
+ ///
+ /// # Errors
+ /// See implementors of [`ExpandMsg`] for errors:
+ /// - [`ExpandMsgXmd`]
+ /// - [`ExpandMsgXof`]
+ ///
+ /// `len_in_bytes = ::Length`
+ ///
+ /// [`ExpandMsgXmd`]: elliptic_curve::hash2curve::ExpandMsgXmd
+ /// [`ExpandMsgXof`]: elliptic_curve::hash2curve::ExpandMsgXof
+ pub fn hash(msg: &[u8], dst: &[u8]) -> Self
+ where
+ X: for<'a> ExpandMsg<'a>,
+ {
+ let mut random_bytes = Array::::default();
+ let dst = [dst];
+ let mut expander =
+ X::expand_message(&[msg], &dst, random_bytes.len()).expect("invalid dst");
+ expander.fill_bytes(&mut random_bytes);
+ Self::from_okm(&random_bytes)
+ }
+}
+
+fn is_zero(b: u8) -> Choice {
+ let res = b as i8;
+ Choice::from((((res | -res) >> 7) + 1) as u8)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use hex_literal::hex;
+
+ #[test]
+ fn test_basic_add() {
+ let five = Scalar::from(5u8);
+ let six = Scalar::from(6u8);
+
+ assert_eq!(five + six, Scalar::from(11u8))
+ }
+
+ #[test]
+ fn test_basic_sub() {
+ let ten = Scalar::from(10u8);
+ let five = Scalar::from(5u8);
+ assert_eq!(ten - five, Scalar::from(5u8))
+ }
+
+ #[test]
+ fn test_basic_mul() {
+ let ten = Scalar::from(10u8);
+ let five = Scalar::from(5u8);
+
+ assert_eq!(ten * five, Scalar::from(50u8))
+ }
+
+ #[test]
+ fn test_mul() {
+ let a = Scalar(U448::from_be_hex(
+ "1e63e8073b089f0747cf8cac2c3dc2732aae8688a8fa552ba8cb0ae8c0be082e74d657641d9ac30a087b8fb97f8ed27dc96a3c35ffb823a3"
+ ));
+
+ let b = Scalar(U448::from_be_hex(
+ "16c5450acae1cb680a92de2d8e59b30824e8d4991adaa0e7bc343bcbd099595b188c6b1a1e30b38b17aa6d9be416b899686eb329d8bedc42"
+ ));
+
+ let exp = Scalar(U448::from_be_hex(
+ "31e055c14ca389edfccd61b3203d424bb9036ff6f2d89c1e07bcd93174e9335f36a1492008a3a0e46abd26f5994c9c2b1f5b3197a18d010a"
+ ));
+
+ assert_eq!(a * b, exp)
+ }
+ #[test]
+ fn test_basic_square() {
+ let a = Scalar(U448::from_be_hex(
+ "3162081604b3273b930392e5d2391f9d21cc3078f22c69514bb395e08dccc4866f08f3311370f8b83fa50692f640922b7e56a34bcf5fac3d",
+ ));
+ let expected_a_squared = Scalar(U448::from_be_hex(
+ "1c1e32fc66b21c9c42d6e8e20487193cf6d49916421b290098f30de3713006cfe8ee9d21eeef7427f82a1fe036630c74b9acc2c2ede40f04",
+ ));
+
+ assert_eq!(a.square(), expected_a_squared)
+ }
+
+ #[test]
+ fn test_sanity_check_index_mut() {
+ let mut x = Scalar::ONE;
+ x[0] = 2;
+ assert_eq!(x, Scalar::from(2u8))
+ }
+ #[test]
+ fn test_basic_halving() {
+ let eight = Scalar::from(8u8);
+ let four = Scalar::from(4u8);
+ let two = Scalar::from(2u8);
+ assert_eq!(eight.halve(), four);
+ assert_eq!(four.halve(), two);
+ assert_eq!(two.halve(), Scalar::ONE);
+ }
+
+ #[test]
+ fn test_equals() {
+ let a = Scalar::from(5u8);
+ let b = Scalar::from(5u8);
+ let c = Scalar::from(10u8);
+ assert_eq!(a, b);
+ assert_ne!(a, c);
+ }
+
+ #[test]
+ fn test_basic_inversion() {
+ // Test inversion from 2 to 100
+ for i in 1..=100u8 {
+ let x = Scalar::from(i);
+ let x_inv = x.invert();
+ assert_eq!(x_inv * x, Scalar::ONE)
+ }
+
+ // Inversion of zero is zero
+ let zero = Scalar::ZERO;
+ let expected_zero = zero.invert();
+ assert_eq!(expected_zero, zero)
+ }
+ #[test]
+ fn test_serialise() {
+ let scalar = Scalar(U448::from_be_hex(
+ "0d79f6e375d3395ed9a6c4c3c49a1433fd7c58aa38363f74e9ab2c22a22347d79988f8e01e8a309f862a9f1052fcd042b9b1ed7115598f62",
+ ));
+ let got = Scalar::from_bytes(&scalar.to_bytes());
+ assert_eq!(scalar, got)
+ }
+ #[test]
+ fn test_from_canonical_bytes() {
+ // ff..ff should fail
+ let mut bytes = ScalarBytes::from(hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
+ bytes.reverse();
+ let s = Scalar::from_canonical_bytes(&bytes);
+ assert!(>::into(s.is_none()));
+
+ // n should fail
+ let mut bytes = ScalarBytes::from(hex!("003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3"));
+ bytes.reverse();
+ let s = Scalar::from_canonical_bytes(&bytes);
+ assert!(>::into(s.is_none()));
+
+ // n-1 should work
+ let mut bytes = ScalarBytes::from(hex!("003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f2"));
+ bytes.reverse();
+ let s = Scalar::from_canonical_bytes(&bytes);
+ match Option::::from(s) {
+ Some(s) => assert_eq!(s, Scalar::ZERO - Scalar::ONE),
+ None => panic!("should not return None"),
+ };
+ }
+
+ #[test]
+ fn test_from_bytes_mod_order_wide() {
+ // n should become 0
+ let mut bytes = WideScalarBytes::from(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3"));
+ bytes.reverse();
+ let s = Scalar::from_bytes_mod_order_wide(&bytes);
+ assert_eq!(s, Scalar::ZERO);
+
+ // n-1 should stay the same
+ let mut bytes = WideScalarBytes::from(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f2"));
+ bytes.reverse();
+ let s = Scalar::from_bytes_mod_order_wide(&bytes);
+ assert_eq!(s, Scalar::ZERO - Scalar::ONE);
+
+ // n+1 should become 1
+ let mut bytes = WideScalarBytes::from(hex!("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f4"));
+ bytes.reverse();
+ let s = Scalar::from_bytes_mod_order_wide(&bytes);
+ assert_eq!(s, Scalar::ONE);
+
+ // 2^912-1 should become 0x2939f823b7292052bcb7e4d070af1a9cc14ba3c47c44ae17cf72c985bb24b6c520e319fb37a63e29800f160787ad1d2e11883fa931e7de81
+ let bytes = WideScalarBytes::from(hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
+ let s = Scalar::from_bytes_mod_order_wide(&bytes);
+ let mut bytes = ScalarBytes::from(hex!("002939f823b7292052bcb7e4d070af1a9cc14ba3c47c44ae17cf72c985bb24b6c520e319fb37a63e29800f160787ad1d2e11883fa931e7de81"));
+ bytes.reverse();
+ let reduced = Scalar::from_canonical_bytes(&bytes).unwrap();
+ assert_eq!(s, reduced);
+ }
+
+ #[test]
+ fn test_to_bytes_rfc8032() {
+ // n-1
+ let mut bytes: [u8; 57] = hex!("003fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f2");
+ bytes.reverse();
+ let x = Scalar::ZERO - Scalar::ONE;
+ let candidate = x.to_bytes_rfc_8032();
+ assert_eq!(&bytes[..], &candidate[..]);
+ }
+
+ #[cfg(all(any(feature = "alloc", feature = "std"), feature = "serde"))]
+ #[test]
+ fn serde() {
+ let res = serde_json::to_string(&Scalar::TWO_INV);
+ assert!(res.is_ok());
+ let sj = res.unwrap();
+
+ let res = serde_json::from_str::(&sj);
+ assert!(res.is_ok());
+ assert_eq!(res.unwrap(), Scalar::TWO_INV);
+
+ let res = serde_bare::to_vec(&Scalar::TWO_INV);
+ assert!(res.is_ok());
+ let sb = res.unwrap();
+ assert_eq!(sb.len(), 57);
+
+ let res = serde_bare::from_slice::(&sb);
+ assert!(res.is_ok());
+ assert_eq!(res.unwrap(), Scalar::TWO_INV);
+ }
+
+ #[test]
+ fn scalar_hash() {
+ let msg = b"hello world";
+ let dst = b"edwards448_XOF:SHAKE256_ELL2_RO_";
+ let res =
+ Scalar::hash::>(msg, dst);
+ let expected: [u8; 57] = hex_literal::hex!("2d32a08f09b88275cc5f437e625696b18de718ed94559e17e4d64aafd143a8527705132178b5ce7395ea6214735387398a35913656b4951300");
+ assert_eq!(res.to_bytes_rfc_8032(), Array::from(expected));
+ }
+}
diff --git a/ed448/src/lib.rs b/ed448/src/lib.rs
new file mode 100644
index 000000000..2cda76ab2
--- /dev/null
+++ b/ed448/src/lib.rs
@@ -0,0 +1,193 @@
+//! This crate provides a pure Rust implementation of Curve448, Edwards, Decaf, and Ristretto.
+//! It is intended to be portable, fast, and safe.
+//!
+//! # Usage
+//! ```
+//! use ed448::{EdwardsPoint, CompressedEdwardsY, Scalar, elliptic_curve::hash2curve::ExpandMsgXof, sha3::Shake256};
+//! use elliptic_curve::Field;
+//! use rand_core::OsRng;
+//!
+//! let secret_key = Scalar::TWO;
+//! let public_key = EdwardsPoint::GENERATOR * &secret_key;
+//!
+//! assert_eq!(public_key, EdwardsPoint::GENERATOR + EdwardsPoint::GENERATOR);
+//!
+//! let secret_key = Scalar::try_from_rng(&mut OsRng).unwrap();
+//! let public_key = EdwardsPoint::GENERATOR * &secret_key;
+//! let compressed_public_key = public_key.compress();
+//!
+//! assert_eq!(compressed_public_key.to_bytes().len(), 57);
+//!
+//! let hashed_scalar = Scalar::hash::>(b"test", b"edwards448_XOF:SHAKE256_ELL2_RO_");
+//! let input = hex_literal::hex!("c8c6c8f584e0c25efdb6af5ad234583c56dedd7c33e0c893468e96740fa0cf7f1a560667da40b7bde340a39252e89262fcf707d1180fd43400");
+//! let expected_scalar = Scalar::from_canonical_bytes(&input.into()).unwrap();
+//! assert_eq!(hashed_scalar, expected_scalar);
+//!
+//! let hashed_point = EdwardsPoint::hash::>(b"test", b"edwards448_XOF:SHAKE256_ELL2_RO_");
+//! let expected = hex_literal::hex!("d15c4427b5c5611a53593c2be611fd3635b90272d331c7e6721ad3735e95dd8b9821f8e4e27501ce01aa3c913114052dce2e91e8ca050f4980");
+//! let expected_point = CompressedEdwardsY(expected).decompress().unwrap();
+//! assert_eq!(hashed_point, expected_point);
+//!
+//! let hashed_point = EdwardsPoint::hash_with_defaults(b"test");
+//! assert_eq!(hashed_point, expected_point);
+//! ```
+//!
+//! [`EdwardsPoint`] implements the [`elliptic_curve::Group`] and [`elliptic_curve::group::GroupEncoding`]
+//! and [`Scalar`] implements [`elliptic_curve::Field`] and [`elliptic_curve::PrimeField`] traits.
+#![deny(unused_attributes, unused_imports, unused_mut, unused_must_use)]
+#![allow(non_snake_case)]
+#![no_std]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![warn(
+ missing_docs,
+ missing_debug_implementations,
+ missing_copy_implementations,
+ trivial_casts,
+ trivial_numeric_casts,
+ unused,
+ clippy::mod_module_files
+)]
+#![deny(clippy::unwrap_used)]
+
+#[cfg(all(feature = "alloc", not(feature = "std")))]
+extern crate alloc;
+#[cfg(feature = "std")]
+extern crate std;
+
+#[cfg(all(feature = "alloc", not(feature = "std")))]
+use alloc::{boxed::Box, vec::Vec};
+
+#[cfg(feature = "std")]
+use std::{boxed::Box, vec::Vec};
+
+// Internal macros. Must come first!
+#[macro_use]
+pub(crate) mod macros;
+
+pub use elliptic_curve;
+pub use rand_core;
+pub use sha3;
+pub use subtle;
+
+// As usual, we will use this file to carefully define the API/ what we expose to the user
+pub(crate) mod constants;
+pub(crate) mod curve;
+pub(crate) mod decaf;
+pub(crate) mod field;
+pub(crate) mod ristretto;
+#[cfg(feature = "signing")]
+pub(crate) mod sign;
+
+pub(crate) use field::{GOLDILOCKS_BASE_POINT, TWISTED_EDWARDS_BASE_POINT};
+
+pub use curve::{
+ AffinePoint, CompressedEdwardsY, EdwardsPoint, MontgomeryPoint, ProjectiveMontgomeryPoint,
+};
+pub use decaf::{AffinePoint as DecafAffinePoint, CompressedDecaf, DecafPoint};
+pub use field::{Scalar, ScalarBytes, WideScalarBytes, MODULUS_LIMBS, ORDER, WIDE_ORDER};
+pub use ristretto::{CompressedRistretto, RistrettoPoint};
+#[cfg(feature = "signing")]
+pub use sign::*;
+
+use elliptic_curve::{
+ array::typenum::U57,
+ bigint::{ArrayEncoding, U448},
+ point::PointCompression,
+ Curve, FieldBytesEncoding, PrimeCurve,
+};
+
+/// Edwards448 curve.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Ed448;
+
+/// Bytes of the Ed448 field
+pub type Ed448FieldBytes = elliptic_curve::FieldBytes;
+
+/// Scalar bits of the Ed448 scalar
+pub type Ed448ScalarBits = elliptic_curve::scalar::ScalarBits;
+
+/// Non-zero scalar of the Ed448 scalar
+pub type Ed448NonZeroScalar = elliptic_curve::NonZeroScalar;
+
+unsafe impl Send for Ed448 {}
+unsafe impl Sync for Ed448 {}
+
+impl Curve for Ed448 {
+ type FieldBytesSize = U57;
+ type Uint = U448;
+
+ const ORDER: U448 = ORDER;
+}
+
+impl PrimeCurve for Ed448 {}
+
+impl PointCompression for Ed448 {
+ const COMPRESS_POINTS: bool = true;
+}
+
+impl FieldBytesEncoding for U448 {
+ fn decode_field_bytes(field_bytes: &Ed448FieldBytes) -> Self {
+ U448::from_le_slice(field_bytes)
+ }
+
+ fn encode_field_bytes(&self) -> Ed448FieldBytes {
+ let mut data = Ed448FieldBytes::default();
+ data.copy_from_slice(&self.to_le_byte_array()[..]);
+ data
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl elliptic_curve::CurveArithmetic for Ed448 {
+ type AffinePoint = AffinePoint;
+ type ProjectivePoint = EdwardsPoint;
+ type Scalar = Scalar;
+}
+
+/// Decaf448 curve.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Decaf448;
+
+/// Bytes of the Decaf448 field
+pub type Decaf448FieldBytes = elliptic_curve::FieldBytes;
+
+/// Scalar bits of the Decaf448 scalar
+pub type Decaf448ScalarBits = elliptic_curve::scalar::ScalarBits;
+
+/// Non-zero scalar of the Decaf448 scalar
+pub type Decaf448NonZeroScalar = elliptic_curve::NonZeroScalar;
+
+unsafe impl Send for Decaf448 {}
+unsafe impl Sync for Decaf448 {}
+
+impl Curve for Decaf448 {
+ type FieldBytesSize = U57;
+ type Uint = U448;
+
+ const ORDER: U448 = ORDER;
+}
+
+impl PrimeCurve for Decaf448 {}
+
+impl PointCompression for Decaf448 {
+ const COMPRESS_POINTS: bool = true;
+}
+
+impl FieldBytesEncoding for U448 {
+ fn decode_field_bytes(field_bytes: &Decaf448FieldBytes) -> Self {
+ U448::from_le_slice(field_bytes)
+ }
+
+ fn encode_field_bytes(&self) -> Decaf448FieldBytes {
+ let mut data = Decaf448FieldBytes::default();
+ data.copy_from_slice(&self.to_le_byte_array()[..]);
+ data
+ }
+}
+
+#[cfg(feature = "zeroize")]
+impl elliptic_curve::CurveArithmetic for Decaf448 {
+ type AffinePoint = DecafAffinePoint;
+ type ProjectivePoint = DecafPoint;
+ type Scalar = Scalar;
+}
diff --git a/ed448/src/macros.rs b/ed448/src/macros.rs
new file mode 100644
index 000000000..ab084b3cb
--- /dev/null
+++ b/ed448/src/macros.rs
@@ -0,0 +1,132 @@
+// -*- mode: rust; -*-
+//
+// This file is part of curve25519-dalek.
+// Copyright (c) 2016-2021 isis agora lovecruft
+// Copyright (c) 2016-2019 Henry de Valence
+// See LICENSE for licensing information.
+//
+// Authors:
+// - isis agora lovecruft
+// - Henry de Valence
+
+//! Internal macros.
+
+/// Define borrow and non-borrow variants of `Add`.
+macro_rules! define_add_variants {
+ (LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => {
+ impl<'b> Add<&'b $rhs> for $lhs {
+ type Output = $out;
+
+ fn add(self, rhs: &'b $rhs) -> $out {
+ &self + rhs
+ }
+ }
+
+ impl<'a> Add<$rhs> for &'a $lhs {
+ type Output = $out;
+
+ fn add(self, rhs: $rhs) -> $out {
+ self + &rhs
+ }
+ }
+
+ impl Add<$rhs> for $lhs {
+ type Output = $out;
+
+ fn add(self, rhs: $rhs) -> $out {
+ &self + &rhs
+ }
+ }
+ };
+}
+
+/// Define non-borrow variants of `AddAssign`.
+macro_rules! define_add_assign_variants {
+ (LHS = $lhs:ty, RHS = $rhs:ty) => {
+ impl AddAssign<$rhs> for $lhs {
+ fn add_assign(&mut self, rhs: $rhs) {
+ *self += &rhs;
+ }
+ }
+ };
+}
+
+/// Define borrow and non-borrow variants of `Sub`.
+macro_rules! define_sub_variants {
+ (LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => {
+ impl<'b> Sub<&'b $rhs> for $lhs {
+ type Output = $out;
+
+ fn sub(self, rhs: &'b $rhs) -> $out {
+ &self - rhs
+ }
+ }
+
+ impl<'a> Sub<$rhs> for &'a $lhs {
+ type Output = $out;
+
+ fn sub(self, rhs: $rhs) -> $out {
+ self - &rhs
+ }
+ }
+
+ impl Sub<$rhs> for $lhs {
+ type Output = $out;
+
+ fn sub(self, rhs: $rhs) -> $out {
+ &self - &rhs
+ }
+ }
+ };
+}
+
+/// Define non-borrow variants of `SubAssign`.
+macro_rules! define_sub_assign_variants {
+ (LHS = $lhs:ty, RHS = $rhs:ty) => {
+ impl SubAssign<$rhs> for $lhs {
+ fn sub_assign(&mut self, rhs: $rhs) {
+ *self -= &rhs;
+ }
+ }
+ };
+}
+
+/// Define borrow and non-borrow variants of `Mul`.
+macro_rules! define_mul_variants {
+ (LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => {
+ impl<'b> Mul<&'b $rhs> for $lhs {
+ type Output = $out;
+
+ fn mul(self, rhs: &'b $rhs) -> $out {
+ &self * rhs
+ }
+ }
+
+ impl<'a> Mul<$rhs> for &'a $lhs {
+ type Output = $out;
+
+ fn mul(self, rhs: $rhs) -> $out {
+ self * &rhs
+ }
+ }
+
+ impl Mul<$rhs> for $lhs {
+ type Output = $out;
+
+ fn mul(self, rhs: $rhs) -> $out {
+ &self * &rhs
+ }
+ }
+ };
+}
+
+/// Define non-borrow variants of `MulAssign`.
+macro_rules! define_mul_assign_variants {
+ (LHS = $lhs:ty, RHS = $rhs:ty) => {
+ impl MulAssign<$rhs> for $lhs {
+ fn mul_assign(&mut self, rhs: $rhs) {
+ *self *= &rhs;
+ }
+ }
+ };
+}
diff --git a/ed448/src/ristretto.rs b/ed448/src/ristretto.rs
new file mode 100644
index 000000000..2e58e8873
--- /dev/null
+++ b/ed448/src/ristretto.rs
@@ -0,0 +1,6 @@
+// This will be the module for Ristretto over Ed448
+
+pub mod constants;
+pub mod points;
+
+pub use points::{CompressedRistretto, RistrettoPoint};
diff --git a/ed448/src/ristretto/constants.rs b/ed448/src/ristretto/constants.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/ed448/src/ristretto/constants.rs
@@ -0,0 +1 @@
+
diff --git a/ed448/src/ristretto/points.rs b/ed448/src/ristretto/points.rs
new file mode 100644
index 000000000..0e2422a6a
--- /dev/null
+++ b/ed448/src/ristretto/points.rs
@@ -0,0 +1,77 @@
+#![allow(non_snake_case)]
+
+use crate::curve::twedwards::extended::ExtendedPoint;
+use subtle::{Choice, ConstantTimeEq};
+
+/// The bytes representation of a compressed point.
+pub type RistrettoPointBytes = [u8; 56];
+
+#[derive(Copy, Clone, Debug)]
+/// Ristretto point.
+pub struct RistrettoPoint(pub(crate) ExtendedPoint);
+
+#[derive(Copy, Clone, Debug)]
+#[repr(transparent)]
+/// Compressed Ristretto point.
+pub struct CompressedRistretto(pub RistrettoPointBytes);
+
+impl Default for CompressedRistretto {
+ fn default() -> Self {
+ Self::IDENTITY
+ }
+}
+
+impl ConstantTimeEq for CompressedRistretto {
+ fn ct_eq(&self, other: &CompressedRistretto) -> Choice {
+ self.as_bytes().ct_eq(other.as_bytes())
+ }
+}
+
+impl PartialEq for CompressedRistretto {
+ fn eq(&self, other: &CompressedRistretto) -> bool {
+ self.ct_eq(other).into()
+ }
+}
+
+impl Eq for CompressedRistretto {}
+
+impl CompressedRistretto {
+ /// The identity element of the group: the point at infinity.
+ pub const IDENTITY: Self = Self([0u8; 56]);
+
+ /// Get the bytes of the compressed point.
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+impl RistrettoPoint {
+ /// The generator of the Ristretto group.
+ pub const GENERATOR: RistrettoPoint = RistrettoPoint(ExtendedPoint::GENERATOR);
+ /// The identity element of the group: the point at infinity.
+ pub const IDENTITY: RistrettoPoint = RistrettoPoint(ExtendedPoint::IDENTITY);
+
+ /// Check whether the point is the identity point.
+ pub fn equals(&self, other: &RistrettoPoint) -> bool {
+ let XY = self.0.X * other.0.Y;
+ let YX = self.0.Y * other.0.X;
+ XY == YX
+ }
+
+ /// Decode the compressed point.
+ pub fn encode(&self) -> CompressedRistretto {
+ todo!()
+ }
+}
+
+impl CompressedRistretto {
+ /// The identity element of the group: the point at infinity.
+ pub fn identity() -> CompressedRistretto {
+ CompressedRistretto([0; 56])
+ }
+
+ /// Decode the compressed point.
+ pub fn decode(&self) -> Option {
+ todo!()
+ }
+}
diff --git a/ed448/src/sign.rs b/ed448/src/sign.rs
new file mode 100644
index 000000000..c6ade4525
--- /dev/null
+++ b/ed448/src/sign.rs
@@ -0,0 +1,113 @@
+//! Ed448 digital signatures implementation
+//!
+//! # Example
+//! Creating an ed448 signature.
+//!
+//! Generate a [`SigningKey`], which includes both the public and secret halves, using
+//! a cryptographically secure pseudorandom number generator (CSPRNG). Next sign a message
+//! to produce a [`Signature`]. Then verify the signature using the corresponding
+//! [`VerifyingKey`].
+//!
+//! ```
+//! use ed448::*;
+//! use rand_core::SeedableRng;
+//! use rand_chacha::ChaChaRng;
+//!
+//! let mut rng = ChaChaRng::from_os_rng();
+//! let signing_key = SigningKey::generate(&mut rng);
+//! let signature = signing_key.sign_raw(b"Hello, world!");
+//! let verifying_key = signing_key.verifying_key();
+//!
+//! assert!(verifying_key.verify_raw(&signature, b"Hello, world!").is_ok());
+//! ```
+//!
+//! This crate also supports using context specific strings when creating and verifying signatures.
+//! In addition, it supports the PKCS#8 standard for encoding and decoding keys, or raw byte forms
+//! using `to_bytes` and `from_bytes` methods. These store the [`SecretKey`] which is the prehash
+//! seed of the [`SigningKey`].
+//!
+//! # PKCS#8 Key Encoding
+//! PKCS#8 is a private key format with support for multiple algorithms. It can be encoded as
+//! binary (DER) or text (PEM). Use the `pkcs8` feature to enable this option.
+//!
+//! # Using Serde
+//! This crate supports serialization and deserialization using the `serde` if the preference
+//! is to encode the keys as other formats. Use the `serde` feature to enable this option.
+//!
+//! # Using Signature
+//! This crate supports signing using the traits defined in the `signature` crate like
+//! - [`Signer`]
+//! - [`DigestSigner`]
+//! - [`PrehashSigner`]
+//! - [`Verifier`]
+//! - [`DigestVerifier`]
+//!
+//! The crate is re-exported as `crypto-signature` for use in other crates.
+//!
+//! # Other Features
+//! Signing and verifying also supports custom digest and prehash algorithms.
+//! Any algorith that implements [`PreHash`] and [`Digest`] can be used.
+//! However, there are two implementations provided in this crate:
+//!
+//! - [`PreHasherXmd`] which supports any implementation of a fixed length digest like SHA3-512.
+//! - [`PreHasherXof`] which supports any implementation of expandable output functions like SHAKE-256.
+//!
+//! # Example
+//! This is an example of using the SHAKE-256 algorithm to sign and verify a message
+//! which is the normal default anyway but performed explicitly.
+//! ```
+//! use ed448::*;
+//! use sha3::{Shake256, digest::Update};
+//! use rand_chacha::ChaChaRng;
+//! use rand_core::SeedableRng;
+//!
+//! let mut rng = ChaChaRng::from_os_rng();
+//! let msg = b"Hello World";
+//! let signing_key = SigningKey::generate(&mut rng);
+//! let signature = signing_key.sign_prehashed::>(
+//! None,
+//! Shake256::default().chain(msg).into(),
+//! ).unwrap();
+//! let verifying_key = signing_key.verifying_key();
+//! assert!(verifying_key.verify_prehashed::>(
+//! &signature, None, Shake256::default().chain(msg).into()).is_ok());
+//! ```
+mod context;
+mod error;
+mod expanded;
+mod signature;
+mod signing_key;
+mod verifying_key;
+
+pub use context::*;
+pub use crypto_signature;
+pub use error::*;
+#[cfg(feature = "pkcs8")]
+pub use pkcs8;
+pub use signature::*;
+pub use signing_key::*;
+pub use verifying_key::*;
+
+/// Length of a secret key in bytes
+pub const SECRET_KEY_LENGTH: usize = 57;
+
+/// Length of a public key in bytes
+pub const PUBLIC_KEY_LENGTH: usize = 57;
+
+/// Length of a signature in bytes
+pub const SIGNATURE_LENGTH: usize = 114;
+
+/// Constant string "SigEd448".
+pub(crate) const HASH_HEAD: [u8; 8] = [0x53, 0x69, 0x67, 0x45, 0x64, 0x34, 0x34, 0x38];
+
+#[cfg(feature = "pkcs8")]
+/// The OID for Ed448 as defined in [RFC8410 §2]
+pub const ALGORITHM_OID: pkcs8::ObjectIdentifier =
+ pkcs8::ObjectIdentifier::new_unwrap("1.3.101.113");
+
+#[cfg(feature = "pkcs8")]
+/// The `AlgorithmIdentifier` for Ed448 as defined in [RFC8410 §2]
+pub const ALGORITHM_ID: pkcs8::AlgorithmIdentifierRef<'static> = pkcs8::AlgorithmIdentifierRef {
+ oid: ALGORITHM_OID,
+ parameters: None,
+};
diff --git a/ed448/src/sign/context.rs b/ed448/src/sign/context.rs
new file mode 100644
index 000000000..03f5e1aa3
--- /dev/null
+++ b/ed448/src/sign/context.rs
@@ -0,0 +1,30 @@
+/// Ed448 contexts as used by Ed448ph.
+///
+/// Contexts are domain separator strings that can be used to isolate uses of
+/// the algorithm between different protocols (which is very hard to reliably do
+/// otherwise) and between different uses within the same protocol.
+///
+/// To create a context, call either of the following:
+///
+/// - [`SigningKey::with_context`](crate::SigningKey::with_context)
+/// - [`VerifyingKey::with_context`](crate::VerifyingKey::with_context)
+#[derive(Copy, Clone, Debug)]
+pub struct Context<'k, 'v, K> {
+ pub(crate) key: &'k K,
+ pub(crate) value: &'v [u8],
+}
+
+impl<'k, 'v, K> Context<'k, 'v, K> {
+ /// Maximum length of a context string.
+ pub const MAX_LENGTH: usize = 255;
+
+ /// Borrow the key
+ pub fn key(&self) -> &'k K {
+ self.key
+ }
+
+ /// Borrow the value
+ pub fn value(&self) -> &'v [u8] {
+ self.value
+ }
+}
diff --git a/ed448/src/sign/error.rs b/ed448/src/sign/error.rs
new file mode 100644
index 000000000..a5ddc200c
--- /dev/null
+++ b/ed448/src/sign/error.rs
@@ -0,0 +1,55 @@
+use core::fmt::{self, Display, Formatter};
+
+#[cfg(feature = "std")]
+use std::error::Error;
+
+/// Signing errors
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
+pub enum SigningError {
+ /// Prehashed context length is invalid
+ PrehashedContextLength,
+ /// Public key bytes are invalid
+ InvalidPublicKeyBytes,
+ /// Signature S component is invalid
+ InvalidSignatureSComponent,
+ /// Signature R component is invalid
+ InvalidSignatureRComponent,
+ /// Signature length is invalid
+ InvalidSignatureLength,
+ /// Signature verification failed
+ Verify,
+}
+
+impl Display for SigningError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match self {
+ SigningError::PrehashedContextLength => {
+ write!(f, "prehashed context length is invalid")
+ }
+ SigningError::InvalidPublicKeyBytes => write!(f, "public key bytes are invalid"),
+ SigningError::InvalidSignatureSComponent => {
+ write!(f, "signature S component is invalid")
+ }
+ SigningError::InvalidSignatureRComponent => {
+ write!(f, "signature R component is invalid")
+ }
+ SigningError::InvalidSignatureLength => write!(f, "signature length is invalid"),
+ SigningError::Verify => write!(f, "signature verification failed"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl Error for SigningError {}
+
+impl From