[#971] Refactor Rust FFI to separate module

---------

Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
Carter Jernigan 2023-05-18 07:36:15 -04:00 committed by GitHub
parent ca0e69d97f
commit bc19797125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 1215 additions and 916 deletions

View File

@ -357,6 +357,9 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: android ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: android
run: | run: |
# Due to issues with the Rust integration, building the release APK requires running the task twice to
# ensure the native libraries are bundled in the APK
./gradlew assembleRelease
./gradlew assembleRelease ./gradlew assembleRelease
- name: Collect Artifacts - name: Collect Artifacts
timeout-minutes: 1 timeout-minutes: 1

View File

@ -0,0 +1,64 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":backend-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.backend-lib.androidTest" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,64 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":lightwallet-client-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.lightwallet-client-lib.androidTest" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,64 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":sdk-incubator-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.sdk-incubator-lib.androidTest" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,7 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="demo-app-benchmark-test:connectedBenchmarkAndroidTest" <configuration default="false" name="demo-app-benchmark-test:connectedBenchmarkAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests"> <module name="zcash-android-sdk.demo-app-benchmark-test.main" />
<module name="zcash-android-sdk.demo-app-benchmark-test" />
<option name="TESTING_TYPE" value="0" /> <option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" /> <option name="CLASS_NAME" value="" />
@ -16,8 +15,8 @@
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" /> <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" /> <option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" /> <option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" /> <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" /> <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" /> <option name="DEBUGGER_TYPE" value="Auto" />
<Auto> <Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -25,6 +24,8 @@
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto> </Auto>
<Hybrid> <Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -32,14 +33,21 @@
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid> </Hybrid>
<Java /> <Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native> <Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" /> <option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native> </Native>
<Profilers> <Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" /> <option name="ADVANCED_PROFILING_ENABLED" value="false" />

View File

@ -1,20 +1,22 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name=":darkside-test-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests"> <configuration default="false" name=":darkside-test-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.darkside-test-lib" /> <module name="zcash-android-sdk.darkside-test-lib.androidTest" />
<option name="TESTING_TYPE" value="0" /> <option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" /> <option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" /> <option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" /> <option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" /> <option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" /> <option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" /> <option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" /> <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" /> <option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" /> <option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" /> <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="api-9130115880275692386-873230" /> <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" /> <option name="DEBUGGER_TYPE" value="Auto" />
<Auto> <Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -22,6 +24,8 @@
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto> </Auto>
<Hybrid> <Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -29,20 +33,27 @@
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid> </Hybrid>
<Java /> <Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native> <Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" /> <option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native> </Native>
<Profilers> <Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" /> <option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" /> <option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" /> <option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" /> <option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" /> <option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" /> <option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers> </Profilers>

View File

@ -5,9 +5,9 @@
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" /> <option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" /> <option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" /> <option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" /> <option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="RETENTION_ENABLED" value="No" /> <option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" /> <option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" /> <option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
@ -16,6 +16,7 @@
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" /> <option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" /> <option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" /> <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" /> <option name="DEBUGGER_TYPE" value="Auto" />
<Auto> <Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -23,6 +24,8 @@
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto> </Auto>
<Hybrid> <Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -30,14 +33,21 @@
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid> </Hybrid>
<Java /> <Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native> <Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" /> <option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native> </Native>
<Profilers> <Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" /> <option name="ADVANCED_PROFILING_ENABLED" value="false" />

View File

@ -1,25 +1,20 @@
Change Log # Change Log
==========
## Unreleased ## Unreleased
- The SDK's `CompactBlockProcessor` switched from processing **all blocks in one run** mechanism to **batched blocks** - Transparent fund balances are now displayed almost immediately
processing. This was necessary for the sync state's parallelization. Example of syncing of the latest - Synchronization of shielded balances and transaction history is about 30% faster
100 blocks: - Disk space usage is reduced by about 90%
- Previously: _Download 100 blocks -> Validate 100 blocks -> Scan 100 blocks -> SYNCED_ - `Synchronizer.status` has been simplified by combining `DOWNLOADING`, `VALIDATING`, and `SCANNING` states into a single `SYNCING` state.
- Now: _10x (Download 10 blocks -> Validate 10 blocks -> Scan 10 blocks) -> SYNCED_ - `Synchronizer.progress` now returns `Flow<PercentDecimal>` instead of `Flow<Int>`. PercentDecimal is a type-safe model. Use `PercentDecimal.toPercentage()` to get a number within 0-100% scale.
- `Synchronizer.progress` now returns `Flow<PercentDecimal>` instead of `Flow<Int>`. PercentDecimal is a type-safe - `Synchronizer.clearedTransactions` has been renamed to `Synchronizer.transactions` and includes sent, received, and pending transactions. Synchronizer APIs for listing sent, received, and pending transactions have been removed. Clients can determine whether a transaction is sent, received, or pending by filtering the `TransactionOverview` objects returned by `Synchronizer.transactions`
model.
Use `PercentDecimal.toPercentage()` to get a number within 0-100% scale.
- `Synchronizer.status` now provides a new `SYNCING` state, which covers all three previous `DOWNLOADING`,
`VALIDATING`, and `SCANNING` states, which were eliminated in favor of `SYNCING` state.
## 1.17.0-beta01
- Synchronizer APIs for listing sent and received transactions have been removed.
- Synchronizer APIs for listing pending transactions have been removed, along with the `PendingTransaction` object.
- `Synchronizer.clearedTransactions` has been renamed to `Synchronizer.transactions` and includes sent, received, and pending transactions.
- `Synchronizer.send()` and `shieldFunds()` are now `suspend` functions with `Long` return values representing the ID of the newly created transaction. Errors are reported by thrown exceptions. - `Synchronizer.send()` and `shieldFunds()` are now `suspend` functions with `Long` return values representing the ID of the newly created transaction. Errors are reported by thrown exceptions.
- `DerivationTool` is now an interface, rather than an `object`, which makes it easier to inject alternative implementations into tests. To adapt to the new API, replace calls to `DerivationTool.methodName()` with `DerivationTool.getInstance().methodName()`.
- `DerivationTool` methods are no longer suspending, which should make it easier to call them in various situations. Obtaining a `DerivationTool` instance via `DerivationTool.getInstance()` frontloads the need for a suspending call.
- `DerivationTool.deriveUnifiedFullViewingKeys()` no longer has a default argument for `numberOfAccounts`. Clients should now pass `DerivationTool.DEFAULT_NUMBER_OF_ACCOUNTS` as the value. Note that the SDK does not currently have proper support for multiple accounts.
- The SDK's internals for connecting with librustzcash have been refactored to a separate Gradle module `backend-lib` (and therefore a separate artifact) which is a transitive dependency of the Zcash Android SDK. SDK consumers that use Gradle dependency locks may notice this difference, but otherwise it should be mostly an invisible change.
## 1.16.0-beta01 ## 1.16.0-beta01
(This version was only deployed as a snapshot and not released on Maven Central)
### Changed ### Changed
- The minimum supported version of Android is now API level 27. - The minimum supported version of Android is now API level 27.
@ -27,7 +22,7 @@ processing. This was necessary for the sync state's parallelization. Example of
### Changed ### Changed
- A new package `sdk-incubator-lib` is now available as a public API. This package contains experimental APIs that may be promoted to the SDK in the future. The APIs in this package are not guaranteed to be stable, and may change at any time. - A new package `sdk-incubator-lib` is now available as a public API. This package contains experimental APIs that may be promoted to the SDK in the future. The APIs in this package are not guaranteed to be stable, and may change at any time.
- `Synchronizer.refreshUtxos` now takes `Account` type as first parameter instead of transparent address of type - `Synchronizer.refreshUtxos` now takes `Account` type as first parameter instead of transparent address of type
`String`, and thus it downloads all UTXOs for the given account addresses. `String`, and thus it downloads all UTXOs for the given account addresses. The Account object provides a default `0` index Account with `Account.DEFAULT`.
## 1.14.0-beta01 ## 1.14.0-beta01
### Changed ### Changed
@ -41,6 +36,7 @@ processing. This was necessary for the sync state's parallelization. Example of
- The new networking module now provides a `LightWalletClient` for asynchronous calls. - The new networking module now provides a `LightWalletClient` for asynchronous calls.
- Most unary calls respond with the new `Response` class and its subclasses. Streaming calls will be updated - Most unary calls respond with the new `Response` class and its subclasses. Streaming calls will be updated
with the Response class later. with the Response class later.
- SDK clients should avoid using generated GRPC objects, as these are an internal implementation detail and are in process of being removed from the public API. Any clients using GRPC objects will find these have been repackaged from `cash.z.wallet.sdk.rpc` to `cash.z.wallet.sdk.internal.rpc` to signal they are not a public API.
## 1.12.0-beta01 ## 1.12.0-beta01
### Changed ### Changed
@ -122,58 +118,48 @@ processing. This was necessary for the sync state's parallelization. Example of
- `DerivationTool.deriveUnifiedViewingKeys` (use `DerivationTool.deriveUnifiedFullViewingKey` instead) - `DerivationTool.deriveUnifiedViewingKeys` (use `DerivationTool.deriveUnifiedFullViewingKey` instead)
- `DerivationTool.validateUnifiedViewingKey` - `DerivationTool.validateUnifiedViewingKey`
Version 1.9.0-beta05 ## Version 1.9.0-beta05
------------------------------------
- The minimum version of Android supported is now API 21 - The minimum version of Android supported is now API 21
- Fixed R8/ProGuard consumer rule, which eliminates a runtime crash for minified apps - Fixed R8/ProGuard consumer rule, which eliminates a runtime crash for minified apps
Version 1.9.0-beta04 ## Version 1.9.0-beta04
------------------------------------
- The SDK now stores sapling param files in `no_backup/co.electricoin.zcash` folder instead of the `cache/params` - The SDK now stores sapling param files in `no_backup/co.electricoin.zcash` folder instead of the `cache/params`
folder. Besides that, `SaplingParamTool` also does validation of downloaded sapling param file hash and size. folder. Besides that, `SaplingParamTool` also does validation of downloaded sapling param file hash and size.
**No action required from client app**. **No action required from client app**.
Version 1.9.0-beta03 ## Version 1.9.0-beta03
------------------------------------
- No changes; this release is a test of a new deployment process - No changes; this release is a test of a new deployment process
Version 1.9.0-beta02 ## Version 1.9.0-beta02
------------------------------------
- The SDK now stores database files in `no_backup/co.electricoin.zcash` folder instead of the `database` folder. **No action required from client app**. - The SDK now stores database files in `no_backup/co.electricoin.zcash` folder instead of the `database` folder. **No action required from client app**.
Version 1.9.0-beta01 ## Version 1.9.0-beta01
------------------------------------
- Split `ZcashNetwork` into `ZcashNetwork` and `LightWalletEndpoint` to decouple network and server configuration - Split `ZcashNetwork` into `ZcashNetwork` and `LightWalletEndpoint` to decouple network and server configuration
- Gradle 7.5.1 - Gradle 7.5.1
- Updated checkpoints - Updated checkpoints
Version 1.8.0-beta01 ## Version 1.8.0-beta01
------------------------------------
- Enabled automated unit tests run on the CI server - Enabled automated unit tests run on the CI server
- Added `BlockHeight` typesafe object to represent block heights - Added `BlockHeight` typesafe object to represent block heights
- Significantly reduced memory usage, fixing potential OutOfMemoryError during block download - Significantly reduced memory usage, fixing potential OutOfMemoryError during block download
- Kotlin 1.7.10 - Kotlin 1.7.10
- Updated checkpoints - Updated checkpoints
Version 1.7.0-beta01 ## Version 1.7.0-beta01
------------------------------------
- Added `Zatoshi` typesafe object to represent amounts. - Added `Zatoshi` typesafe object to represent amounts.
- Kotlin 1.7.0 - Kotlin 1.7.0
Version 1.6.0-beta01 ## Version 1.6.0-beta01
------------------------------------
- Updated checkpoints for Mainnet and Testnet - Updated checkpoints for Mainnet and Testnet
- Fix: SDK can now be used on Intel x86_64 emulators - Fix: SDK can now be used on Intel x86_64 emulators
- Prevent R8 warnings for apps consuming the SDK - Prevent R8 warnings for apps consuming the SDK
Version 1.5.0-beta01 ## Version 1.5.0-beta01
------------------------------------
- New: Transactions can be created after NU5 activation. - New: Transactions can be created after NU5 activation.
- New: Support for receiving v5 transactions. - New: Support for receiving v5 transactions.
- Known issues: The SDK will not run on Intel 64-bit API 31+ emulators. Workarounds include: testing on a physical device, using an older 32-bit API version Intel emulator, or using an ARM emulator. - Known issues: The SDK will not run on Intel 64-bit API 31+ emulators. Workarounds include: testing on a physical device, using an older 32-bit API version Intel emulator, or using an ARM emulator.
Version 1.4.0-beta01 ## Version 1.4.0-beta01
------------------------------------
- Main entrypoint to the SDK has changed. See [MIGRATIONS.md](MIGRATIONS.md) - Main entrypoint to the SDK has changed. See [MIGRATIONS.md](MIGRATIONS.md)
- The minimum version of Android supported is now API 19 - The minimum version of Android supported is now API 19
- Updated checkpoints for Mainnet and Testnet - Updated checkpoints for Mainnet and Testnet
@ -182,83 +168,68 @@ Version 1.4.0-beta01
- Updated dependencies, including Kotlin 1.6.21, Coroutines 1.6.1, GRPC 1.46.0, Okio 3.1.0, NDK 23 - Updated dependencies, including Kotlin 1.6.21, Coroutines 1.6.1, GRPC 1.46.0, Okio 3.1.0, NDK 23
- Known issues: The SDK will not run on Intel 64-bit API 31+ emulators. Workarounds include: testing on a physical device, using an older 32-bit API version Intel emulator, or using an ARM emulator. - Known issues: The SDK will not run on Intel 64-bit API 31+ emulators. Workarounds include: testing on a physical device, using an older 32-bit API version Intel emulator, or using an ARM emulator.
Version 1.3.0-beta20 ## Version 1.3.0-beta20
------------------------------------
- New: Updated checkpoints for Mainnet and Testnet - New: Updated checkpoints for Mainnet and Testnet
Version 1.3.0-beta19 ## Version 1.3.0-beta19
------------------------------------
- New: Updated checkpoints for Mainnet and Testnet - New: Updated checkpoints for Mainnet and Testnet
- Fix: Repackaged internal classes to a new `internal` package name - Fix: Repackaged internal classes to a new `internal` package name
- Fix: Testnet checkpoints have been corrected - Fix: Testnet checkpoints have been corrected
- Updated dependencies - Updated dependencies
Version 1.3.0-beta18 ## Version 1.3.0-beta18
------------------------------------
- Fix: Corrected logic when calculating birthdates for wallets with zero received notes. - Fix: Corrected logic when calculating birthdates for wallets with zero received notes.
Version 1.3.0-beta17 ## Version 1.3.0-beta17
------------------------------------
- Fix: Autoshielding confirmation count error so funds are available after 10 confirmations. - Fix: Autoshielding confirmation count error so funds are available after 10 confirmations.
- New: Allow developers to enable Rust logs. - New: Allow developers to enable Rust logs.
- New: Accept GZIP compression from lightwalletd. - New: Accept GZIP compression from lightwalletd.
- New: Reduce the UTXO retry time. - New: Reduce the UTXO retry time.
Version 1.3.0-beta16 ## Version 1.3.0-beta16
------------------------------------
- Fix: Gracefully handle failures while fetching UTXOs. - Fix: Gracefully handle failures while fetching UTXOs.
- New: Expose StateFlows for balances. - New: Expose StateFlows for balances.
- New: Make it easier to subscribe to transactions. - New: Make it easier to subscribe to transactions.
- New: Cleanup default logs. - New: Cleanup default logs.
- New: Convenience functions for WalletBalance objects. - New: Convenience functions for WalletBalance objects.
Version 1.3.0-beta15 ## Version 1.3.0-beta15
------------------------------------
- Fix: Increase reconnection attempts on failed app restart. - Fix: Increase reconnection attempts on failed app restart.
- New: Updated checkpoints for testnet and mainnet. - New: Updated checkpoints for testnet and mainnet.
Version 1.3.0-beta14 ## Version 1.3.0-beta14
------------------------------------
- New: Add separate flows for sapling, orchard and tranparent balances. - New: Add separate flows for sapling, orchard and tranparent balances.
- Fix: Continue troubleshooting and fixing server disconnects. - Fix: Continue troubleshooting and fixing server disconnects.
- Updated dependencies. - Updated dependencies.
Version 1.3.0-beta12 ## Version 1.3.0-beta12
------------------------------------
- New: Expose network height as StateFlow. - New: Expose network height as StateFlow.
- Fix: Reconnect to lightwalletd when a service exception occurs. - Fix: Reconnect to lightwalletd when a service exception occurs.
Version 1.3.0-beta11 ## Version 1.3.0-beta11
------------------------------------
- Fix: Remove unused flag that was breaking new wallet creation for some wallets. - Fix: Remove unused flag that was breaking new wallet creation for some wallets.
Version 1.3.0-beta10 ## Version 1.3.0-beta10
------------------------------------
- Fix: Make it safe to call the new prepare function more than once. - Fix: Make it safe to call the new prepare function more than once.
Version 1.3.0-beta09 ## Version 1.3.0-beta09
------------------------------------
- New: Add quick rewind feature, which makes it easy to rescan blocks after an upgrade. - New: Add quick rewind feature, which makes it easy to rescan blocks after an upgrade.
- Fix: Repair complex data migration bug that caused crashes on upgrades. - Fix: Repair complex data migration bug that caused crashes on upgrades.
Version 1.3.0-beta08 ## Version 1.3.0-beta08
------------------------------------
- Fix: Disable librustzcash logs by default. - Fix: Disable librustzcash logs by default.
Version 1.3.0-beta07 ## Version 1.3.0-beta07
------------------------------------
- Fix: Address issues with key migration, allowing wallets to reset viewing keys, when needed. - Fix: Address issues with key migration, allowing wallets to reset viewing keys, when needed.
Version 1.3.0-beta06 ## Version 1.3.0-beta06
------------------------------------
- Fix: Repair publishing so that AARs work on Windows machines [issue #222]. - Fix: Repair publishing so that AARs work on Windows machines [issue #222].
- Fix: Incorrect BranchId on 32-bit devics [issue #224]. - Fix: Incorrect BranchId on 32-bit devics [issue #224].
- Fix: Rescan should not go beyond the wallet checkpoint. - Fix: Rescan should not go beyond the wallet checkpoint.
- New: Drop Android Jetifier since it is no longer used. - New: Drop Android Jetifier since it is no longer used.
- Updated checkpoints, improved tests (added Test Suites) and better error messages. - Updated checkpoints, improved tests (added Test Suites) and better error messages.
Version 1.3.0-beta05 ## Version 1.3.0-beta05
------------------------------------
- Major: Consolidate product flavors into one library for the SDK instead of two. - Major: Consolidate product flavors into one library for the SDK instead of two.
- Major: Integrates with latest Librustzcash including full Data Access API support. - Major: Integrates with latest Librustzcash including full Data Access API support.
- Major: Move off of JCenter and onto Maven Central. - Major: Move off of JCenter and onto Maven Central.
@ -283,44 +254,36 @@ Version 1.3.0-beta05
- New: Derive sapling activation height from the active network. - New: Derive sapling activation height from the active network.
- New: Latest checkpoints for mainnet and testnet. - New: Latest checkpoints for mainnet and testnet.
Version 1.2.1-beta04 ## Version 1.2.1-beta04
------------------------------------
- New: Updated to latest versions of grpc, grpc-okhttp and protoc - New: Updated to latest versions of grpc, grpc-okhttp and protoc
- Fix: Addresses root issue of Android 11 crash on SSL sockets - Fix: Addresses root issue of Android 11 crash on SSL sockets
Version 1.2.1-beta03 ## Version 1.2.1-beta03
------------------------------------
- New: Implements ZIP-313, reducing the default fee from 10,000 to 1,000 zats. - New: Implements ZIP-313, reducing the default fee from 10,000 to 1,000 zats.
- Fix: 80% reduction in build warnings from 90 -> 18 and improved docs [Credit: @herou]. - Fix: 80% reduction in build warnings from 90 -> 18 and improved docs [Credit: @herou].
Version 1.2.1-beta02 ## Version 1.2.1-beta02
------------------------------------
- New: Improve birthday configuration and config functions. - New: Improve birthday configuration and config functions.
- Fix: Broken layout in demo app transaction list. - Fix: Broken layout in demo app transaction list.
Version 1.2.1-beta01 ## Version 1.2.1-beta01
------------------------------------
- New: Added latest checkpoints for testnet and mainnet. - New: Added latest checkpoints for testnet and mainnet.
- New: Added display name for Canopy. - New: Added display name for Canopy.
- New: Update to the latest lightwalletd service definition. - New: Update to the latest lightwalletd service definition.
- Fix: Convert Initializer.Builder to Initializer.Config to simplify the constructors. - Fix: Convert Initializer.Builder to Initializer.Config to simplify the constructors.
Version 1.2.0-beta01 ## Version 1.2.0-beta01
------------------------------------
- New: Added ability to erase initializer data. - New: Added ability to erase initializer data.
- Fix: Updated to latest librustzcash, fixing send functionality on Canopy. - Fix: Updated to latest librustzcash, fixing send functionality on Canopy.
Version 1.1.0-beta10 ## Version 1.1.0-beta10
------------------------------------
- New: Modified visibility on a few things to facilitate partner integrations. - New: Modified visibility on a few things to facilitate partner integrations.
Version 1.1.0-beta08 ## Version 1.1.0-beta08
------------------------------------
- Fix: Publishing has been corrected by jcenter's support team. - Fix: Publishing has been corrected by jcenter's support team.
- New: Minor improvement to initializer - New: Minor improvement to initializer
Version 1.1.0-beta05 ## Version 1.1.0-beta05
------------------------------------
- New: Synchronizer can now be started with just a viewing key. - New: Synchronizer can now be started with just a viewing key.
- New: Initializer improvements. - New: Initializer improvements.
- New: Added tool for loading checkpoints. - New: Added tool for loading checkpoints.
@ -330,8 +293,7 @@ Version 1.1.0-beta05
- Fix: Broken testnet demo app. - Fix: Broken testnet demo app.
- Fix: Publishing configuration. - Fix: Publishing configuration.
Version 1.1.0-beta04 ## Version 1.1.0-beta04
------------------------------------
- New: Add support for canopy on testnet. - New: Add support for canopy on testnet.
- New: Change the default lightwalletd server. - New: Change the default lightwalletd server.
- New: Add lightwalletd service for fetching t-addr transactions. - New: Add lightwalletd service for fetching t-addr transactions.
@ -340,8 +302,7 @@ Version 1.1.0-beta04
- New: Added new checkpoints. - New: Added new checkpoints.
- Fix: Minor enhancements. - Fix: Minor enhancements.
Version 1.1.0-beta03 ## Version 1.1.0-beta03
------------------------------------
- New: Add robust support for transaction cancellation. - New: Add robust support for transaction cancellation.
- New: Update to latest version of librustzcash. - New: Update to latest version of librustzcash.
- New: Expand test support. - New: Expand test support.

View File

@ -1,27 +1,7 @@
Troubleshooting Migrations Troubleshooting Migrations
========== ==========
Migration to Version 1.17 Note: Going forward, migrations will be incorporated into the CHANGELOG.md.
---------------------------------
Synchronizer APIs for listing sent and received transactions have been removed. Clients should use `Synchronizer.transactions`, filtering on the field `TransactionOverview.isSentTransaction`.
Synchronizer APIs for pending transactions have been removed. Clients should use `Synchronizer.transactions`, filtering on the field `TransactionOverview.transactionState`
The Synchronizer APIs for sending transactions (`send()` and `shieldFunds()`) are now suspend functions that return `Long` which contains the ID of the newly created transaction. To monitor for changes to pending transactions, observe `Synchronizer.transactions`. If a failure occurs, `send()` and `shieldFunds()` throw exceptions.
Migration to Version 1.15
---------------------------------
The updated `Synchronizer.refreshUtxos` is now supposed to be called with `Account` parameter instead of `String` address parameter. The Account object provides a default `0` index Account with `Account.DEFAULT`.
Migration to Version 1.13
---------------------------------
Update usages of `z.cash.ecc.android.sdk.model.LightWalletEndpoint` to `co.electriccoin.lightwallet.client.model.LightWalletEndpoint`.
SDK clients should avoid using generated GRPC objects, as these are an internal implementation detail and are in process of being removed from the public API. Any clients using GRPC objects will find these have been repackaged from `cash.z.wallet.sdk.rpc` to `cash.z.wallet.sdk.internal.rpc` to signal they are not a public API.
Migration to Version 1.12
---------------------------------
`TransactionOverview`, `Transaction.Sent`, and `Transaction.Received` have been updated to reflect that `minedHeight` is nullable.
Migration to Version 1.11 Migration to Version 1.11
--------------------------------- ---------------------------------

View File

@ -0,0 +1,124 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("zcash-sdk.android-conventions")
id("org.jetbrains.dokka")
id("org.mozilla.rust-android-gradle.rust-android")
id("wtf.emulator.gradle")
id("zcash-sdk.emulator-wtf-conventions")
id("maven-publish")
id("signing")
id("zcash-sdk.publishing-conventions")
}
// Publishing information
val myVersion = project.property("LIBRARY_VERSION").toString()
val myArtifactId = "zcash-android-backend"
publishing {
publications {
publications.withType<MavenPublication>().all {
artifactId = myArtifactId
}
}
}
android {
namespace = "cash.z.ecc.android.backend"
useLibrary("android.test.runner")
defaultConfig {
consumerProguardFiles("proguard-consumer.txt")
}
buildTypes {
getByName("debug").apply {
// test builds exceed the dex limit because they pull in large test libraries
isMinifyEnabled = false
}
getByName("release").apply {
isMinifyEnabled = project.property("IS_MINIFY_SDK_ENABLED").toString().toBoolean()
proguardFiles.addAll(
listOf(
getDefaultProguardFile("proguard-android-optimize.txt"),
File("proguard-project.txt")
)
)
}
create("benchmark") {
// We provide the extra benchmark build type just for benchmarking purposes
initWith(buildTypes.getByName("release"))
matchingFallbacks += listOf("release")
}
}
lint {
baseline = File("lint-baseline.xml")
}
}
cargo {
module = "."
libname = "zcashwalletsdk"
targets = listOf(
"arm",
"arm64",
"x86",
"x86_64"
)
val minSdkVersion = project.property("ANDROID_MIN_SDK_VERSION").toString().toInt()
apiLevels = mapOf(
"arm" to minSdkVersion,
"arm64" to minSdkVersion,
"x86" to minSdkVersion,
"x86_64" to minSdkVersion,
)
profile = "release"
prebuiltToolchains = true
}
dependencies {
api(projects.lightwalletClientLib)
implementation(libs.androidx.annotation)
// Kotlin
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
androidTestImplementation(libs.androidx.multidex)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.kotlin.test)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.zcashwalletplgn)
androidTestImplementation(libs.bip39)
}
tasks {
/*
* The Mozilla Rust Gradle plugin caches the native build data under the "target" directory,
* which does not normally get deleted during a clean. The following task and dependency solves
* that.
*/
getByName<Delete>("clean").dependsOn(create<Delete>("cleanRustBuildOutput") {
delete("target")
})
}
project.afterEvaluate {
val cargoTask = tasks.getByName("cargoBuild")
tasks.getByName("javaPreCompileDebug").dependsOn(cargoTask)
tasks.getByName("javaPreCompileRelease").dependsOn(cargoTask)
}
fun MinimalExternalModuleDependency.asCoordinateString() =
"${module.group}:${module.name}:${versionConstraint.displayName}"

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="all" version="7.4.0">
</issues>

4
backend-lib/lint.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
</lint>

View File

@ -0,0 +1,3 @@
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}

View File

@ -0,0 +1,18 @@
# This improves obfuscation and moves non-public classes to their own namespace.
-repackageclasses 'cash.z.ecc.android.sdk.internal'
# This makes it easier to autocomplete methods in an IDE using this obfuscated library.
-keepparameternames
# The ProGuard manual recommends keeping these attributes for libraries.
-keepattributes EnclosingMethod,InnerClasses,Signature,Exceptions,*Annotation*
# Ensure that stacktraces are reversible.
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
# Keep the public interface of the library.
-keep public class cash.z.ecc.android.sdk.internal.Backend { public protected *; }
-keep public class cash.z.ecc.android.sdk.internal.Derivation { public protected *; }
-keep public class cash.z.ecc.android.sdk.internal.jni.RustBackend { public protected *; }
-keep public class cash.z.ecc.android.sdk.internal.model.* { public protected *; }

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- For code coverage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:name="androidx.multidex.MultiDexApplication" />
</manifest>

View File

@ -0,0 +1,26 @@
package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.bip39.Mnemonics
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertContentEquals
class RustDerivationToolTest {
companion object {
private const val SEED_PHRASE =
"kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner"
}
@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun create_spending_key_does_not_mutate_passed_bytes() = runTest {
val bytesOne = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
val bytesTwo = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
RustDerivationTool.new().deriveUnifiedSpendingKey(bytesOne, networkId = 1, accountIndex = 0)
assertContentEquals(bytesTwo, bytesOne)
}
}

View File

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,9 +1,7 @@
package cash.z.ecc.android.sdk.jni package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.io.File
/** /**
* Contract defining the exposed capabilities of the Rust backend. * Contract defining the exposed capabilities of the Rust backend.
@ -14,11 +12,9 @@ import java.io.File
// TODO [#920]: Tweak RustBackend public APIs to have void return values // TODO [#920]: Tweak RustBackend public APIs to have void return values
// TODO [#920]: https://github.com/zcash/zcash-android-wallet-sdk/issues/920 // TODO [#920]: https://github.com/zcash/zcash-android-wallet-sdk/issues/920
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
internal interface Backend { interface Backend {
val network: ZcashNetwork val networkId: Int
val saplingParamDir: File
suspend fun initBlockMetaDb(): Int suspend fun initBlockMetaDb(): Int
@ -53,7 +49,7 @@ internal interface Backend {
suspend fun initDataDb(seed: ByteArray?): Int suspend fun initDataDb(seed: ByteArray?): Int
suspend fun createAccount(seed: ByteArray): UnifiedSpendingKeyJni suspend fun createAccount(seed: ByteArray): JniUnifiedSpendingKey
fun isValidShieldedAddr(addr: String): Boolean fun isValidShieldedAddr(addr: String): Boolean
@ -112,6 +108,6 @@ internal interface Backend {
index: Int, index: Int,
script: ByteArray, script: ByteArray,
value: Long, value: Long,
height: BlockHeight height: Long
): Boolean ): Boolean
} }

View File

@ -0,0 +1,44 @@
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
interface Derivation {
fun deriveUnifiedAddress(
viewingKey: String,
networkId: Int
): String
fun deriveUnifiedAddress(
seed: ByteArray,
networkId: Int,
accountIndex: Int
): String
fun deriveUnifiedSpendingKey(
seed: ByteArray,
networkId: Int,
accountIndex: Int
): JniUnifiedSpendingKey
/**
* @return a unified full viewing key.
*/
fun deriveUnifiedFullViewingKey(
usk: JniUnifiedSpendingKey,
networkId: Int
): String
/**
* @param numberOfAccounts Use [DEFAULT_NUMBER_OF_ACCOUNTS] to derive a single key.
* @return an array of unified full viewing keys, one for each account.
*/
fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
networkId: Int,
numberOfAccounts: Int
): Array<String>
companion object {
const val DEFAULT_NUMBER_OF_ACCOUNTS = 1
}
}

View File

@ -16,7 +16,7 @@ internal object SdkExecutors {
val DATABASE_IO = Executors.newSingleThreadExecutor() val DATABASE_IO = Executors.newSingleThreadExecutor()
} }
internal object SdkDispatchers { object SdkDispatchers {
/** /**
* Dispatcher used for database IO that's shared with the Rust native library. * Dispatcher used for database IO that's shared with the Rust native library.
*/ */

View File

@ -9,15 +9,15 @@ import java.io.FileInputStream
import java.security.DigestInputStream import java.security.DigestInputStream
import java.security.MessageDigest import java.security.MessageDigest
internal suspend fun File.canWriteSuspend() = withContext(Dispatchers.IO) { canWrite() } suspend fun File.canWriteSuspend() = withContext(Dispatchers.IO) { canWrite() }
suspend fun File.createNewFileSuspend() = withContext(Dispatchers.IO) { createNewFile() } suspend fun File.createNewFileSuspend() = withContext(Dispatchers.IO) { createNewFile() }
internal suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() } suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() }
suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() } suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() }
internal suspend fun File.existsSuspend() = withContext(Dispatchers.IO) { exists() } suspend fun File.existsSuspend() = withContext(Dispatchers.IO) { exists() }
suspend fun File.inputStreamSuspend(): FileInputStream = withContext(Dispatchers.IO) { inputStream() } suspend fun File.inputStreamSuspend(): FileInputStream = withContext(Dispatchers.IO) { inputStream() }
@ -27,11 +27,11 @@ suspend fun File.listFilesSuspend(): Array<File>? = withContext(Dispatchers.IO)
suspend fun File.listSuspend(): Array<String>? = withContext(Dispatchers.IO) { list() } suspend fun File.listSuspend(): Array<String>? = withContext(Dispatchers.IO) { list() }
internal suspend fun File.mkdirsSuspend() = withContext(Dispatchers.IO) { mkdirs() } suspend fun File.mkdirsSuspend() = withContext(Dispatchers.IO) { mkdirs() }
suspend fun File.readBytesSuspend() = withContext(Dispatchers.IO) { readBytes() } suspend fun File.readBytesSuspend() = withContext(Dispatchers.IO) { readBytes() }
internal suspend fun File.renameToSuspend(dest: File) = withContext(Dispatchers.IO) { renameTo(dest) } suspend fun File.renameToSuspend(dest: File) = withContext(Dispatchers.IO) { renameTo(dest) }
suspend fun File.writeBytesSuspend(byteArray: ByteArray) = withContext(Dispatchers.IO) { writeBytes(byteArray) } suspend fun File.writeBytesSuspend(byteArray: ByteArray) = withContext(Dispatchers.IO) { writeBytes(byteArray) }

View File

@ -1,12 +1,10 @@
package cash.z.ecc.android.sdk.jni package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.sdk.internal.Twig
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import kotlin.system.measureTimeMillis
/** /**
* Loads a native library once. This class is thread-safe. * Loads a native library once. This class is thread-safe.
@ -39,14 +37,7 @@ internal class NativeLibraryLoader(private val libraryName: String) {
private suspend fun loadNativeLibrary() { private suspend fun loadNativeLibrary() {
runCatching { runCatching {
Twig.debug { "Loading native library $libraryName" } loadLibrarySuspend(libraryName)
val loadTimeMillis = measureTimeMillis {
loadLibrarySuspend(libraryName)
}
Twig.debug { "Loading native library took $loadTimeMillis milliseconds" }
isLoaded.set(true) isLoaded.set(true)
}.onFailure { }.onFailure {
// Fail fast, because this is not a recoverable error // Fail fast, because this is not a recoverable error

View File

@ -1,13 +1,11 @@
package cash.z.ecc.android.sdk.jni package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.sdk.internal.SaplingParamTool import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.SdkDispatchers import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
@ -17,12 +15,12 @@ import java.io.File
* should be used such as Wallet.kt or CompactBlockProcessor.kt. * should be used such as Wallet.kt or CompactBlockProcessor.kt.
*/ */
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
internal class RustBackend private constructor( class RustBackend private constructor(
override val network: ZcashNetwork, override val networkId: Int,
val birthdayHeight: BlockHeight, private val dataDbFile: File,
val dataDbFile: File, private val fsBlockDbRoot: File,
val fsBlockDbRoot: File, private val saplingSpendFile: File,
override val saplingParamDir: File private val saplingOutputFile: File,
) : Backend { ) : Backend {
/** /**
@ -38,16 +36,14 @@ internal class RustBackend private constructor(
var cacheClearResult = true var cacheClearResult = true
var dataClearResult = true var dataClearResult = true
if (clearCache) { if (clearCache) {
Twig.debug { "Deleting the cache files..." }
fsBlockDbRoot.deleteRecursivelySuspend().also { result -> fsBlockDbRoot.deleteRecursivelySuspend().also { result ->
Twig.debug { "Deletion of the cache files ${if (result) "succeeded" else "failed"}!" } // Twig.debug { "Deletion of the cache files ${if (result) "succeeded" else "failed"}!" }
cacheClearResult = result cacheClearResult = result
} }
} }
if (clearDataDb) { if (clearDataDb) {
Twig.debug { "Deleting the data database..." }
dataDbFile.deleteSuspend().also { result -> dataDbFile.deleteSuspend().also { result ->
Twig.debug { "Deletion of the data database ${if (result) "succeeded" else "failed"}!" } // Twig.debug { "Deletion of the data database ${if (result) "succeeded" else "failed"}!" }
dataClearResult = result dataClearResult = result
} }
} }
@ -68,16 +64,16 @@ internal class RustBackend private constructor(
initDataDb( initDataDb(
dataDbFile.absolutePath, dataDbFile.absolutePath,
seed, seed,
networkId = network.id networkId = networkId
) )
} }
override suspend fun createAccount(seed: ByteArray): UnifiedSpendingKeyJni { override suspend fun createAccount(seed: ByteArray): JniUnifiedSpendingKey {
return withContext(SdkDispatchers.DATABASE_IO) { return withContext(SdkDispatchers.DATABASE_IO) {
createAccount( createAccount(
dataDbFile.absolutePath, dataDbFile.absolutePath,
seed, seed,
networkId = network.id networkId = networkId
) )
} }
} }
@ -90,7 +86,7 @@ internal class RustBackend private constructor(
initAccountsTableWithKeys( initAccountsTableWithKeys(
dataDbFile.absolutePath, dataDbFile.absolutePath,
keys, keys,
networkId = network.id networkId = networkId
) )
} }
} }
@ -108,7 +104,7 @@ internal class RustBackend private constructor(
checkpointHash, checkpointHash,
checkpointTime, checkpointTime,
checkpointSaplingTree, checkpointSaplingTree,
networkId = network.id networkId = networkId
) )
} }
} }
@ -118,7 +114,7 @@ internal class RustBackend private constructor(
getCurrentAddress( getCurrentAddress(
dataDbFile.absolutePath, dataDbFile.absolutePath,
account, account,
networkId = network.id networkId = networkId
) )
} }
@ -131,7 +127,7 @@ internal class RustBackend private constructor(
listTransparentReceivers( listTransparentReceivers(
dbDataPath = dataDbFile.absolutePath, dbDataPath = dataDbFile.absolutePath,
account = account, account = account,
networkId = network.id networkId = networkId
).asList() ).asList()
} }
} }
@ -141,7 +137,7 @@ internal class RustBackend private constructor(
getBalance( getBalance(
dataDbFile.absolutePath, dataDbFile.absolutePath,
account, account,
networkId = network.id networkId = networkId
) )
} }
@ -153,7 +149,7 @@ internal class RustBackend private constructor(
getVerifiedBalance( getVerifiedBalance(
dbDataPath = dataDbFile.absolutePath, dbDataPath = dataDbFile.absolutePath,
account = account, account = account,
networkId = network.id networkId = networkId
) )
} }
@ -165,7 +161,7 @@ internal class RustBackend private constructor(
getReceivedMemoAsUtf8( getReceivedMemoAsUtf8(
dataDbFile.absolutePath, dataDbFile.absolutePath,
idNote, idNote,
networkId = network.id networkId = networkId
) )
} }
@ -174,7 +170,7 @@ internal class RustBackend private constructor(
getSentMemoAsUtf8( getSentMemoAsUtf8(
dataDbFile.absolutePath, dataDbFile.absolutePath,
idNote, idNote,
networkId = network.id networkId = networkId
) )
} }
@ -219,7 +215,7 @@ internal class RustBackend private constructor(
dbCachePath = fsBlockDbRoot.absolutePath, dbCachePath = fsBlockDbRoot.absolutePath,
dbDataPath = dataDbFile.absolutePath, dbDataPath = dataDbFile.absolutePath,
limit = limit ?: -1, limit = limit ?: -1,
networkId = network.id networkId = networkId
) )
if (-1L == validationResult) { if (-1L == validationResult) {
@ -234,7 +230,7 @@ internal class RustBackend private constructor(
getVerifiedTransparentBalance( getVerifiedTransparentBalance(
dataDbFile.absolutePath, dataDbFile.absolutePath,
address, address,
networkId = network.id networkId = networkId
) )
} }
@ -243,7 +239,7 @@ internal class RustBackend private constructor(
getTotalTransparentBalance( getTotalTransparentBalance(
dataDbFile.absolutePath, dataDbFile.absolutePath,
address, address,
networkId = network.id networkId = networkId
) )
} }
@ -252,7 +248,7 @@ internal class RustBackend private constructor(
getNearestRewindHeight( getNearestRewindHeight(
dataDbFile.absolutePath, dataDbFile.absolutePath,
height, height,
networkId = network.id networkId = networkId
) )
} }
@ -266,7 +262,7 @@ internal class RustBackend private constructor(
rewindToHeight( rewindToHeight(
dataDbFile.absolutePath, dataDbFile.absolutePath,
height, height,
networkId = network.id networkId = networkId
) )
} }
@ -276,7 +272,7 @@ internal class RustBackend private constructor(
fsBlockDbRoot.absolutePath, fsBlockDbRoot.absolutePath,
dataDbFile.absolutePath, dataDbFile.absolutePath,
limit ?: -1, limit ?: -1,
networkId = network.id networkId = networkId
) )
} }
} }
@ -286,7 +282,7 @@ internal class RustBackend private constructor(
decryptAndStoreTransaction( decryptAndStoreTransaction(
dataDbFile.absolutePath, dataDbFile.absolutePath,
tx, tx,
networkId = network.id networkId = networkId
) )
} }
@ -303,9 +299,9 @@ internal class RustBackend private constructor(
to, to,
value, value,
memo ?: ByteArray(0), memo ?: ByteArray(0),
File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath, spendParamsPath = saplingSpendFile.absolutePath,
File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath, outputParamsPath = saplingOutputFile.absolutePath,
networkId = network.id, networkId = networkId,
useZip317Fees = IS_USE_ZIP_317_FEES useZip317Fees = IS_USE_ZIP_317_FEES
) )
} }
@ -315,15 +311,14 @@ internal class RustBackend private constructor(
unifiedSpendingKey: ByteArray, unifiedSpendingKey: ByteArray,
memo: ByteArray? memo: ByteArray?
): Long { ): Long {
Twig.debug { "TMP: shieldToAddress with db path: $dataDbFile, ${memo?.size}" }
return withContext(SdkDispatchers.DATABASE_IO) { return withContext(SdkDispatchers.DATABASE_IO) {
shieldToAddress( shieldToAddress(
dataDbFile.absolutePath, dataDbFile.absolutePath,
unifiedSpendingKey, unifiedSpendingKey,
memo ?: ByteArray(0), memo ?: ByteArray(0),
File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath, spendParamsPath = saplingSpendFile.absolutePath,
File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath, outputParamsPath = saplingOutputFile.absolutePath,
networkId = network.id, networkId = networkId,
useZip317Fees = IS_USE_ZIP_317_FEES useZip317Fees = IS_USE_ZIP_317_FEES
) )
} }
@ -335,7 +330,7 @@ internal class RustBackend private constructor(
index: Int, index: Int,
script: ByteArray, script: ByteArray,
value: Long, value: Long,
height: BlockHeight height: Long
): Boolean = withContext(SdkDispatchers.DATABASE_IO) { ): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
putUtxo( putUtxo(
dataDbFile.absolutePath, dataDbFile.absolutePath,
@ -344,22 +339,22 @@ internal class RustBackend private constructor(
index, index,
script, script,
value, value,
height.value, height,
networkId = network.id networkId = networkId
) )
} }
override fun isValidShieldedAddr(addr: String) = override fun isValidShieldedAddr(addr: String) =
isValidShieldedAddress(addr, networkId = network.id) isValidShieldedAddress(addr, networkId = networkId)
override fun isValidTransparentAddr(addr: String) = override fun isValidTransparentAddr(addr: String) =
isValidTransparentAddress(addr, networkId = network.id) isValidTransparentAddress(addr, networkId = networkId)
override fun isValidUnifiedAddr(addr: String) = override fun isValidUnifiedAddr(addr: String) =
isValidUnifiedAddress(addr, networkId = network.id) isValidUnifiedAddress(addr, networkId = networkId)
override fun getBranchIdForHeight(height: Long): Long = override fun getBranchIdForHeight(height: Long): Long =
branchIdForHeight(height, networkId = network.id) branchIdForHeight(height, networkId = networkId)
// /** // /**
// * This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI // * This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI
@ -400,21 +395,21 @@ internal class RustBackend private constructor(
* Loads the library and initializes path variables. Although it is best to only call this * Loads the library and initializes path variables. Although it is best to only call this
* function once, it is idempotent. * function once, it is idempotent.
*/ */
suspend fun init( suspend fun new(
fsBlockDbRoot: File, fsBlockDbRoot: File,
dataDbFile: File, dataDbFile: File,
saplingParamsDir: File, saplingSpendFile: File,
zcashNetwork: ZcashNetwork, saplingOutputFile: File,
birthdayHeight: BlockHeight zcashNetworkId: Int,
): RustBackend { ): RustBackend {
loadLibrary() loadLibrary()
return RustBackend( return RustBackend(
zcashNetwork, zcashNetworkId,
birthdayHeight,
dataDbFile = dataDbFile, dataDbFile = dataDbFile,
fsBlockDbRoot = fsBlockDbRoot, fsBlockDbRoot = fsBlockDbRoot,
saplingParamDir = saplingParamsDir saplingSpendFile = saplingSpendFile,
saplingOutputFile = saplingOutputFile
) )
} }
@ -450,7 +445,7 @@ internal class RustBackend private constructor(
): Boolean ): Boolean
@JvmStatic @JvmStatic
private external fun createAccount(dbDataPath: String, seed: ByteArray, networkId: Int): UnifiedSpendingKeyJni private external fun createAccount(dbDataPath: String, seed: ByteArray, networkId: Int): JniUnifiedSpendingKey
@JvmStatic @JvmStatic
private external fun getCurrentAddress( private external fun getCurrentAddress(
@ -468,7 +463,7 @@ internal class RustBackend private constructor(
@JvmStatic @JvmStatic
private external fun listTransparentReceivers(dbDataPath: String, account: Int, networkId: Int): Array<String> private external fun listTransparentReceivers(dbDataPath: String, account: Int, networkId: Int): Array<String>
internal fun validateUnifiedSpendingKey(bytes: ByteArray) = fun validateUnifiedSpendingKey(bytes: ByteArray) =
isValidSpendingKey(bytes) isValidSpendingKey(bytes)
@JvmStatic @JvmStatic

View File

@ -0,0 +1,78 @@
package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
class RustDerivationTool private constructor() : Derivation {
override fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
networkId: Int,
numberOfAccounts: Int
): Array<String> =
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId)
override fun deriveUnifiedFullViewingKey(
usk: JniUnifiedSpendingKey,
networkId: Int
): String =
deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId)
override fun deriveUnifiedSpendingKey(
seed: ByteArray,
networkId: Int,
accountIndex: Int
): JniUnifiedSpendingKey = deriveSpendingKey(seed, accountIndex, networkId = networkId)
override fun deriveUnifiedAddress(seed: ByteArray, networkId: Int, accountIndex: Int): String =
deriveUnifiedAddressFromSeed(seed, accountIndex = accountIndex, networkId = networkId)
/**
* Given a Unified Full Viewing Key string, return the associated Unified Address.
*
* @param viewingKey the viewing key to use for deriving the address. The viewing key is tied to
* a specific account so no account index is required.
*
* @return the address that corresponds to the viewing key.
*/
override fun deriveUnifiedAddress(
viewingKey: String,
networkId: Int
): String =
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId)
companion object {
suspend fun new(): Derivation {
RustBackend.loadLibrary()
return RustDerivationTool()
}
@JvmStatic
private external fun deriveSpendingKey(
seed: ByteArray,
account: Int,
networkId: Int
): JniUnifiedSpendingKey
@JvmStatic
private external fun deriveUnifiedFullViewingKeysFromSeed(
seed: ByteArray,
numberOfAccounts: Int,
networkId: Int
): Array<String>
@JvmStatic
private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String
@JvmStatic
private external fun deriveUnifiedAddressFromSeed(
seed: ByteArray,
accountIndex: Int,
networkId: Int
): String
@JvmStatic
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String
}
}

View File

@ -37,7 +37,7 @@ class JniBlockMeta(
companion object { companion object {
private val UINT_RANGE = 0.toLong()..UInt.MAX_VALUE.toLong() private val UINT_RANGE = 0.toLong()..UInt.MAX_VALUE.toLong()
internal fun new(block: CompactBlockUnsafe): JniBlockMeta { fun new(block: CompactBlockUnsafe): JniBlockMeta {
return JniBlockMeta( return JniBlockMeta(
height = block.height, height = block.height,
hash = block.hash, hash = block.hash,

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.jni package cash.z.ecc.android.sdk.internal.model
import androidx.annotation.Keep import androidx.annotation.Keep
@ -12,7 +12,7 @@ import androidx.annotation.Keep
* export/import, or backup purposes. * export/import, or backup purposes.
*/ */
@Keep @Keep
class UnifiedSpendingKeyJni( class JniUnifiedSpendingKey(
val account: Int, val account: Int,
/** /**
* The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending * The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending
@ -22,27 +22,17 @@ class UnifiedSpendingKeyJni(
* inherently unstable, and only intended to be passed between the SDK and the storage * inherently unstable, and only intended to be passed between the SDK and the storage
* backend. Wallets **MUST NOT** allow this encoding to be exported or imported. * backend. Wallets **MUST NOT** allow this encoding to be exported or imported.
*/ */
private val bytes: ByteArray val bytes: ByteArray
) { ) {
/**
* The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending
* Key for [account].
*
* This encoding **MUST NOT** be exposed to users. It is an internal encoding that is
* inherently unstable, and only intended to be passed between the SDK and the storage
* backend. Wallets **MUST NOT** allow this encoding to be exported or imported.
*/
fun copyBytes() = bytes.copyOf()
// Override to prevent leaking key to logs // Override to prevent leaking key to logs
override fun toString() = "UnifiedSpendingKeyJni(account=$account)" override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)"
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as UnifiedSpendingKeyJni other as JniUnifiedSpendingKey
if (account != other.account) return false if (account != other.account) return false
if (!bytes.contentEquals(other.bytes)) return false if (!bytes.contentEquals(other.bytes)) return false

View File

@ -95,7 +95,7 @@ fn block_db(env: &JNIEnv<'_>, fsblockdb_root: JString<'_>) -> Result<FsBlockDb,
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initOnLoad( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initOnLoad(
_env: JNIEnv<'_>, _env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
) { ) {
@ -134,7 +134,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initOnLoad(
/// ///
/// Returns 0 if successful, or -1 otherwise. /// Returns 0 if successful, or -1 otherwise.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlockMetaDb( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initBlockMetaDb(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
fsblockdb_root: JString<'_>, fsblockdb_root: JString<'_>,
@ -160,7 +160,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlockMe
/// Returns 0 if successful, 1 if the seed must be provided in order to execute the requested /// Returns 0 if successful, 1 if the seed must be provided in order to execute the requested
/// migrations, or -1 otherwise. /// migrations, or -1 otherwise.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initDataDb( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initDataDb(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -197,7 +197,7 @@ fn encode_usk(
let encoded = SecretVec::new(usk.to_bytes(Era::Orchard)); let encoded = SecretVec::new(usk.to_bytes(Era::Orchard));
let bytes = env.byte_array_from_slice(encoded.expose_secret())?; let bytes = env.byte_array_from_slice(encoded.expose_secret())?;
let output = env.new_object( let output = env.new_object(
"cash/z/ecc/android/sdk/jni/UnifiedSpendingKeyJni", "cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey",
"(I[B)V", "(I[B)V",
&[ &[
JValue::Int(u32::from(account) as i32), JValue::Int(u32::from(account) as i32),
@ -241,7 +241,7 @@ fn decode_usk(env: &JNIEnv<'_>, usk: jbyteArray) -> Result<UnifiedSpendingKey, f
/// ///
/// [ZIP 316]: https://zips.z.cash/zip-0316 /// [ZIP 316]: https://zips.z.cash/zip-0316
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createAccount( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createAccount(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -268,7 +268,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createAccou
/// This should only be used in special cases for implementing wallet recovery; prefer /// This should only be used in special cases for implementing wallet recovery; prefer
/// `RustBackend.createAccount` for normal account creation purposes. /// `RustBackend.createAccount` for normal account creation purposes.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccountsTableWithKeys( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initAccountsTableWithKeys(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -315,7 +315,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccount
/// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store /// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store
/// the returned spending key in a secure fashion. /// the returned spending key in a secure fashion.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveSpendingKey( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveSpendingKey(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
seed: jbyteArray, seed: jbyteArray,
@ -340,7 +340,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveS
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedFullViewingKeysFromSeed( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKeysFromSeed(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
seed: jbyteArray, seed: jbyteArray,
@ -379,7 +379,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedAddressFromSeed( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromSeed(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
seed: jbyteArray, seed: jbyteArray,
@ -413,7 +413,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedAddressFromViewingKey( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromViewingKey(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
ufvk_string: JString<'_>, ufvk_string: JString<'_>,
@ -445,7 +445,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedFullViewingKey( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKey(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
usk: jbyteArray, usk: jbyteArray,
@ -467,7 +467,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlocksTable( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initBlocksTable(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -509,7 +509,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlocksT
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getCurrentAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getCurrentAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -553,7 +553,7 @@ impl zcash_address::TryFromRawAddress for UnifiedAddressParser {
/// Returns the transparent receiver within the given Unified Address, if any. /// Returns the transparent receiver within the given Unified Address, if any.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTransparentReceiverForUnifiedAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTransparentReceiverForUnifiedAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
ua: JString<'_>, ua: JString<'_>,
@ -593,7 +593,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTranspar
/// Returns the Sapling receiver within the given Unified Address, if any. /// Returns the Sapling receiver within the given Unified Address, if any.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSaplingReceiverForUnifiedAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getSaplingReceiverForUnifiedAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
ua: JString<'_>, ua: JString<'_>,
@ -623,7 +623,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSaplingR
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidSpendingKey( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidSpendingKey(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
usk: jbyteArray, usk: jbyteArray,
@ -636,7 +636,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidSpen
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidShieldedAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidShieldedAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
addr: JString<'_>, addr: JString<'_>,
@ -658,7 +658,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidShie
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidTransparentAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidTransparentAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
addr: JString<'_>, addr: JString<'_>,
@ -680,7 +680,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidTran
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidUnifiedAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidUnifiedAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
addr: JString<'_>, addr: JString<'_>,
@ -702,7 +702,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidUnif
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getBalance( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getBalance(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -740,7 +740,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getBalance(
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerifiedTransparentBalance( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getVerifiedTransparentBalance(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -778,7 +778,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerified
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTotalTransparentBalance( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTotalTransparentBalance(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -820,7 +820,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTotalTra
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerifiedBalance( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getVerifiedBalance(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -852,7 +852,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerified
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getReceivedMemoAsUtf8( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getReceivedMemoAsUtf8(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -879,7 +879,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getReceived
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSentMemoAsUtf8( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getSentMemoAsUtf8(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -946,7 +946,7 @@ fn decode_blockmeta(env: &JNIEnv<'_>, obj: JObject<'_>) -> Result<BlockMeta, fai
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_writeBlockMetadata( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_writeBlockMetadata(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_cache: JString<'_>, db_cache: JString<'_>,
@ -978,7 +978,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_writeBlockM
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getLatestHeight( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getLatestHeight(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
fsblockdb_root: JString<'_>, fsblockdb_root: JString<'_>,
@ -1000,7 +1000,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getLatestHe
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_findBlockMetadata( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_findBlockMetadata(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
fsblockdb_root: JString<'_>, fsblockdb_root: JString<'_>,
@ -1023,7 +1023,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_findBlockMe
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindBlockMetadataToHeight( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_rewindBlockMetadataToHeight(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
fsblockdb_root: JString<'_>, fsblockdb_root: JString<'_>,
@ -1046,7 +1046,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindBlock
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_validateCombinedChain( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_validateCombinedChain(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_cache: JString<'_>, db_cache: JString<'_>,
@ -1084,7 +1084,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_validateCom
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getNearestRewindHeight( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getNearestRewindHeight(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -1120,7 +1120,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getNearestR
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindToHeight( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_rewindToHeight(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -1143,7 +1143,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindToHei
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_scanBlocks( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_scanBlocks(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_cache: JString<'_>, db_cache: JString<'_>,
@ -1171,7 +1171,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_scanBlocks(
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_putUtxo( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_putUtxo(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -1218,7 +1218,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_putUtxo(
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_decryptAndStoreTransaction( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_decryptAndStoreTransaction(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -1271,7 +1271,7 @@ fn zip317_helper<Ctx, DbT, R>(
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createToAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -1363,7 +1363,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAddress( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_shieldToAddress(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,
@ -1454,7 +1454,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_branchIdForHeight( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_branchIdForHeight(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
height: jlong, height: jlong,
@ -1499,7 +1499,7 @@ fn parse_network(value: u32) -> Result<Network, failure::Error> {
/// - Call [`zcashlc_free_keys`] to free the memory associated with the returned pointer /// - Call [`zcashlc_free_keys`] to free the memory associated with the returned pointer
/// when done using it. /// when done using it.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_listTransparentReceivers( pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_listTransparentReceivers(
env: JNIEnv<'_>, env: JNIEnv<'_>,
_: JClass<'_>, _: JClass<'_>,
db_data: JString<'_>, db_data: JString<'_>,

View File

@ -58,7 +58,7 @@ class TestWallet(
private val context = InstrumentationRegistry.getInstrumentation().context private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed() private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey = private val shieldedSpendingKey =
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, account) } runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context, context,
network, network,

View File

@ -178,7 +178,7 @@ class SampleCodeTest {
val amount = 0.123.convertZecToZatoshi() val amount = 0.123.convertZecToZatoshi()
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw" val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
val memo = "Test Transaction" val memo = "Test Transaction"
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(seed, ZcashNetwork.Mainnet, Account.DEFAULT) val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
synchronizer.sendToAddress(spendingKey, amount, address, memo) synchronizer.sendToAddress(spendingKey, amount, address, memo)
} }

View File

@ -134,7 +134,7 @@ internal fun ComposeActivity.Navigation() {
} else { } else {
Transactions( Transactions(
synchronizer = synchronizer, synchronizer = synchronizer,
onBack = { navController.popBackStackJustOnce(SEND) } onBack = { navController.popBackStackJustOnce(TRANSACTIONS) }
) )
} }
} }

View File

@ -84,7 +84,7 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
override fun onActionButtonClicked() { override fun onActionButtonClicked() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
copyToClipboard( copyToClipboard(
DerivationTool.deriveUnifiedAddress( DerivationTool.getInstance().deriveUnifiedAddress(
viewingKey.encoding, viewingKey.encoding,
ZcashNetwork.fromResources(requireApplicationContext()) ZcashNetwork.fromResources(requireApplicationContext())
), ),

View File

@ -69,7 +69,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
setOnClickListener { setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
sharedViewModel.synchronizerFlow.value?.shieldFunds( sharedViewModel.synchronizerFlow.value?.shieldFunds(
DerivationTool.deriveUnifiedSpendingKey( DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed, seed,
network, network,
Account.DEFAULT Account.DEFAULT

View File

@ -47,14 +47,14 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
lifecycleScope.launch { lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@Suppress("MagicNumber") @Suppress("MagicNumber")
val spendingKey = DerivationTool.deriveUnifiedSpendingKey( val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed, seed,
ZcashNetwork.fromResources(requireApplicationContext()), ZcashNetwork.fromResources(requireApplicationContext()),
Account(5) Account(5)
) )
// derive the key that allows you to view but not spend transactions // derive the key that allows you to view but not spend transactions
val viewingKey = DerivationTool.deriveUnifiedFullViewingKey( val viewingKey = DerivationTool.getInstance().deriveUnifiedFullViewingKey(
spendingKey, spendingKey,
ZcashNetwork.fromResources(requireApplicationContext()) ZcashNetwork.fromResources(requireApplicationContext())
) )
@ -91,9 +91,10 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
override fun onActionButtonClicked() { override fun onActionButtonClicked() {
lifecycleScope.launch { lifecycleScope.launch {
copyToClipboard( copyToClipboard(
DerivationTool.deriveUnifiedFullViewingKeys( DerivationTool.getInstance().deriveUnifiedFullViewingKeys(
seed, seed,
ZcashNetwork.fromResources(requireApplicationContext()) ZcashNetwork.fromResources(requireApplicationContext()),
DerivationTool.DEFAULT_NUMBER_OF_ACCOUNTS
).first().encoding, ).first().encoding,
"UnifiedFullViewingKey copied to clipboard!" "UnifiedFullViewingKey copied to clipboard!"
) )

View File

@ -90,7 +90,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
val bip39Seed = withContext(Dispatchers.IO) { val bip39Seed = withContext(Dispatchers.IO) {
Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed() Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed()
} }
DerivationTool.deriveUnifiedSpendingKey( DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed = bip39Seed, seed = bip39Seed,
network = it.network, network = it.network,
account = Account.DEFAULT account = Account.DEFAULT

View File

@ -10,6 +10,7 @@ Thankfully, the only thing an app developer has to be concerned with is the foll
# Modules # Modules
The SDK is broken down into several logical components, implemented as Gradle modules. At a high level, the modularization is: The SDK is broken down into several logical components, implemented as Gradle modules. At a high level, the modularization is:
* backend-lib — Native library interfaces. This is internal to the SDK.
* sdk-lib — Compiles all of the modules together for the SDK. * sdk-lib — Compiles all of the modules together for the SDK.
* sdk-incubator-lib — Incubator for new APIs that may eventually be promoted to the SDK. Classes are packaged to match the SDK, so that moving them will not necessarily change the API. While SDK clients can use classes in sdk-incubator-lib, they should anticipate a greater amount of public API churn. * sdk-incubator-lib — Incubator for new APIs that may eventually be promoted to the SDK. Classes are packaged to match the SDK, so that moving them will not necessarily change the API. While SDK clients can use classes in sdk-incubator-lib, they should anticipate a greater amount of public API churn.
* lightwallet-client-lib — Provides a set of Kotlin APIs for interacting with lightwalletd over the network. * lightwallet-client-lib — Provides a set of Kotlin APIs for interacting with lightwalletd over the network.
@ -18,6 +19,7 @@ The SDK is broken down into several logical components, implemented as Gradle mo
```mermaid ```mermaid
flowchart TB; flowchart TB;
backendLib[[backend-lib]] --> sdkLib[[sdk-lib]];
lightwalletClientLib[[lightwallet-client-lib]] --> sdkLib[[sdk-lib]]; lightwalletClientLib[[lightwallet-client-lib]] --> sdkLib[[sdk-lib]];
sdkLib[[sdk-lib]] --> demoApp[[demo-app]]; sdkLib[[sdk-lib]] --> demoApp[[demo-app]];
sdkLib[[sdk-lib]] --> sdkIncubatorLib[[sdk-incubator-lib]]; sdkLib[[sdk-lib]] --> sdkIncubatorLib[[sdk-incubator-lib]];
@ -28,9 +30,11 @@ The SDK is broken down into several logical components, implemented as Gradle mo
# Data model # Data model
Before diving into some of the module specifics, it is helpful to provide some context on the data model representations as data flows between the different modules of this repository. There are multiple data representations, including: Before diving into some of the module specifics, it is helpful to provide some context on the data model representations as data flows between the different modules of this repository. There are multiple data representations, including:
1. Network — The wire representation for calls to and from the Lightwalletd server. These are generated by `protoc` at compile time. These are not generally a public API. 1. Primitive — The JNI generally requires primitive values (e.g. `Int`, `Long`, `String`, `ByteArray`). These are not generally part of the public API.
2. Unsafe — The representation provided as the output from `lightwallet-client-lib`. These values are not necessarily validated, hence the smurf naming with the suffix `Unsafe`. These are not generally a public API for clients of the SDK. 2. JNI — Objects that may cross the JNI boundary. These are not generally part of the public API.
3. SDK — Objects exposed as a public API for clients of the SDK. 3. Network — The wire representation for calls to and from the Lightwalletd server. These are generated by `protoc` at compile time. These are not generally a public API.
4. Unsafe — The representation provided as the output from `lightwallet-client-lib`. These values are not necessarily validated, hence the smurf naming with the suffix `Unsafe`. These are not generally a public API for clients of the SDK.
5. SDK — Objects exposed as a public API for clients of the SDK.
# lightwallet-client-lib # lightwallet-client-lib
This library is a work-in-progress. This library is a work-in-progress.

View File

@ -115,7 +115,8 @@ dependencies {
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.android)
// TODO [#673]: Make `implementation` https://github.com/zcash/zcash-android-wallet-sdk/issues/673 // TODO [#1030]: Make sdk-lib gRPC objects free (and then make this `implementation`)
// TODO [#1030]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1030
api(libs.bundles.grpc) api(libs.bundles.grpc)
// Tests // Tests

View File

@ -1,48 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.2.1" client="gradle" variant="all" version="4.2.1"> <issues format="6" by="lint 7.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="all" version="7.4.0">
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="29"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="68"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="33"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="68"/>
</issue>
</issues> </issues>

View File

@ -1,7 +1,3 @@
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
# https://github.com/grpc/grpc-java/blob/master/android/proguard-rules.txt # https://github.com/grpc/grpc-java/blob/master/android/proguard-rules.txt
-keepclassmembers class io.grpc.okhttp.OkHttpChannelBuilder { -keepclassmembers class io.grpc.okhttp.OkHttpChannelBuilder {
io.grpc.okhttp.OkHttpChannelBuilder forTarget(java.lang.String); io.grpc.okhttp.OkHttpChannelBuilder forTarget(java.lang.String);
@ -10,6 +6,9 @@
io.grpc.okhttp.OkHttpChannelBuilder transportExecutor(java.util.concurrent.Executor); io.grpc.okhttp.OkHttpChannelBuilder transportExecutor(java.util.concurrent.Executor);
} }
# gRPC related - https://github.com/grpc/grpc-java/issues/6612
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
# Prevent OKHttp from causing warnings for consumers of the SDK # Prevent OKHttp from causing warnings for consumers of the SDK
-dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket -dontwarn org.bouncycastle.jsse.BCSSLSocket

View File

@ -46,7 +46,7 @@ class CompactBlockUnsafe(
} }
} }
private data class CompactBlockOutputsCounts( data class CompactBlockOutputsCounts(
val saplingOutputsCount: UInt, val saplingOutputsCount: UInt,
val orchardActionsCount: UInt val orchardActionsCount: UInt
) )

View File

@ -20,7 +20,11 @@ sealed class WalletFixture {
seed: String = seedPhrase, seed: String = seedPhrase,
network: ZcashNetwork, network: ZcashNetwork,
account: Account = Account.DEFAULT account: Account = Account.DEFAULT
) = DerivationTool.deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account) ) = DerivationTool.getInstance().deriveUnifiedSpendingKey(
Mnemonics.MnemonicCode(seed).toEntropy(),
network,
account
)
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
object Ben : WalletFixture() { object Ben : WalletFixture() {
@ -34,9 +38,11 @@ sealed class WalletFixture {
ZcashNetwork.ID_TESTNET -> { ZcashNetwork.ID_TESTNET -> {
BlockHeight.new(zcashNetwork, 2170000L) BlockHeight.new(zcashNetwork, 2170000L)
} }
ZcashNetwork.ID_MAINNET -> { ZcashNetwork.ID_MAINNET -> {
BlockHeight.new(zcashNetwork, 1935000L) BlockHeight.new(zcashNetwork, 1935000L)
} }
else -> error("Unknown network $zcashNetwork") else -> error("Unknown network $zcashNetwork")
} }
@ -50,6 +56,7 @@ sealed class WalletFixture {
transparent = "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE" transparent = "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE"
) )
} }
ZcashNetwork.ID_MAINNET -> { ZcashNetwork.ID_MAINNET -> {
Addresses( Addresses(
unified = unified =
@ -59,6 +66,7 @@ sealed class WalletFixture {
transparent = "t1JP7PHu72xHztsZiwH6cye4yvC9Prb3EvQ" transparent = "t1JP7PHu72xHztsZiwH6cye4yvC9Prb3EvQ"
) )
} }
else -> error("Unknown network $zcashNetwork") else -> error("Unknown network $zcashNetwork")
} }
} }
@ -76,9 +84,11 @@ sealed class WalletFixture {
ZcashNetwork.ID_TESTNET -> { ZcashNetwork.ID_TESTNET -> {
BlockHeight.new(zcashNetwork, 2170000L) BlockHeight.new(zcashNetwork, 2170000L)
} }
ZcashNetwork.ID_MAINNET -> { ZcashNetwork.ID_MAINNET -> {
BlockHeight.new(zcashNetwork, 1935000L) BlockHeight.new(zcashNetwork, 1935000L)
} }
else -> error("Unknown network $zcashNetwork") else -> error("Unknown network $zcashNetwork")
} }
@ -92,6 +102,7 @@ sealed class WalletFixture {
transparent = "tmCxJG72RWN66xwPtNgu4iKHpyysGrc7rEg" transparent = "tmCxJG72RWN66xwPtNgu4iKHpyysGrc7rEg"
) )
} }
ZcashNetwork.ID_MAINNET -> { ZcashNetwork.ID_MAINNET -> {
Addresses( Addresses(
unified = unified =
@ -101,6 +112,7 @@ sealed class WalletFixture {
transparent = "t1duiEGg7b39nfQee3XaTY4f5McqfyJKhBi" transparent = "t1duiEGg7b39nfQee3XaTY4f5McqfyJKhBi"
) )
} }
else -> error("Unknown network $zcashNetwork") else -> error("Unknown network $zcashNetwork")
} }
} }

View File

@ -5,7 +5,6 @@ plugins {
id("org.jetbrains.kotlin.plugin.allopen") id("org.jetbrains.kotlin.plugin.allopen")
id("org.jetbrains.dokka") id("org.jetbrains.dokka")
id("org.mozilla.rust-android-gradle.rust-android")
id("wtf.emulator.gradle") id("wtf.emulator.gradle")
id("zcash-sdk.emulator-wtf-conventions") id("zcash-sdk.emulator-wtf-conventions")
@ -84,28 +83,9 @@ tasks.dokkaHtml.configure {
} }
} }
cargo {
module = "."
libname = "zcashwalletsdk"
targets = listOf(
"arm",
"arm64",
"x86",
"x86_64"
)
apiLevels = mapOf(
"arm" to 16,
"arm64" to 21,
"x86" to 16,
"x86_64" to 21,
)
profile = "release"
prebuiltToolchains = true
}
dependencies { dependencies {
api(projects.lightwalletClientLib) api(projects.lightwalletClientLib)
implementation(projects.backendLib)
implementation(libs.androidx.annotation) implementation(libs.androidx.annotation)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
@ -149,22 +129,5 @@ dependencies {
androidTestImplementation(libs.bip39) androidTestImplementation(libs.bip39)
} }
tasks {
/*
* The Mozilla Rust Gradle plugin caches the native build data under the "target" directory,
* which does not normally get deleted during a clean. The following task and dependency solves
* that.
*/
getByName<Delete>("clean").dependsOn(create<Delete>("cleanRustBuildOutput") {
delete("target")
})
}
project.afterEvaluate {
val cargoTask = tasks.getByName("cargoBuild")
tasks.getByName("javaPreCompileDebug").dependsOn(cargoTask)
tasks.getByName("javaPreCompileRelease").dependsOn(cargoTask)
}
fun MinimalExternalModuleDependency.asCoordinateString() = fun MinimalExternalModuleDependency.asCoordinateString() =
"${module.group}:${module.name}:${versionConstraint.displayName}" "${module.group}:${module.name}:${versionConstraint.displayName}"

View File

@ -1,48 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.2.1" client="gradle" variant="all" version="4.2.1"> <issues format="6" by="lint 7.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="all" version="7.4.0">
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="29"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="68"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="33"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="68"/>
</issue>
</issues> </issues>

View File

@ -1,24 +0,0 @@
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
# https://github.com/grpc/grpc-java/blob/master/android/proguard-rules.txt
-keepclassmembers class io.grpc.okhttp.OkHttpChannelBuilder {
io.grpc.okhttp.OkHttpChannelBuilder forTarget(java.lang.String);
io.grpc.okhttp.OkHttpChannelBuilder scheduledExecutorService(java.util.concurrent.ScheduledExecutorService);
io.grpc.okhttp.OkHttpChannelBuilder sslSocketFactory(javax.net.ssl.SSLSocketFactory);
io.grpc.okhttp.OkHttpChannelBuilder transportExecutor(java.util.concurrent.Executor);
}
# gRPC related - https://github.com/grpc/grpc-java/issues/6612
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
# Prevent OKHttp from causing warnings for consumers of the SDK
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

View File

@ -19,6 +19,5 @@
-keep public class cash.z.ecc.android.sdk.db.entity.* { public protected *; } -keep public class cash.z.ecc.android.sdk.db.entity.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.exception.* { public protected *; } -keep public class cash.z.ecc.android.sdk.exception.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.ext.* { public protected *; } -keep public class cash.z.ecc.android.sdk.ext.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.jni.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.tool.* { public protected *; } -keep public class cash.z.ecc.android.sdk.tool.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.type.* { public protected *; } -keep public class cash.z.ecc.android.sdk.type.* { public protected *; }

View File

@ -1,8 +1,9 @@
package cash.z.ecc.android.sdk.db package cash.z.ecc.android.sdk.db
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.internal.ext.existsSuspend import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.getAppContext import cash.z.ecc.android.sdk.test.getAppContext
import cash.z.ecc.fixture.DatabaseCacheFilesRootFixture import cash.z.ecc.fixture.DatabaseCacheFilesRootFixture
import cash.z.ecc.fixture.DatabaseNameFixture import cash.z.ecc.fixture.DatabaseNameFixture
@ -124,28 +125,28 @@ class DatabaseCoordinatorTest {
DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME) DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME)
) )
assertTrue(originalDbFile.existsSuspend()) assertTrue(originalDbFile.exists())
assertTrue(originalDbJournalFile.existsSuspend()) assertTrue(originalDbJournalFile.exists())
assertTrue(originalDbWalFile.existsSuspend()) assertTrue(originalDbWalFile.exists())
assertFalse(expectedDbFile.existsSuspend()) assertFalse(expectedDbFile.exists())
assertFalse(expectedDbJournalFile.existsSuspend()) assertFalse(expectedDbJournalFile.exists())
assertFalse(expectedDbWalFile.existsSuspend()) assertFalse(expectedDbWalFile.exists())
dbCoordinator.dataDbFile( dbCoordinator.dataDbFile(
DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS DatabaseNameFixture.TEST_DB_ALIAS
).also { resultFile -> ).also { resultFile ->
assertTrue(resultFile.existsSuspend()) assertTrue(resultFile.exists())
assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath) assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath)
assertTrue(expectedDbFile.existsSuspend()) assertTrue(expectedDbFile.exists())
assertTrue(expectedDbJournalFile.existsSuspend()) assertTrue(expectedDbJournalFile.exists())
assertTrue(expectedDbWalFile.existsSuspend()) assertTrue(expectedDbWalFile.exists())
assertFalse(originalDbFile.existsSuspend()) assertFalse(originalDbFile.exists())
assertFalse(originalDbJournalFile.existsSuspend()) assertFalse(originalDbJournalFile.exists())
assertFalse(originalDbWalFile.existsSuspend()) assertFalse(originalDbWalFile.exists())
} }
} }
@ -186,14 +187,14 @@ class DatabaseCoordinatorTest {
fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME) fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME)
) )
assertTrue(dbFile.existsSuspend()) assertTrue(dbFile.exists())
assertTrue(dbJournalFile.existsSuspend()) assertTrue(dbJournalFile.exists())
assertTrue(dbWalFile.existsSuspend()) assertTrue(dbWalFile.exists())
dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also { dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also {
assertFalse(dbFile.existsSuspend()) assertFalse(dbFile.exists())
assertFalse(dbJournalFile.existsSuspend()) assertFalse(dbJournalFile.exists())
assertFalse(dbWalFile.existsSuspend()) assertFalse(dbWalFile.exists())
} }
} }
@ -276,13 +277,13 @@ class DatabaseCoordinatorTest {
) )
// check all files in place // check all files in place
assertTrue(olderLegacyDbFile.existsSuspend()) assertTrue(olderLegacyDbFile.exists())
assertTrue(olderLegacyDbJournalFile.existsSuspend()) assertTrue(olderLegacyDbJournalFile.exists())
assertTrue(olderLegacyDbWalFile.existsSuspend()) assertTrue(olderLegacyDbWalFile.exists())
assertTrue(newerLegacyDbFile.existsSuspend()) assertTrue(newerLegacyDbFile.exists())
assertTrue(newerLegacyDbJournalFile.existsSuspend()) assertTrue(newerLegacyDbJournalFile.exists())
assertTrue(newerLegacyDbWalFile.existsSuspend()) assertTrue(newerLegacyDbWalFile.exists())
// once we access the latest file system blocks storage root directory, all the legacy database files should // once we access the latest file system blocks storage root directory, all the legacy database files should
// be removed // be removed
@ -290,13 +291,41 @@ class DatabaseCoordinatorTest {
network = DatabaseNameFixture.TEST_DB_NETWORK, network = DatabaseNameFixture.TEST_DB_NETWORK,
alias = DatabaseNameFixture.TEST_DB_ALIAS alias = DatabaseNameFixture.TEST_DB_ALIAS
).also { ).also {
assertFalse(olderLegacyDbFile.existsSuspend()) assertFalse(olderLegacyDbFile.exists())
assertFalse(olderLegacyDbJournalFile.existsSuspend()) assertFalse(olderLegacyDbJournalFile.exists())
assertFalse(olderLegacyDbWalFile.existsSuspend()) assertFalse(olderLegacyDbWalFile.exists())
assertFalse(newerLegacyDbFile.existsSuspend()) assertFalse(newerLegacyDbFile.exists())
assertFalse(newerLegacyDbJournalFile.existsSuspend()) assertFalse(newerLegacyDbJournalFile.exists())
assertFalse(newerLegacyDbWalFile.existsSuspend()) assertFalse(newerLegacyDbWalFile.exists())
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@SmallTest
fun data_db_path() = runTest {
val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext())
val dataDbFile = coordinator.dataDbFile(ZcashNetwork.Testnet, "TestWallet")
assertTrue(
"Invalid DataDB file",
dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@SmallTest
fun cache_path() = runTest {
val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext())
val cache = coordinator.fsBlockDbRoot(ZcashNetwork.Testnet, "TestWallet")
assertTrue(
"Invalid CacheDB file",
cache.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_FS_BLOCK_DB_ROOT_NAME}"
)
)
}
} }

View File

@ -1,9 +1,10 @@
package cash.z.ecc.android.sdk.fixture package cash.z.ecc.android.sdk.fixture
import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
object WalletFixture { object WalletFixture {
val NETWORK = ZcashNetwork.Mainnet val NETWORK = ZcashNetwork.Mainnet
@ -14,5 +15,5 @@ object WalletFixture {
seed: String = SEED_PHRASE, seed: String = SEED_PHRASE,
network: ZcashNetwork = NETWORK, network: ZcashNetwork = NETWORK,
account: Account = Account.DEFAULT account: Account = Account.DEFAULT
) = DerivationTool.deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account) ) = RustDerivationTool.new().deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account)
} }

View File

@ -1,48 +0,0 @@
package cash.z.ecc.android.sdk.integration
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.DefaultSynchronizerFactory
import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Test
class SynchronizerFactoryTest {
@Test
@SmallTest
fun testFilePaths() {
val rustBackend = runBlocking {
val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext())
DefaultSynchronizerFactory.defaultRustBackend(
ZcashNetwork.Testnet,
"TestWallet",
TestWallet.Backups.SAMPLE_WALLET.testnetBirthday,
SaplingParamTool.new(ApplicationProvider.getApplicationContext()),
coordinator
)
}
assertTrue(
"Invalid DataDB file",
rustBackend.dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
assertTrue(
"Invalid CacheDB file",
rustBackend.fsBlockDbRoot.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_FS_BLOCK_DB_ROOT_NAME}"
)
)
assertTrue(
"Invalid CacheDB params dir",
rustBackend.saplingParamDir.endsWith(
"no_backup/co.electricoin.zcash"
)
)
}
}

View File

@ -101,7 +101,7 @@ class TestnetIntegrationTest : ScopedTest() {
} }
private suspend fun sendFunds(): Boolean { private suspend fun sendFunds(): Boolean {
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(seed, synchronizer.network, Account.DEFAULT) val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, synchronizer.network, Account.DEFAULT)
log("sending to address") log("sending to address")
synchronizer.sendToAddress( synchronizer.sendToAddress(
spendingKey, spendingKey,

View File

@ -2,6 +2,13 @@ package cash.z.ecc.android.sdk.internal
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.ext.KEY_EPOCH_SECONDS
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HASH
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HEIGHT
import cash.z.ecc.android.sdk.internal.model.ext.KEY_TREE
import cash.z.ecc.android.sdk.internal.model.ext.KEY_VERSION
import cash.z.ecc.android.sdk.internal.model.ext.VERSION_1
import cash.z.ecc.android.sdk.internal.model.ext.from
import cash.z.ecc.fixture.CheckpointFixture import cash.z.ecc.fixture.CheckpointFixture
import cash.z.ecc.fixture.toJson import cash.z.ecc.fixture.toJson
import org.json.JSONObject import org.json.JSONObject

View File

@ -1,5 +1,6 @@
package cash.z.ecc.android.sdk.internal package cash.z.ecc.android.sdk.internal
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.exception.TransactionEncoderException import cash.z.ecc.android.sdk.exception.TransactionEncoderException
@ -11,6 +12,7 @@ import cash.z.ecc.fixture.SaplingParamsFixture
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
@ -168,4 +170,17 @@ class SaplingParamToolBasicTest {
saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY) saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY)
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@SmallTest
fun sapling_params_path() = runTest {
val paramsDir = SaplingParamTool.new(ApplicationProvider.getApplicationContext()).properties.paramsDirectory
Assert.assertTrue(
"Invalid CacheDB params dir",
paramsDir.endsWith(
"no_backup/co.electricoin.zcash"
)
)
}
} }

View File

@ -1,10 +1,10 @@
package cash.z.ecc.android.sdk.internal.storage.block package cash.z.ecc.android.sdk.internal.storage.block
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
import cash.z.ecc.android.sdk.internal.ext.existsSuspend import cash.z.ecc.android.sdk.internal.ext.existsSuspend
import cash.z.ecc.android.sdk.internal.ext.listSuspend import cash.z.ecc.android.sdk.internal.ext.listSuspend
import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
import cash.z.ecc.android.sdk.jni.Backend
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.fixture.FakeRustBackendFixture import cash.z.ecc.fixture.FakeRustBackendFixture

View File

@ -2,6 +2,10 @@ package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.annotation.MaintainedTest import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.getBranchIdForHeight
import cash.z.ecc.android.sdk.internal.jni.RustBackend
import cash.z.ecc.android.sdk.internal.network
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -47,21 +51,21 @@ class BranchIdTest internal constructor(
// However, due to quirks on certain devices, we created this test at the Android level, // However, due to quirks on certain devices, we created this test at the Android level,
// as a sanity check // as a sanity check
val testnetBackend = runBlocking { val testnetBackend = runBlocking {
RustBackend.init( RustBackend.new(
File(""), File(""),
File(""), File(""),
File(""), File(""),
ZcashNetwork.Testnet, File(""),
ZcashNetwork.Testnet.saplingActivationHeight ZcashNetwork.Testnet.id,
) )
} }
val mainnetBackend = runBlocking { val mainnetBackend = runBlocking {
RustBackend.init( RustBackend.new(
File(""), File(""),
File(""), File(""),
File(""), File(""),
ZcashNetwork.Mainnet, File(""),
ZcashNetwork.Mainnet.saplingActivationHeight ZcashNetwork.Mainnet.id,
) )
} }
return listOf( return listOf(

View File

@ -5,8 +5,11 @@ import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode
import cash.z.ecc.android.bip39.toSeed import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.annotation.MaintainedTest import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.deriveUnifiedAddress
import cash.z.ecc.android.sdk.internal.deriveUnifiedFullViewingKeysTypesafe
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.BeforeClass import org.junit.BeforeClass
@ -21,10 +24,15 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
@Test @Test
fun deriveUnifiedFullViewingKeysFromSeedTest() = runBlocking { fun deriveUnifiedFullViewingKeysFromSeedTest() = runBlocking {
val ufvks = DerivationTool.deriveUnifiedFullViewingKeys(SEED, network = network) val ufvks = RustDerivationTool.new().deriveUnifiedFullViewingKeysTypesafe(
SEED,
network = network,
numberOfAccounts =
Derivation.DEFAULT_NUMBER_OF_ACCOUNTS
)
assertEquals(1, ufvks.size) assertEquals(1, ufvks.size)
val ufvk = ufvks.first() val ufvk = ufvks.first()
assertEquals(expected.uAddr, DerivationTool.deriveUnifiedAddress(ufvk.encoding, network = network)) assertEquals(expected.uAddr, RustDerivationTool.new().deriveUnifiedAddress(ufvk.encoding, network = network))
// TODO: If we need this, change DerivationTool to derive from the UFVK instead of the public key. // TODO: If we need this, change DerivationTool to derive from the UFVK instead of the public key.
// assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(ufvk.encoding, // assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(ufvk.encoding,
// network = network)) // network = network))

View File

@ -1,22 +0,0 @@
package cash.z.ecc.android.sdk.tool
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.fixture.WalletFixture
import cash.z.ecc.android.sdk.model.Account
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertContentEquals
class DerivationToolTest {
@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun create_spending_key_does_not_mutate_passed_bytes() = runTest {
val bytesOne = Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy()
val bytesTwo = Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy()
DerivationTool.deriveUnifiedSpendingKey(bytesOne, WalletFixture.NETWORK, Account.DEFAULT)
assertContentEquals(bytesTwo, bytesOne)
}
}

View File

@ -1,9 +1,10 @@
package cash.z.ecc.android.sdk.util package cash.z.ecc.android.sdk.util
import cash.z.ecc.android.sdk.internal.deriveUnifiedAddress
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.readFileLinesInFlow import cash.z.ecc.android.sdk.test.readFileLinesInFlow
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -31,7 +32,7 @@ class AddressGeneratorUtil {
.map { seedPhrase -> .map { seedPhrase ->
mnemonics.toSeed(seedPhrase.toCharArray()) mnemonics.toSeed(seedPhrase.toCharArray())
}.map { seed -> }.map { seed ->
DerivationTool.deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT) RustDerivationTool.new().deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
}.collect { address -> }.collect { address ->
println("xrxrx2\t$address") println("xrxrx2\t$address")
assertTrue(address.startsWith("u1")) assertTrue(address.startsWith("u1"))

View File

@ -6,13 +6,14 @@ import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Testnet import cash.z.ecc.android.sdk.model.Testnet
import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@ -58,7 +59,7 @@ class TestWallet(
private val context = InstrumentationRegistry.getInstrumentation().context private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed() private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val spendingKey = private val spendingKey =
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, account) } runBlocking { RustDerivationTool.new().deriveUnifiedSpendingKey(seed, network = network, account) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context, context,
network, network,

View File

@ -1,12 +1,12 @@
package cash.z.ecc.fixture package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.KEY_EPOCH_SECONDS
import cash.z.ecc.android.sdk.internal.KEY_HASH
import cash.z.ecc.android.sdk.internal.KEY_HEIGHT
import cash.z.ecc.android.sdk.internal.KEY_TREE
import cash.z.ecc.android.sdk.internal.KEY_VERSION
import cash.z.ecc.android.sdk.internal.VERSION_1
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.ext.KEY_EPOCH_SECONDS
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HASH
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HEIGHT
import cash.z.ecc.android.sdk.internal.model.ext.KEY_TREE
import cash.z.ecc.android.sdk.internal.model.ext.KEY_VERSION
import cash.z.ecc.android.sdk.internal.model.ext.VERSION_1
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import org.json.JSONObject import org.json.JSONObject

View File

@ -1,15 +1,11 @@
package cash.z.ecc.fixture package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.jni.Backend import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
import cash.z.ecc.android.sdk.jni.UnifiedSpendingKeyJni
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.io.File
internal class FakeRustBackend( internal class FakeRustBackend(
override val network: ZcashNetwork, override val networkId: Int,
override val saplingParamDir: File,
val metadata: MutableList<JniBlockMeta> val metadata: MutableList<JniBlockMeta>
) : Backend { ) : Backend {
@ -34,6 +30,17 @@ internal class FakeRustBackend(
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: Long
): Boolean {
TODO("Not yet implemented")
}
override suspend fun findBlockMetadata(height: Long): JniBlockMeta? { override suspend fun findBlockMetadata(height: Long): JniBlockMeta? {
return metadata.findLast { it.height == height } return metadata.findLast { it.height == height }
} }
@ -78,7 +85,7 @@ internal class FakeRustBackend(
override suspend fun initDataDb(seed: ByteArray?): Int = override suspend fun initDataDb(seed: ByteArray?): Int =
error("Intentionally not implemented in mocked FakeRustBackend implementation.") error("Intentionally not implemented in mocked FakeRustBackend implementation.")
override suspend fun createAccount(seed: ByteArray): UnifiedSpendingKeyJni = override suspend fun createAccount(seed: ByteArray): JniUnifiedSpendingKey =
error("Intentionally not implemented in mocked FakeRustBackend implementation.") error("Intentionally not implemented in mocked FakeRustBackend implementation.")
override fun isValidShieldedAddr(addr: String): Boolean = override fun isValidShieldedAddr(addr: String): Boolean =
@ -128,13 +135,4 @@ internal class FakeRustBackend(
override suspend fun scanBlocks(limit: Long?): Boolean = override suspend fun scanBlocks(limit: Long?): Boolean =
error("Intentionally not implemented in mocked FakeRustBackend implementation.") error("Intentionally not implemented in mocked FakeRustBackend implementation.")
override suspend fun putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: BlockHeight
): Boolean = error("Intentionally not implemented in mocked FakeRustBackend implementation.")
} }

View File

@ -2,20 +2,16 @@ package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.io.File
internal class FakeRustBackendFixture { internal class FakeRustBackendFixture {
private val DEFAULT_SAPLING_PARAM_DIR = File(DatabasePathFixture.new())
private val DEFAULT_NETWORK = ZcashNetwork.Testnet private val DEFAULT_NETWORK = ZcashNetwork.Testnet
fun new( fun new(
saplingParamDir: File = DEFAULT_SAPLING_PARAM_DIR,
network: ZcashNetwork = DEFAULT_NETWORK, network: ZcashNetwork = DEFAULT_NETWORK,
metadata: MutableList<JniBlockMeta> = mutableListOf() metadata: MutableList<JniBlockMeta> = mutableListOf()
) = FakeRustBackend( ) = FakeRustBackend(
saplingParamDir = saplingParamDir, networkId = network.id,
network = network,
metadata = metadata metadata = metadata
) )
} }

View File

@ -17,15 +17,17 @@ import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.exception.TransactionSubmitException import cash.z.ecc.android.sdk.exception.TransactionSubmitException
import cash.z.ecc.android.sdk.ext.ConsensusBranchId import cash.z.ecc.android.sdk.ext.ConsensusBranchId
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.SaplingParamTool import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.internal.db.derived.DbDerivedDataRepository import cash.z.ecc.android.sdk.internal.db.derived.DbDerivedDataRepository
import cash.z.ecc.android.sdk.internal.db.derived.DerivedDataDb import cash.z.ecc.android.sdk.internal.db.derived.DerivedDataDb
import cash.z.ecc.android.sdk.internal.ext.isNullOrEmpty
import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.ext.toHexReversed
import cash.z.ecc.android.sdk.internal.ext.tryNull import cash.z.ecc.android.sdk.internal.ext.tryNull
import cash.z.ecc.android.sdk.internal.isNullOrEmpty import cash.z.ecc.android.sdk.internal.jni.RustBackend
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
@ -34,7 +36,6 @@ import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManager
import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManagerImpl import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManagerImpl
import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoder import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoder
import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoderImpl import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoderImpl
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.PercentDecimal
@ -69,6 +70,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -92,7 +94,7 @@ class SdkSynchronizer private constructor(
private val storage: DerivedDataRepository, private val storage: DerivedDataRepository,
private val txManager: OutboundTransactionManager, private val txManager: OutboundTransactionManager,
val processor: CompactBlockProcessor, val processor: CompactBlockProcessor,
private val rustBackend: RustBackend private val backend: Backend
) : CloseableSynchronizer { ) : CloseableSynchronizer {
companion object { companion object {
@ -116,7 +118,7 @@ class SdkSynchronizer private constructor(
repository: DerivedDataRepository, repository: DerivedDataRepository,
txManager: OutboundTransactionManager, txManager: OutboundTransactionManager,
processor: CompactBlockProcessor, processor: CompactBlockProcessor,
rustBackend: RustBackend backend: Backend
): CloseableSynchronizer { ): CloseableSynchronizer {
val synchronizerKey = SynchronizerKey(zcashNetwork, alias) val synchronizerKey = SynchronizerKey(zcashNetwork, alias)
@ -128,7 +130,7 @@ class SdkSynchronizer private constructor(
repository, repository,
txManager, txManager,
processor, processor,
rustBackend backend
).apply { ).apply {
instances[synchronizerKey] = InstanceState.Active instances[synchronizerKey] = InstanceState.Active
@ -311,10 +313,10 @@ class SdkSynchronizer private constructor(
return storage.getNoteIds(transactionOverview.id).map { return storage.getNoteIds(transactionOverview.id).map {
when (transactionOverview.isSentTransaction) { when (transactionOverview.isSentTransaction) {
true -> { true -> {
rustBackend.getSentMemoAsUtf8(it) backend.getSentMemoAsUtf8(it)
} }
false -> { false -> {
rustBackend.getReceivedMemoAsUtf8(it) backend.getReceivedMemoAsUtf8(it)
} }
} }
}.filterNotNull() }.filterNotNull()
@ -405,7 +407,6 @@ class SdkSynchronizer private constructor(
lastScanTime = now lastScanTime = now
SYNCED SYNCED
} }
is Stopped -> STOPPED is Stopped -> STOPPED
is Disconnected -> DISCONNECTED is Disconnected -> DISCONNECTED
is Syncing, Initialized -> SYNCING is Syncing, Initialized -> SYNCING
@ -509,25 +510,25 @@ class SdkSynchronizer private constructor(
// Not ready to be a public API; internal for testing only // Not ready to be a public API; internal for testing only
internal suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey = internal suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey =
CompactBlockProcessor.createAccount(rustBackend, seed) CompactBlockProcessor.createAccount(backend, seed)
/** /**
* Returns the current Unified Address for this account. * Returns the current Unified Address for this account.
*/ */
override suspend fun getUnifiedAddress(account: Account): String = override suspend fun getUnifiedAddress(account: Account): String =
CompactBlockProcessor.getCurrentAddress(rustBackend, account) CompactBlockProcessor.getCurrentAddress(backend, account)
/** /**
* Returns the legacy Sapling address corresponding to the current Unified Address for this account. * Returns the legacy Sapling address corresponding to the current Unified Address for this account.
*/ */
override suspend fun getSaplingAddress(account: Account): String = override suspend fun getSaplingAddress(account: Account): String =
CompactBlockProcessor.getLegacySaplingAddress(rustBackend, account) CompactBlockProcessor.getLegacySaplingAddress(backend, account)
/** /**
* Returns the legacy transparent address corresponding to the current Unified Address for this account. * Returns the legacy transparent address corresponding to the current Unified Address for this account.
*/ */
override suspend fun getTransparentAddress(account: Account): String = override suspend fun getTransparentAddress(account: Account): String =
CompactBlockProcessor.getTransparentAddress(rustBackend, account) CompactBlockProcessor.getTransparentAddress(backend, account)
@Throws(TransactionEncoderException::class, TransactionSubmitException::class) @Throws(TransactionEncoderException::class, TransactionSubmitException::class)
override suspend fun sendToAddress( override suspend fun sendToAddress(
@ -557,7 +558,7 @@ class SdkSynchronizer private constructor(
memo: String memo: String
): Long { ): Long {
Twig.debug { "Initializing shielding transaction" } Twig.debug { "Initializing shielding transaction" }
val tAddr = CompactBlockProcessor.getTransparentAddress(rustBackend, usk.account) val tAddr = CompactBlockProcessor.getTransparentAddress(backend, usk.account)
val tBalance = processor.getUtxoCacheBalance(tAddr) val tBalance = processor.getUtxoCacheBalance(tAddr)
val encodedTx = txManager.encode( val encodedTx = txManager.encode(
@ -628,26 +629,26 @@ class SdkSynchronizer private constructor(
*/ */
internal object DefaultSynchronizerFactory { internal object DefaultSynchronizerFactory {
internal suspend fun defaultRustBackend( internal suspend fun defaultBackend(
network: ZcashNetwork, network: ZcashNetwork,
alias: String, alias: String,
blockHeight: BlockHeight,
saplingParamTool: SaplingParamTool, saplingParamTool: SaplingParamTool,
coordinator: DatabaseCoordinator coordinator: DatabaseCoordinator
): RustBackend { ): Backend {
return RustBackend.init( return RustBackend.new(
coordinator.fsBlockDbRoot(network, alias), coordinator.fsBlockDbRoot(network, alias),
coordinator.dataDbFile(network, alias), coordinator.dataDbFile(network, alias),
saplingParamTool.properties.paramsDirectory, saplingOutputFile = saplingParamTool.outputParamsFile,
network, saplingSpendFile = saplingParamTool.spendParamsFile,
blockHeight zcashNetworkId = network.id
) )
} }
@Suppress("LongParameterList") @Suppress("LongParameterList")
internal suspend fun defaultDerivedDataRepository( internal suspend fun defaultDerivedDataRepository(
context: Context, context: Context,
rustBackend: RustBackend, rustBackend: Backend,
databaseFile: File,
zcashNetwork: ZcashNetwork, zcashNetwork: ZcashNetwork,
checkpoint: Checkpoint, checkpoint: Checkpoint,
seed: ByteArray?, seed: ByteArray?,
@ -657,6 +658,7 @@ internal object DefaultSynchronizerFactory {
DerivedDataDb.new( DerivedDataDb.new(
context, context,
rustBackend, rustBackend,
databaseFile,
zcashNetwork, zcashNetwork,
checkpoint, checkpoint,
seed, seed,
@ -664,19 +666,20 @@ internal object DefaultSynchronizerFactory {
) )
) )
internal suspend fun defaultFileCompactBlockRepository(rustBackend: RustBackend): CompactBlockRepository = internal suspend fun defaultCompactBlockRepository(blockCacheRoot: File, backend: Backend): CompactBlockRepository =
FileCompactBlockRepository.new( FileCompactBlockRepository.new(
rustBackend blockCacheRoot,
backend
) )
fun defaultService(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletClient = fun defaultService(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletClient =
LightWalletClient.new(context, lightWalletEndpoint) LightWalletClient.new(context, lightWalletEndpoint)
internal fun defaultEncoder( internal fun defaultEncoder(
rustBackend: RustBackend, backend: Backend,
saplingParamTool: SaplingParamTool, saplingParamTool: SaplingParamTool,
repository: DerivedDataRepository repository: DerivedDataRepository
): TransactionEncoder = TransactionEncoderImpl(rustBackend, saplingParamTool, repository) ): TransactionEncoder = TransactionEncoderImpl(backend, saplingParamTool, repository)
fun defaultDownloader( fun defaultDownloader(
service: LightWalletClient, service: LightWalletClient,
@ -689,19 +692,20 @@ internal object DefaultSynchronizerFactory {
): OutboundTransactionManager { ): OutboundTransactionManager {
return OutboundTransactionManagerImpl.new( return OutboundTransactionManagerImpl.new(
encoder, encoder,
service, service
) )
} }
internal fun defaultProcessor( internal fun defaultProcessor(
rustBackend: RustBackend, backend: Backend,
downloader: CompactBlockDownloader, downloader: CompactBlockDownloader,
repository: DerivedDataRepository repository: DerivedDataRepository,
birthdayHeight: BlockHeight
): CompactBlockProcessor = CompactBlockProcessor( ): CompactBlockProcessor = CompactBlockProcessor(
downloader, downloader,
repository, repository,
rustBackend, backend,
rustBackend.birthdayHeight birthdayHeight
) )
} }

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk
import android.content.Context import android.content.Context
import cash.z.ecc.android.sdk.block.CompactBlockProcessor import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.SaplingParamTool import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
@ -412,7 +413,7 @@ interface Synchronizer {
* If customized initialization is required (e.g. for dependency injection or testing), see * If customized initialization is required (e.g. for dependency injection or testing), see
* [DefaultSynchronizerFactory]. * [DefaultSynchronizerFactory].
*/ */
@Suppress("LongParameterList") @Suppress("LongParameterList", "LongMethod")
suspend fun new( suspend fun new(
context: Context, context: Context,
zcashNetwork: ZcashNetwork, zcashNetwork: ZcashNetwork,
@ -437,29 +438,29 @@ interface Synchronizer {
// The pending transaction database no longer exists, so we can delete the file // The pending transaction database no longer exists, so we can delete the file
coordinator.deletePendingTransactionDatabase(zcashNetwork, alias) coordinator.deletePendingTransactionDatabase(zcashNetwork, alias)
val rustBackend = DefaultSynchronizerFactory.defaultRustBackend( val backend = DefaultSynchronizerFactory.defaultBackend(
zcashNetwork, zcashNetwork,
alias, alias,
loadedCheckpoint.height,
saplingParamTool, saplingParamTool,
coordinator coordinator
) )
val blockStore = val blockStore =
DefaultSynchronizerFactory DefaultSynchronizerFactory
.defaultFileCompactBlockRepository(rustBackend) .defaultCompactBlockRepository(coordinator.fsBlockDbRoot(zcashNetwork, alias), backend)
val viewingKeys = seed?.let { val viewingKeys = seed?.let {
DerivationTool.deriveUnifiedFullViewingKeys( DerivationTool.getInstance().deriveUnifiedFullViewingKeys(
seed, seed,
zcashNetwork, zcashNetwork,
1 Derivation.DEFAULT_NUMBER_OF_ACCOUNTS
).toList() ).toList()
} ?: emptyList() } ?: emptyList()
val repository = DefaultSynchronizerFactory.defaultDerivedDataRepository( val repository = DefaultSynchronizerFactory.defaultDerivedDataRepository(
applicationContext, applicationContext,
rustBackend, backend,
coordinator.dataDbFile(zcashNetwork, alias),
zcashNetwork, zcashNetwork,
loadedCheckpoint, loadedCheckpoint,
seed, seed,
@ -467,10 +468,19 @@ interface Synchronizer {
) )
val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint) val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint)
val encoder = DefaultSynchronizerFactory.defaultEncoder(rustBackend, saplingParamTool, repository) val encoder = DefaultSynchronizerFactory.defaultEncoder(backend, saplingParamTool, repository)
val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore) val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore)
val txManager = DefaultSynchronizerFactory.defaultTxManager(encoder, service) val txManager = DefaultSynchronizerFactory.defaultTxManager(
val processor = DefaultSynchronizerFactory.defaultProcessor(rustBackend, downloader, repository) encoder,
service
)
val processor = DefaultSynchronizerFactory.defaultProcessor(
backend,
downloader,
repository,
birthday
?: zcashNetwork.saplingActivationHeight
)
return SdkSynchronizer.new( return SdkSynchronizer.new(
zcashNetwork, zcashNetwork,
@ -478,7 +488,7 @@ interface Synchronizer {
repository, repository,
txManager, txManager,
processor, processor,
rustBackend backend
) )
} }

View File

@ -14,30 +14,31 @@ import cash.z.ecc.android.sdk.ext.BatchMetrics
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.ZcashSdk.MAX_BACKOFF_INTERVAL import cash.z.ecc.android.sdk.ext.ZcashSdk.MAX_BACKOFF_INTERVAL
import cash.z.ecc.android.sdk.ext.ZcashSdk.POLL_INTERVAL import cash.z.ecc.android.sdk.ext.ZcashSdk.POLL_INTERVAL
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
import cash.z.ecc.android.sdk.internal.createAccountAndGetSpendingKey
import cash.z.ecc.android.sdk.internal.ext.isNullOrEmpty
import cash.z.ecc.android.sdk.internal.ext.length
import cash.z.ecc.android.sdk.internal.ext.retryUpTo import cash.z.ecc.android.sdk.internal.ext.retryUpTo
import cash.z.ecc.android.sdk.internal.ext.retryWithBackoff import cash.z.ecc.android.sdk.internal.ext.retryWithBackoff
import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.ext.toHexReversed
import cash.z.ecc.android.sdk.internal.isNullOrEmpty import cash.z.ecc.android.sdk.internal.getBalance
import cash.z.ecc.android.sdk.internal.length import cash.z.ecc.android.sdk.internal.getBranchIdForHeight
import cash.z.ecc.android.sdk.internal.getCurrentAddress
import cash.z.ecc.android.sdk.internal.getDownloadedUtxoBalance
import cash.z.ecc.android.sdk.internal.getNearestRewindHeight
import cash.z.ecc.android.sdk.internal.getVerifiedBalance
import cash.z.ecc.android.sdk.internal.listTransparentReceivers
import cash.z.ecc.android.sdk.internal.model.BlockBatch import cash.z.ecc.android.sdk.internal.model.BlockBatch
import cash.z.ecc.android.sdk.internal.model.DbTransactionOverview import cash.z.ecc.android.sdk.internal.model.DbTransactionOverview
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.internal.model.ext.from import cash.z.ecc.android.sdk.internal.model.ext.from
import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight
import cash.z.ecc.android.sdk.internal.network
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
import cash.z.ecc.android.sdk.jni.Backend import cash.z.ecc.android.sdk.internal.rewindToHeight
import cash.z.ecc.android.sdk.jni.createAccountAndGetSpendingKey import cash.z.ecc.android.sdk.internal.validateCombinedChainOrErrorBlockHeight
import cash.z.ecc.android.sdk.jni.getBalance
import cash.z.ecc.android.sdk.jni.getBranchIdForHeight
import cash.z.ecc.android.sdk.jni.getCurrentAddress
import cash.z.ecc.android.sdk.jni.getDownloadedUtxoBalance
import cash.z.ecc.android.sdk.jni.getNearestRewindHeight
import cash.z.ecc.android.sdk.jni.getVerifiedBalance
import cash.z.ecc.android.sdk.jni.listTransparentReceivers
import cash.z.ecc.android.sdk.jni.rewindToHeight
import cash.z.ecc.android.sdk.jni.validateCombinedChainOrErrorBlockHeight
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.PercentDecimal
@ -80,7 +81,7 @@ import kotlin.time.Duration.Companion.days
* @property downloader the component responsible for downloading compact blocks and persisting them * @property downloader the component responsible for downloading compact blocks and persisting them
* locally for processing. * locally for processing.
* @property repository the repository holding transaction information. * @property repository the repository holding transaction information.
* @property rustBackend the librustzcash functionality available and exposed to the SDK. * @property backend the librustzcash functionality available and exposed to the SDK.
* @param minimumHeight the lowest height that we could care about. This is mostly used during * @param minimumHeight the lowest height that we could care about. This is mostly used during
* reorgs as a backstop to make sure we do not rewind beyond sapling activation. It also is factored * reorgs as a backstop to make sure we do not rewind beyond sapling activation. It also is factored
* in when considering initial range to download. In most cases, this should be the birthday height * in when considering initial range to download. In most cases, this should be the birthday height
@ -91,8 +92,8 @@ import kotlin.time.Duration.Companion.days
class CompactBlockProcessor internal constructor( class CompactBlockProcessor internal constructor(
val downloader: CompactBlockDownloader, val downloader: CompactBlockDownloader,
private val repository: DerivedDataRepository, private val repository: DerivedDataRepository,
private val rustBackend: Backend, private val backend: Backend,
minimumHeight: BlockHeight = rustBackend.network.saplingActivationHeight minimumHeight: BlockHeight
) { ) {
/** /**
* Callback for any non-trivial errors that occur while processing compact blocks. * Callback for any non-trivial errors that occur while processing compact blocks.
@ -130,9 +131,15 @@ class CompactBlockProcessor internal constructor(
var onScanMetricCompleteListener: ((BatchMetrics, Boolean) -> Unit)? = null var onScanMetricCompleteListener: ((BatchMetrics, Boolean) -> Unit)? = null
private val consecutiveChainErrors = AtomicInteger(0) private val consecutiveChainErrors = AtomicInteger(0)
/**
* The zcash network that is being processed. Either Testnet or Mainnet.
*/
val network = backend.network
private val lowerBoundHeight: BlockHeight = BlockHeight( private val lowerBoundHeight: BlockHeight = BlockHeight(
max( max(
rustBackend.network.saplingActivationHeight.value, network.saplingActivationHeight.value,
minimumHeight.value - MAX_REORG_SIZE minimumHeight.value - MAX_REORG_SIZE
) )
) )
@ -152,11 +159,6 @@ class CompactBlockProcessor internal constructor(
*/ */
private val _birthdayHeight = MutableStateFlow(lowerBoundHeight) private val _birthdayHeight = MutableStateFlow(lowerBoundHeight)
/**
* The zcash network that is being processed. Either Testnet or Mainnet.
*/
val network = rustBackend.network
/** /**
* The flow of state values so that a wallet can monitor the state of this class without needing * The flow of state values so that a wallet can monitor the state of this class without needing
* to poll. * to poll.
@ -357,7 +359,7 @@ class CompactBlockProcessor internal constructor(
// Sync // Sync
var syncResult: BlockProcessingResult = BlockProcessingResult.Success var syncResult: BlockProcessingResult = BlockProcessingResult.Success
syncNewBlocks( syncNewBlocks(
backend = rustBackend, backend = backend,
downloader = downloader, downloader = downloader,
repository = repository, repository = repository,
network = network, network = network,
@ -508,7 +510,7 @@ class CompactBlockProcessor internal constructor(
is Response.Success -> { is Response.Success -> {
runCatching { runCatching {
Twig.debug { "decrypting and storing transaction (id:$id block:$minedHeight)" } Twig.debug { "decrypting and storing transaction (id:$id block:$minedHeight)" }
rustBackend.decryptAndStoreTransaction(response.result.data) backend.decryptAndStoreTransaction(response.result.data)
}.onSuccess { }.onSuccess {
Twig.debug { "DONE: enhancing transaction (id:$id block:$minedHeight)" } Twig.debug { "DONE: enhancing transaction (id:$id block:$minedHeight)" }
}.onFailure { error -> }.onFailure { error ->
@ -548,9 +550,9 @@ class CompactBlockProcessor internal constructor(
} else { } else {
val clientBranch = "%x".format( val clientBranch = "%x".format(
Locale.ROOT, Locale.ROOT,
rustBackend.getBranchIdForHeight(serverBlockHeight) backend.getBranchIdForHeight(serverBlockHeight)
) )
val network = rustBackend.network.networkName val network = backend.network.networkName
if (!clientBranch.equals(info.consensusBranchId, true)) { if (!clientBranch.equals(info.consensusBranchId, true)) {
MismatchedNetwork( MismatchedNetwork(
@ -610,7 +612,7 @@ class CompactBlockProcessor internal constructor(
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
try { try {
retryUpTo(3) { retryUpTo(3) {
val tAddresses = rustBackend.listTransparentReceivers(account) val tAddresses = backend.listTransparentReceivers(account)
downloader.lightWalletClient.fetchUtxos( downloader.lightWalletClient.fetchUtxos(
tAddresses, tAddresses,
@ -689,13 +691,13 @@ class CompactBlockProcessor internal constructor(
// TODO [#920]: Tweak RustBackend public APIs to have void return values. // TODO [#920]: Tweak RustBackend public APIs to have void return values.
// TODO [#920]: Thus, we don't need to check the boolean result of this call until fixed. // TODO [#920]: Thus, we don't need to check the boolean result of this call until fixed.
// TODO [#920]: https://github.com/zcash/zcash-android-wallet-sdk/issues/920 // TODO [#920]: https://github.com/zcash/zcash-android-wallet-sdk/issues/920
rustBackend.putUtxo( backend.putUtxo(
utxo.address, utxo.address,
utxo.txid, utxo.txid,
utxo.index, utxo.index,
utxo.script, utxo.script,
utxo.valueZat, utxo.valueZat,
BlockHeight(utxo.height) utxo.height
) )
true true
} catch (t: Throwable) { } catch (t: Throwable) {
@ -1071,7 +1073,7 @@ class CompactBlockProcessor internal constructor(
// tricky: subtract one because we delete ABOVE this block // tricky: subtract one because we delete ABOVE this block
// This could create an invalid height if if height was saplingActivationHeight // This could create an invalid height if if height was saplingActivationHeight
val rewindHeight = BlockHeight(height.value - 1) val rewindHeight = BlockHeight(height.value - 1)
rustBackend.getNearestRewindHeight(rewindHeight) backend.getNearestRewindHeight(rewindHeight)
} }
} }
@ -1109,10 +1111,10 @@ class CompactBlockProcessor internal constructor(
if (null == lastSyncedHeight && targetHeight < lastLocalBlock) { if (null == lastSyncedHeight && targetHeight < lastLocalBlock) {
Twig.debug { "Rewinding because targetHeight is less than lastLocalBlock." } Twig.debug { "Rewinding because targetHeight is less than lastLocalBlock." }
rustBackend.rewindToHeight(targetHeight) backend.rewindToHeight(targetHeight)
} else if (null != lastSyncedHeight && targetHeight < lastSyncedHeight) { } else if (null != lastSyncedHeight && targetHeight < lastSyncedHeight) {
Twig.debug { "Rewinding because targetHeight is less than lastSyncedHeight." } Twig.debug { "Rewinding because targetHeight is less than lastSyncedHeight." }
rustBackend.rewindToHeight(targetHeight) backend.rewindToHeight(targetHeight)
} else { } else {
Twig.debug { Twig.debug {
"Not rewinding dataDb because the last synced height is $lastSyncedHeight and the" + "Not rewinding dataDb because the last synced height is $lastSyncedHeight and the" +
@ -1265,7 +1267,7 @@ class CompactBlockProcessor internal constructor(
} }
return buildList<BlockHeight> { return buildList<BlockHeight> {
add(lowerBoundHeight) add(lowerBoundHeight)
add(rustBackend.network.saplingActivationHeight) add(backend.network.saplingActivationHeight)
oldestTransactionHeight?.let { add(it) } oldestTransactionHeight?.let { add(it) }
}.maxOf { it } }.maxOf { it }
} }
@ -1280,9 +1282,9 @@ class CompactBlockProcessor internal constructor(
suspend fun getBalanceInfo(account: Account): WalletBalance { suspend fun getBalanceInfo(account: Account): WalletBalance {
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
return try { return try {
val balanceTotal = rustBackend.getBalance(account) val balanceTotal = backend.getBalance(account)
Twig.debug { "found total balance: $balanceTotal" } Twig.debug { "found total balance: $balanceTotal" }
val balanceAvailable = rustBackend.getVerifiedBalance(account) val balanceAvailable = backend.getVerifiedBalance(account)
Twig.debug { "found available balance: $balanceAvailable" } Twig.debug { "found available balance: $balanceAvailable" }
WalletBalance(balanceTotal, balanceAvailable) WalletBalance(balanceTotal, balanceAvailable)
} catch (t: Throwable) { } catch (t: Throwable) {
@ -1292,7 +1294,7 @@ class CompactBlockProcessor internal constructor(
} }
suspend fun getUtxoCacheBalance(address: String): WalletBalance = suspend fun getUtxoCacheBalance(address: String): WalletBalance =
rustBackend.getDownloadedUtxoBalance(address) backend.getDownloadedUtxoBalance(address)
/** /**
* Transmits the given state for this processor. * Transmits the given state for this processor.

View File

@ -1,8 +1,7 @@
@file:Suppress("TooManyFunctions") @file:Suppress("TooManyFunctions")
package cash.z.ecc.android.sdk.jni package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
@ -11,9 +10,13 @@ import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
internal val Backend.network: ZcashNetwork
get() = ZcashNetwork.from(networkId)
internal suspend fun Backend.initAccountsTable(vararg keys: UnifiedFullViewingKey): Boolean { internal suspend fun Backend.initAccountsTable(vararg keys: UnifiedFullViewingKey): Boolean {
val ufvks = Array(keys.size) { keys[it].encoding } val ufvks = Array(keys.size) { keys[it].encoding }
@ -21,14 +24,11 @@ internal suspend fun Backend.initAccountsTable(vararg keys: UnifiedFullViewingKe
return initAccountsTable(*ufvks) return initAccountsTable(*ufvks)
} }
internal suspend fun Backend.initAccountsTable( internal suspend fun Backend.initAccountsTableTypesafe(
seed: ByteArray, seed: ByteArray,
numberOfAccounts: Int numberOfAccounts: Int
): Array<UnifiedFullViewingKey> { ): List<UnifiedFullViewingKey> {
return DerivationTool.deriveUnifiedFullViewingKeys(seed, network, numberOfAccounts).apply { return DerivationTool.getInstance().deriveUnifiedFullViewingKeys(seed, network, numberOfAccounts)
@Suppress("SpreadOperator")
initAccountsTable(*this)
}
} }
internal suspend fun Backend.initBlocksTable(checkpoint: Checkpoint): Boolean = initBlocksTable( internal suspend fun Backend.initBlocksTable(checkpoint: Checkpoint): Boolean = initBlocksTable(
@ -80,7 +80,7 @@ internal suspend fun Backend.getVerifiedBalance(account: Account): Zatoshi = Zat
) )
internal suspend fun Backend.getNearestRewindHeight(height: BlockHeight): BlockHeight = BlockHeight.new( internal suspend fun Backend.getNearestRewindHeight(height: BlockHeight): BlockHeight = BlockHeight.new(
network, ZcashNetwork.from(networkId),
getNearestRewindHeight(height.value) getNearestRewindHeight(height.value)
) )
@ -88,7 +88,7 @@ internal suspend fun Backend.rewindToHeight(height: BlockHeight): Boolean = rewi
internal suspend fun Backend.getLatestBlockHeight(): BlockHeight? = getLatestHeight()?.let { internal suspend fun Backend.getLatestBlockHeight(): BlockHeight? = getLatestHeight()?.let {
BlockHeight.new( BlockHeight.new(
network, ZcashNetwork.from(networkId),
it it
) )
} }
@ -106,7 +106,7 @@ internal suspend fun Backend.rewindBlockMetadataToHeight(height: BlockHeight) =
internal suspend fun Backend.validateCombinedChainOrErrorBlockHeight(limit: Long?): BlockHeight? = internal suspend fun Backend.validateCombinedChainOrErrorBlockHeight(limit: Long?): BlockHeight? =
validateCombinedChainOrErrorHeight(limit)?.let { validateCombinedChainOrErrorHeight(limit)?.let {
BlockHeight.new( BlockHeight.new(
network, ZcashNetwork.from(networkId),
it it
) )
} }
@ -124,3 +124,20 @@ internal suspend fun Backend.getDownloadedUtxoBalance(address: String): WalletBa
} }
return WalletBalance(Zatoshi(total), Zatoshi(verified)) return WalletBalance(Zatoshi(total), Zatoshi(verified))
} }
@Suppress("LongParameterList")
internal suspend fun Backend.putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: BlockHeight
): Boolean = putUtxo(
tAddress,
txId,
index,
script,
value,
height.value
)

View File

@ -0,0 +1,44 @@
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
fun Derivation.deriveUnifiedAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String = deriveUnifiedAddress(seed, network.id, account.value)
fun Derivation.deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork,
): String = deriveUnifiedAddress(viewingKey, network.id)
fun Derivation.deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): UnifiedSpendingKey = UnifiedSpendingKey(deriveUnifiedSpendingKey(seed, network.id, account.value))
fun Derivation.deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey,
network: ZcashNetwork
): UnifiedFullViewingKey = UnifiedFullViewingKey(
deriveUnifiedFullViewingKey(
JniUnifiedSpendingKey(
usk.account.value,
usk.copyBytes()
),
network.id
)
)
fun Derivation.deriveUnifiedFullViewingKeysTypesafe(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int
): List<UnifiedFullViewingKey> =
deriveUnifiedFullViewingKeys(seed, network.id, numberOfAccounts).map { UnifiedFullViewingKey(it) }

View File

@ -21,6 +21,13 @@ import java.nio.channels.Channels
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
internal class SaplingParamTool(val properties: SaplingParamToolProperties) { internal class SaplingParamTool(val properties: SaplingParamToolProperties) {
val spendParamsFile: File
get() = File(properties.paramsDirectory, SPEND_PARAM_FILE_NAME)
val outputParamsFile: File
get() = File(properties.paramsDirectory, OUTPUT_PARAM_FILE_NAME)
companion object { companion object {
/** /**
* Maximum file size for the sapling spend params - 50MB * Maximum file size for the sapling spend params - 50MB

View File

@ -1,43 +0,0 @@
package cash.z.ecc.android.sdk.internal
/**
* Simple implementation of Simple moving average.
*/
class Sma(val window: Int = 3) {
private val values = Array(window) { 0.0 }
var average = 0.0
private set
var count: Int = 0
var index: Int = 0
fun add(value: Number) = add(value.toDouble())
fun add(value: Double): Double {
when {
// full window
count == window -> {
index = (index + 1) % window
average += ((value - values[index]) / count.toFloat())
values[index] = value
}
// partially-filled window
count != 0 -> {
index = (index + 1) % window
average = ((value + count.toFloat() * average) / (count + 1).toFloat())
values[index] = value
count++
}
// empty window
else -> {
// simply assign given value as current average:
average = value
values[0] = value
count = 1
}
}
return average
}
fun format(places: Int = 0) = "%.${places}f".format(average)
}

View File

@ -0,0 +1,28 @@
package cash.z.ecc.android.sdk.internal
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Implements a coroutines-friendly lazy singleton pattern with an input argument.
*
* This class is thread-safe.
*/
internal class SuspendingLazy<in Input, out Output>(private val deferredCreator: suspend ((Input) -> Output)) {
private var singletonInstance: Output? = null
private val mutex = Mutex()
suspend fun getInstance(input: Input): Output {
mutex.withLock {
singletonInstance?.let {
return it
}
val newInstance = deferredCreator(input)
singletonInstance = newInstance
return newInstance
}
}
}

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.jni package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
@ -16,7 +16,7 @@ internal interface TypesafeBackend {
suspend fun initAccountsTable( suspend fun initAccountsTable(
seed: ByteArray, seed: ByteArray,
numberOfAccounts: Int numberOfAccounts: Int
): Array<UnifiedFullViewingKey> ): List<UnifiedFullViewingKey>
suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.jni package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
// This class is currently unused, although the goal is to swap out usages of BackendExt for this throughout the SDK.
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBackend { internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBackend {
override suspend fun initAccountsTable(vararg keys: UnifiedFullViewingKey): Boolean = override suspend fun initAccountsTable(vararg keys: UnifiedFullViewingKey): Boolean =
@ -16,7 +17,7 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
override suspend fun initAccountsTable( override suspend fun initAccountsTable(
seed: ByteArray, seed: ByteArray,
numberOfAccounts: Int numberOfAccounts: Int
): Array<UnifiedFullViewingKey> = backend.initAccountsTable(seed, numberOfAccounts) ): List<UnifiedFullViewingKey> = backend.initAccountsTableTypesafe(seed, numberOfAccounts)
override suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean = backend.initBlocksTable(checkpoint) override suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean = backend.initBlocksTable(checkpoint)

View File

@ -0,0 +1,38 @@
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
internal class TypesafeDerivationToolImpl(private val derivation: Derivation) : DerivationTool {
override suspend fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int
): List<UnifiedFullViewingKey> = derivation.deriveUnifiedFullViewingKeysTypesafe(seed, network, numberOfAccounts)
override suspend fun deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey,
network: ZcashNetwork
): UnifiedFullViewingKey = derivation.deriveUnifiedFullViewingKey(usk, network)
override suspend fun deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): UnifiedSpendingKey = derivation.deriveUnifiedSpendingKey(seed, network, account)
override suspend fun deriveUnifiedAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String = derivation.deriveUnifiedAddress(seed, network, account)
override suspend fun deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork,
): String = derivation.deriveUnifiedAddress(viewingKey, network)
}

View File

@ -2,17 +2,18 @@ package cash.z.ecc.android.sdk.internal.db.derived
import android.content.Context import android.content.Context
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.NoBackupContextWrapper import cash.z.ecc.android.sdk.internal.NoBackupContextWrapper
import cash.z.ecc.android.sdk.internal.db.ReadOnlySupportSqliteOpenHelper import cash.z.ecc.android.sdk.internal.db.ReadOnlySupportSqliteOpenHelper
import cash.z.ecc.android.sdk.internal.ext.tryWarn import cash.z.ecc.android.sdk.internal.ext.tryWarn
import cash.z.ecc.android.sdk.internal.initAccountsTable
import cash.z.ecc.android.sdk.internal.initBlocksTable
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.initAccountsTable
import cash.z.ecc.android.sdk.jni.initBlocksTable
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File
internal class DerivedDataDb private constructor( internal class DerivedDataDb private constructor(
zcashNetwork: ZcashNetwork, zcashNetwork: ZcashNetwork,
@ -42,13 +43,14 @@ internal class DerivedDataDb private constructor(
@Suppress("LongParameterList", "SpreadOperator") @Suppress("LongParameterList", "SpreadOperator")
suspend fun new( suspend fun new(
context: Context, context: Context,
rustBackend: RustBackend, backend: Backend,
databaseFile: File,
zcashNetwork: ZcashNetwork, zcashNetwork: ZcashNetwork,
checkpoint: Checkpoint, checkpoint: Checkpoint,
seed: ByteArray?, seed: ByteArray?,
viewingKeys: List<UnifiedFullViewingKey> viewingKeys: List<UnifiedFullViewingKey>
): DerivedDataDb { ): DerivedDataDb {
rustBackend.initDataDb(seed) backend.initDataDb(seed)
// TODO [#681]: consider converting these to typed exceptions in the welding layer // TODO [#681]: consider converting these to typed exceptions in the welding layer
// TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681 // TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681
@ -56,22 +58,22 @@ internal class DerivedDataDb private constructor(
"Did not initialize the blocks table. It probably was already initialized.", "Did not initialize the blocks table. It probably was already initialized.",
ifContains = "table is not empty" ifContains = "table is not empty"
) { ) {
rustBackend.initBlocksTable(checkpoint) backend.initBlocksTable(checkpoint)
} }
tryWarn( tryWarn(
"Did not initialize the accounts table. It probably was already initialized.", "Did not initialize the accounts table. It probably was already initialized.",
ifContains = "table is not empty" ifContains = "table is not empty"
) { ) {
rustBackend.initAccountsTable(*viewingKeys.toTypedArray()) backend.initAccountsTable(*viewingKeys.toTypedArray())
} }
val database = ReadOnlySupportSqliteOpenHelper.openExistingDatabaseAsReadOnly( val database = ReadOnlySupportSqliteOpenHelper.openExistingDatabaseAsReadOnly(
NoBackupContextWrapper( NoBackupContextWrapper(
context, context,
rustBackend.dataDbFile.parentFile!! databaseFile.parentFile!!
), ),
rustBackend.dataDbFile, databaseFile,
DATABASE_VERSION DATABASE_VERSION
) )

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.internal package cash.z.ecc.android.sdk.internal.ext
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight

View File

@ -0,0 +1,17 @@
package cash.z.ecc.android.sdk.internal.model
import cash.z.wallet.sdk.internal.rpc.CompactFormats
import co.electriccoin.lightwallet.client.model.CompactBlockUnsafe
internal fun JniBlockMeta.Companion.new(
block: CompactFormats.CompactBlock,
outputs: CompactBlockUnsafe.CompactBlockOutputsCounts
): JniBlockMeta {
return JniBlockMeta(
height = block.height,
hash = block.hash.toByteArray(),
time = block.time.toLong(),
saplingOutputsCount = outputs.saplingOutputsCount.toLong(),
orchardOutputsCount = outputs.orchardActionsCount.toLong()
)
}

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.internal package cash.z.ecc.android.sdk.internal.model.ext
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.sdk.internal.storage.block package cash.z.ecc.android.sdk.internal.storage.block
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
@ -12,13 +13,11 @@ import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
import cash.z.ecc.android.sdk.internal.ext.renameToSuspend import cash.z.ecc.android.sdk.internal.ext.renameToSuspend
import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.ext.toHexReversed
import cash.z.ecc.android.sdk.internal.ext.writeBytesSuspend import cash.z.ecc.android.sdk.internal.ext.writeBytesSuspend
import cash.z.ecc.android.sdk.internal.findBlockMetadata
import cash.z.ecc.android.sdk.internal.getLatestBlockHeight
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository
import cash.z.ecc.android.sdk.jni.Backend import cash.z.ecc.android.sdk.internal.rewindBlockMetadataToHeight
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.findBlockMetadata
import cash.z.ecc.android.sdk.jni.getLatestBlockHeight
import cash.z.ecc.android.sdk.jni.rewindBlockMetadataToHeight
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import co.electriccoin.lightwallet.client.model.CompactBlockUnsafe import co.electriccoin.lightwallet.client.model.CompactBlockUnsafe
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -26,12 +25,12 @@ import java.io.File
internal class FileCompactBlockRepository( internal class FileCompactBlockRepository(
private val blocksDirectory: File, private val blocksDirectory: File,
private val rustBackend: Backend private val backend: Backend
) : CompactBlockRepository { ) : CompactBlockRepository {
override suspend fun getLatestHeight() = rustBackend.getLatestBlockHeight() override suspend fun getLatestHeight() = backend.getLatestBlockHeight()
override suspend fun findCompactBlock(height: BlockHeight) = rustBackend.findBlockMetadata(height) override suspend fun findCompactBlock(height: BlockHeight) = backend.findBlockMetadata(height)
override suspend fun write(blocks: Flow<CompactBlockUnsafe>): List<JniBlockMeta> { override suspend fun write(blocks: Flow<CompactBlockUnsafe>): List<JniBlockMeta> {
val processingBlocks = mutableListOf<JniBlockMeta>() val processingBlocks = mutableListOf<JniBlockMeta>()
@ -66,11 +65,11 @@ internal class FileCompactBlockRepository(
* Write block metadata to storage when the buffer is full or when we reached the current range end. * Write block metadata to storage when the buffer is full or when we reached the current range end.
*/ */
private suspend fun writeAndClearBuffer(metaDataBuffer: MutableList<JniBlockMeta>) { private suspend fun writeAndClearBuffer(metaDataBuffer: MutableList<JniBlockMeta>) {
rustBackend.writeBlockMetadata(metaDataBuffer) backend.writeBlockMetadata(metaDataBuffer)
metaDataBuffer.clear() metaDataBuffer.clear()
} }
override suspend fun rewindTo(height: BlockHeight) = rustBackend.rewindBlockMetadataToHeight(height) override suspend fun rewindTo(height: BlockHeight) = backend.rewindBlockMetadataToHeight(height)
override suspend fun deleteAllCompactBlockFiles(): Boolean { override suspend fun deleteAllCompactBlockFiles(): Boolean {
Twig.verbose { "Deleting all blocks from directory ${blocksDirectory.path}" } Twig.verbose { "Deleting all blocks from directory ${blocksDirectory.path}" }
@ -129,13 +128,16 @@ internal class FileCompactBlockRepository(
*/ */
const val BLOCKS_METADATA_BUFFER_SIZE = 10 const val BLOCKS_METADATA_BUFFER_SIZE = 10
/**
* @param blockCacheRoot The root directory for the compact block cache (contains the database and a
* subdirectory for the blocks).
*/
suspend fun new( suspend fun new(
rustBackend: RustBackend blockCacheRoot: File,
rustBackend: Backend
): FileCompactBlockRepository { ): FileCompactBlockRepository {
Twig.debug { "${rustBackend.fsBlockDbRoot.absolutePath} \n ${rustBackend.dataDbFile.absolutePath}" }
// create and check cache directories // create and check cache directories
val blocksDirectory = File(rustBackend.fsBlockDbRoot, BLOCKS_DOWNLOAD_DIRECTORY).also { val blocksDirectory = File(blockCacheRoot, BLOCKS_DOWNLOAD_DIRECTORY).also {
it.mkdirsSuspend() it.mkdirsSuspend()
} }
if (!blocksDirectory.existsSuspend()) { if (!blocksDirectory.existsSuspend()) {

View File

@ -2,14 +2,15 @@ package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.exception.TransactionEncoderException import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.ext.masked import cash.z.ecc.android.sdk.ext.masked
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.SaplingParamTool import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.createToAddress
import cash.z.ecc.android.sdk.internal.getBranchIdForHeight
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.internal.network
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
import cash.z.ecc.android.sdk.jni.Backend import cash.z.ecc.android.sdk.internal.shieldToAddress
import cash.z.ecc.android.sdk.jni.createToAddress
import cash.z.ecc.android.sdk.jni.getBranchIdForHeight
import cash.z.ecc.android.sdk.jni.shieldToAddress
import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
@ -19,7 +20,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
* behaving like a stateless API so that callers can request [createTransaction] and receive a * behaving like a stateless API so that callers can request [createTransaction] and receive a
* result, even though there are intermediate database interactions. * result, even though there are intermediate database interactions.
* *
* @property backend the instance of RustBackendWelding to use for creating and validating. * @property rustBackend the instance of RustBackendWelding to use for creating and validating.
* @property repository the repository that stores information about the transactions being created * @property repository the repository that stores information about the transactions being created
* such as the raw bytes and raw txId. * such as the raw bytes and raw txId.
*/ */
@ -132,7 +133,7 @@ internal class TransactionEncoderImpl(
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
return try { return try {
saplingParamTool.ensureParams(backend.saplingParamDir) saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
Twig.debug { "params exist! attempting to send..." } Twig.debug { "params exist! attempting to send..." }
backend.createToAddress( backend.createToAddress(
usk, usk,
@ -154,7 +155,7 @@ internal class TransactionEncoderImpl(
): Long { ): Long {
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
return try { return try {
saplingParamTool.ensureParams(backend.saplingParamDir) saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
Twig.debug { "params exist! attempting to shield..." } Twig.debug { "params exist! attempting to shield..." }
backend.shieldToAddress( backend.shieldToAddress(
usk, usk,

View File

@ -1,37 +0,0 @@
package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
// Implemented by `DerivationTool`
interface Derivation {
suspend fun deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork
): String
suspend fun deriveUnifiedAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String
suspend fun deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): UnifiedSpendingKey
suspend fun deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey,
network: ZcashNetwork
): UnifiedFullViewingKey
suspend fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int = 1
): Array<UnifiedFullViewingKey>
}

View File

@ -1,7 +1,7 @@
package cash.z.ecc.android.sdk.model package cash.z.ecc.android.sdk.model
import cash.z.ecc.android.sdk.jni.RustBackend import cash.z.ecc.android.sdk.internal.jni.RustBackend
import cash.z.ecc.android.sdk.jni.UnifiedSpendingKeyJni import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
/** /**
* A [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending Key. * A [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending Key.
@ -26,9 +26,9 @@ class UnifiedSpendingKey private constructor(
private val bytes: FirstClassByteArray private val bytes: FirstClassByteArray
) { ) {
internal constructor(uskJni: UnifiedSpendingKeyJni) : this( internal constructor(uskJni: JniUnifiedSpendingKey) : this(
Account(uskJni.account), Account(uskJni.account),
FirstClassByteArray(uskJni.copyBytes()) FirstClassByteArray(uskJni.bytes.copyOf())
) )
/** /**
@ -75,9 +75,7 @@ class UnifiedSpendingKey private constructor(
val bytesCopy = bytes.copyOf() val bytesCopy = bytes.copyOf()
RustBackend.loadLibrary() RustBackend.loadLibrary()
return runCatching { return runCatching {
// We can ignore the Boolean returned from this, because if an error require(RustBackend.validateUnifiedSpendingKey(bytesCopy))
// occurs the Rust side will throw.
RustBackend.validateUnifiedSpendingKey(bytesCopy)
UnifiedSpendingKey(account, FirstClassByteArray(bytesCopy)) UnifiedSpendingKey(account, FirstClassByteArray(bytesCopy))
} }
} }

View File

@ -4,8 +4,8 @@ import android.content.Context
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import cash.z.ecc.android.sdk.exception.BirthdayException import cash.z.ecc.android.sdk.exception.BirthdayException
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.from
import cash.z.ecc.android.sdk.internal.model.Checkpoint import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.ext.from
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View File

@ -1,15 +1,15 @@
package cash.z.ecc.android.sdk.tool package cash.z.ecc.android.sdk.tool
import cash.z.ecc.android.sdk.jni.Derivation import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.jni.RustBackend import cash.z.ecc.android.sdk.internal.SuspendingLazy
import cash.z.ecc.android.sdk.jni.UnifiedSpendingKeyJni import cash.z.ecc.android.sdk.internal.TypesafeDerivationToolImpl
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
@Suppress("TooManyFunctions") interface DerivationTool {
object DerivationTool : Derivation {
/** /**
* Given a seed and a number of accounts, return the associated Unified Full Viewing Keys. * Given a seed and a number of accounts, return the associated Unified Full Viewing Keys.
@ -20,16 +20,11 @@ object DerivationTool : Derivation {
* *
* @return the UFVKs derived from the seed, encoded as Strings. * @return the UFVKs derived from the seed, encoded as Strings.
*/ */
override suspend fun deriveUnifiedFullViewingKeys( suspend fun deriveUnifiedFullViewingKeys(
seed: ByteArray, seed: ByteArray,
network: ZcashNetwork, network: ZcashNetwork,
numberOfAccounts: Int numberOfAccounts: Int
): Array<UnifiedFullViewingKey> = ): List<UnifiedFullViewingKey>
withRustBackendLoaded {
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = network.id).map {
UnifiedFullViewingKey(it)
}.toTypedArray()
}
/** /**
* Given a unified spending key, return the associated unified full viewing key. * Given a unified spending key, return the associated unified full viewing key.
@ -38,14 +33,10 @@ object DerivationTool : Derivation {
* *
* @return a unified full viewing key. * @return a unified full viewing key.
*/ */
override suspend fun deriveUnifiedFullViewingKey( suspend fun deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey, usk: UnifiedSpendingKey,
network: ZcashNetwork network: ZcashNetwork
): UnifiedFullViewingKey = withRustBackendLoaded { ): UnifiedFullViewingKey
UnifiedFullViewingKey(
deriveUnifiedFullViewingKey(usk.copyBytes(), networkId = network.id)
)
}
/** /**
* Derives and returns a unified spending key from the given seed for the given account ID. * Derives and returns a unified spending key from the given seed for the given account ID.
@ -59,26 +50,21 @@ object DerivationTool : Derivation {
* *
* @return the unified spending key for the account. * @return the unified spending key for the account.
*/ */
override suspend fun deriveUnifiedSpendingKey( suspend fun deriveUnifiedSpendingKey(
seed: ByteArray, seed: ByteArray,
network: ZcashNetwork, network: ZcashNetwork,
account: Account account: Account
): UnifiedSpendingKey = withRustBackendLoaded { ): UnifiedSpendingKey
UnifiedSpendingKey(deriveSpendingKey(seed, account.value, networkId = network.id))
}
/** /**
* Given a seed and account index, return the associated Unified Address. * Given a seed and account index, return the associated Unified Address.
* *
* @param seed the seed from which to derive the address. * @param seed the seed from which to derive the address.
* @param accountIndex the index of the account to use for deriving the address. * @param account the index of the account to use for deriving the address.
* *
* @return the address that corresponds to the seed and account index. * @return the address that corresponds to the seed and account index.
*/ */
override suspend fun deriveUnifiedAddress(seed: ByteArray, network: ZcashNetwork, account: Account): String = suspend fun deriveUnifiedAddress(seed: ByteArray, network: ZcashNetwork, account: Account): String
withRustBackendLoaded {
deriveUnifiedAddressFromSeed(seed, account.value, networkId = network.id)
}
/** /**
* Given a Unified Full Viewing Key string, return the associated Unified Address. * Given a Unified Full Viewing Key string, return the associated Unified Address.
@ -88,56 +74,17 @@ object DerivationTool : Derivation {
* *
* @return the address that corresponds to the viewing key. * @return the address that corresponds to the viewing key.
*/ */
override suspend fun deriveUnifiedAddress( suspend fun deriveUnifiedAddress(
viewingKey: String, viewingKey: String,
network: ZcashNetwork network: ZcashNetwork
): String = withRustBackendLoaded {
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = network.id)
}
@Suppress("UNUSED_PARAMETER")
fun validateUnifiedFullViewingKey(viewingKey: UnifiedFullViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) {
// TODO [#654] https://github.com/zcash/zcash-android-wallet-sdk/issues/654
}
/**
* A helper function to ensure that the Rust libraries are loaded before any code in this
* class attempts to interact with it, indirectly, by invoking JNI functions. It would be
* nice to have an annotation like @UsesSystemLibrary for this
*/
private suspend fun <T> withRustBackendLoaded(block: () -> T): T {
RustBackend.loadLibrary()
return block()
}
//
// JNI functions
//
@JvmStatic
private external fun deriveSpendingKey(
seed: ByteArray,
account: Int,
networkId: Int
): UnifiedSpendingKeyJni
@JvmStatic
private external fun deriveUnifiedFullViewingKeysFromSeed(
seed: ByteArray,
numberOfAccounts: Int,
networkId: Int
): Array<String>
@JvmStatic
private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String
@JvmStatic
private external fun deriveUnifiedAddressFromSeed(
seed: ByteArray,
accountIndex: Int,
networkId: Int
): String ): String
@JvmStatic companion object {
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String const val DEFAULT_NUMBER_OF_ACCOUNTS = Derivation.DEFAULT_NUMBER_OF_ACCOUNTS
private val instance = SuspendingLazy<Unit, DerivationTool> {
TypesafeDerivationToolImpl(RustDerivationTool.new())
}
suspend fun getInstance() = instance.getInstance(Unit)
}
} }

View File

@ -1,46 +0,0 @@
task unregistered {
println "configuring unregistered"
doLast {
println 'unregistered'
}
}
tasks.register("pb") {
println "configuring pb"
doLast {
println 'preBuild'
}
}
tasks.register("generateJni") {
println "configuring generateJni"
doLast {
println 'jni'
}
}
tasks.register("copyA") {
dependsOn generateJni
println "configuring copyA"
doLast {
println 'copyA'
}
}
tasks.register("copyB") {
dependsOn generateJni
println "configuring copyB"
doLast {
println 'copyB'
}
}
tasks.register("copyC") {
dependsOn generateJni
println "configuring copyC"
doLast {
println 'copyC'
}
}
task copyAll {
dependsOn copyA, copyB, copyC
}
pb.dependsOn copyAll

View File

@ -269,6 +269,7 @@ rootProject.name = "zcash-android-sdk"
includeBuild("build-conventions") includeBuild("build-conventions")
include("backend-lib")
include("darkside-test-lib") include("darkside-test-lib")
include("demo-app") include("demo-app")
include("demo-app-benchmark-test") include("demo-app-benchmark-test")