├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── sdk ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro ├── src │ ├── androidTest │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── network │ │ │ └── xyo │ │ │ └── client │ │ │ ├── account │ │ │ ├── AccountTest.kt │ │ │ ├── WalletTest.kt │ │ │ └── WalletTestVectors.kt │ │ │ ├── boundwitness │ │ │ ├── BoundWitnessBuilderTest.kt │ │ │ ├── BoundWitnessTest.kt │ │ │ ├── TestBoundWitnessSequence.kt │ │ │ ├── TestPayload1.kt │ │ │ └── TestPayload2.kt │ │ │ ├── lib │ │ │ └── Constants.kt │ │ │ ├── node │ │ │ └── client │ │ │ │ ├── NodeClientTest.kt │ │ │ │ └── QueryResponseWrapperTest.kt │ │ │ ├── payload │ │ │ ├── EventPayload.kt │ │ │ └── PayloadTest.kt │ │ │ ├── prefs │ │ │ ├── AccountPrefsRepositoryTest.kt │ │ │ └── PreviousHashStorePrefsRepositoryTest.kt │ │ │ ├── settings │ │ │ └── XyoSdkTest.kt │ │ │ └── witness │ │ │ ├── LocationWitnessTest.kt │ │ │ ├── SystemInfoWitnessTest.kt │ │ │ ├── WitnessLocationHandlerTest.kt │ │ │ └── XyoPanelTest.kt │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── network │ │ └── xyo │ │ └── client │ │ ├── account │ │ ├── Account.kt │ │ ├── Wallet.kt │ │ └── model │ │ │ ├── Account.kt │ │ │ ├── AccountStatic.kt │ │ │ ├── PreviousHashStore.kt │ │ │ ├── PrivateKey.kt │ │ │ ├── PublicKey.kt │ │ │ ├── Wallet.kt │ │ │ └── WalletStatic.kt │ │ ├── archivist │ │ └── wrapper │ │ │ ├── ArchivistGetQueryPayload.kt │ │ │ ├── ArchivistInsertQueryPayload.kt │ │ │ └── ArchivistWrapper.kt │ │ ├── boundwitness │ │ ├── BoundWitness.kt │ │ ├── BoundWitnessBuilder.kt │ │ ├── BoundWitnessFields.kt │ │ ├── QueryBoundWitness.kt │ │ ├── QueryBoundWitnessBuilder.kt │ │ ├── QueryBoundWitnessFields.kt │ │ └── model │ │ │ ├── BoundWitnessFields.kt │ │ │ └── BoundWitnessMeta.kt │ │ ├── datastore │ │ ├── accounts │ │ │ ├── AccountPrefsDataStoreSerializer.kt │ │ │ ├── AccountPrefsRepository.kt │ │ │ └── Context.xyoAccountDataStore.kt │ │ └── previous_hash_store │ │ │ ├── MemoryPreviousHashStore.kt │ │ │ ├── PreviousHashDataStore.kt │ │ │ ├── PreviousHashStorePrefsDataStoreSerializer.kt │ │ │ └── PreviousHashStorePrefsRepository.kt │ │ ├── lib │ │ ├── ClientCoroutineScope.kt │ │ ├── JsonSerializable.kt │ │ ├── Secp256k1CurveConstants.kt │ │ ├── hasPermission.kt │ │ ├── hexStringToByteArray.kt │ │ ├── recoverAddress.kt │ │ └── xyoScope.kt │ │ ├── module │ │ └── ModuleQueryResult.kt │ │ ├── node │ │ └── client │ │ │ ├── NodeClient.kt │ │ │ ├── PostQueryResult.kt │ │ │ └── QueryResponse.kt │ │ ├── payload │ │ ├── Payload.kt │ │ └── model │ │ │ ├── Payload.kt │ │ │ └── WithMeta.kt │ │ ├── proto │ │ ├── AccountPrefsDataStore.proto │ │ └── PreviousHashPrefsDataStore.proto │ │ ├── settings │ │ ├── Defaults.kt │ │ ├── SettingsInterface.kt │ │ └── XyoSdk.kt │ │ ├── types │ │ └── TypeAlias.kt │ │ └── witness │ │ ├── XyoPanel.kt │ │ ├── XyoWitness.kt │ │ ├── location │ │ └── info │ │ │ ├── Handler.kt │ │ │ ├── LocationActivity.kt │ │ │ ├── LocationPayload.kt │ │ │ ├── LocationPayloadRaw.kt │ │ │ ├── PermissionCheck.kt │ │ │ ├── XyoLocationCurrent.kt │ │ │ ├── XyoLocationPayloads.kt │ │ │ └── XyoLocationWitness.kt │ │ ├── system │ │ └── info │ │ │ ├── SystemInfoPayload.kt │ │ │ ├── XyoSystemInfoDevice.kt │ │ │ ├── XyoSystemInfoNetwork.kt │ │ │ ├── XyoSystemInfoNetworkCellular.kt │ │ │ ├── XyoSystemInfoNetworkWifi.kt │ │ │ ├── XyoSystemInfoNetworkWired.kt │ │ │ ├── XyoSystemInfoOs.kt │ │ │ └── XyoSystemInfoWitness.kt │ │ └── types │ │ ├── HandlerInterface.kt │ │ └── Result.kt └── version.properties └── settings.gradle /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 7 | 8 | name: Java CI with Gradle 9 | 10 | on: 11 | push: 12 | branches: [ main ] 13 | pull_request: 14 | branches: [ main ] 15 | 16 | jobs: 17 | build: 18 | 19 | strategy: 20 | matrix: 21 | os: [windows-latest, ubuntu-latest, macos-latest] 22 | node: [18] 23 | runs-on: ${{ matrix.os }} 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Set up JDK 17 28 | uses: actions/setup-java@v3 29 | with: 30 | java-version: '17' 31 | distribution: 'temurin' 32 | - name: Build with Gradle 33 | uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7 34 | with: 35 | arguments: build 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | 17 | .idea/ 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![logo][]](https://xyo.network) 2 | 3 | # sdk-xyo-client-android 4 | 5 | [![jitpack-badge][]][jitpack-link] 6 | [![codacy-badge][]][codacy-link] 7 | [![codeclimate-badge][]][codeclimate-link] 8 | [![snyk-badge][]][snyk-link] 9 | 10 | > The XYO Foundation provides this source code available in our efforts to advance the understanding of the XYO Procotol and its possible uses. We continue to maintain this software in the interest of developer education. Usage of this source code is not intended for production. 11 | 12 | ## Table of Contents 13 | 14 | - [Title](#sdk-xyo-client-android) 15 | - [Description](#description) 16 | - [Instructions](#instructions) 17 | - [Maintainers](#maintainers) 18 | - [License](#license) 19 | - [Credits](#credits) 20 | 21 | ## Description 22 | 23 | Primary SDK for using the XYO Protocol 2.0 from Android. 24 | 25 | ## Instructions 26 | 27 | ### Adding to Project 28 | [See instructions on JitPack](https://jitpack.io/#xyoraclenetwork/sdk-xyo-client-android) 29 | 30 | ### Configure XYO Panel 31 | ```kotlin 32 | var panel = XyoPanel(context, "test", listOf(NodeClient), listOf(XyoSystemInfoWitness())) 33 | ``` 34 | 35 | ### Generate BoundWitness report 36 | ```kotlin 37 | var bw = panel.reportAsync().bw 38 | ``` 39 | 40 | ### Proguard Issues 41 | 42 | > There seems to be issues using proguard with the SDK, or more specifically, Moshi, where it will remove generated classes 43 | > We strongly recommend that you use R8 over Proguard. 44 | 45 | ## Maintainers 46 | 47 | - Arie Trouw 48 | 49 | ## License 50 | 51 | See the [LICENSE](LICENSE) file for license details 52 | 53 | ## Credits 54 | 55 | Made with 🔥 and ❄️ by [XYO](https://xyo.network) 56 | 57 | [logo]: https://cdn.xy.company/img/brand/XYO_full_colored.png 58 | 59 | [jitpack-badge]: https://jitpack.io/v/xyoraclenetwork/sdk-xyo-client-android.svg 60 | [jitpack-link]: https://jitpack.io/#xyoraclenetwork/sdk-xyo-client-android 61 | 62 | [codacy-badge]: https://app.codacy.com/project/badge/Grade/e5647b5338044a958e18c0fe91b4ed4f 63 | [codacy-link]: https://www.codacy.com/gh/XYOracleNetwork/sdk-xyo-client-swift/dashboard?utm_source=github.com&utm_medium=referral&utm_content=XYOracleNetwork/sdk-xyo-client-android&utm_campaign=Badge_Grade 64 | 65 | [codeclimate-badge]: https://api.codeclimate.com/v1/badges/127abaccfe85048dcf38/maintainability 66 | [codeclimate-link]: https://codeclimate.com/github/XYOracleNetwork/sdk-xyo-client-android/maintainability 67 | 68 | [snyk-badge]: https://snyk.io/test/github/XYOracleNetwork/sdk-xyo-client-android/badge.svg 69 | [snyk-link]: https://snyk.io/test/github/XYOracleNetwork/sdk-xyo-client-android 70 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:8.7.3' 8 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24" 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 10 | classpath("de.mannodermaus.gradle.plugins:android-junit5:1.11.0.0") 11 | classpath "com.google.protobuf:protobuf-gradle-plugin:0.9.4" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | plugins { 19 | id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.10' apply false 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | google() 25 | mavenCentral() 26 | } 27 | } 28 | 29 | tasks.register('clean', Delete) { 30 | delete rootProject.layout.buildDirectory 31 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork/sdk-xyo-client-android/bb068f56bb49a34ebd3770d6f549eeedebdaa860/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 -------------------------------------------------------------------------------- /sdk/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /sdk/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'maven-publish' 4 | apply plugin: "de.mannodermaus.android-junit5" 5 | apply plugin: "com.google.protobuf" 6 | 7 | group = 'network.xyo' 8 | 9 | def majorVersion = 2 10 | def minorVersion = 1 11 | def patchVersion = 5 12 | 13 | def verCode = majorVersion * 10000000 + minorVersion * 10000 + patchVersion 14 | def verString = "" + majorVersion + "." + minorVersion + "(Build-" + patchVersion + ")" 15 | 16 | android { 17 | compileSdk 35 18 | 19 | defaultConfig { 20 | minSdkVersion 21 21 | targetSdkVersion 33 22 | versionCode verCode 23 | versionName verString 24 | 25 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 26 | testInstrumentationRunnerArgument("runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder") 27 | consumerProguardFiles "consumer-rules.pro" 28 | } 29 | 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | shrinkResources false 34 | } 35 | } 36 | 37 | compileOptions { 38 | sourceCompatibility JavaVersion.VERSION_1_8 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | } 41 | 42 | kotlinOptions { 43 | jvmTarget = JavaVersion.VERSION_1_8 44 | } 45 | 46 | namespace 'sdk.xyo.client.android' 47 | 48 | sourceSets { 49 | main { 50 | java { 51 | exclude '**/test/**', '**/androidTest/**' 52 | } 53 | manifest.srcFile 'src/main/AndroidManifest.xml' 54 | proto { 55 | srcDir 'src/main/java/network/xyo/client/proto' 56 | } 57 | } 58 | } 59 | } 60 | 61 | dependencies { 62 | implementation 'org.jetbrains.kotlin:kotlin-reflect:2.0.21' 63 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0' 64 | implementation 'com.squareup.moshi:moshi-kotlin:1.15.1' 65 | implementation 'androidx.core:core-ktx:1.15.0' 66 | implementation 'com.google.android.gms:play-services-location:21.3.0' 67 | implementation 'tech.figure.hdwallet:hdwallet:0.4.4' 68 | implementation 'com.squareup.okhttp3:okhttp:4.12.0' 69 | implementation 'com.madgag.spongycastle:prov:1.58.0.0' 70 | implementation 'com.madgag.spongycastle:pkix:1.54.0.0' 71 | implementation 'com.ionspin.kotlin:bignum:0.3.10' 72 | androidTestImplementation 'androidx.test:runner:1.6.2' 73 | androidTestImplementation 'androidx.test:rules:1.6.1' 74 | androidTestImplementation 'androidx.test.ext:junit:1.2.1' 75 | androidTestImplementation 'de.mannodermaus.junit5:android-test-core:1.6.0' 76 | androidTestRuntimeOnly 'de.mannodermaus.junit5:android-test-runner:1.6.0' 77 | androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.11.3' 78 | implementation 'androidx.datastore:datastore:1.1.1' 79 | implementation 'com.google.protobuf:protobuf-javalite:4.28.3' 80 | } 81 | 82 | publishing { 83 | publications { 84 | Production(MavenPublication) { 85 | artifact("$buildDir/outputs/aar/sdk-release.aar") { 86 | builtBy assemble 87 | } 88 | groupId 'network.xyo' 89 | artifactId 'sdk-xyo-client-android' 90 | version verString 91 | 92 | //The publication doesn't know about our dependencies, so we have to manually add them to the pom 93 | pom.withXml { 94 | def dependenciesNode = asNode()['dependencies'][0] ?: asNode().appendNode('dependencies') 95 | //Iterate over the compile dependencies (we don't want the test ones), adding a node for each 96 | configurations.implementation.allDependencies.each { 97 | if (it.name != 'unspecified') { 98 | def dependencyNode = dependenciesNode.appendNode('dependency') 99 | dependencyNode.appendNode('groupId', it.group) 100 | dependencyNode.appendNode('artifactId', it.name) 101 | dependencyNode.appendNode('version', it.version) 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | protobuf { 110 | protoc { 111 | artifact = 'com.google.protobuf:protoc:3.20.1' 112 | } 113 | generateProtoTasks { 114 | all().configureEach { task -> 115 | task.builtins { 116 | java { 117 | option "lite" 118 | } 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /sdk/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class network.xyo.**,** { *; } 2 | -keepclassmembers class network.xyo.**,** { *; } -------------------------------------------------------------------------------- /sdk/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class network.xyo.**,** { *; } 2 | -keepclassmembers class network.xyo.**,** { *; } -------------------------------------------------------------------------------- /sdk/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/account/AccountTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import kotlinx.coroutines.runBlocking 5 | import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository 6 | import network.xyo.client.lib.hexStringToByteArray 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | class AccountTest { 11 | 12 | val testVectorPrivateKey = "7f71bc5644f8f521f7e9b73f7a391e82c05432f8a9d36c44d6b1edbf1d8db62f" 13 | val testVectorPublicKey 14 | = "ed6f3b86542f45aab88ec48ab1366b462bd993fec83e234054afd8f2311fba774800fdb40c04918463b463a6044b83413a604550bfba8f8911beb65475d6528e" 15 | val testVectorAddress = "5e7a847447e7fec41011ae7d32d768f86605ba03" 16 | val testVectorHash = "4b688df40bcedbe641ddb16ff0a1842d9c67ea1c3bf63f3e0471baa664531d1a" 17 | val testVectorSignature 18 | = "b61dad551e910e2793b4f9f880125b5799086510ce102fad0222c1b093c60a6b38aa35ef56f97f86537269e8be95832aaa37d3b64d86b67f0cda467ac7cb5b3e" 19 | 20 | @Before 21 | fun setupAccount() { 22 | Account.previousHashStore = PreviousHashStorePrefsRepository.getInstance(InstrumentationRegistry.getInstrumentation().targetContext) 23 | } 24 | 25 | @Test 26 | fun testRandomAccount() { 27 | val account = Account.random() 28 | assert(account.privateKey.count() == 32) 29 | assert(account.publicKey.count() == 33) 30 | assert(account.publicKeyUncompressed.count() == 64) 31 | } 32 | 33 | @OptIn(ExperimentalStdlibApi::class) 34 | @Test 35 | fun testKnownPrivateKeyAccount() { 36 | runBlocking { 37 | val account = Account.fromPrivateKey(hexStringToByteArray(testVectorPrivateKey)) 38 | assert(account.privateKey.count() == 32) 39 | assert(account.publicKey.count() == 33) 40 | assert(account.publicKeyUncompressed.count() == 64) 41 | assert(account.publicKeyUncompressed.toHexString() == testVectorPublicKey) 42 | assert(account.address.toHexString() == testVectorAddress) 43 | val signature = account.sign(hexStringToByteArray(testVectorHash)) 44 | assert(signature.toHexString() == testVectorSignature) 45 | assert(account.verify(hexStringToByteArray(testVectorHash), signature)) 46 | } 47 | } 48 | 49 | @OptIn(ExperimentalStdlibApi::class) 50 | @Test 51 | fun testPreviousHash() { 52 | runBlocking { 53 | val address = hexStringToByteArray(testVectorPrivateKey) 54 | val account = Account.fromPrivateKey(address) 55 | account.sign(hexStringToByteArray(testVectorHash)) 56 | 57 | val savedAddressInStore = Account.addressFromUncompressedPublicKey(account.publicKeyUncompressed) 58 | val previousHashInStore = Account.previousHashStore?.getItem(savedAddressInStore)?.toHexString() 59 | assert(previousHashInStore == testVectorHash) 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/account/WalletTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account 2 | 3 | import android.util.Log 4 | import org.junit.Test 5 | 6 | class WalletTest { 7 | val words = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" 8 | val privateKeyVectors = arrayOf("m/44'/0'/0'/0/0", "e284129cc0922579a535bbf4d1a3b25773090d28c909bc0fed73b5e0222cc372") 9 | 10 | @OptIn(ExperimentalStdlibApi::class) 11 | @Test 12 | fun testWallet() { 13 | val wallet = Wallet.fromMnemonic(words, privateKeyVectors[0]) 14 | Log.i("privateKey", wallet.privateKey.toHexString()) 15 | assert(wallet.privateKey.toHexString() == privateKeyVectors[1]) 16 | } 17 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/account/WalletTestVectors.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account 2 | 3 | import android.util.Log 4 | import org.junit.Test 5 | 6 | data class TestVector( 7 | val path: String, 8 | val address: String, 9 | val privateKey: String, 10 | val publicKey: String 11 | ) 12 | 13 | class WalletTestVectors { 14 | val phrase = "later puppy sound rebuild rebuild noise ozone amazing hope broccoli crystal grief" 15 | val testVectors = arrayOf( 16 | TestVector( 17 | "m/44'/60'/0'/0/0", 18 | "e46c258c74c7c1df33d7caa4c2c664dc0843ab3f", 19 | "96a7705eedbb701a03ee235911253fd3eb80e48a06106c0bf957d42b72bd8efa", 20 | "03a9f10779cb44e73a1983b8225ce9de96ff63cbc8a2900db102fa55a38a14b206")) 21 | 22 | @OptIn(ExperimentalStdlibApi::class) 23 | @Test 24 | fun testWallet() { 25 | val index = 0 26 | val vector = this.testVectors[index] 27 | val wallet = Wallet.fromMnemonic(phrase, vector.path) 28 | Log.i("privateKey", wallet.privateKey.toHexString()) 29 | val calcPrivateKey = wallet.privateKey.toHexString() 30 | val calcPublicKey = wallet.publicKey.toHexString() 31 | val calcAddress = wallet.address.toHexString() 32 | assert(calcPrivateKey == vector.privateKey) 33 | assert(calcPublicKey == vector.publicKey) 34 | assert(calcAddress == vector.address) 35 | } 36 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/boundwitness/BoundWitnessBuilderTest.kt: -------------------------------------------------------------------------------- 1 | import network.xyo.client.account.Account 2 | import network.xyo.client.account.Wallet 3 | import org.junit.jupiter.api.BeforeEach 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.api.Assertions.* 6 | 7 | @OptIn(ExperimentalStdlibApi::class) 8 | class BoundWitnessBuilderTest { 9 | 10 | @BeforeEach 11 | fun setUp() { 12 | // Ensure previousHash = null for test accounts 13 | Account.previousHashStore = MemoryPreviousHashStore() 14 | } 15 | 16 | @Test 17 | fun build_returns_expected_hash() { 18 | for (testCase in boundWitnessSequenceTestCases) { 19 | // Create accounts 20 | val signers = mutableListOf() 21 | for ((i, mnemonic) in testCase.mnemonics.withIndex()) { 22 | val path = testCase.paths[i] 23 | val account = try { 24 | Wallet.fromMnemonic(mnemonic, path) 25 | } catch (e: Exception) { 26 | null 27 | } 28 | if (account != null) { 29 | signers.add(account) 30 | } else { 31 | fail("Error creating account from mnemonic") 32 | } 33 | } 34 | 35 | assertEquals( 36 | testCase.addresses.size, 37 | signers.size, 38 | "Incorrect number of accounts created." 39 | ) 40 | 41 | val expectedAddresses = testCase.addresses.map { it.lowercase() } 42 | val actualAddresses = signers.map { it.address.toHexString().lowercase() } 43 | assertEquals( 44 | expectedAddresses, 45 | actualAddresses, 46 | "Incorrect addresses when creating accounts." 47 | ) 48 | 49 | /* 50 | // Ensure correct initial account state 51 | for ((i, expectedPreviousHash) in testCase.previousHashes.withIndex()) { 52 | val signer = signers.getOrNull(i) 53 | assertNotNull(signer, "Signer at index $i should not be null.") 54 | assertEquals( 55 | expectedPreviousHash, 56 | signer.previousHash, 57 | "Incorrect previous hash for account at index $i" 58 | ) 59 | } 60 | 61 | // Build the Bound Witness (BW) 62 | val bw = BoundWitnessBuilder() 63 | .signers(signers) 64 | .payloads(testCase.payloads) 65 | 66 | val (bwJson, _) = bw.build() 67 | val hash = PayloadBuilder.dataHash(bwJson.typedPayload) 68 | val rootHash = PayloadBuilder.hash(bwJson.typedPayload) 69 | 70 | // Ensure the BW is correct 71 | assertEquals( 72 | testCase.dataHash, 73 | hash.toHex(), 74 | "Incorrect data hash in BW" 75 | ) 76 | 77 | for ((i, expectedPayloadHash) in testCase.payloadHashes.withIndex()) { 78 | val actualPayloadHash = bwJson.typedPayload.payload_hashes.getOrNull(i) 79 | assertNotNull(actualPayloadHash, "Payload hash at index $i should not be null.") 80 | assertEquals( 81 | expectedPayloadHash, 82 | actualPayloadHash, 83 | "Incorrect payload hash in BW at index $i" 84 | ) 85 | } 86 | 87 | for ((i, payload) in testCase.payloads.withIndex()) { 88 | val actualSchema = bwJson.typedPayload.payload_schemas.getOrNull(i) 89 | assertNotNull(actualSchema, "Payload schema at index $i should not be null.") 90 | assertEquals( 91 | payload.schema, 92 | actualSchema, 93 | "Incorrect payload schema in BW at index $i" 94 | ) 95 | } 96 | 97 | // Ensure correct ending account state 98 | for (signer in signers) { 99 | assertEquals( 100 | hash.toHex(), 101 | signer.previousHash, 102 | "Incorrect previous hash for account ${signer.address}" 103 | ) 104 | } 105 | 106 | */ 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/boundwitness/BoundWitnessTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness 2 | 3 | import android.content.Context 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import androidx.test.rule.GrantPermissionRule 6 | import kotlinx.coroutines.ExperimentalCoroutinesApi 7 | import kotlinx.coroutines.runBlocking 8 | import network.xyo.client.payload.TestPayload1 9 | import network.xyo.client.account.Account 10 | import network.xyo.client.account.Wallet 11 | import network.xyo.client.archivist.wrapper.ArchivistWrapper 12 | import network.xyo.client.node.client.DiscoverPayload 13 | import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository 14 | import network.xyo.client.lib.JsonSerializable 15 | import network.xyo.client.node.client.NodeClient 16 | import network.xyo.client.payload.Payload 17 | import org.json.JSONObject 18 | import org.junit.Before 19 | import org.junit.Rule 20 | import org.junit.Test 21 | import org.junit.jupiter.api.Assertions.* 22 | 23 | data class RequestDependencies(val client: NodeClient, val query: Payload, val payloads: List) 24 | 25 | class BoundWitnessTest { 26 | 27 | val apiDomainBeta = "https://beta.api.archivist.xyo.network" 28 | val apiDomainLocal = "http://10.0.2.2:8080" 29 | 30 | @Rule 31 | @JvmField 32 | val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.INTERNET, android.Manifest.permission.ACCESS_WIFI_STATE) 33 | 34 | 35 | lateinit var appContext: Context 36 | 37 | @Before 38 | fun useAppContext() { 39 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 40 | } 41 | 42 | @Before 43 | fun setupAccount() { 44 | Account.previousHashStore = PreviousHashStorePrefsRepository.getInstance(InstrumentationRegistry.getInstrumentation().targetContext) 45 | } 46 | 47 | fun generateQuery(nodeUrl: String): RequestDependencies { 48 | val account = Account.random() 49 | val client = NodeClient(nodeUrl, account, appContext) 50 | val query = DiscoverPayload() 51 | val payloads = mutableListOf() 52 | payloads.add(TestPayload1()) 53 | return RequestDependencies(client, query, payloads) 54 | } 55 | 56 | @OptIn(ExperimentalCoroutinesApi::class) 57 | fun testSendQueryBW(nodeUrl: String) { 58 | runBlocking { 59 | val(client, query, payloads) = generateQuery(nodeUrl) 60 | val postResult = client.query(query, payloads) 61 | assertEquals(null, postResult.errors) 62 | } 63 | } 64 | 65 | /* @Test 66 | fun testSendQueryBWSendLocal() { 67 | testSendQueryBW(apiDomainLocal) 68 | } 69 | */ 70 | 71 | @Test 72 | fun testSendQueryBWSendBeta() { 73 | testSendQueryBW(apiDomainBeta) 74 | } 75 | 76 | @Test 77 | fun testBoundWitnessMeta() { 78 | runBlocking { 79 | val bw = BoundWitnessBuilder().signer(Account.random()).payloads(listOf( 80 | TestPayload1() 81 | )).build() 82 | assert(bw.__signatures.size == 1) 83 | } 84 | } 85 | 86 | @Test 87 | fun testBoundWitnessMetaSerialization() { 88 | runBlocking { 89 | val bw = BoundWitnessBuilder().signer(Account.random()).payloads(listOf( 90 | TestPayload1() 91 | )).build() 92 | val serializedBw = JsonSerializable.toJson(bw) 93 | val bwJson = JSONObject(serializedBw) 94 | assert(bwJson.get("_client") == "android") 95 | assertNotNull(bwJson.get("\$signatures")) 96 | } 97 | } 98 | 99 | @OptIn(ExperimentalStdlibApi::class) 100 | @Test 101 | fun testBoundWitnessPreviousHash() { 102 | runBlocking { 103 | val testAccount = Wallet.fromMnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about") 104 | val knownBw1DataHash = "cb8b63aaaa8da5763f3e62541421c48b9b2356b4b9da24f58359072b89549e66" 105 | val testPayload = TestPayload1() 106 | val bw = BoundWitnessBuilder().signer(testAccount).payloads(listOf( 107 | testPayload 108 | )).build() 109 | val calcDataHash = bw.dataHash().toHexString() 110 | val calcJson = bw.toJson() 111 | val calcDataJson = bw.toJson(true) 112 | val calcDataHash2 = JsonSerializable.sha256(calcDataJson).toHexString() 113 | assertEquals(calcDataHash, calcDataHash2) 114 | assertNotEquals(calcJson, calcDataJson) 115 | assertEquals(knownBw1DataHash, calcDataHash) 116 | val bw2 = BoundWitnessBuilder().signer(testAccount).payloads(listOf( 117 | TestPayload1() 118 | )).build() 119 | assertEquals(bw2.previous_hashes.first(), knownBw1DataHash) 120 | assertEquals(bw2.previous_hashes.first(), bw.dataHash().toHexString()) 121 | } 122 | } 123 | 124 | @OptIn(ExperimentalStdlibApi::class) 125 | @Test 126 | fun testBoundWitnessRoundTripToArchivist() { 127 | runBlocking { 128 | val client = ArchivistWrapper(NodeClient("$apiDomainLocal/Archivist", null, appContext)) 129 | val testAccount = Account.random() 130 | val testPayload = TestPayload1() 131 | val bw = BoundWitnessBuilder().signer(testAccount).payloads(listOf(testPayload)).build() 132 | val bwJson: String = bw.toJson() 133 | println("bwJson-start") 134 | println(bwJson) 135 | println("bwJson-end") 136 | val queryResult = client.insert(listOf(bw)) 137 | 138 | assert((queryResult.errors?.size ?: 0) == 0) 139 | assert(((queryResult.response?.payloads?.size ?: 0) > 0)) 140 | assert(((queryResult.response?.payloads?.size ?: 0) == 2)) 141 | 142 | val bwDataHash = bw.dataHash().toHexString() 143 | assert(((queryResult.response?.payloads?.filter { it.dataHash().toHexString() == bwDataHash})?.size == 1)) 144 | 145 | val dataResult = client.get(listOf(bwDataHash)) 146 | val dataResponse = dataResult.response?.rawResponse 147 | assert(dataResponse!!.contains(bwDataHash)) 148 | 149 | val bwHash = bw.dataHash().toHexString() 150 | val result = client.get(listOf(bwHash)) 151 | val response = result.response?.rawResponse 152 | assert(response!!.contains(bwHash)) 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/boundwitness/TestBoundWitnessSequence.kt: -------------------------------------------------------------------------------- 1 | import network.xyo.client.payload.Payload 2 | 3 | class IdPayload(val salt: String): Payload("network.xyo.id") { 4 | constructor(salt: UInt) : this("${salt}") 5 | } 6 | 7 | data class BoundWitnessSequenceTestCase( 8 | val mnemonics: List, 9 | val paths: List, 10 | val addresses: List, 11 | val payloads: List, 12 | val payloadHashes: List, 13 | val previousHashes: List, 14 | val dataHash: String 15 | ) 16 | 17 | // Define the PayloadsWithHashes data class 18 | data class PayloadsWithHashes( 19 | val payloads: List, 20 | val payloadHashes: List 21 | ) 22 | 23 | // Initialize payload sequences 24 | val payloadSequences: List = listOf( 25 | PayloadsWithHashes( 26 | payloads = listOf(IdPayload(0u)), 27 | payloadHashes = listOf( 28 | "ada56ff753c0c9b2ce5e1f823eda9ac53501db2843d8883d6cf6869c18ef7f65" 29 | ) 30 | ), 31 | PayloadsWithHashes( 32 | payloads = listOf(IdPayload(1u)), 33 | payloadHashes = listOf( 34 | "3a3b8deca568ff820b0b7c8714fbdf82b40fb54f4b15aca8745e06b81291558e" 35 | ) 36 | ), 37 | PayloadsWithHashes( 38 | payloads = listOf(IdPayload(2u), IdPayload(3u)), 39 | payloadHashes = listOf( 40 | "1a40207fab71fc184e88557d5bee6196cbbb49f11f73cda85000555a628a8f0a", 41 | "c4bce9b4d3239fcc9a248251d1bef1ba7677e3c0c2c43ce909a6668885b519e6" 42 | ) 43 | ), 44 | PayloadsWithHashes( 45 | payloads = listOf(IdPayload(4u), IdPayload(5u)), 46 | payloadHashes = listOf( 47 | "59c0374dd801ae64ddddba27320ca028d7bd4b3d460f6674c7da1b4aa9c956d6", 48 | "5d9b8e84bc824280fcbb6290904c2edbb401d626ad9789717c0a23d1cab937b0" 49 | ) 50 | ) 51 | ) 52 | 53 | // Define wallet mnemonics, paths, and addresses 54 | const val wallet1Mnemonic = 55 | "report door cry include salad horn recipe luxury access pledge husband maple busy double olive" 56 | const val wallet1Path = "m/44'/60'/0'/0/0" 57 | const val wallet1Address = "25524Ca99764D76CA27604Bb9727f6e2f27C4533" 58 | 59 | const val wallet2Mnemonic = 60 | "turn you orphan sauce act patient village entire lava transfer height sense enroll quit idle" 61 | const val wallet2Path = "m/44'/60'/0'/0/0" 62 | const val wallet2Address = "FdCeD2c3549289049BeBf743fB721Df211633fBF" 63 | 64 | // Initialize BoundWitnessSequenceTestCase instances 65 | val boundWitnessSequenceTestCase1 = BoundWitnessSequenceTestCase( 66 | mnemonics = listOf(wallet1Mnemonic), 67 | paths = listOf(wallet1Path), 68 | addresses = listOf(wallet1Address), 69 | payloads = payloadSequences[0].payloads, 70 | payloadHashes = payloadSequences[0].payloadHashes, 71 | previousHashes = listOf(null), 72 | dataHash = "750113b9826ba94b622667b06cd8467f1330837581c28907c16160fec20d0a4b" 73 | ) 74 | 75 | val boundWitnessSequenceTestCase2 = BoundWitnessSequenceTestCase( 76 | mnemonics = listOf(wallet2Mnemonic), 77 | paths = listOf(wallet2Path), 78 | addresses = listOf(wallet2Address), 79 | payloads = payloadSequences[1].payloads, 80 | payloadHashes = payloadSequences[1].payloadHashes, 81 | previousHashes = listOf(null), 82 | dataHash = "bacd010d79126a154339e59c11c5b46be032c3bef65626f83bcafe968dc6dd1b" 83 | ) 84 | 85 | val boundWitnessSequenceTestCase3 = BoundWitnessSequenceTestCase( 86 | mnemonics = listOf(wallet1Mnemonic, wallet2Mnemonic), 87 | paths = listOf(wallet1Path, wallet2Path), 88 | addresses = listOf(wallet1Address, wallet2Address), 89 | payloads = payloadSequences[2].payloads, 90 | payloadHashes = payloadSequences[2].payloadHashes, 91 | previousHashes = listOf( 92 | "750113b9826ba94b622667b06cd8467f1330837581c28907c16160fec20d0a4b", 93 | "bacd010d79126a154339e59c11c5b46be032c3bef65626f83bcafe968dc6dd1b" 94 | ), 95 | dataHash = "73245ef73517913f4b57c12d56d81199968ecd8fbefea9ddc474f43dd6cfa8c8" 96 | ) 97 | 98 | val boundWitnessSequenceTestCase4 = BoundWitnessSequenceTestCase( 99 | mnemonics = listOf(wallet1Mnemonic, wallet2Mnemonic), 100 | paths = listOf(wallet1Path, wallet2Path), 101 | addresses = listOf(wallet1Address, wallet2Address), 102 | payloads = payloadSequences[3].payloads, 103 | payloadHashes = payloadSequences[3].payloadHashes, 104 | previousHashes = listOf( 105 | "73245ef73517913f4b57c12d56d81199968ecd8fbefea9ddc474f43dd6cfa8c8", 106 | "73245ef73517913f4b57c12d56d81199968ecd8fbefea9ddc474f43dd6cfa8c8" 107 | ), 108 | dataHash = "210d86ea43d82b85a49b77959a8ee4e6016ff7036254cfa39953befc66073010" 109 | ) 110 | 111 | // Aggregate all test cases into a list 112 | val boundWitnessSequenceTestCases = listOf( 113 | boundWitnessSequenceTestCase1, 114 | boundWitnessSequenceTestCase2, 115 | boundWitnessSequenceTestCase3, 116 | boundWitnessSequenceTestCase4 117 | ) -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/boundwitness/TestPayload1.kt: -------------------------------------------------------------------------------- 1 | import network.xyo.client.payload.Payload 2 | 3 | class TestPayload1SubObject { 4 | var number_value = 2 5 | var string_value = "yo" 6 | } 7 | 8 | class TestPayload1: Payload("network.xyo.test") { 9 | var timestamp = 1_618_603_439_107 10 | var number_field = 1 11 | var object_field = TestPayload1SubObject() 12 | var string_field = "there" 13 | } 14 | 15 | val testPayload1 = TestPayload1() 16 | val testPayload1Hash: String = "c915c56dd93b5e0db509d1a63ca540cfb211e11f03039b05e19712267bb8b6db" 17 | -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/boundwitness/TestPayload2.kt: -------------------------------------------------------------------------------- 1 | import network.xyo.client.payload.Payload 2 | 3 | class TestPayload2SubObject { 4 | var number_value = 2 5 | var string_value = "yo" 6 | var optional_field: String? = null 7 | } 8 | 9 | class TestPayload2: Payload("network.xyo.test") { 10 | var timestamp = 1_618_603_439_107 11 | var object_field = TestPayload2SubObject() 12 | var string_field = "there" 13 | var number_field = 1 14 | } 15 | 16 | val testPayload2 = TestPayload2() 17 | val testPayload2Hash: String = "c915c56dd93b5e0db509d1a63ca540cfb211e11f03039b05e19712267bb8b6db" 18 | -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/lib/Constants.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | import network.xyo.client.account.Account 4 | import network.xyo.client.payload.Payload 5 | 6 | class DebugPayload(val nonce: Int) : Payload(SCHEMA) { 7 | companion object { 8 | const val SCHEMA = "network.xyo.debug" 9 | } 10 | } 11 | 12 | class BasicPayload : Payload(SCHEMA) { 13 | companion object { 14 | const val SCHEMA = "network.xyo.basic" 15 | } 16 | } 17 | 18 | 19 | class TestConstants { 20 | companion object { 21 | const val accountPrivateKey = "69f0b123c094c34191f22c25426036d6e46d5e1fab0a04a164b3c1c2621152ab" 22 | val TestAccount = Account.fromPrivateKey(accountPrivateKey) 23 | val debugPayload = DebugPayload(1) 24 | const val debugPayloadHash = "15b8d0e30ca5aa96ca6cc9e1528c075aec88cd3f2c3eb0394fde647eb4bf4547" 25 | const val nodeUrlLocal = "http://10.0.2.2:8080" 26 | const val nodeUrlBeta = "https://beta.api.archivist.xyo.network" 27 | const val queryResponseJson = "{\"data\":[{\"addresses\":[\"cddaa9e8922142dfd53e5067f5b9e5de5c2ea0cf\",\"6cfeb02624f01112892e02f18a5e3409ae0a0739\"],\"payload_hashes\":[\"09a4dda042973bcb69f7a7f63a8a79763760e0e2ca9b0d486d7edf72ac2288be\"],\"payload_schemas\":[\"network.xyo.boundwitness\"],\"previous_hashes\":[\"09a4dda042973bcb69f7a7f63a8a79763760e0e2ca9b0d486d7edf72ac2288be\",null],\"schema\":\"network.xyo.boundwitness\",\"timestamp\":1682101895916,\"_signatures\":[\"5c94a98e2da7a4d3750c54c6a3a539900d7722c49419555f0563227876fc7eb3c67718b20bce7ba84913754557eea1a7864a1bc60aa5de5184f1e711a5b54637\",\"02910768ebb01049ccc10e957cbc70c32b71d1891bee66dcb71bedee284f796ca82c791e0efd361d49b34c10afce2aecacf9f27655c3496c91a83f5ad6ed8398\"]},[{\"addresses\":[\"cddaa9e8922142dfd53e5067f5b9e5de5c2ea0cf\"],\"payload_hashes\":[\"a83df3b9d9e92a391d6e172817762a90f1f6ca19da226e7a4679284d6af91f9b\",\"89dafadc4fa4249d73fce1bae21fe14ecff36175564fcbb8dc343982402f4d7e\",\"3cef5677c2f800d6603cebebab17e1cb459e086b81a50fd8d154a5dd5f279d2d\"],\"payload_schemas\":[\"network.xyo.boundwitness\",\"network.xyo.system.info\",\"network.xyo.query.archivist.insert\"],\"previous_hashes\":[null],\"schema\":\"network.xyo.boundwitness\",\"timestamp\":1682101895915,\"_signatures\":[\"5cd4359570fd0273f79a9181c3345aa034481e62c01e6f29e29861eeb167fbf0c2451fc9096c2c9d99b2a5e1ad3e53318fb6a47647fb083842cc82ead6d7c58c\"]}]],\"meta\":{\"profile\":{\"duration\":37,\"endTime\":1682101895919,\"startTime\":1682101895882}}}" 28 | const val queryResponseBWHash = "eb5bcbf881e5f91f8b10bb360bcb660b6e70b9ed429ac3c5c0f9b74150b59a07" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/node/client/NodeClientTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.node.client 2 | 3 | import android.content.Context 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import kotlinx.coroutines.runBlocking 7 | import network.xyo.client.lib.TestConstants 8 | import network.xyo.client.account.Account 9 | import network.xyo.client.archivist.wrapper.ArchivistWrapper 10 | import network.xyo.client.boundwitness.BoundWitnessFields 11 | import network.xyo.client.payload.Payload 12 | import org.junit.Before 13 | import org.junit.Test 14 | import org.junit.jupiter.api.Assertions.* 15 | 16 | class DiscoverPayload : Payload(SCHEMA) { 17 | companion object { 18 | const val SCHEMA = "network.xyo.query.module.discover" 19 | } 20 | } 21 | 22 | class NodeClientTest { 23 | private val apiDomainBeta = "${TestConstants.nodeUrlBeta}/Archivist" 24 | 25 | private lateinit var appContext: Context 26 | 27 | @Before 28 | fun useAppContext() { 29 | // Context of the app under test. 30 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 31 | } 32 | 33 | @OptIn(ExperimentalCoroutinesApi::class) 34 | @Test 35 | fun discoverTestBeta() { 36 | val account = Account.random() 37 | val client = NodeClient(TestConstants.nodeUrlBeta, account, appContext) 38 | val query = DiscoverPayload() 39 | 40 | runBlocking { 41 | val postResult = client.query(query, null) 42 | assertEquals(null, postResult.errors) 43 | } 44 | } 45 | 46 | /* 47 | @OptIn(ExperimentalCoroutinesApi::class) 48 | @Test 49 | fun discoverTestLocal() { 50 | val account = Account.random() 51 | val client = NodeClient(TestConstants.nodeUrlBeta, account) 52 | val query = XyoPayload("network.xyo.query.module.discover") 53 | 54 | runBlocking { 55 | val postResult = client.query(query, null, null) 56 | assertEquals(null, postResult.errors) 57 | } 58 | } 59 | */ 60 | 61 | @Test 62 | fun archivistInsertTest() { 63 | val archivist = ArchivistWrapper(NodeClient(apiDomainBeta, 64 | TestConstants.TestAccount, appContext)) 65 | 66 | val payloads = arrayListOf(TestConstants.debugPayload) 67 | 68 | runBlocking { 69 | val (response, errors) = archivist.insert(payloads) 70 | assertNotEquals(response, null) 71 | assertEquals(errors, null) 72 | 73 | if (response != null) { 74 | assertEquals(response.bw?.schema, BoundWitnessFields.SCHEMA) 75 | } else { 76 | throw(Error("Response should not be null")) 77 | } 78 | } 79 | 80 | runBlocking { 81 | val getResult = archivist.get(arrayListOf(TestConstants.debugPayloadHash)) 82 | assertNotEquals(getResult.response, null) 83 | 84 | val response = getResult.response 85 | if (response != null) { 86 | assertEquals(response.payloads?.get(0)?.schema, TestConstants.debugPayload.schema) 87 | } else { 88 | throw(Error("Response should not be null")) 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/node/client/QueryResponseWrapperTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.node.client 2 | 3 | import network.xyo.client.lib.TestConstants 4 | import org.junit.Test 5 | import org.junit.jupiter.api.Assertions.* 6 | 7 | @OptIn(ExperimentalStdlibApi::class) 8 | class QueryResponseWrapperTest { 9 | @Test 10 | fun parseTest() { 11 | val queryResponseWrapper = QueryResponseWrapper.parse(TestConstants.queryResponseJson) 12 | assertNotEquals(queryResponseWrapper?.bw, null) 13 | assertEquals(queryResponseWrapper?.bwHash?.toHexString(), TestConstants.queryResponseBWHash) 14 | 15 | assertNotEquals(queryResponseWrapper?.payloads?.size, 2) 16 | } 17 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/payload/EventPayload.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.payload 2 | 3 | import com.squareup.moshi.JsonClass 4 | import java.util.* 5 | 6 | @JsonClass(generateAdapter = true) 7 | open class EventPayload(val event: String): Payload(SCHEMA) { 8 | val timestamp = Date().time 9 | 10 | companion object { 11 | const val SCHEMA = "network.xyo.event" 12 | } 13 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/payload/PayloadTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.payload 2 | 3 | import android.content.Context 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import androidx.test.rule.GrantPermissionRule 6 | import com.squareup.moshi.Json 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.launch 10 | import network.xyo.client.lib.BasicPayload 11 | import network.xyo.client.lib.TestConstants 12 | import network.xyo.client.account.Account 13 | import network.xyo.client.boundwitness.BoundWitnessBuilder 14 | import network.xyo.client.lib.JsonSerializable 15 | import network.xyo.client.payload.model.WithMeta 16 | import network.xyo.client.types.HashHex 17 | import network.xyo.client.witness.XyoWitness 18 | import org.junit.Before 19 | import org.junit.Rule 20 | import org.junit.Test 21 | import org.junit.jupiter.api.Assertions.* 22 | import java.io.Serializable 23 | 24 | open class TestSubjectPayload: Payload("network.xyo.test") { 25 | 26 | } 27 | 28 | class TestPayload1SubObject { 29 | var number_value = 2 30 | var string_value = "yo" 31 | } 32 | 33 | class TestPayload1Meta: Serializable { 34 | var name = "yo" 35 | } 36 | 37 | open class TestPayload1Body: TestSubjectPayload() { 38 | var timestamp = 1618603439107 39 | var number_field = 1 40 | var object_field = TestPayload1SubObject() 41 | var string_field = "there" 42 | } 43 | 44 | @OptIn(ExperimentalStdlibApi::class) 45 | class TestPayload1: TestPayload1Body(), WithMeta { 46 | @Json(name = "\$meta") 47 | override var _meta = TestPayload1Meta() 48 | } 49 | 50 | class TestPayload2SubObject { 51 | var string_value = "yo" 52 | var number_value = 2 53 | } 54 | 55 | class TestPayload2: TestSubjectPayload() { 56 | var string_field = "there" 57 | var object_field = TestPayload2SubObject() 58 | var timestamp = 1618603439107 59 | var number_field = 1 60 | } 61 | 62 | class TestInvalidSchemaPayload: TestSubjectPayload() { 63 | var timestamp = 1618603439107 64 | var number_field = 1 65 | var object_field = TestPayload1SubObject() 66 | var string_field = "there" 67 | } 68 | 69 | val knownAddress = Account.fromPrivateKey(ByteArray(32) {index -> index.toByte()}) 70 | const val knownHash = "6e173bbfc0577ebde66b44b090316eca5ecad8ecdb5c51886211d805c769d2ea" 71 | 72 | @OptIn(ExperimentalStdlibApi::class) 73 | class PayloadTest { 74 | 75 | @Rule 76 | @JvmField 77 | val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.INTERNET, android.Manifest.permission.ACCESS_WIFI_STATE) 78 | 79 | lateinit var appContext: Context 80 | 81 | @Before 82 | fun useAppContext() { 83 | // Context of the app under test. 84 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 85 | } 86 | 87 | /*@Test 88 | fun testInvalidSchemaPayload() { 89 | val payload = TestInvalidSchemaPayload() 90 | assertThrows(XyoValidationException::class.java) { 91 | payload.validate() 92 | } 93 | }*/ 94 | 95 | @Test 96 | fun testRoundTripPayload() { 97 | val payload = TestPayload1() 98 | val payloadJsonString = JsonSerializable.toJson(payload) 99 | 100 | // ensure the schema is properly serialized from the base class 101 | assert(payloadJsonString.contains("network.xyo.test")) 102 | 103 | val payloadMirrored = JsonSerializable.fromJson(payloadJsonString, TestPayload1()) 104 | assertNotNull(payloadMirrored) 105 | if (payloadMirrored != null) { 106 | assertEquals(payload.schema, payloadMirrored.schema) 107 | } 108 | } 109 | 110 | 111 | @Test 112 | fun testRoundTripPanel() { 113 | val address = Account.fromPrivateKey("5a95531488b4d0d3645aea49678297ae9e2034879ce0389b80eb788e8b533592") 114 | val witness = XyoWitness(address, fun(_: Context): List { 115 | return listOf(BasicPayload()) 116 | }) 117 | 118 | CoroutineScope(Dispatchers.Main).launch { 119 | val response = arrayListOf(witness.observe(appContext)) 120 | val payloads = response.mapNotNull { payload -> payload }.flatten() 121 | val bwJson = BoundWitnessBuilder() 122 | .payloads(payloads) 123 | .signer(Account.random()) 124 | .build() 125 | val bwJsonString = JsonSerializable.toJson(bwJson) 126 | val bwMirrored = JsonSerializable.fromJson(bwJsonString, bwJson) 127 | assertNotNull(bwMirrored) 128 | } 129 | } 130 | 131 | @Test 132 | fun testHashing() { 133 | val payload = TestConstants.debugPayload 134 | assertEquals(payload.dataHash().toHexString(), TestConstants.debugPayloadHash) 135 | } 136 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/prefs/AccountPrefsRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.prefs 2 | 3 | import android.content.Context 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import kotlinx.coroutines.runBlocking 6 | import network.xyo.client.lib.TestConstants 7 | import network.xyo.client.witness.XyoPanel 8 | import network.xyo.client.account.Account 9 | import network.xyo.client.boundwitness.BoundWitnessBuilder 10 | import network.xyo.client.datastore.accounts.AccountPrefsRepository 11 | import network.xyo.client.settings.AccountPreferences 12 | import network.xyo.client.settings.PreviousHashStorePreferences 13 | import network.xyo.client.settings.SettingsInterface 14 | import network.xyo.client.witness.system.info.XyoSystemInfoWitness 15 | import org.junit.Before 16 | import org.junit.Test 17 | import org.junit.jupiter.api.Assertions.assertEquals 18 | import org.junit.jupiter.api.Assertions.assertNotEquals 19 | 20 | class AccountPrefsRepositoryTest { 21 | 22 | private lateinit var appContext: Context 23 | 24 | private val apiDomainBeta = "${TestConstants.nodeUrlBeta}/Archivist" 25 | 26 | @Before 27 | fun useAppContext() { 28 | // Context of the app under test. 29 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 30 | } 31 | 32 | @OptIn(ExperimentalStdlibApi::class) 33 | @Test 34 | fun testAccountPersistence() { 35 | runBlocking { 36 | val prefsRepository = 37 | AccountPrefsRepository.getInstance(appContext) 38 | prefsRepository.clearSavedAccountKey() 39 | 40 | val testAccount = Account.random() 41 | 42 | val panel = XyoPanel( 43 | appContext, testAccount, arrayListOf(Pair(apiDomainBeta, null)), listOf( 44 | XyoSystemInfoWitness() 45 | ) 46 | ) 47 | panel.resolveNodes() 48 | val generatedAddress = panel.account.address.toHexString() 49 | assertNotEquals(generatedAddress, null) 50 | 51 | val panel2 = XyoPanel( 52 | appContext, testAccount, arrayListOf(Pair(apiDomainBeta, null)), listOf( 53 | XyoSystemInfoWitness() 54 | ) 55 | ) 56 | panel2.resolveNodes() 57 | val secondGeneratedAddress = panel2.account.address.toHexString() 58 | assertEquals(generatedAddress, secondGeneratedAddress) 59 | } 60 | } 61 | 62 | @OptIn(ExperimentalStdlibApi::class) 63 | @Test 64 | fun testClearingExistingAccount() { 65 | runBlocking { 66 | val instance = AccountPrefsRepository.getInstance(appContext) 67 | val originalAddress = instance.getAccount().privateKey.toHexString() 68 | 69 | instance.clearSavedAccountKey() 70 | 71 | val refreshedAddress = instance.getAccount().privateKey.toHexString() 72 | 73 | assert(originalAddress !== refreshedAddress) 74 | } 75 | } 76 | 77 | @OptIn(ExperimentalStdlibApi::class) 78 | @Test 79 | fun testUpdatingAccountPreferences() { 80 | runBlocking { 81 | val instance = AccountPrefsRepository.getInstance(appContext) 82 | val originalAddress = instance.getAccount().privateKey.toHexString() 83 | 84 | class UpdatedAccountPreferences : AccountPreferences { 85 | override val fileName = "network-xyo-sdk-prefs-1" 86 | override val storagePath = "__xyo-client-sdk-1__" 87 | } 88 | 89 | class UpdatedPreviousHashShorePreferences : PreviousHashStorePreferences { 90 | override val fileName = "network-xyo-sdk-prefs-2" 91 | override val storagePath = "__xyo-client-sdk-1__" 92 | } 93 | 94 | class Settings: SettingsInterface { 95 | override val accountPreferences = UpdatedAccountPreferences() 96 | override val previousHashStorePreferences = UpdatedPreviousHashShorePreferences() 97 | } 98 | 99 | val updatedSettings = Settings() 100 | 101 | val refreshedInstance = 102 | AccountPrefsRepository.refresh(appContext, updatedSettings) 103 | 104 | // Test that accountPreferences are updated 105 | assertEquals( 106 | refreshedInstance.accountPreferences.fileName, 107 | updatedSettings.accountPreferences.fileName 108 | ) 109 | assertEquals( 110 | refreshedInstance.accountPreferences.storagePath, 111 | updatedSettings.accountPreferences.storagePath 112 | ) 113 | 114 | val refreshedAddress = refreshedInstance.getAccount().privateKey.toHexString() 115 | 116 | assert(originalAddress !== refreshedAddress) 117 | } 118 | } 119 | 120 | @OptIn(ExperimentalStdlibApi::class) 121 | @Test 122 | fun testAccountDeserialization() { 123 | runBlocking { 124 | val testAccount = Account.random() 125 | val instance = AccountPrefsRepository.getInstance(appContext) 126 | // Clear previously saved accounts 127 | instance.clearSavedAccountKey() 128 | // Serialize the test account 129 | instance.initializeAccount(testAccount) 130 | 131 | // Deserialize the test account 132 | val firstAccount = instance.getAccount() 133 | assertEquals(firstAccount.privateKey.toHexString(), testAccount.privateKey.toHexString()) 134 | 135 | // Sign with the test account 136 | val firstBw = BoundWitnessBuilder().signer(firstAccount).payloads(listOf( 137 | TestConstants.debugPayload 138 | )).build() 139 | val firstAddress = firstBw.addresses.first() 140 | 141 | // Deserialize the test account (Ideally we would refresh the singleton but in tests this seems to cause errors with multiple instances of the prefs DataStore) 142 | val secondInstance = AccountPrefsRepository.getInstance(appContext) 143 | val secondAccount = secondInstance.getAccount() 144 | 145 | // Sign with the test account 146 | val secondBw = BoundWitnessBuilder().signer(secondAccount).payloads(listOf( 147 | TestConstants.debugPayload 148 | )).build() 149 | val secondAddress = secondBw.addresses.first() 150 | 151 | // check that addresses have not changed and no errors occurred during signing 152 | assertEquals(firstAddress, secondAddress) 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/prefs/PreviousHashStorePrefsRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.prefs 2 | 3 | import android.content.Context 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import kotlinx.coroutines.runBlocking 6 | import network.xyo.client.lib.TestConstants 7 | import network.xyo.client.account.Account 8 | import network.xyo.client.lib.hexStringToByteArray 9 | import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.junit.jupiter.api.Assertions.assertNotNull 13 | 14 | class PreviousHashStorePrefsRepositoryTest { 15 | private lateinit var appContext: Context 16 | 17 | @Before 18 | fun useAppContext() { 19 | // Context of the app under test. 20 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 21 | } 22 | 23 | @Test 24 | fun testPreviousHashStorePersistence() { 25 | runBlocking { 26 | val prefsRepository = 27 | PreviousHashStorePrefsRepository.getInstance(appContext) 28 | prefsRepository.clearStore() 29 | 30 | val testAccountAddress = Account.random().address 31 | val testPreviousHash = hexStringToByteArray(TestConstants.debugPayloadHash) 32 | prefsRepository.setItem(testAccountAddress, testPreviousHash) 33 | 34 | val savedItem = prefsRepository.getItem(testAccountAddress) 35 | assertNotNull(savedItem) 36 | assert(savedItem!!.contentEquals(testPreviousHash)) 37 | 38 | prefsRepository.removeItem(testAccountAddress) 39 | val removedItem = prefsRepository.getItem(testAccountAddress) 40 | assert(removedItem == null) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/settings/XyoSdkTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.settings 2 | 3 | import android.content.Context 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import kotlinx.coroutines.runBlocking 7 | import network.xyo.client.payload.EventPayload 8 | import network.xyo.client.witness.XyoPanel 9 | import network.xyo.client.account.model.Account 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.junit.jupiter.api.Assertions.assertEquals 13 | import org.junit.jupiter.api.assertInstanceOf 14 | 15 | class XyoSdkTest { 16 | 17 | private lateinit var appContext: Context 18 | 19 | @Before 20 | fun resetSingleton() { 21 | XyoSdk.resetInstance() 22 | } 23 | 24 | @Before 25 | fun useAppContext() { 26 | // Context of the app under test. 27 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 28 | } 29 | 30 | @Test 31 | fun testDefaultSettings() { 32 | runBlocking { 33 | val instance = XyoSdk.getInstance(appContext) 34 | assertEquals(instance.settings.accountPreferences.fileName, DefaultXyoSdkSettings().accountPreferences.fileName) 35 | assertEquals(instance.settings.accountPreferences.storagePath, DefaultXyoSdkSettings().accountPreferences.storagePath) 36 | } 37 | } 38 | 39 | @Test 40 | fun testCustomSettings() { 41 | runBlocking { 42 | class UpdatedAccountPreferences : AccountPreferences { 43 | override val fileName = "network-xyo-sdk-prefs-1" 44 | override val storagePath = "__xyo-client-sdk-1__" 45 | } 46 | class UpdatedSettings: DefaultXyoSdkSettings() { 47 | override val accountPreferences = UpdatedAccountPreferences() 48 | } 49 | val updatedSettings = UpdatedSettings() 50 | val instance = XyoSdk.getInstance(appContext, updatedSettings) 51 | assertEquals(instance.settings.accountPreferences.fileName, updatedSettings.accountPreferences.fileName) 52 | assertEquals(instance.settings.accountPreferences.storagePath, updatedSettings.accountPreferences.storagePath) 53 | } 54 | } 55 | 56 | @Test 57 | fun testGetAccount() { 58 | runBlocking { 59 | val instance = XyoSdk.getInstance(appContext) 60 | val account = instance.getAccount(appContext) 61 | assertInstanceOf(account) 62 | } 63 | } 64 | 65 | @OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class) 66 | @Test 67 | fun testValidChainOnInit() { 68 | runBlocking { 69 | val instance = XyoSdk.getInstance(appContext) 70 | 71 | val testAccount = instance.getAccount(appContext) 72 | val panel = XyoPanel(appContext, testAccount, fun(_:Context): List { 73 | return listOf(EventPayload("test_event")) 74 | }) 75 | val result = panel.reportAsyncQuery() 76 | val bw = result.bw 77 | 78 | val result2 = panel.reportAsyncQuery() 79 | assert(result2.bw.previous_hashes.contains(bw.dataHash().toHexString())) 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/witness/LocationWitnessTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import androidx.test.core.app.ApplicationProvider 6 | import androidx.test.ext.junit.rules.ActivityScenarioRule 7 | import androidx.test.rule.GrantPermissionRule 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.ExperimentalCoroutinesApi 11 | import kotlinx.coroutines.launch 12 | import kotlinx.coroutines.runBlocking 13 | import network.xyo.client.account.Account 14 | import network.xyo.client.lib.TestConstants 15 | import network.xyo.client.witness.location.info.LocationActivity 16 | import network.xyo.client.witness.location.info.LocationPayload 17 | import network.xyo.client.witness.location.info.LocationPayloadRaw 18 | import network.xyo.client.witness.location.info.XyoLocationWitness 19 | import org.junit.Before 20 | import org.junit.Rule 21 | import org.junit.Test 22 | import org.junit.jupiter.api.assertInstanceOf 23 | 24 | class LocationWitnessTest { 25 | @get:Rule 26 | val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( 27 | Manifest.permission.ACCESS_FINE_LOCATION 28 | ) 29 | 30 | @get:Rule 31 | val activityRule = ActivityScenarioRule(LocationActivity::class.java) 32 | 33 | lateinit var appContext: Context 34 | 35 | @Before 36 | fun useContext() { 37 | this.appContext = ApplicationProvider.getApplicationContext() 38 | } 39 | 40 | @Test 41 | fun testObserve() { 42 | CoroutineScope(Dispatchers.Main).launch { 43 | val witness = XyoLocationWitness() 44 | val locationPayload = witness.observe(appContext)?.first() 45 | 46 | assertInstanceOf(locationPayload) 47 | assert(locationPayload.schema == LocationPayload.SCHEMA) 48 | assert(locationPayload.currentLocation !== null) 49 | assert(locationPayload.currentLocation?.coords?.latitude !== null) 50 | assert(locationPayload.currentLocation?.coords?.longitude !== null) 51 | 52 | val locationRawPayload = witness.observe(appContext)?.get(1) 53 | assertInstanceOf(locationRawPayload) 54 | assert(locationRawPayload.schema == LocationPayloadRaw.SCHEMA) 55 | } 56 | } 57 | 58 | @OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class) 59 | @Test 60 | fun testInsidePanel() { 61 | runBlocking { 62 | val panel = XyoPanel(appContext, Account.random(), arrayListOf(Pair("${TestConstants.nodeUrlBeta}/Archivist", null)), listOf(XyoLocationWitness())) 63 | val result = panel.reportAsyncQuery() 64 | result.payloads?.forEach{ payload -> 65 | val hash = payload.dataHash().toHexString() 66 | assert(result.bw.payload_hashes.contains(hash)) 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/witness/SystemInfoWitnessTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness 2 | 3 | import android.content.Context 4 | import androidx.test.core.app.ApplicationProvider 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.launch 8 | import network.xyo.client.witness.system.info.SystemInfoPayload 9 | import network.xyo.client.witness.system.info.XyoSystemInfoWitness 10 | import org.junit.Test 11 | import org.junit.jupiter.api.assertInstanceOf 12 | 13 | class SystemInfoWitnessTest { 14 | @Test 15 | fun testObserve() { 16 | // Get the application context 17 | val context = ApplicationProvider.getApplicationContext() 18 | 19 | CoroutineScope(Dispatchers.Main).launch { 20 | val witness = XyoSystemInfoWitness() 21 | val payload = witness.observe(context)?.first() 22 | 23 | assertInstanceOf(payload) 24 | assert(payload.os != null) 25 | assert(payload.device != null) 26 | assert(payload.network != null) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/witness/WitnessLocationHandlerTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness 2 | 3 | import network.xyo.client.witness.location.info.WitnessLocationHandler 4 | import android.Manifest 5 | import android.content.Context 6 | import androidx.test.ext.junit.rules.ActivityScenarioRule 7 | import androidx.test.platform.app.InstrumentationRegistry 8 | import androidx.test.rule.GrantPermissionRule 9 | import kotlinx.coroutines.runBlocking 10 | import network.xyo.client.lib.TestConstants 11 | import network.xyo.client.account.Account 12 | import network.xyo.client.boundwitness.BoundWitnessFields 13 | import network.xyo.client.boundwitness.BoundWitness 14 | import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository 15 | import network.xyo.client.witness.types.WitnessResult 16 | import network.xyo.client.payload.Payload 17 | import network.xyo.client.witness.location.info.LocationActivity 18 | import network.xyo.client.witness.location.info.LocationPayload 19 | import network.xyo.client.witness.location.info.LocationPayloadRaw 20 | import org.junit.Before 21 | import org.junit.Rule 22 | import org.junit.Test 23 | import org.junit.jupiter.api.assertInstanceOf 24 | 25 | class WitnessLocationHandlerTest { 26 | @get:Rule 27 | val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( 28 | Manifest.permission.ACCESS_FINE_LOCATION 29 | ) 30 | 31 | @get:Rule 32 | val activityRule = ActivityScenarioRule(LocationActivity::class.java) 33 | 34 | lateinit var appContext: Context 35 | 36 | @Before 37 | fun useAppContext() { 38 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 39 | } 40 | 41 | @Before 42 | fun useAccount() { 43 | Account.previousHashStore = PreviousHashStorePrefsRepository.getInstance(InstrumentationRegistry.getInstrumentation().targetContext) 44 | } 45 | 46 | private val apiDomainBeta = "${TestConstants.nodeUrlBeta}/Archivist" 47 | private val apiDomainLocal = "${TestConstants.nodeUrlLocal}/Archivist" 48 | 49 | @OptIn(ExperimentalStdlibApi::class) 50 | @Test 51 | fun testObserve() { 52 | runBlocking { 53 | var firstBw: BoundWitness? = null 54 | val result1 = WitnessLocationHandler().witness(appContext.applicationContext, arrayListOf(Pair(apiDomainBeta, null))) 55 | when (result1) { 56 | is WitnessResult.Success> -> { 57 | firstBw = result1.data.first 58 | assertInstanceOf(firstBw) 59 | assertInstanceOf(result1.data.second) 60 | assertInstanceOf(result1.data.third) 61 | } 62 | is WitnessResult.Error -> { 63 | assert(result1.exception.size > 0) 64 | } 65 | } 66 | 67 | var secondBw: BoundWitness? = null 68 | val result2 = WitnessLocationHandler().witness(appContext.applicationContext, arrayListOf(Pair(apiDomainBeta, null))) 69 | when (result2) { 70 | is WitnessResult.Success> -> { 71 | secondBw = result2.data.first 72 | assertInstanceOf(secondBw) 73 | assertInstanceOf(result2.data.second) 74 | assertInstanceOf(result2.data.third) 75 | 76 | } 77 | is WitnessResult.Error -> { 78 | assert(result2.exception.size > 0) 79 | } 80 | } 81 | 82 | val firstBwHash = firstBw!!.dataHash().toHexString() 83 | assert(secondBw!!.previous_hashes.size == 1) 84 | assert(secondBw.previous_hashes.first() == firstBwHash) 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /sdk/src/androidTest/java/network/xyo/client/witness/XyoPanelTest.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import androidx.test.ext.junit.rules.ActivityScenarioRule 6 | import androidx.test.platform.app.InstrumentationRegistry 7 | import androidx.test.rule.GrantPermissionRule 8 | import kotlinx.coroutines.ExperimentalCoroutinesApi 9 | import kotlinx.coroutines.runBlocking 10 | import network.xyo.client.lib.BasicPayload 11 | import network.xyo.client.lib.TestConstants 12 | import network.xyo.client.payload.EventPayload 13 | import network.xyo.client.account.Account 14 | import network.xyo.client.boundwitness.BoundWitness 15 | import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository 16 | import network.xyo.client.payload.Payload 17 | import network.xyo.client.witness.location.info.LocationActivity 18 | import network.xyo.client.witness.location.info.XyoLocationWitness 19 | import network.xyo.client.witness.system.info.SystemInfoPayload 20 | import network.xyo.client.witness.system.info.XyoSystemInfoWitness 21 | import org.junit.Before 22 | import org.junit.Rule 23 | import org.junit.Test 24 | import org.junit.jupiter.api.Assertions.* 25 | import org.junit.jupiter.api.assertInstanceOf 26 | 27 | class XyoPanelTest { 28 | @Rule 29 | @JvmField 30 | val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( 31 | Manifest.permission.INTERNET, 32 | Manifest.permission.ACCESS_WIFI_STATE, 33 | Manifest.permission.ACCESS_COARSE_LOCATION, 34 | Manifest.permission.ACCESS_FINE_LOCATION 35 | ) 36 | 37 | @get:Rule 38 | val activityRule = ActivityScenarioRule(LocationActivity::class.java) 39 | 40 | lateinit var appContext: Context 41 | 42 | private val apiDomainBeta = "${TestConstants.nodeUrlBeta}/Archivist" 43 | private val apiDomainLocal = "${TestConstants.nodeUrlLocal}/Archivist" 44 | 45 | @Before 46 | fun useAppContext() { 47 | // Context of the app under test. 48 | this.appContext = InstrumentationRegistry.getInstrumentation().targetContext 49 | } 50 | 51 | @Before 52 | fun useAccount() { 53 | Account.previousHashStore = PreviousHashStorePrefsRepository.getInstance(InstrumentationRegistry.getInstrumentation().targetContext) 54 | } 55 | 56 | private fun testCreatePanel(nodeUrl: String) { 57 | val witness = XyoWitness(Account.random()) 58 | val panel = XyoPanel(appContext, Account.random(), arrayListOf(Pair(nodeUrl, Account.random())), listOf(witness)) 59 | assertNotNull(panel) 60 | } 61 | 62 | @Test 63 | fun testCreatePanelBeta() { 64 | testCreatePanel(apiDomainBeta) 65 | } 66 | 67 | @Test 68 | fun testCreatePanelLocal() { 69 | testCreatePanel(apiDomainLocal) 70 | } 71 | 72 | @OptIn(ExperimentalCoroutinesApi::class) 73 | fun testPanelReport(nodeUrl: String) { 74 | runBlocking { 75 | val witnessAccount = Account.fromPrivateKey("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08") 76 | val witness2Account = Account.fromPrivateKey("5a95531488b4d0d3645aea49678297ae9e2034879ce0389b80eb788e8b533592") 77 | val witness = XyoWitness(witnessAccount, fun(_: Context): List { 78 | return listOf(BasicPayload()) 79 | }) 80 | val panel = XyoPanel(appContext, Account.random(), arrayListOf(Pair(nodeUrl, Account.random())), listOf(witness, XyoSystemInfoWitness(witness2Account), XyoLocationWitness())) 81 | val result = panel.reportAsyncQuery() 82 | if (result.apiResults === null) throw NullPointerException("apiResults should not be null") 83 | assert(result.payloads?.size == 4) 84 | result.apiResults?.forEach { 85 | assertEquals(it.errors, null) 86 | } 87 | } 88 | } 89 | 90 | @Test 91 | fun testPanelReportBeta() { 92 | testPanelReport(apiDomainBeta) 93 | } 94 | 95 | /* 96 | @Test 97 | fun testPanelReportLocal() { 98 | testPanelReport(apiDomainLocal) 99 | } 100 | */ 101 | 102 | @OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class) 103 | @Test 104 | fun testSimplePanelReport() { 105 | runBlocking { 106 | val testAccount = Account.random() 107 | val panel = XyoPanel(appContext, testAccount, fun(_:Context): List { 108 | return listOf(EventPayload("test_event")) 109 | }) 110 | val result = panel.reportAsyncQuery() 111 | if (result.apiResults === null) throw NullPointerException("apiResults should not be null") 112 | result.apiResults?.forEach { assertEquals(it.errors, null) } 113 | val bw = result.bw 114 | 115 | val result2 = panel.reportAsyncQuery() 116 | assert(result2.bw.previous_hashes.contains(bw.dataHash().toHexString())) 117 | } 118 | } 119 | 120 | @OptIn(ExperimentalCoroutinesApi::class) 121 | @Test 122 | fun testReportEvent() { 123 | runBlocking { 124 | val panel = XyoPanel(appContext, Account.random(), arrayListOf(Pair(apiDomainBeta, Account.random())), listOf(XyoSystemInfoWitness())) 125 | val result = panel.reportAsyncQuery() 126 | if (result.apiResults === null) throw Error() 127 | result.apiResults?.forEach { assertEquals(it.errors, null) } 128 | } 129 | } 130 | 131 | @OptIn(ExperimentalCoroutinesApi::class) 132 | @Test 133 | fun testMissingNodes() { 134 | runBlocking { 135 | val panel = XyoPanel(appContext, Account.random(), arrayListOf(), listOf(XyoSystemInfoWitness())) 136 | val results = panel.reportAsyncQuery() 137 | assertInstanceOf(results.bw) 138 | assertInstanceOf(results.payloads?.first()) 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /sdk/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/Account.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import network.xyo.client.account.model.Account 6 | import network.xyo.client.account.model.AccountStatic 7 | import network.xyo.client.account.model.PreviousHashStore 8 | import network.xyo.client.lib.Secp256k1CurveConstants 9 | import network.xyo.client.lib.hexStringToByteArray 10 | import network.xyo.client.lib.publicKeyToAddress 11 | import network.xyo.client.lib.recoverPublicKey 12 | import org.spongycastle.jcajce.provider.digest.Keccak 13 | import tech.figure.hdwallet.ec.PrivateKey 14 | import tech.figure.hdwallet.ec.extensions.toBytesPadded 15 | import tech.figure.hdwallet.ec.secp256k1Curve 16 | import tech.figure.hdwallet.signer.BCECSigner 17 | import java.math.BigInteger 18 | import java.security.SecureRandom 19 | 20 | open class Account private constructor (private val _privateKey: PrivateKey, private var _previousHash: ByteArray? = null): 21 | Account { 22 | 23 | constructor(privateKey: ByteArray, previousHash: ByteArray? = null) : this(PrivateKey.fromBytes(privateKey, secp256k1Curve), previousHash) 24 | constructor(privateKey: BigInteger, previousHash: ByteArray? = null) : this(privateKey.toByteArray(), previousHash) 25 | 26 | private val _address = addressFromUncompressedPublicKey(publicKeyUncompressed) 27 | 28 | final override val address: ByteArray 29 | get() = _address 30 | final override val previousHash: ByteArray? 31 | get() = _previousHash 32 | final override val privateKey: ByteArray 33 | get() = _privateKey.key.toBytesPadded(32) 34 | final override val publicKey: ByteArray 35 | get() = _privateKey.toPublicKey().compressed() 36 | final override val publicKeyUncompressed: ByteArray 37 | get() = _privateKey.toPublicKey().key.toBytesPadded(64) 38 | 39 | override suspend fun sign(hash: ByteArray): ByteArray { 40 | val result = BCECSigner().sign(_privateKey, hash) 41 | _previousHash = hash 42 | previousHashStore?.setItem(address, hash) 43 | return result.encodeAsBTC().toByteArray() 44 | } 45 | 46 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 47 | override fun verify(msg: ByteArray, signature: ByteArray): Boolean { 48 | val recoveredPublicKey = recoverPublicKey(msg, signature) ?: return false 49 | val recoveredAddress = publicKeyToAddress(recoveredPublicKey) 50 | return recoveredAddress.contentEquals(address) 51 | } 52 | 53 | companion object: AccountStatic { 54 | override var previousHashStore: PreviousHashStore? = null 55 | 56 | override fun fromPrivateKey(key: ByteArray): Account { 57 | return Account(key) 58 | } 59 | 60 | override fun fromPrivateKey(key: String): Account { 61 | return fromPrivateKey(hexStringToByteArray(key)) 62 | } 63 | 64 | override fun random(): Account { 65 | return fromPrivateKey(generatePrivateKeyBytes()) 66 | } 67 | 68 | fun addressFromUncompressedPublicKey(key: ByteArray): ByteArray { 69 | assert(key.size == 64, ) { "Invalid Key Length" } 70 | val publicKeyHash = toKeccak(key) 71 | return publicKeyHash.copyOfRange(12, publicKeyHash.size) 72 | } 73 | 74 | private fun toKeccak(bytes: ByteArray): ByteArray { 75 | val keccak = Keccak.Digest256() 76 | keccak.update(bytes) 77 | return keccak.digest() 78 | } 79 | 80 | private fun generatePrivateKeyBytes(): ByteArray { 81 | val secureRandom = SecureRandom() 82 | val private = ByteArray(32) 83 | secureRandom.nextBytes(private) 84 | //this line is to make sure the key is below n 85 | while(BigInteger(private) > Secp256k1CurveConstants.n) { 86 | secureRandom.nextBytes(private) 87 | } 88 | return private 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/Wallet.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import network.xyo.client.account.model.PreviousHashStore 6 | import network.xyo.client.account.model.Wallet 7 | import network.xyo.client.account.model.WalletStatic 8 | import network.xyo.client.lib.hexStringToByteArray 9 | import tech.figure.hdwallet.bip32.ExtKey 10 | import tech.figure.hdwallet.bip32.toRootKey 11 | import tech.figure.hdwallet.bip39.DeterministicSeed 12 | import tech.figure.hdwallet.bip39.MnemonicWords 13 | import tech.figure.hdwallet.ec.extensions.toBytesPadded 14 | 15 | @RequiresApi(Build.VERSION_CODES.M) 16 | open class Wallet(private val _extKey: ExtKey, previousHash: ByteArray? = null): 17 | Account(_extKey.keyPair.privateKey.key.toBytesPadded(32), previousHash), 18 | Wallet { 19 | 20 | override fun derivePath(path: String): Wallet { 21 | return Wallet(_extKey.childKey(path)) 22 | } 23 | 24 | companion object: WalletStatic { 25 | 26 | val defaultPath = "m/44'/60'/0'/0/0" 27 | 28 | override var previousHashStore: PreviousHashStore? = null 29 | 30 | override fun fromExtendedKey(key: ExtKey): Wallet { 31 | return Wallet(key) 32 | } 33 | 34 | override fun fromMnemonic(mnemonic: String, path: String?): Wallet { 35 | return fromMnemonic(MnemonicWords.of(mnemonic), path) 36 | } 37 | 38 | override fun fromMnemonic(mnemonic: MnemonicWords, path: String?): Wallet { 39 | val root = fromSeed(mnemonic.toSeed("".toCharArray())) 40 | return if (path === null) { 41 | root.derivePath(defaultPath) 42 | } else { 43 | root.derivePath(path) 44 | } 45 | } 46 | 47 | override fun fromSeed(seed: String): Wallet { 48 | return fromSeed(hexStringToByteArray(seed)) 49 | } 50 | 51 | override fun fromSeed(seed: ByteArray): Wallet { 52 | return fromSeed(DeterministicSeed.fromBytes(seed)) 53 | } 54 | 55 | override fun fromSeed(seed: DeterministicSeed): Wallet { 56 | val key = seed.toRootKey() 57 | return Wallet(key) 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/model/Account.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account.model 2 | 3 | interface Account: PrivateKey { 4 | val previousHash: ByteArray? 5 | val privateKey: ByteArray 6 | val publicKey: ByteArray 7 | val publicKeyUncompressed: ByteArray 8 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/model/AccountStatic.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account.model 2 | 3 | interface AccountStatic { 4 | var previousHashStore: PreviousHashStore? 5 | fun fromPrivateKey(key: ByteArray): T 6 | fun fromPrivateKey(key: String): T 7 | fun random(): T 8 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/model/PreviousHashStore.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account.model 2 | 3 | import network.xyo.client.types.Address 4 | import network.xyo.client.types.Hash 5 | 6 | interface PreviousHashStore { 7 | suspend fun getItem(address: Address): Hash? 8 | suspend fun removeItem(address: Address) 9 | suspend fun setItem(address: Address, previousHash: Hash) 10 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/model/PrivateKey.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account.model 2 | 3 | interface PrivateKey: PublicKey { 4 | suspend fun sign(hash: ByteArray): ByteArray 5 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/model/PublicKey.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account.model 2 | 3 | interface PublicKey { 4 | val address: ByteArray 5 | fun verify(msg: ByteArray, signature: ByteArray): Boolean 6 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/model/Wallet.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account.model 2 | 3 | interface Wallet: Account { 4 | fun derivePath(path: String): Wallet 5 | } 6 | -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/account/model/WalletStatic.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.account.model 2 | 3 | import tech.figure.hdwallet.bip32.ExtKey 4 | import tech.figure.hdwallet.bip39.DeterministicSeed 5 | import tech.figure.hdwallet.bip39.MnemonicWords 6 | 7 | interface WalletStatic { 8 | var previousHashStore: PreviousHashStore? 9 | fun fromExtendedKey(key: ExtKey): T 10 | fun fromMnemonic(mnemonic: MnemonicWords, path: String? = null): T 11 | fun fromMnemonic(mnemonic: String, path: String? = null): T 12 | fun fromSeed(seed: String): T 13 | fun fromSeed(seed: ByteArray): T 14 | fun fromSeed(seed: DeterministicSeed): T 15 | } 16 | -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/archivist/wrapper/ArchivistGetQueryPayload.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.archivist.wrapper 2 | 3 | import com.squareup.moshi.JsonClass 4 | import network.xyo.client.payload.Payload 5 | 6 | @JsonClass(generateAdapter = true) 7 | open class ArchivistGetQueryPayload(val hashes: List): Payload(SCHEMA) { 8 | companion object { 9 | const val SCHEMA = "network.xyo.query.archivist.get" 10 | } 11 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/archivist/wrapper/ArchivistInsertQueryPayload.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.archivist.wrapper 2 | 3 | import com.squareup.moshi.JsonClass 4 | import network.xyo.client.payload.Payload 5 | 6 | @JsonClass(generateAdapter = true) 7 | class ArchivistInsertQueryPayload : Payload(SCHEMA) { 8 | companion object { 9 | const val SCHEMA = "network.xyo.query.archivist.insert" 10 | } 11 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/archivist/wrapper/ArchivistWrapper.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.archivist.wrapper 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import network.xyo.client.node.client.NodeClient 7 | import network.xyo.client.node.client.PostQueryResult 8 | import network.xyo.client.payload.Payload 9 | 10 | open class ArchivistWrapper(private val nodeClient: NodeClient) { 11 | @OptIn(ExperimentalCoroutinesApi::class) 12 | @RequiresApi(Build.VERSION_CODES.M) 13 | suspend fun get(hashes: List): PostQueryResult { 14 | val query = ArchivistGetQueryPayload(hashes) 15 | return nodeClient.query(query, null) 16 | } 17 | 18 | @OptIn(ExperimentalCoroutinesApi::class) 19 | @RequiresApi(Build.VERSION_CODES.M) 20 | suspend fun insert(payloads: List): PostQueryResult { 21 | val query = ArchivistInsertQueryPayload() 22 | return nodeClient.query(query, payloads) 23 | } 24 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/BoundWitness.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | import network.xyo.client.boundwitness.model.BoundWitnessMeta 6 | 7 | @JsonClass(generateAdapter = true) 8 | open class BoundWitness(client: String? = "android", signatures: List = emptyList()) : BoundWitnessMeta, 9 | BoundWitnessFields() { 10 | 11 | @Json(name = "\$signatures") 12 | final override var __signatures = signatures 13 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/BoundWitnessBuilder.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import network.xyo.client.lib.JsonSerializable 6 | import network.xyo.client.account.model.Account 7 | import network.xyo.client.payload.Payload 8 | import network.xyo.client.payload.XyoValidationException 9 | import network.xyo.client.types.Hash 10 | 11 | @OptIn(ExperimentalStdlibApi::class) 12 | @RequiresApi(Build.VERSION_CODES.M) 13 | open class BoundWitnessBuilder { 14 | protected var _signers = mutableListOf() 15 | protected var _payload_hashes = mutableListOf() 16 | protected var _payload_schemas = mutableListOf() 17 | protected var _payloads = mutableListOf() 18 | protected open var bw: BoundWitness = BoundWitness() 19 | 20 | var _timestamp: Long? = null 21 | 22 | @OptIn(ExperimentalStdlibApi::class) 23 | val addresses: List 24 | get() = _signers.map { witness -> witness.address.toHexString() } 25 | 26 | open fun signers(signers: List): BoundWitnessBuilder { 27 | signers.forEach { signer -> signer(signer) } 28 | return this 29 | } 30 | 31 | open fun signer(signer: Account): BoundWitnessBuilder { 32 | _signers.add(signer) 33 | return this 34 | } 35 | 36 | @OptIn(ExperimentalStdlibApi::class) 37 | private fun setPreviousHashes() { 38 | bw.previous_hashes = _signers.map { 39 | signer -> signer.previousHash?.toHexString() 40 | } 41 | } 42 | 43 | @Throws(XyoValidationException::class) 44 | fun payload(schema: String, payload: T): BoundWitnessBuilder { 45 | payload.validate() 46 | _payloads.add(payload) 47 | _payload_hashes.add(payload.hash().toHexString()) 48 | _payload_schemas.add(schema) 49 | return this 50 | } 51 | 52 | @Throws(XyoValidationException::class) 53 | fun payloads(payloads: List): BoundWitnessBuilder { 54 | payloads.forEach { 55 | payload(it.schema, it) 56 | } 57 | return this 58 | } 59 | 60 | private suspend fun sign(hash: Hash): List { 61 | return _signers.map { 62 | val sig = JsonSerializable.bytesToHex(it.sign(hash)) 63 | sig 64 | } 65 | } 66 | 67 | @OptIn(ExperimentalStdlibApi::class) 68 | protected suspend fun constructFields() { 69 | // update json class properties 70 | bw.payload_hashes = _payload_hashes 71 | bw.payload_schemas = _payload_schemas 72 | bw.previous_hashes = _signers.map {account -> account.previousHash?.toHexString()} 73 | bw.addresses = addresses 74 | 75 | // construct fields involved in hashing 76 | constructHashableFieldsFields() 77 | } 78 | 79 | private suspend fun constructHashableFieldsFields() { 80 | // Note: Once fields are hashed, do not update class properties that are expected 81 | // in the serialized version of the bw because they will invalidate the hash 82 | setPreviousHashes() 83 | val dataHash = bw.dataHash() 84 | bw.__signatures = this.sign(dataHash) 85 | } 86 | 87 | open suspend fun build(): BoundWitness { 88 | return bw.let{ 89 | // update fields 90 | constructFields() 91 | it 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/BoundWitnessFields.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness 2 | 3 | import com.squareup.moshi.JsonClass 4 | import network.xyo.client.boundwitness.model.BoundWitnessFields 5 | import network.xyo.client.payload.Payload 6 | 7 | @JsonClass(generateAdapter = true) 8 | open class BoundWitnessFields(): BoundWitnessFields, Payload(SCHEMA) { 9 | final override var addresses = emptyList() 10 | final override var payload_hashes = emptyList() 11 | final override var payload_schemas = emptyList() 12 | final override var previous_hashes = emptyList() 13 | final override var timestamp: Long? = null 14 | 15 | constructor ( 16 | addresses: List, 17 | previous_hashes: List, 18 | payload_hashes: List, 19 | payload_schemas: List, 20 | timestamp: Long? = null, 21 | ) : this() { 22 | this.addresses = addresses 23 | this.previous_hashes = previous_hashes 24 | this.payload_hashes = payload_hashes 25 | this.payload_schemas = payload_schemas 26 | this.timestamp = timestamp 27 | } 28 | 29 | companion object { 30 | const val SCHEMA = "network.xyo.boundwitness" 31 | } 32 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/QueryBoundWitness.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | @JsonClass(generateAdapter = true) 6 | open class QueryBoundWitness: BoundWitness() { 7 | var query: String? = null 8 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/QueryBoundWitnessBuilder.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import network.xyo.client.account.model.Account 6 | import network.xyo.client.payload.Payload 7 | import network.xyo.client.types.Hash 8 | 9 | 10 | @OptIn(ExperimentalStdlibApi::class) 11 | @RequiresApi(Build.VERSION_CODES.M) 12 | class QueryBoundWitnessBuilder : BoundWitnessBuilder() { 13 | private lateinit var queryHash: Hash 14 | 15 | fun query(query: Payload): QueryBoundWitnessBuilder { 16 | this.queryHash = query.dataHash() 17 | this.payload(query.schema, query) 18 | return this 19 | } 20 | 21 | override fun signer(signer: Account): QueryBoundWitnessBuilder { 22 | super.signer(signer) 23 | return this 24 | } 25 | 26 | override fun signers(signers: List): QueryBoundWitnessBuilder { 27 | super.signers(signers) 28 | return this 29 | } 30 | 31 | override suspend fun build(): QueryBoundWitness { 32 | bw = QueryBoundWitness() 33 | // override to support additional properties for query bound witnesses 34 | return bw.let { 35 | val qbw = it as QueryBoundWitness 36 | qbw.query = this.queryHash.toHexString() 37 | constructFields() 38 | it 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/QueryBoundWitnessFields.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | @JsonClass(generateAdapter = true) 6 | open class QueryBoundWitnessFields( 7 | addresses: List, 8 | previous_hashes: List, 9 | payload_hashes: List, 10 | payload_schemas: List, 11 | val query: String, 12 | timestamp: Long?, 13 | ): BoundWitnessFields(addresses, previous_hashes, payload_hashes, payload_schemas, timestamp) -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/model/BoundWitnessFields.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness.model 2 | 3 | import network.xyo.client.payload.model.Payload 4 | 5 | interface BoundWitnessFields : Payload { 6 | var addresses: List 7 | var payload_hashes: List 8 | var payload_schemas: List 9 | var previous_hashes: List 10 | // Note: Long is a higher precision type than JavaScript's Number type but it is the default type from 11 | // Kotlin's System.currentTimeMillis(). 12 | var timestamp: Long? 13 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/boundwitness/model/BoundWitnessMeta.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.boundwitness.model 2 | 3 | import java.io.Serializable 4 | 5 | interface BoundWitnessMeta: Serializable { 6 | var __signatures: List 7 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/datastore/accounts/AccountPrefsDataStoreSerializer.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.datastore.accounts 2 | 3 | import androidx.datastore.core.CorruptionException 4 | import androidx.datastore.core.Serializer 5 | import com.google.protobuf.InvalidProtocolBufferException 6 | import network.xyo.data.AccountPrefsDataStoreProtos.AccountPrefsDataStore 7 | import java.io.InputStream 8 | import java.io.OutputStream 9 | 10 | object AccountPrefsDataStoreSerializer : Serializer { 11 | override val defaultValue: AccountPrefsDataStore = AccountPrefsDataStore.getDefaultInstance() 12 | override suspend fun readFrom(input: InputStream): AccountPrefsDataStore { 13 | try { 14 | return AccountPrefsDataStore.parseFrom(input) 15 | } catch (exception: InvalidProtocolBufferException) { 16 | throw CorruptionException("Cannot read proto.", exception) 17 | } 18 | } 19 | 20 | override suspend fun writeTo(t: AccountPrefsDataStore, output: OutputStream) = t.writeTo(output) 21 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/datastore/accounts/AccountPrefsRepository.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.datastore.accounts 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import android.util.Log 6 | import androidx.annotation.RequiresApi 7 | import androidx.datastore.core.DataStore 8 | import network.xyo.data.AccountPrefsDataStoreProtos.AccountPrefsDataStore 9 | import kotlinx.coroutines.flow.first 10 | import kotlinx.coroutines.launch 11 | import network.xyo.client.account.Account 12 | import network.xyo.client.settings.AccountPreferences 13 | import network.xyo.client.settings.SettingsInterface 14 | import network.xyo.client.settings.defaultXyoSdkSettings 15 | import network.xyo.client.lib.xyoScope 16 | 17 | 18 | class AccountPrefsRepository(context: Context, settings: SettingsInterface = defaultXyoSdkSettings) { 19 | private val appContext = context.applicationContext 20 | val accountPreferences: AccountPreferences = settings.accountPreferences 21 | 22 | // This should set the proper paths for the prefs datastore each time the the class is instantiated 23 | @Volatile 24 | private var accountPrefsDataStore: DataStore = appContext.xyoAccountDataStore( 25 | accountPreferences.fileName, accountPreferences.storagePath 26 | ) 27 | 28 | @RequiresApi(Build.VERSION_CODES.M) 29 | suspend fun getAccount(): network.xyo.client.account.model.Account { 30 | val saveKeyHex = getAccountKey() 31 | return Account.fromPrivateKey(saveKeyHex) 32 | } 33 | 34 | @OptIn(ExperimentalStdlibApi::class) 35 | @RequiresApi(Build.VERSION_CODES.M) 36 | suspend fun initializeAccount(account: network.xyo.client.account.model.Account): network.xyo.client.account.model.Account? { 37 | var updatedKey: String? = null 38 | val job = xyoScope.launch { 39 | val savedKey = accountPrefsDataStore.data.first().accountKey 40 | if (savedKey.isNullOrEmpty()) { 41 | // no saved key so save the passed in one 42 | updatedKey = null 43 | setAccountKey(account.privateKey.toHexString()) 44 | } else { 45 | updatedKey = null 46 | Log.w("xyoClient", "Key already exists. Clear it first before initializing prefs with new account") 47 | } 48 | } 49 | job.join() 50 | return if (updatedKey !== null) { 51 | account 52 | } else { 53 | null 54 | } 55 | } 56 | 57 | @OptIn(ExperimentalStdlibApi::class) 58 | @RequiresApi(Build.VERSION_CODES.M) 59 | private suspend fun getAccountKey(): String { 60 | val savedKey = accountPrefsDataStore.data.first().accountKey 61 | return if (savedKey.isEmpty()) { 62 | val newAccount: network.xyo.client.account.model.Account = Account.random() 63 | setAccountKey(newAccount.privateKey.toHexString()) 64 | newAccount.privateKey.toHexString() 65 | } else { 66 | return savedKey 67 | } 68 | } 69 | 70 | private suspend fun setAccountKey(accountKey: String): DataStore { 71 | val job = xyoScope.launch { 72 | this@AccountPrefsRepository.accountPrefsDataStore.updateData { currentPrefs -> 73 | currentPrefs.toBuilder() 74 | .setAccountKey(accountKey) 75 | .build() 76 | } 77 | } 78 | job.join() 79 | return accountPrefsDataStore 80 | } 81 | 82 | suspend fun clearSavedAccountKey(): DataStore { 83 | val job = xyoScope.launch { 84 | this@AccountPrefsRepository.accountPrefsDataStore.updateData { currentPrefs -> 85 | currentPrefs.toBuilder() 86 | .setAccountKey("") 87 | .build() 88 | } 89 | } 90 | job.join() 91 | return accountPrefsDataStore 92 | } 93 | 94 | companion object { 95 | @Volatile 96 | private var INSTANCE: AccountPrefsRepository? = null 97 | 98 | fun getInstance(context: Context, settings: SettingsInterface = defaultXyoSdkSettings): AccountPrefsRepository { 99 | val newInstance = INSTANCE ?: synchronized(this) { 100 | INSTANCE ?: AccountPrefsRepository(context.applicationContext, settings).also { INSTANCE = it } 101 | } 102 | return newInstance 103 | } 104 | 105 | fun refresh(context: Context, settings: SettingsInterface = defaultXyoSdkSettings): AccountPrefsRepository { 106 | synchronized(this) { 107 | INSTANCE = AccountPrefsRepository(context.applicationContext, settings) 108 | return INSTANCE!! 109 | } 110 | } 111 | 112 | fun resetInstance() { 113 | synchronized(this) { 114 | INSTANCE = null 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/datastore/accounts/Context.xyoAccountDataStore.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.datastore.accounts 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.core.MultiProcessDataStoreFactory 6 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import network.xyo.client.settings.defaultXyoSdkSettings 10 | import network.xyo.data.AccountPrefsDataStoreProtos.AccountPrefsDataStore 11 | import java.io.File 12 | 13 | fun Context.xyoAccountDataStore(name: String?, path: String?): DataStore { 14 | val resolvedName = name ?: defaultXyoSdkSettings.accountPreferences.fileName 15 | val resolvedPath = path ?: defaultXyoSdkSettings.accountPreferences.storagePath 16 | 17 | val dataStoreFile = File(filesDir, "$resolvedPath/$resolvedName") 18 | 19 | return MultiProcessDataStoreFactory.create( 20 | serializer = AccountPrefsDataStoreSerializer, 21 | produceFile = { dataStoreFile }, 22 | corruptionHandler = ReplaceFileCorruptionHandler( 23 | produceNewData = { AccountPrefsDataStore.getDefaultInstance() } 24 | ), 25 | scope = CoroutineScope(Dispatchers.IO) 26 | ) 27 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/datastore/previous_hash_store/MemoryPreviousHashStore.kt: -------------------------------------------------------------------------------- 1 | import network.xyo.client.account.model.PreviousHashStore 2 | import network.xyo.client.types.Address 3 | import network.xyo.client.types.Hash 4 | 5 | class MemoryPreviousHashStore : PreviousHashStore { 6 | private val store: MutableMap = mutableMapOf() 7 | 8 | /** 9 | * Retrieves the Hash associated with the given Address. 10 | * 11 | * @param address The address for which to retrieve the Hash. 12 | * @return The corresponding Hash if it exists, or null otherwise. 13 | */ 14 | override suspend fun getItem(address: Address): Hash? { 15 | return store[address] 16 | } 17 | 18 | /** 19 | * Removes the Hash associated with the given Address. 20 | * 21 | * @param address The address for which to remove the Hash. 22 | */ 23 | override suspend fun removeItem(address: Address) { 24 | store.remove(address) 25 | } 26 | 27 | /** 28 | * Associates the given Hash with the specified Address. 29 | * 30 | * @param address The address to associate with the Hash. 31 | * @param previousHash The Hash to associate with the address. 32 | */ 33 | override suspend fun setItem(address: Address, previousHash: Hash) { 34 | store[address] = previousHash 35 | } 36 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/datastore/previous_hash_store/PreviousHashDataStore.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.datastore.previous_hash_store 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.core.MultiProcessDataStoreFactory 6 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import network.xyo.client.datastore.accounts.AccountPrefsDataStoreSerializer 10 | import network.xyo.client.settings.defaultXyoSdkSettings 11 | import network.xyo.data.AccountPrefsDataStoreProtos.AccountPrefsDataStore 12 | import network.xyo.data.PreviousHashPrefsDataStoreProtos 13 | import network.xyo.data.PreviousHashPrefsDataStoreProtos.PreviousHashPrefsDataStore 14 | import java.io.File 15 | 16 | fun Context.xyoPreviousHashDataStore(name: String?, path: String?): DataStore { 17 | val resolvedName = name ?: defaultXyoSdkSettings.previousHashStorePreferences.fileName 18 | val resolvedPath = path ?: defaultXyoSdkSettings.previousHashStorePreferences.storagePath 19 | 20 | val dataStoreFile = File(filesDir, "$resolvedPath/$resolvedName") 21 | 22 | return MultiProcessDataStoreFactory.create( 23 | serializer = PreviousHashStorePrefsDataStoreSerializer, 24 | produceFile = { dataStoreFile }, 25 | corruptionHandler = ReplaceFileCorruptionHandler( 26 | produceNewData = { PreviousHashPrefsDataStore.getDefaultInstance() } 27 | ), 28 | scope = CoroutineScope(Dispatchers.IO) 29 | ) 30 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/datastore/previous_hash_store/PreviousHashStorePrefsDataStoreSerializer.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.datastore.previous_hash_store 2 | 3 | import androidx.datastore.core.CorruptionException 4 | import androidx.datastore.core.Serializer 5 | import com.google.protobuf.InvalidProtocolBufferException 6 | import network.xyo.data.PreviousHashPrefsDataStoreProtos.PreviousHashPrefsDataStore 7 | import java.io.InputStream 8 | import java.io.OutputStream 9 | 10 | object PreviousHashStorePrefsDataStoreSerializer : Serializer { 11 | override val defaultValue: PreviousHashPrefsDataStore = PreviousHashPrefsDataStore.getDefaultInstance() 12 | override suspend fun readFrom(input: InputStream): PreviousHashPrefsDataStore { 13 | try { 14 | return PreviousHashPrefsDataStore.parseFrom(input) 15 | } catch (exception: InvalidProtocolBufferException) { 16 | throw CorruptionException("Cannot read proto.", exception) 17 | } 18 | } 19 | 20 | override suspend fun writeTo(t: PreviousHashPrefsDataStore, output: OutputStream) = t.writeTo(output) 21 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/datastore/previous_hash_store/PreviousHashStorePrefsRepository.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.datastore.previous_hash_store 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import kotlinx.coroutines.flow.first 6 | import kotlinx.coroutines.launch 7 | import network.xyo.client.lib.hexStringToByteArray 8 | import network.xyo.client.account.model.PreviousHashStore 9 | import network.xyo.client.settings.PreviousHashStorePreferences 10 | import network.xyo.client.settings.SettingsInterface 11 | import network.xyo.client.settings.defaultXyoSdkSettings 12 | import network.xyo.client.lib.xyoScope 13 | import network.xyo.data.PreviousHashPrefsDataStoreProtos.PreviousHashPrefsDataStore 14 | 15 | 16 | class PreviousHashStorePrefsRepository( 17 | context: Context, 18 | settings: SettingsInterface = defaultXyoSdkSettings 19 | ): PreviousHashStore { 20 | private val appContext = context.applicationContext 21 | private val previousHashStorePreferences: PreviousHashStorePreferences = settings.previousHashStorePreferences 22 | 23 | // This should set the proper paths for the prefs datastore each time the the class is instantiated 24 | @Volatile 25 | private var previousHashStorePrefsDataStore: DataStore = appContext.xyoPreviousHashDataStore( 26 | previousHashStorePreferences.fileName, previousHashStorePreferences.storagePath 27 | ) 28 | 29 | @OptIn(ExperimentalStdlibApi::class) 30 | override suspend fun getItem(address: ByteArray): ByteArray? { 31 | var savedPreviousHash: String? = null 32 | val job = xyoScope.launch { 33 | val savedPreviousHashStore = previousHashStorePrefsDataStore.data.first().addressToHashMap 34 | val searchKey = address.toHexString() 35 | savedPreviousHash = savedPreviousHashStore[searchKey] 36 | } 37 | job.join() 38 | return if (savedPreviousHash != null) { 39 | hexStringToByteArray(savedPreviousHash!!) 40 | } else { 41 | null 42 | } 43 | } 44 | 45 | @OptIn(ExperimentalStdlibApi::class) 46 | override suspend fun setItem(address: ByteArray, previousHash: ByteArray) { 47 | val addressString = address.toHexString() 48 | val previousHashString = previousHash.toHexString() 49 | val job = xyoScope.launch { 50 | this@PreviousHashStorePrefsRepository.previousHashStorePrefsDataStore.updateData { currentPrefs -> 51 | currentPrefs.toBuilder() 52 | .putAddressToHash(addressString, previousHashString) 53 | .build() 54 | } 55 | } 56 | job.join() 57 | } 58 | 59 | @OptIn(ExperimentalStdlibApi::class) 60 | override suspend fun removeItem(address: ByteArray) { 61 | val addressString = address.toHexString() 62 | val job = xyoScope.launch { 63 | this@PreviousHashStorePrefsRepository.previousHashStorePrefsDataStore.updateData { currentPrefs -> 64 | currentPrefs.toBuilder() 65 | .removeAddressToHash(addressString) 66 | .build() 67 | } 68 | } 69 | job.join() 70 | } 71 | 72 | suspend fun clearStore() { 73 | val job = xyoScope.launch { 74 | this@PreviousHashStorePrefsRepository.previousHashStorePrefsDataStore.updateData { currentPrefs -> 75 | currentPrefs.toBuilder() 76 | .clearAddressToHash() 77 | .build() 78 | } 79 | } 80 | job.join() 81 | } 82 | 83 | 84 | 85 | companion object { 86 | @Volatile 87 | private var INSTANCE: PreviousHashStorePrefsRepository? = null 88 | 89 | // Method to retrieve the singleton instance 90 | fun getInstance(context: Context, settings: SettingsInterface = defaultXyoSdkSettings): PreviousHashStorePrefsRepository { 91 | val newInstance = INSTANCE ?: synchronized(this) { 92 | INSTANCE ?: PreviousHashStorePrefsRepository(context.applicationContext, settings).also { INSTANCE = it } 93 | } 94 | return newInstance 95 | } 96 | 97 | fun refresh(context: Context, settings: SettingsInterface = defaultXyoSdkSettings): PreviousHashStorePrefsRepository { 98 | synchronized(this) { 99 | INSTANCE = PreviousHashStorePrefsRepository(context.applicationContext, settings) 100 | return INSTANCE!! 101 | } 102 | } 103 | 104 | fun resetInstance() { 105 | synchronized(this) { 106 | INSTANCE = null 107 | } 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/lib/ClientCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.coroutines.CoroutineContext 5 | 6 | class ClientCoroutineScope : CoroutineScope { 7 | 8 | private var parentJob = Job() 9 | 10 | override val coroutineContext: CoroutineContext 11 | get() = Dispatchers.Default + parentJob 12 | 13 | fun onStart() { 14 | parentJob = Job() 15 | } 16 | 17 | fun onStop() { 18 | parentJob.cancel() 19 | // You can also cancel the whole scope with `cancel(cause: CancellationException)` 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/lib/JsonSerializable.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 5 | import network.xyo.client.types.Hash 6 | import org.json.JSONArray 7 | import org.json.JSONObject 8 | import java.io.Serializable 9 | import java.security.MessageDigest 10 | 11 | abstract class JsonSerializable: Serializable { 12 | 13 | fun toJson(removeMeta: Boolean = false): String { 14 | return toJson(this, removeMeta) 15 | } 16 | 17 | companion object { 18 | 19 | fun sortJson(json: String, removeMeta: Boolean = false): String { 20 | return sortJson(JSONObject(json), removeMeta).toString() 21 | } 22 | 23 | fun sortJson(jsonObject: JSONObject, removeMeta: Boolean = false): JSONObject { 24 | val keys = jsonObject.keys().asSequence().sorted() 25 | val newJsonObject = JSONObject() 26 | keys.forEach { 27 | if (removeMeta) { 28 | if (it.startsWith("_")) { 29 | return@forEach 30 | } 31 | if (it.startsWith("$")) { 32 | return@forEach 33 | } 34 | } 35 | val value = jsonObject.get(it) 36 | if (value is JSONObject) { 37 | newJsonObject.put(it, sortJson(value, removeMeta)) 38 | } else if (value is JSONArray) { 39 | newJsonObject.put(it, sortJson(value, removeMeta)) 40 | } else { 41 | newJsonObject.put(it, value) 42 | } 43 | } 44 | return newJsonObject 45 | } 46 | 47 | fun sortJson(jsonArray: JSONArray, removeMeta: Boolean = false): JSONArray { 48 | val newJsonArray = JSONArray() 49 | for (i in 0 until jsonArray.length()) { 50 | when (val value = jsonArray[i]) { 51 | is JSONArray -> { 52 | newJsonArray.put(sortJson(value, removeMeta)) 53 | } 54 | is JSONObject -> { 55 | newJsonArray.put(sortJson(value, removeMeta)) 56 | } 57 | else -> { 58 | newJsonArray.put(value) 59 | } 60 | } 61 | } 62 | return newJsonArray 63 | } 64 | 65 | fun toJson(obj: Any, removeMeta: Boolean = false): String { 66 | val moshi = Moshi.Builder() 67 | .addLast(KotlinJsonAdapterFactory()) 68 | .build() 69 | val adapter = moshi.adapter(obj.javaClass) 70 | return sortJson(adapter.toJson(obj), removeMeta) 71 | } 72 | 73 | fun toJson(obj: List, removeMeta: Boolean = false): String { 74 | val moshi = Moshi.Builder() 75 | .addLast(KotlinJsonAdapterFactory()) 76 | .build() 77 | val adapter = moshi.adapter(obj.first().javaClass) 78 | val items = obj.map {item -> sortJson(adapter.toJson(item), removeMeta) } 79 | return items.joinToString(",", "[", "]") 80 | } 81 | 82 | fun fromJson(json: String, obj: T): T? { 83 | val moshi = Moshi.Builder() 84 | .addLast(KotlinJsonAdapterFactory()) 85 | .build() 86 | val adapter = moshi.adapter(obj.javaClass) 87 | return adapter.fromJson(json) 88 | } 89 | 90 | fun sha256(value: String): Hash { 91 | val md = MessageDigest.getInstance("SHA256") 92 | val valueBytes = value.encodeToByteArray() 93 | var total = 0 94 | for (byte in valueBytes) { 95 | total += byte 96 | } 97 | val len = value.length 98 | if (len == valueBytes.size) { 99 | println(total) 100 | println(len) 101 | } 102 | md.update(valueBytes) 103 | return md.digest() 104 | } 105 | 106 | @JvmStatic 107 | fun sha256(obj: T, removeMeta: Boolean = true): Hash { 108 | val json = toJson(obj, removeMeta) 109 | return sha256(json) 110 | } 111 | 112 | @JvmStatic 113 | fun sha256String(obj: T, removeMeta: Boolean = true): String { 114 | val shaBytes = sha256(obj, removeMeta) 115 | return bytesToHex(shaBytes) 116 | } 117 | 118 | private val hexArray = "0123456789abcdef".toCharArray() 119 | 120 | fun bytesToHex(bytes: ByteArray): String { 121 | val hexChars = CharArray(bytes.size * 2) 122 | for (j in bytes.indices) { 123 | val v = bytes[j].toInt() and 0xFF 124 | 125 | hexChars[j * 2] = hexArray[v ushr 4] 126 | hexChars[j * 2 + 1] = hexArray[v and 0x0F] 127 | } 128 | return String(hexChars) 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/lib/Secp256k1CurveConstants.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | import java.math.BigInteger 4 | 5 | object Secp256k1CurveConstants { 6 | val p: BigInteger = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) 7 | val a: BigInteger = BigInteger.ZERO 8 | val b: BigInteger = BigInteger.valueOf(7) 9 | val n: BigInteger = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) 10 | val g: Point = Point( 11 | BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16), 12 | BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) 13 | ) 14 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/lib/hasPermission.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import android.util.Log 6 | import androidx.core.content.ContextCompat 7 | 8 | fun hasPermission(context: Context, permission: String, message: String? = null): Boolean { 9 | val result = ContextCompat.checkSelfPermission( 10 | context.applicationContext, 11 | permission 12 | ) == PackageManager.PERMISSION_GRANTED 13 | if (!result && message != null) { 14 | Log.e("xyoClientSdk", "Missing Permission: ${message}") 15 | } 16 | return result 17 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/lib/hexStringToByteArray.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | fun hexStringToByteArray(hex: String): ByteArray { 4 | require(hex.length % 2 == 0) { "Hex string must have an even length" } 5 | 6 | return ByteArray(hex.length / 2) { i -> 7 | val index = i * 2 8 | hex.substring(index, index + 2).toInt(16).toByte() 9 | } 10 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/lib/recoverAddress.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import org.spongycastle.jcajce.provider.digest.Keccak 6 | import java.math.BigInteger 7 | 8 | data class Point(val x: BigInteger, val y: BigInteger) { 9 | fun isAtInfinity() = x == BigInteger.ZERO && y == BigInteger.ZERO 10 | } 11 | 12 | fun ByteArray.padStart(targetLength: Int, padValue: Byte = 0): ByteArray { 13 | if (this.size >= targetLength) return this 14 | val padding = ByteArray(targetLength - this.size) { padValue } 15 | return padding + this 16 | } 17 | 18 | // Double and Add algorithm for point multiplication 19 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 20 | fun pointMultiply(k: BigInteger, point: Point): Point { 21 | var result = Point(BigInteger.ZERO, BigInteger.ZERO) // Infinity point 22 | var addend = point 23 | var scalar = k 24 | 25 | while (scalar > BigInteger.ZERO) { 26 | if (scalar.and(BigInteger.ONE) == BigInteger.ONE) { 27 | result = pointAdd(result, addend) 28 | } 29 | addend = pointDouble(addend) 30 | scalar = scalar.shiftRight(1) 31 | } 32 | 33 | return result 34 | } 35 | 36 | // Point addition on the elliptic curve 37 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 38 | fun pointAdd(p: Point, q: Point): Point { 39 | if (p.isAtInfinity()) return q 40 | if (q.isAtInfinity()) return p 41 | 42 | val slope = if (p.x == q.x) { 43 | if ((p.y + q.y).mod(Secp256k1CurveConstants.p) == BigInteger.ZERO) return Point(BigInteger.ZERO, BigInteger.ZERO) 44 | (BigInteger.valueOf(3) * p.x.pow(2) + Secp256k1CurveConstants.a).mod(Secp256k1CurveConstants.p) * (BigInteger.TWO * p.y).modInverse(Secp256k1CurveConstants.p) 45 | } else { 46 | (q.y - p.y).mod(Secp256k1CurveConstants.p) * (q.x - p.x).modInverse(Secp256k1CurveConstants.p) 47 | }.mod(Secp256k1CurveConstants.p) 48 | 49 | val xR = (slope.pow(2) - p.x - q.x).mod(Secp256k1CurveConstants.p) 50 | val yR = (slope * (p.x - xR) - p.y).mod(Secp256k1CurveConstants.p) 51 | 52 | return Point(xR, yR) 53 | } 54 | 55 | // Point doubling on the elliptic curve 56 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 57 | fun pointDouble(p: Point): Point = pointAdd(p, p) 58 | 59 | // Recover public key from signature 60 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 61 | fun recoverPublicKey(messageHash: BigInteger, r: BigInteger, s: BigInteger, v: Int): Point? { 62 | val isYEven = (v % 2 == 0) 63 | val x = r.add(Secp256k1CurveConstants.n.multiply(BigInteger.valueOf((v / 2).toLong()))) 64 | 65 | if (x >= Secp256k1CurveConstants.p) return null 66 | 67 | // Calculate y-coordinate 68 | val alpha = (x.pow(3) + Secp256k1CurveConstants.a * x + Secp256k1CurveConstants.b).mod(Secp256k1CurveConstants.p) 69 | val beta = alpha.modPow((Secp256k1CurveConstants.p + BigInteger.ONE).shiftRight(2), Secp256k1CurveConstants.p) 70 | val y = if (beta.testBit(0) == isYEven) beta else Secp256k1CurveConstants.p - beta 71 | 72 | val rPoint = Point(x, y) 73 | 74 | // Calculate e and r^-1 75 | val e = messageHash 76 | val rInv = r.modInverse(Secp256k1CurveConstants.n) 77 | 78 | // Public key Q = r^-1 * (s * R - e * G) 79 | val sR = pointMultiply(s, rPoint) 80 | val eG = pointMultiply(e, Secp256k1CurveConstants.g) 81 | 82 | return pointMultiply(rInv, pointAdd(sR, Point(eG.x, Secp256k1CurveConstants.p - eG.y))) 83 | } 84 | 85 | private fun toKeccak(bytes: ByteArray): ByteArray { 86 | val keccak = Keccak.Digest256() 87 | keccak.update(bytes) 88 | return keccak.digest() 89 | } 90 | 91 | // Convert public key to Ethereum address 92 | fun publicKeyToAddress(publicKey: ByteArray): ByteArray { 93 | val hash = toKeccak(publicKey.sliceArray(1 until publicKey.size)) 94 | return hash.sliceArray(12 until hash.size) 95 | } 96 | 97 | // Main function to recover address 98 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 99 | fun recoverPublicKey(messageHash: ByteArray, signature: ByteArray): ByteArray? { 100 | if (signature.size != 64) return null 101 | 102 | val r = BigInteger(1, signature.copyOfRange(0, 32)) 103 | val s = BigInteger(1, signature.copyOfRange(32, 64)) 104 | 105 | val messageHashBI = BigInteger(1, messageHash) 106 | 107 | val publicPoint = recoverPublicKey(messageHashBI, r, s, 0) ?: recoverPublicKey(messageHashBI, r, s, 1) ?: return null 108 | val uncompressedKey = 109 | publicPoint.x.toByteArray().padStart(32, 0) + 110 | publicPoint.y.toByteArray().padStart(32, 0) 111 | return uncompressedKey 112 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/lib/xyoScope.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.lib 2 | 3 | val xyoScope = ClientCoroutineScope() -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/module/ModuleQueryResult.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.module 2 | 3 | import network.xyo.client.boundwitness.BoundWitness 4 | 5 | typealias ModuleQueryResult = Triple, List> -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/node/client/NodeClient.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.node.client 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import android.util.Log 6 | import androidx.annotation.RequiresApi 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.ExperimentalCoroutinesApi 9 | import kotlinx.coroutines.suspendCancellableCoroutine 10 | import kotlinx.coroutines.withContext 11 | import network.xyo.client.lib.JsonSerializable 12 | import network.xyo.client.account.Account 13 | import network.xyo.client.boundwitness.QueryBoundWitnessBuilder 14 | import network.xyo.client.boundwitness.QueryBoundWitness 15 | import network.xyo.client.payload.Payload 16 | import okhttp3.MediaType.Companion.toMediaType 17 | import okhttp3.OkHttpClient 18 | import okhttp3.Request 19 | import okhttp3.RequestBody.Companion.toRequestBody 20 | import org.json.JSONArray 21 | import org.json.JSONObject 22 | import java.io.IOException 23 | 24 | @RequiresApi(Build.VERSION_CODES.M) 25 | class NodeClient(private val url: String, private val accountToUse: network.xyo.client.account.model.Account?, private val context: Context) { 26 | private val _internalAccount = Account.random() 27 | private val okHttp = OkHttpClient() 28 | 29 | private val account: network.xyo.client.account.model.Account 30 | get() { 31 | if (this.accountToUse === null) { 32 | println("WARNING: Anonymous Queries not allowed, but running anyway.") 33 | return this._internalAccount 34 | } 35 | return this.accountToUse 36 | } 37 | 38 | 39 | @ExperimentalCoroutinesApi 40 | @RequiresApi(Build.VERSION_CODES.M) 41 | suspend fun query(query: Payload, payloads: List?): PostQueryResult { 42 | val bodyString = buildQuery(query, payloads) 43 | val postBody = bodyString.toRequestBody(MEDIA_TYPE_JSON) 44 | val request = Request.Builder() 45 | .url(url) 46 | .post(postBody) 47 | .build() 48 | 49 | return withContext(Dispatchers.IO) { 50 | return@withContext suspendCancellableCoroutine { continuation -> 51 | try { 52 | okHttp.newCall(request).execute().use { response -> 53 | if (!response.isSuccessful) { 54 | continuation.resume( 55 | PostQueryResult( 56 | null, 57 | arrayListOf(Error(response.message)) 58 | ) 59 | ) { cause, _, _ -> null?.let { it(cause) } } 60 | } else { 61 | continuation.resume( 62 | PostQueryResult( 63 | QueryResponseWrapper.parse(response.body!!.string()), 64 | null 65 | ) 66 | ) { cause, _, _ -> null?.let { it(cause) } } 67 | } 68 | } 69 | } catch (ex: IOException) { 70 | Log.e("xyoClient", ex.message ?: ex.toString()) 71 | continuation.resume( 72 | PostQueryResult( 73 | null, 74 | arrayListOf(Error(ex.message)) 75 | ) 76 | ) { cause, _, _ -> null?.let { it(cause) } } 77 | } 78 | } 79 | } 80 | } 81 | 82 | @RequiresApi(Build.VERSION_CODES.M) 83 | private suspend fun buildQuery(query: Payload, payloads: List?): String { 84 | val builtQuery = queryBuilder(query, payloads) 85 | val queryPayloads = buildQueryPayloads(query, payloads) 86 | val queryPayloadsJsonArray = queryPayloadsJsonArray(queryPayloads) 87 | val builtQueryTuple = arrayListOf(JsonSerializable.toJson((builtQuery)), queryPayloadsJsonArray.toString()) 88 | return builtQueryTuple.joinToString(",", "[", "]") 89 | } 90 | 91 | private suspend fun queryBuilder(query: Payload, payloads: List?): QueryBoundWitness { 92 | return QueryBoundWitnessBuilder().let { 93 | payloads?.let { payload -> 94 | it.payloads(payload) 95 | } 96 | it.signer(this.account).query(query).build() 97 | 98 | } 99 | } 100 | 101 | // combine payloads and query 102 | private fun buildQueryPayloads(query: Payload, payloads: List?): List{ 103 | return arrayListOf().let { queryPayloads -> 104 | payloads?.let { payloads -> 105 | payloads.forEach { payload -> 106 | queryPayloads.add(payload) 107 | } 108 | } 109 | queryPayloads.add(query) 110 | queryPayloads 111 | } 112 | } 113 | 114 | // stringify combined payloads 115 | private fun queryPayloadsJsonArray(payloads: List): JSONArray { 116 | return JSONArray().apply { 117 | payloads.forEach { payload -> 118 | val serializedPayload = JsonSerializable.toJson(payload) 119 | val obj = JSONObject(serializedPayload) 120 | this.put(obj) 121 | } 122 | } 123 | } 124 | 125 | companion object { 126 | val MEDIA_TYPE_JSON = "application/json; charset=utf-8".toMediaType() 127 | } 128 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/node/client/PostQueryResult.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.node.client 2 | 3 | import network.xyo.client.lib.JsonSerializable 4 | 5 | class PostQueryResult( 6 | val response: QueryResponseWrapper?, 7 | val errors: ArrayList?, 8 | ) : JsonSerializable(){ 9 | operator fun component1() = response 10 | operator fun component2() = errors 11 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/node/client/QueryResponse.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.node.client 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.squareup.moshi.Types 5 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 6 | import network.xyo.client.boundwitness.BoundWitnessFields 7 | import network.xyo.client.payload.Payload 8 | import network.xyo.client.types.Hash 9 | import org.json.JSONArray 10 | import org.json.JSONObject 11 | 12 | open class QueryResponseWrapper(val rawResponse: String) { 13 | val moshi: Moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() 14 | var bwHash: Hash? = null 15 | var bw: BoundWitnessFields? = null 16 | var payloads: List? = null 17 | 18 | private fun unwrap() { 19 | val response = JSONObject(rawResponse) 20 | val data = response.get("data") as JSONArray 21 | return splitTuple(data) 22 | } 23 | 24 | private fun splitTuple(tuple: JSONArray) { 25 | val wrapperBwString = tuple[0].toString() 26 | val wrapperBw = parseBW(wrapperBwString) 27 | if (wrapperBw !== null) { 28 | bwHash = wrapperBw.dataHash() 29 | bw = wrapperBw 30 | } 31 | 32 | val payloadsString = tuple[1].toString() 33 | payloads = parsePayloads(payloadsString) 34 | } 35 | 36 | protected open fun parseBW(bwString: String): BoundWitnessFields? { 37 | val bwAdapter = moshi.adapter(BoundWitnessFields::class.java) 38 | val bw = bwAdapter.fromJson(bwString) 39 | return bw 40 | } 41 | 42 | 43 | /** 44 | * Parse payloads 45 | * 46 | * Override to parse payloads into their specific types 47 | * 48 | * @param payloadsString 49 | * @return 50 | */ 51 | open fun parsePayloads(payloadsString: String): List? { 52 | val type = Types.newParameterizedType(List::class.java, Payload::class.java) 53 | val payloadAdapter = moshi.adapter>(type) 54 | return payloadAdapter.fromJson(payloadsString) 55 | } 56 | 57 | companion object { 58 | fun parse(rawResponse: String?): QueryResponseWrapper? { 59 | if (rawResponse != null) { 60 | val instance = QueryResponseWrapper(rawResponse) 61 | instance.unwrap() 62 | return instance 63 | } 64 | return null 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/payload/Payload.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.payload 2 | 3 | import com.squareup.moshi.JsonClass 4 | import network.xyo.client.lib.JsonSerializable 5 | import network.xyo.client.types.Hash 6 | 7 | open class XyoException(message: String): Throwable(message) 8 | open class XyoValidationException(message: String): XyoException(message) 9 | open class XyoInvalidSchemaException(val schema: String): XyoValidationException("'schema' must be lowercase [${schema}]") 10 | 11 | @JsonClass(generateAdapter = true) 12 | open class Payload(override var schema: String) : network.xyo.client.payload.model.Payload, JsonSerializable() { 13 | @Throws(XyoValidationException::class) 14 | open fun validate() { 15 | if (schema != schema.lowercase()) { 16 | throw XyoInvalidSchemaException(schema) 17 | } 18 | } 19 | 20 | open fun dataHash(): Hash { 21 | return sha256(this, true) 22 | } 23 | 24 | open fun hash(): Hash { 25 | return sha256(this, false) 26 | } 27 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/payload/model/Payload.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.payload.model 2 | 3 | import java.io.Serializable 4 | 5 | interface Payload : Serializable { 6 | var schema: String 7 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/payload/model/WithMeta.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.payload.model 2 | 3 | import com.squareup.moshi.Json 4 | import network.xyo.client.types.HashHex 5 | import java.io.Serializable 6 | 7 | interface WithMeta : Serializable { 8 | @Json(name = "\$meta") 9 | var _meta: T 10 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/proto/AccountPrefsDataStore.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package network.xyo.proto; 4 | 5 | option java_package = "network.xyo.data"; 6 | option java_outer_classname = "AccountPrefsDataStoreProtos"; 7 | 8 | message AccountPrefsDataStore { 9 | optional string account_key = 1; 10 | 11 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/proto/PreviousHashPrefsDataStore.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package network.xyo.proto; 4 | 5 | option java_package = "network.xyo.data"; 6 | option java_outer_classname = "PreviousHashPrefsDataStoreProtos"; 7 | 8 | message PreviousHashPrefsDataStore { 9 | map address_to_hash = 1; 10 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/settings/Defaults.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.settings 2 | 3 | const val DefaultStoragePath = "__xyo-client-sdk__" 4 | const val DefaultFileName = "network-xyo-sdk-prefs" 5 | 6 | open class DefaultXyoSdkSettings : SettingsInterface { 7 | override val accountPreferences: AccountPreferences = DefaultAccountPreferences() 8 | override val previousHashStorePreferences: PreviousHashStorePreferences = DefaultPreviousHashStorePreferences() 9 | } 10 | 11 | open class DefaultStorageLocationPreferences: StorageLocationPreference { 12 | override val storagePath = DefaultStoragePath 13 | override val fileName = DefaultFileName 14 | } 15 | 16 | open class DefaultAccountPreferences: DefaultStorageLocationPreferences(), AccountPreferences { 17 | override val fileName = "network-xyo-sdk-account-prefs" 18 | } 19 | open class DefaultPreviousHashStorePreferences: DefaultStorageLocationPreferences(), PreviousHashStorePreferences { 20 | override val fileName = "network-xyo-sdk-previous-hash-store-prefs" 21 | } 22 | 23 | val defaultXyoSdkSettings = DefaultXyoSdkSettings() -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/settings/SettingsInterface.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.settings 2 | 3 | interface SettingsInterface { 4 | val accountPreferences: AccountPreferences 5 | val previousHashStorePreferences: PreviousHashStorePreferences 6 | } 7 | 8 | interface StorageLocationPreference { 9 | val fileName: String? 10 | val storagePath: String? 11 | } 12 | 13 | interface AccountPreferences: StorageLocationPreference 14 | 15 | interface PreviousHashStorePreferences: StorageLocationPreference -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/settings/XyoSdk.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.settings 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import network.xyo.client.account.Account 7 | import network.xyo.client.datastore.accounts.AccountPrefsRepository 8 | import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository 9 | 10 | class XyoSdk private constructor(val settings: SettingsInterface) { 11 | private var _account: network.xyo.client.account.model.Account? = null 12 | 13 | @RequiresApi(Build.VERSION_CODES.M) 14 | suspend fun getAccount(context: Context): network.xyo.client.account.model.Account { 15 | if (INSTANCE !== null) { 16 | val validInstance = INSTANCE!! 17 | if (validInstance._account !== null) { 18 | return validInstance._account!! 19 | } else { 20 | val repository = AccountPrefsRepository.getInstance(context.applicationContext) 21 | validInstance._account = repository.getAccount() 22 | return _account!! 23 | } 24 | } else { 25 | throw Exception("Tried to access instance but it was null. Did you forget to initialize XyoSdk first?") 26 | } 27 | } 28 | 29 | companion object { 30 | @Volatile 31 | private var INSTANCE: XyoSdk? = null 32 | 33 | fun getInstance(context: Context, settings: SettingsInterface = DefaultXyoSdkSettings()): XyoSdk { 34 | // Initialize the global AccountPrefs DataStore 35 | AccountPrefsRepository.getInstance(context.applicationContext, settings) 36 | // Initialize the global Account object with the previous hash store DataStore 37 | Account.previousHashStore = PreviousHashStorePrefsRepository.getInstance(context.applicationContext) 38 | 39 | val newInstance = INSTANCE ?: synchronized(this) { 40 | INSTANCE ?: XyoSdk(settings).also { INSTANCE = it } 41 | } 42 | return newInstance 43 | } 44 | 45 | fun refresh(context: Context, settings: SettingsInterface = DefaultXyoSdkSettings()): XyoSdk { 46 | synchronized(this) { 47 | // Initialize the singleton with the users accountPreferences 48 | AccountPrefsRepository.getInstance(context, settings) 49 | INSTANCE = XyoSdk(settings) 50 | } 51 | return INSTANCE!! 52 | } 53 | 54 | fun resetInstance() { 55 | INSTANCE = null 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/types/TypeAlias.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.types 2 | 3 | typealias Hash = ByteArray 4 | typealias Address = ByteArray 5 | 6 | typealias HashHex = String 7 | typealias AddressHex = String -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/XyoPanel.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import android.util.Log 6 | import androidx.annotation.RequiresApi 7 | import network.xyo.client.account.model.Account 8 | import network.xyo.client.archivist.wrapper.ArchivistWrapper 9 | import network.xyo.client.boundwitness.BoundWitnessBuilder 10 | import network.xyo.client.boundwitness.BoundWitness 11 | import network.xyo.client.node.client.NodeClient 12 | import network.xyo.client.node.client.PostQueryResult 13 | import network.xyo.client.payload.Payload 14 | 15 | data class XyoPanelReportQueryResult(val bw: BoundWitness, val apiResults: List?, val payloads: List?) 16 | 17 | @RequiresApi(Build.VERSION_CODES.M) 18 | class XyoPanel( 19 | val context: Context, 20 | val account: Account, 21 | private val witnesses: List>?, 22 | private val nodeUrlsAndAccounts: ArrayList>? 23 | ) { 24 | private var nodes: MutableList? = null 25 | 26 | constructor( 27 | context: Context, 28 | account: Account, 29 | nodeUrlsAndAccounts: ArrayList>, 30 | witnesses: List>? = null 31 | ): this( 32 | context, 33 | account, 34 | witnesses, 35 | nodeUrlsAndAccounts 36 | ) 37 | 38 | constructor( 39 | context: Context, 40 | account: Account, 41 | observe: ((context: Context) -> List?)? 42 | ): this( 43 | context, 44 | account, 45 | listOf(XyoWitness(observe)), 46 | null, 47 | ) 48 | 49 | fun resolveNodes(resetNodes: Boolean = false) { 50 | if (resetNodes) nodes = null 51 | if (nodeUrlsAndAccounts?.isNotEmpty() == true) { 52 | nodes = mutableListOf().let { 53 | this@XyoPanel.nodeUrlsAndAccounts.forEach { pair -> 54 | val nodeUrl = pair.first 55 | val account = pair.second 56 | it.add(NodeClient(nodeUrl, account, context)) 57 | } 58 | it 59 | } 60 | } 61 | } 62 | 63 | private suspend fun generateBoundWitnessJson(payloads: List): BoundWitness { 64 | return BoundWitnessBuilder() 65 | .payloads(payloads) 66 | .signer(account) 67 | .build() 68 | } 69 | 70 | 71 | private suspend fun generatePayloads(adhocWitnesses: List> = emptyList()): List { 72 | val witnesses: List> = (this.witnesses ?: emptyList()).plus(adhocWitnesses) 73 | val payloads = witnesses.map { witness -> 74 | witness.observe(context) 75 | } 76 | 77 | return payloads.mapNotNull { payload -> payload }.flatten() 78 | } 79 | 80 | @kotlinx.coroutines.ExperimentalCoroutinesApi 81 | suspend fun reportAsyncQuery(adhocWitnesses: List> = emptyList()): XyoPanelReportQueryResult { 82 | if (nodes == null) resolveNodes() 83 | val payloads = generatePayloads(adhocWitnesses) 84 | val bw = generateBoundWitnessJson(payloads) 85 | val results = mutableListOf() 86 | 87 | if (nodes.isNullOrEmpty()) { 88 | Log.e("xyoClient", "No Nodes found, so no payloads will be sent to archivist(s)") 89 | } 90 | 91 | nodes?.forEach { node -> 92 | val archivist = ArchivistWrapper(node) 93 | val payloadsWithBoundWitness = payloads.plus(bw) 94 | val queryResult = archivist.insert(payloadsWithBoundWitness) 95 | results.add(queryResult) 96 | } 97 | return XyoPanelReportQueryResult(bw, results, payloads) 98 | } 99 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/XyoWitness.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import network.xyo.client.account.Account 7 | import network.xyo.client.payload.Payload 8 | 9 | abstract class DeferredObserver { 10 | abstract suspend fun deferredDetect(context: Context): List? 11 | } 12 | 13 | @RequiresApi(Build.VERSION_CODES.M) 14 | open class XyoWitness ( 15 | val address: network.xyo.client.account.model.Account = Account.random(), 16 | private val observer: ((context: Context) -> List?)? = null, 17 | val deferredObserver: DeferredObserver? = null 18 | ) { 19 | 20 | constructor( 21 | observer: ((context: Context) -> List?)?, 22 | account: network.xyo.client.account.model.Account = Account.random() 23 | ): this(account, observer) 24 | 25 | constructor( 26 | observer: DeferredObserver?, 27 | account: network.xyo.client.account.model.Account = Account.random() 28 | ): this(account, null, observer) 29 | 30 | open suspend fun observe(context: Context): List? { 31 | val appContext = context.applicationContext 32 | if (deferredObserver !== null) { 33 | val payload = deferredObserver.deferredDetect(appContext) 34 | return payload 35 | } 36 | observer?.let { 37 | val payloads = it(appContext) 38 | return payloads 39 | } 40 | return null 41 | } 42 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/Handler.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.ExperimentalCoroutinesApi 8 | import kotlinx.coroutines.withContext 9 | import network.xyo.client.witness.types.WitnessHandlerInterface 10 | import network.xyo.client.witness.types.WitnessResult 11 | import network.xyo.client.witness.XyoPanel 12 | import network.xyo.client.account.model.Account 13 | import network.xyo.client.boundwitness.BoundWitness 14 | import network.xyo.client.payload.Payload 15 | import network.xyo.client.settings.XyoSdk 16 | 17 | open class WitnessLocationHandler : WitnessHandlerInterface> { 18 | @RequiresApi(Build.VERSION_CODES.M) 19 | override suspend fun witness(context: Context, nodeUrlsAndAccounts: ArrayList>): WitnessResult> { 20 | val account = XyoSdk.getInstance(context.applicationContext).getAccount(context) 21 | val panel = XyoPanel(context, account, nodeUrlsAndAccounts, listOf( 22 | XyoLocationWitness(account) 23 | )) 24 | return getLocation(panel) 25 | } 26 | 27 | @OptIn(ExperimentalCoroutinesApi::class) 28 | @RequiresApi(Build.VERSION_CODES.M) 29 | private suspend fun getLocation(panel: XyoPanel): WitnessResult> { 30 | return withContext(Dispatchers.IO) { 31 | var locationPayload: Payload? = null 32 | var locationPayloadRaw: Payload? = null 33 | var bw: BoundWitness? = null 34 | val errors: MutableList = mutableListOf() 35 | panel.let { 36 | it.reportAsyncQuery().let { result -> 37 | val actualPayloads = result.payloads 38 | actualPayloads?.forEach { payload -> 39 | when (payload) { 40 | is LocationPayload -> locationPayload = payload 41 | is LocationPayloadRaw -> locationPayloadRaw = payload 42 | } 43 | } 44 | 45 | // target the first result because we are only looking at a single location witness 46 | val apiResult = result.apiResults?.first() 47 | if (apiResult?.errors?.size !== null && apiResult.errors.size > 0) { 48 | apiResult.errors.forEach { error -> errors.add(error)} 49 | } 50 | 51 | bw = result.bw 52 | } 53 | } 54 | if (errors.size > 0) return@withContext WitnessResult.Error(errors) 55 | return@withContext WitnessResult.Success(Triple(bw, locationPayload, locationPayloadRaw)) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/LocationActivity.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.os.Bundle 6 | import android.os.Looper 7 | import android.util.Log 8 | import com.google.android.gms.location.LocationCallback 9 | import com.google.android.gms.location.LocationRequest 10 | import com.google.android.gms.location.LocationResult 11 | import com.google.android.gms.location.LocationServices 12 | 13 | const val REQUESTING_LOCATION_UPDATES_KEY = "REQUESTING_LOCATION_UPDATES_KEY" 14 | 15 | open class LocationActivity : Activity() { 16 | private var requestingLocationUpdates: Boolean = false 17 | private lateinit var locationCallback: LocationCallback 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | 22 | requestingLocationUpdates = true 23 | locationCallback = object : LocationCallback() { 24 | override fun onLocationResult(locationResult: LocationResult) { 25 | // No action necessary but we need to start the activity to get location results 26 | Log.i("xyoClient", "LocationActivity: locationCallback fired successfully") 27 | } 28 | } 29 | startLocationUpdates() 30 | } 31 | 32 | override fun onResume() { 33 | super.onResume() 34 | if (requestingLocationUpdates) startLocationUpdates() 35 | } 36 | 37 | override fun onSaveInstanceState(outState: Bundle) { 38 | outState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY, requestingLocationUpdates) 39 | super.onSaveInstanceState(outState) 40 | } 41 | 42 | // Already checking in class 43 | @SuppressLint("MissingPermission") 44 | private fun startLocationUpdates() { 45 | if (LocationPermissions.check((this))) { 46 | val locationRequest: LocationRequest = LocationRequest.Builder(5000).build() 47 | val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) 48 | fusedLocationClient.requestLocationUpdates(locationRequest, 49 | locationCallback, 50 | Looper.getMainLooper()) 51 | return 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/LocationPayload.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import com.squareup.moshi.JsonClass 4 | import network.xyo.client.payload.Payload 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class Coordinates( 8 | val accuracy: Float?, 9 | val altitude: Double?, 10 | val altitudeAccuracy: Double?, 11 | val heading: Float?, 12 | val latitude: Double, 13 | val longitude: Double, 14 | val speed: Float? 15 | ) 16 | 17 | @JsonClass(generateAdapter = true) 18 | data class CurrentLocation( 19 | val coords: Coordinates, 20 | val timestamp: Long 21 | ) 22 | 23 | @JsonClass(generateAdapter = true) 24 | class LocationPayload( 25 | val currentLocation: CurrentLocation? = null, 26 | ): Payload(SCHEMA) { 27 | companion object { 28 | const val SCHEMA = "network.xyo.location.current" 29 | 30 | fun detect(currentLocation: CurrentLocation?): LocationPayload { 31 | return LocationPayload(currentLocation) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/LocationPayloadRaw.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import android.os.Bundle 4 | import com.squareup.moshi.JsonClass 5 | import network.xyo.client.payload.Payload 6 | 7 | // Extension function for safely retrieving typed values from a Bundle 8 | private fun Bundle.getTypedValue(key: String): Any? { 9 | return when { 10 | containsKey(key) -> { 11 | when { 12 | getString(key) != null -> getString(key) 13 | getInt(key, Int.MIN_VALUE) != Int.MIN_VALUE -> getInt(key) 14 | getLong(key, Long.MIN_VALUE) != Long.MIN_VALUE -> getLong(key) 15 | getFloat(key, Float.MIN_VALUE) != Float.MIN_VALUE -> getFloat(key) 16 | getDouble(key, Double.MIN_VALUE) != Double.MIN_VALUE -> getDouble(key) 17 | getBoolean(key) -> getBoolean(key) 18 | else -> null 19 | } 20 | } 21 | else -> null 22 | } 23 | } 24 | 25 | @JsonClass(generateAdapter = true) 26 | open class LocationPayloadRaw( 27 | val provider: String?, 28 | val latitude: Double, 29 | val longitude: Double, 30 | val altitude: Double, 31 | val accuracy: Float?, 32 | val bearing: Float?, 33 | val bearingAccuracyDegrees: Float?, 34 | val speed: Float?, 35 | val speedAccuracyMetersPerSecond: Float?, 36 | val verticalAccuracyMeters: Float?, 37 | val time: Long, 38 | val isMock: Boolean?, 39 | val extras: Map? = null 40 | ): Payload(SCHEMA) { 41 | companion object { 42 | const val SCHEMA = "network.xyo.location.android" 43 | fun detect( 44 | provider: String?, 45 | latitude: Double, 46 | longitude: Double, 47 | altitude: Double, 48 | accuracy: Float?, 49 | bearing: Float?, 50 | bearingAccuracyDegrees: Float?, 51 | speed: Float?, 52 | speedAccuracyMetersPerSecond: Float?, 53 | verticalAccuracyMeters: Float?, 54 | time: Long, 55 | isMock: Boolean?, 56 | extras: Bundle? = null 57 | ): LocationPayloadRaw { 58 | val extrasMap = extras?.keySet()?.associateWith { key -> 59 | extras.getTypedValue(key) 60 | } 61 | 62 | return LocationPayloadRaw( 63 | provider, 64 | latitude, 65 | longitude, 66 | altitude, 67 | accuracy, 68 | bearing, 69 | bearingAccuracyDegrees, 70 | speed, 71 | speedAccuracyMetersPerSecond, 72 | verticalAccuracyMeters, 73 | time, 74 | isMock, 75 | extrasMap 76 | ) 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/PermissionCheck.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.util.Log 7 | import androidx.core.app.ActivityCompat 8 | import com.google.android.gms.common.GoogleApiAvailability 9 | 10 | class LocationPermissions { 11 | companion object { 12 | fun check(context: Context): Boolean { 13 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) 14 | != PackageManager.PERMISSION_GRANTED) { 15 | Log.e("xyoClient", "ACCESS_FINE_LOCATION permission not allowed") 16 | return false 17 | } 18 | 19 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) 20 | != PackageManager.PERMISSION_GRANTED) { 21 | Log.e("xyoClient", "ACCESS_COARSE_LOCATION permission not allowed") 22 | return false 23 | } 24 | return true 25 | } 26 | 27 | fun checkGooglePlayServices(context: Context): Boolean { 28 | val gpInstance = GoogleApiAvailability.getInstance() 29 | val available = gpInstance.isGooglePlayServicesAvailable(context) 30 | val googlePlayServicesAvailable = available == 1 31 | if (googlePlayServicesAvailable) { 32 | Log.e("xyoClient", "Google Play Service not installed") 33 | return false 34 | } 35 | return true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/XyoLocationCurrent.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.location.Location 6 | import android.os.Build 7 | import android.util.Log 8 | import androidx.annotation.RequiresApi 9 | import com.google.android.gms.location.LocationServices 10 | import com.squareup.moshi.JsonClass 11 | import kotlinx.coroutines.suspendCancellableCoroutine 12 | import network.xyo.client.lib.JsonSerializable 13 | import kotlin.coroutines.resumeWithException 14 | 15 | @JsonClass(generateAdapter = true) 16 | class XyoLocationCurrent { 17 | companion object { 18 | 19 | @RequiresApi(Build.VERSION_CODES.O) 20 | @SuppressLint("MissingPermission") 21 | suspend fun detect(context: Context): Pair? { 22 | if (LocationPermissions.check((context)) && LocationPermissions.checkGooglePlayServices(context)) { 23 | val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) 24 | return suspendCancellableCoroutine { continuation -> 25 | fusedLocationProviderClient.lastLocation 26 | .addOnSuccessListener { location -> 27 | if (location === null) { 28 | continuation.resumeWith(Result.success(null)) 29 | return@addOnSuccessListener 30 | } 31 | 32 | val locationRaw = buildRawLocationPayload(location) 33 | val coordinates = setCoordinatesFromLocation(location) 34 | val currentLocation = CurrentLocation(coordinates, System.currentTimeMillis()) 35 | 36 | // Resume the coroutine with the retrieved location 37 | continuation.resumeWith(Result.success(Pair(LocationPayload(currentLocation), locationRaw))) 38 | } 39 | .addOnFailureListener { exception -> 40 | // Resume the coroutine with an exception if the task fails 41 | continuation.resumeWithException(exception) 42 | } 43 | } 44 | } 45 | return null 46 | } 47 | 48 | private fun setCoordinatesFromLocation(location: Location): Coordinates { 49 | Log.i("xyoClient", "Inside setCoordinatesFromLocation") 50 | 51 | val coordinates = Coordinates( 52 | location.accuracy, 53 | location.altitude, 54 | null, 55 | location.bearing, 56 | location.latitude, 57 | location.longitude, 58 | location.speed 59 | ) 60 | val serialized = JsonSerializable.toJson(coordinates) 61 | Log.i("xyoClient", "serialized Coordinates: $serialized") 62 | return coordinates 63 | } 64 | 65 | @RequiresApi(Build.VERSION_CODES.O) 66 | private fun buildRawLocationPayload(location: Location): LocationPayloadRaw { 67 | return LocationPayloadRaw.detect( 68 | location.provider, 69 | location.latitude, 70 | location.longitude, 71 | location.altitude, 72 | location.accuracy, 73 | location.bearing, 74 | location.bearingAccuracyDegrees, 75 | location.speed, 76 | location.speedAccuracyMetersPerSecond, 77 | location.verticalAccuracyMeters, 78 | location.time, 79 | handleIsMock(location), 80 | location.extras 81 | ) 82 | } 83 | 84 | private fun handleIsMock(location: Location): Boolean? { 85 | // Conditionally figure out if using mocked location 86 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 87 | location.isMock 88 | } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { 89 | location.isFromMockProvider 90 | } else { 91 | null 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/XyoLocationPayloads.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.os.Build 6 | import androidx.annotation.RequiresApi 7 | 8 | class XyoLocationPayloads { 9 | companion object { 10 | 11 | @RequiresApi(Build.VERSION_CODES.O) 12 | @SuppressLint("MissingPermission") 13 | suspend fun detect(context: Context): Pair? { 14 | return XyoLocationCurrent.detect(context) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/location/info/XyoLocationWitness.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.location.info 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import android.util.Log 6 | import androidx.annotation.RequiresApi 7 | import network.xyo.client.witness.DeferredObserver 8 | import network.xyo.client.witness.XyoWitness 9 | import network.xyo.client.account.Account 10 | import network.xyo.client.payload.Payload 11 | 12 | class DeferredLocationObserver : DeferredObserver() { 13 | @RequiresApi(Build.VERSION_CODES.O) 14 | override suspend fun deferredDetect( 15 | context: Context, 16 | ): List? { 17 | try { 18 | val payloads = XyoLocationPayloads.detect(context) 19 | // only return the payloads that were found 20 | val foundPayloads = payloads.takeIf { it !== null } 21 | return if (foundPayloads !== null) { 22 | listOf(foundPayloads.first, foundPayloads.second) 23 | } else { 24 | return null 25 | } 26 | } catch (e: Exception) { 27 | Log.e("xyoClient", "Error building location payload: ${e.toString() + e.stackTraceToString()}") 28 | return null 29 | } 30 | } 31 | } 32 | 33 | @RequiresApi(Build.VERSION_CODES.M) 34 | class XyoLocationWitness(address: network.xyo.client.account.model.Account = Account.random()) : XyoWitness( 35 | DeferredLocationObserver(), 36 | address 37 | ) -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/SystemInfoPayload.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import com.squareup.moshi.JsonClass 7 | import network.xyo.client.payload.Payload 8 | 9 | @JsonClass(generateAdapter = true) 10 | class SystemInfoPayload( 11 | val device: XyoSystemInfoDevice? = null, 12 | val network: XyoSystemInfoNetwork? = null, 13 | val os: XyoSystemInfoOs? = null 14 | ): Payload (SCHEMA) { 15 | companion object { 16 | const val SCHEMA = "network.xyo.system.info.android" 17 | 18 | @RequiresApi(Build.VERSION_CODES.M) 19 | fun detect(context: Context): SystemInfoPayload { 20 | return SystemInfoPayload( 21 | XyoSystemInfoDevice.detect(context), 22 | XyoSystemInfoNetwork.detect(context), 23 | XyoSystemInfoOs.detect(context) 24 | ) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/XyoSystemInfoDevice.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import com.squareup.moshi.JsonClass 6 | import java.net.URLEncoder 7 | 8 | @JsonClass(generateAdapter = true) 9 | class XyoSystemInfoDevice( 10 | val board: String?, 11 | val bootloader: String?, 12 | val brand: String?, 13 | val device: String?, 14 | val fingerprint: String?, 15 | val hardware: String?, 16 | val host: String?, 17 | val id: String?, 18 | val manufacturer: String?, 19 | val model: String?, 20 | val product: String?, 21 | val tags: String?, 22 | val time: Long?, 23 | val type: String?, 24 | val user: String?, 25 | ) { 26 | companion object { 27 | fun detect(context: Context): XyoSystemInfoDevice { 28 | return XyoSystemInfoDevice( 29 | URLEncoder.encode(Build.BOARD, "UTF-8"), 30 | URLEncoder.encode(Build.BOOTLOADER, "UTF-8"), 31 | URLEncoder.encode(Build.BRAND, "UTF-8"), 32 | URLEncoder.encode(Build.DEVICE, "UTF-8"), 33 | URLEncoder.encode(Build.FINGERPRINT, "UTF-8"), 34 | URLEncoder.encode(Build.HARDWARE, "UTF-8"), 35 | URLEncoder.encode(Build.HOST, "UTF-8"), 36 | URLEncoder.encode(Build.ID, "UTF-8"), 37 | URLEncoder.encode(Build.MANUFACTURER, "UTF-8"), 38 | URLEncoder.encode(Build.MODEL, "UTF-8"), 39 | URLEncoder.encode(Build.PRODUCT, "UTF-8"), 40 | URLEncoder.encode(Build.TAGS, "UTF-8"), 41 | Build.TIME, 42 | URLEncoder.encode(Build.TYPE, "UTF-8"), 43 | URLEncoder.encode(Build.USER, "UTF-8") 44 | ) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/XyoSystemInfoNetwork.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import com.squareup.moshi.JsonClass 7 | 8 | @JsonClass(generateAdapter = true) 9 | class XyoSystemInfoNetwork ( 10 | val cellular: XyoSystemInfoNetworkCellular?, 11 | val wifi: XyoSystemInfoNetworkWifi?, 12 | val wired: XyoSystemInfoNetworkWired? 13 | ) { 14 | companion object { 15 | @RequiresApi(Build.VERSION_CODES.M) 16 | fun detect(context: Context): XyoSystemInfoNetwork? { 17 | val result = XyoSystemInfoNetwork( 18 | XyoSystemInfoNetworkCellular.detect(context), 19 | XyoSystemInfoNetworkWifi.detect(context), 20 | XyoSystemInfoNetworkWired.detect(context) 21 | ) 22 | if (result.cellular != null || result.wifi != null || result.wired != null) { 23 | return result 24 | } 25 | return null 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/XyoSystemInfoNetworkCellular.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | import android.os.Build 7 | import android.telephony.TelephonyManager 8 | import com.squareup.moshi.JsonClass 9 | import java.net.NetworkInterface 10 | 11 | @JsonClass(generateAdapter = true) 12 | data class XyoSystemInfoNetworkCellularProvider( 13 | val name: String?, 14 | ) 15 | 16 | @JsonClass(generateAdapter = true) 17 | class XyoSystemInfoNetworkCellular( 18 | val ip: String?, 19 | val provider: XyoSystemInfoNetworkCellularProvider? 20 | ) { 21 | companion object { 22 | fun detect(context: Context): XyoSystemInfoNetworkCellular? { 23 | if (Build.VERSION.SDK_INT >= 23) { 24 | val connectivityManager = 25 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 26 | val network = connectivityManager.activeNetwork 27 | val networkCaps = connectivityManager.getNetworkCapabilities(network) 28 | if (networkCaps?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true) { 29 | val telephonyManager = 30 | context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager 31 | val provider = XyoSystemInfoNetworkCellularProvider( 32 | telephonyManager.networkOperatorName 33 | ) 34 | 35 | return XyoSystemInfoNetworkCellular(getIpAddress(), provider) 36 | } 37 | } 38 | return null 39 | } 40 | 41 | private fun getIpAddress(): String? { 42 | val networkInterfaces = NetworkInterface.getNetworkInterfaces() 43 | networkInterfaces.asSequence().forEach { networkInterface -> 44 | networkInterface.inetAddresses.asSequence().forEach { inetAddress -> 45 | if (!inetAddress.isLoopbackAddress) { 46 | return inetAddress.hostAddress 47 | } 48 | } 49 | } 50 | return null 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/XyoSystemInfoNetworkWifi.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.net.ConnectivityManager 7 | import android.net.ConnectivityManager.NetworkCallback 8 | import android.net.Network 9 | import android.net.NetworkCapabilities 10 | import android.net.NetworkRequest 11 | import android.net.wifi.WifiInfo 12 | import android.os.Build 13 | import androidx.annotation.RequiresApi 14 | import com.squareup.moshi.JsonClass 15 | import network.xyo.client.lib.hasPermission 16 | import java.net.NetworkInterface 17 | import java.util.concurrent.CountDownLatch 18 | import java.util.concurrent.TimeUnit 19 | 20 | @JsonClass(generateAdapter = true) 21 | 22 | class XyoSystemInfoNetworkWifi ( 23 | @Suppress("unused") 24 | val ip: String?, 25 | @Suppress("unused") 26 | val mac: String?, 27 | @Suppress("unused") 28 | val rssi: Int?, 29 | @Suppress("unused") 30 | val ssid: String? 31 | ) { 32 | companion object { 33 | @RequiresApi(Build.VERSION_CODES.M) 34 | @SuppressLint("HardwareIds") 35 | fun detect(context: Context): XyoSystemInfoNetworkWifi? { 36 | // Accessing Wifi network requires CHANGE_NETWORK_STATE permission 37 | if (!hasPermission(context, Manifest.permission.CHANGE_NETWORK_STATE)) { 38 | return null 39 | } 40 | // setup latch and wifiInfo inside companion so it isn't shared across instances 41 | val latch = CountDownLatch(1) 42 | val wifiInfo = accessNetworkChanges(context, latch) 43 | 44 | try { 45 | // Wait for up to 5 seconds for the network capabilities 46 | latch.await(5, TimeUnit.SECONDS) 47 | val connectivityManager = context.getSystemService(ConnectivityManager::class.java) 48 | 49 | if (Build.VERSION.SDK_INT >= 23) { 50 | val network = connectivityManager.activeNetwork 51 | val networkCaps = connectivityManager.getNetworkCapabilities(network) 52 | if (networkCaps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) { 53 | return XyoSystemInfoNetworkWifi( 54 | getIpAddress(), 55 | wifiInfo?.macAddress, 56 | wifiInfo?.rssi, 57 | wifiInfo?.ssid?.replace("\"", "") 58 | ) 59 | } 60 | } 61 | return null 62 | } catch (e: InterruptedException) { 63 | e.printStackTrace() 64 | } 65 | return null 66 | } 67 | 68 | @RequiresApi(Build.VERSION_CODES.M) 69 | private fun accessNetworkChanges(context: Context, latch: CountDownLatch): WifiInfo? { 70 | var wifiInfo: WifiInfo? = null 71 | val request = NetworkRequest.Builder() 72 | .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 73 | .build() 74 | 75 | val connectivityManager = context.getSystemService(ConnectivityManager::class.java) 76 | 77 | val networkCallback = object : NetworkCallback() { 78 | @RequiresApi(Build.VERSION_CODES.Q) 79 | override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { 80 | val localWifiInfo = networkCapabilities.transportInfo as? WifiInfo 81 | wifiInfo = localWifiInfo 82 | // countDown to zero to lift the latch 83 | latch.countDown() 84 | } 85 | } 86 | 87 | // For requesting a network 88 | connectivityManager.requestNetwork(request, networkCallback) 89 | 90 | // For listening to network changes 91 | connectivityManager.registerNetworkCallback(request, networkCallback) 92 | 93 | return wifiInfo 94 | } 95 | 96 | private fun getIpAddress(): String? { 97 | val networkInterfaces = NetworkInterface.getNetworkInterfaces() 98 | networkInterfaces.asSequence().forEach { networkInterface -> 99 | networkInterface.inetAddresses.asSequence().forEach { inetAddress -> 100 | if (!inetAddress.isLoopbackAddress) { 101 | return inetAddress.hostAddress 102 | } 103 | } 104 | } 105 | return null 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/XyoSystemInfoNetworkWired.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | import android.os.Build 7 | import com.squareup.moshi.JsonClass 8 | import java.net.NetworkInterface 9 | 10 | @JsonClass(generateAdapter = true) 11 | class XyoSystemInfoNetworkWired( 12 | val ip: String? 13 | ) { 14 | companion object { 15 | fun detect(context: Context): XyoSystemInfoNetworkWired? { 16 | val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 17 | if (Build.VERSION.SDK_INT >= 23) { 18 | val network = connectivityManager.activeNetwork 19 | val networkCaps = connectivityManager.getNetworkCapabilities(network) 20 | if (networkCaps?.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) == true) { 21 | return XyoSystemInfoNetworkWired(getIpAddress()) 22 | } 23 | } 24 | return null 25 | } 26 | 27 | private fun getIpAddress(): String? { 28 | val networkInterfaces = NetworkInterface.getNetworkInterfaces() 29 | networkInterfaces.asSequence().forEach { networkInterface -> 30 | networkInterface.inetAddresses.asSequence().forEach { inetAddress -> 31 | if (!inetAddress.isLoopbackAddress) { 32 | return inetAddress.hostAddress 33 | } 34 | } 35 | } 36 | return null 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/XyoSystemInfoOs.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import com.squareup.moshi.JsonClass 7 | 8 | @JsonClass(generateAdapter = true) 9 | class XyoSystemInfoOs ( 10 | val base_os: String?, 11 | val codename: String?, 12 | val incremental: String?, 13 | val preview_sdk_int: Int?, 14 | val release: String?, 15 | val sdk_int: Int?, 16 | val security_patch: String? 17 | ) { 18 | companion object { 19 | @RequiresApi(Build.VERSION_CODES.M) 20 | fun detect(context: Context): XyoSystemInfoOs { 21 | return XyoSystemInfoOs( 22 | Build.VERSION.BASE_OS, 23 | Build.VERSION.CODENAME, 24 | Build.VERSION.INCREMENTAL, 25 | Build.VERSION.PREVIEW_SDK_INT, 26 | Build.VERSION.RELEASE, 27 | Build.VERSION.SDK_INT, 28 | Build.VERSION.SECURITY_PATCH 29 | ) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/system/info/XyoSystemInfoWitness.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.system.info 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import network.xyo.client.witness.XyoWitness 7 | import network.xyo.client.account.Account 8 | 9 | @RequiresApi(Build.VERSION_CODES.M) 10 | class XyoSystemInfoWitness(address: network.xyo.client.account.model.Account = Account.random()) : XyoWitness( 11 | fun (context: Context): List { 12 | return listOf(SystemInfoPayload.detect(context)) 13 | }, 14 | address 15 | ) -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/types/HandlerInterface.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.types 2 | 3 | import android.content.Context 4 | import network.xyo.client.account.model.Account 5 | 6 | interface WitnessHandlerInterface { 7 | suspend fun witness(context: Context, nodeUrlsAndAccounts: ArrayList>): WitnessResult 8 | } -------------------------------------------------------------------------------- /sdk/src/main/java/network/xyo/client/witness/types/Result.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.client.witness.types 2 | 3 | sealed class WitnessResult { 4 | data class Success(val data: T) : WitnessResult() 5 | data class Error(val exception: MutableList) : WitnessResult() 6 | } -------------------------------------------------------------------------------- /sdk/version.properties: -------------------------------------------------------------------------------- 1 | #Fri May 14 18:59:11 PDT 2021 2 | VERSION_PATCH=1 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "XYO Client" 2 | include ':sdk' 3 | --------------------------------------------------------------------------------