Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/previous hash prefs #21

Merged
merged 20 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions sdk/src/androidTest/java/network/xyo/client/account/AccountTest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package network.xyo.client.account

import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository
import org.junit.Before
import org.junit.Test

class AccountTest {
Expand All @@ -12,6 +16,11 @@ class AccountTest {
val testVectorSignature
= "b61dad551e910e2793b4f9f880125b5799086510ce102fad0222c1b093c60a6b38aa35ef56f97f86537269e8be95832aaa37d3b64d86b67f0cda467ac7cb5b3e"

@Before
fun setupAccount() {
Account.previousHashStore = PreviousHashStorePrefsRepository.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)
}

@Test
fun testRandomAccount() {
val account = Account.random()
Expand All @@ -22,13 +31,29 @@ class AccountTest {
@OptIn(ExperimentalStdlibApi::class)
@Test
fun testKnownPrivateKeyAccount() {
val account = Account.fromPrivateKey(hexStringToByteArray(testVectorPrivateKey))
assert(account.privateKey.count() == 32)
assert(account.publicKey.count() == 64)
assert(account.publicKey.toHexString() == testVectorPublicKey)
assert(account.address.toHexString() == testVectorAddress)
val signature = account.sign(hexStringToByteArray(testVectorHash))
assert(signature.toHexString() == testVectorSignature)
assert(account.verify(hexStringToByteArray(testVectorHash), signature))
runBlocking {
val account = Account.fromPrivateKey(hexStringToByteArray(testVectorPrivateKey))
assert(account.privateKey.count() == 32)
assert(account.publicKey.count() == 64)
assert(account.publicKey.toHexString() == testVectorPublicKey)
assert(account.address.toHexString() == testVectorAddress)
val signature = account.sign(hexStringToByteArray(testVectorHash))
assert(signature.toHexString() == testVectorSignature)
assert(account.verify(hexStringToByteArray(testVectorHash), signature))
}
}

@OptIn(ExperimentalStdlibApi::class)
@Test
fun testPreviousHash() {
runBlocking {
val address = hexStringToByteArray(testVectorPrivateKey)
val account = Account.fromPrivateKey(address)
account.sign(hexStringToByteArray(testVectorHash))

val savedAddressInStore = Account.addressFromPublicKey(account.publicKey)
val previousHashInStore = Account.previousHashStore?.getItem(savedAddressInStore)?.toHexString()
assert(previousHashInStore == testVectorHash)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package network.xyo.client
package network.xyo.client.prefs

import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import network.xyo.client.TestConstants
import network.xyo.client.XyoPanel
import network.xyo.client.account.Account
import network.xyo.client.boundwitness.XyoBoundWitnessBuilder
import network.xyo.client.datastore.AccountPrefsRepository
import network.xyo.client.datastore.accounts.AccountPrefsRepository
import network.xyo.client.settings.AccountPreferences
import network.xyo.client.settings.PreviousHashStorePreferences
import network.xyo.client.settings.SettingsInterface
import network.xyo.client.witness.system.info.XyoSystemInfoWitness
import org.junit.Before
import org.junit.Test
Expand Down Expand Up @@ -80,19 +84,29 @@ class AccountPrefsRepositoryTest {
override val storagePath = "__xyo-client-sdk-1__"
}

val updatedAccountPrefs = UpdatedAccountPreferences()
class UpdatedPreviousHashShorePreferences : PreviousHashStorePreferences {
override val fileName = "network-xyo-sdk-prefs-2"
override val storagePath = "__xyo-client-sdk-1__"
}

class Settings: SettingsInterface {
override val accountPreferences = UpdatedAccountPreferences()
override val previousHashStorePreferences = UpdatedPreviousHashShorePreferences()
}

val updatedSettings = Settings()

val refreshedInstance =
AccountPrefsRepository.refresh(appContext, updatedAccountPrefs)
AccountPrefsRepository.refresh(appContext, updatedSettings)

// Test that accountPreferences are updated
assertEquals(
refreshedInstance.accountPreferences.fileName,
updatedAccountPrefs.fileName
updatedSettings.accountPreferences.fileName
)
assertEquals(
refreshedInstance.accountPreferences.storagePath,
updatedAccountPrefs.storagePath
updatedSettings.accountPreferences.storagePath
)

val refreshedAddress = refreshedInstance.getAccount().privateKey.toHexString()
Expand All @@ -117,15 +131,19 @@ class AccountPrefsRepositoryTest {
assertEquals(firstAccount.privateKey.toHexString(), testAccount.privateKey.toHexString())

// Sign with the test account
val firstBw = XyoBoundWitnessBuilder().witness(firstAccount, null).payloads(listOf(TestConstants.debugPayload)).build()
val firstBw = XyoBoundWitnessBuilder().witness(firstAccount, null).payloads(listOf(
TestConstants.debugPayload
)).build()
val firstAddress = firstBw.addresses.first()

// Deserialize the test account (Ideally we would refresh the singleton but in tests this seems to cause errors with multiple instances of the prefs DataStore)
val secondInstance = AccountPrefsRepository.getInstance(appContext)
val secondAccount = secondInstance.getAccount()

// Sign with the test account
val secondBw = XyoBoundWitnessBuilder().witness(secondAccount, null).payloads(listOf(TestConstants.debugPayload)).build()
val secondBw = XyoBoundWitnessBuilder().witness(secondAccount, null).payloads(listOf(
TestConstants.debugPayload
)).build()
val secondAddress = secondBw.addresses.first()

// check that addresses have not changed and no errors occurred during signing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package network.xyo.client.prefs

import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import network.xyo.client.TestConstants
import network.xyo.client.account.Account
import network.xyo.client.account.hexStringToByteArray
import network.xyo.client.datastore.previous_hash_store.PreviousHashStorePrefsRepository
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertNotNull

class PreviousHashStorePrefsRepositoryTest {
private lateinit var appContext: Context

@Before
fun useAppContext() {
// Context of the app under test.
this.appContext = InstrumentationRegistry.getInstrumentation().targetContext
}

@OptIn(ExperimentalStdlibApi::class)
@Test
fun testPreviousHashStorePersistence() {
runBlocking {
val prefsRepository =
PreviousHashStorePrefsRepository.getInstance(appContext)
prefsRepository.clearStore()

val testAccountAddress = Account.random().address
val testPreviousHash = hexStringToByteArray(TestConstants.debugPayloadHash)
prefsRepository.setItem(testAccountAddress, testPreviousHash)

val savedItem = prefsRepository.getItem(testAccountAddress)
assertNotNull(savedItem)
assert(savedItem!!.contentEquals(testPreviousHash))

prefsRepository.removeItem(testAccountAddress)
val removedItem = prefsRepository.getItem(testAccountAddress)
assert(removedItem == null)
}
}
}
3 changes: 2 additions & 1 deletion sdk/src/main/java/network/xyo/client/account/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ open class Account private constructor (private val _privateKey: PrivateKey, pri
final override val publicKey: ByteArray
get() = _privateKey.toPublicKey().key.toBytesPadded(64)

override fun sign(hash: ByteArray): ByteArray {
override suspend fun sign(hash: ByteArray): ByteArray {
val result = BCECSigner().sign(_privateKey, hash)
_previousHash = hash
previousHashStore?.setItem(address, hash)
return result.encodeAsBTC().toByteArray()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package network.xyo.client.account.model

interface PreviousHashStore {
fun getItem(address: ByteArray): ByteArray?
fun removeItem(address: ByteArray)
fun setItem(address: ByteArray, previousHash: ByteArray)
suspend fun getItem(address: ByteArray): ByteArray?
suspend fun removeItem(address: ByteArray)
suspend fun setItem(address: ByteArray, previousHash: ByteArray)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package network.xyo.client.account.model

interface PrivateKeyInstance: PublicKeyInstance {
fun sign(hash: ByteArray): ByteArray
suspend fun sign(hash: ByteArray): ByteArray
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class QueryBoundWitnessBuilder : XyoBoundWitnessBuilder() {
return this
}

override fun build(previousHash: String?): QueryBoundWitnessJson {
override suspend fun build(previousHash: String?): QueryBoundWitnessJson {
bw = QueryBoundWitnessJson()
// override to support additional properties for query bound witnesses
return bw.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ open class XyoBoundWitnessBuilder {
return this
}

private fun sign(hash: String): List<String> {
private suspend fun sign(hash: String): List<String> {
return _witnesses.map {
val sig = XyoSerializable.bytesToHex(it.sign(hexStringToByteArray(hash)))
sig
}
}

protected fun constructFields() {
protected suspend fun constructFields() {
// update json class properties
bw.payload_hashes = _payload_hashes
bw.payload_schemas = _payload_schemas
Expand All @@ -82,7 +82,7 @@ open class XyoBoundWitnessBuilder {
constructHashableFieldsFields()
}

private fun constructHashableFieldsFields() {
private suspend fun constructHashableFieldsFields() {
// Note: Once fields are hashed, do not update class properties that are expected
// in the serialized version of the bw because they will invalidate the hash
val hashable = hashableFields()
Expand All @@ -91,7 +91,7 @@ open class XyoBoundWitnessBuilder {
bw._hash = hash
}

open fun build(previousHash: String? = null): XyoBoundWitnessJson {
open suspend fun build(previousHash: String? = null): XyoBoundWitnessJson {
return bw.let{
// store the previous hash on the class
it._previous_hash = previousHash
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
package network.xyo.client.datastore
package network.xyo.client.datastore.accounts

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.MultiProcessDataStoreFactory
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import network.xyo.client.settings.DefaultXyoSdkSettings
import network.xyo.data.PrefsDataStoreProtos.PrefsDataStore
import network.xyo.client.settings.defaultXyoSdkSettings
import network.xyo.data.AccountPrefsDataStoreProtos.AccountPrefsDataStore
import java.io.File

val defaults = DefaultXyoSdkSettings()

fun Context.xyoAccountDataStore(name: String?, path: String?): DataStore<PrefsDataStore> {
val resolvedName = name ?: defaults.accountPreferences.fileName
val resolvedPath = path ?: defaults.accountPreferences.storagePath
fun Context.xyoAccountDataStore(name: String?, path: String?): DataStore<AccountPrefsDataStore> {
val resolvedName = name ?: defaultXyoSdkSettings.accountPreferences.fileName
val resolvedPath = path ?: defaultXyoSdkSettings.accountPreferences.storagePath

val dataStoreFile = File(filesDir, "$resolvedPath/$resolvedName")

return MultiProcessDataStoreFactory.create(
serializer = PrefsDataStoreSerializer,
serializer = AccountPrefsDataStoreSerializer,
produceFile = { dataStoreFile },
corruptionHandler = ReplaceFileCorruptionHandler(
produceNewData = { PrefsDataStore.getDefaultInstance() }
produceNewData = { AccountPrefsDataStore.getDefaultInstance() }
),
scope = CoroutineScope(Dispatchers.IO)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package network.xyo.client.datastore.accounts

import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import com.google.protobuf.InvalidProtocolBufferException
import network.xyo.data.AccountPrefsDataStoreProtos.AccountPrefsDataStore
import java.io.InputStream
import java.io.OutputStream

object AccountPrefsDataStoreSerializer : Serializer<AccountPrefsDataStore> {
override val defaultValue: AccountPrefsDataStore = AccountPrefsDataStore.getDefaultInstance()
override suspend fun readFrom(input: InputStream): AccountPrefsDataStore {
try {
return AccountPrefsDataStore.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}

override suspend fun writeTo(t: AccountPrefsDataStore, output: OutputStream) = t.writeTo(output)
}
Loading
Loading