Ver Fonte

Add wireguard

世界 há 4 anos atrás
pai
commit
7e81441033
53 ficheiros alterados com 4695 adições e 16 exclusões
  1. 23 0
      .github/workflows/debug.yml
  2. 2 2
      .github/workflows/release_hysteria.yml
  3. 198 0
      .github/workflows/release_wireguard.yml
  4. 3 0
      .gitmodules
  5. 1 0
      .idea/gradle.xml
  6. 3 0
      README.md
  7. 1 0
      app/build.gradle.kts
  8. 474 0
      app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/10.json
  9. 3 0
      app/src/main/AndroidManifest.xml
  10. 497 0
      app/src/main/java/com/wireguard/crypto/Curve25519.java
  11. 2508 0
      app/src/main/java/com/wireguard/crypto/Ed25519.java
  12. 288 0
      app/src/main/java/com/wireguard/crypto/Key.java
  13. 34 0
      app/src/main/java/com/wireguard/crypto/KeyFormatException.java
  14. 51 0
      app/src/main/java/com/wireguard/crypto/KeyPair.java
  15. 1 0
      app/src/main/java/io/nekohasekai/sagernet/Constants.kt
  16. 2 1
      app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt
  17. 3 1
      app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt
  18. 31 4
      app/src/main/java/io/nekohasekai/sagernet/bg/proto/V2RayInstance.kt
  19. 1 0
      app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
  20. 15 0
      app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt
  21. 1 1
      app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt
  22. 3 0
      app/src/main/java/io/nekohasekai/sagernet/database/preference/KeyValuePair.kt
  23. 7 0
      app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java
  24. 1 0
      app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt
  25. 1 0
      app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt
  26. 88 0
      app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardBean.java
  27. 40 0
      app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt
  28. 38 1
      app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
  29. 37 6
      app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt
  30. 1 0
      app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt
  31. 76 0
      app/src/main/java/io/nekohasekai/sagernet/ui/profile/WireGuardSettingsActivity.kt
  32. 3 0
      app/src/main/res/menu/add_profile_menu.xml
  33. 4 0
      app/src/main/res/values/strings.xml
  34. 43 0
      app/src/main/res/xml/wireguard_preferences.xml
  35. 8 0
      bin/plugin/wireguard.sh
  36. 9 0
      bin/plugin/wireguard/arm64-v8a.sh
  37. 9 0
      bin/plugin/wireguard/armeabi-v7a.sh
  38. 15 0
      bin/plugin/wireguard/build.sh
  39. 5 0
      bin/plugin/wireguard/end.sh
  40. 13 0
      bin/plugin/wireguard/init.sh
  41. 9 0
      bin/plugin/wireguard/x86.sh
  42. 9 0
      bin/plugin/wireguard/x86_64.sh
  43. 5 0
      plugin/wireguard/build.gradle.kts
  44. 39 0
      plugin/wireguard/src/main/AndroidManifest.xml
  45. 1 0
      plugin/wireguard/src/main/go/wgsocks
  46. BIN
      plugin/wireguard/src/main/ic_launcher-playstore.png
  47. 40 0
      plugin/wireguard/src/main/java/io/nekohasekai/sagernet/plugin/wireguard/BinaryProvider.kt
  48. 34 0
      plugin/wireguard/src/main/res/drawable/ic_launcher_foreground.xml
  49. 5 0
      plugin/wireguard/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  50. 5 0
      plugin/wireguard/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  51. 4 0
      plugin/wireguard/src/main/res/values/ic_launcher_background.xml
  52. 2 0
      sager.properties
  53. 1 0
      settings.gradle.kts

+ 23 - 0
.github/workflows/debug.yml

@@ -184,6 +184,29 @@ jobs:
       - name: Native Build
         if: steps.cache.outputs.cache-hit != 'true'
         run: ./run plugin hysteria
+  wireguard:
+    name: Native Build (WireGuard)
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Fetch Status
+        run: git submodule status 'plugin/wireguard/*' > wireguard_status
+      - name: WireGuard Cache
+        id: cache
+        uses: actions/cache@v2
+        with:
+          path: |
+            plugin/wireguard/src/main/jniLibs
+          key: ${{ hashFiles('bin/lib/wireguard/*', 'wireguard_status') }}
+      - name: Install Golang
+        uses: actions/setup-go@v2
+        if: steps.cache.outputs.cache-hit != 'true'
+        with:
+          go-version: 1.17
+      - name: Native Build
+        if: steps.cache.outputs.cache-hit != 'true'
+        run: ./run plugin wireguard
   shadowsocks:
     name: Native Build (shadowsocks-rust)
     runs-on: ubuntu-latest

+ 2 - 2
.github/workflows/release_hysteria.yml

@@ -69,7 +69,7 @@ jobs:
         with:
           path: |
             plugin/hysteria/src/main/jniLibs
-          key: ${{ hashFiles('bin/lib/brook/*', 'hysteria_status') }}
+          key: ${{ hashFiles('bin/lib/hysteria/*', 'hysteria_status') }}
       - name: Gradle cache
         uses: actions/cache@v2
         with:
@@ -177,7 +177,7 @@ jobs:
         with:
           path: |
             plugin/hysteria/src/main/jniLibs
-          key: ${{ hashFiles('bin/lib/brook/*', 'hysteria_status') }}
+          key: ${{ hashFiles('bin/lib/hysteria/*', 'hysteria_status') }}
       - name: Gradle cache
         uses: actions/cache@v2
         with:

+ 198 - 0
.github/workflows/release_wireguard.yml

@@ -0,0 +1,198 @@
+name: WireGuard Plugin Release Build
+on:
+  workflow_dispatch:
+    inputs:
+      tag:
+        description: 'Release Tag'
+        required: true
+      upload:
+        description: 'Upload: If want ignore'
+        required: false
+      publish:
+        description: 'Publish: If want ignore'
+        required: false
+      play:
+        description: 'Play: If want ignore'
+        required: false
+jobs:
+  check:
+    name: Check Access
+    runs-on: ubuntu-latest
+    steps:
+      - name: "Check access"
+        uses: "lannonbr/[email protected]"
+        with:
+          permission: "write"
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  native:
+    name: Native Build
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Fetch Status
+        run: git submodule status 'plugin/wireguard/*' > wireguard_status
+      - name: WireGuard Cache
+        id: cache
+        uses: actions/cache@v2
+        with:
+          path: |
+            plugin/wireguard/src/main/jniLibs
+          key: ${{ hashFiles('bin/lib/wireguard/*', 'wireguard_status') }}
+      - name: Gradle cache
+        uses: actions/cache@v2
+        with:
+          path: ~/.gradle
+          key: gradle-${{ hashFiles('**/*.gradle.kts') }}
+      - name: Install Golang
+        uses: actions/setup-go@v2
+        if: steps.cache.outputs.cache-hit != 'true'
+        with:
+          go-version: 1.17
+      - name: Native Build
+        if: steps.cache.outputs.cache-hit != 'true'
+        run: ./run plugin wireguard
+  build:
+    name: Gradle Build
+    runs-on: ubuntu-latest
+    needs:
+      - native
+      - check
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Fetch Status
+        run: git submodule status 'plugin/wireguard/*' > wireguard_status
+      - name: WireGuard Cache
+        uses: actions/cache@v2
+        with:
+          path: |
+            plugin/wireguard/src/main/jniLibs
+          key: ${{ hashFiles('bin/lib/wireguard/*', 'wireguard_status') }}
+      - name: Gradle cache
+        uses: actions/cache@v2
+        with:
+          path: ~/.gradle
+          key: gradle-${{ hashFiles('**/*.gradle.kts') }}
+      - name: Release Build
+        env:
+          SKIP_BUILD: on
+          BUILD_PLUGIN: wireguard
+        run: |
+          echo "sdk.dir=${ANDROID_HOME}" > local.properties
+          echo "ndk.dir=${ANDROID_HOME}/ndk/21.4.7075529" >> local.properties
+          export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}"
+          ./run init action library
+          ./gradlew :plugin:wireguard:assembleOssRelease
+          APK=$(find plugin/wireguard/build/outputs/apk -name '*arm64-v8a*.apk')
+          APK=$(dirname $APK)
+          echo "APK=$APK" >> $GITHUB_ENV
+      - uses: actions/upload-artifact@v2
+        with:
+          name: APKs
+          path: ${{ env.APK }}
+      - uses: actions/upload-artifact@v2
+        with:
+          name: "SHA256-ARM ${{ env.SHA256_ARM }}"
+          path: ${{ env.SUM_ARM }}
+      - uses: actions/upload-artifact@v2
+        with:
+          name: "SHA256-ARM64 ${{ env.SHA256_ARM64 }}"
+          path: ${{ env.SUM_ARM64 }}
+      - uses: actions/upload-artifact@v2
+        with:
+          name: "SHA256-X64 ${{ env.SHA256_X64 }}"
+          path: ${{ env.SUM_X64 }}
+      - uses: actions/upload-artifact@v2
+        with:
+          name: "SHA256-X86 ${{ env.SHA256_X86 }}"
+          path: ${{ env.SUM_X86 }}
+  publish:
+    name: Publish Release
+    if: github.event.inputs.publish != 'y'
+    runs-on: ubuntu-latest
+    needs: build
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Donwload Artifacts
+        uses: actions/download-artifact@v2
+        with:
+          name: APKs
+          path: artifacts
+      - name: Release
+        run: |
+          wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz
+          tar -xvf ghr.tar.gz
+          mv ghr*linux_amd64/ghr .
+          mkdir apks
+          find artifacts -name "*.apk" -exec cp {} apks \;
+          find artifacts -name "*.sha256sum.txt" -exec cp {} apks \;
+          ./ghr -delete -prerelease -t "${{ github.token }}" -n "${{ github.event.inputs.tag }}" "${{ github.event.inputs.tag }}" apks
+  upload:
+    name: Upload Release
+    if: github.event.inputs.upload != 'y'
+    runs-on: ubuntu-latest
+    needs: build
+    steps:
+      - name: Donwload Artifacts
+        uses: actions/download-artifact@v2
+        with:
+          name: APKs
+          path: artifacts
+      - name: Release
+        run: |
+          mkdir apks
+          find artifacts -name "*.apk" -exec cp {} apks \;
+
+          function upload() {
+            for apk in $@; do
+              echo ">> Uploading $apk"
+              curl https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendDocument \
+                -X POST \
+                -F chat_id="${{ secrets.TELEGRAM_CHANNEL }}" \
+                -F document="@$apk" \
+                --silent --show-error --fail >/dev/null &
+            done
+            for job in $(jobs -p); do
+              wait $job || exit 1
+            done
+          }
+          upload apks/*
+  play:
+    name: Publish to Play Store
+    if: github.event.inputs.play != 'y'
+    runs-on: ubuntu-latest
+    needs:
+      - native
+      - check
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Fetch Status
+        run: git submodule status 'plugin/wireguard/*' > wireguard_status
+      - name: WireGuard Cache
+        uses: actions/cache@v2
+        with:
+          path: |
+            plugin/wireguard/src/main/jniLibs
+          key: ${{ hashFiles('bin/lib/wireguard/*', 'wireguard_status') }}
+      - name: Gradle cache
+        uses: actions/cache@v2
+        with:
+          path: ~/.gradle
+          key: gradle-${{ hashFiles('**/*.gradle.kts') }}
+      - name: Release Build
+        env:
+          SKIP_BUILD: on
+          BUILD_PLUGIN: wireguard
+        run: |
+          echo "sdk.dir=${ANDROID_HOME}" > local.properties
+          echo "ndk.dir=${ANDROID_HOME}/ndk/21.4.7075529" >> local.properties
+          export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}"
+          cat > service_account_credentials.json << EOF
+          ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}"
+          EOF
+          ./run init action library
+          ./gradlew :plugin:wireguard:publishPlayReleaseBundle

+ 3 - 0
.gitmodules

@@ -62,3 +62,6 @@
 [submodule "library/shadowsocks-libev/src/main/jni/shadowsocks-libev"]
 	path = library/shadowsocks-libev/src/main/jni/shadowsocks-libev
 	url = https://github.com/SagerNet/shadowsocks-libev
+[submodule "plugin/wireguard/src/main/go/wgsocks"]
+	path = plugin/wireguard/src/main/go/wgsocks
+	url = https://github.com/SagerNet/wgsocks

+ 1 - 0
.idea/gradle.xml

@@ -87,6 +87,7 @@
             <option value="$PROJECT_DIR$/plugin/relaybaton" />
             <option value="$PROJECT_DIR$/plugin/trojan" />
             <option value="$PROJECT_DIR$/plugin/trojan-go" />
+            <option value="$PROJECT_DIR$/plugin/wireguard" />
           </set>
         </option>
         <option name="resolveModulePerSourceSet" value="false" />

+ 3 - 0
README.md

@@ -31,6 +31,7 @@ The application is designed to be used whenever possible.
 * relaybaton ( relaybaton-plugin )
 * Brook ( brook-plugin )
 * Hysteria ( hysteria-plugin )
+* WireGuard ( wireguard-plugin )
 
 ##### ROOT required
 
@@ -109,6 +110,8 @@ Licensed under [GPLv3][clash]
     <li><a href="https://github.com/iyouport-org/relaybaton/blob/ech/LICENSE">iyouport-org/relaybaton</a>:  <code>MIT</code></li>
     <li><a href="https://github.com/txthinking/brook/blob/master/LICENSE">txthinking/brook</a>:  <code>GPL 3.0</code></li>
     <li><a href="https://github.com/HyNetwork/hysteria/blob/master/LICENSE.md">HyNetwork/hysteria</a>:  <code>MIT</code></li>
+    <li><a href="https://github.com/WireGuard/wireguard-go/blob/master/LICENSE">WireGuard/wireguard-go</a>:  <code>MIT</code></li>
+
 </ul>
 
 ## License

+ 1 - 0
app/build.gradle.kts

@@ -67,6 +67,7 @@ dependencies {
     implementation("org.conscrypt:conscrypt-android:2.5.2")
     implementation("com.google.guava:guava:30.1.1-android")
     implementation("com.journeyapps:zxing-android-embedded:4.2.0")
+    implementation("org.ini4j:ini4j:0.5.4")
 
     implementation("com.simplecityapps:recyclerview-fastscroll:2.0.1") {
         exclude(group = "androidx.recyclerview")

+ 474 - 0
app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/10.json

@@ -0,0 +1,474 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 10,
+    "identityHash": "e429a05e6fa8d85cb18332d6a20b490e",
+    "entities": [
+      {
+        "tableName": "proxy_groups",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userOrder",
+            "columnName": "userOrder",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "ungrouped",
+            "columnName": "ungrouped",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "subscription",
+            "columnName": "subscription",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "order",
+            "columnName": "order",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "proxy_entities",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `ssrBean` BLOB, `vmessBean` BLOB, `vlessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `naiveBean` BLOB, `ptBean` BLOB, `rbBean` BLOB, `brookBean` BLOB, `hysteriaBean` BLOB, `snellBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `configBean` BLOB, `chainBean` BLOB, `balancerBean` BLOB)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "groupId",
+            "columnName": "groupId",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userOrder",
+            "columnName": "userOrder",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "tx",
+            "columnName": "tx",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "rx",
+            "columnName": "rx",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "status",
+            "columnName": "status",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "ping",
+            "columnName": "ping",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uuid",
+            "columnName": "uuid",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "error",
+            "columnName": "error",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "socksBean",
+            "columnName": "socksBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "httpBean",
+            "columnName": "httpBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ssBean",
+            "columnName": "ssBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ssrBean",
+            "columnName": "ssrBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "vmessBean",
+            "columnName": "vmessBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "vlessBean",
+            "columnName": "vlessBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "trojanBean",
+            "columnName": "trojanBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "trojanGoBean",
+            "columnName": "trojanGoBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "naiveBean",
+            "columnName": "naiveBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "ptBean",
+            "columnName": "ptBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "rbBean",
+            "columnName": "rbBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "brookBean",
+            "columnName": "brookBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "hysteriaBean",
+            "columnName": "hysteriaBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "snellBean",
+            "columnName": "snellBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "sshBean",
+            "columnName": "sshBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "wgBean",
+            "columnName": "wgBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "configBean",
+            "columnName": "configBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "chainBean",
+            "columnName": "chainBean",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "balancerBean",
+            "columnName": "balancerBean",
+            "affinity": "BLOB",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "groupId",
+            "unique": false,
+            "columnNames": [
+              "groupId"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "rules",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `attrs` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `reverse` INTEGER NOT NULL, `redirect` TEXT NOT NULL, `packages` TEXT NOT NULL, `appStatus` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userOrder",
+            "columnName": "userOrder",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "enabled",
+            "columnName": "enabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "domains",
+            "columnName": "domains",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "ip",
+            "columnName": "ip",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "port",
+            "columnName": "port",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sourcePort",
+            "columnName": "sourcePort",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "network",
+            "columnName": "network",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "source",
+            "columnName": "source",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "protocol",
+            "columnName": "protocol",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "attrs",
+            "columnName": "attrs",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "outbound",
+            "columnName": "outbound",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "reverse",
+            "columnName": "reverse",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "redirect",
+            "columnName": "redirect",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "packages",
+            "columnName": "packages",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "appStatus",
+            "columnName": "appStatus",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "stats",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `packageName` TEXT NOT NULL, `tcpConnections` INTEGER NOT NULL, `udpConnections` INTEGER NOT NULL, `uplink` INTEGER NOT NULL, `downlink` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "packageName",
+            "columnName": "packageName",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "tcpConnections",
+            "columnName": "tcpConnections",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "udpConnections",
+            "columnName": "udpConnections",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uplink",
+            "columnName": "uplink",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "downlink",
+            "columnName": "downlink",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "index_stats_packageName",
+            "unique": true,
+            "columnNames": [
+              "packageName"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_stats_packageName` ON `${TABLE_NAME}` (`packageName`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "KeyValuePair",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
+        "fields": [
+          {
+            "fieldPath": "key",
+            "columnName": "key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "valueType",
+            "columnName": "valueType",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "BLOB",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "key"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e429a05e6fa8d85cb18332d6a20b490e')"
+    ]
+  }
+}

+ 3 - 0
app/src/main/AndroidManifest.xml

@@ -180,6 +180,9 @@
         <activity
             android:name="io.nekohasekai.sagernet.ui.profile.SSHSettingsActivity"
             android:configChanges="uiMode" />
+        <activity
+            android:name="io.nekohasekai.sagernet.ui.profile.WireGuardSettingsActivity"
+            android:configChanges="uiMode" />
         <activity
             android:name="io.nekohasekai.sagernet.ui.profile.ConfigSettingsActivity"
             android:configChanges="uiMode" />

+ 497 - 0
app/src/main/java/com/wireguard/crypto/Curve25519.java

@@ -0,0 +1,497 @@
+/*
+ * Copyright © 2016 Southern Storm Software, Pty Ltd.
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.crypto;
+
+import androidx.annotation.Nullable;
+
+import java.util.Arrays;
+
+/**
+ * Implementation of Curve25519 ECDH.
+ * <p>
+ * This implementation was imported to WireGuard from noise-java:
+ * https://github.com/rweather/noise-java
+ * <p>
+ * This implementation is based on that from arduinolibs:
+ * https://github.com/rweather/arduinolibs
+ * <p>
+ * Differences in this version are due to using 26-bit limbs for the
+ * representation instead of the 8/16/32-bit limbs in the original.
+ * <p>
+ * References: http://cr.yp.to/ecdh.html, RFC 7748
+ */
+@SuppressWarnings({"MagicNumber", "NonConstantFieldWithUpperCaseName", "SuspiciousNameCombination"})
+public final class Curve25519 {
+    // Numbers modulo 2^255 - 19 are broken up into ten 26-bit words.
+    private static final int NUM_LIMBS_255BIT = 10;
+    private static final int NUM_LIMBS_510BIT = 20;
+
+    private final int[] A;
+    private final int[] AA;
+    private final int[] B;
+    private final int[] BB;
+    private final int[] C;
+    private final int[] CB;
+    private final int[] D;
+    private final int[] DA;
+    private final int[] E;
+    private final long[] t1;
+    private final int[] t2;
+    private final int[] x_1;
+    private final int[] x_2;
+    private final int[] x_3;
+    private final int[] z_2;
+    private final int[] z_3;
+
+    /**
+     * Constructs the temporary state holder for Curve25519 evaluation.
+     */
+    private Curve25519() {
+        // Allocate memory for all of the temporary variables we will need.
+        x_1 = new int[NUM_LIMBS_255BIT];
+        x_2 = new int[NUM_LIMBS_255BIT];
+        x_3 = new int[NUM_LIMBS_255BIT];
+        z_2 = new int[NUM_LIMBS_255BIT];
+        z_3 = new int[NUM_LIMBS_255BIT];
+        A = new int[NUM_LIMBS_255BIT];
+        B = new int[NUM_LIMBS_255BIT];
+        C = new int[NUM_LIMBS_255BIT];
+        D = new int[NUM_LIMBS_255BIT];
+        E = new int[NUM_LIMBS_255BIT];
+        AA = new int[NUM_LIMBS_255BIT];
+        BB = new int[NUM_LIMBS_255BIT];
+        DA = new int[NUM_LIMBS_255BIT];
+        CB = new int[NUM_LIMBS_255BIT];
+        t1 = new long[NUM_LIMBS_510BIT];
+        t2 = new int[NUM_LIMBS_510BIT];
+    }
+
+    /**
+     * Conditional swap of two values.
+     *
+     * @param select Set to 1 to swap, 0 to leave as-is.
+     * @param x      The first value.
+     * @param y      The second value.
+     */
+    private static void cswap(int select, final int[] x, final int[] y) {
+        select = -select;
+        for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
+            final int dummy = select & (x[index] ^ y[index]);
+            x[index] ^= dummy;
+            y[index] ^= dummy;
+        }
+    }
+
+    /**
+     * Evaluates the Curve25519 curve.
+     *
+     * @param result     Buffer to place the result of the evaluation into.
+     * @param offset     Offset into the result buffer.
+     * @param privateKey The private key to use in the evaluation.
+     * @param publicKey  The public key to use in the evaluation, or null
+     *                   if the base point of the curve should be used.
+     */
+    public static void eval(final byte[] result, final int offset,
+                            final byte[] privateKey, @Nullable final byte[] publicKey) {
+        final Curve25519 state = new Curve25519();
+        try {
+            // Unpack the public key value.  If null, use 9 as the base point.
+            Arrays.fill(state.x_1, 0);
+            if (publicKey != null) {
+                // Convert the input value from little-endian into 26-bit limbs.
+                for (int index = 0; index < 32; ++index) {
+                    final int bit = (index * 8) % 26;
+                    final int word = (index * 8) / 26;
+                    final int value = publicKey[index] & 0xFF;
+                    if (bit <= (26 - 8)) {
+                        state.x_1[word] |= value << bit;
+                    } else {
+                        state.x_1[word] |= value << bit;
+                        state.x_1[word] &= 0x03FFFFFF;
+                        state.x_1[word + 1] |= value >> (26 - bit);
+                    }
+                }
+
+                // Just in case, we reduce the number modulo 2^255 - 19 to
+                // make sure that it is in range of the field before we start.
+                // This eliminates values between 2^255 - 19 and 2^256 - 1.
+                state.reduceQuick(state.x_1);
+                state.reduceQuick(state.x_1);
+            } else {
+                state.x_1[0] = 9;
+            }
+
+            // Initialize the other temporary variables.
+            Arrays.fill(state.x_2, 0);            // x_2 = 1
+            state.x_2[0] = 1;
+            Arrays.fill(state.z_2, 0);            // z_2 = 0
+            System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length);  // x_3 = x_1
+            Arrays.fill(state.z_3, 0);            // z_3 = 1
+            state.z_3[0] = 1;
+
+            // Evaluate the curve for every bit of the private key.
+            state.evalCurve(privateKey);
+
+            // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19.
+            state.recip(state.z_3, state.z_2);
+            state.mul(state.x_2, state.x_2, state.z_3);
+
+            // Convert x_2 into little-endian in the result buffer.
+            for (int index = 0; index < 32; ++index) {
+                final int bit = (index * 8) % 26;
+                final int word = (index * 8) / 26;
+                if (bit <= (26 - 8))
+                    result[offset + index] = (byte) (state.x_2[word] >> bit);
+                else
+                    result[offset + index] = (byte) ((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit)));
+            }
+        } finally {
+            // Clean up all temporary state before we exit.
+            state.destroy();
+        }
+    }
+
+    /**
+     * Subtracts two numbers modulo 2^255 - 19.
+     *
+     * @param result The result.
+     * @param x      The first number to subtract.
+     * @param y      The second number to subtract.
+     */
+    private static void sub(final int[] result, final int[] x, final int[] y) {
+        int index;
+        int borrow;
+
+        // Subtract y from x to generate the intermediate result.
+        borrow = 0;
+        for (index = 0; index < NUM_LIMBS_255BIT; ++index) {
+            borrow = x[index] - y[index] - ((borrow >> 26) & 0x01);
+            result[index] = borrow & 0x03FFFFFF;
+        }
+
+        // If we had a borrow, then the result has gone negative and we
+        // have to add 2^255 - 19 to the result to make it positive again.
+        // The top bits of "borrow" will be all 1's if there is a borrow
+        // or it will be all 0's if there was no borrow.  Easiest is to
+        // conditionally subtract 19 and then mask off the high bits.
+        borrow = result[0] - ((-((borrow >> 26) & 0x01)) & 19);
+        result[0] = borrow & 0x03FFFFFF;
+        for (index = 1; index < NUM_LIMBS_255BIT; ++index) {
+            borrow = result[index] - ((borrow >> 26) & 0x01);
+            result[index] = borrow & 0x03FFFFFF;
+        }
+        result[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
+    }
+
+    /**
+     * Adds two numbers modulo 2^255 - 19.
+     *
+     * @param result The result.
+     * @param x      The first number to add.
+     * @param y      The second number to add.
+     */
+    private void add(final int[] result, final int[] x, final int[] y) {
+        int carry = x[0] + y[0];
+        result[0] = carry & 0x03FFFFFF;
+        for (int index = 1; index < NUM_LIMBS_255BIT; ++index) {
+            carry = (carry >> 26) + x[index] + y[index];
+            result[index] = carry & 0x03FFFFFF;
+        }
+        reduceQuick(result);
+    }
+
+    /**
+     * Destroy all sensitive data in this object.
+     */
+    private void destroy() {
+        // Destroy all temporary variables.
+        Arrays.fill(x_1, 0);
+        Arrays.fill(x_2, 0);
+        Arrays.fill(x_3, 0);
+        Arrays.fill(z_2, 0);
+        Arrays.fill(z_3, 0);
+        Arrays.fill(A, 0);
+        Arrays.fill(B, 0);
+        Arrays.fill(C, 0);
+        Arrays.fill(D, 0);
+        Arrays.fill(E, 0);
+        Arrays.fill(AA, 0);
+        Arrays.fill(BB, 0);
+        Arrays.fill(DA, 0);
+        Arrays.fill(CB, 0);
+        Arrays.fill(t1, 0L);
+        Arrays.fill(t2, 0);
+    }
+
+    /**
+     * Evaluates the curve for every bit in a secret key.
+     *
+     * @param s The 32-byte secret key.
+     */
+    private void evalCurve(final byte[] s) {
+        int sposn = 31;
+        int sbit = 6;
+        int svalue = s[sposn] | 0x40;
+        int swap = 0;
+
+        // Iterate over all 255 bits of "s" from the highest to the lowest.
+        // We ignore the high bit of the 256-bit representation of "s".
+        while (true) {
+            // Conditional swaps on entry to this bit but only if we
+            // didn't swap on the previous bit.
+            final int select = (svalue >> sbit) & 0x01;
+            swap ^= select;
+            cswap(swap, x_2, x_3);
+            cswap(swap, z_2, z_3);
+            swap = select;
+
+            // Evaluate the curve.
+            add(A, x_2, z_2);               // A = x_2 + z_2
+            square(AA, A);                  // AA = A^2
+            sub(B, x_2, z_2);               // B = x_2 - z_2
+            square(BB, B);                  // BB = B^2
+            sub(E, AA, BB);                 // E = AA - BB
+            add(C, x_3, z_3);               // C = x_3 + z_3
+            sub(D, x_3, z_3);               // D = x_3 - z_3
+            mul(DA, D, A);                  // DA = D * A
+            mul(CB, C, B);                  // CB = C * B
+            add(x_3, DA, CB);               // x_3 = (DA + CB)^2
+            square(x_3, x_3);
+            sub(z_3, DA, CB);               // z_3 = x_1 * (DA - CB)^2
+            square(z_3, z_3);
+            mul(z_3, z_3, x_1);
+            mul(x_2, AA, BB);               // x_2 = AA * BB
+            mulA24(z_2, E);                 // z_2 = E * (AA + a24 * E)
+            add(z_2, z_2, AA);
+            mul(z_2, z_2, E);
+
+            // Move onto the next lower bit of "s".
+            if (sbit > 0) {
+                --sbit;
+            } else if (sposn == 0) {
+                break;
+            } else if (sposn == 1) {
+                --sposn;
+                svalue = s[sposn] & 0xF8;
+                sbit = 7;
+            } else {
+                --sposn;
+                svalue = s[sposn];
+                sbit = 7;
+            }
+        }
+
+        // Final conditional swaps.
+        cswap(swap, x_2, x_3);
+        cswap(swap, z_2, z_3);
+    }
+
+    /**
+     * Multiplies two numbers modulo 2^255 - 19.
+     *
+     * @param result The result.
+     * @param x      The first number to multiply.
+     * @param y      The second number to multiply.
+     */
+    private void mul(final int[] result, final int[] x, final int[] y) {
+        // Multiply the two numbers to create the intermediate result.
+        long v = x[0];
+        for (int i = 0; i < NUM_LIMBS_255BIT; ++i) {
+            t1[i] = v * y[i];
+        }
+        for (int i = 1; i < NUM_LIMBS_255BIT; ++i) {
+            v = x[i];
+            for (int j = 0; j < (NUM_LIMBS_255BIT - 1); ++j) {
+                t1[i + j] += v * y[j];
+            }
+            t1[i + NUM_LIMBS_255BIT - 1] = v * y[NUM_LIMBS_255BIT - 1];
+        }
+
+        // Propagate carries and convert back into 26-bit words.
+        v = t1[0];
+        t2[0] = ((int) v) & 0x03FFFFFF;
+        for (int i = 1; i < NUM_LIMBS_510BIT; ++i) {
+            v = (v >> 26) + t1[i];
+            t2[i] = ((int) v) & 0x03FFFFFF;
+        }
+
+        // Reduce the result modulo 2^255 - 19.
+        reduce(result, t2, NUM_LIMBS_255BIT);
+    }
+
+    /**
+     * Multiplies a number by the a24 constant, modulo 2^255 - 19.
+     *
+     * @param result The result.
+     * @param x      The number to multiply by a24.
+     */
+    private void mulA24(final int[] result, final int[] x) {
+        final long a24 = 121665;
+        long carry = 0;
+        for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
+            carry += a24 * x[index];
+            t2[index] = ((int) carry) & 0x03FFFFFF;
+            carry >>= 26;
+        }
+        t2[NUM_LIMBS_255BIT] = ((int) carry) & 0x03FFFFFF;
+        reduce(result, t2, 1);
+    }
+
+    /**
+     * Raise x to the power of (2^250 - 1).
+     *
+     * @param result The result.  Must not overlap with x.
+     * @param x      The argument.
+     */
+    private void pow250(final int[] result, final int[] x) {
+        // The big-endian hexadecimal expansion of (2^250 - 1) is:
+        // 03FFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
+        //
+        // The naive implementation needs to do 2 multiplications per 1 bit and
+        // 1 multiplication per 0 bit.  We can improve upon this by creating a
+        // pattern 0000000001 ... 0000000001.  If we square and multiply the
+        // pattern by itself we can turn the pattern into the partial results
+        // 0000000011 ... 0000000011, 0000000111 ... 0000000111, etc.
+        // This averages out to about 1.1 multiplications per 1 bit instead of 2.
+
+        // Build a pattern of 250 bits in length of repeated copies of 0000000001.
+        square(A, x);
+        for (int j = 0; j < 9; ++j)
+            square(A, A);
+        mul(result, A, x);
+        for (int i = 0; i < 23; ++i) {
+            for (int j = 0; j < 10; ++j)
+                square(A, A);
+            mul(result, result, A);
+        }
+
+        // Multiply bit-shifted versions of the 0000000001 pattern into
+        // the result to "fill in" the gaps in the pattern.
+        square(A, result);
+        mul(result, result, A);
+        for (int j = 0; j < 8; ++j) {
+            square(A, A);
+            mul(result, result, A);
+        }
+    }
+
+    /**
+     * Computes the reciprocal of a number modulo 2^255 - 19.
+     *
+     * @param result The result.  Must not overlap with x.
+     * @param x      The argument.
+     */
+    private void recip(final int[] result, final int[] x) {
+        // The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19.
+        // The big-endian hexadecimal expansion of (p - 2) is:
+        // 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB
+        // Start with the 250 upper bits of the expansion of (p - 2).
+        pow250(result, x);
+
+        // Deal with the 5 lowest bits of (p - 2), 01011, from highest to lowest.
+        square(result, result);
+        square(result, result);
+        mul(result, result, x);
+        square(result, result);
+        square(result, result);
+        mul(result, result, x);
+        square(result, result);
+        mul(result, result, x);
+    }
+
+    /**
+     * Reduce a number modulo 2^255 - 19.
+     *
+     * @param result The result.
+     * @param x      The value to be reduced.  This array will be
+     *               modified during the reduction.
+     * @param size   The number of limbs in the high order half of x.
+     */
+    private void reduce(final int[] result, final int[] x, final int size) {
+        // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will
+        // either produce the answer we want or it will produce a
+        // value of the form "answer + j * (2^255 - 19)".  There are
+        // 5 left-over bits in the top-most limb of the bottom half.
+        int carry = 0;
+        int limb = x[NUM_LIMBS_255BIT - 1] >> 21;
+        x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
+        for (int index = 0; index < size; ++index) {
+            limb += x[NUM_LIMBS_255BIT + index] << 5;
+            carry += (limb & 0x03FFFFFF) * 19 + x[index];
+            x[index] = carry & 0x03FFFFFF;
+            limb >>= 26;
+            carry >>= 26;
+        }
+        if (size < NUM_LIMBS_255BIT) {
+            // The high order half of the number is short; e.g. for mulA24().
+            // Propagate the carry through the rest of the low order part.
+            for (int index = size; index < NUM_LIMBS_255BIT; ++index) {
+                carry += x[index];
+                x[index] = carry & 0x03FFFFFF;
+                carry >>= 26;
+            }
+        }
+
+        // The "j" value may still be too large due to the final carry-out.
+        // We must repeat the reduction.  If we already have the answer,
+        // then this won't do any harm but we must still do the calculation
+        // to preserve the overall timing.  The "j" value will be between
+        // 0 and 19, which means that the carry we care about is in the
+        // top 5 bits of the highest limb of the bottom half.
+        carry = (x[NUM_LIMBS_255BIT - 1] >> 21) * 19;
+        x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
+        for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
+            carry += x[index];
+            result[index] = carry & 0x03FFFFFF;
+            carry >>= 26;
+        }
+
+        // At this point "x" will either be the answer or it will be the
+        // answer plus (2^255 - 19).  Perform a trial subtraction to
+        // complete the reduction process.
+        reduceQuick(result);
+    }
+
+    /**
+     * Reduces a number modulo 2^255 - 19 where it is known that the
+     * number can be reduced with only 1 trial subtraction.
+     *
+     * @param x The number to reduce, and the result.
+     */
+    private void reduceQuick(final int[] x) {
+        // Perform a trial subtraction of (2^255 - 19) from "x" which is
+        // equivalent to adding 19 and subtracting 2^255.  We add 19 here;
+        // the subtraction of 2^255 occurs in the next step.
+        int carry = 19;
+        for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
+            carry += x[index];
+            t2[index] = carry & 0x03FFFFFF;
+            carry >>= 26;
+        }
+
+        // If there was a borrow, then the original "x" is the correct answer.
+        // If there was no borrow, then "t2" is the correct answer.  Select the
+        // correct answer but do it in a way that instruction timing will not
+        // reveal which value was selected.  Borrow will occur if bit 21 of
+        // "t2" is zero.  Turn the bit into a selection mask.
+        final int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01);
+        final int nmask = ~mask;
+        t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
+        for (int index = 0; index < NUM_LIMBS_255BIT; ++index)
+            x[index] = (x[index] & nmask) | (t2[index] & mask);
+    }
+
+    /**
+     * Squares a number modulo 2^255 - 19.
+     *
+     * @param result The result.
+     * @param x      The number to square.
+     */
+    private void square(final int[] result, final int[] x) {
+        mul(result, x, x);
+    }
+}

+ 2508 - 0
app/src/main/java/com/wireguard/crypto/Ed25519.java

@@ -0,0 +1,2508 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * Copyright 2017 Google Inc.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.crypto;
+
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+/**
+ * Implementation of Ed25519 signature verification.
+ *
+ * <p>This implementation is based on the ed25519/ref10 implementation in NaCl.</p>
+ *
+ * <p>It implements this twisted Edwards curve:
+ *
+ * <pre>
+ * -x^2 + y^2 = 1 + (-121665 / 121666 mod 2^255-19)*x^2*y^2
+ * </pre>
+ *
+ * @see <a href="https://eprint.iacr.org/2008/013.pdf">Bernstein D.J., Birkner P., Joye M., Lange
+ * T., Peters C. (2008) Twisted Edwards Curves</a>
+ * @see <a href="https://eprint.iacr.org/2008/522.pdf">Hisil H., Wong K.KH., Carter G., Dawson E.
+ * (2008) Twisted Edwards Curves Revisited</a>
+ */
+public final class Ed25519 {
+
+    // d = -121665 / 121666 mod 2^255-19
+    private static final long[] D;
+    // 2d
+    private static final long[] D2;
+    // 2^((p-1)/4) mod p where p = 2^255-19
+    private static final long[] SQRTM1;
+
+    /**
+     * Base point for the Edwards twisted curve = (x, 4/5) and its exponentiations. B_TABLE[i][j] =
+     * (j+1)*256^i*B for i in [0, 32) and j in [0, 8). Base point B = B_TABLE[0][0]
+     */
+    private static final CachedXYT[][] B_TABLE;
+    private static final CachedXYT[] B2;
+
+    private static final BigInteger P_BI =
+            BigInteger.valueOf(2).pow(255).subtract(BigInteger.valueOf(19));
+    private static final BigInteger D_BI =
+            BigInteger.valueOf(-121665).multiply(BigInteger.valueOf(121666).modInverse(P_BI)).mod(P_BI);
+    private static final BigInteger D2_BI = BigInteger.valueOf(2).multiply(D_BI).mod(P_BI);
+    private static final BigInteger SQRTM1_BI =
+            BigInteger.valueOf(2).modPow(P_BI.subtract(BigInteger.ONE).divide(BigInteger.valueOf(4)), P_BI);
+
+    private Ed25519() {
+    }
+
+    private static class Point {
+        private BigInteger x;
+        private BigInteger y;
+    }
+
+    private static BigInteger recoverX(BigInteger y) {
+        // x^2 = (y^2 - 1) / (d * y^2 + 1) mod 2^255-19
+        BigInteger xx =
+                y.pow(2)
+                        .subtract(BigInteger.ONE)
+                        .multiply(D_BI.multiply(y.pow(2)).add(BigInteger.ONE).modInverse(P_BI));
+        BigInteger x = xx.modPow(P_BI.add(BigInteger.valueOf(3)).divide(BigInteger.valueOf(8)), P_BI);
+        if (!x.pow(2).subtract(xx).mod(P_BI).equals(BigInteger.ZERO)) {
+            x = x.multiply(SQRTM1_BI).mod(P_BI);
+        }
+        if (x.testBit(0)) {
+            x = P_BI.subtract(x);
+        }
+        return x;
+    }
+
+    private static Point edwards(Point a, Point b) {
+        Point o = new Point();
+        BigInteger xxyy = D_BI.multiply(a.x.multiply(b.x).multiply(a.y).multiply(b.y)).mod(P_BI);
+        o.x =
+                (a.x.multiply(b.y).add(b.x.multiply(a.y)))
+                        .multiply(BigInteger.ONE.add(xxyy).modInverse(P_BI))
+                        .mod(P_BI);
+        o.y =
+                (a.y.multiply(b.y).add(a.x.multiply(b.x)))
+                        .multiply(BigInteger.ONE.subtract(xxyy).modInverse(P_BI))
+                        .mod(P_BI);
+        return o;
+    }
+
+    private static byte[] toLittleEndian(BigInteger n) {
+        byte[] b = new byte[32];
+        byte[] nBytes = n.toByteArray();
+        System.arraycopy(nBytes, 0, b, 32 - nBytes.length, nBytes.length);
+        for (int i = 0; i < b.length / 2; i++) {
+            byte t = b[i];
+            b[i] = b[b.length - i - 1];
+            b[b.length - i - 1] = t;
+        }
+        return b;
+    }
+
+    private static CachedXYT getCachedXYT(Point p) {
+        return new CachedXYT(
+                Field25519.expand(toLittleEndian(p.y.add(p.x).mod(P_BI))),
+                Field25519.expand(toLittleEndian(p.y.subtract(p.x).mod(P_BI))),
+                Field25519.expand(toLittleEndian(D2_BI.multiply(p.x).multiply(p.y).mod(P_BI))));
+    }
+
+    static {
+        Point b = new Point();
+        b.y = BigInteger.valueOf(4).multiply(BigInteger.valueOf(5).modInverse(P_BI)).mod(P_BI);
+        b.x = recoverX(b.y);
+
+        D = Field25519.expand(toLittleEndian(D_BI));
+        D2 = Field25519.expand(toLittleEndian(D2_BI));
+        SQRTM1 = Field25519.expand(toLittleEndian(SQRTM1_BI));
+
+        Point bi = b;
+        B_TABLE = new CachedXYT[32][8];
+        for (int i = 0; i < 32; i++) {
+            Point bij = bi;
+            for (int j = 0; j < 8; j++) {
+                B_TABLE[i][j] = getCachedXYT(bij);
+                bij = edwards(bij, bi);
+            }
+            for (int j = 0; j < 8; j++) {
+                bi = edwards(bi, bi);
+            }
+        }
+        bi = b;
+        Point b2 = edwards(b, b);
+        B2 = new CachedXYT[8];
+        for (int i = 0; i < 8; i++) {
+            B2[i] = getCachedXYT(bi);
+            bi = edwards(bi, b2);
+        }
+    }
+
+    private static final int PUBLIC_KEY_LEN = Field25519.FIELD_LEN;
+    private static final int SIGNATURE_LEN = Field25519.FIELD_LEN * 2;
+
+    /**
+     * Defines field 25519 function based on <a
+     * href="https://github.com/agl/curve25519-donna/blob/master/curve25519-donna.c">curve25519-donna C
+     * implementation</a> (mostly identical).
+     *
+     * <p>Field elements are written as an array of signed, 64-bit limbs (an array of longs), least
+     * significant first. The value of the field element is:
+     *
+     * <pre>
+     * x[0] + 2^26·x[1] + 2^51·x[2] + 2^77·x[3] + 2^102·x[4] + 2^128·x[5] + 2^153·x[6] + 2^179·x[7] +
+     * 2^204·x[8] + 2^230·x[9],
+     * </pre>
+     *
+     * <p>i.e. the limbs are 26, 25, 26, 25, ... bits wide.
+     */
+    private static final class Field25519 {
+        /**
+         * During Field25519 computation, the mixed radix representation may be in different forms:
+         * <ul>
+         *  <li> Reduced-size form: the array has size at most 10.
+         *  <li> Non-reduced-size form: the array is not reduced modulo 2^255 - 19 and has size at most
+         *  19.
+         * </ul>
+         * <p>
+         * TODO(quannguyen):
+         * <ul>
+         *  <li> Clarify ill-defined terminologies.
+         *  <li> The reduction procedure is different from DJB's paper
+         *  (http://cr.yp.to/ecdh/curve25519-20060209.pdf). The coefficients after reducing degree and
+         *  reducing coefficients aren't guaranteed to be in range {-2^25, ..., 2^25}. We should check to
+         *  see what's going on.
+         *  <li> Consider using method mult() everywhere and making product() private.
+         * </ul>
+         */
+
+        static final int FIELD_LEN = 32;
+        static final int LIMB_CNT = 10;
+        private static final long TWO_TO_25 = 1 << 25;
+        private static final long TWO_TO_26 = TWO_TO_25 << 1;
+
+        private static final int[] EXPAND_START = {0, 3, 6, 9, 12, 16, 19, 22, 25, 28};
+        private static final int[] EXPAND_SHIFT = {0, 2, 3, 5, 6, 0, 1, 3, 4, 6};
+        private static final int[] MASK = {0x3ffffff, 0x1ffffff};
+        private static final int[] SHIFT = {26, 25};
+
+        /**
+         * Sums two numbers: output = in1 + in2
+         * <p>
+         * On entry: in1, in2 are in reduced-size form.
+         */
+        static void sum(long[] output, long[] in1, long[] in2) {
+            for (int i = 0; i < LIMB_CNT; i++) {
+                output[i] = in1[i] + in2[i];
+            }
+        }
+
+        /**
+         * Sums two numbers: output += in
+         * <p>
+         * On entry: in is in reduced-size form.
+         */
+        static void sum(long[] output, long[] in) {
+            sum(output, output, in);
+        }
+
+        /**
+         * Find the difference of two numbers: output = in1 - in2
+         * (note the order of the arguments!).
+         * <p>
+         * On entry: in1, in2 are in reduced-size form.
+         */
+        static void sub(long[] output, long[] in1, long[] in2) {
+            for (int i = 0; i < LIMB_CNT; i++) {
+                output[i] = in1[i] - in2[i];
+            }
+        }
+
+        /**
+         * Find the difference of two numbers: output = in - output
+         * (note the order of the arguments!).
+         * <p>
+         * On entry: in, output are in reduced-size form.
+         */
+        static void sub(long[] output, long[] in) {
+            sub(output, in, output);
+        }
+
+        /**
+         * Multiply a number by a scalar: output = in * scalar
+         */
+        static void scalarProduct(long[] output, long[] in, long scalar) {
+            for (int i = 0; i < LIMB_CNT; i++) {
+                output[i] = in[i] * scalar;
+            }
+        }
+
+        /**
+         * Multiply two numbers: out = in2 * in
+         * <p>
+         * output must be distinct to both inputs. The inputs are reduced coefficient form,
+         * the output is not.
+         * <p>
+         * out[x] <= 14 * the largest product of the input limbs.
+         */
+        static void product(long[] out, long[] in2, long[] in) {
+            out[0] = in2[0] * in[0];
+            out[1] = in2[0] * in[1]
+                    + in2[1] * in[0];
+            out[2] = 2 * in2[1] * in[1]
+                    + in2[0] * in[2]
+                    + in2[2] * in[0];
+            out[3] = in2[1] * in[2]
+                    + in2[2] * in[1]
+                    + in2[0] * in[3]
+                    + in2[3] * in[0];
+            out[4] = in2[2] * in[2]
+                    + 2 * (in2[1] * in[3] + in2[3] * in[1])
+                    + in2[0] * in[4]
+                    + in2[4] * in[0];
+            out[5] = in2[2] * in[3]
+                    + in2[3] * in[2]
+                    + in2[1] * in[4]
+                    + in2[4] * in[1]
+                    + in2[0] * in[5]
+                    + in2[5] * in[0];
+            out[6] = 2 * (in2[3] * in[3] + in2[1] * in[5] + in2[5] * in[1])
+                    + in2[2] * in[4]
+                    + in2[4] * in[2]
+                    + in2[0] * in[6]
+                    + in2[6] * in[0];
+            out[7] = in2[3] * in[4]
+                    + in2[4] * in[3]
+                    + in2[2] * in[5]
+                    + in2[5] * in[2]
+                    + in2[1] * in[6]
+                    + in2[6] * in[1]
+                    + in2[0] * in[7]
+                    + in2[7] * in[0];
+            out[8] = in2[4] * in[4]
+                    + 2 * (in2[3] * in[5] + in2[5] * in[3] + in2[1] * in[7] + in2[7] * in[1])
+                    + in2[2] * in[6]
+                    + in2[6] * in[2]
+                    + in2[0] * in[8]
+                    + in2[8] * in[0];
+            out[9] = in2[4] * in[5]
+                    + in2[5] * in[4]
+                    + in2[3] * in[6]
+                    + in2[6] * in[3]
+                    + in2[2] * in[7]
+                    + in2[7] * in[2]
+                    + in2[1] * in[8]
+                    + in2[8] * in[1]
+                    + in2[0] * in[9]
+                    + in2[9] * in[0];
+            out[10] =
+                    2 * (in2[5] * in[5] + in2[3] * in[7] + in2[7] * in[3] + in2[1] * in[9] + in2[9] * in[1])
+                            + in2[4] * in[6]
+                            + in2[6] * in[4]
+                            + in2[2] * in[8]
+                            + in2[8] * in[2];
+            out[11] = in2[5] * in[6]
+                    + in2[6] * in[5]
+                    + in2[4] * in[7]
+                    + in2[7] * in[4]
+                    + in2[3] * in[8]
+                    + in2[8] * in[3]
+                    + in2[2] * in[9]
+                    + in2[9] * in[2];
+            out[12] = in2[6] * in[6]
+                    + 2 * (in2[5] * in[7] + in2[7] * in[5] + in2[3] * in[9] + in2[9] * in[3])
+                    + in2[4] * in[8]
+                    + in2[8] * in[4];
+            out[13] = in2[6] * in[7]
+                    + in2[7] * in[6]
+                    + in2[5] * in[8]
+                    + in2[8] * in[5]
+                    + in2[4] * in[9]
+                    + in2[9] * in[4];
+            out[14] = 2 * (in2[7] * in[7] + in2[5] * in[9] + in2[9] * in[5])
+                    + in2[6] * in[8]
+                    + in2[8] * in[6];
+            out[15] = in2[7] * in[8]
+                    + in2[8] * in[7]
+                    + in2[6] * in[9]
+                    + in2[9] * in[6];
+            out[16] = in2[8] * in[8]
+                    + 2 * (in2[7] * in[9] + in2[9] * in[7]);
+            out[17] = in2[8] * in[9]
+                    + in2[9] * in[8];
+            out[18] = 2 * in2[9] * in[9];
+        }
+
+        /**
+         * Reduce a field element by calling reduceSizeByModularReduction and reduceCoefficients.
+         *
+         * @param input  An input array of any length. If the array has 19 elements, it will be used as
+         *               temporary buffer and its contents changed.
+         * @param output An output array of size LIMB_CNT. After the call |output[i]| < 2^26 will hold.
+         */
+        static void reduce(long[] input, long[] output) {
+            long[] tmp;
+            if (input.length == 19) {
+                tmp = input;
+            } else {
+                tmp = new long[19];
+                System.arraycopy(input, 0, tmp, 0, input.length);
+            }
+            reduceSizeByModularReduction(tmp);
+            reduceCoefficients(tmp);
+            System.arraycopy(tmp, 0, output, 0, LIMB_CNT);
+        }
+
+        /**
+         * Reduce a long form to a reduced-size form by taking the input mod 2^255 - 19.
+         * <p>
+         * On entry: |output[i]| < 14*2^54
+         * On exit: |output[0..8]| < 280*2^54
+         */
+        static void reduceSizeByModularReduction(long[] output) {
+            // The coefficients x[10], x[11],..., x[18] are eliminated by reduction modulo 2^255 - 19.
+            // For example, the coefficient x[18] is multiplied by 19 and added to the coefficient x[8].
+            //
+            // Each of these shifts and adds ends up multiplying the value by 19.
+            //
+            // For output[0..8], the absolute entry value is < 14*2^54 and we add, at most, 19*14*2^54 thus,
+            // on exit, |output[0..8]| < 280*2^54.
+            output[8] += output[18] << 4;
+            output[8] += output[18] << 1;
+            output[8] += output[18];
+            output[7] += output[17] << 4;
+            output[7] += output[17] << 1;
+            output[7] += output[17];
+            output[6] += output[16] << 4;
+            output[6] += output[16] << 1;
+            output[6] += output[16];
+            output[5] += output[15] << 4;
+            output[5] += output[15] << 1;
+            output[5] += output[15];
+            output[4] += output[14] << 4;
+            output[4] += output[14] << 1;
+            output[4] += output[14];
+            output[3] += output[13] << 4;
+            output[3] += output[13] << 1;
+            output[3] += output[13];
+            output[2] += output[12] << 4;
+            output[2] += output[12] << 1;
+            output[2] += output[12];
+            output[1] += output[11] << 4;
+            output[1] += output[11] << 1;
+            output[1] += output[11];
+            output[0] += output[10] << 4;
+            output[0] += output[10] << 1;
+            output[0] += output[10];
+        }
+
+        /**
+         * Reduce all coefficients of the short form input so that |x| < 2^26.
+         * <p>
+         * On entry: |output[i]| < 280*2^54
+         */
+        static void reduceCoefficients(long[] output) {
+            output[10] = 0;
+
+            for (int i = 0; i < LIMB_CNT; i += 2) {
+                long over = output[i] / TWO_TO_26;
+                // The entry condition (that |output[i]| < 280*2^54) means that over is, at most, 280*2^28 in
+                // the first iteration of this loop. This is added to the next limb and we can approximate the
+                // resulting bound of that limb by 281*2^54.
+                output[i] -= over << 26;
+                output[i + 1] += over;
+
+                // For the first iteration, |output[i+1]| < 281*2^54, thus |over| < 281*2^29. When this is
+                // added to the next limb, the resulting bound can be approximated as 281*2^54.
+                //
+                // For subsequent iterations of the loop, 281*2^54 remains a conservative bound and no
+                // overflow occurs.
+                over = output[i + 1] / TWO_TO_25;
+                output[i + 1] -= over << 25;
+                output[i + 2] += over;
+            }
+            // Now |output[10]| < 281*2^29 and all other coefficients are reduced.
+            output[0] += output[10] << 4;
+            output[0] += output[10] << 1;
+            output[0] += output[10];
+
+            output[10] = 0;
+            // Now output[1..9] are reduced, and |output[0]| < 2^26 + 19*281*2^29 so |over| will be no more
+            // than 2^16.
+            long over = output[0] / TWO_TO_26;
+            output[0] -= over << 26;
+            output[1] += over;
+            // Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 2^16 < 2^26. The bound on
+            // |output[1]| is sufficient to meet our needs.
+        }
+
+        /**
+         * A helpful wrapper around {@ref Field25519#product}: output = in * in2.
+         * <p>
+         * On entry: |in[i]| < 2^27 and |in2[i]| < 2^27.
+         * <p>
+         * The output is reduced degree (indeed, one need only provide storage for 10 limbs) and
+         * |output[i]| < 2^26.
+         */
+        static void mult(long[] output, long[] in, long[] in2) {
+            long[] t = new long[19];
+            product(t, in, in2);
+            // |t[i]| < 2^26
+            reduce(t, output);
+        }
+
+        /**
+         * Square a number: out = in**2
+         * <p>
+         * output must be distinct from the input. The inputs are reduced coefficient form, the output is
+         * not.
+         * <p>
+         * out[x] <= 14 * the largest product of the input limbs.
+         */
+        private static void squareInner(long[] out, long[] in) {
+            out[0] = in[0] * in[0];
+            out[1] = 2 * in[0] * in[1];
+            out[2] = 2 * (in[1] * in[1] + in[0] * in[2]);
+            out[3] = 2 * (in[1] * in[2] + in[0] * in[3]);
+            out[4] = in[2] * in[2]
+                    + 4 * in[1] * in[3]
+                    + 2 * in[0] * in[4];
+            out[5] = 2 * (in[2] * in[3] + in[1] * in[4] + in[0] * in[5]);
+            out[6] = 2 * (in[3] * in[3] + in[2] * in[4] + in[0] * in[6] + 2 * in[1] * in[5]);
+            out[7] = 2 * (in[3] * in[4] + in[2] * in[5] + in[1] * in[6] + in[0] * in[7]);
+            out[8] = in[4] * in[4]
+                    + 2 * (in[2] * in[6] + in[0] * in[8] + 2 * (in[1] * in[7] + in[3] * in[5]));
+            out[9] = 2 * (in[4] * in[5] + in[3] * in[6] + in[2] * in[7] + in[1] * in[8] + in[0] * in[9]);
+            out[10] = 2 * (in[5] * in[5]
+                    + in[4] * in[6]
+                    + in[2] * in[8]
+                    + 2 * (in[3] * in[7] + in[1] * in[9]));
+            out[11] = 2 * (in[5] * in[6] + in[4] * in[7] + in[3] * in[8] + in[2] * in[9]);
+            out[12] = in[6] * in[6]
+                    + 2 * (in[4] * in[8] + 2 * (in[5] * in[7] + in[3] * in[9]));
+            out[13] = 2 * (in[6] * in[7] + in[5] * in[8] + in[4] * in[9]);
+            out[14] = 2 * (in[7] * in[7] + in[6] * in[8] + 2 * in[5] * in[9]);
+            out[15] = 2 * (in[7] * in[8] + in[6] * in[9]);
+            out[16] = in[8] * in[8] + 4 * in[7] * in[9];
+            out[17] = 2 * in[8] * in[9];
+            out[18] = 2 * in[9] * in[9];
+        }
+
+        /**
+         * Returns in^2.
+         * <p>
+         * On entry: The |in| argument is in reduced coefficients form and |in[i]| < 2^27.
+         * <p>
+         * On exit: The |output| argument is in reduced coefficients form (indeed, one need only provide
+         * storage for 10 limbs) and |out[i]| < 2^26.
+         */
+        static void square(long[] output, long[] in) {
+            long[] t = new long[19];
+            squareInner(t, in);
+            // |t[i]| < 14*2^54 because the largest product of two limbs will be < 2^(27+27) and SquareInner
+            // adds together, at most, 14 of those products.
+            reduce(t, output);
+        }
+
+        /**
+         * Takes a little-endian, 32-byte number and expands it into mixed radix form.
+         */
+        static long[] expand(byte[] input) {
+            long[] output = new long[LIMB_CNT];
+            for (int i = 0; i < LIMB_CNT; i++) {
+                output[i] = ((((long) (input[EXPAND_START[i]] & 0xff))
+                        | ((long) (input[EXPAND_START[i] + 1] & 0xff)) << 8
+                        | ((long) (input[EXPAND_START[i] + 2] & 0xff)) << 16
+                        | ((long) (input[EXPAND_START[i] + 3] & 0xff)) << 24) >> EXPAND_SHIFT[i]) & MASK[i & 1];
+            }
+            return output;
+        }
+
+        /**
+         * Takes a fully reduced mixed radix form number and contract it into a little-endian, 32-byte
+         * array.
+         * <p>
+         * On entry: |input_limbs[i]| < 2^26
+         */
+        @SuppressWarnings("NarrowingCompoundAssignment")
+        static byte[] contract(long[] inputLimbs) {
+            long[] input = Arrays.copyOf(inputLimbs, LIMB_CNT);
+            for (int j = 0; j < 2; j++) {
+                for (int i = 0; i < 9; i++) {
+                    // This calculation is a time-invariant way to make input[i] non-negative by borrowing
+                    // from the next-larger limb.
+                    int carry = -(int) ((input[i] & (input[i] >> 31)) >> SHIFT[i & 1]);
+                    input[i] = input[i] + (carry << SHIFT[i & 1]);
+                    input[i + 1] -= carry;
+                }
+
+                // There's no greater limb for input[9] to borrow from, but we can multiply by 19 and borrow
+                // from input[0], which is valid mod 2^255-19.
+                {
+                    int carry = -(int) ((input[9] & (input[9] >> 31)) >> 25);
+                    input[9] += (carry << 25);
+                    input[0] -= (carry * 19);
+                }
+
+                // After the first iteration, input[1..9] are non-negative and fit within 25 or 26 bits,
+                // depending on position. However, input[0] may be negative.
+            }
+
+            // The first borrow-propagation pass above ended with every limb except (possibly) input[0]
+            // non-negative.
+            //
+            // If input[0] was negative after the first pass, then it was because of a carry from input[9].
+            // On entry, input[9] < 2^26 so the carry was, at most, one, since (2**26-1) >> 25 = 1. Thus
+            // input[0] >= -19.
+            //
+            // In the second pass, each limb is decreased by at most one. Thus the second borrow-propagation
+            // pass could only have wrapped around to decrease input[0] again if the first pass left
+            // input[0] negative *and* input[1] through input[9] were all zero.  In that case, input[1] is
+            // now 2^25 - 1, and this last borrow-propagation step will leave input[1] non-negative.
+            {
+                int carry = -(int) ((input[0] & (input[0] >> 31)) >> 26);
+                input[0] += (carry << 26);
+                input[1] -= carry;
+            }
+
+            // All input[i] are now non-negative. However, there might be values between 2^25 and 2^26 in a
+            // limb which is, nominally, 25 bits wide.
+            for (int j = 0; j < 2; j++) {
+                for (int i = 0; i < 9; i++) {
+                    int carry = (int) (input[i] >> SHIFT[i & 1]);
+                    input[i] &= MASK[i & 1];
+                    input[i + 1] += carry;
+                }
+            }
+
+            {
+                int carry = (int) (input[9] >> 25);
+                input[9] &= 0x1ffffff;
+                input[0] += 19 * carry;
+            }
+
+            // If the first carry-chain pass, just above, ended up with a carry from input[9], and that
+            // caused input[0] to be out-of-bounds, then input[0] was < 2^26 + 2*19, because the carry was,
+            // at most, two.
+            //
+            // If the second pass carried from input[9] again then input[0] is < 2*19 and the input[9] ->
+            // input[0] carry didn't push input[0] out of bounds.
+
+            // It still remains the case that input might be between 2^255-19 and 2^255. In this case,
+            // input[1..9] must take their maximum value and input[0] must be >= (2^255-19) & 0x3ffffff,
+            // which is 0x3ffffed.
+            int mask = gte((int) input[0], 0x3ffffed);
+            for (int i = 1; i < LIMB_CNT; i++) {
+                mask &= eq((int) input[i], MASK[i & 1]);
+            }
+
+            // mask is either 0xffffffff (if input >= 2^255-19) and zero otherwise. Thus this conditionally
+            // subtracts 2^255-19.
+            input[0] -= mask & 0x3ffffed;
+            input[1] -= mask & 0x1ffffff;
+            for (int i = 2; i < LIMB_CNT; i += 2) {
+                input[i] -= mask & 0x3ffffff;
+                input[i + 1] -= mask & 0x1ffffff;
+            }
+
+            for (int i = 0; i < LIMB_CNT; i++) {
+                input[i] <<= EXPAND_SHIFT[i];
+            }
+            byte[] output = new byte[FIELD_LEN];
+            for (int i = 0; i < LIMB_CNT; i++) {
+                output[EXPAND_START[i]] |= input[i] & 0xff;
+                output[EXPAND_START[i] + 1] |= (input[i] >> 8) & 0xff;
+                output[EXPAND_START[i] + 2] |= (input[i] >> 16) & 0xff;
+                output[EXPAND_START[i] + 3] |= (input[i] >> 24) & 0xff;
+            }
+            return output;
+        }
+
+        /**
+         * Computes inverse of z = z(2^255 - 21)
+         * <p>
+         * Shamelessly copied from agl's code which was shamelessly copied from djb's code. Only the
+         * comment format and the variable namings are different from those.
+         */
+        static void inverse(long[] out, long[] z) {
+            long[] z2 = new long[Field25519.LIMB_CNT];
+            long[] z9 = new long[Field25519.LIMB_CNT];
+            long[] z11 = new long[Field25519.LIMB_CNT];
+            long[] z2To5Minus1 = new long[Field25519.LIMB_CNT];
+            long[] z2To10Minus1 = new long[Field25519.LIMB_CNT];
+            long[] z2To20Minus1 = new long[Field25519.LIMB_CNT];
+            long[] z2To50Minus1 = new long[Field25519.LIMB_CNT];
+            long[] z2To100Minus1 = new long[Field25519.LIMB_CNT];
+            long[] t0 = new long[Field25519.LIMB_CNT];
+            long[] t1 = new long[Field25519.LIMB_CNT];
+
+            square(z2, z);                          // 2
+            square(t1, z2);                         // 4
+            square(t0, t1);                         // 8
+            mult(z9, t0, z);                        // 9
+            mult(z11, z9, z2);                      // 11
+            square(t0, z11);                        // 22
+            mult(z2To5Minus1, t0, z9);              // 2^5 - 2^0 = 31
+
+            square(t0, z2To5Minus1);                // 2^6 - 2^1
+            square(t1, t0);                         // 2^7 - 2^2
+            square(t0, t1);                         // 2^8 - 2^3
+            square(t1, t0);                         // 2^9 - 2^4
+            square(t0, t1);                         // 2^10 - 2^5
+            mult(z2To10Minus1, t0, z2To5Minus1);    // 2^10 - 2^0
+
+            square(t0, z2To10Minus1);               // 2^11 - 2^1
+            square(t1, t0);                         // 2^12 - 2^2
+            for (int i = 2; i < 10; i += 2) {       // 2^20 - 2^10
+                square(t0, t1);
+                square(t1, t0);
+            }
+            mult(z2To20Minus1, t1, z2To10Minus1);   // 2^20 - 2^0
+
+            square(t0, z2To20Minus1);               // 2^21 - 2^1
+            square(t1, t0);                         // 2^22 - 2^2
+            for (int i = 2; i < 20; i += 2) {       // 2^40 - 2^20
+                square(t0, t1);
+                square(t1, t0);
+            }
+            mult(t0, t1, z2To20Minus1);             // 2^40 - 2^0
+
+            square(t1, t0);                         // 2^41 - 2^1
+            square(t0, t1);                         // 2^42 - 2^2
+            for (int i = 2; i < 10; i += 2) {       // 2^50 - 2^10
+                square(t1, t0);
+                square(t0, t1);
+            }
+            mult(z2To50Minus1, t0, z2To10Minus1);   // 2^50 - 2^0
+
+            square(t0, z2To50Minus1);               // 2^51 - 2^1
+            square(t1, t0);                         // 2^52 - 2^2
+            for (int i = 2; i < 50; i += 2) {       // 2^100 - 2^50
+                square(t0, t1);
+                square(t1, t0);
+            }
+            mult(z2To100Minus1, t1, z2To50Minus1);  // 2^100 - 2^0
+
+            square(t1, z2To100Minus1);              // 2^101 - 2^1
+            square(t0, t1);                         // 2^102 - 2^2
+            for (int i = 2; i < 100; i += 2) {      // 2^200 - 2^100
+                square(t1, t0);
+                square(t0, t1);
+            }
+            mult(t1, t0, z2To100Minus1);            // 2^200 - 2^0
+
+            square(t0, t1);                         // 2^201 - 2^1
+            square(t1, t0);                         // 2^202 - 2^2
+            for (int i = 2; i < 50; i += 2) {       // 2^250 - 2^50
+                square(t0, t1);
+                square(t1, t0);
+            }
+            mult(t0, t1, z2To50Minus1);             // 2^250 - 2^0
+
+            square(t1, t0);                         // 2^251 - 2^1
+            square(t0, t1);                         // 2^252 - 2^2
+            square(t1, t0);                         // 2^253 - 2^3
+            square(t0, t1);                         // 2^254 - 2^4
+            square(t1, t0);                         // 2^255 - 2^5
+            mult(out, t1, z11);                     // 2^255 - 21
+        }
+
+
+        /**
+         * Returns 0xffffffff iff a == b and zero otherwise.
+         */
+        private static int eq(int a, int b) {
+            a = ~(a ^ b);
+            a &= a << 16;
+            a &= a << 8;
+            a &= a << 4;
+            a &= a << 2;
+            a &= a << 1;
+            return a >> 31;
+        }
+
+        /**
+         * returns 0xffffffff if a >= b and zero otherwise, where a and b are both non-negative.
+         */
+        private static int gte(int a, int b) {
+            a -= b;
+            // a >= 0 iff a >= b.
+            return ~(a >> 31);
+        }
+    }
+
+    // (x = 0, y = 1) point
+    private static final CachedXYT CACHED_NEUTRAL = new CachedXYT(
+            new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+            new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+            new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+    private static final PartialXYZT NEUTRAL = new PartialXYZT(
+            new XYZ(new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+                    new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+                    new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
+            new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+
+    /**
+     * Projective point representation (X:Y:Z) satisfying x = X/Z, y = Y/Z
+     * <p>
+     * Note that this is referred as ge_p2 in ref10 impl.
+     * Also note that x = X, y = Y and z = Z below following Java coding style.
+     * <p>
+     * See
+     * Koyama K., Tsuruoka Y. (1993) Speeding up Elliptic Cryptosystems by Using a Signed Binary
+     * Window Method.
+     * <p>
+     * https://hyperelliptic.org/EFD/g1p/auto-twisted-projective.html
+     */
+    private static class XYZ {
+
+        final long[] x;
+        final long[] y;
+        final long[] z;
+
+        XYZ() {
+            this(new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT]);
+        }
+
+        XYZ(long[] x, long[] y, long[] z) {
+            this.x = x;
+            this.y = y;
+            this.z = z;
+        }
+
+        XYZ(XYZ xyz) {
+            x = Arrays.copyOf(xyz.x, Field25519.LIMB_CNT);
+            y = Arrays.copyOf(xyz.y, Field25519.LIMB_CNT);
+            z = Arrays.copyOf(xyz.z, Field25519.LIMB_CNT);
+        }
+
+        XYZ(PartialXYZT partialXYZT) {
+            this();
+            fromPartialXYZT(this, partialXYZT);
+        }
+
+        /**
+         * ge_p1p1_to_p2.c
+         */
+        static XYZ fromPartialXYZT(XYZ out, PartialXYZT in) {
+            Field25519.mult(out.x, in.xyz.x, in.t);
+            Field25519.mult(out.y, in.xyz.y, in.xyz.z);
+            Field25519.mult(out.z, in.xyz.z, in.t);
+            return out;
+        }
+
+        /**
+         * Encodes this point to bytes.
+         */
+        byte[] toBytes() {
+            long[] recip = new long[Field25519.LIMB_CNT];
+            long[] x = new long[Field25519.LIMB_CNT];
+            long[] y = new long[Field25519.LIMB_CNT];
+            Field25519.inverse(recip, z);
+            Field25519.mult(x, this.x, recip);
+            Field25519.mult(y, this.y, recip);
+            byte[] s = Field25519.contract(y);
+            s[31] = (byte) (s[31] ^ (getLsb(x) << 7));
+            return s;
+        }
+
+
+        /**
+         * Best effort fix-timing array comparison.
+         *
+         * @return true if two arrays are equal.
+         */
+        private static boolean bytesEqual(final byte[] x, final byte[] y) {
+            if (x == null || y == null) {
+                return false;
+            }
+            if (x.length != y.length) {
+                return false;
+            }
+            int res = 0;
+            for (int i = 0; i < x.length; i++) {
+                res |= x[i] ^ y[i];
+            }
+            return res == 0;
+        }
+
+        /**
+         * Checks that the point is on curve
+         */
+        boolean isOnCurve() {
+            long[] x2 = new long[Field25519.LIMB_CNT];
+            Field25519.square(x2, x);
+            long[] y2 = new long[Field25519.LIMB_CNT];
+            Field25519.square(y2, y);
+            long[] z2 = new long[Field25519.LIMB_CNT];
+            Field25519.square(z2, z);
+            long[] z4 = new long[Field25519.LIMB_CNT];
+            Field25519.square(z4, z2);
+            long[] lhs = new long[Field25519.LIMB_CNT];
+            // lhs = y^2 - x^2
+            Field25519.sub(lhs, y2, x2);
+            // lhs = z^2 * (y2 - x2)
+            Field25519.mult(lhs, lhs, z2);
+            long[] rhs = new long[Field25519.LIMB_CNT];
+            // rhs = x^2 * y^2
+            Field25519.mult(rhs, x2, y2);
+            // rhs = D * x^2 * y^2
+            Field25519.mult(rhs, rhs, D);
+            // rhs = z^4 + D * x^2 * y^2
+            Field25519.sum(rhs, z4);
+            // Field25519.mult reduces its output, but Field25519.sum does not, so we have to manually
+            // reduce it here.
+            Field25519.reduce(rhs, rhs);
+            // z^2 (y^2 - x^2) == z^4 + D * x^2 * y^2
+            return bytesEqual(Field25519.contract(lhs), Field25519.contract(rhs));
+        }
+    }
+
+    /**
+     * Represents extended projective point representation (X:Y:Z:T) satisfying x = X/Z, y = Y/Z,
+     * XY = ZT
+     * <p>
+     * Note that this is referred as ge_p3 in ref10 impl.
+     * Also note that t = T below following Java coding style.
+     * <p>
+     * See
+     * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
+     * <p>
+     * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html
+     */
+    private static class XYZT {
+
+        final XYZ xyz;
+        final long[] t;
+
+        XYZT() {
+            this(new XYZ(), new long[Field25519.LIMB_CNT]);
+        }
+
+        XYZT(XYZ xyz, long[] t) {
+            this.xyz = xyz;
+            this.t = t;
+        }
+
+        XYZT(PartialXYZT partialXYZT) {
+            this();
+            fromPartialXYZT(this, partialXYZT);
+        }
+
+        /**
+         * ge_p1p1_to_p2.c
+         */
+        private static XYZT fromPartialXYZT(XYZT out, PartialXYZT in) {
+            Field25519.mult(out.xyz.x, in.xyz.x, in.t);
+            Field25519.mult(out.xyz.y, in.xyz.y, in.xyz.z);
+            Field25519.mult(out.xyz.z, in.xyz.z, in.t);
+            Field25519.mult(out.t, in.xyz.x, in.xyz.y);
+            return out;
+        }
+
+        /**
+         * Decodes {@code s} into an extented projective point.
+         * See Section 5.1.3 Decoding in https://tools.ietf.org/html/rfc8032#section-5.1.3
+         */
+        private static XYZT fromBytesNegateVarTime(byte[] s) throws GeneralSecurityException {
+            long[] x = new long[Field25519.LIMB_CNT];
+            long[] y = Field25519.expand(s);
+            long[] z = new long[Field25519.LIMB_CNT];
+            z[0] = 1;
+            long[] t = new long[Field25519.LIMB_CNT];
+            long[] u = new long[Field25519.LIMB_CNT];
+            long[] v = new long[Field25519.LIMB_CNT];
+            long[] vxx = new long[Field25519.LIMB_CNT];
+            long[] check = new long[Field25519.LIMB_CNT];
+            Field25519.square(u, y);
+            Field25519.mult(v, u, D);
+            Field25519.sub(u, u, z); // u = y^2 - 1
+            Field25519.sum(v, v, z); // v = dy^2 + 1
+
+            long[] v3 = new long[Field25519.LIMB_CNT];
+            Field25519.square(v3, v);
+            Field25519.mult(v3, v3, v); // v3 = v^3
+            Field25519.square(x, v3);
+            Field25519.mult(x, x, v);
+            Field25519.mult(x, x, u); // x = uv^7
+
+            pow2252m3(x, x); // x = (uv^7)^((q-5)/8)
+            Field25519.mult(x, x, v3);
+            Field25519.mult(x, x, u); // x = uv^3(uv^7)^((q-5)/8)
+
+            Field25519.square(vxx, x);
+            Field25519.mult(vxx, vxx, v);
+            Field25519.sub(check, vxx, u); // vx^2-u
+            if (isNonZeroVarTime(check)) {
+                Field25519.sum(check, vxx, u); // vx^2+u
+                if (isNonZeroVarTime(check)) {
+                    throw new GeneralSecurityException("Cannot convert given bytes to extended projective "
+                            + "coordinates. No square root exists for modulo 2^255-19");
+                }
+                Field25519.mult(x, x, SQRTM1);
+            }
+
+            if (!isNonZeroVarTime(x) && (s[31] & 0xff) >> 7 != 0) {
+                throw new GeneralSecurityException("Cannot convert given bytes to extended projective "
+                        + "coordinates. Computed x is zero and encoded x's least significant bit is not zero");
+            }
+            if (getLsb(x) == ((s[31] & 0xff) >> 7)) {
+                neg(x, x);
+            }
+
+            Field25519.mult(t, x, y);
+            return new XYZT(new XYZ(x, y, z), t);
+        }
+    }
+
+    /**
+     * Partial projective point representation ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
+     * <p>
+     * Note that this is referred as complete form in the original ref10 impl (ge_p1p1).
+     * Also note that t = T below following Java coding style.
+     * <p>
+     * Although this has the same types as XYZT, it is redefined to have its own type so that it is
+     * readable and 1:1 corresponds to ref10 impl.
+     * <p>
+     * Can be converted to XYZT as follows:
+     * X1 = X * T = x * Z * T = x * Z1
+     * Y1 = Y * Z = y * T * Z = y * Z1
+     * Z1 = Z * T = Z * T
+     * T1 = X * Y = x * Z * y * T = x * y * Z1 = X1Y1 / Z1
+     */
+    private static class PartialXYZT {
+
+        final XYZ xyz;
+        final long[] t;
+
+        PartialXYZT() {
+            this(new XYZ(), new long[Field25519.LIMB_CNT]);
+        }
+
+        PartialXYZT(XYZ xyz, long[] t) {
+            this.xyz = xyz;
+            this.t = t;
+        }
+
+        PartialXYZT(PartialXYZT other) {
+            xyz = new XYZ(other.xyz);
+            t = Arrays.copyOf(other.t, Field25519.LIMB_CNT);
+        }
+    }
+
+    /**
+     * Corresponds to the caching mentioned in the last paragraph of Section 3.1 of
+     * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
+     * with Z = 1.
+     */
+    private static class CachedXYT {
+
+        final long[] yPlusX;
+        final long[] yMinusX;
+        final long[] t2d;
+
+        /**
+         * Creates a cached XYZT with Z = 1
+         *
+         * @param yPlusX  y + x
+         * @param yMinusX y - x
+         * @param t2d     2d * xy
+         */
+        CachedXYT(long[] yPlusX, long[] yMinusX, long[] t2d) {
+            this.yPlusX = yPlusX;
+            this.yMinusX = yMinusX;
+            this.t2d = t2d;
+        }
+
+        CachedXYT(CachedXYT other) {
+            yPlusX = Arrays.copyOf(other.yPlusX, Field25519.LIMB_CNT);
+            yMinusX = Arrays.copyOf(other.yMinusX, Field25519.LIMB_CNT);
+            t2d = Arrays.copyOf(other.t2d, Field25519.LIMB_CNT);
+        }
+
+        // z is one implicitly, so this just copies {@code in} to {@code output}.
+        void multByZ(long[] output, long[] in) {
+            System.arraycopy(in, 0, output, 0, Field25519.LIMB_CNT);
+        }
+
+        /**
+         * If icopy is 1, copies {@code other} into this point. Time invariant wrt to icopy value.
+         */
+        void copyConditional(CachedXYT other, int icopy) {
+            copyConditional(yPlusX, other.yPlusX, icopy);
+            copyConditional(yMinusX, other.yMinusX, icopy);
+            copyConditional(t2d, other.t2d, icopy);
+        }
+
+        /**
+         * Conditionally copies a reduced-form limb arrays {@code b} into {@code a} if {@code icopy} is 1,
+         * but leave {@code a} unchanged if 'iswap' is 0. Runs in data-invariant time to avoid
+         * side-channel attacks.
+         *
+         * <p>NOTE that this function requires that {@code icopy} be 1 or 0; other values give wrong
+         * results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the
+         * values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must
+         * have magnitude less than Integer.MAX_VALUE.
+         */
+        static void copyConditional(long[] a, long[] b, int icopy) {
+            int copy = -icopy;
+            for (int i = 0; i < Field25519.LIMB_CNT; i++) {
+                int x = copy & (((int) a[i]) ^ ((int) b[i]));
+                a[i] = ((int) a[i]) ^ x;
+            }
+        }
+    }
+
+    private static class CachedXYZT extends CachedXYT {
+
+        private final long[] z;
+
+        CachedXYZT() {
+            this(new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT]);
+        }
+
+        /**
+         * ge_p3_to_cached.c
+         */
+        CachedXYZT(XYZT xyzt) {
+            this();
+            Field25519.sum(yPlusX, xyzt.xyz.y, xyzt.xyz.x);
+            Field25519.sub(yMinusX, xyzt.xyz.y, xyzt.xyz.x);
+            System.arraycopy(xyzt.xyz.z, 0, z, 0, Field25519.LIMB_CNT);
+            Field25519.mult(t2d, xyzt.t, D2);
+        }
+
+        /**
+         * Creates a cached XYZT
+         *
+         * @param yPlusX  Y + X
+         * @param yMinusX Y - X
+         * @param z       Z
+         * @param t2d     2d * (XY/Z)
+         */
+        CachedXYZT(long[] yPlusX, long[] yMinusX, long[] z, long[] t2d) {
+            super(yPlusX, yMinusX, t2d);
+            this.z = z;
+        }
+
+        @Override
+        public void multByZ(long[] output, long[] in) {
+            Field25519.mult(output, in, z);
+        }
+    }
+
+    /**
+     * Addition defined in Section 3.1 of
+     * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
+     * <p>
+     * Please note that this is a partial of the operation listed there leaving out the final
+     * conversion from PartialXYZT to XYZT.
+     *
+     * @param extended extended projective point input
+     * @param cached   cached projective point input
+     */
+    private static void add(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) {
+        long[] t = new long[Field25519.LIMB_CNT];
+
+        // Y1 + X1
+        Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x);
+
+        // Y1 - X1
+        Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x);
+
+        // A = (Y1 - X1) * (Y2 - X2)
+        Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yMinusX);
+
+        // B = (Y1 + X1) * (Y2 + X2)
+        Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yPlusX);
+
+        // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper)
+        Field25519.mult(partialXYZT.t, extended.t, cached.t2d);
+
+        // Z1 * Z2
+        cached.multByZ(partialXYZT.xyz.x, extended.xyz.z);
+
+        // D = 2 * Z1 * Z2
+        Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x);
+
+        // X3 = B - A
+        Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y);
+
+        // Y3 = B + A
+        Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y);
+
+        // Z3 = D + C
+        Field25519.sum(partialXYZT.xyz.z, t, partialXYZT.t);
+
+        // T3 = D - C
+        Field25519.sub(partialXYZT.t, t, partialXYZT.t);
+    }
+
+    /**
+     * Based on the addition defined in Section 3.1 of
+     * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
+     * <p>
+     * Please note that this is a partial of the operation listed there leaving out the final
+     * conversion from PartialXYZT to XYZT.
+     *
+     * @param extended extended projective point input
+     * @param cached   cached projective point input
+     */
+    private static void sub(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) {
+        long[] t = new long[Field25519.LIMB_CNT];
+
+        // Y1 + X1
+        Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x);
+
+        // Y1 - X1
+        Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x);
+
+        // A = (Y1 - X1) * (Y2 + X2)
+        Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yPlusX);
+
+        // B = (Y1 + X1) * (Y2 - X2)
+        Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yMinusX);
+
+        // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper)
+        Field25519.mult(partialXYZT.t, extended.t, cached.t2d);
+
+        // Z1 * Z2
+        cached.multByZ(partialXYZT.xyz.x, extended.xyz.z);
+
+        // D = 2 * Z1 * Z2
+        Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x);
+
+        // X3 = B - A
+        Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y);
+
+        // Y3 = B + A
+        Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y);
+
+        // Z3 = D - C
+        Field25519.sub(partialXYZT.xyz.z, t, partialXYZT.t);
+
+        // T3 = D + C
+        Field25519.sum(partialXYZT.t, t, partialXYZT.t);
+    }
+
+    /**
+     * Doubles {@code p} and puts the result into this PartialXYZT.
+     * <p>
+     * This is based on the addition defined in formula 7 in Section 3.3 of
+     * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
+     * <p>
+     * Please note that this is a partial of the operation listed there leaving out the final
+     * conversion from PartialXYZT to XYZT and also this fixes a typo in calculation of Y3 and T3 in
+     * the paper, H should be replaced with A+B.
+     */
+    private static void doubleXYZ(PartialXYZT partialXYZT, XYZ p) {
+        long[] t0 = new long[Field25519.LIMB_CNT];
+
+        // XX = X1^2
+        Field25519.square(partialXYZT.xyz.x, p.x);
+
+        // YY = Y1^2
+        Field25519.square(partialXYZT.xyz.z, p.y);
+
+        // B' = Z1^2
+        Field25519.square(partialXYZT.t, p.z);
+
+        // B = 2 * B'
+        Field25519.sum(partialXYZT.t, partialXYZT.t, partialXYZT.t);
+
+        // A = X1 + Y1
+        Field25519.sum(partialXYZT.xyz.y, p.x, p.y);
+
+        // AA = A^2
+        Field25519.square(t0, partialXYZT.xyz.y);
+
+        // Y3 = YY + XX
+        Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.x);
+
+        // Z3 = YY - XX
+        Field25519.sub(partialXYZT.xyz.z, partialXYZT.xyz.z, partialXYZT.xyz.x);
+
+        // X3 = AA - Y3
+        Field25519.sub(partialXYZT.xyz.x, t0, partialXYZT.xyz.y);
+
+        // T3 = B - Z3
+        Field25519.sub(partialXYZT.t, partialXYZT.t, partialXYZT.xyz.z);
+    }
+
+    /**
+     * Doubles {@code p} and puts the result into this PartialXYZT.
+     */
+    private static void doubleXYZT(PartialXYZT partialXYZT, XYZT p) {
+        doubleXYZ(partialXYZT, p.xyz);
+    }
+
+    /**
+     * Compares two byte values in constant time.
+     */
+    private static int eq(int a, int b) {
+        int r = ~(a ^ b) & 0xff;
+        r &= r << 4;
+        r &= r << 2;
+        r &= r << 1;
+        return (r >> 7) & 1;
+    }
+
+    /**
+     * This is a constant time operation where point b*B*256^pos is stored in {@code t}.
+     * When b is 0, t remains the same (i.e., neutral point).
+     * <p>
+     * Although B_TABLE[32][8] (B_TABLE[i][j] = (j+1)*B*256^i) has j values in [0, 7], the select
+     * method negates the corresponding point if b is negative (which is straight forward in elliptic
+     * curves by just negating y coordinate). Therefore we can get multiples of B with the half of
+     * memory requirements.
+     *
+     * @param t   neutral element (i.e., point 0), also serves as output.
+     * @param pos in B[pos][j] = (j+1)*B*256^pos
+     * @param b   value in [-8, 8] range.
+     */
+    private static void select(CachedXYT t, int pos, byte b) {
+        int bnegative = (b & 0xff) >> 7;
+        int babs = b - (((-bnegative) & b) << 1);
+
+        t.copyConditional(B_TABLE[pos][0], eq(babs, 1));
+        t.copyConditional(B_TABLE[pos][1], eq(babs, 2));
+        t.copyConditional(B_TABLE[pos][2], eq(babs, 3));
+        t.copyConditional(B_TABLE[pos][3], eq(babs, 4));
+        t.copyConditional(B_TABLE[pos][4], eq(babs, 5));
+        t.copyConditional(B_TABLE[pos][5], eq(babs, 6));
+        t.copyConditional(B_TABLE[pos][6], eq(babs, 7));
+        t.copyConditional(B_TABLE[pos][7], eq(babs, 8));
+
+        long[] yPlusX = Arrays.copyOf(t.yMinusX, Field25519.LIMB_CNT);
+        long[] yMinusX = Arrays.copyOf(t.yPlusX, Field25519.LIMB_CNT);
+        long[] t2d = Arrays.copyOf(t.t2d, Field25519.LIMB_CNT);
+        neg(t2d, t2d);
+        CachedXYT minust = new CachedXYT(yPlusX, yMinusX, t2d);
+        t.copyConditional(minust, bnegative);
+    }
+
+    /**
+     * Computes {@code a}*B
+     * where a = a[0]+256*a[1]+...+256^31 a[31] and
+     * B is the Ed25519 base point (x,4/5) with x positive.
+     * <p>
+     * Preconditions:
+     * a[31] <= 127
+     *
+     * @throws IllegalStateException iff there is arithmetic error.
+     */
+    @SuppressWarnings("NarrowingCompoundAssignment")
+    private static XYZ scalarMultWithBase(byte[] a) {
+        byte[] e = new byte[2 * Field25519.FIELD_LEN];
+        for (int i = 0; i < Field25519.FIELD_LEN; i++) {
+            e[2 * i + 0] = (byte) (((a[i] & 0xff) >> 0) & 0xf);
+            e[2 * i + 1] = (byte) (((a[i] & 0xff) >> 4) & 0xf);
+        }
+        // each e[i] is between 0 and 15
+        // e[63] is between 0 and 7
+
+        // Rewrite e in a way that each e[i] is in [-8, 8].
+        // This can be done since a[63] is in [0, 7], the carry-over onto the most significant byte
+        // a[63] can be at most 1.
+        int carry = 0;
+        for (int i = 0; i < e.length - 1; i++) {
+            e[i] += carry;
+            carry = e[i] + 8;
+            carry >>= 4;
+            e[i] -= carry << 4;
+        }
+        e[e.length - 1] += carry;
+
+        PartialXYZT ret = new PartialXYZT(NEUTRAL);
+        XYZT xyzt = new XYZT();
+        // Although B_TABLE's i can be at most 31 (stores only 32 4bit multiples of B) and we have 64
+        // 4bit values in e array, the below for loop adds cached values by iterating e by two in odd
+        // indices. After the result, we can double the result point 4 times to shift the multiplication
+        // scalar by 4 bits.
+        for (int i = 1; i < e.length; i += 2) {
+            CachedXYT t = new CachedXYT(CACHED_NEUTRAL);
+            select(t, i / 2, e[i]);
+            add(ret, XYZT.fromPartialXYZT(xyzt, ret), t);
+        }
+
+        // Doubles the result 4 times to shift the multiplication scalar 4 bits to get the actual result
+        // for the odd indices in e.
+        XYZ xyz = new XYZ();
+        doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
+        doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
+        doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
+        doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
+
+        // Add multiples of B for even indices of e.
+        for (int i = 0; i < e.length; i += 2) {
+            CachedXYT t = new CachedXYT(CACHED_NEUTRAL);
+            select(t, i / 2, e[i]);
+            add(ret, XYZT.fromPartialXYZT(xyzt, ret), t);
+        }
+
+        // This check is to protect against flaws, i.e. if there is a computation error through a
+        // faulty CPU or if the implementation contains a bug.
+        XYZ result = new XYZ(ret);
+        if (!result.isOnCurve()) {
+            throw new IllegalStateException("arithmetic error in scalar multiplication");
+        }
+        return result;
+    }
+
+    @SuppressWarnings("NarrowingCompoundAssignment")
+    private static byte[] slide(byte[] a) {
+        byte[] r = new byte[256];
+        // Writes each bit in a[0..31] into r[0..255]:
+        // a = a[0]+256*a[1]+...+256^31*a[31] is equal to
+        // r = r[0]+2*r[1]+...+2^255*r[255]
+        for (int i = 0; i < 256; i++) {
+            r[i] = (byte) (1 & ((a[i >> 3] & 0xff) >> (i & 7)));
+        }
+
+        // Transforms r[i] as odd values in [-15, 15]
+        for (int i = 0; i < 256; i++) {
+            if (r[i] != 0) {
+                for (int b = 1; b <= 6 && i + b < 256; b++) {
+                    if (r[i + b] != 0) {
+                        if (r[i] + (r[i + b] << b) <= 15) {
+                            r[i] += r[i + b] << b;
+                            r[i + b] = 0;
+                        } else if (r[i] - (r[i + b] << b) >= -15) {
+                            r[i] -= r[i + b] << b;
+                            for (int k = i + b; k < 256; k++) {
+                                if (r[k] == 0) {
+                                    r[k] = 1;
+                                    break;
+                                }
+                                r[k] = 0;
+                            }
+                        } else {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return r;
+    }
+
+    /**
+     * Computes {@code a}*{@code pointA}+{@code b}*B
+     * where a = a[0]+256*a[1]+...+256^31*a[31].
+     * and b = b[0]+256*b[1]+...+256^31*b[31].
+     * B is the Ed25519 base point (x,4/5) with x positive.
+     * <p>
+     * Note that execution time varies based on the input since this will only be used in verification
+     * of signatures.
+     */
+    private static XYZ doubleScalarMultVarTime(byte[] a, XYZT pointA, byte[] b) {
+        // pointA, 3*pointA, 5*pointA, 7*pointA, 9*pointA, 11*pointA, 13*pointA, 15*pointA
+        CachedXYZT[] pointAArray = new CachedXYZT[8];
+        pointAArray[0] = new CachedXYZT(pointA);
+        PartialXYZT t = new PartialXYZT();
+        doubleXYZT(t, pointA);
+        XYZT doubleA = new XYZT(t);
+        for (int i = 1; i < pointAArray.length; i++) {
+            add(t, doubleA, pointAArray[i - 1]);
+            pointAArray[i] = new CachedXYZT(new XYZT(t));
+        }
+
+        byte[] aSlide = slide(a);
+        byte[] bSlide = slide(b);
+        t = new PartialXYZT(NEUTRAL);
+        XYZT u = new XYZT();
+        int i = 255;
+        for (; i >= 0; i--) {
+            if (aSlide[i] != 0 || bSlide[i] != 0) {
+                break;
+            }
+        }
+        for (; i >= 0; i--) {
+            doubleXYZ(t, new XYZ(t));
+            if (aSlide[i] > 0) {
+                add(t, XYZT.fromPartialXYZT(u, t), pointAArray[aSlide[i] / 2]);
+            } else if (aSlide[i] < 0) {
+                sub(t, XYZT.fromPartialXYZT(u, t), pointAArray[-aSlide[i] / 2]);
+            }
+            if (bSlide[i] > 0) {
+                add(t, XYZT.fromPartialXYZT(u, t), B2[bSlide[i] / 2]);
+            } else if (bSlide[i] < 0) {
+                sub(t, XYZT.fromPartialXYZT(u, t), B2[-bSlide[i] / 2]);
+            }
+        }
+
+        return new XYZ(t);
+    }
+
+    /**
+     * Returns true if {@code in} is nonzero.
+     * <p>
+     * Note that execution time might depend on the input {@code in}.
+     */
+    private static boolean isNonZeroVarTime(long[] in) {
+        long[] inCopy = new long[in.length + 1];
+        System.arraycopy(in, 0, inCopy, 0, in.length);
+        Field25519.reduceCoefficients(inCopy);
+        byte[] bytes = Field25519.contract(inCopy);
+        for (byte b : bytes) {
+            if (b != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the least significant bit of {@code in}.
+     */
+    private static int getLsb(long[] in) {
+        return Field25519.contract(in)[0] & 1;
+    }
+
+    /**
+     * Negates all values in {@code in} and store it in {@code out}.
+     */
+    private static void neg(long[] out, long[] in) {
+        for (int i = 0; i < in.length; i++) {
+            out[i] = -in[i];
+        }
+    }
+
+    /**
+     * Computes {@code in}^(2^252-3) mod 2^255-19 and puts the result in {@code out}.
+     */
+    private static void pow2252m3(long[] out, long[] in) {
+        long[] t0 = new long[Field25519.LIMB_CNT];
+        long[] t1 = new long[Field25519.LIMB_CNT];
+        long[] t2 = new long[Field25519.LIMB_CNT];
+
+        // z2 = z1^2^1
+        Field25519.square(t0, in);
+
+        // z8 = z2^2^2
+        Field25519.square(t1, t0);
+        for (int i = 1; i < 2; i++) {
+            Field25519.square(t1, t1);
+        }
+
+        // z9 = z1*z8
+        Field25519.mult(t1, in, t1);
+
+        // z11 = z2*z9
+        Field25519.mult(t0, t0, t1);
+
+        // z22 = z11^2^1
+        Field25519.square(t0, t0);
+
+        // z_5_0 = z9*z22
+        Field25519.mult(t0, t1, t0);
+
+        // z_10_5 = z_5_0^2^5
+        Field25519.square(t1, t0);
+        for (int i = 1; i < 5; i++) {
+            Field25519.square(t1, t1);
+        }
+
+        // z_10_0 = z_10_5*z_5_0
+        Field25519.mult(t0, t1, t0);
+
+        // z_20_10 = z_10_0^2^10
+        Field25519.square(t1, t0);
+        for (int i = 1; i < 10; i++) {
+            Field25519.square(t1, t1);
+        }
+
+        // z_20_0 = z_20_10*z_10_0
+        Field25519.mult(t1, t1, t0);
+
+        // z_40_20 = z_20_0^2^20
+        Field25519.square(t2, t1);
+        for (int i = 1; i < 20; i++) {
+            Field25519.square(t2, t2);
+        }
+
+        // z_40_0 = z_40_20*z_20_0
+        Field25519.mult(t1, t2, t1);
+
+        // z_50_10 = z_40_0^2^10
+        Field25519.square(t1, t1);
+        for (int i = 1; i < 10; i++) {
+            Field25519.square(t1, t1);
+        }
+
+        // z_50_0 = z_50_10*z_10_0
+        Field25519.mult(t0, t1, t0);
+
+        // z_100_50 = z_50_0^2^50
+        Field25519.square(t1, t0);
+        for (int i = 1; i < 50; i++) {
+            Field25519.square(t1, t1);
+        }
+
+        // z_100_0 = z_100_50*z_50_0
+        Field25519.mult(t1, t1, t0);
+
+        // z_200_100 = z_100_0^2^100
+        Field25519.square(t2, t1);
+        for (int i = 1; i < 100; i++) {
+            Field25519.square(t2, t2);
+        }
+
+        // z_200_0 = z_200_100*z_100_0
+        Field25519.mult(t1, t2, t1);
+
+        // z_250_50 = z_200_0^2^50
+        Field25519.square(t1, t1);
+        for (int i = 1; i < 50; i++) {
+            Field25519.square(t1, t1);
+        }
+
+        // z_250_0 = z_250_50*z_50_0
+        Field25519.mult(t0, t1, t0);
+
+        // z_252_2 = z_250_0^2^2
+        Field25519.square(t0, t0);
+        for (int i = 1; i < 2; i++) {
+            Field25519.square(t0, t0);
+        }
+
+        // z_252_3 = z_252_2*z1
+        Field25519.mult(out, t0, in);
+    }
+
+    /**
+     * Returns 3 bytes of {@code in} starting from {@code idx} in Little-Endian format.
+     */
+    private static long load3(byte[] in, int idx) {
+        long result;
+        result = (long) in[idx] & 0xff;
+        result |= (long) (in[idx + 1] & 0xff) << 8;
+        result |= (long) (in[idx + 2] & 0xff) << 16;
+        return result;
+    }
+
+    /**
+     * Returns 4 bytes of {@code in} starting from {@code idx} in Little-Endian format.
+     */
+    private static long load4(byte[] in, int idx) {
+        long result = load3(in, idx);
+        result |= (long) (in[idx + 3] & 0xff) << 24;
+        return result;
+    }
+
+    /**
+     * Input:
+     * s[0]+256*s[1]+...+256^63*s[63] = s
+     * <p>
+     * Output:
+     * s[0]+256*s[1]+...+256^31*s[31] = s mod l
+     * where l = 2^252 + 27742317777372353535851937790883648493.
+     * Overwrites s in place.
+     */
+    private static void reduce(byte[] s) {
+        // Observation:
+        // 2^252 mod l is equivalent to -27742317777372353535851937790883648493 mod l
+        // Let m = -27742317777372353535851937790883648493
+        // Thus a*2^252+b mod l is equivalent to a*m+b mod l
+        //
+        // First s is divided into chunks of 21 bits as follows:
+        // s0+2^21*s1+2^42*s3+...+2^462*s23 = s[0]+256*s[1]+...+256^63*s[63]
+        long s0 = 2097151 & load3(s, 0);
+        long s1 = 2097151 & (load4(s, 2) >> 5);
+        long s2 = 2097151 & (load3(s, 5) >> 2);
+        long s3 = 2097151 & (load4(s, 7) >> 7);
+        long s4 = 2097151 & (load4(s, 10) >> 4);
+        long s5 = 2097151 & (load3(s, 13) >> 1);
+        long s6 = 2097151 & (load4(s, 15) >> 6);
+        long s7 = 2097151 & (load3(s, 18) >> 3);
+        long s8 = 2097151 & load3(s, 21);
+        long s9 = 2097151 & (load4(s, 23) >> 5);
+        long s10 = 2097151 & (load3(s, 26) >> 2);
+        long s11 = 2097151 & (load4(s, 28) >> 7);
+        long s12 = 2097151 & (load4(s, 31) >> 4);
+        long s13 = 2097151 & (load3(s, 34) >> 1);
+        long s14 = 2097151 & (load4(s, 36) >> 6);
+        long s15 = 2097151 & (load3(s, 39) >> 3);
+        long s16 = 2097151 & load3(s, 42);
+        long s17 = 2097151 & (load4(s, 44) >> 5);
+        long s18 = 2097151 & (load3(s, 47) >> 2);
+        long s19 = 2097151 & (load4(s, 49) >> 7);
+        long s20 = 2097151 & (load4(s, 52) >> 4);
+        long s21 = 2097151 & (load3(s, 55) >> 1);
+        long s22 = 2097151 & (load4(s, 57) >> 6);
+        long s23 = (load4(s, 60) >> 3);
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+        long carry10;
+        long carry11;
+        long carry12;
+        long carry13;
+        long carry14;
+        long carry15;
+        long carry16;
+
+        // s23*2^462 = s23*2^210*2^252 is equivalent to s23*2^210*m in mod l
+        // As m is a 125 bit number, the result needs to scattered to 6 limbs (125/21 ceil is 6)
+        // starting from s11 (s11*2^210)
+        // m = [666643, 470296, 654183, -997805, 136657, -683901] in 21-bit limbs
+        s11 += s23 * 666643;
+        s12 += s23 * 470296;
+        s13 += s23 * 654183;
+        s14 -= s23 * 997805;
+        s15 += s23 * 136657;
+        s16 -= s23 * 683901;
+        // s23 = 0;
+
+        s10 += s22 * 666643;
+        s11 += s22 * 470296;
+        s12 += s22 * 654183;
+        s13 -= s22 * 997805;
+        s14 += s22 * 136657;
+        s15 -= s22 * 683901;
+        // s22 = 0;
+
+        s9 += s21 * 666643;
+        s10 += s21 * 470296;
+        s11 += s21 * 654183;
+        s12 -= s21 * 997805;
+        s13 += s21 * 136657;
+        s14 -= s21 * 683901;
+        // s21 = 0;
+
+        s8 += s20 * 666643;
+        s9 += s20 * 470296;
+        s10 += s20 * 654183;
+        s11 -= s20 * 997805;
+        s12 += s20 * 136657;
+        s13 -= s20 * 683901;
+        // s20 = 0;
+
+        s7 += s19 * 666643;
+        s8 += s19 * 470296;
+        s9 += s19 * 654183;
+        s10 -= s19 * 997805;
+        s11 += s19 * 136657;
+        s12 -= s19 * 683901;
+        // s19 = 0;
+
+        s6 += s18 * 666643;
+        s7 += s18 * 470296;
+        s8 += s18 * 654183;
+        s9 -= s18 * 997805;
+        s10 += s18 * 136657;
+        s11 -= s18 * 683901;
+        // s18 = 0;
+
+        // Reduce the bit length of limbs from s6 to s15 to 21-bits.
+        carry6 = (s6 + (1 << 20)) >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry8 = (s8 + (1 << 20)) >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry10 = (s10 + (1 << 20)) >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+        carry12 = (s12 + (1 << 20)) >> 21;
+        s13 += carry12;
+        s12 -= carry12 << 21;
+        carry14 = (s14 + (1 << 20)) >> 21;
+        s15 += carry14;
+        s14 -= carry14 << 21;
+        carry16 = (s16 + (1 << 20)) >> 21;
+        s17 += carry16;
+        s16 -= carry16 << 21;
+
+        carry7 = (s7 + (1 << 20)) >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry9 = (s9 + (1 << 20)) >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry11 = (s11 + (1 << 20)) >> 21;
+        s12 += carry11;
+        s11 -= carry11 << 21;
+        carry13 = (s13 + (1 << 20)) >> 21;
+        s14 += carry13;
+        s13 -= carry13 << 21;
+        carry15 = (s15 + (1 << 20)) >> 21;
+        s16 += carry15;
+        s15 -= carry15 << 21;
+
+        // Resume reduction where we left off.
+        s5 += s17 * 666643;
+        s6 += s17 * 470296;
+        s7 += s17 * 654183;
+        s8 -= s17 * 997805;
+        s9 += s17 * 136657;
+        s10 -= s17 * 683901;
+        // s17 = 0;
+
+        s4 += s16 * 666643;
+        s5 += s16 * 470296;
+        s6 += s16 * 654183;
+        s7 -= s16 * 997805;
+        s8 += s16 * 136657;
+        s9 -= s16 * 683901;
+        // s16 = 0;
+
+        s3 += s15 * 666643;
+        s4 += s15 * 470296;
+        s5 += s15 * 654183;
+        s6 -= s15 * 997805;
+        s7 += s15 * 136657;
+        s8 -= s15 * 683901;
+        // s15 = 0;
+
+        s2 += s14 * 666643;
+        s3 += s14 * 470296;
+        s4 += s14 * 654183;
+        s5 -= s14 * 997805;
+        s6 += s14 * 136657;
+        s7 -= s14 * 683901;
+        // s14 = 0;
+
+        s1 += s13 * 666643;
+        s2 += s13 * 470296;
+        s3 += s13 * 654183;
+        s4 -= s13 * 997805;
+        s5 += s13 * 136657;
+        s6 -= s13 * 683901;
+        // s13 = 0;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        // Reduce the range of limbs from s0 to s11 to 21-bits.
+        carry0 = (s0 + (1 << 20)) >> 21;
+        s1 += carry0;
+        s0 -= carry0 << 21;
+        carry2 = (s2 + (1 << 20)) >> 21;
+        s3 += carry2;
+        s2 -= carry2 << 21;
+        carry4 = (s4 + (1 << 20)) >> 21;
+        s5 += carry4;
+        s4 -= carry4 << 21;
+        carry6 = (s6 + (1 << 20)) >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry8 = (s8 + (1 << 20)) >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry10 = (s10 + (1 << 20)) >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+
+        carry1 = (s1 + (1 << 20)) >> 21;
+        s2 += carry1;
+        s1 -= carry1 << 21;
+        carry3 = (s3 + (1 << 20)) >> 21;
+        s4 += carry3;
+        s3 -= carry3 << 21;
+        carry5 = (s5 + (1 << 20)) >> 21;
+        s6 += carry5;
+        s5 -= carry5 << 21;
+        carry7 = (s7 + (1 << 20)) >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry9 = (s9 + (1 << 20)) >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry11 = (s11 + (1 << 20)) >> 21;
+        s12 += carry11;
+        s11 -= carry11 << 21;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        // Carry chain reduction to propagate excess bits from s0 to s5 to the most significant limbs.
+        carry0 = s0 >> 21;
+        s1 += carry0;
+        s0 -= carry0 << 21;
+        carry1 = s1 >> 21;
+        s2 += carry1;
+        s1 -= carry1 << 21;
+        carry2 = s2 >> 21;
+        s3 += carry2;
+        s2 -= carry2 << 21;
+        carry3 = s3 >> 21;
+        s4 += carry3;
+        s3 -= carry3 << 21;
+        carry4 = s4 >> 21;
+        s5 += carry4;
+        s4 -= carry4 << 21;
+        carry5 = s5 >> 21;
+        s6 += carry5;
+        s5 -= carry5 << 21;
+        carry6 = s6 >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry7 = s7 >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry8 = s8 >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry9 = s9 >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry10 = s10 >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+        carry11 = s11 >> 21;
+        s12 += carry11;
+        s11 -= carry11 << 21;
+
+        // Do one last reduction as s12 might be 1.
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        // s12 = 0;
+
+        carry0 = s0 >> 21;
+        s1 += carry0;
+        s0 -= carry0 << 21;
+        carry1 = s1 >> 21;
+        s2 += carry1;
+        s1 -= carry1 << 21;
+        carry2 = s2 >> 21;
+        s3 += carry2;
+        s2 -= carry2 << 21;
+        carry3 = s3 >> 21;
+        s4 += carry3;
+        s3 -= carry3 << 21;
+        carry4 = s4 >> 21;
+        s5 += carry4;
+        s4 -= carry4 << 21;
+        carry5 = s5 >> 21;
+        s6 += carry5;
+        s5 -= carry5 << 21;
+        carry6 = s6 >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry7 = s7 >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry8 = s8 >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry9 = s9 >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry10 = s10 >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+
+        // Serialize the result into the s.
+        s[0] = (byte) s0;
+        s[1] = (byte) (s0 >> 8);
+        s[2] = (byte) ((s0 >> 16) | (s1 << 5));
+        s[3] = (byte) (s1 >> 3);
+        s[4] = (byte) (s1 >> 11);
+        s[5] = (byte) ((s1 >> 19) | (s2 << 2));
+        s[6] = (byte) (s2 >> 6);
+        s[7] = (byte) ((s2 >> 14) | (s3 << 7));
+        s[8] = (byte) (s3 >> 1);
+        s[9] = (byte) (s3 >> 9);
+        s[10] = (byte) ((s3 >> 17) | (s4 << 4));
+        s[11] = (byte) (s4 >> 4);
+        s[12] = (byte) (s4 >> 12);
+        s[13] = (byte) ((s4 >> 20) | (s5 << 1));
+        s[14] = (byte) (s5 >> 7);
+        s[15] = (byte) ((s5 >> 15) | (s6 << 6));
+        s[16] = (byte) (s6 >> 2);
+        s[17] = (byte) (s6 >> 10);
+        s[18] = (byte) ((s6 >> 18) | (s7 << 3));
+        s[19] = (byte) (s7 >> 5);
+        s[20] = (byte) (s7 >> 13);
+        s[21] = (byte) s8;
+        s[22] = (byte) (s8 >> 8);
+        s[23] = (byte) ((s8 >> 16) | (s9 << 5));
+        s[24] = (byte) (s9 >> 3);
+        s[25] = (byte) (s9 >> 11);
+        s[26] = (byte) ((s9 >> 19) | (s10 << 2));
+        s[27] = (byte) (s10 >> 6);
+        s[28] = (byte) ((s10 >> 14) | (s11 << 7));
+        s[29] = (byte) (s11 >> 1);
+        s[30] = (byte) (s11 >> 9);
+        s[31] = (byte) (s11 >> 17);
+    }
+
+    /**
+     * Input:
+     * a[0]+256*a[1]+...+256^31*a[31] = a
+     * b[0]+256*b[1]+...+256^31*b[31] = b
+     * c[0]+256*c[1]+...+256^31*c[31] = c
+     * <p>
+     * Output:
+     * s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l
+     * where l = 2^252 + 27742317777372353535851937790883648493.
+     */
+    private static void mulAdd(byte[] s, byte[] a, byte[] b, byte[] c) {
+        // This is very similar to Ed25519.reduce, the difference in here is that it computes ab+c
+        // See Ed25519.reduce for related comments.
+        long a0 = 2097151 & load3(a, 0);
+        long a1 = 2097151 & (load4(a, 2) >> 5);
+        long a2 = 2097151 & (load3(a, 5) >> 2);
+        long a3 = 2097151 & (load4(a, 7) >> 7);
+        long a4 = 2097151 & (load4(a, 10) >> 4);
+        long a5 = 2097151 & (load3(a, 13) >> 1);
+        long a6 = 2097151 & (load4(a, 15) >> 6);
+        long a7 = 2097151 & (load3(a, 18) >> 3);
+        long a8 = 2097151 & load3(a, 21);
+        long a9 = 2097151 & (load4(a, 23) >> 5);
+        long a10 = 2097151 & (load3(a, 26) >> 2);
+        long a11 = (load4(a, 28) >> 7);
+        long b0 = 2097151 & load3(b, 0);
+        long b1 = 2097151 & (load4(b, 2) >> 5);
+        long b2 = 2097151 & (load3(b, 5) >> 2);
+        long b3 = 2097151 & (load4(b, 7) >> 7);
+        long b4 = 2097151 & (load4(b, 10) >> 4);
+        long b5 = 2097151 & (load3(b, 13) >> 1);
+        long b6 = 2097151 & (load4(b, 15) >> 6);
+        long b7 = 2097151 & (load3(b, 18) >> 3);
+        long b8 = 2097151 & load3(b, 21);
+        long b9 = 2097151 & (load4(b, 23) >> 5);
+        long b10 = 2097151 & (load3(b, 26) >> 2);
+        long b11 = (load4(b, 28) >> 7);
+        long c0 = 2097151 & load3(c, 0);
+        long c1 = 2097151 & (load4(c, 2) >> 5);
+        long c2 = 2097151 & (load3(c, 5) >> 2);
+        long c3 = 2097151 & (load4(c, 7) >> 7);
+        long c4 = 2097151 & (load4(c, 10) >> 4);
+        long c5 = 2097151 & (load3(c, 13) >> 1);
+        long c6 = 2097151 & (load4(c, 15) >> 6);
+        long c7 = 2097151 & (load3(c, 18) >> 3);
+        long c8 = 2097151 & load3(c, 21);
+        long c9 = 2097151 & (load4(c, 23) >> 5);
+        long c10 = 2097151 & (load3(c, 26) >> 2);
+        long c11 = (load4(c, 28) >> 7);
+        long s0;
+        long s1;
+        long s2;
+        long s3;
+        long s4;
+        long s5;
+        long s6;
+        long s7;
+        long s8;
+        long s9;
+        long s10;
+        long s11;
+        long s12;
+        long s13;
+        long s14;
+        long s15;
+        long s16;
+        long s17;
+        long s18;
+        long s19;
+        long s20;
+        long s21;
+        long s22;
+        long s23;
+        long carry0;
+        long carry1;
+        long carry2;
+        long carry3;
+        long carry4;
+        long carry5;
+        long carry6;
+        long carry7;
+        long carry8;
+        long carry9;
+        long carry10;
+        long carry11;
+        long carry12;
+        long carry13;
+        long carry14;
+        long carry15;
+        long carry16;
+        long carry17;
+        long carry18;
+        long carry19;
+        long carry20;
+        long carry21;
+        long carry22;
+
+        s0 = c0 + a0 * b0;
+        s1 = c1 + a0 * b1 + a1 * b0;
+        s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
+        s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
+        s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
+        s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
+        s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
+        s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0;
+        s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1
+                + a8 * b0;
+        s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2
+                + a8 * b1 + a9 * b0;
+        s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3
+                + a8 * b2 + a9 * b1 + a10 * b0;
+        s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4
+                + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
+        s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3
+                + a10 * b2 + a11 * b1;
+        s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3
+                + a11 * b2;
+        s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4
+                + a11 * b3;
+        s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4;
+        s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
+        s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
+        s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
+        s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
+        s20 = a9 * b11 + a10 * b10 + a11 * b9;
+        s21 = a10 * b11 + a11 * b10;
+        s22 = a11 * b11;
+        s23 = 0;
+
+        carry0 = (s0 + (1 << 20)) >> 21;
+        s1 += carry0;
+        s0 -= carry0 << 21;
+        carry2 = (s2 + (1 << 20)) >> 21;
+        s3 += carry2;
+        s2 -= carry2 << 21;
+        carry4 = (s4 + (1 << 20)) >> 21;
+        s5 += carry4;
+        s4 -= carry4 << 21;
+        carry6 = (s6 + (1 << 20)) >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry8 = (s8 + (1 << 20)) >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry10 = (s10 + (1 << 20)) >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+        carry12 = (s12 + (1 << 20)) >> 21;
+        s13 += carry12;
+        s12 -= carry12 << 21;
+        carry14 = (s14 + (1 << 20)) >> 21;
+        s15 += carry14;
+        s14 -= carry14 << 21;
+        carry16 = (s16 + (1 << 20)) >> 21;
+        s17 += carry16;
+        s16 -= carry16 << 21;
+        carry18 = (s18 + (1 << 20)) >> 21;
+        s19 += carry18;
+        s18 -= carry18 << 21;
+        carry20 = (s20 + (1 << 20)) >> 21;
+        s21 += carry20;
+        s20 -= carry20 << 21;
+        carry22 = (s22 + (1 << 20)) >> 21;
+        s23 += carry22;
+        s22 -= carry22 << 21;
+
+        carry1 = (s1 + (1 << 20)) >> 21;
+        s2 += carry1;
+        s1 -= carry1 << 21;
+        carry3 = (s3 + (1 << 20)) >> 21;
+        s4 += carry3;
+        s3 -= carry3 << 21;
+        carry5 = (s5 + (1 << 20)) >> 21;
+        s6 += carry5;
+        s5 -= carry5 << 21;
+        carry7 = (s7 + (1 << 20)) >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry9 = (s9 + (1 << 20)) >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry11 = (s11 + (1 << 20)) >> 21;
+        s12 += carry11;
+        s11 -= carry11 << 21;
+        carry13 = (s13 + (1 << 20)) >> 21;
+        s14 += carry13;
+        s13 -= carry13 << 21;
+        carry15 = (s15 + (1 << 20)) >> 21;
+        s16 += carry15;
+        s15 -= carry15 << 21;
+        carry17 = (s17 + (1 << 20)) >> 21;
+        s18 += carry17;
+        s17 -= carry17 << 21;
+        carry19 = (s19 + (1 << 20)) >> 21;
+        s20 += carry19;
+        s19 -= carry19 << 21;
+        carry21 = (s21 + (1 << 20)) >> 21;
+        s22 += carry21;
+        s21 -= carry21 << 21;
+
+        s11 += s23 * 666643;
+        s12 += s23 * 470296;
+        s13 += s23 * 654183;
+        s14 -= s23 * 997805;
+        s15 += s23 * 136657;
+        s16 -= s23 * 683901;
+        // s23 = 0;
+
+        s10 += s22 * 666643;
+        s11 += s22 * 470296;
+        s12 += s22 * 654183;
+        s13 -= s22 * 997805;
+        s14 += s22 * 136657;
+        s15 -= s22 * 683901;
+        // s22 = 0;
+
+        s9 += s21 * 666643;
+        s10 += s21 * 470296;
+        s11 += s21 * 654183;
+        s12 -= s21 * 997805;
+        s13 += s21 * 136657;
+        s14 -= s21 * 683901;
+        // s21 = 0;
+
+        s8 += s20 * 666643;
+        s9 += s20 * 470296;
+        s10 += s20 * 654183;
+        s11 -= s20 * 997805;
+        s12 += s20 * 136657;
+        s13 -= s20 * 683901;
+        // s20 = 0;
+
+        s7 += s19 * 666643;
+        s8 += s19 * 470296;
+        s9 += s19 * 654183;
+        s10 -= s19 * 997805;
+        s11 += s19 * 136657;
+        s12 -= s19 * 683901;
+        // s19 = 0;
+
+        s6 += s18 * 666643;
+        s7 += s18 * 470296;
+        s8 += s18 * 654183;
+        s9 -= s18 * 997805;
+        s10 += s18 * 136657;
+        s11 -= s18 * 683901;
+        // s18 = 0;
+
+        carry6 = (s6 + (1 << 20)) >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry8 = (s8 + (1 << 20)) >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry10 = (s10 + (1 << 20)) >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+        carry12 = (s12 + (1 << 20)) >> 21;
+        s13 += carry12;
+        s12 -= carry12 << 21;
+        carry14 = (s14 + (1 << 20)) >> 21;
+        s15 += carry14;
+        s14 -= carry14 << 21;
+        carry16 = (s16 + (1 << 20)) >> 21;
+        s17 += carry16;
+        s16 -= carry16 << 21;
+
+        carry7 = (s7 + (1 << 20)) >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry9 = (s9 + (1 << 20)) >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry11 = (s11 + (1 << 20)) >> 21;
+        s12 += carry11;
+        s11 -= carry11 << 21;
+        carry13 = (s13 + (1 << 20)) >> 21;
+        s14 += carry13;
+        s13 -= carry13 << 21;
+        carry15 = (s15 + (1 << 20)) >> 21;
+        s16 += carry15;
+        s15 -= carry15 << 21;
+
+        s5 += s17 * 666643;
+        s6 += s17 * 470296;
+        s7 += s17 * 654183;
+        s8 -= s17 * 997805;
+        s9 += s17 * 136657;
+        s10 -= s17 * 683901;
+        // s17 = 0;
+
+        s4 += s16 * 666643;
+        s5 += s16 * 470296;
+        s6 += s16 * 654183;
+        s7 -= s16 * 997805;
+        s8 += s16 * 136657;
+        s9 -= s16 * 683901;
+        // s16 = 0;
+
+        s3 += s15 * 666643;
+        s4 += s15 * 470296;
+        s5 += s15 * 654183;
+        s6 -= s15 * 997805;
+        s7 += s15 * 136657;
+        s8 -= s15 * 683901;
+        // s15 = 0;
+
+        s2 += s14 * 666643;
+        s3 += s14 * 470296;
+        s4 += s14 * 654183;
+        s5 -= s14 * 997805;
+        s6 += s14 * 136657;
+        s7 -= s14 * 683901;
+        // s14 = 0;
+
+        s1 += s13 * 666643;
+        s2 += s13 * 470296;
+        s3 += s13 * 654183;
+        s4 -= s13 * 997805;
+        s5 += s13 * 136657;
+        s6 -= s13 * 683901;
+        // s13 = 0;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = (s0 + (1 << 20)) >> 21;
+        s1 += carry0;
+        s0 -= carry0 << 21;
+        carry2 = (s2 + (1 << 20)) >> 21;
+        s3 += carry2;
+        s2 -= carry2 << 21;
+        carry4 = (s4 + (1 << 20)) >> 21;
+        s5 += carry4;
+        s4 -= carry4 << 21;
+        carry6 = (s6 + (1 << 20)) >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry8 = (s8 + (1 << 20)) >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry10 = (s10 + (1 << 20)) >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+
+        carry1 = (s1 + (1 << 20)) >> 21;
+        s2 += carry1;
+        s1 -= carry1 << 21;
+        carry3 = (s3 + (1 << 20)) >> 21;
+        s4 += carry3;
+        s3 -= carry3 << 21;
+        carry5 = (s5 + (1 << 20)) >> 21;
+        s6 += carry5;
+        s5 -= carry5 << 21;
+        carry7 = (s7 + (1 << 20)) >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry9 = (s9 + (1 << 20)) >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry11 = (s11 + (1 << 20)) >> 21;
+        s12 += carry11;
+        s11 -= carry11 << 21;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        s12 = 0;
+
+        carry0 = s0 >> 21;
+        s1 += carry0;
+        s0 -= carry0 << 21;
+        carry1 = s1 >> 21;
+        s2 += carry1;
+        s1 -= carry1 << 21;
+        carry2 = s2 >> 21;
+        s3 += carry2;
+        s2 -= carry2 << 21;
+        carry3 = s3 >> 21;
+        s4 += carry3;
+        s3 -= carry3 << 21;
+        carry4 = s4 >> 21;
+        s5 += carry4;
+        s4 -= carry4 << 21;
+        carry5 = s5 >> 21;
+        s6 += carry5;
+        s5 -= carry5 << 21;
+        carry6 = s6 >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry7 = s7 >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry8 = s8 >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry9 = s9 >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry10 = s10 >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+        carry11 = s11 >> 21;
+        s12 += carry11;
+        s11 -= carry11 << 21;
+
+        s0 += s12 * 666643;
+        s1 += s12 * 470296;
+        s2 += s12 * 654183;
+        s3 -= s12 * 997805;
+        s4 += s12 * 136657;
+        s5 -= s12 * 683901;
+        // s12 = 0;
+
+        carry0 = s0 >> 21;
+        s1 += carry0;
+        s0 -= carry0 << 21;
+        carry1 = s1 >> 21;
+        s2 += carry1;
+        s1 -= carry1 << 21;
+        carry2 = s2 >> 21;
+        s3 += carry2;
+        s2 -= carry2 << 21;
+        carry3 = s3 >> 21;
+        s4 += carry3;
+        s3 -= carry3 << 21;
+        carry4 = s4 >> 21;
+        s5 += carry4;
+        s4 -= carry4 << 21;
+        carry5 = s5 >> 21;
+        s6 += carry5;
+        s5 -= carry5 << 21;
+        carry6 = s6 >> 21;
+        s7 += carry6;
+        s6 -= carry6 << 21;
+        carry7 = s7 >> 21;
+        s8 += carry7;
+        s7 -= carry7 << 21;
+        carry8 = s8 >> 21;
+        s9 += carry8;
+        s8 -= carry8 << 21;
+        carry9 = s9 >> 21;
+        s10 += carry9;
+        s9 -= carry9 << 21;
+        carry10 = s10 >> 21;
+        s11 += carry10;
+        s10 -= carry10 << 21;
+
+        s[0] = (byte) s0;
+        s[1] = (byte) (s0 >> 8);
+        s[2] = (byte) ((s0 >> 16) | (s1 << 5));
+        s[3] = (byte) (s1 >> 3);
+        s[4] = (byte) (s1 >> 11);
+        s[5] = (byte) ((s1 >> 19) | (s2 << 2));
+        s[6] = (byte) (s2 >> 6);
+        s[7] = (byte) ((s2 >> 14) | (s3 << 7));
+        s[8] = (byte) (s3 >> 1);
+        s[9] = (byte) (s3 >> 9);
+        s[10] = (byte) ((s3 >> 17) | (s4 << 4));
+        s[11] = (byte) (s4 >> 4);
+        s[12] = (byte) (s4 >> 12);
+        s[13] = (byte) ((s4 >> 20) | (s5 << 1));
+        s[14] = (byte) (s5 >> 7);
+        s[15] = (byte) ((s5 >> 15) | (s6 << 6));
+        s[16] = (byte) (s6 >> 2);
+        s[17] = (byte) (s6 >> 10);
+        s[18] = (byte) ((s6 >> 18) | (s7 << 3));
+        s[19] = (byte) (s7 >> 5);
+        s[20] = (byte) (s7 >> 13);
+        s[21] = (byte) s8;
+        s[22] = (byte) (s8 >> 8);
+        s[23] = (byte) ((s8 >> 16) | (s9 << 5));
+        s[24] = (byte) (s9 >> 3);
+        s[25] = (byte) (s9 >> 11);
+        s[26] = (byte) ((s9 >> 19) | (s10 << 2));
+        s[27] = (byte) (s10 >> 6);
+        s[28] = (byte) ((s10 >> 14) | (s11 << 7));
+        s[29] = (byte) (s11 >> 1);
+        s[30] = (byte) (s11 >> 9);
+        s[31] = (byte) (s11 >> 17);
+    }
+
+    // The order of the generator as unsigned bytes in little endian order.
+    // (2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed, cf. RFC 7748)
+    private static final byte[] GROUP_ORDER = {
+            (byte) 0xed, (byte) 0xd3, (byte) 0xf5, (byte) 0x5c,
+            (byte) 0x1a, (byte) 0x63, (byte) 0x12, (byte) 0x58,
+            (byte) 0xd6, (byte) 0x9c, (byte) 0xf7, (byte) 0xa2,
+            (byte) 0xde, (byte) 0xf9, (byte) 0xde, (byte) 0x14,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10};
+
+    // Checks whether s represents an integer smaller than the order of the group.
+    // This is needed to ensure that EdDSA signatures are non-malleable, as failing to check
+    // the range of S allows to modify signatures (cf. RFC 8032, Section 5.2.7 and Section 8.4.)
+    // @param s an integer in little-endian order.
+    private static boolean isSmallerThanGroupOrder(byte[] s) {
+        for (int j = Field25519.FIELD_LEN - 1; j >= 0; j--) {
+            // compare unsigned bytes
+            int a = s[j] & 0xff;
+            int b = GROUP_ORDER[j] & 0xff;
+            if (a != b) {
+                return a < b;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the EdDSA {@code signature} with {@code message}, can be verified with
+     * {@code publicKey}.
+     */
+    public static boolean verify(final byte[] message, final byte[] signature,
+                                 final byte[] publicKey) {
+        try {
+            if (signature.length != SIGNATURE_LEN) {
+                return false;
+            }
+            if (publicKey.length != PUBLIC_KEY_LEN) {
+                return false;
+            }
+            byte[] s = Arrays.copyOfRange(signature, Field25519.FIELD_LEN, SIGNATURE_LEN);
+            if (!isSmallerThanGroupOrder(s)) {
+                return false;
+            }
+            MessageDigest digest = MessageDigest.getInstance("SHA-512");
+            digest.update(signature, 0, Field25519.FIELD_LEN);
+            digest.update(publicKey);
+            digest.update(message);
+            byte[] h = digest.digest();
+            reduce(h);
+
+            XYZT negPublicKey = XYZT.fromBytesNegateVarTime(publicKey);
+            XYZ xyz = doubleScalarMultVarTime(h, negPublicKey, s);
+            byte[] expectedR = xyz.toBytes();
+            for (int i = 0; i < Field25519.FIELD_LEN; i++) {
+                if (expectedR[i] != signature[i]) {
+                    return false;
+                }
+            }
+            return true;
+        } catch (final GeneralSecurityException ignored) {
+            return false;
+        }
+    }
+}

+ 288 - 0
app/src/main/java/com/wireguard/crypto/Key.java

@@ -0,0 +1,288 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.crypto;
+
+import com.wireguard.crypto.KeyFormatException.Type;
+
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+/**
+ * Represents a WireGuard public or private key. This class uses specialized constant-time base64
+ * and hexadecimal codec implementations that resist side-channel attacks.
+ * <p>
+ * Instances of this class are immutable.
+ */
+@SuppressWarnings("MagicNumber")
+public final class Key {
+    private final byte[] key;
+
+    /**
+     * Constructs an object encapsulating the supplied key.
+     *
+     * @param key an array of bytes containing a binary key. Callers of this constructor are
+     *            responsible for ensuring that the array is of the correct length.
+     */
+    private Key(final byte[] key) {
+        // Defensively copy to ensure immutability.
+        this.key = Arrays.copyOf(key, key.length);
+    }
+
+    /**
+     * Decodes a single 4-character base64 chunk to an integer in constant time.
+     *
+     * @param src       an array of at least 4 characters in base64 format
+     * @param srcOffset the offset of the beginning of the chunk in {@code src}
+     * @return the decoded 3-byte integer, or some arbitrary integer value if the input was not
+     * valid base64
+     */
+    private static int decodeBase64(final char[] src, final int srcOffset) {
+        int val = 0;
+        for (int i = 0; i < 4; ++i) {
+            final char c = src[i + srcOffset];
+            val |= (-1
+                    + ((((('A' - 1) - c) & (c - ('Z' + 1))) >>> 8) & (c - 64))
+                    + ((((('a' - 1) - c) & (c - ('z' + 1))) >>> 8) & (c - 70))
+                    + ((((('0' - 1) - c) & (c - ('9' + 1))) >>> 8) & (c + 5))
+                    + ((((('+' - 1) - c) & (c - ('+' + 1))) >>> 8) & 63)
+                    + ((((('/' - 1) - c) & (c - ('/' + 1))) >>> 8) & 64)
+            ) << (18 - 6 * i);
+        }
+        return val;
+    }
+
+    /**
+     * Encodes a single 4-character base64 chunk from 3 consecutive bytes in constant time.
+     *
+     * @param src        an array of at least 3 bytes
+     * @param srcOffset  the offset of the beginning of the chunk in {@code src}
+     * @param dest       an array of at least 4 characters
+     * @param destOffset the offset of the beginning of the chunk in {@code dest}
+     */
+    private static void encodeBase64(final byte[] src, final int srcOffset,
+                                     final char[] dest, final int destOffset) {
+        final byte[] input = {
+                (byte) ((src[srcOffset] >>> 2) & 63),
+                (byte) ((src[srcOffset] << 4 | ((src[1 + srcOffset] & 0xff) >>> 4)) & 63),
+                (byte) ((src[1 + srcOffset] << 2 | ((src[2 + srcOffset] & 0xff) >>> 6)) & 63),
+                (byte) ((src[2 + srcOffset]) & 63),
+        };
+        for (int i = 0; i < 4; ++i) {
+            dest[i + destOffset] = (char) (input[i] + 'A'
+                    + (((25 - input[i]) >>> 8) & 6)
+                    - (((51 - input[i]) >>> 8) & 75)
+                    - (((61 - input[i]) >>> 8) & 15)
+                    + (((62 - input[i]) >>> 8) & 3));
+        }
+    }
+
+    /**
+     * Decodes a WireGuard public or private key from its base64 string representation. This
+     * function throws a {@link KeyFormatException} if the source string is not well-formed.
+     *
+     * @param str the base64 string representation of a WireGuard key
+     * @return the decoded key encapsulated in an immutable container
+     */
+    public static Key fromBase64(final String str) throws KeyFormatException {
+        final char[] input = str.toCharArray();
+        if (input.length != Format.BASE64.length || input[Format.BASE64.length - 1] != '=')
+            throw new KeyFormatException(Format.BASE64, Type.LENGTH);
+        final byte[] key = new byte[Format.BINARY.length];
+        int i;
+        int ret = 0;
+        for (i = 0; i < key.length / 3; ++i) {
+            final int val = decodeBase64(input, i * 4);
+            ret |= val >>> 31;
+            key[i * 3] = (byte) ((val >>> 16) & 0xff);
+            key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
+            key[i * 3 + 2] = (byte) (val & 0xff);
+        }
+        final char[] endSegment = {
+                input[i * 4],
+                input[i * 4 + 1],
+                input[i * 4 + 2],
+                'A',
+        };
+        final int val = decodeBase64(endSegment, 0);
+        ret |= (val >>> 31) | (val & 0xff);
+        key[i * 3] = (byte) ((val >>> 16) & 0xff);
+        key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
+
+        if (ret != 0)
+            throw new KeyFormatException(Format.BASE64, Type.CONTENTS);
+        return new Key(key);
+    }
+
+    /**
+     * Wraps a WireGuard public or private key in an immutable container. This function throws a
+     * {@link KeyFormatException} if the source data is not the correct length.
+     *
+     * @param bytes an array of bytes containing a WireGuard key in binary format
+     * @return the key encapsulated in an immutable container
+     */
+    public static Key fromBytes(final byte[] bytes) throws KeyFormatException {
+        if (bytes.length != Format.BINARY.length)
+            throw new KeyFormatException(Format.BINARY, Type.LENGTH);
+        return new Key(bytes);
+    }
+
+    /**
+     * Decodes a WireGuard public or private key from its hexadecimal string representation. This
+     * function throws a {@link KeyFormatException} if the source string is not well-formed.
+     *
+     * @param str the hexadecimal string representation of a WireGuard key
+     * @return the decoded key encapsulated in an immutable container
+     */
+    public static Key fromHex(final String str) throws KeyFormatException {
+        final char[] input = str.toCharArray();
+        if (input.length != Format.HEX.length)
+            throw new KeyFormatException(Format.HEX, Type.LENGTH);
+        final byte[] key = new byte[Format.BINARY.length];
+        int ret = 0;
+        for (int i = 0; i < key.length; ++i) {
+            int c;
+            int cNum;
+            int cNum0;
+            int cAlpha;
+            int cAlpha0;
+            int cVal;
+            final int cAcc;
+
+            c = input[i * 2];
+            cNum = c ^ 48;
+            cNum0 = ((cNum - 10) >>> 8) & 0xff;
+            cAlpha = (c & ~32) - 55;
+            cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff;
+            ret |= ((cNum0 | cAlpha0) - 1) >>> 8;
+            cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha);
+            cAcc = cVal * 16;
+
+            c = input[i * 2 + 1];
+            cNum = c ^ 48;
+            cNum0 = ((cNum - 10) >>> 8) & 0xff;
+            cAlpha = (c & ~32) - 55;
+            cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff;
+            ret |= ((cNum0 | cAlpha0) - 1) >>> 8;
+            cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha);
+            key[i] = (byte) (cAcc | cVal);
+        }
+        if (ret != 0)
+            throw new KeyFormatException(Format.HEX, Type.CONTENTS);
+        return new Key(key);
+    }
+
+    /**
+     * Generates a private key using the system's {@link SecureRandom} number generator.
+     *
+     * @return a well-formed random private key
+     */
+    static Key generatePrivateKey() {
+        final SecureRandom secureRandom = new SecureRandom();
+        final byte[] privateKey = new byte[Format.BINARY.getLength()];
+        secureRandom.nextBytes(privateKey);
+        privateKey[0] &= 248;
+        privateKey[31] &= 127;
+        privateKey[31] |= 64;
+        return new Key(privateKey);
+    }
+
+    /**
+     * Generates a public key from an existing private key.
+     *
+     * @param privateKey a private key
+     * @return a well-formed public key that corresponds to the supplied private key
+     */
+    static Key generatePublicKey(final Key privateKey) {
+        final byte[] publicKey = new byte[Format.BINARY.getLength()];
+        Curve25519.eval(publicKey, 0, privateKey.getBytes(), null);
+        return new Key(publicKey);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this)
+            return true;
+        if (obj == null || obj.getClass() != getClass())
+            return false;
+        final Key other = (Key) obj;
+        return MessageDigest.isEqual(key, other.key);
+    }
+
+    /**
+     * Returns the key as an array of bytes.
+     *
+     * @return an array of bytes containing the raw binary key
+     */
+    public byte[] getBytes() {
+        // Defensively copy to ensure immutability.
+        return Arrays.copyOf(key, key.length);
+    }
+
+    @Override
+    public int hashCode() {
+        int ret = 0;
+        for (int i = 0; i < key.length / 4; ++i)
+            ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24);
+        return ret;
+    }
+
+    /**
+     * Encodes the key to base64.
+     *
+     * @return a string containing the encoded key
+     */
+    public String toBase64() {
+        final char[] output = new char[Format.BASE64.length];
+        int i;
+        for (i = 0; i < key.length / 3; ++i)
+            encodeBase64(key, i * 3, output, i * 4);
+        final byte[] endSegment = {
+                key[i * 3],
+                key[i * 3 + 1],
+                0,
+        };
+        encodeBase64(endSegment, 0, output, i * 4);
+        output[Format.BASE64.length - 1] = '=';
+        return new String(output);
+    }
+
+    /**
+     * Encodes the key to hexadecimal ASCII characters.
+     *
+     * @return a string containing the encoded key
+     */
+    public String toHex() {
+        final char[] output = new char[Format.HEX.length];
+        for (int i = 0; i < key.length; ++i) {
+            output[i * 2] = (char) (87 + (key[i] >> 4 & 0xf)
+                    + ((((key[i] >> 4 & 0xf) - 10) >> 8) & ~38));
+            output[i * 2 + 1] = (char) (87 + (key[i] & 0xf)
+                    + ((((key[i] & 0xf) - 10) >> 8) & ~38));
+        }
+        return new String(output);
+    }
+
+    /**
+     * The supported formats for encoding a WireGuard key.
+     */
+    public enum Format {
+        BASE64(44),
+        BINARY(32),
+        HEX(64);
+
+        private final int length;
+
+        Format(final int length) {
+            this.length = length;
+        }
+
+        public int getLength() {
+            return length;
+        }
+    }
+
+}

+ 34 - 0
app/src/main/java/com/wireguard/crypto/KeyFormatException.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.crypto;
+
+/**
+ * An exception thrown when attempting to parse an invalid key (too short, too long, or byte
+ * data inappropriate for the format). The format being parsed can be accessed with the
+ * {@link #getFormat} method.
+ */
+public final class KeyFormatException extends Exception {
+    private final Key.Format format;
+    private final Type type;
+
+    KeyFormatException(final Key.Format format, final Type type) {
+        this.format = format;
+        this.type = type;
+    }
+
+    public Key.Format getFormat() {
+        return format;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public enum Type {
+        CONTENTS,
+        LENGTH
+    }
+}

+ 51 - 0
app/src/main/java/com/wireguard/crypto/KeyPair.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.crypto;
+
+/**
+ * Represents a Curve25519 key pair as used by WireGuard.
+ * <p>
+ * Instances of this class are immutable.
+ */
+public class KeyPair {
+    private final Key privateKey;
+    private final Key publicKey;
+
+    /**
+     * Creates a key pair using a newly-generated private key.
+     */
+    public KeyPair() {
+        this(Key.generatePrivateKey());
+    }
+
+    /**
+     * Creates a key pair using an existing private key.
+     *
+     * @param privateKey a private key, used to derive the public key
+     */
+    public KeyPair(final Key privateKey) {
+        this.privateKey = privateKey;
+        publicKey = Key.generatePublicKey(privateKey);
+    }
+
+    /**
+     * Returns the private key from the key pair.
+     *
+     * @return the private key
+     */
+    public Key getPrivateKey() {
+        return privateKey;
+    }
+
+    /**
+     * Returns the public key from the key pair.
+     *
+     * @return the public key
+     */
+    public Key getPublicKey() {
+        return publicKey;
+    }
+}

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/Constants.kt

@@ -148,6 +148,7 @@ object Key {
     const val SERVER_VMESS_EXPERIMENTAL_NO_TERMINATION_SIGNAL = "serverVMessExperimentalNoTerminationSignal"
 
     const val SERVER_PRIVATE_KEY = "serverPrivateKey"
+    const val SERVER_LOCAL_ADDRESS = "serverLocalAddress"
 
     const val BALANCER_TYPE = "balancerType"
     const val BALANCER_GROUP = "balancerGroup"

+ 2 - 1
app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt

@@ -42,7 +42,8 @@ object Executable {
         "libbrook.so",
         "libhysteria.so",
         "libpingtunnel.so",
-        "librelaybaton.so"
+        "librelaybaton.so",
+        "libwg.so"
     )
 
     fun killAll() {

+ 3 - 1
app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt

@@ -51,7 +51,9 @@ class ProxyInstance(profile: ProxyEntity, val service: BaseService.Interface) :
 
     override fun init() {
         if (service is VpnService) {
-            Libcore.setProtector { service.protect(it.toInt()) }
+            Libcore.setProtector { service.protect(it) }
+        } else {
+            Libcore.setProtector { true }
         }
 
         Libcore.setIPv6Mode(DataStore.ipv6Mode.toLong())

+ 31 - 4
app/src/main/java/io/nekohasekai/sagernet/bg/proto/V2RayInstance.kt

@@ -28,10 +28,7 @@ import android.webkit.WebViewClient
 import io.nekohasekai.sagernet.SagerNet
 import io.nekohasekai.sagernet.ShadowsocksProvider
 import io.nekohasekai.sagernet.TrojanProvider
-import io.nekohasekai.sagernet.bg.AbstractInstance
-import io.nekohasekai.sagernet.bg.Executable
-import io.nekohasekai.sagernet.bg.ExternalInstance
-import io.nekohasekai.sagernet.bg.GuardedProcessPool
+import io.nekohasekai.sagernet.bg.*
 import io.nekohasekai.sagernet.database.DataStore
 import io.nekohasekai.sagernet.database.ProxyEntity
 import io.nekohasekai.sagernet.fmt.LOCALHOST
@@ -59,6 +56,8 @@ import io.nekohasekai.sagernet.fmt.trojan.buildTrojanGoConfig
 import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean
 import io.nekohasekai.sagernet.fmt.trojan_go.buildCustomTrojanConfig
 import io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig
+import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
+import io.nekohasekai.sagernet.fmt.wireguard.buildWireGuardUapiConf
 import io.nekohasekai.sagernet.ktx.*
 import io.nekohasekai.sagernet.plugin.PluginManager
 import kotlinx.coroutines.CoroutineScope
@@ -169,6 +168,10 @@ abstract class V2RayInstance(
                             }
                         }
                     }
+                    is WireGuardBean -> {
+                        initPlugin("wireguard-plugin")
+                        pluginConfigs[port] = profile.type to bean.buildWireGuardUapiConf()
+                    }
                     is ConfigBean -> {
                         when (bean.type) {
                             "trojan-go" -> {
@@ -386,6 +389,30 @@ abstract class V2RayInstance(
                             "client"
                         )
 
+                        processes.start(commands)
+                    }
+                    bean is WireGuardBean -> {
+                        val configFile = File(
+                            context.noBackupFilesDir,
+                            "wg_" + SystemClock.elapsedRealtime() + ".conf"
+                        )
+
+                        configFile.parentFile?.mkdirs()
+                        configFile.writeText(config)
+                        cacheFiles.add(configFile)
+
+                        val commands = mutableListOf(
+                            initPlugin("wireguard-plugin").path,
+                            "-a",
+                            bean.localAddress,
+                            "-b",
+                            "127.0.0.1:$port",
+                            "-c",
+                            configFile.absolutePath,
+                            "-d",
+                            "127.0.0.1:${DataStore.localDNSPort}"
+                        )
+
                         processes.start(commands)
                     }
                 }

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt

@@ -252,6 +252,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
 
     var serverProtocolVersion by profileCacheStore.stringToInt(Key.SERVER_PROTOCOL)
     var serverPrivateKey by profileCacheStore.string(Key.SERVER_PRIVATE_KEY)
+    var serverLocalAddress by profileCacheStore.string(Key.SERVER_LOCAL_ADDRESS)
 
     var balancerType by profileCacheStore.stringToInt(Key.BALANCER_TYPE)
     var balancerGroup by profileCacheStore.stringToLong(Key.BALANCER_GROUP)

+ 15 - 0
app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt

@@ -68,6 +68,7 @@ import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean
 import io.nekohasekai.sagernet.fmt.v2ray.VLESSBean
 import io.nekohasekai.sagernet.fmt.v2ray.VMessBean
 import io.nekohasekai.sagernet.fmt.v2ray.toUri
+import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
 import io.nekohasekai.sagernet.ktx.app
 import io.nekohasekai.sagernet.ktx.applyDefaultValues
 import io.nekohasekai.sagernet.ktx.ssSecureList
@@ -102,6 +103,7 @@ data class ProxyEntity(
     var hysteriaBean: HysteriaBean? = null,
     var snellBean: SnellBean? = null,
     var sshBean: SSHBean? = null,
+    var wgBean: WireGuardBean? = null,
     var configBean: ConfigBean? = null,
     var chainBean: ChainBean? = null,
     var balancerBean: BalancerBean? = null
@@ -123,6 +125,7 @@ data class ProxyEntity(
         const val TYPE_HYSTERIA = 15
         const val TYPE_SNELL = 16
         const val TYPE_SSH = 17
+        const val TYPE_WG = 18
 
         const val TYPE_CHAIN = 8
         const val TYPE_BALANCER = 14
@@ -185,6 +188,8 @@ data class ProxyEntity(
             TYPE_HYSTERIA -> hysteriaBean = KryoConverters.hysteriaDeserialize(byteArray)
             TYPE_SNELL -> snellBean = KryoConverters.snellDeserialize(byteArray)
             TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray)
+            TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray)
+
             TYPE_CONFIG -> configBean = KryoConverters.configDeserialize(byteArray)
             TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray)
             TYPE_BALANCER -> balancerBean = KryoConverters.balancerBeanDeserialize(byteArray)
@@ -220,6 +225,7 @@ data class ProxyEntity(
         TYPE_HYSTERIA -> "Hysteria"
         TYPE_SNELL -> "Snell"
         TYPE_SSH -> "SSH"
+        TYPE_WG -> "WireGuard"
         TYPE_CHAIN -> chainName
         TYPE_CONFIG -> configName
         TYPE_BALANCER -> balancerName
@@ -246,6 +252,7 @@ data class ProxyEntity(
             TYPE_HYSTERIA -> hysteriaBean
             TYPE_SNELL -> snellBean
             TYPE_SSH -> sshBean
+            TYPE_WG -> wgBean
 
             TYPE_CONFIG -> configBean
             TYPE_CHAIN -> chainBean
@@ -271,6 +278,7 @@ data class ProxyEntity(
             is HysteriaBean -> false
             is SnellBean -> false
             is SSHBean -> false
+            is WireGuardBean -> false
             else -> true
         }
     }
@@ -293,6 +301,7 @@ data class ProxyEntity(
             is HysteriaBean -> toUniversalLink()
             is SnellBean -> toUniversalLink()
             is SSHBean -> toUniversalLink()
+            is WireGuardBean -> toUniversalLink()
             else -> null
         }
     }
@@ -494,6 +503,7 @@ data class ProxyEntity(
         hysteriaBean = null
         snellBean = null
         sshBean = null
+        wgBean = null
 
         configBean = null
         chainBean = null
@@ -560,6 +570,10 @@ data class ProxyEntity(
                 type = TYPE_SSH
                 sshBean = bean
             }
+            is WireGuardBean -> {
+                type = TYPE_WG
+                wgBean = bean
+            }
             is ConfigBean -> {
                 type = TYPE_CONFIG
                 configBean = bean
@@ -595,6 +609,7 @@ data class ProxyEntity(
                 TYPE_HYSTERIA -> HysteriaSettingsActivity::class.java
                 TYPE_SNELL -> SnellSettingsActivity::class.java
                 TYPE_SSH -> SSHSettingsActivity::class.java
+                TYPE_WG -> WireGuardSettingsActivity::class.java
 
                 TYPE_CONFIG -> ConfigSettingsActivity::class.java
                 TYPE_CHAIN -> ChainSettingsActivity::class.java

+ 1 - 1
app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt

@@ -36,7 +36,7 @@ import kotlinx.coroutines.launch
 
 @Database(
     entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class, StatsEntity::class, KeyValuePair::class],
-    version = 9
+    version = 10
 )
 @TypeConverters(value = [KryoConverters::class, GsonConverters::class])
 @GenerateRoomMigrations

+ 3 - 0
app/src/main/java/io/nekohasekai/sagernet/database/preference/KeyValuePair.kt

@@ -45,6 +45,9 @@ class KeyValuePair() {
         @Query("SELECT * FROM `KeyValuePair`")
         fun all(): List<KeyValuePair>
 
+        @Query("DELETE FROM `KeyValuePair`")
+        fun deleteAll(): Int
+
         @Query("SELECT * FROM `KeyValuePair` WHERE `key` = :key")
         operator fun get(key: String): KeyValuePair?
 

+ 7 - 0
app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java

@@ -49,6 +49,7 @@ import io.nekohasekai.sagernet.fmt.trojan.TrojanBean;
 import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean;
 import io.nekohasekai.sagernet.fmt.v2ray.VLESSBean;
 import io.nekohasekai.sagernet.fmt.v2ray.VMessBean;
+import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean;
 import io.nekohasekai.sagernet.ktx.KryosKt;
 import io.nekohasekai.sagernet.ktx.Logs;
 
@@ -187,6 +188,12 @@ public class KryoConverters {
         return deserialize(new SSHBean(), bytes);
     }
 
+    @TypeConverter
+    public static WireGuardBean wireguardDeserialize(byte[] bytes) {
+        if (ArrayUtil.isEmpty(bytes)) return null;
+        return deserialize(new WireGuardBean(), bytes);
+    }
+
     @TypeConverter
     public static ConfigBean configDeserialize(byte[] bytes) {
         if (ArrayUtil.isEmpty(bytes)) return null;

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt

@@ -35,6 +35,7 @@ enum class PluginEntry(
     RelayBaton("relaybaton-plugin", R.string.action_relay_baton, "io.nekohasekai.sagernet.plugin.relaybaton"),
     Brook("brook-plugin", R.string.action_brook, "io.nekohasekai.sagernet.plugin.brook"),
     Hysteria("hysteria-plugin", R.string.action_hysteria, "io.nekohasekai.sagernet.plugin.hysteria", DownloadSource(fdroid = false)),
+    WireGuard("wireguard-plugin", R.string.action_wireguard, "io.nekohasekai.sagernet.plugin.wireguard", DownloadSource(fdroid = false)),
 
     // shadowsocks plugins
 

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt

@@ -39,6 +39,7 @@ object TypeMap : HashMap<String, Int>() {
         this["hysteria"] = ProxyEntity.TYPE_HYSTERIA
         this["snell"] = ProxyEntity.TYPE_SNELL
         this["ssh"] = ProxyEntity.TYPE_SSH
+        this["wg"] = ProxyEntity.TYPE_WG
     }
 
     val reversed = HashMap<Int, String>()

+ 88 - 0
app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardBean.java

@@ -0,0 +1,88 @@
+/******************************************************************************
+ * Copyright (C) 2021 by nekohasekai <[email protected]>                  *
+ *                                                                            *
+ * This program is free software: you can redistribute it and/or modify       *
+ * it under the terms of the GNU General Public License as published by       *
+ * the Free Software Foundation, either version 3 of the License, or          *
+ *  (at your option) any later version.                                       *
+ *                                                                            *
+ * This program is distributed in the hope that it will be useful,            *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
+ * GNU General Public License for more details.                               *
+ *                                                                            *
+ * You should have received a copy of the GNU General Public License          *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
+ *                                                                            *
+ ******************************************************************************/
+
+package io.nekohasekai.sagernet.fmt.wireguard;
+
+import androidx.annotation.NonNull;
+
+import com.esotericsoftware.kryo.io.ByteBufferInput;
+import com.esotericsoftware.kryo.io.ByteBufferOutput;
+import com.wireguard.crypto.Key;
+import com.wireguard.crypto.KeyPair;
+
+import org.jetbrains.annotations.NotNull;
+
+import io.nekohasekai.sagernet.fmt.AbstractBean;
+import io.nekohasekai.sagernet.fmt.KryoConverters;
+import io.nekohasekai.sagernet.fmt.ssh.SSHBean;
+
+public class WireGuardBean extends AbstractBean {
+
+    public String localAddress;
+    public String privateKey;
+    public String peerPublicKey;
+    public String peerPreSharedKey;
+
+    @Override
+    public void initializeDefaultValues() {
+        super.initializeDefaultValues();
+        if (localAddress == null) localAddress = "";
+        if (privateKey == null) privateKey = "";
+        if (peerPublicKey == null) peerPublicKey = "";
+        if (peerPreSharedKey == null) peerPreSharedKey = "";
+    }
+
+    @Override
+    public void serialize(ByteBufferOutput output) {
+        output.writeInt(0);
+        super.serialize(output);
+        output.writeString(localAddress);
+        output.writeString(privateKey);
+        output.writeString(peerPublicKey);
+        output.writeString(peerPreSharedKey);
+    }
+
+    @Override
+    public void deserialize(ByteBufferInput input) {
+        int version = input.readInt();
+        super.deserialize(input);
+        localAddress = input.readString();
+        privateKey = input.readString();
+        peerPublicKey = input.readString();
+        peerPreSharedKey = input.readString();
+    }
+
+    @NotNull
+    @Override
+    public WireGuardBean clone() {
+        return KryoConverters.deserialize(new WireGuardBean(), KryoConverters.serialize(this));
+    }
+
+    public static final Creator<WireGuardBean> CREATOR = new CREATOR<WireGuardBean>() {
+        @NonNull
+        @Override
+        public WireGuardBean newInstance() {
+            return new WireGuardBean();
+        }
+
+        @Override
+        public WireGuardBean[] newArray(int size) {
+            return new WireGuardBean[size];
+        }
+    };
+}

+ 40 - 0
app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt

@@ -0,0 +1,40 @@
+/******************************************************************************
+ * Copyright (C) 2021 by nekohasekai <[email protected]>                  *
+ *                                                                            *
+ * This program is free software: you can redistribute it and/or modify       *
+ * it under the terms of the GNU General Public License as published by       *
+ * the Free Software Foundation, either version 3 of the License, or          *
+ *  (at your option) any later version.                                       *
+ *                                                                            *
+ * This program is distributed in the hope that it will be useful,            *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
+ * GNU General Public License for more details.                               *
+ *                                                                            *
+ * You should have received a copy of the GNU General Public License          *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
+ *                                                                            *
+ ******************************************************************************/
+
+
+package io.nekohasekai.sagernet.fmt.wireguard
+
+import cn.hutool.core.codec.Base64
+import cn.hutool.core.util.HexUtil
+import com.wireguard.crypto.Key
+import io.nekohasekai.sagernet.ktx.wrapUri
+
+fun WireGuardBean.buildWireGuardUapiConf(): String {
+
+    var conf = "private_key="
+    conf += Key.fromBase64(privateKey).toHex()
+    conf += "\npublic_key="
+    conf += Key.fromBase64(peerPublicKey).toHex()
+    if (peerPreSharedKey.isNotBlank()) {
+        conf += "\npreshared_key="
+        conf += HexUtil.encodeHexStr(Base64.decode(peerPreSharedKey))
+    }
+    conf += "\nendpoint=${wrapUri()}"
+    conf += "\nallowed_ip=0.0.0.0/0"
+    return conf
+}

+ 38 - 1
app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt

@@ -40,13 +40,16 @@ import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo
 import io.nekohasekai.sagernet.fmt.v2ray.V2RayConfig
 import io.nekohasekai.sagernet.fmt.v2ray.VLESSBean
 import io.nekohasekai.sagernet.fmt.v2ray.VMessBean
+import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
 import io.nekohasekai.sagernet.ktx.*
 import okhttp3.HttpUrl.Companion.toHttpUrl
 import okhttp3.OkHttpClient
 import okhttp3.Request
+import org.ini4j.Ini
 import org.yaml.snakeyaml.TypeDescription
 import org.yaml.snakeyaml.Yaml
 import org.yaml.snakeyaml.error.YAMLException
+import java.io.StringReader
 
 @Suppress("EXPERIMENTAL_API_USAGE")
 object RawUpdater : GroupUpdater() {
@@ -223,7 +226,7 @@ object RawUpdater : GroupUpdater() {
     @Suppress("UNCHECKED_CAST")
     fun parseRaw(text: String): List<AbstractBean>? {
 
-        val proxies = ArrayList<AbstractBean>()
+        val proxies = mutableListOf<AbstractBean>()
 
         if (text.contains("proxies:")) {
 
@@ -357,6 +360,14 @@ object RawUpdater : GroupUpdater() {
             } catch (e: YAMLException) {
                 Logs.w(e)
             }
+        } else if (text.contains("[Interface]")) {
+            // wireguard
+            try {
+                proxies.addAll(parseWireGuard(text))
+                return proxies
+            } catch (e: Exception) {
+                Logs.w(e)
+            }
         }
 
         try {
@@ -382,6 +393,32 @@ object RawUpdater : GroupUpdater() {
         return null
     }
 
+    fun parseWireGuard(conf: String): List<WireGuardBean> {
+        val ini = Ini(StringReader(conf))
+        val iface = ini["Interface"] ?: error("Missing 'Interface' selection")
+        val bean = WireGuardBean().applyDefaultValues()
+        bean.localAddress = iface["Address"]?.substringBefore("/")
+        bean.privateKey = iface["PrivateKey"]
+        val peers = ini.getAll("Peer")
+        if (peers.isNullOrEmpty()) error("Missing 'Peer' selections")
+        val beans = mutableListOf<WireGuardBean>()
+        for (peer in peers) {
+            val endpoint = peer["Endpoint"]
+            if (endpoint.isNullOrBlank() || !endpoint.contains(":")) {
+                continue
+            }
+
+            val peerBean = bean.clone()
+            peerBean.serverAddress = endpoint.substringBefore(":")
+            peerBean.serverPort = endpoint.substringAfter(":").toIntOrNull() ?: continue
+            peerBean.peerPublicKey = peer["PublicKey"] ?: continue
+            peerBean.peerPreSharedKey = peer["PresharedKey"]
+            beans.add(peerBean)
+        }
+        if (beans.isEmpty()) error("Empty available peer list")
+        return beans
+    }
+
     fun parseJSON(json: JSON): List<AbstractBean> {
         val proxies = ArrayList<AbstractBean>()
 

+ 37 - 6
app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt

@@ -25,6 +25,7 @@ import android.graphics.Color
 import android.net.Uri
 import android.os.Bundle
 import android.os.SystemClock
+import android.provider.OpenableColumns
 import android.text.format.Formatter
 import android.text.method.LinkMovementMethod
 import android.text.util.Linkify
@@ -70,11 +71,13 @@ import io.nekohasekai.sagernet.widget.QRCodeDialog
 import io.nekohasekai.sagernet.widget.UndoSnackbarManager
 import kotlinx.coroutines.*
 import libcore.Libcore
+import okhttp3.internal.closeQuietly
 import java.net.InetAddress
 import java.net.InetSocketAddress
 import java.net.Socket
 import java.net.UnknownHostException
 import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.zip.ZipInputStream
 
 class ConfigurationFragment @JvmOverloads constructor(
     val select: Boolean = false,
@@ -172,11 +175,34 @@ class ConfigurationFragment @JvmOverloads constructor(
     val importFile = registerForActivityResult(ActivityResultContracts.GetContent()) {
         if (it != null) runOnDefaultDispatcher {
             try {
-                val fileText = requireContext().contentResolver.openInputStream(it)!!
-                    .bufferedReader()
-                    .readText()
-                val proxies = RawUpdater.parseRaw(fileText)
-                if (proxies.isNullOrEmpty()) onMainDispatcher {
+                val fileName = requireContext().contentResolver.query(it, null, null, null, null)
+                    ?.use { cursor ->
+                        cursor.moveToFirst()
+                        cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
+                            .let(cursor::getString)
+                    }
+
+                val proxies = mutableListOf<AbstractBean>()
+                if (fileName != null && fileName.endsWith(".zip")) {
+                    // try parse wireguard zip
+
+                    val zip = ZipInputStream(requireContext().contentResolver.openInputStream(it)!!)
+                    while (true) {
+                        val entry = zip.nextEntry ?: break
+                        if (entry.isDirectory) continue
+                        val fileText = zip.bufferedReader().readText()
+                        RawUpdater.parseRaw(fileText)?.let { pl -> proxies.addAll(pl) }
+                        zip.closeEntry()
+                    }
+                    zip.closeQuietly()
+                } else {
+                    val fileText = requireContext().contentResolver.openInputStream(it)!!
+                        .bufferedReader()
+                        .readText()
+                    RawUpdater.parseRaw(fileText)?.let { pl -> proxies.addAll(pl) }
+                }
+
+                if (proxies.isEmpty()) onMainDispatcher {
                     snackbar(getString(R.string.no_proxies_found_in_file)).show()
                 } else import(proxies)
             } catch (e: SubscriptionFoundException) {
@@ -291,6 +317,9 @@ class ConfigurationFragment @JvmOverloads constructor(
             R.id.action_new_ssh -> {
                 startActivity(Intent(requireActivity(), SSHSettingsActivity::class.java))
             }
+            R.id.action_new_wg -> {
+                startActivity(Intent(requireActivity(), WireGuardSettingsActivity::class.java))
+            }
             R.id.action_new_config -> {
                 startActivity(Intent(requireActivity(), ConfigSettingsActivity::class.java))
             }
@@ -758,7 +787,9 @@ class ConfigurationFragment @JvmOverloads constructor(
                     groupList = ArrayList(SagerDatabase.groupDao.allGroups())
                 }
 
-                val hideUngrouped = groupList.size > 1 && SagerDatabase.proxyDao.countByGroup(groupList.find { it.ungrouped }!!.id) == 0L
+                val hideUngrouped = groupList.size > 1 && SagerDatabase.proxyDao.countByGroup(
+                    groupList.find { it.ungrouped }!!.id
+                ) == 0L
 
                 if (hideUngrouped) groupList.removeAll { it.ungrouped }
 

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt

@@ -114,6 +114,7 @@ class LogcatFragment : ToolbarFragment(R.layout.layout_logcat),
                 "libhysteria:D",
                 "libpingtunnel:D",
                 "librelaybaton:D",
+                "libwg:D",
 
                 "*:S"
             ), arrayOf(), 3000, this

+ 76 - 0
app/src/main/java/io/nekohasekai/sagernet/ui/profile/WireGuardSettingsActivity.kt

@@ -0,0 +1,76 @@
+/******************************************************************************
+ *                                                                            *
+ * Copyright (C) 2021 by nekohasekai <[email protected]>             *
+ *                                                                            *
+ * This program is free software: you can redistribute it and/or modify       *
+ * it under the terms of the GNU General Public License as published by       *
+ * the Free Software Foundation, either version 3 of the License, or          *
+ *  (at your option) any later version.                                       *
+ *                                                                            *
+ * This program is distributed in the hope that it will be useful,            *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
+ * GNU General Public License for more details.                               *
+ *                                                                            *
+ * You should have received a copy of the GNU General Public License          *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
+ *                                                                            *
+ ******************************************************************************/
+
+package io.nekohasekai.sagernet.ui.profile
+
+import android.os.Bundle
+import androidx.preference.EditTextPreference
+import com.takisoft.preferencex.PreferenceFragmentCompat
+import com.takisoft.preferencex.SimpleMenuPreference
+import io.nekohasekai.sagernet.Key
+import io.nekohasekai.sagernet.R
+import io.nekohasekai.sagernet.database.DataStore
+import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers
+import io.nekohasekai.sagernet.fmt.ssh.SSHBean
+import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
+
+class WireGuardSettingsActivity : ProfileSettingsActivity<WireGuardBean>() {
+
+    override fun createEntity() = WireGuardBean()
+
+    override fun WireGuardBean.init() {
+        DataStore.profileName = name
+
+        DataStore.serverLocalAddress = localAddress
+        DataStore.serverPrivateKey = privateKey
+
+        DataStore.serverAddress = serverAddress
+        DataStore.serverPort = serverPort
+
+        DataStore.serverCertificates = peerPublicKey
+        DataStore.serverPassword = peerPreSharedKey
+    }
+
+    override fun WireGuardBean.serialize() {
+        name = DataStore.profileName
+
+        localAddress = DataStore.serverLocalAddress
+        privateKey = DataStore.serverPrivateKey
+
+        serverAddress = DataStore.serverAddress
+        serverPort = DataStore.serverPort
+
+        peerPublicKey = DataStore.serverCertificates
+        peerPreSharedKey = DataStore.serverPassword
+    }
+
+    override fun PreferenceFragmentCompat.createPreferences(
+        savedInstanceState: Bundle?,
+        rootKey: String?,
+    ) {
+        addPreferencesFromResource(R.xml.wireguard_preferences)
+        findPreference<EditTextPreference>(Key.SERVER_PORT)!!.apply {
+            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
+        }
+        findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {
+            summaryProvider = PasswordSummaryProvider
+        }
+    }
+
+}

+ 3 - 0
app/src/main/res/menu/add_profile_menu.xml

@@ -63,6 +63,9 @@
                     <item
                         android:id="@+id/action_new_ssh"
                         android:title="@string/action_ssh" />
+                    <item
+                        android:id="@+id/action_new_wg"
+                        android:title="@string/action_wireguard" />
                     <item
                         android:id="@+id/action_new_config"
                         android:title="@string/custom_config" />

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -217,6 +217,7 @@
     <string name="action_hysteria" translatable="false">Hysteria</string>
     <string name="action_snell" translatable="false">Snell</string>
     <string name="action_ssh" translatable="false">SSH</string>
+    <string name="action_wireguard" translatable="false">WireGuard</string>
     <string name="proxy_chain">Proxy Chain</string>
     <string name="custom_config">Custom Config</string>
     <string name="balancer">Balancer</string>
@@ -453,5 +454,8 @@
     <string name="menu_tools">Tools</string>
     <string name="menu_log">Logs</string>
     <string name="clear_logcat">Clear Logcat</string>
+    <string name="wireguard_local_address">Local Address</string>
+    <string name="wireguard_public_key">Peer Public Key</string>
+    <string name="wireguard_psk">Peer Pre-Shared Key</string>
 
 </resources>

+ 43 - 0
app/src/main/res/xml/wireguard_preferences.xml

@@ -0,0 +1,43 @@
+<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <EditTextPreference
+        app:icon="@drawable/ic_social_emoji_symbols"
+        app:key="profileName"
+        app:title="@string/profile_name"
+        app:useSimpleSummaryProvider="true" />
+
+    <PreferenceCategory app:title="@string/proxy_cat">
+        <EditTextPreference
+            app:icon="@drawable/ic_baseline_domain_24"
+            app:key="serverLocalAddress"
+            app:title="@string/wireguard_local_address"
+            app:useSimpleSummaryProvider="true" />
+        <EditTextPreference
+            app:icon="@drawable/ic_baseline_vpn_key_24"
+            app:key="serverPrivateKey"
+            app:title="@string/ssh_private_key"
+            app:useSimpleSummaryProvider="true" />
+        <EditTextPreference
+            app:icon="@drawable/ic_hardware_router"
+            app:key="serverAddress"
+            app:title="@string/server_address"
+            app:useSimpleSummaryProvider="true" />
+        <EditTextPreference
+            app:icon="@drawable/ic_maps_directions_boat"
+            app:key="serverPort"
+            app:title="@string/server_port"
+            app:useSimpleSummaryProvider="true" />
+
+        <EditTextPreference
+            app:icon="@drawable/ic_action_copyright"
+            app:key="serverCertificates"
+            app:title="@string/wireguard_public_key"
+            app:useSimpleSummaryProvider="true" />
+        <EditTextPreference
+            app:dialogLayout="@layout/layout_password_dialog"
+            app:icon="@drawable/ic_settings_password"
+            app:key="serverPassword"
+            app:title="@string/wireguard_psk" />
+    </PreferenceCategory>
+
+</PreferenceScreen>

+ 8 - 0
bin/plugin/wireguard.sh

@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+bin/plugin/wireguard/init.sh &&
+  bin/plugin/wireguard/armeabi-v7a.sh &&
+  bin/plugin/wireguard/arm64-v8a.sh &&
+  bin/plugin/wireguard/x86.sh &&
+  bin/plugin/wireguard/x86_64.sh &&
+  bin/plugin/wireguard/end.sh

+ 9 - 0
bin/plugin/wireguard/arm64-v8a.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+source "bin/init/env.sh"
+source "bin/plugin/wireguard/build.sh"
+
+DIR="$ROOT/arm64-v8a"
+mkdir -p $DIR
+env CC=$ANDROID_ARM64_CC GOARCH=arm64 go build -x -o $DIR/$LIB_OUTPUT -trimpath -ldflags="-s -w -buildid="
+$ANDROID_ARM64_STRIP $DIR/$LIB_OUTPUT

+ 9 - 0
bin/plugin/wireguard/armeabi-v7a.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+source "bin/init/env.sh"
+source "bin/plugin/wireguard/build.sh"
+
+DIR="$ROOT/armeabi-v7a"
+mkdir -p $DIR
+env CC=$ANDROID_ARM_CC GOARCH=arm GOARM=7 go build -x -o $DIR/$LIB_OUTPUT -trimpath -ldflags="-s -w -buildid="
+$ANDROID_ARM_STRIP $DIR/$LIB_OUTPUT

+ 15 - 0
bin/plugin/wireguard/build.sh

@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+source "bin/init/env.sh"
+
+export CGO_ENABLED=1
+export GOOS=android
+
+CURR="plugin/wireguard"
+CURR_PATH="$PROJECT/$CURR"
+
+ROOT="$CURR_PATH/src/main/jniLibs"
+OUTPUT="wg"
+LIB_OUTPUT="lib$OUTPUT.so"
+
+cd $CURR_PATH/src/main/go/wgsocks

+ 5 - 0
bin/plugin/wireguard/end.sh

@@ -0,0 +1,5 @@
+source "bin/init/env.sh"
+source "bin/plugin/wireguard/build.sh"
+
+git reset HEAD --hard
+git clean -fdx

+ 13 - 0
bin/plugin/wireguard/init.sh

@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+source "bin/init/env.sh"
+
+export CGO_ENABLED=1
+export GOOS=android
+
+CURR="plugin/wireguard"
+CURR_PATH="$PROJECT/$CURR"
+
+git submodule update --init "$CURR/*"
+cd $CURR_PATH/src/main/go/wgsocks
+go mod download -x

+ 9 - 0
bin/plugin/wireguard/x86.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+source "bin/init/env.sh"
+source "bin/plugin/wireguard/build.sh"
+
+DIR="$ROOT/x86"
+mkdir -p $DIR
+env CC=$ANDROID_X86_CC GOARCH=386 go build -x -o $DIR/$LIB_OUTPUT -trimpath -ldflags="-s -w -buildid="
+$ANDROID_X86_STRIP $DIR/$LIB_OUTPUT

+ 9 - 0
bin/plugin/wireguard/x86_64.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+source "bin/init/env.sh"
+source "bin/plugin/wireguard/build.sh"
+
+DIR="$ROOT/x86_64"
+mkdir -p $DIR
+env CC=$ANDROID_X86_64_CC GOARCH=amd64 go build -x -o $DIR/$LIB_OUTPUT -trimpath -ldflags="-s -w -buildid="
+$ANDROID_X86_64_STRIP $DIR/$LIB_OUTPUT

+ 5 - 0
plugin/wireguard/build.gradle.kts

@@ -0,0 +1,5 @@
+plugins {
+    id("com.android.application")
+}
+
+setupPlugin("wireguard")

+ 39 - 0
plugin/wireguard/src/main/AndroidManifest.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="io.nekohasekai.sagernet.plugin.wireguard"
+    android:installLocation="internalOnly">
+
+    <application
+        android:allowBackup="false"
+        android:extractNativeLibs="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="WireGuard Plugin"
+        android:roundIcon="@mipmap/ic_launcher_round">
+        <provider
+            android:name=".BinaryProvider"
+            android:authorities="io.nekohasekai.sagernet.plugin.wireguard.BinaryProvider"
+            android:directBootAware="true"
+            android:exported="true"
+            tools:ignore="ExportedContentProvider">
+            <intent-filter>
+                <action android:name="io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN" />
+                <data
+                    android:host="io.nekohasekai.sagernet"
+                    android:path="/wireguard-plugin"
+                    android:scheme="plugin" />
+            </intent-filter>
+
+            <meta-data
+                android:name="io.nekohasekai.sagernet.plugin.id"
+                android:value="wireguard-plugin" />
+            <meta-data
+                android:name="io.nekohasekai.sagernet.plugin.executable_path"
+                android:value="libwg.so" />
+        </provider>
+    </application>
+
+</manifest>

+ 1 - 0
plugin/wireguard/src/main/go/wgsocks

@@ -0,0 +1 @@
+Subproject commit 9f00cd91d3f26c372a3bb2a236e7bed792e30780

BIN
plugin/wireguard/src/main/ic_launcher-playstore.png


+ 40 - 0
plugin/wireguard/src/main/java/io/nekohasekai/sagernet/plugin/wireguard/BinaryProvider.kt

@@ -0,0 +1,40 @@
+/******************************************************************************
+ *                                                                            *
+ * Copyright (C) 2021 by nekohasekai <[email protected]>             *
+ *                                                                            *
+ * This program is free software: you can redistribute it and/or modify       *
+ * it under the terms of the GNU General Public License as published by       *
+ * the Free Software Foundation, either version 3 of the License, or          *
+ *  (at your option) any later version.                                       *
+ *                                                                            *
+ * This program is distributed in the hope that it will be useful,            *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
+ * GNU General Public License for more details.                               *
+ *                                                                            *
+ * You should have received a copy of the GNU General Public License          *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
+ *                                                                            *
+ ******************************************************************************/
+
+package io.nekohasekai.sagernet.plugin.wireguard
+
+import android.net.Uri
+import android.os.ParcelFileDescriptor
+import io.nekohasekai.sagernet.plugin.NativePluginProvider
+import io.nekohasekai.sagernet.plugin.PathProvider
+import java.io.File
+import java.io.FileNotFoundException
+
+class BinaryProvider : NativePluginProvider() {
+    override fun populateFiles(provider: PathProvider) {
+        provider.addPath("wireguard-plugin", 0b111101101)
+    }
+
+    override fun getExecutable() = context!!.applicationInfo.nativeLibraryDir + "/libwg.so"
+    override fun openFile(uri: Uri): ParcelFileDescriptor = when (uri.path) {
+        "/wireguard-plugin" -> ParcelFileDescriptor.open(File(getExecutable()),
+            ParcelFileDescriptor.MODE_READ_ONLY)
+        else -> throw FileNotFoundException()
+    }
+}

+ 34 - 0
plugin/wireguard/src/main/res/drawable/ic_launcher_foreground.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <group android:scaleX="0.11997853"
+      android:scaleY="0.11997853"
+      android:translateX="28.682308"
+      android:translateY="31.433317">
+    <group android:translateY="146.88005">
+      <path android:pathData="M15.703125,-0L3.59375,-105.5L15.984375,-105.5L23.90625,-27.453125Q24.34375,-22.671875,24.703125,-17.546875Q25.0625,-12.421875,25.203125,-9.390625Q25.484375,-12.421875,25.984375,-17.546875Q26.5,-22.671875,27.078125,-27.453125L36.859375,-105.5L50.25,-105.5L59.1875,-27.390625Q59.765625,-22.609375,60.328125,-17.46875Q60.90625,-12.34375,61.203125,-9.3125Q61.484375,-12.34375,61.84375,-17.46875Q62.203125,-22.609375,62.78125,-27.390625L70.984375,-105.5L82.796875,-105.5L70.421875,-0L54.140625,-0L45.21875,-79.484375Q44.640625,-84.40625,44.203125,-89.09375Q43.78125,-93.796875,43.484375,-96.390625Q43.203125,-93.796875,42.6875,-89.09375Q42.1875,-84.40625,41.609375,-79.484375L31.96875,-0L15.703125,-0Z"
+          android:fillColor="#FFFFFF"/>
+      <path android:pathData="M98.234375,0L98.234375,-11.859375L126.3125,-11.859375L126.3125,-68L101.84375,-68L101.84375,-79.859375L139.28125,-79.859375L139.28125,-11.859375L165.92188,-11.859375L165.92188,0L98.234375,0ZM131.35938,-94Q126.609375,-94,123.875,-96.453125Q121.140625,-98.921875,121.140625,-103.109375Q121.140625,-107.453125,123.875,-109.984375Q126.609375,-112.515625,131.35938,-112.515625Q136.10938,-112.515625,138.84375,-109.984375Q141.57812,-107.453125,141.57812,-103.109375Q141.57812,-98.921875,138.84375,-96.453125Q136.10938,-94,131.35938,-94Z"
+          android:fillColor="#FFFFFF"/>
+      <path android:pathData="M187.98438,0L187.98438,-79.546875L200.65625,-79.546875L200.65625,-65.375Q204.54688,-81,221.53125,-81Q234.0625,-81,241.46875,-73.109375Q248.89062,-65.234375,248.89062,-51.78125L248.89062,-47.015625L235.9375,-47.015625L235.9375,-50.625Q235.9375,-60.03125,231.39062,-65.015625Q226.85938,-70,218.51562,-70Q210.29688,-70,205.60938,-64.9375Q200.9375,-59.875,200.9375,-50.625L200.9375,0L187.98438,0Z"
+          android:fillColor="#FFFFFF"/>
+      <path android:pathData="M301.20312,1Q291.84375,1,284.85938,-2.78125Q277.875,-6.5625,274.04688,-13.453125Q270.23438,-20.359375,270.23438,-29.515625L270.23438,-51.328125Q270.23438,-60.625,274.04688,-67.453125Q277.875,-74.296875,284.85938,-78.0625Q291.84375,-81.84375,301.20312,-81.84375Q310.5625,-81.84375,317.54688,-78.046875Q324.53125,-74.25,328.34375,-67.375Q332.15625,-60.515625,332.15625,-51.171875L332.15625,-37L282.90625,-37L282.90625,-29.34375Q282.90625,-19.96875,287.65625,-14.984375Q292.42188,-10,301.20312,-10Q308.6875,-10,313.29688,-12.65625Q317.90625,-15.328125,318.90625,-20.6875L331.875,-20.6875Q330.57812,-10.703125,322.15625,-4.84375Q313.73438,1,301.20312,1ZM282.90625,-46.84375L319.48438,-46.84375L319.48438,-51.109375Q319.48438,-60.6875,314.79688,-65.84375Q310.125,-71,301.20312,-71Q292.42188,-71,287.65625,-65.84375Q282.90625,-60.6875,282.90625,-51.109375L282.90625,-46.84375Z"
+          android:fillColor="#FFFFFF"/>
+    </group>
+    <group android:translateY="336.96014">
+      <path android:pathData="M43.78125,1Q29.375,1,21.015625,-7.109375Q12.671875,-15.234375,12.671875,-29.4375L12.671875,-77.28125Q12.671875,-91.484375,21.015625,-99.59375Q29.375,-107.71875,43.78125,-107.71875Q57.890625,-107.71875,66.234375,-99.484375Q74.59375,-91.25,74.59375,-77.125L61.625,-77.125Q61.625,-86.203125,56.9375,-91.09375Q52.265625,-96,43.78125,-96Q35.28125,-96,30.453125,-91.203125Q25.625,-86.421875,25.625,-77.453125L25.625,-29.421875Q25.625,-20.4375,30.453125,-15.5Q35.28125,-10.578125,43.78125,-10.578125Q52.265625,-10.578125,56.9375,-15.453125Q61.625,-20.328125,61.625,-29.21875L61.625,-42.140625L40.3125,-42.140625L40.3125,-54L74.59375,-54L74.59375,-29.234375Q74.59375,-15.265625,66.234375,-7.125Q57.890625,1,43.78125,1Z"
+          android:fillColor="#FFFFFF"/>
+      <path android:pathData="M129.0625,1Q115.375,1,107.15625,-7.171875Q98.953125,-15.34375,98.953125,-29.375L98.953125,-80L111.921875,-80L111.921875,-29.375Q111.921875,-20.40625,116.53125,-15.421875Q121.140625,-10.4375,129.0625,-10.4375Q137.125,-10.4375,141.79688,-15.421875Q146.48438,-20.40625,146.48438,-29.375L146.48438,-80L159.4375,-80L159.4375,-29.375Q159.4375,-15.34375,151.07812,-7.171875Q142.73438,1,129.0625,1Z"
+          android:fillColor="#FFFFFF"/>
+      <path android:pathData="M208.28125,1Q195.76562,1,188.5625,-5.421875Q181.35938,-11.859375,181.35938,-22.859375Q181.35938,-33.984375,188.5625,-40.328125Q195.76562,-46.6875,208,-46.6875L232.48438,-46.6875L232.48438,-54.640625Q232.48438,-61.890625,228.09375,-65.9375Q223.70312,-70,215.625,-70Q208.57812,-70,203.96875,-67.03125Q199.35938,-64.0625,198.35938,-59L185.39062,-59Q186.6875,-69.265625,195.03125,-75.34375Q203.39062,-81.421875,215.92188,-81.421875Q229.59375,-81.421875,237.51562,-74.328125Q245.4375,-67.25,245.4375,-55.109375L245.4375,-0.453125L232.76562,-0.453125L232.76562,-15.640625L232.625,-15.640625Q231.76562,-7.96875,225.20312,-3.484375Q218.65625,1,208.28125,1ZM211.45312,-9.28125Q220.67188,-9.28125,226.57812,-13.890625Q232.48438,-18.515625,232.48438,-25.734375L232.48438,-37L208.28125,-37Q201.8125,-37,198.0625,-33.390625Q194.3125,-29.78125,194.3125,-23.5625Q194.3125,-16.921875,198.84375,-13.09375Q203.39062,-9.28125,211.45312,-9.28125Z"
+          android:fillColor="#FFFFFF"/>
+      <path android:pathData="M273.98438,0L273.98438,-79.546875L286.65625,-79.546875L286.65625,-65.375Q290.54688,-81,307.53125,-81Q320.0625,-81,327.46875,-73.109375Q334.89062,-65.234375,334.89062,-51.78125L334.89062,-47.015625L321.9375,-47.015625L321.9375,-50.625Q321.9375,-60.03125,317.39062,-65.015625Q312.85938,-70,304.51562,-70Q296.29688,-70,291.60938,-64.9375Q286.9375,-59.875,286.9375,-50.625L286.9375,0L273.98438,0Z"
+          android:fillColor="#FFFFFF"/>
+      <path android:pathData="M382.59375,1Q370.78125,1,363.5,-6.9375Q356.23438,-14.890625,356.23438,-28.453125L356.23438,-51.6875Q356.23438,-65.40625,363.4375,-73.34375Q370.64062,-81.28125,382.59375,-81.28125Q391.51562,-81.28125,397.28125,-76.796875Q403.04688,-72.328125,404.04688,-64.65625L404.48438,-64.65625L404.1875,-82.796875L404.1875,-106L417.15625,-106L417.15625,-0.40625L404.1875,-0.40625L404.1875,-15.625L404.04688,-15.625Q403.04688,-7.8125,397.28125,-3.40625Q391.51562,1,382.59375,1ZM386.90625,-10.28125Q394.96875,-10.28125,399.57812,-15.328125Q404.1875,-20.375,404.1875,-29.3125L404.1875,-50.96875Q404.1875,-59.90625,399.57812,-64.953125Q394.96875,-70,386.90625,-70Q378.70312,-70,373.95312,-65.09375Q369.20312,-60.1875,369.20312,-50.96875L369.20312,-29.3125Q369.20312,-20.09375,373.95312,-15.1875Q378.70312,-10.28125,386.90625,-10.28125Z"
+          android:fillColor="#FFFFFF"/>
+    </group>
+  </group>
+</vector>

+ 5 - 0
plugin/wireguard/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>

+ 5 - 0
plugin/wireguard/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>

+ 4 - 0
plugin/wireguard/src/main/res/values/ic_launcher_background.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#E91E63</color>
+</resources>

+ 2 - 0
sager.properties

@@ -23,3 +23,5 @@ TROJAN_VERSION=1
 HYSTERIA_VERSION_NAME=0.8.5
 HYSTERIA_VERSION=3
 
+WIREGUARD_VERSION_NAME=20210424
+WIREGUARD_VERSION=1

+ 1 - 0
settings.gradle.kts

@@ -17,6 +17,7 @@ when {
         include(":plugin:trojan")
         include(":plugin:trojan-go")
         include(":plugin:hysteria")
+        include(":plugin:wireguard")
     }
     buildPlugin == "none" -> {
     }