diff --git a/build.gradle b/build.gradle index 41b5d801..33a0c6cc 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext.kotlin_version = '1.4.21' - ext.ktlint_version = '0.36.0' + ext.ktlint_version = '0.41.0' ext.coroutines_version = '1.4.2' ext.ktlint_gradle_version = '9.1.1' // https://github.com/cashapp/sqldelight/issues/1574 @@ -18,7 +18,7 @@ buildscript { maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0' + classpath 'com.android.tools.build:gradle:4.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jlleitschuh.gradle:ktlint-gradle:$ktlint_gradle_version" classpath "com.squareup.sqldelight:gradle-plugin:$sqldelight_version" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 38750810..cefe5d5a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 12 11:23:56 CET 2021 +#Tue May 11 09:51:47 CEST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/ipv8-android/src/main/java/nl/tudelft/ipv8/android/messaging/bluetooth/BluetoothLeDiscovery.kt b/ipv8-android/src/main/java/nl/tudelft/ipv8/android/messaging/bluetooth/BluetoothLeDiscovery.kt index 740ac743..c1cbb01d 100644 --- a/ipv8-android/src/main/java/nl/tudelft/ipv8/android/messaging/bluetooth/BluetoothLeDiscovery.kt +++ b/ipv8-android/src/main/java/nl/tudelft/ipv8/android/messaging/bluetooth/BluetoothLeDiscovery.kt @@ -10,7 +10,7 @@ private val logger = KotlinLogging.logger {} * A strategy for selecting discovered Bluetooth peers we should connect to. */ class BluetoothLeDiscovery( - private val overlay: Overlay, + override val overlay: Overlay, private val peers: Int ) : DiscoveryStrategy { override fun takeStep() { diff --git a/ipv8-android/src/main/java/nl/tudelft/ipv8/android/peerdiscovery/NetworkServiceDiscovery.kt b/ipv8-android/src/main/java/nl/tudelft/ipv8/android/peerdiscovery/NetworkServiceDiscovery.kt index 47e39637..ea25a811 100644 --- a/ipv8-android/src/main/java/nl/tudelft/ipv8/android/peerdiscovery/NetworkServiceDiscovery.kt +++ b/ipv8-android/src/main/java/nl/tudelft/ipv8/android/peerdiscovery/NetworkServiceDiscovery.kt @@ -16,7 +16,7 @@ private val logger = KotlinLogging.logger {} */ class NetworkServiceDiscovery( private val nsdManager: NsdManager, - private val overlay: Overlay + override val overlay: Overlay ) : DiscoveryStrategy { private var serviceName: String? = null diff --git a/ipv8/build.gradle b/ipv8/build.gradle index ee4e7fb9..6f6ecb93 100644 --- a/ipv8/build.gradle +++ b/ipv8/build.gradle @@ -10,6 +10,8 @@ apply plugin: 'org.jlleitschuh.gradle.ktlint' apply plugin: 'com.squareup.sqldelight' +apply plugin: 'kotlin-kapt' + dokka { outputFormat = 'html' outputDirectory = "$buildDir/dokka" @@ -78,6 +80,13 @@ dependencies { testImplementation "com.squareup.sqldelight:sqlite-driver:$sqldelight_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" + implementation "org.openjdk.jmh:jmh-core:1.21" + kapt "org.openjdk.jmh:jmh-generator-annprocess:1.21" + + // Guava + implementation "com.google.guava:guava:30.1.1-jre" + implementation "com.squareup.sqldelight:sqlite-driver:$sqldelight_version" + // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.63' @@ -88,6 +97,7 @@ dependencies { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions.freeCompilerArgs += [ "-Xuse-experimental=kotlin.Experimental,kotlin.ExperimentalUnsignedTypes", + "-Xopt-in=kotlin.RequiresOptIn", "-Werror" // Set Kotlin compiler warnings as errors ] } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/Community.kt b/ipv8/src/main/java/nl/tudelft/ipv8/Community.kt index 6b19b02c..28a25485 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/Community.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/Community.kt @@ -76,9 +76,8 @@ abstract class Community : Overlay { } override fun unload() { - super.unload() - job.cancel() + super.unload() } override fun bootstrap() { diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/IPv8.kt b/ipv8/src/main/java/nl/tudelft/ipv8/IPv8.kt index ee0fdb63..b3444b2a 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/IPv8.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/IPv8.kt @@ -11,11 +11,21 @@ class IPv8( private val endpoint: EndpointAggregator, private val configuration: IPv8Configuration, val myPeer: Peer, - val network: Network = Network() + val network: Network = Network(), ) { private val overlayLock = Object() + /* + * Primary Overlays. Should not contain duplicates. + */ val overlays = mutableMapOf, Overlay>() + + /* + * Secondary Overlays. These contain alternative channels of an Overlay. + * Ensure unique Service IDs. TODO: remove Overlays singleton constraint. + */ + val secondaryOverlays = mutableMapOf() + private val strategies = mutableListOf() private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -31,6 +41,10 @@ class IPv8( return overlays[T::class.java] as? T } + inline fun getSecondaryOverlay(serviceId: String): T? { + return secondaryOverlays[serviceId] as? T + } + fun start() { if (isStarted()) throw IllegalStateException("IPv8 has already started") @@ -68,6 +82,45 @@ class IPv8( startLoopingCall() } + /* + * Method for adding secondary overlays. These allow for multiple class instances, however, service IDs must be unique. + * As a consequence, the used peer, endpoint and network can be different than the main ones. + */ + fun addSecondaryOverlayStrategy(overlayConfiguration: SecondaryOverlayConfiguration<*>): Overlay { + synchronized(overlayLock) { + val overlay = overlayConfiguration.factory.create() + if (!this.secondaryOverlays.containsKey(overlay.serviceId)) { + overlay.myPeer = overlayConfiguration.myPeer ?: myPeer + overlay.endpoint = overlayConfiguration.endpoint ?: endpoint + overlay.network = overlayConfiguration.network ?: network + overlay.maxPeers = overlayConfiguration.maxPeers + overlay.load() + + this.secondaryOverlays[overlay.serviceId] = overlay + + for (strategyFactory in overlayConfiguration.walkers) { + val strategy = strategyFactory + .setOverlay(overlay) + .create() + strategy.load() + strategies.add(strategy) + } + } + return secondaryOverlays[overlay.serviceId]!! + } + } + + fun unloadSecondaryOverlayStrategy(serviceId: String) { + synchronized(overlayLock) { + val overlay = this.secondaryOverlays.remove(serviceId) + for (strategy in strategies) { + if (strategy.overlay == overlay) { + this.strategies.remove(strategy) + } + } + } + } + private fun onTick() { if (endpoint.isOpen()) { synchronized(overlayLock) { @@ -104,6 +157,11 @@ class IPv8( } overlays.clear() + for ((_, overlay) in secondaryOverlays) { + overlay.unload() + } + secondaryOverlays.clear() + for (strategy in strategies) { strategy.unload() } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/IPv8Configuration.kt b/ipv8/src/main/java/nl/tudelft/ipv8/IPv8Configuration.kt index 33adc73a..f9634bdb 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/IPv8Configuration.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/IPv8Configuration.kt @@ -1,5 +1,7 @@ package nl.tudelft.ipv8 +import nl.tudelft.ipv8.messaging.EndpointAggregator +import nl.tudelft.ipv8.peerdiscovery.Network import nl.tudelft.ipv8.peerdiscovery.strategy.DiscoveryStrategy class IPv8Configuration( @@ -9,8 +11,17 @@ class IPv8Configuration( val overlays: List> ) -class OverlayConfiguration( +open class OverlayConfiguration( val factory: Overlay.Factory, val walkers: List>, val maxPeers: Int = 30 ) + +class SecondaryOverlayConfiguration( + factory: Overlay.Factory, + walkers: List>, + maxPeers: Int = 30, + val myPeer: Peer? = null, + val endpoint: EndpointAggregator? = null, + val network: Network? = null +) : OverlayConfiguration(factory, walkers, maxPeers) diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/Overlay.kt b/ipv8/src/main/java/nl/tudelft/ipv8/Overlay.kt index e3a9ab04..78170b8c 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/Overlay.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/Overlay.kt @@ -22,7 +22,7 @@ interface Overlay : EndpointListener { get() = myPeer.lamportTimestamp /** - * Called to inintialize this overlay. + * Called to initialize this overlay. */ fun load() { endpoint.addListener(this) @@ -84,7 +84,7 @@ interface Overlay : EndpointListener { /** * Get a peer for introduction. * - * @param Optionally specify a peer that is not considered eligible for introduction. + * @param exclude Optionally specify a peer that is not considered eligible for introduction. * @return A peer to send an introduction request to, or null if are none available. */ fun getPeerForIntroduction(exclude: Peer? = null): Peer? diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/TrustedAuthorityManager.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/TrustedAuthorityManager.kt deleted file mode 100644 index b3483f79..00000000 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/TrustedAuthorityManager.kt +++ /dev/null @@ -1,60 +0,0 @@ -package nl.tudelft.ipv8.attestation - -import nl.tudelft.ipv8.attestation.wallet.AttestationStore -import nl.tudelft.ipv8.keyvault.PublicKey -import nl.tudelft.ipv8.util.toHex - -class Authority( - val publicKey: PublicKey, - val hash: String, -) - -class TrustedAuthorityManager(private val database: AttestationStore) { - - private val trustedAuthorities = hashMapOf() - private val lock = Object() - - fun loadAuthorities() { - val authorities = database.getAllAuthorities() - synchronized(lock) { - authorities.forEach { - trustedAuthorities[it.hash] = Authority(it.publicKey, it.hash) - } - } - } - - fun loadDefaultAuthorities() { - TODO("Preinstalled Authorities yet to be designed.") - } - - fun getAuthorities(): List { - return this.database.getAllAuthorities() - } - - fun addTrustedAuthority(publicKey: PublicKey) { - val hash = publicKey.keyToHash().toHex() - if (!this.contains(hash)) { - database.insertAuthority(publicKey, hash) - synchronized(lock) { - this.trustedAuthorities[hash] = Authority(publicKey, hash) - } - } - } - - fun deleteTrustedAuthority(hash: String) { - if (this.contains(hash)) { - this.trustedAuthorities.remove(hash) - this.database.deleteAuthorityByHash(hash) - } - } - - fun deleteTrustedAuthority(publicKey: PublicKey) { - return this.deleteTrustedAuthority(publicKey.keyToHash().toHex()) - } - - fun contains(hash: String): Boolean { - return this.trustedAuthorities.containsKey(hash) - } - - -} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/AuthorityManager.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/AuthorityManager.kt new file mode 100644 index 00000000..727b2bf9 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/AuthorityManager.kt @@ -0,0 +1,167 @@ +package nl.tudelft.ipv8.attestation.common + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import mu.KotlinLogging +import nl.tudelft.ipv8.attestation.revocation.AuthorityStore +import nl.tudelft.ipv8.attestation.revocation.RevocationBlob +import nl.tudelft.ipv8.attestation.revocation.datastructures.BloomFilter +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.util.ByteArrayKey +import nl.tudelft.ipv8.util.toKey + +class Authority( + val publicKey: PublicKey?, + val hash: ByteArray, + var version: Long = 0L, + val recognized: Boolean = false, +) + +const val DEFAULT_CAPACITY = 100 +private val logger = KotlinLogging.logger {} + +class AuthorityManager(val authorityDatabase: AuthorityStore) { + + private val trustedAuthorities = hashMapOf() + private val revocations: BloomFilter + private val lock = Object() + + init { + loadTrustedAuthorities() + val revocationsAmount = authorityDatabase.getNumberOfRevocations() + if (revocationsAmount + 100 >= Integer.MAX_VALUE) { + logger.error("Number of revocations overflows bloom filter capacity.") + } + revocations = BloomFilter(revocationsAmount.toInt() + DEFAULT_CAPACITY) + GlobalScope.launch { + authorityDatabase.getAllRevocations().forEach { revocations.add(it) } + } + } + + fun loadTrustedAuthorities() { + val authorities = authorityDatabase.getRecognizedAuthorities() + synchronized(lock) { + authorities.forEach { + trustedAuthorities[it.hash.toKey()] = it + } + } + } + + fun verify(signature: ByteArray): Boolean { + return !(this.revocations.probablyContains(signature) && authorityDatabase.isRevoked( + signature + )) + } + + fun verify(signature: ByteArray, authorityKeyHash: ByteArray): Boolean { + return !(this.revocations.probablyContains(signature) && authorityDatabase.isRevokedBy( + signature, + authorityKeyHash + )) + } + + fun insertRevocations( + publicKeyHash: ByteArray, + versionNumber: Long, + signature: ByteArray, + revokedHashes: List + ) { + authorityDatabase.insertRevocations(publicKeyHash, versionNumber, signature, revokedHashes) + if (this.trustedAuthorities.containsKey(publicKeyHash.toKey())) { + this.trustedAuthorities[publicKeyHash.toKey()]!!.version = versionNumber + } + revokedHashes.forEach { revocations.add(it) } + } + + /** + * Method for fetching all latest known versions. + */ + fun getLatestRevocationPreviews(): Map { + val authorities = authorityDatabase.getKnownAuthorities() + val localRefs = hashMapOf() + authorities.forEach { + if (it.version > 0) + localRefs[it.hash.toKey()] = it.version + } + return localRefs + } + + /** + * Method for fetching the lowest missing versions. + */ + fun getMissingRevocationPreviews(): Map { + val authorities = authorityDatabase.getKnownAuthorities() + val localRefs = hashMapOf() + authorities.forEach { + localRefs[it.hash.toKey()] = + it.version.coerceAtMost( + authorityDatabase.getMissingVersion(it.hash) ?: Long.MAX_VALUE + ) + } + return localRefs + } + + fun getRevocations(publicKeyHash: ByteArray, fromVersion: Long = 0): List { + val versions = authorityDatabase.getVersionsSince(publicKeyHash, fromVersion) + return authorityDatabase.getRevocations(publicKeyHash, versions) + } + + fun getAllRevocations(): List { + return authorityDatabase.getAllRevocations() + } + + fun loadDefaultAuthorities() { + TODO("Preinstalled Authorities yet to be designed.") + } + + fun getAuthorities(): List { + return this.authorityDatabase.getKnownAuthorities() + } + + fun getTrustedAuthorities(): List { + return this.trustedAuthorities.values.toList() + } + + fun addTrustedAuthority(publicKey: PublicKey) { + val hash = publicKey.keyToHash() + if (!this.containsAuthority(hash)) { + val localAuthority = authorityDatabase.getAuthorityByHash(hash) + if (localAuthority == null) { + authorityDatabase.insertTrustedAuthority(publicKey) + synchronized(lock) { + this.trustedAuthorities[hash.toKey()] = Authority(publicKey, hash) + } + } else { + authorityDatabase.recognizeAuthority(publicKey.keyToHash()) + synchronized(lock) { + this.trustedAuthorities[hash.toKey()] = localAuthority + } + } + } + } + + fun deleteTrustedAuthority(hash: ByteArray) { + if (this.containsAuthority(hash)) { + this.trustedAuthorities.remove(hash.toKey()) + this.authorityDatabase.disregardAuthority(hash) + } + } + + fun getTrustedAuthority(hash: ByteArray): Authority? { + return this.trustedAuthorities[hash.toKey()] + } + + fun getAuthority(hash: ByteArray): Authority? { + return this.trustedAuthorities[hash.toKey()] ?: authorityDatabase.getAuthorityByHash( + hash + ) + } + + fun deleteTrustedAuthority(publicKey: PublicKey) { + return this.deleteTrustedAuthority(publicKey.keyToHash()) + } + + fun containsAuthority(hash: ByteArray): Boolean { + return this.trustedAuthorities.containsKey(hash.toKey()) + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/RequestCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/RequestCache.kt similarity index 59% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/RequestCache.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/RequestCache.kt index ce503a4b..e37f9c99 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/RequestCache.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/RequestCache.kt @@ -1,5 +1,8 @@ -package nl.tudelft.ipv8.attestation.wallet +package nl.tudelft.ipv8.attestation.common +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch import mu.KotlinLogging import nl.tudelft.ipv8.attestation.wallet.caches.NumberCache import java.math.BigInteger @@ -10,17 +13,21 @@ class RequestCache { private val lock = Object() private val identifiers = hashMapOf() + val size: Int + get() = this.identifiers.size + fun add(cache: NumberCache): NumberCache? { synchronized(lock) { val identifier = this.createIdentifier(cache.prefix, cache.number) return if (identifiers.containsKey(identifier)) { - this.logger.error("Attempted to add cache with duplicate identifier $identifier.") + this.logger.warn("Attempted to add cache with duplicate identifier $identifier.") null } else { this.logger.debug("Add cache $cache") this.identifiers[identifier] = cache + GlobalScope.launch { cache.start(calleeCallback = { pop(cache.prefix, cache.number) }) } cache } } @@ -34,18 +41,33 @@ class RequestCache { return this.has(identifierPair.first, identifierPair.second) } + fun pop(identifierPair: Pair): NumberCache? { + return this.pop(identifierPair.first, identifierPair.second) + } + fun pop(prefix: String, number: BigInteger): NumberCache? { val identifier = this.createIdentifier(prefix, number) - return this.identifiers.remove(identifier) + val cache = this.identifiers.remove(identifier) + cache?.stop() + return cache } fun get(prefix: String, number: BigInteger): NumberCache? { return this.identifiers.get(this.createIdentifier(prefix, number)) } - private fun createIdentifier(prefix: String, number: BigInteger): String { - return "$prefix:$number" + fun get(identifierPair: Pair): NumberCache? { + return this.get(identifierPair.first, identifierPair.second) } + fun clear() { + synchronized(lock) { + this.identifiers.values.forEach { it.stop() } + this.identifiers.clear() + } + } + private fun createIdentifier(prefix: String, number: BigInteger): String { + return "$prefix:$number" + } } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/schema/SchemaManager.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/SchemaManager.kt similarity index 57% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/schema/SchemaManager.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/SchemaManager.kt index 51de6cc3..57a899e0 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/schema/SchemaManager.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/SchemaManager.kt @@ -1,13 +1,26 @@ -package nl.tudelft.ipv8.attestation.schema - -import nl.tudelft.ipv8.attestation.IdentityAlgorithm -import nl.tudelft.ipv8.attestation.WalletAttestation -import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehExactAlgorithm +package nl.tudelft.ipv8.attestation.common + +import nl.tudelft.ipv8.attestation.common.consts.AlgorithmNames.BONEH_EXACT +import nl.tudelft.ipv8.attestation.common.consts.AlgorithmNames.IRMA_EXACT +import nl.tudelft.ipv8.attestation.common.consts.AlgorithmNames.PENG_BAO_RANGE +import nl.tudelft.ipv8.attestation.common.consts.SchemaConstants.ID_METADATA +import nl.tudelft.ipv8.attestation.common.consts.SchemaConstants.ID_METADATA_BIG +import nl.tudelft.ipv8.attestation.common.consts.SchemaConstants.ID_METADATA_HUGE +import nl.tudelft.ipv8.attestation.common.consts.SchemaConstants.ID_METADATA_RANGE_18PLUS +import nl.tudelft.ipv8.attestation.common.consts.SchemaConstants.ID_METADATA_RANGE_UNDERAGE +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.ALGORITHM +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.HASH +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.KEY_SIZE +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.SHA256 +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.SHA256_4 +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.SHA512 +import nl.tudelft.ipv8.attestation.wallet.cryptography.IdentityAlgorithm +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehExact import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.attestations.BonehAttestation -import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.PengBaoAttestation -import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.Pengbaorange - +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.PengBaoRange +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.attestations.PengBaoAttestation class AlgorithmScheme( val schemaName: String, val algorithmName: String, @@ -17,28 +30,19 @@ class AlgorithmScheme( val max: Int? = null, ) -const val ID_METADATA = "id_metadata" -const val ID_METADATA_BIG = "id_metadata_big" -const val ID_METADATA_HUGE = "id_metadata_huge" -const val ID_METADATA_RANGE_18PLUS = "id_metadata_range_18plus" -const val ID_METADATA_RANGE_18PLUS_PUBLIC_VALUE = "18+" -const val ID_METADATA_RANGE_UNDERAGE = "id_metadata_range_underage" -const val ID_METADATA_RANGE_UNDERAGE_PUBLIC_VALUE = "underage" - - class SchemaManager { private val formats = HashMap>() fun deserialize(serialized: ByteArray, idFormat: String): WalletAttestation { return when (val algorithmName = getAlgorithmName(idFormat)) { - "bonehexact" -> { + BONEH_EXACT -> { BonehAttestation.deserialize(serialized, idFormat) } - "pengbaorange" -> { + PENG_BAO_RANGE -> { PengBaoAttestation.deserialize(serialized, idFormat) } - "irmaexact" -> { + IRMA_EXACT -> { TODO("Not yet implemented.") } else -> { @@ -53,13 +57,13 @@ class SchemaManager { idFormat: String, ): WalletAttestation { return when (val algorithmName = getAlgorithmName(idFormat)) { - "bonehexact" -> { + BONEH_EXACT -> { BonehAttestation.deserializePrivate(privateKey, serialized, idFormat) } - "pengbaorange" -> { + PENG_BAO_RANGE -> { PengBaoAttestation.deserializePrivate(privateKey, serialized, idFormat) } - "irmaexact" -> { + IRMA_EXACT -> { TODO("Not yet implemented.") } else -> { @@ -82,23 +86,24 @@ class SchemaManager { // TODO: Read in default schemas. fun registerDefaultSchemas() { val defaultSchemas = arrayListOf() - defaultSchemas.add(AlgorithmScheme(ID_METADATA, "bonehexact", 32, "sha256_4")) - defaultSchemas.add(AlgorithmScheme(ID_METADATA_BIG, "bonehexact", 64, "sha256")) - defaultSchemas.add(AlgorithmScheme(ID_METADATA_HUGE, "bonehexact", 96, "sha512")) - defaultSchemas.add(AlgorithmScheme(ID_METADATA_RANGE_18PLUS, "pengbaorange", 32, min = 18, max = 200)) - defaultSchemas.add(AlgorithmScheme(ID_METADATA_RANGE_UNDERAGE, "pengbaorange", 32, min = 0, max = 17)) + defaultSchemas.add(AlgorithmScheme(ID_METADATA, BONEH_EXACT, 32, SHA256_4)) + defaultSchemas.add(AlgorithmScheme(ID_METADATA_BIG, BONEH_EXACT, 64, SHA256)) + defaultSchemas.add(AlgorithmScheme(ID_METADATA_HUGE, BONEH_EXACT, 96, SHA512)) + defaultSchemas.add(AlgorithmScheme(ID_METADATA_RANGE_18PLUS, PENG_BAO_RANGE, 32, min = 18, max = 200)) + defaultSchemas.add(AlgorithmScheme(ID_METADATA_RANGE_UNDERAGE, PENG_BAO_RANGE, 32, min = 0, max = 17)) defaultSchemas.forEach { - val params = hashMapOf("key_size" to it.keySize) + val params = hashMapOf(KEY_SIZE to it.keySize) if (it.hashAlgorithm != null) { - params["hash"] = it.hashAlgorithm + params[HASH] = it.hashAlgorithm } if (it.min != null && it.max != null) { params["min"] = it.min params["max"] = it.max } - this.registerSchema(it.schemaName, + this.registerSchema( + it.schemaName, it.algorithmName, params ) @@ -107,14 +112,14 @@ class SchemaManager { fun getAlgorithmInstance(idFormat: String): IdentityAlgorithm { return when (val algorithmName = getAlgorithmName(idFormat)) { - "bonehexact" -> { - BonehExactAlgorithm(idFormat, this.formats) + BONEH_EXACT -> { + BonehExact(idFormat, this.formats) } - "pengbaorange" -> { - Pengbaorange(idFormat, this.formats) + PENG_BAO_RANGE -> { + PengBaoRange(idFormat, this.formats) } - "irmaexact" -> { - TODO("Not yet implemented.") + IRMA_EXACT -> { + TODO("IRMA is not implemented.") } else -> { throw RuntimeException("Attempted to load unknown proof algorithm: ${algorithmName}.") @@ -123,11 +128,10 @@ class SchemaManager { } fun getAlgorithmName(idFormat: String): String { - return this.formats[idFormat]?.get("algorithm").toString() + return this.formats[idFormat]?.get(ALGORITHM).toString() } fun getSchemaNames(): List { return this.formats.keys.toList() } - } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/consts/SchemaConstants.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/consts/SchemaConstants.kt new file mode 100644 index 00000000..b64dea04 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/common/consts/SchemaConstants.kt @@ -0,0 +1,17 @@ +package nl.tudelft.ipv8.attestation.common.consts + +object SchemaConstants { + const val ID_METADATA = "id_metadata" + const val ID_METADATA_BIG = "id_metadata_big" + const val ID_METADATA_HUGE = "id_metadata_huge" + const val ID_METADATA_RANGE_18PLUS = "id_metadata_range_18plus" + const val ID_METADATA_RANGE_18PLUS_PUBLIC_VALUE = "true" + const val ID_METADATA_RANGE_UNDERAGE = "id_metadata_range_underage" + const val ID_METADATA_RANGE_UNDERAGE_PUBLIC_VALUE = "true" +} + +object AlgorithmNames { + const val BONEH_EXACT = "bonehexact" + const val PENG_BAO_RANGE = "pengbaorange" + const val IRMA_EXACT = "irmaexact" +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/CommunicationChannel.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/CommunicationChannel.kt new file mode 100644 index 00000000..2829620b --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/CommunicationChannel.kt @@ -0,0 +1,510 @@ +package nl.tudelft.ipv8.attestation.communication + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import mu.KotlinLogging +import nl.tudelft.ipv8.Peer +import nl.tudelft.ipv8.attestation.identity.IdentityCommunity +import nl.tudelft.ipv8.attestation.identity.datastructures.Metadata +import nl.tudelft.ipv8.attestation.wallet.AttestationCommunity +import nl.tudelft.ipv8.attestation.communication.caches.DisclosureRequestCache +import nl.tudelft.ipv8.attestation.identity.datastructures.IdentityAttestation +import nl.tudelft.ipv8.attestation.identity.store.Credential +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation +import nl.tudelft.ipv8.attestation.revocation.RevocationCommunity +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.util.ByteArrayKey +import nl.tudelft.ipv8.util.SettableDeferred +import nl.tudelft.ipv8.util.asMap +import nl.tudelft.ipv8.util.defaultEncodingUtils +import nl.tudelft.ipv8.util.sha3_256 +import nl.tudelft.ipv8.util.stripSHA1Padding +import nl.tudelft.ipv8.util.toByteArray +import nl.tudelft.ipv8.util.toHex +import nl.tudelft.ipv8.util.toKey +import org.json.JSONObject +import java.util.UUID + +const val DEFAULT_TIME_OUT = 30_000L +private val logger = KotlinLogging.logger {} + +class CommunicationChannel( + val attestationOverlay: AttestationCommunity, + val identityOverlay: IdentityCommunity, + val revocationOverlay: RevocationCommunity +) { + val attestationRequests = + hashMapOf, String, String?>>() + val verifyRequests = hashMapOf>() + val verificationOutput = hashMapOf>>() + private val attestationMetadata = hashMapOf>() + + init { + this.attestationOverlay.setAttestationRequestCallback(this::onRequestAttestationAsync) + this.attestationOverlay.setAttestationRequestCompleteCallback(this::onAttestationComplete) + this.attestationOverlay.setVerifyRequestCallback(this::onVerifyRequestAsync) + this.identityOverlay.setAttestationPresentationCallback(this::onAttestationPresentationComplete) + } + + val publicKeyBin + get() = this.identityOverlay.myPeer.publicKey.keyToBin() + + val peers + get() = this.identityOverlay.getPeers() + + val myPeer + get() = this.identityOverlay.myPeer + + val schemas + get() = this.attestationOverlay.schemaManager.getSchemaNames() + + val authorityManager + get() = this.revocationOverlay.authorityManager + + private fun onRequestAttestationAsync( + peer: Peer, + attributeName: String, + metadataString: String, + proposedValue: String? + ): Deferred { + // Promise some ByteArray. + val deferred = SettableDeferred() + this.attestationRequests[AttributePointer(peer, attributeName)] = + Triple(deferred, metadataString, proposedValue) + @Suppress("UNCHECKED_CAST") + this.attestationMetadata[AttributePointer(peer, attributeName)] = + JSONObject(metadataString).asMap() as MutableMap + + return GlobalScope.async(start = CoroutineStart.LAZY) { deferred.await() } + } + + @Suppress("UNUSED_PARAMETER") + private fun onAttestationComplete( + forPeer: Peer, + attributeName: String, + attestation: WalletAttestation, + attributeHash: ByteArray, + idFormat: String, + fromPeer: Peer?, + value: ByteArray? + ) { + val metadata = this.attestationMetadata[AttributePointer(forPeer, attributeName)]!! + value?.let { metadata["value"] = defaultEncodingUtils.encodeBase64ToString(sha3_256(it)) } + + if (forPeer == myPeer) { + if (fromPeer == myPeer) { + @Suppress("UNCHECKED_CAST") + this.identityOverlay.selfAdvertise( + attributeHash, attributeName, idFormat, + metadata as HashMap? + ) + } else { + @Suppress("UNCHECKED_CAST") + this.identityOverlay.advertiseAttestation( + fromPeer!!, attributeHash, attributeName, idFormat, + metadata as HashMap? + ) + } + } else { + @Suppress("UNCHECKED_CAST") + this.identityOverlay.addKnownHash( + attributeHash, attributeName, value, forPeer.publicKey, + metadata as HashMap? + ) + } + } + + private fun onVerifyRequestAsync(peer: Peer, attributeHash: ByteArray): Deferred { + val metadata = + this.identityOverlay.getAttestationByHash(attributeHash) ?: return CompletableDeferred( + null + ) + val attributeName = JSONObject(String(metadata.serializedMetadata)).getString("name") + val deferred = SettableDeferred() + this.verifyRequests[AttributePointer(peer, attributeName)] = deferred + return GlobalScope.async(start = CoroutineStart.LAZY) { deferred.await() } + } + + private fun onVerificationResults(attributeHash: ByteArray, value: List) { + val references = this.verificationOutput[attributeHash.toKey()] + val out = arrayListOf>() + if (references != null) { + for (i in references.indices) { + out.add(Pair(references[i].first, value[i])) + } + this.verificationOutput[attributeHash.toKey()] = out + } else { + throw RuntimeException("Could not locate reference for ${attributeHash.toHex()} in verification output.") + } + } + + @Suppress("UNUSED_PARAMETER") + private fun onAttestationPresentationComplete( + peer: Peer, + attributeHash: ByteArray, + value: ByteArray, + metadata: Metadata, + attestations: List, + disclosureInformation: String, + ) { + logger.info("Received correct attestation presentation with hash ${attributeHash.toHex()}.") + val parsedMD = JSONObject(String(metadata.serializedMetadata)) + val idFormat = parsedMD.getString("schema") + this.verify(peer, stripSHA1Padding(attributeHash), listOf(value), idFormat) + } + + private fun dropIdentityTableData(): List { + val database = this.identityOverlay.identityManager.database + return database.dropIdentityTable(this.myPeer.publicKey) + } + + private fun dropAttestationTableData(attestationHashes: List) { + val database = this.attestationOverlay.database + return database.deleteAttestations(attestationHashes) + } + + fun deleteIdentity() { + val hashes = this.dropIdentityTableData().map { stripSHA1Padding(it) } + this.dropAttestationTableData(hashes) + this.attestationRequests.clear() + this.verifyRequests.clear() + } + + fun requestAttestation( + peer: Peer, + attributeName: String, + idFormat: String, + metadata: HashMap, + proposedValue: String? = null, + ) { + val key = this.attestationOverlay.getIdAlgorithm(idFormat).generateSecretKey() + this.attestationMetadata[AttributePointer(this.identityOverlay.myPeer, attributeName)] = + metadata + this.attestationOverlay.requestAttestation( + peer, + attributeName, + key, + metadata, + proposedValue + ) + } + + fun attest(peer: Peer, attributeName: String, value: ByteArray) { + val outstanding = this.attestationRequests.remove(AttributePointer(peer, attributeName))!! + GlobalScope.launch { outstanding.first.setResult(value) } + } + + fun dismissAttestationRequest(peer: Peer, attributeName: String) { + val outstanding = this.attestationRequests.remove(AttributePointer(peer, attributeName))!! + GlobalScope.launch { outstanding.first.setResult(null) } + } + + fun allowVerification(peer: Peer, attributeName: String) { + val outstanding = this.verifyRequests.remove(AttributePointer(peer, attributeName))!! + GlobalScope.launch { outstanding.setResult(true) } + } + + fun disallowVerification(peer: Peer, attributeName: String) { + val outstanding = this.verifyRequests.remove(AttributePointer(peer, attributeName))!! + GlobalScope.launch { outstanding.setResult(false) } + } + + fun generateAttestationPresentationRequest( + attributeName: String + ): String { + val id = UUID.randomUUID().toString() + this.identityOverlay.requestCache.add( + DisclosureRequestCache( + this.identityOverlay.requestCache, + id, + DisclosureRequest(attributeName), + ) + ) + return id + } + + fun presentAttestation( + peer: Peer, + requestId: String, + attributeHash: ByteArray, + attributeValue: ByteArray, + credential: Credential, + ) { + val presentationMetadata = JSONObject() + presentationMetadata.put("id", requestId) + presentationMetadata.put("attestationHash", attributeHash.toHex()) + presentationMetadata.put("value", attributeValue.toHex()) + + this.identityOverlay.presentAttestationAdvertisement( + peer, + credential, + presentationMetadata.toString(), + ) + } + + fun verify( + peer: Peer, + attestationHash: ByteArray, + referenceValues: List, + idFormat: String + ) { + this.verificationOutput[attestationHash.toKey()] = referenceValues.map { Pair(it, null) } + this.attestationOverlay.verifyAttestationValues( + peer.address, + attestationHash, + referenceValues, + this::onVerificationResults, + idFormat + ) + } + + fun revoke(signature: ByteArray) { + return revoke(listOf(signature)) + } + + fun revoke(signatures: List) { + this.revocationOverlay.revokeAttestations(signatures) + } + + fun verifyLocally( + attestationHash: ByteArray, + proposedAttestationValue: ByteArray, + metadata: Metadata, + subjectKey: PublicKey, + challengePair: Pair, + attestors: List> + ): Boolean { + if (System.currentTimeMillis() - challengePair.second > DEFAULT_TIME_OUT) { + logger.info("Not accepting ${attestationHash.toHex()}, challenge timed out!") + return false + } + + if (!subjectKey.verify(challengePair.first, challengePair.second.toByteArray())) { + logger.info("Not accepting ${attestationHash.toHex()}, challenge not valid!") + return false + } + + if (!subjectKey.verify(metadata.signature, metadata.getPlaintext())) { + logger.info("Not accepting ${attestationHash.toHex()}, metadata signature not valid!") + return false + } + + val parsedMD = JSONObject(String(metadata.serializedMetadata)) + val valueHash = parsedMD.getString("value") + val proposedHash = + defaultEncodingUtils.encodeBase64ToString(sha3_256(proposedAttestationValue)) + if (valueHash != proposedHash) { + logger.info("Not accepting ${attestationHash.toHex()}, value not valid!") + return false + } + + // Check if authority is recognized and the corresponding signature is correct. + if (!attestors.any { attestor -> + val authority = + this.authorityManager.getTrustedAuthority(attestor.first) + authority?.let { + it.publicKey?.verify(attestor.second, metadata.hash) + } == true + }) { + logger.info("Not accepting ${attestationHash.toHex()}, no recognized authority or valid signature found.") + return false + } + + // Check if any authority has not revoked a signature + if (!this.authorityManager.verify(metadata.hash)) { + logger.info("Not accepting ${attestationHash.toHex()}, signature was revoked.") + return false + } + + return true + } + + fun generateChallenge(): Pair { + val timestamp = System.currentTimeMillis() + return Pair(timestamp, (this.myPeer.key as PrivateKey).sign(timestamp.toByteArray())) + } + + // Hash, Metadata, Value + fun getAttributeByName(attributeName: String): Triple? { + val pseudonym = this.identityOverlay.identityManager.getPseudonym(myPeer.publicKey) + + for (credential in pseudonym.getCredentials()) { + val attestations = credential.attestations.toList() + val attestors = mutableListOf() + + for (attestation in attestations) { + attestors += this.identityOverlay.identityManager.database.getAuthority(attestation) + } + val attributeHash = + pseudonym.tree.elements[credential.metadata.tokenPointer.toKey()]!!.contentHash + val jsonMetadata = JSONObject(String(credential.metadata.serializedMetadata)) + val attributeValue = + this.attestationOverlay.database.getValueByHash( + stripSHA1Padding(attributeHash) + )!! + + if (jsonMetadata.getString("name").equals(attributeName, ignoreCase = true)) { + return Triple(attributeHash, credential, attributeValue) + } + } + return null + } + + fun getOfflineVerifiableAttributes(publicKey: PublicKey = this.myPeer.publicKey): List { + val out = mutableListOf() + val pseudonym = this.identityOverlay.identityManager.getPseudonym(publicKey) + + for (credential in pseudonym.getCredentials()) { + val attestations = credential.attestations.toList() + if (attestations.isEmpty()) { + continue + } + + val attestors = mutableListOf>() + + for (attestation in attestations) { + val attestor = defaultCryptoProvider.keyFromPublicBin( + this.identityOverlay.identityManager.database.getAuthority( + attestation + ) + ).keyToHash() + attestors += Pair(attestor, attestation.signature) + } + + val attributeHash = + pseudonym.tree.elements[credential.metadata.tokenPointer.toKey()]!!.contentHash + val jsonMetadata = JSONObject(String(credential.metadata.serializedMetadata)) + + val attributeName = jsonMetadata.getString("name") + val attributeValue = + this.attestationOverlay.database.getValueByHash( + stripSHA1Padding(attributeHash) + )!! + val idFormat = jsonMetadata.getString("schema") + val signDate = jsonMetadata.getDouble("date").toFloat() + out += AttestationPresentation( + publicKey, + attributeHash, + attributeName, + attributeValue, + idFormat, + signDate, + credential.metadata, + attestors + ) + } + return out.sortedBy { it.attributeName } + } + + fun getAttributesSignedBy(peer: Peer): List { + val db = this.identityOverlay.identityManager.database + val myKeyHash = myPeer.publicKey.keyToHash() + // Exclude own public key + val subjects = db.getKnownSubjects() - peer.publicKey + val out = mutableListOf() + for (subject in subjects) { + val mdList = db.getMetadataFor(subject) + for (metadata in mdList) { + val md = JSONObject(String(metadata.serializedMetadata)) + out.add( + SubjectAttestationPresentation( + subject, + metadata.hash, + metadata, + md.getString("name"), + md.getDouble("date").toFloat(), + !this.revocationOverlay.authorityManager.verify(metadata.hash, myKeyHash) + ) + ) + } + } + return out.sortedBy { -it.signDate } + } + + @Deprecated("This should not be used.") + fun getAttributes(peer: Peer): HashMap, List>> { + val pseudonym = this.identityOverlay.identityManager.getPseudonym(peer.publicKey) + val out: HashMap, List>> = + hashMapOf() + + for (credential in pseudonym.getCredentials()) { + val attestations = credential.attestations.toList() + val attestors = mutableListOf() + + for (attestation in attestations) { + attestors += this.identityOverlay.identityManager.database.getAuthority(attestation) + } + val attributeHash = + pseudonym.tree.elements[credential.metadata.tokenPointer.toKey()]!!.contentHash + val jsonMetadata = JSONObject(String(credential.metadata.serializedMetadata)) + out[attributeHash.toKey()] = + Triple( + jsonMetadata.getString("name"), + jsonMetadata.asMap() as HashMap, + attestors + ) + } + return out + } +} + +class SubjectAttestationPresentation( + val publicKey: PublicKey, + val metadataHash: ByteArray, + val metadata: Metadata, + val attributeName: String, + val signDate: Float, + val isRevoked: Boolean +) { + override fun equals(other: Any?): Boolean { + return other is SubjectAttestationPresentation && this.publicKey == other.publicKey && this.metadata == other.metadata && this.isRevoked == other.isRevoked + } + + override fun hashCode(): Int { + var result = publicKey.hashCode() + result = 31 * result + metadataHash.contentHashCode() + result = 31 * result + metadata.hashCode() + result = 31 * result + attributeName.hashCode() + result = 31 * result + signDate.hashCode() + return result + } +} + +class AttestationPresentation( + val publicKey: PublicKey, + val attributeHash: ByteArray, + val attributeName: String, + val attributeValue: ByteArray, + val idFormat: String, + val signDate: Float, + val metadata: Metadata, + val attestors: List> +) { + override fun equals(other: Any?): Boolean { + return other is AttestationPresentation && this.publicKey == other.publicKey && this.attributeHash.contentEquals( + other.attributeHash + ) && this.attestors.size == other.attestors.size + } + + override fun hashCode(): Int { + var result = publicKey.hashCode() + result = 31 * result + attributeHash.contentHashCode() + result = 31 * result + attributeName.hashCode() + result = 31 * result + attributeValue.contentHashCode() + result = 31 * result + idFormat.hashCode() + result = 31 * result + signDate.hashCode() + result = 31 * result + metadata.hashCode() + result = 31 * result + attestors.hashCode() + return result + } +} + +class DisclosureRequest( + val attributeName: String +) diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/CommunicationManager.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/CommunicationManager.kt new file mode 100644 index 00000000..d042d4c0 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/CommunicationManager.kt @@ -0,0 +1,205 @@ +package nl.tudelft.ipv8.attestation.communication + +import mu.KotlinLogging +import nl.tudelft.ipv8.IPv8 +import nl.tudelft.ipv8.Peer +import nl.tudelft.ipv8.attestation.common.AuthorityManager +import nl.tudelft.ipv8.attestation.identity.IdentityCommunity +import nl.tudelft.ipv8.attestation.identity.createCommunity +import nl.tudelft.ipv8.attestation.identity.datastructures.IdentityAttestation +import nl.tudelft.ipv8.attestation.identity.store.IdentityStore +import nl.tudelft.ipv8.attestation.identity.manager.IdentityManager +import nl.tudelft.ipv8.attestation.revocation.RevocationCommunity +import nl.tudelft.ipv8.attestation.wallet.AttestationCommunity +import nl.tudelft.ipv8.attestation.wallet.store.AttestationStore +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.util.* +import java.io.File +import java.io.FileNotFoundException +import java.nio.file.Files +import java.nio.file.Paths + +private val logger = KotlinLogging.logger {} + +class CommunicationManager( + private val iPv8Instance: IPv8, + private val attestationStore: AttestationStore, + private val identityStore: IdentityStore, + val authorityManager: AuthorityManager, + storePseudonym: ((String, PrivateKey) -> Unit)? = null, + loadPseudonym: ((String) -> PrivateKey?)? = null, +) { + private val channels = hashMapOf() + private val nameToChannel = hashMapOf() + private val loadedCommunity + get() = iPv8Instance.getOverlay() + var identityManager = loadedCommunity?.identityManager + + private val loadPseudonym = loadPseudonym ?: Companion::loadPseudonym + private val storePseudonym = storePseudonym ?: Companion::storePseudonym + + private lateinit var attestationCallback: (peer: Peer, attestation: IdentityAttestation) -> Unit + + fun setAttestationCallback(f: (peer: Peer, attestation: IdentityAttestation) -> Unit) { + this.attestationCallback = f + channels.values.forEach { it.identityOverlay.setAttestationCallback(f) } + } + + private fun lazyIdentityManager(): IdentityManager { + if (this.identityManager == null) { + this.identityManager = IdentityManager(identityStore) + } + return this.identityManager!! + } + + fun load( + name: String, + rendezvousToken: String? = null, + ): CommunicationChannel { + if (nameToChannel.containsKey(name)) { + return this.nameToChannel[name]!! + } + + // Load private key with function or via default method. + var privateKey = this.loadPseudonym(name) + if (privateKey == null) { + privateKey = defaultCryptoProvider.generateKey() + this.storePseudonym(name, privateKey) + } + val publicKeyBytes = privateKey.pub().keyToBin() + + if (!this.channels.containsKey(publicKeyBytes.toKey())) { + val decodedRendezvousToken: ByteArray? = + (rendezvousToken?.let { defaultEncodingUtils.decodeBase64FromString(it) }) + + val identityOverlay = createCommunity( + privateKey, + this.iPv8Instance, + this.lazyIdentityManager(), + identityStore, + decodedRendezvousToken + ) + if (this::attestationCallback.isInitialized) { + identityOverlay.setAttestationCallback(this.attestationCallback) + } + + val attestationOverlay = AttestationCommunity( + identityOverlay.myPeer, + identityOverlay.endpoint, + identityOverlay.network, + attestationStore + ) + attestationOverlay.load() + + val revocationOverlay = RevocationCommunity( + identityOverlay.myPeer, + identityOverlay.endpoint, + identityOverlay.network, + authorityManager, + identityOverlay::getPeers + ) + revocationOverlay.load() + + this.channels[publicKeyBytes.toKey()] = + CommunicationChannel(attestationOverlay, identityOverlay, revocationOverlay) + this.nameToChannel[name] = this.channels[publicKeyBytes.toKey()]!! + } + return this.nameToChannel[name]!! + } + + fun unload(name: String) { + if (this.nameToChannel.containsKey(name)) { + val communicationChannel = this.nameToChannel.remove(name)!! + this.channels.remove(communicationChannel.publicKeyBin.toKey()) + this.iPv8Instance.unloadSecondaryOverlayStrategy(communicationChannel.identityOverlay.serviceId) + communicationChannel.attestationOverlay.unload() + communicationChannel.revocationOverlay.unload() + // TODO: Endpoint close? + } + } + + fun listPseudonyms(): List { + return getPseudonyms() + } + + fun listLoadedPseudonyms(): List { + val pseudonyms = this.listPseudonyms() + return this.nameToChannel.keys.filter { pseudonyms.contains(it) } + } + + fun shutdown() { + for (name in this.nameToChannel.keys) { + this.unload(name) + } + } + + fun getAllPeers(): List { + val peers = mutableListOf() + this.channels.values.map { peers += it.peers } + return peers + } + + companion object { + private const val PSEUDONYM_PATH = "\\pseudonyms\\" + + // Note. This is not secure and should not be used in a production setting without encryption. + @Suppress("NewApi") + fun loadPseudonym(name: String): PrivateKey? { + return try { + val directoryPath = "${System.getProperty("user.dir")}\\$PSEUDONYM_PATH" + val directory = File(directoryPath) + if (!directory.exists()) { + directory.mkdirs() + } + val bytes = File("$directoryPath\\$name").readBytes() + if (bytes.isNotEmpty()) { + defaultCryptoProvider.keyFromPrivateBin(bytes) + } else { + null + } + } catch (e: FileNotFoundException) { + logger.error("Failed to locate pseudonym file $name") + null + } + } + + // Note. This is not secure and should not be used in a production setting without encryption. + @Suppress("NewApi") + fun storePseudonym(name: String, privateKey: PrivateKey) { + val directoryPath = "${System.getProperty("user.dir")}\\$PSEUDONYM_PATH" + val directory = File(directoryPath) + if (!directory.exists()) { + directory.mkdirs() + } + File("$directoryPath\\$name").writeBytes(privateKey.keyToBin()) + } + + @Suppress("NewApi", "UNCHECKED_CAST") + fun getPseudonyms(): List { + return Files.walk(Paths.get("${System.getProperty("user.dir")}\\$PSEUDONYM_PATH")) + .filter(Files::isRegularFile).map { it.fileName.toString() } + .toArray().toList() as List + } + } +} + +class AttributePointer(val peer: Peer, val attributeName: String) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AttributePointer + + if (peer != other.peer) return false + if (attributeName != other.attributeName) return false + + return true + } + + override fun hashCode(): Int { + var result = peer.hashCode() + result = 31 * result + attributeName.hashCode() + return result + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/caches/DisclosureRequestCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/caches/DisclosureRequestCache.kt new file mode 100644 index 00000000..fc23c26a --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/caches/DisclosureRequestCache.kt @@ -0,0 +1,29 @@ +package nl.tudelft.ipv8.attestation.communication.caches + +import nl.tudelft.ipv8.attestation.common.RequestCache +import nl.tudelft.ipv8.attestation.communication.DisclosureRequest +import nl.tudelft.ipv8.attestation.wallet.caches.DEFAULT_TIMEOUT +import nl.tudelft.ipv8.attestation.wallet.caches.HashCache +import nl.tudelft.ipv8.attestation.wallet.caches.NumberCache +import java.math.BigInteger + +const val DISCLOSURE_REQUEST_PREFIX = "disclosure-request" + +class DisclosureRequestCache( + requestCache: RequestCache, + id: String, + val disclosureRequest: DisclosureRequest, + timeout: Int = DEFAULT_TIMEOUT +) : NumberCache( + requestCache, DISCLOSURE_REQUEST_PREFIX, + idFromUUID(id).second, timeout +) { + + + + companion object { + fun idFromUUID(id: String): Pair { + return HashCache.idFromHash(DISCLOSURE_REQUEST_PREFIX, id.toByteArray()) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/caches/TokenRequestCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/caches/TokenRequestCache.kt new file mode 100644 index 00000000..93ad3325 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/communication/caches/TokenRequestCache.kt @@ -0,0 +1,19 @@ +package nl.tudelft.ipv8.attestation.communication.caches + +import nl.tudelft.ipv8.attestation.common.RequestCache +import nl.tudelft.ipv8.attestation.wallet.caches.HashCache +import nl.tudelft.ipv8.attestation.wallet.caches.NumberCache +import java.math.BigInteger + +const val TOKEN_REQUEST_CACHE = "token-request" + +// TODO: add second parameter for uniqueness. +class TokenRequestCache(cache: RequestCache, mid: String, val requestedAttributeName: String, val disclosureInformation: String) : + NumberCache(cache, TOKEN_REQUEST_CACHE, generateId(mid).second) { + + companion object { + fun generateId(mid: String): Pair { + return HashCache.idFromHash(TOKEN_REQUEST_CACHE, mid.toByteArray()) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/IdentityCommunity.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/IdentityCommunity.kt new file mode 100644 index 00000000..32039569 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/IdentityCommunity.kt @@ -0,0 +1,576 @@ +package nl.tudelft.ipv8.attestation.identity + +import mu.KotlinLogging +import nl.tudelft.ipv8.* +import nl.tudelft.ipv8.attestation.common.RequestCache +import nl.tudelft.ipv8.attestation.common.consts.SchemaConstants.ID_METADATA +import nl.tudelft.ipv8.attestation.communication.caches.DisclosureRequestCache +import nl.tudelft.ipv8.attestation.communication.caches.TokenRequestCache +import nl.tudelft.ipv8.attestation.identity.consts.Metadata.DATE +import nl.tudelft.ipv8.attestation.identity.consts.Metadata.ID +import nl.tudelft.ipv8.attestation.identity.consts.Metadata.NAME +import nl.tudelft.ipv8.attestation.identity.consts.Metadata.SCHEMA +import nl.tudelft.ipv8.attestation.identity.consts.Metadata.VALUE +import nl.tudelft.ipv8.attestation.identity.consts.PayloadIds.ATTEST_PAYLOAD +import nl.tudelft.ipv8.attestation.identity.consts.PayloadIds.DISCLOSURE_PAYLOAD +import nl.tudelft.ipv8.attestation.identity.consts.PayloadIds.MISSING_RESPONSE_PAYLOAD +import nl.tudelft.ipv8.attestation.identity.consts.PayloadIds.REQUEST_MISSING_PAYLOAD +import nl.tudelft.ipv8.attestation.identity.datastructures.IdentityAttestation +import nl.tudelft.ipv8.attestation.identity.datastructures.Metadata +import nl.tudelft.ipv8.attestation.identity.store.Credential +import nl.tudelft.ipv8.attestation.identity.store.IdentityStore +import nl.tudelft.ipv8.attestation.identity.manager.Disclosure +import nl.tudelft.ipv8.attestation.identity.manager.IdentityManager +import nl.tudelft.ipv8.attestation.identity.manager.PseudonymManager +import nl.tudelft.ipv8.attestation.identity.payloads.AttestPayload +import nl.tudelft.ipv8.attestation.identity.payloads.DisclosePayload +import nl.tudelft.ipv8.attestation.identity.payloads.MissingResponsePayload +import nl.tudelft.ipv8.attestation.identity.payloads.RequestMissingPayload +import nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree.Token +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.messaging.Packet +import nl.tudelft.ipv8.peerdiscovery.Network +import nl.tudelft.ipv8.peerdiscovery.strategy.RandomWalk +import nl.tudelft.ipv8.util.ByteArrayKey +import nl.tudelft.ipv8.util.asMap +import nl.tudelft.ipv8.util.hexToBytes +import nl.tudelft.ipv8.util.padSHA1Hash +import nl.tudelft.ipv8.util.toHex +import nl.tudelft.ipv8.util.toKey +import org.json.JSONObject +import kotlin.math.max + +const val SAFE_UDP_PACKET_LENGTH = 1296 +const val DEFAULT_TIME_OUT = 300 +const val TOKEN_SIZE = 64 + +val DEFAULT_METADATA = listOf(NAME, DATE, SCHEMA) + +class HashInformation( + val name: String, + val value: ByteArray?, + val time: Float, + val publicKey: PublicKey, + val metadata: HashMap?, +) + +private val logger = KotlinLogging.logger {} + +/** + * Community for signing metadata over attestations, allowing for chains of attestations. + */ +class IdentityCommunity( + override var myPeer: Peer, + identityManager: IdentityManager? = null, + database: IdentityStore, + override var serviceId: String = SERVICE_ID, + override var network: Network = Network(), +) : Community() { + + var identityManager: IdentityManager = identityManager ?: IdentityManager(database) + val requestCache = RequestCache() + + private lateinit var attestationPresentationCallback: (peer: Peer, attributeHash: ByteArray, value: ByteArray, metadata: Metadata, attestations: List, disclosureInformation: String) -> Unit + private lateinit var attestationCallback: (peer: Peer, attestation: IdentityAttestation) -> Unit + + private val knownAttestationHashes = hashMapOf() + private val pseudonymManager = this.identityManager.getPseudonym(this.myPeer.key) + + private var tokenChain: MutableList = mutableListOf() + private var metadataChain: MutableList = mutableListOf() + private var attestationChain: MutableList = mutableListOf() + private val permissions: HashMap = hashMapOf() + + init { + super.myPeer = myPeer + super.network = network + + for (token in this.pseudonymManager.tree.elements.values) { + val chain = this.pseudonymManager.tree.getRootPath(token).reversed() + if (chain.size > this.tokenChain.size) { + this.tokenChain = chain.toMutableList() + } + } + for (credential in this.pseudonymManager.getCredentials()) { + for (token in this.tokenChain) { + if (credential.metadata.tokenPointer.contentEquals(token.hash)) { + this.metadataChain.add(credential.metadata) + this.attestationChain.addAll(credential.attestations) + break + } + } + } + + messageHandlers[DISCLOSURE_PAYLOAD] = ::onDisclosureWrapper + messageHandlers[ATTEST_PAYLOAD] = ::onAttestWrapper + messageHandlers[REQUEST_MISSING_PAYLOAD] = ::onRequestMissingWrapper + messageHandlers[MISSING_RESPONSE_PAYLOAD] = ::onMissingResponseWrapper + } + + fun presentAttestationAdvertisement( + peer: Peer, + credential: Credential, + presentationMetadata: String, + ) { + this.permissions[peer] = this.tokenChain.size + val disclosure = this.pseudonymManager.discloseCredentials(listOf(credential), setOf()) + val (metadataObj, tokens, attestations, authorities) = this.fitDisclosure(disclosure) + val payload = + DisclosePayload(metadataObj, tokens, attestations, authorities, presentationMetadata) + this.endpoint.send(peer, serializePacket(DISCLOSURE_PAYLOAD, payload)) + } + + fun advertiseAttestation( + peer: Peer, + attributeHash: ByteArray, + name: String, + blockType: String = ID_METADATA, + metadata: HashMap?, + ) { + val credential = this.selfAdvertise(attributeHash, name, blockType, metadata) + this.permissions[peer] = this.tokenChain.size + val disclosure = this.pseudonymManager.discloseCredentials(listOf(credential), setOf()) + val (metadataObj, tokens, attestations, authorities) = this.fitDisclosure(disclosure) + val payload = DisclosePayload(metadataObj, tokens, attestations, authorities) + this.endpoint.send(peer, serializePacket(DISCLOSURE_PAYLOAD, payload)) + } + + fun selfAdvertise( + attributeHash: ByteArray, + name: String, + blockType: String, + metadata: HashMap?, + ): Credential { + val hash = if (attributeHash.size == 20) padSHA1Hash(attributeHash) else attributeHash + + val extendedMetadata = + hashMapOf( + NAME to name, + SCHEMA to blockType, + DATE to System.currentTimeMillis() / 1000F + ) + if (metadata != null) { + extendedMetadata.putAll(metadata) + } + + val credential = this.pseudonymManager.createCredential( + hash, + extendedMetadata, + if (this.metadataChain.isNotEmpty()) this.metadataChain.last() else null + ) + + this.attestationChain.addAll(credential.attestations) + this.metadataChain.add(credential.metadata) + this.tokenChain.add(pseudonymManager.tree.elements[credential.metadata.tokenPointer.toKey()]!!) + + return credential + } + + fun addKnownHash( + attributeHash: ByteArray, + name: String, + value: ByteArray?, + publicKey: PublicKey, + metadata: HashMap? = null, + ) { + val hash = if (attributeHash.size == 20) padSHA1Hash(attributeHash) else attributeHash + this.knownAttestationHashes[ByteArrayKey(hash)] = + HashInformation(name, value, System.currentTimeMillis() / 1000F, publicKey, metadata) + } + + fun getAttestationByHash(attributeHash: ByteArray): Metadata? { + val hash = if (attributeHash.size == 20) padSHA1Hash(attributeHash) else attributeHash + for (credential in this.pseudonymManager.getCredentials()) { + val token = + this.pseudonymManager.tree.elements[ByteArrayKey(credential.metadata.tokenPointer)] + if (token?.contentHash.contentEquals(hash)) { + return credential.metadata + } + } + return null + } + + private fun shouldSign( + pseudonym: PseudonymManager, + metadata: Metadata, + isVerification: Boolean = false + ): Boolean { + val transaction = JSONObject(String(metadata.serializedMetadata)) + val requestedKeys = transaction.keySet() + if (!pseudonym.tree.elements.containsKey(metadata.tokenPointer.toKey())) { + logger.debug("Not signing $metadata, unknown token!") + return false + } + val attributeHash = pseudonym.tree.elements[metadata.tokenPointer.toKey()]!!.contentHash + if (NAME !in requestedKeys || DATE !in requestedKeys || SCHEMA !in requestedKeys) { + logger.debug("Not signing $metadata, it doesn't include the required fields!") + return false + } + if (!this.knownAttestationHashes.containsKey(attributeHash.toKey())) { + logger.debug("Not signing $metadata, it doesn't point to known content!") + return false + } + if (!pseudonym.publicKey.keyToBin() + .contentEquals(this.knownAttestationHashes[attributeHash.toKey()]?.publicKey?.keyToBin()) + ) { + logger.debug("Not signing $metadata, attribute doesn't belong to key!") + return false + } + // Refuse to sign blocks older than 5 minutes + if (!isVerification && System.currentTimeMillis() / 1000F > this.knownAttestationHashes[attributeHash.toKey()]?.time?.plus( + (DEFAULT_TIME_OUT) + ) ?: 0F + ) { + logger.debug("Not signing $metadata, timed out!") + return false + } + if (transaction[NAME] != this.knownAttestationHashes[attributeHash.toKey()]?.name) { + logger.debug("Not signing $metadata, name does not match!") + return false + } + if (this.knownAttestationHashes[attributeHash.toKey()]!!.metadata != null + && transaction.asMap().filterKeys { it !in DEFAULT_METADATA } + != this.knownAttestationHashes[attributeHash.toKey()]!!.metadata!! + ) { + logger.debug("Not signing $metadata, metadata does not match!") + return false + } + if (!isVerification) { + for (attestation in pseudonym.database.getAttestationsOver(metadata)) { + if (this.myPeer.publicKey.keyToBin() + .contentEquals(pseudonym.database.getAuthority(attestation)) + ) { + logger.debug("Not signing $metadata, already attested!") + return false + } + } + } + return true + } + + private fun fitDisclosure(disclosure: Disclosure): Disclosure { + val tokenSize = 64 + this.myPeer.publicKey.getSignatureLength() + val (metadata, tokens, attestations, authorities) = disclosure + var tokensOut = tokens + val metaLength = metadata.size + attestations.size + authorities.size + if (metaLength + tokens.size > SAFE_UDP_PACKET_LENGTH) { + var packetSpace = SAFE_UDP_PACKET_LENGTH - metaLength + if (packetSpace < 0) { + logger.warn("Attempting to disclose with packet of length $metaLength, hoping for the best!") + } + packetSpace = max(0, packetSpace) + val trimLength = packetSpace / tokenSize + tokensOut = tokens.copyOfRange(0, (trimLength * tokenSize)) + } + return Disclosure(metadata, tokensOut, attestations, authorities) + } + + private fun receivedDisclosureForAttest(peer: Peer, disclosure: Disclosure) { + val (correct, pseudonym) = this.identityManager.substantiate( + peer.publicKey, + disclosure.metadata, + disclosure.tokens, + disclosure.attestations, + disclosure.authorities + ) + val requiredAttributes = + this.knownAttestationHashes.filter { it.value.publicKey == peer.publicKey }.keys.toTypedArray() + val knownAttributes: List = + pseudonym.tree.elements.values.map { ByteArrayKey(it.contentHash) } + + if (correct && requiredAttributes.any { knownAttributes.contains(it) }) { + for (credential in pseudonym.getCredentials()) { + if (shouldSign(pseudonym, credential.metadata)) { + logger.info("Attesting to ${credential.metadata}.") + val myPrivateKey = this.myPeer.key as PrivateKey + val attestation = pseudonym.createAttestation( + credential.metadata, + myPrivateKey + ) + pseudonym.addAttestation(this.myPeer.publicKey, attestation) + val payload = AttestPayload(attestation.getPlaintextSigned()) + this.endpoint.send(peer, serializePacket(ATTEST_PAYLOAD, payload)) + } + } + } + + for (attributeHash in requiredAttributes) { + if (!knownAttributes.contains(attributeHash)) { + logger.info("Missing information for attestation ${attributeHash.bytes.toHex()}, requesting more.") + val payload = RequestMissingPayload(pseudonym.tree.elements.size) + this.endpoint.send(peer, serializePacket(REQUEST_MISSING_PAYLOAD, payload)) + } + } + } + + // TODO: remove parameters from disclosureInformation. + private fun receivedDisclosureForPresentation( + peer: Peer, + disclosure: Disclosure, + attributeName: String, + disclosureInformation: String + ) { + val (correct, pseudonym) = this.identityManager.substantiate( + peer.publicKey, + disclosure.metadata, + disclosure.tokens, + disclosure.attestations, + disclosure.authorities + ) + + val disclosureJSON = JSONObject(disclosureInformation) + val requiredAttributes = + listOf(disclosureJSON.getString("attestationHash").hexToBytes().toKey()) + val knownAttributes: List = + pseudonym.tree.elements.values.map { ByteArrayKey(it.contentHash) } + + if (correct && requiredAttributes.any { knownAttributes.contains(it) }) { + for (credential in pseudonym.getCredentials()) { + val value = disclosureJSON.getString(VALUE).hexToBytes() + @Suppress("UNCHECKED_CAST") + this.addKnownHash( + requiredAttributes[0].bytes, + attributeName, + value, + peer.publicKey, + JSONObject(String(credential.metadata.serializedMetadata)).toMap() as HashMap + ) + if (shouldSign(pseudonym, credential.metadata)) { + val presentedAttributeName = + JSONObject(String(credential.metadata.serializedMetadata)).getString(NAME) + if (presentedAttributeName != attributeName) { + logger.warn("Client sent wrong attestation. Requested: $attributeName, received: $presentedAttributeName") + return + } + logger.info("Received valid attestation presentation ${String(credential.metadata.serializedMetadata)}") + + this.attestationPresentationCallback( + peer, requiredAttributes[0].bytes, value, credential.metadata, + credential.attestations.toList(), + disclosureInformation + ) + } + } + } + + for (attributeHash in requiredAttributes) { + if (!knownAttributes.contains(attributeHash)) { + logger.info("Missing information for attestation ${attributeHash.bytes.toHex()}, requesting more.") + // TODO: add second parameter for uniqueness. + requestCache.add( + TokenRequestCache( + requestCache, + peer.mid, + attributeName, + disclosureInformation + ) + ) + val payload = RequestMissingPayload(pseudonym.tree.elements.size) + this.endpoint.send(peer, serializePacket(REQUEST_MISSING_PAYLOAD, payload)) + } + } + } + + private fun onDisclosure(peer: Peer, payload: DisclosePayload) { + val isAttestationRequest = + this.knownAttestationHashes.values.any { it.publicKey == peer.publicKey } + val disclosureMD = JSONObject(payload.advertisementInformation ?: "{}") + val id = disclosureMD.optString(ID) + val idPair = DisclosureRequestCache.idFromUUID(id) + val isAttestationPresentation = this.requestCache.has(idPair) + + when { + // Presentation takes precedence, as id should not be set otherwise. + isAttestationPresentation -> { + val cache = this.requestCache.pop(idPair)!! as DisclosureRequestCache + this.receivedDisclosureForPresentation( + peer, + Disclosure( + payload.metadata, + payload.tokens, + payload.attestations, + payload.authorities + ), + cache.disclosureRequest.attributeName, + payload.advertisementInformation!! + ) + } + isAttestationRequest -> { + this.receivedDisclosureForAttest( + peer, + Disclosure( + payload.metadata, + payload.tokens, + payload.attestations, + payload.authorities + ) + ) + } + else -> { + logger.warn("Received unsolicited disclosure from ${peer.mid}, dropping.") + } + } + } + + private fun onAttest(peer: Peer, payload: AttestPayload) { + val attestation = IdentityAttestation.deserialize(payload.attestation, peer.publicKey) + if (this.pseudonymManager.addAttestation(peer.publicKey, attestation)) { + logger.info("Received attestation from ${peer.mid}.") + if (this::attestationCallback.isInitialized) { + this.attestationCallback(peer, attestation) + } + } else { + logger.warn("Received invalid attestation from ${peer.mid}.") + } + } + + private fun onRequestMissing(peer: Peer, payload: RequestMissingPayload) { + logger.info("Received missing request from ${peer.mid} for ${payload.known} tokens.") + var out = byteArrayOf() + val permitted = this.tokenChain.subList(0, this.permissions[peer] ?: 0) + + for ((index, token) in permitted.withIndex()) { + if (index >= payload.known) { + val serialized = token.getPlaintextSigned() + if (out.size + serialized.size > SAFE_UDP_PACKET_LENGTH) { + break + } + out += serialized + } + } + val responsePayload = MissingResponsePayload(out) + this.endpoint.send(peer, serializePacket(MISSING_RESPONSE_PAYLOAD, responsePayload)) + } + + private fun onMissingResponse(peer: Peer, payload: MissingResponsePayload) { + val solicitedAttestationRequest = + this.knownAttestationHashes.values.any { it.publicKey == peer.publicKey } + val idPair = TokenRequestCache.generateId(peer.mid) + val solicitedAttestationPresentation = this.requestCache.has(idPair) + + when { + solicitedAttestationPresentation -> { + val cache = (this.requestCache.pop(idPair)!! as TokenRequestCache) + this.receivedDisclosureForPresentation( + peer, + Disclosure(byteArrayOf(), payload.tokens, byteArrayOf(), byteArrayOf()), + cache.requestedAttributeName, + cache.disclosureInformation + ) + } + solicitedAttestationRequest -> { + this.receivedDisclosureForAttest( + peer, + Disclosure(byteArrayOf(), payload.tokens, byteArrayOf(), byteArrayOf()) + ) + } + + else -> { + logger.warn("Received unsolicited disclosure from $peer, dropping.") + } + } + } + + fun setAttestationPresentationCallback(f: (peer: Peer, attributeHash: ByteArray, value: ByteArray, metadata: Metadata, attestations: List, disclosureInformation: String) -> Unit) { + this.attestationPresentationCallback = f + } + + fun setAttestationCallback(f: (peer: Peer, attestation: IdentityAttestation) -> Unit) { + this.attestationCallback = f + } + + private fun onDisclosureWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(DisclosePayload.Deserializer) + logger.info(" Disclose payload from ${peer.mid}.") + this.onDisclosure(peer, payload) + } + + private fun onAttestWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(AttestPayload.Deserializer) + logger.info("Received Attest payload from ${peer.mid}.") + this.onAttest(peer, payload) + } + + private fun onRequestMissingWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(RequestMissingPayload.Deserializer) + logger.info("Received Request Missing payload from ${peer.mid}.") + this.onRequestMissing(peer, payload) + } + + private fun onMissingResponseWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(MissingResponsePayload.Deserializer) + logger.info("Received Missing Response payload from ${peer.mid}.") + this.onMissingResponse(peer, payload) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IdentityCommunity + + if (serviceId != other.serviceId) return false + + return true + } + + override fun hashCode(): Int { + return serviceId.hashCode() + } + + companion object { + const val SERVICE_ID = "d5889074c1e4c50423cdb6e9307ee0ca5695ead7" + } + + class Factory( + private val myPeer: Peer, + private val identityManager: IdentityManager, + private val database: IdentityStore, + private val rendezvousId: String? = null, + private val network: Network? = null + ) : Overlay.Factory(IdentityCommunity::class.java) { + override fun create(): IdentityCommunity { + return if (rendezvousId != null) { + if (network != null) { + IdentityCommunity(myPeer, identityManager, database, rendezvousId, network) + } else { + IdentityCommunity(myPeer, identityManager, database, rendezvousId) + } + } else { + if (network != null) { + IdentityCommunity(myPeer, identityManager, database, network = network) + } else { + IdentityCommunity(myPeer, identityManager, database) + } + } + } + } +} + +fun createCommunity( + privateKey: PrivateKey, + ipv8: IPv8, + identityManager: IdentityManager, + database: IdentityStore, + rendezvousToken: ByteArray?, +): IdentityCommunity { + val myPeer = Peer(privateKey) + val network = Network() + var rendezvousId: String? = null + if (rendezvousToken != null) { + rendezvousId = + IdentityCommunity.SERVICE_ID.mapIndexed { i, c -> if (i < rendezvousToken.size) (c.toInt() xor rendezvousToken[i].toInt()).toChar() else c } + .joinToString("") + } + + val randomWalk = RandomWalk.Factory(timeout = 3.0, peers = 20) + val config = SecondaryOverlayConfiguration( + IdentityCommunity.Factory(myPeer, identityManager, database, rendezvousId, network), + listOf(randomWalk), + myPeer = myPeer, + network = network + ) + + return ipv8.addSecondaryOverlayStrategy(config) as IdentityCommunity +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/consts/MetadataConsts.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/consts/MetadataConsts.kt new file mode 100644 index 00000000..c35d26f1 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/consts/MetadataConsts.kt @@ -0,0 +1,9 @@ +package nl.tudelft.ipv8.attestation.identity.consts + +object Metadata { + const val NAME = "name" + const val SCHEMA = "schema" + const val DATE = "date" + const val VALUE = "value" + const val ID = "id" +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/consts/PayloadConsts.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/consts/PayloadConsts.kt new file mode 100644 index 00000000..27dee285 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/consts/PayloadConsts.kt @@ -0,0 +1,8 @@ +package nl.tudelft.ipv8.attestation.identity.consts + +object PayloadIds { + const val DISCLOSURE_PAYLOAD = 1 + const val ATTEST_PAYLOAD = 2 + const val REQUEST_MISSING_PAYLOAD = 3 + const val MISSING_RESPONSE_PAYLOAD = 4 +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/IdentityAttestation.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/IdentityAttestation.kt similarity index 83% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/IdentityAttestation.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/IdentityAttestation.kt index f24f0419..c29e93cc 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/IdentityAttestation.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/IdentityAttestation.kt @@ -1,6 +1,5 @@ -package nl.tudelft.ipv8.attestation.identity +package nl.tudelft.ipv8.attestation.identity.datastructures -import nl.tudelft.ipv8.attestation.SignedObject import nl.tudelft.ipv8.keyvault.PrivateKey import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.ipv8.util.toHex @@ -10,6 +9,9 @@ class IdentityAttestation( privateKey: PrivateKey? = null, signature: ByteArray? = null, ) : SignedObject(privateKey, signature) { + init { + super.init() + } override fun getPlaintext(): ByteArray { return this.metadataPointer @@ -23,7 +25,6 @@ class IdentityAttestation( return "Attestation${metadataPointer.toHex()})" } - override fun deserialize(data: ByteArray, publicKey: PublicKey, offset: Int): IdentityAttestation { return Companion.deserialize(data, publicKey, offset) } @@ -31,18 +32,18 @@ class IdentityAttestation( companion object { fun deserialize(data: ByteArray, publicKey: PublicKey, offset: Int = 0): IdentityAttestation { val signLength = publicKey.getSignatureLength() - return IdentityAttestation(data.copyOfRange(offset, offset + 32), - signature = data.copyOfRange(offset + 32, offset + 32 + signLength)) + return IdentityAttestation( + data.copyOfRange(offset, offset + 32), + signature = data.copyOfRange(offset + 32, offset + 32 + signLength) + ) } fun create(metadata: Metadata, privateKey: PrivateKey): IdentityAttestation { return IdentityAttestation(metadata.hash, privateKey = privateKey) } - fun fromDatabaseTuple(metadataPointer: ByteArray, signature: ByteArray): IdentityAttestation { + fun fromDatabaseTuple(metadataPointer: ByteArray, signature: ByteArray?): IdentityAttestation { return IdentityAttestation(metadataPointer, signature = signature) } } - - } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/Metadata.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/Metadata.kt similarity index 53% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/Metadata.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/Metadata.kt index b3d1b2fe..b9e0c2bd 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/Metadata.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/Metadata.kt @@ -1,30 +1,32 @@ -package nl.tudelft.ipv8.attestation.identity +package nl.tudelft.ipv8.attestation.identity.datastructures -import nl.tudelft.ipv8.attestation.SignedObject +import nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree.Token import nl.tudelft.ipv8.keyvault.PrivateKey import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.ipv8.util.toHex import org.json.JSONObject class Metadata( - private val tokenPointer: ByteArray, - private val serializedJSONObject: ByteArray, + val tokenPointer: ByteArray, + val serializedMetadata: ByteArray, privateKey: PrivateKey? = null, - signature: ByteArray? = null + signature: ByteArray? = null, ) : SignedObject(privateKey, signature) { + init { + super.init() + } override fun getPlaintext(): ByteArray { - return this.tokenPointer + this.serializedJSONObject + return this.tokenPointer + this.serializedMetadata } - fun toDatabaseTuple(): Array { - return arrayOf(this.tokenPointer, this.signature, this.serializedJSONObject) + fun toDatabaseTuple(): Triple { + return Triple(this.tokenPointer, this.signature, this.serializedMetadata) } - @ExperimentalStdlibApi override fun toString(): String { - return "Metadata(${this.tokenPointer.toHex()},\n${this.serializedJSONObject.toString(Charsets.UTF_8)}" + return "Metadata(${this.tokenPointer.toHex()},\n${String(this.serializedMetadata)}" } override fun deserialize(data: ByteArray, publicKey: PublicKey, offset: Int): Metadata { @@ -38,26 +40,28 @@ class Metadata( } // Index on which signature start. - val signIndex = data.lastIndex - publicKey.getSignatureLength() - return Metadata(data.copyOfRange(0, 32), + val signIndex = data.size - publicKey.getSignatureLength() + return Metadata( + data.copyOfRange(0, 32), data.copyOfRange(32, signIndex), - signature = data.copyOfRange(signIndex, data.size)) + signature = data.copyOfRange(signIndex, data.size) + ) } fun create(token: Token, jsonObject: JSONObject, privateKey: PrivateKey): Metadata { - return Metadata(token.hash, + return Metadata( + token.hash, jsonObject.toString().toByteArray(), - privateKey = privateKey) + privateKey = privateKey + ) } fun fromDatabaseTuple( tokenPointer: ByteArray, - signature: ByteArray, - serializedJSONObject: ByteArray + signature: ByteArray?, + serializedMetadata: ByteArray, ): Metadata { - return Metadata(tokenPointer, serializedJSONObject, signature = signature) + return Metadata(tokenPointer, serializedMetadata, signature = signature) } } - - } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/SignedObject.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/SignedObject.kt similarity index 80% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/SignedObject.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/SignedObject.kt index a8931ac3..5c0fe98d 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/SignedObject.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/SignedObject.kt @@ -1,4 +1,4 @@ -package nl.tudelft.ipv8.attestation +package nl.tudelft.ipv8.attestation.identity.datastructures import nl.tudelft.ipv8.keyvault.PrivateKey import nl.tudelft.ipv8.keyvault.PublicKey @@ -6,15 +6,13 @@ import nl.tudelft.ipv8.keyvault.defaultCryptoProvider import nl.tudelft.ipv8.messaging.deserializeULong import nl.tudelft.ipv8.util.sha3_256 -abstract class SignedObject(val privateKey: PrivateKey? = null, signature: ByteArray? = null) { +abstract class SignedObject(val privateKey: PrivateKey? = null, private val knownSignature: ByteArray? = null) { var hash = byteArrayOf() open lateinit var signature: ByteArray - private val crypto = defaultCryptoProvider - - init { - this.sign(privateKey, signature) + fun init() { + this.sign(privateKey, knownSignature) } fun verify(publicKey: PublicKey): Boolean { @@ -23,15 +21,13 @@ abstract class SignedObject(val privateKey: PrivateKey? = null, signature: ByteA private fun sign(privateKey: PrivateKey? = null, signature: ByteArray? = null) { if (privateKey != null && signature == null) { - privateKey.sign(this.getPlaintext()) + this.signature = privateKey.sign(this.getPlaintext()) } else if (privateKey == null && signature != null) { this.signature = signature } else { throw RuntimeException("Specify either a private key or a signature.") } - this.hash = sha3_256(this.getPlaintextSigned()) - } abstract fun getPlaintext(): ByteArray @@ -43,7 +39,7 @@ abstract class SignedObject(val privateKey: PrivateKey? = null, signature: ByteA abstract fun deserialize(data: ByteArray, publicKey: PublicKey, offset: Int = 0): SignedObject override fun equals(other: Any?): Boolean { - if (other !is SignedObject){ + if (other !is SignedObject) { return false } return this.getPlaintextSigned().contentEquals(other.getPlaintextSigned()) @@ -51,6 +47,6 @@ abstract class SignedObject(val privateKey: PrivateKey? = null, signature: ByteA override fun hashCode(): Int { // TODO: verify that this is correct. - return deserializeULong(this.hash).toInt() + return deserializeULong(this.hash.copyOfRange(0, 8)).toInt() } } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/Token.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/Token.kt similarity index 66% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/Token.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/Token.kt index 86bad85f..d68d03c8 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/Token.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/Token.kt @@ -1,20 +1,20 @@ -package nl.tudelft.ipv8.attestation.identity +package nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree -import nl.tudelft.ipv8.attestation.SignedObject +import nl.tudelft.ipv8.attestation.identity.datastructures.SignedObject import nl.tudelft.ipv8.keyvault.PrivateKey import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.ipv8.util.sha3_256 import nl.tudelft.ipv8.util.toHex class Token( - private val previousTokenHash: ByteArray, + val previousTokenHash: ByteArray, content: ByteArray? = null, contentHash: ByteArray? = null, privateKey: PrivateKey? = null, - signature: ByteArray? = null + signature: ByteArray? = null, ) : SignedObject(privateKey, signature) { - private var content: ByteArray? = null + var content: ByteArray? = null var contentHash: ByteArray init { @@ -24,6 +24,7 @@ class Token( } else if (content == null && contentHash != null) { this.contentHash = contentHash } else throw RuntimeException("Specify either `content` or `content_hash`.") + super.init() } override fun getPlaintext(): ByteArray { @@ -35,8 +36,8 @@ class Token( } fun receiveContent(content: ByteArray): Boolean { - contentHash = sha3_256(content) - if (this.contentHash == contentHash) { + val contentHash = sha3_256(content) + if (contentHash.contentEquals(this.contentHash)) { this.content = content return true } @@ -51,12 +52,28 @@ class Token( return "Token[${this.hash.toHex()}](${this.previousTokenHash.toHex()}, ${contentHash.toHex()})" } + operator fun component1(): ByteArray { + return this.previousTokenHash + } + + operator fun component2(): ByteArray { + return this.signature + } + + operator fun component3(): ByteArray { + return this.contentHash + } + + operator fun component4(): ByteArray? { + return this.content + } + companion object { fun deserialize(data: ByteArray, publicKey: PublicKey, offset: Int = 0): Token { val sigLength = publicKey.getSignatureLength() return Token(data.copyOfRange(offset, offset + 32), - data.copyOfRange(offset + 32, offset + 64), - data.copyOfRange(offset + 64, offset + 64 + sigLength)) + contentHash = data.copyOfRange(offset + 32, offset + 64), + signature = data.copyOfRange(offset + 64, offset + 64 + sigLength)) } fun create(previousToken: Token, content: ByteArray, privateKey: PrivateKey): Token { @@ -65,11 +82,11 @@ class Token( fun fromDatabaseTuple( previousTokenHash: ByteArray, - signature: ByteArray, - contentHash: ByteArray, - content: ByteArray? + signature: ByteArray?, + contentHash: ByteArray?, + content: ByteArray?, ): Token { - val token = Token(previousTokenHash, signature, contentHash = contentHash) + val token = Token(previousTokenHash, contentHash = contentHash, signature = signature) if (content != null) { token.receiveContent(content) } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTree.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTree.kt new file mode 100644 index 00000000..15bb89a1 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTree.kt @@ -0,0 +1,178 @@ +package nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree + +import mu.KotlinLogging +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.util.ByteArrayKey +import nl.tudelft.ipv8.util.sha3_256 +import nl.tudelft.ipv8.util.toKey + +const val UNCHAINED_MAX_SIZE = 100 +const val DEFAULT_CHUNK_SIZE = 64 + +private val logger = KotlinLogging.logger {} + +class TokenTree(publicKey: PublicKey? = null, privateKey: PrivateKey? = null) { + + var unchainedLimit = UNCHAINED_MAX_SIZE + + val elements = hashMapOf() + val unchained = linkedMapOf() + + var publicKey: PublicKey + var privateKey: PrivateKey? + var genesisHash: ByteArray + + init { + if (publicKey != null && privateKey == null) { + this.publicKey = publicKey + this.privateKey = null + } else if (publicKey == null && privateKey != null) { + this.privateKey = privateKey + this.publicKey = privateKey.pub() + } else { + throw RuntimeException("Both public key and private key are null.") + } + this.genesisHash = sha3_256(this.publicKey.keyToBin()) + } + + fun add(content: ByteArray, after: Token? = null): Token { + if (privateKey == null) { + throw RuntimeException("Attempted to create a token without a private key.") + } + val previousHash = after?.hash ?: this.genesisHash + return this.append(Token(previousHash, content = content, privateKey = this.privateKey)) + } + + fun addByHash(contentHash: ByteArray, after: Token? = null): Token { + if (privateKey == null) { + throw RuntimeException("Attempted to create a token without a private key.") + } + val previousHash = after?.hash ?: this.genesisHash + return this.append(Token(previousHash, contentHash = contentHash, privateKey = this.privateKey)) + } + + fun gatherToken(token: Token): Token? { + if (token.verify(this.publicKey)) { + if (!token.previousTokenHash.contentEquals(this.genesisHash) && !this.elements.containsKey(ByteArrayKey( + token.previousTokenHash)) + ) { + this.unchained[token] = null + if (this.unchained.size > unchainedLimit) { + this.unchained.remove(this.unchained.keys.first()) + } + logger.info("Delaying unchained token $token!") + return null + } else if (this.elements.containsKey(token.hash.toKey())) { + val shadowToken = this.elements[token.hash.toKey()]!! + if (shadowToken.content == null && token.content != null) { + shadowToken.receiveContent(token.content!!) + } + return shadowToken + } else { + this.appendChainReactionToken(token) + } + return token + } + return null + } + + fun getMissing(): Set { + return this.unchained.keys.map { it.previousTokenHash }.toSet() + } + + fun verify(token: Token, maxDepth: Int = 1000): Boolean { + var current = token + var steps = 0 + while (maxDepth == -1 || maxDepth > steps) { + if (!current.verify(this.publicKey)) { + return false + } + if (current.previousTokenHash.contentEquals(this.genesisHash)) { + break + } + if (!this.elements.containsKey(ByteArrayKey(current.previousTokenHash))) { + return false + } + current = this.elements[ByteArrayKey(current.previousTokenHash)]!! + steps += 1 + } + return steps < maxDepth + } + + fun getRootPath(token: Token, maxDepth: Int = 1000): List { + var current = token + var steps = 0 + val path = mutableListOf(token) + while (maxDepth == -1 || maxDepth > steps) { + if (!current.verify(this.publicKey)) { + return arrayListOf() + } + if (current.previousTokenHash.contentEquals(this.genesisHash)) { + break + } + if (!this.elements.containsKey(ByteArrayKey(current.previousTokenHash))) { + return arrayListOf() + } + current = this.elements[ByteArrayKey(current.previousTokenHash)]!! + path.add(current) + steps += 1 + } + return if (steps < maxDepth) { + path + } else { + arrayListOf() + } + } + + fun serializePublic(upTo: Token? = null): ByteArray { + return if (upTo != null) { + var out = upTo.getPlaintextSigned() + var nextTokenHash = upTo.previousTokenHash + var token: Token + while (this.elements.contains(ByteArrayKey(nextTokenHash))) { + token = this.elements[ByteArrayKey(nextTokenHash)]!! + out += token.getPlaintextSigned() + nextTokenHash = token.previousTokenHash + } + out + } else { + var out = byteArrayOf() + this.elements.values.forEach { out += it.getPlaintextSigned() } + out + } + } + + fun deserializePublic(serialized: ByteArray): Boolean { + val signatureLength = this.publicKey.getSignatureLength() + val chunkSize = DEFAULT_CHUNK_SIZE + signatureLength + var isCorrect = true + for (i in serialized.indices step chunkSize) { + isCorrect = isCorrect and (this.gatherToken(Token.deserialize(serialized, this.publicKey, offset = i)) != null) + } + return isCorrect + } + + private fun append(token: Token): Token { + this.elements[ByteArrayKey(token.hash)] = token + return token + } + + private fun appendChainReactionToken(token: Token) { + this.append(token) + var retryToken: Token? = null + for (lostToken in this.unchained.keys) { + if (lostToken.previousTokenHash.contentEquals(token.hash)) { + retryToken = lostToken + break + } + } + if (retryToken != null) { + this.unchained.remove(retryToken) + if (this.gatherToken(retryToken) == null) { + logger.warn { "Dropped illegal token $retryToken!" } + } + } + } + +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/manager/IdentityManager.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/manager/IdentityManager.kt new file mode 100644 index 00000000..45ea1549 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/manager/IdentityManager.kt @@ -0,0 +1,79 @@ +package nl.tudelft.ipv8.attestation.identity.manager + +import nl.tudelft.ipv8.attestation.identity.datastructures.IdentityAttestation +import nl.tudelft.ipv8.attestation.identity.datastructures.Metadata +import nl.tudelft.ipv8.attestation.identity.store.IdentityStore +import nl.tudelft.ipv8.keyvault.Key +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.messaging.SERIALIZED_UINT_SIZE +import nl.tudelft.ipv8.messaging.SERIALIZED_USHORT_SIZE +import nl.tudelft.ipv8.messaging.deserializeUInt +import nl.tudelft.ipv8.messaging.deserializeUShort +import nl.tudelft.ipv8.util.ByteArrayKey +import nl.tudelft.ipv8.util.toKey + +class IdentityManager(internal var database: IdentityStore) { + + val pseudonyms = hashMapOf() + + fun getPseudonym(key: Key): PseudonymManager { + val publicKeyMaterial = key.pub().keyToBin() + if (!this.pseudonyms.containsKey(publicKeyMaterial.toKey())) { + if (key is PrivateKey) { + this.pseudonyms[publicKeyMaterial.toKey()] = PseudonymManager(this.database, privateKey = key) + } else { + this.pseudonyms[publicKeyMaterial.toKey()] = + PseudonymManager(this.database, publicKey = key as PublicKey) + } + } + return this.pseudonyms[publicKeyMaterial.toKey()]!! + } + + fun substantiate( + publicKey: PublicKey, + serializeMetadata: ByteArray, + serializedTokens: ByteArray, + serializedAttestations: ByteArray, + serializedAuthorities: ByteArray, + ): Pair { + val pseudonym = this.getPseudonym(publicKey) + var correct = pseudonym.tree.deserializePublic(serializedTokens) + + var metadataOffset = 0 + while (metadataOffset < serializeMetadata.size) { + val metadataSize = deserializeUInt(serializeMetadata, metadataOffset).toInt() + metadataOffset += SERIALIZED_UINT_SIZE + val metadata = + Metadata.deserialize( + serializeMetadata.copyOfRange(metadataOffset, metadataOffset + metadataSize), + publicKey + ) + pseudonym.addMetadata(metadata) + metadataOffset += metadataSize + } + + var attestationOffset = 0 + var authorityOffset = 0 + + while (authorityOffset < serializedAuthorities.size) { + val authoritySize = deserializeUShort(serializedAuthorities, authorityOffset) + authorityOffset += SERIALIZED_USHORT_SIZE + val authority = defaultCryptoProvider.keyFromPublicBin( + serializedAuthorities.copyOfRange( + authorityOffset, + authorityOffset + authoritySize + ) + ) + authorityOffset += authoritySize + correct = correct and pseudonym.addAttestation( + authority, + IdentityAttestation.deserialize(serializedAttestations, authority, attestationOffset) + ) + attestationOffset += 32 + authority.getSignatureLength() + } + + return Pair(correct, pseudonym) + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/manager/PseudonymManager.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/manager/PseudonymManager.kt new file mode 100644 index 00000000..c64af544 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/manager/PseudonymManager.kt @@ -0,0 +1,188 @@ +package nl.tudelft.ipv8.attestation.identity.manager + +import mu.KotlinLogging +import nl.tudelft.ipv8.attestation.identity.datastructures.IdentityAttestation +import nl.tudelft.ipv8.attestation.identity.datastructures.Metadata +import nl.tudelft.ipv8.attestation.identity.store.Credential +import nl.tudelft.ipv8.attestation.identity.store.IdentityStore +import nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree.Token +import nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree.TokenTree +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.messaging.serializeUInt +import nl.tudelft.ipv8.messaging.serializeUShort +import nl.tudelft.ipv8.util.ByteArrayKey +import nl.tudelft.ipv8.util.toHex +import nl.tudelft.ipv8.util.toKey +import org.json.JSONObject + +private val logger = KotlinLogging.logger {} + +class PseudonymManager( + internal val database: IdentityStore, + publicKey: PublicKey? = null, + privateKey: PrivateKey? = null, +) { + + internal val tree = TokenTree(publicKey, privateKey) + + val publicKey: PublicKey + get() = this.tree.publicKey + + private var credentials = this.database.getCredentialsFor(this.publicKey).toMutableList() + + init { + logger.info("Loading public key ${this.publicKey.keyToHash().toHex()} from database") + for (token in this.database.getTokensFor(this.publicKey)) { + this.tree.elements[token.hash.toKey()] = token + } + } + + fun addCredential( + token: Token, + metadata: Metadata, + attestations: Set> = setOf(), + ): Credential? { + if (this.tree.gatherToken(token) != null) { + this.database.insertToken(this.publicKey, token) + + if (metadata.verify(this.publicKey) && metadata.tokenPointer.contentEquals(token.hash)) { + this.database.insertMetadata(this.publicKey, metadata) + + val validAttestations = mutableSetOf() + + for ((authorityPublicKey, identityAttestation) in attestations) { + if (this.addAttestation(authorityPublicKey, identityAttestation)) { + validAttestations.add(identityAttestation) + } + } + + val out = Credential(metadata, validAttestations) + this.credentials.add(out) + return out + } + } + return null + } + + fun addAttestation(publicKey: PublicKey, attestation: IdentityAttestation): Boolean { + if (attestation.verify(publicKey)) { + this.database.insertAttestation(this.publicKey, publicKey, attestation) + return true + } + return false + } + + fun addMetadata(metadata: Metadata): Boolean { + if (metadata.verify(this.publicKey)) { + this.database.insertMetadata(this.publicKey, metadata) + return true + } + return false + } + + fun createAttestation(metadata: Metadata, privateKey: PrivateKey): IdentityAttestation { + return IdentityAttestation.create(metadata, privateKey) + } + + fun createCredential( + attestationHash: ByteArray, + metadata: HashMap, + after: Metadata? = null, + ): Credential { + val preceding = if (after == null) null else this.tree.elements[after.tokenPointer.toKey()] + val token = this.tree.addByHash(attestationHash, preceding) + val metadataObj = Metadata( + token.hash, + JSONObject(metadata).toString().toByteArray(), + this.tree.privateKey + ) + return this.addCredential(token, metadataObj, setOf())!! + } + + fun getCredential(metadata: Metadata): Credential { + return this.database.getCredentialOver(metadata) + } + + fun getCredentials(): List { + return this.database.getCredentialsFor(this.publicKey) + } + + fun discloseCredentials( + credentials: List, + attestationSelector: Set + ): Disclosure { + return createDisclosure(credentials.map { it.metadata }.toSet(), attestationSelector) + } + + private fun createDisclosure(metadata: Set, attestationSelector: Set): Disclosure { + val attSelector = attestationSelector.map { ByteArrayKey(it) } + var serializedMetadata = byteArrayOf() + for (md in metadata) { + val serialized = md.getPlaintextSigned() + serializedMetadata += serializeUInt(serialized.size.toUInt()) + serialized + } + + var attestations = byteArrayOf() + var authorities = byteArrayOf() + + for (md in metadata) { + val availableAttestations = this.database.getAttestationsOver(md) + for (attestation in availableAttestations) { + if (attSelector.contains(attestation.hash.toKey())) { + attestations += attestation.getPlaintextSigned() + val authority = this.database.getAuthority(attestation) + authorities += serializeUShort(authority.size) + authority + } + } + } + val requiredTokenHashes = metadata.map { it.tokenPointer } + val tokens = mutableListOf() + + for (requiredTokenHash in requiredTokenHashes) { + val rootToken = this.tree.elements[requiredTokenHash.toKey()]!! + + if (!this.tree.verify(rootToken)) { + throw RuntimeException("Attempted to create disclosure for undisclosable Token!") + } + + tokens.add(rootToken) + var currentToken = rootToken + + while (!currentToken.previousTokenHash.contentEquals(this.tree.genesisHash)) { + currentToken = this.tree.elements[currentToken.previousTokenHash.toKey()]!! + tokens.add(currentToken) + } + } + + // This is the order that they are present in the tree, as we stepped backwards. + tokens.reverse() + var serializedTokens = byteArrayOf() + tokens.forEach { serializedTokens += it.getPlaintextSigned() } + return Disclosure(serializedMetadata, serializedTokens, attestations, authorities) + } +} + +class Disclosure( + val metadata: ByteArray, + val tokens: ByteArray, + val attestations: ByteArray, + val authorities: ByteArray, +) { + operator fun component1(): ByteArray { + return metadata + } + + operator fun component2(): ByteArray { + return tokens + } + + operator fun component3(): ByteArray { + return attestations + } + + operator fun component4(): ByteArray { + return authorities + } +} + diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/AttestPayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/AttestPayload.kt new file mode 100644 index 00000000..ad3d2541 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/AttestPayload.kt @@ -0,0 +1,24 @@ + package nl.tudelft.ipv8.attestation.identity.payloads + +import nl.tudelft.ipv8.messaging.Deserializable +import nl.tudelft.ipv8.messaging.SERIALIZED_SHA3_256_SIZE +import nl.tudelft.ipv8.messaging.SIGNATURE_SIZE +import nl.tudelft.ipv8.messaging.Serializable +import nl.tudelft.ipv8.messaging.deserializeSHA3_256 + +class AttestPayload(val attestation: ByteArray) : Serializable { + override fun serialize(): ByteArray { + return attestation + } + + companion object Deserializer : Deserializable { + override fun deserialize(buffer: ByteArray, offset: Int): Pair { + var localOffset = offset + + val attestation = buffer.copyOfRange(localOffset, localOffset + SERIALIZED_SHA3_256_SIZE + SIGNATURE_SIZE) + localOffset += SERIALIZED_SHA3_256_SIZE + SIGNATURE_SIZE + + return Pair(AttestPayload(attestation), localOffset) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/DisclosePayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/DisclosePayload.kt new file mode 100644 index 00000000..fa171079 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/DisclosePayload.kt @@ -0,0 +1,46 @@ +package nl.tudelft.ipv8.attestation.identity.payloads + +import nl.tudelft.ipv8.messaging.* + +class DisclosePayload( + val metadata: ByteArray, + val tokens: ByteArray, + val attestations: ByteArray, + val authorities: ByteArray, + val advertisementInformation: String? = null, +) : Serializable { + override fun serialize(): ByteArray { + return serializeVarLen(metadata) + serializeVarLen(tokens) + serializeVarLen(attestations) + + serializeVarLen(authorities) + if (advertisementInformation != null) + serializeVarLen(advertisementInformation.toByteArray()) else byteArrayOf() + } + + companion object Deserializer : Deserializable { + override fun deserialize(buffer: ByteArray, offset: Int): Pair { + var localOffset = offset + val (metadata, offset1) = deserializeVarLen(buffer, localOffset) + localOffset += offset1 + + val (tokens, offset2) = deserializeVarLen(buffer, localOffset) + localOffset += offset2 + + val (attestations, offset3) = deserializeVarLen(buffer, localOffset) + localOffset += offset3 + + val (authorities, offset4) = deserializeVarLen(buffer, localOffset) + localOffset += offset4 + + var disclosureMetadata: String? = null + if (buffer.size > localOffset) { + val deserializedMetadataPair = deserializeVarLen(buffer, localOffset) + disclosureMetadata = String(deserializedMetadataPair.first) + localOffset += deserializedMetadataPair.second + } + + return Pair( + DisclosePayload(metadata, tokens, attestations, authorities, disclosureMetadata), + localOffset + ) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/MissingResponsePayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/MissingResponsePayload.kt new file mode 100644 index 00000000..b18d6df7 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/MissingResponsePayload.kt @@ -0,0 +1,21 @@ +package nl.tudelft.ipv8.attestation.identity.payloads + +import nl.tudelft.ipv8.messaging.* + +class MissingResponsePayload( + val tokens: ByteArray, +) : Serializable { + override fun serialize(): ByteArray { + return tokens + } + + companion object Deserializer : Deserializable { + override fun deserialize(buffer: ByteArray, offset: Int): Pair { + var localOffset = offset + val (tokens, localOffset1) = deserializeRaw(buffer, localOffset) + localOffset += localOffset1 + + return Pair(MissingResponsePayload(tokens), localOffset) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/RequestMissingPayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/RequestMissingPayload.kt new file mode 100644 index 00000000..6e14ae3c --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/payloads/RequestMissingPayload.kt @@ -0,0 +1,21 @@ +package nl.tudelft.ipv8.attestation.identity.payloads + +import nl.tudelft.ipv8.messaging.* + +class RequestMissingPayload( + val known: Int, +) : Serializable { + override fun serialize(): ByteArray { + return serializeUInt(known.toUInt()) + } + + companion object Deserializer : Deserializable { + override fun deserialize(buffer: ByteArray, offset: Int): Pair { + var localOffset = offset + val known = deserializeUInt(buffer, localOffset) + localOffset += SERIALIZED_UINT_SIZE + + return Pair(RequestMissingPayload(known.toInt()), localOffset) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/store/IdentitySQLiteStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/store/IdentitySQLiteStore.kt new file mode 100644 index 00000000..deaa5a49 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/store/IdentitySQLiteStore.kt @@ -0,0 +1,114 @@ +package nl.tudelft.ipv8.attestation.identity.store + +import nl.tudelft.ipv8.attestation.identity.datastructures.IdentityAttestation +import nl.tudelft.ipv8.attestation.identity.datastructures.Metadata +import nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree.Token +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.sqldelight.Database + +private val tokenMapper: ( + ByteArray, + ByteArray, + ByteArray, + ByteArray?, +) -> Token = { previousTokenHash, signature, contentHash, content -> + Token.fromDatabaseTuple(previousTokenHash, signature, contentHash, content) +} + +private val metadataMapper: ( + ByteArray, + ByteArray, + ByteArray, +) -> Metadata = { tokenPointer, signature, serializedMetadata -> + Metadata.fromDatabaseTuple(tokenPointer, signature, serializedMetadata) +} + +private val identityAttestationMapper: ( + ByteArray, + ByteArray?, +) -> IdentityAttestation = { metadataPointer, signature -> + IdentityAttestation.fromDatabaseTuple(metadataPointer, signature) +} + +class IdentitySQLiteStore(database: Database) : IdentityStore { + private val dao = database.dbIdentityQueries + + override fun insertToken(publicKey: PublicKey, token: Token) { + val (previousTokenHash, signature, contentHash, content) = token + dao.insertToken(publicKey.keyToBin(), previousTokenHash, signature, contentHash, content) + } + + override fun insertMetadata(publicKey: PublicKey, metadata: Metadata) { + val (tokenPointer, signature, serializedMetadata) = metadata.toDatabaseTuple() + dao.insertMetadata(publicKey.keyToBin(), tokenPointer, signature, serializedMetadata) + } + + override fun insertAttestation( + publicKey: PublicKey, + authorityKey: PublicKey, + attestation: IdentityAttestation + ) { + val (metadataPointer, signature) = attestation.toDatabaseTuple() + dao.insertIdentityAttestation( + publicKey.keyToBin(), + authorityKey.keyToBin(), + metadataPointer, + signature + ) + } + + override fun getTokensFor(publicKey: PublicKey): List { + return dao.getTokensFor(publicKey.keyToBin(), tokenMapper).executeAsList() + } + + override fun getMetadataFor(publicKey: PublicKey): List { + return dao.getMetadataFor(publicKey.keyToBin(), metadataMapper).executeAsList() + } + + override fun getAttestationsFor(publicKey: PublicKey): Set { + return dao.getAttestationsFor(publicKey.keyToBin(), identityAttestationMapper) + .executeAsList().toSet() + } + + override fun getAttestationsBy(publicKey: PublicKey): Set { + return dao.getAttestationsBy(publicKey.keyToBin(), identityAttestationMapper) + .executeAsList().toSet() + } + + override fun getAttestationsOver(metadata: Metadata): Set { + return dao.getAttestationsOver(metadata.hash, identityAttestationMapper).executeAsList() + .toSet() + } + + override fun getAuthority(attestation: IdentityAttestation): ByteArray { + return dao.getAuthority(attestation.signature).executeAsOne() + } + + override fun getCredentialOver(metadata: Metadata): Credential { + return Credential(metadata, this.getAttestationsOver(metadata)) + } + + override fun getCredentialsFor(publicKey: PublicKey): List { + return this.getMetadataFor(publicKey).map { Credential(it, this.getAttestationsOver(it)) } + } + + override fun getKnownIdentities(): List { + return dao.getKnownIdentities().executeAsList() + .map { defaultCryptoProvider.keyFromPublicBin(it) } + } + + override fun getKnownSubjects(): List { + return dao.getKnownSubjects().executeAsList() + .map { defaultCryptoProvider.keyFromPublicBin(it) } + } + + override fun dropIdentityTable(publicKey: PublicKey): List { + val attestationHashes = this.getTokensFor(publicKey).map { it.contentHash } + val keyBin = publicKey.keyToBin() + dao.deleteTokensFor(keyBin) + dao.deleteMetadataFor(keyBin) + dao.deleteIdentityAttestationsFor(keyBin) + return attestationHashes + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/store/IdentityStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/store/IdentityStore.kt new file mode 100644 index 00000000..a593b308 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/identity/store/IdentityStore.kt @@ -0,0 +1,31 @@ +package nl.tudelft.ipv8.attestation.identity.store + +import nl.tudelft.ipv8.attestation.identity.datastructures.IdentityAttestation +import nl.tudelft.ipv8.attestation.identity.datastructures.Metadata +import nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree.Token +import nl.tudelft.ipv8.keyvault.PublicKey + +class Credential(val metadata: Metadata, val attestations: Set) + +interface IdentityStore { + + fun insertToken(publicKey: PublicKey, token: Token) + fun insertMetadata(publicKey: PublicKey, metadata: Metadata) + fun insertAttestation( + publicKey: PublicKey, + authorityKey: PublicKey, + attestation: IdentityAttestation + ) + + fun getTokensFor(publicKey: PublicKey): List + fun getMetadataFor(publicKey: PublicKey): List + fun getAttestationsFor(publicKey: PublicKey): Set + fun getAttestationsBy(publicKey: PublicKey): Set + fun getAttestationsOver(metadata: Metadata): Set + fun getAuthority(attestation: IdentityAttestation): ByteArray + fun getCredentialOver(metadata: Metadata): Credential + fun getCredentialsFor(publicKey: PublicKey): List + fun getKnownIdentities(): List + fun getKnownSubjects(): List + fun dropIdentityTable(publicKey: PublicKey): List +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/AuthoritySQLiteStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/AuthoritySQLiteStore.kt new file mode 100644 index 00000000..25802e7b --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/AuthoritySQLiteStore.kt @@ -0,0 +1,144 @@ +package nl.tudelft.ipv8.attestation.revocation + +import nl.tudelft.ipv8.attestation.common.Authority +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.sqldelight.Database + +private val authorityMapper: ( + ByteArray?, + ByteArray, + Long?, + Long?, +) -> Authority = { public_key, hash, version, recognized -> + Authority( + public_key?.let { defaultCryptoProvider.keyFromPublicBin(it) }, + hash, + version ?: 0L, + recognized?.toInt() == 1 + ) +} + +class AuthoritySQLiteStore(database: Database) : AuthorityStore { + private val dao = database.dbAuthorityQueries + + override fun getKnownAuthorities(): List { + return dao.getAllAuthorities(authorityMapper).executeAsList() + } + + override fun getRecognizedAuthorities(): List { + return dao.getAllRecognizedAuthorities(authorityMapper).executeAsList() + } + + override fun getAuthorityByHash(hash: ByteArray): Authority? { + return dao.getAuthorityByHash(hash, authorityMapper).executeAsOneOrNull() + } + + override fun recognizeAuthority(hash: ByteArray) { + return dao.recognizeAuthority(hash) + } + + override fun disregardAuthority(hash: ByteArray) { + return dao.disregardAuthority(hash) + } + + override fun insertAuthority(publicKey: PublicKey) { + return dao.insertAuthority(publicKey.keyToBin(), publicKey.keyToHash(), null, null) + } + + override fun insertTrustedAuthority(publicKey: PublicKey) { + return dao.insertAuthority(publicKey.keyToBin(), publicKey.keyToHash(), null, 1) + } + + override fun insertAuthority(hash: ByteArray) { + return dao.insertAuthority(null, hash, null, null) + } + + override fun insertRevocations( + publicKeyHash: ByteArray, + version: Long, + signature: ByteArray, + revokedHashes: List, + ) { + var authorityId = dao.getAuthorityIdByHash(publicKeyHash).executeAsOneOrNull() + if (authorityId == null) { + this.insertAuthority(publicKeyHash) + authorityId = dao.getAuthorityIdByHash(publicKeyHash).executeAsOne() + } + + var versionId = + dao.getVersionByAuthorityIDandVersionNumber(authorityId, version) + .executeAsOneOrNull()?.version_id + if (versionId == null) { + dao.insertVersion(authorityId, version, signature) + versionId = dao.getVersionByAuthorityIDandVersionNumber(authorityId, version) + .executeAsOne().version_id + } + + val authority = dao.getAuthorityByHash(publicKeyHash).executeAsOne() + + revokedHashes.forEach { dao.insertRevocation(authorityId, versionId, it) } + + if ((authority.version_number ?: 0) < version) { + dao.updateVersionFor(versionId, publicKeyHash) + } + } + + override fun getVersionsSince(publicKeyHash: ByteArray, sinceVersion: Long): List { + val authorityId = dao.getAuthorityIdByHash(publicKeyHash).executeAsOneOrNull() + return if (authorityId == null) { + emptyList() + } else + dao.getVersionsSince(authorityId, sinceVersion).executeAsList() + } + + override fun getRevocations( + publicKeyHash: ByteArray, + versions: List, + ): List { + val authorityId = dao.getAuthorityIdByHash(publicKeyHash).executeAsOne() + val versionEntries = + dao.getVersionsByAuthorityIDandVersionNumbers(authorityId, versions).executeAsList() + + return versionEntries.map { + RevocationBlob( + publicKeyHash, it.version_number, + it.signature, + dao.getRevocationsByAuthorityIdAndVersionId(authorityId, it.version_id) + .executeAsList() + ) + } + } + + override fun getAllRevocations(): List { + return dao.getRevocations().executeAsList() + } + + override fun getNumberOfRevocations(): Long { + return dao.getNumberOfRevocations().executeAsOne() + } + + override fun getMissingVersion(authorityKeyHash: ByteArray): Long? { + val authorityId = dao.getAuthorityIdByHash(authorityKeyHash).executeAsOneOrNull() + return authorityId?.let { dao.getMissingVersionByAuthorityID(it).executeAsOneOrNull()?.MIN } + } + + override fun isRevoked(signature: ByteArray): Boolean { + return dao.isRevoked(signature).executeAsList().isNotEmpty() + } + + override fun isRevokedBy(signature: ByteArray, authorityKeyHash: ByteArray): Boolean { + val authorityId = dao.getAuthorityIdByHash(authorityKeyHash).executeAsOneOrNull() + return if (authorityId == null) { + false + } else { + dao.isRevokedBy(signature, authorityId).executeAsList().isNotEmpty() + } + } + + fun clearRevocations() { + dao.clearRevocations() + dao.clearVersions() + dao.clearAuthorityVersions() + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/AuthorityStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/AuthorityStore.kt new file mode 100644 index 00000000..6be97fba --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/AuthorityStore.kt @@ -0,0 +1,37 @@ +package nl.tudelft.ipv8.attestation.revocation + +import nl.tudelft.ipv8.attestation.common.Authority +import nl.tudelft.ipv8.keyvault.PublicKey + +class RevocationBlob( + val publicKeyHash: ByteArray, + val version: Long, + val signature: ByteArray, + val revocations: List +) + +interface AuthorityStore { + + fun getKnownAuthorities(): List + fun getRecognizedAuthorities(): List + fun getAuthorityByHash(hash: ByteArray): Authority? + fun recognizeAuthority(hash: ByteArray) + fun disregardAuthority(hash: ByteArray) + fun insertTrustedAuthority(publicKey: PublicKey) + fun insertAuthority(publicKey: PublicKey) + fun insertAuthority(hash: ByteArray) + fun insertRevocations( + publicKeyHash: ByteArray, + version: Long, + signature: ByteArray, + revokedHashes: List, + ) + + fun isRevoked(signature: ByteArray): Boolean + fun isRevokedBy(signature: ByteArray, authorityKeyHash: ByteArray): Boolean + fun getRevocations(publicKeyHash: ByteArray, versions: List): List + fun getVersionsSince(publicKeyHash: ByteArray, sinceVersion: Long): List + fun getAllRevocations(): List + fun getNumberOfRevocations(): Long + fun getMissingVersion(authorityKeyHash: ByteArray): Long? +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/RevocationCommunity.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/RevocationCommunity.kt new file mode 100644 index 00000000..52e139d3 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/RevocationCommunity.kt @@ -0,0 +1,559 @@ +package nl.tudelft.ipv8.attestation.revocation + +import kotlinx.coroutines.* +import mu.KotlinLogging +import nl.tudelft.ipv8.Community +import nl.tudelft.ipv8.Overlay +import nl.tudelft.ipv8.Peer +import nl.tudelft.ipv8.attestation.common.AuthorityManager +import nl.tudelft.ipv8.attestation.revocation.caches.PENDING_REVOCATION_UPDATE_CACHE_PREFIX +import nl.tudelft.ipv8.attestation.revocation.caches.PendingRevocationUpdateCache +import nl.tudelft.ipv8.attestation.revocation.payloads.RevocationUpdateChunkPayload +import nl.tudelft.ipv8.attestation.revocation.payloads.RevocationUpdatePreviewPayload +import nl.tudelft.ipv8.attestation.revocation.payloads.RevocationUpdateRequestPayload +import nl.tudelft.ipv8.attestation.common.RequestCache +import nl.tudelft.ipv8.attestation.revocation.caches.AllowedRevocationUpdateRequestCache +import nl.tudelft.ipv8.attestation.wallet.caches.PeerCache +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.messaging.* +import nl.tudelft.ipv8.peerdiscovery.Network +import nl.tudelft.ipv8.util.* + +const val DELAY = 2500L +const val DEFAULT_GOSSIP_AMOUNT = 5 +private const val CHUNK_SIZE = 800 + +private val logger = KotlinLogging.logger {} + +const val REVOCATION_PRESENTATION_PAYLOAD = 1 +const val REVOCATION_UPDATE_REQUEST_PAYLOAD = 2 +const val REVOCATION_UPDATE_CHUNK_PAYLOAD = 3 + +class RevocationCommunity(val authorityManager: AuthorityManager) : Community() { + private var udpDelay: Long = 0L + override val serviceId = "fdbb9c5c18bf480a4baba08d352727e66ee89173" + + @Suppress("JoinDeclarationAndAssignment", "LateinitVarOverridesLateinitVar") + override lateinit var myPeer: Peer + + @Suppress("JoinDeclarationAndAssignment", "LateinitVarOverridesLateinitVar") + override lateinit var endpoint: EndpointAggregator + + @Suppress("JoinDeclarationAndAssignment", "LateinitVarOverridesLateinitVar") + override lateinit var network: Network + + // Possibility to override the getPeers method when using shared network between Communities. + private var fetchPeers: () -> List = ::getPeers + + var paused: Boolean = false + + private lateinit var revocationUpdateCallback: (publicKeyHash: ByteArray, version: Long, amount: Int) -> Unit + + constructor( + myPeer: Peer, + endpoint: EndpointAggregator, + network: Network, + authorityManager: AuthorityManager, + onGetPeers: (() -> List)? = null + ) : this( + authorityManager + ) { + this.myPeer = myPeer + this.endpoint = endpoint + this.network = network + if (onGetPeers != null) { + this.fetchPeers = onGetPeers + } + } + + private lateinit var gossipRoutine: Job + val requestCache = RequestCache() + + init { + messageHandlers[REVOCATION_PRESENTATION_PAYLOAD] = ::onRevocationUpdatePreviewPayloadWrapper + messageHandlers[REVOCATION_UPDATE_REQUEST_PAYLOAD] = + ::onRevocationUpdateRequestPayloadWrapper + messageHandlers[REVOCATION_UPDATE_CHUNK_PAYLOAD] = ::onRevocationUpdateChunkPayloadWrapper + } + + override fun load() { + super.load() + if (!this::network.isInitialized) { + this.network = super.network + } + + if (!this::myPeer.isInitialized) { + this.myPeer = super.myPeer + } + + if (!this::endpoint.isInitialized) { + this.endpoint = super.endpoint + } + start() + } + + override fun unload() { + super.unload() + this.gossipRoutine.cancel() + } + + fun setUDPDelay(delay: Long) { + this.udpDelay = delay + } + + fun start() { + this.gossipRoutine = GlobalScope.launch { + while (isActive) { + if (!paused) { + try { + gossipRevocations(getRandomPeers(DEFAULT_GOSSIP_AMOUNT)) + } catch (e: UninitializedPropertyAccessException) { + logger.info("Community not fully initialised.") + } finally { + delay(DELAY) + } + } else { + delay(DELAY) + } + } + } + } + + fun revokeAttestations(attestationHashes: List) { + val myPublicKeyHash = this.myPeer.publicKey.keyToHash() + var myAuthority = this.authorityManager.getAuthority(myPublicKeyHash) + if (myAuthority == null) { + this.authorityManager.addTrustedAuthority(this.myPeer.publicKey) + myAuthority = this.authorityManager.getAuthority(myPublicKeyHash)!! + } + val version = myAuthority.version + 1 + var signableData = version.toByteArray() + attestationHashes.forEach { signableData += it } + val signature = (this.myPeer.key as PrivateKey).sign(sha3_256(signableData)) + + logger.info("Revoking ${attestationHashes.size} signature(s) with version $version") + this.authorityManager.insertRevocations( + myPublicKeyHash, + version, + signature, + attestationHashes + ) + } + + private fun onRevocationUpdatePreviewPayloadWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(RevocationUpdatePreviewPayload.Deserializer) + logger.info("Received RevocationPresentationPayload from ${peer.mid}.") + this.onRevocationUpdatePreviewPayload(peer, payload) + } + + private fun onRevocationUpdateRequestPayloadWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(RevocationUpdateRequestPayload.Deserializer) + logger.info("Received RevocationUpdateRequestPayload from ${peer.mid}.") + GlobalScope.launch { onRevocationUpdateRequestPayload(peer, payload) } + } + + private fun onRevocationUpdateChunkPayloadWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(RevocationUpdateChunkPayload.Deserializer) + logger.info("Received RevocationUpdateChunkPayload ${payload.sequenceNumber} from ${peer.mid}.") + this.onRevocationUpdateChunkPayload(peer, payload) + } + + private fun onRevocationUpdatePreviewPayload( + peer: Peer, + payload: RevocationUpdatePreviewPayload + ) { + val remoteRefs = + payload.revocationRefs.filter { authorityManager.containsAuthority(it.key.bytes) } + val localRefs = this.authorityManager.getMissingRevocationPreviews() + val requestedRefs = hashMapOf() + + for (key in remoteRefs.keys) { + val localVersion = localRefs[key] + val remoteVersion = remoteRefs[key]!! + + // If we're already waiting on a version, wait until we receive it or the cache times out. + // We want to receive versions concurrent for now. + val idPair = + PendingRevocationUpdateCache.generateId( + peer.publicKey.keyToHash(), + key.bytes, + (localVersion ?: 0L) + 1 + ) + if (!this.requestCache.has(idPair)) { + if (localVersion == null) { + requestedRefs[key] = 0L + for (i in 1L..remoteVersion) { + this.requestCache.add( + PendingRevocationUpdateCache( + this.requestCache, + peer.publicKey.keyToHash(), + key.bytes, + i, + timeout = (30 + i * 10).toInt() + ) + ) + } + } else if (localVersion < remoteVersion) { + requestedRefs[key] = localVersion + for (i in (localVersion + 1L)..remoteVersion) { + val peerHash = peer.publicKey.keyToHash() + if (!this.requestCache.has( + PendingRevocationUpdateCache.generateId( + peerHash, + key.bytes, + i + ) + ) + ) { + this.requestCache.add( + PendingRevocationUpdateCache( + this.requestCache, + peerHash, + key.bytes, + i, + timeout = (30 + i * 10).toInt() + ) + ) + } + } + } + } else { + logger.info("Not requesting V$remoteVersion:${key.bytes.toHex()} as we have already requested it.") + } + } + + if (requestedRefs.isNotEmpty()) { + val updateRequestPayload = RevocationUpdateRequestPayload(requestedRefs) + val packet = serializePacket(REVOCATION_UPDATE_REQUEST_PAYLOAD, updateRequestPayload) + logger.info("Requesting revocation update: ${requestedRefs}.") + this.endpoint.send(peer, packet) + } + } + + @OptIn(ExperimentalUnsignedTypes::class) + private suspend fun onRevocationUpdateRequestPayload( + peer: Peer, + payload: RevocationUpdateRequestPayload, + bulkSending: Boolean = false, + ) { + if (bulkSending) { + val revocations = hashMapOf>() + payload.revocationRefs.forEach { (hash, version) -> + revocations[hash] = this.authorityManager.getRevocations(hash.bytes, version) + } + + val blob = serializeRevocationMap(revocations) + val hash = sha1(blob) + var sequenceNumber = 0 + logger.info("Sending update chunks.") + for (i in blob.indices step CHUNK_SIZE) { + val endIndex = if (i + CHUNK_SIZE > blob.size) blob.size else i + CHUNK_SIZE + val chunkPayload = RevocationUpdateChunkPayload( + sequenceNumber, + hash, + this.myPeer.publicKey.keyToHash(), + 0L, + blob.copyOfRange(i, endIndex) + ) + val packet = serializePacket(REVOCATION_UPDATE_CHUNK_PAYLOAD, chunkPayload) + logger.info("Sending update chunk $sequenceNumber") + this.endpoint.send(peer, packet) + sequenceNumber += 1 + } + } else { + val solicited = + this.requestCache.pop(AllowedRevocationUpdateRequestCache.generateId(peer)) != null + if (solicited) { + val revocations = mutableListOf() + + payload.revocationRefs.forEach { (hash, version) -> + revocations.addAll(this.authorityManager.getRevocations(hash.bytes, version)) + } + + logger.info("Client request the following versions: ${revocations.forEachIndexed { index, it -> if (index != revocations.lastIndex) it.version.toString() + ", " else it.version }}") + + // TODO: ths could be performed in coroutines, however, would most likely lead to package lost. + for (rev in revocations) { + val blob = serializeRevocationBlob(rev) + val hash = sha1(blob) + var sequenceNumber = 0 + for (i in blob.indices step CHUNK_SIZE) { + val endIndex = if (i + CHUNK_SIZE > blob.size) blob.size else i + CHUNK_SIZE + val chunkPayload = + RevocationUpdateChunkPayload( + sequenceNumber, hash, rev.publicKeyHash, rev.version, + blob.copyOfRange(i, endIndex) + ) + val packet = serializePacket(REVOCATION_UPDATE_CHUNK_PAYLOAD, chunkPayload) + logger.info("Sending update chunk $sequenceNumber") + this.endpoint.send(peer, packet) + delay(udpDelay) + sequenceNumber += 1 + } + } + } else { + logger.warn("Ignoring unsolicited update request.") + } + } + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun onRevocationUpdateChunkPayload( + peer: Peer, + payload: RevocationUpdateChunkPayload, + unsafeInsertion: Boolean = false, + bulkSending: Boolean = false, + ) { + if (bulkSending) { + val (prefix, id) = PeerCache.idFromAddress( + PENDING_REVOCATION_UPDATE_CACHE_PREFIX, + peer.mid + ) + if (this.requestCache.has(prefix, id)) { + val cache = this.requestCache.get(prefix, id) as PendingRevocationUpdateCache + var localBlob = byteArrayOf() + cache.revocationMap[payload.sequenceNumber] = payload.data + cache.revocationMap.keys.sorted().forEach { localBlob += cache.revocationMap[it]!! } + if (sha1(localBlob).contentEquals(payload.payloadHash)) { + this.requestCache.pop(prefix, id) as PendingRevocationUpdateCache + val revocationMap = this.deserializeRevocationMap(localBlob) + logger.info("Received update: ${revocationMap.keys.size}") + revocationMap.forEach { (hash, list) -> + for (blob in list) { + val authority = this.authorityManager.getAuthority(hash.bytes) + + if (authority?.publicKey != null) { + var signable = serializeULong(blob.version.toULong()) + blob.revocations.forEach { signable += it } + if (!authority.publicKey.verify( + blob.signature, + sha3_256(signable) + ) + ) { + logger.warn("Peer ${peer.mid} might have altered the revoked signatures, skipping!") + continue + } + } else if (!unsafeInsertion) { + logger.info("Dropping unsolicited revocations.") + continue + } else { + logger.info("Inserting revocations without verification as authority is not recognized.") + } + + this.authorityManager.insertRevocations( + hash.bytes, + blob.version, + blob.signature, + blob.revocations + ) + if (this::revocationUpdateCallback.isInitialized) { + this.revocationUpdateCallback( + hash.bytes, + blob.version, + blob.revocations.size + ) + } + } + } + } + } else { + logger.warn("Received update we did not request, dropping.") + } + } else { + val idPair = + PendingRevocationUpdateCache.generateId( + peer.publicKey.keyToHash(), + payload.authorityKeyHash, + payload.version + ) + if (this.requestCache.has(idPair)) { + val cache = this.requestCache.get(idPair) as PendingRevocationUpdateCache + var localBlob = byteArrayOf() + + cache.revocationMap[payload.sequenceNumber] = payload.data + cache.revocationMap.keys.sorted().forEach { localBlob += cache.revocationMap[it]!! } + if (sha1(localBlob).contentEquals(payload.payloadHash)) { + this.requestCache.pop(idPair) as PendingRevocationUpdateCache + val revocationBlob = this.deserializeRevocationBlob(localBlob) + logger.info("Received update: ${revocationBlob.revocations.size} revoked signatures") + + val authority = this.authorityManager.getAuthority(revocationBlob.publicKeyHash) + if (authority?.publicKey != null) { + var signable = revocationBlob.version.toByteArray() + revocationBlob.revocations.forEach { signable += it } + if (!authority.publicKey.verify( + revocationBlob.signature, + sha3_256(signable) + ) + ) { + logger.warn("Peer ${peer.mid} might have altered the revoked signatures, skipping!") + return + } + } else if (!unsafeInsertion) { + logger.info("Dropping unsolicited revocations.") + return + } else { + logger.info("Inserting revocations without verification as authority is not recognized.") + } + + logger.info("IG-SSI: Inserting version ${revocationBlob.version} at ${System.currentTimeMillis()}, tot: ${this.authorityManager.authorityDatabase.getNumberOfRevocations()}") + this.authorityManager.insertRevocations( + revocationBlob.publicKeyHash, + revocationBlob.version, + revocationBlob.signature, + revocationBlob.revocations + ) + if (this::revocationUpdateCallback.isInitialized) { + this.revocationUpdateCallback( + revocationBlob.publicKeyHash, + revocationBlob.version, + revocationBlob.revocations.size + ) + } + } + } else { + logger.warn("Received update for version ${payload.version} we did not request, dropping.") + } + } + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun serializeRevocationBlob(blob: RevocationBlob): ByteArray { + var out = + blob.publicKeyHash + blob.signature + serializeULong(blob.version.toULong()) + serializeUInt( + blob.revocations.size.toUInt() + ) + for (hash in blob.revocations) { + out += hash + } + return out + } + + private fun deserializeRevocationBlob(serialized: ByteArray): RevocationBlob { + var offset = 0 + + val keyHash = serialized.copyOfRange(offset, offset + SERIALIZED_SHA1_HASH_SIZE) + offset += SERIALIZED_SHA1_HASH_SIZE + + val signature = serialized.copyOfRange(offset, offset + SIGNATURE_SIZE) + offset += SIGNATURE_SIZE + + val version = deserializeULong(serialized, offset).toLong() + offset += SERIALIZED_ULONG_SIZE + + val revocationAmount = deserializeUInt(serialized, offset).toInt() + offset += SERIALIZED_UINT_SIZE + + val revocations = mutableListOf() + for (i in 0 until revocationAmount) { + val revocation = serialized.copyOfRange(offset, offset + SERIALIZED_SHA3_256_SIZE) + offset += SERIALIZED_SHA3_256_SIZE + revocations.add(revocation) + } + + return RevocationBlob(keyHash, version, signature, revocations) + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun serializeRevocationMap(map: Map>): ByteArray { + val size = map.size + var out = serializeUInt(size.toUInt()) + map.forEach { (keyHashKey, list) -> + out += keyHashKey.bytes + val versionAmount = list.size.toUInt() + out += serializeUInt(versionAmount) + for (blob in list) { + out += serializeULong(blob.version.toULong()) + out += serializeVarLen(blob.signature) + val revocationAmount = blob.revocations.size.toUInt() + out += serializeUInt(revocationAmount) + for (revocation in blob.revocations) { + out += revocation + } + } + } + return out + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun deserializeRevocationMap(serialized: ByteArray): Map> { + var localOffset = 0 + val out = hashMapOf>() + val size = deserializeUInt(serialized, localOffset).toInt() + localOffset += SERIALIZED_UINT_SIZE + for (i in 0 until size) { + val keyHash = + serialized.copyOfRange(localOffset, localOffset + SERIALIZED_SHA1_HASH_SIZE) + localOffset += SERIALIZED_SHA1_HASH_SIZE + val versionAmount = deserializeUInt(serialized, localOffset).toInt() + localOffset += SERIALIZED_UINT_SIZE + + val revocationBlobs = mutableListOf() + for (j in 0 until versionAmount) { + val version = deserializeULong(serialized, localOffset).toLong() + localOffset += SERIALIZED_ULONG_SIZE + val (signature, signatureOffset) = deserializeVarLen(serialized, localOffset) + localOffset += signatureOffset + val revocationAmount = deserializeUInt(serialized, localOffset).toInt() + localOffset += SERIALIZED_UINT_SIZE + + val revocations = mutableListOf() + for (k in 0 until revocationAmount) { + val revocation = + serialized.copyOfRange(localOffset, localOffset + SERIALIZED_SHA3_256_SIZE) + revocations.add(revocation) + localOffset += SERIALIZED_SHA3_256_SIZE + } + revocationBlobs.add(RevocationBlob(keyHash, version, signature, revocations)) + } + out[keyHash.toKey()] = revocationBlobs + } + return out + } + + fun gossipRevocations() { + gossipRevocations(getRandomPeers(DEFAULT_GOSSIP_AMOUNT)) + } + + private fun getRandomPeers(amount: Int): List { + return this.fetchPeers().random(amount).toList() + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun gossipRevocations(peers: List) { + val revocations = this.authorityManager.getLatestRevocationPreviews() + if (revocations.isEmpty()) { + return + } + val payload = RevocationUpdatePreviewPayload(revocations) + val packet = serializePacket(REVOCATION_PRESENTATION_PAYLOAD, payload) + peers.forEach { + if (!this.requestCache.has(AllowedRevocationUpdateRequestCache.generateId(it))) { + try { + this.requestCache.add( + AllowedRevocationUpdateRequestCache( + this.requestCache, + it + ) + ) + logger.info("Sending revocation preview to ${it.mid}.") + endpoint.send(it, packet) + } catch (e: RuntimeException) { + // Other co-routine was faster. + } + } + } + } + + fun setRevocationUpdateCallback(f: (publicKeyHash: ByteArray, version: Long, amount: Int) -> Unit) { + this.revocationUpdateCallback = f + } + + class Factory( + private val authorityManager: AuthorityManager, + ) : Overlay.Factory(RevocationCommunity::class.java) { + override fun create(): RevocationCommunity { + return RevocationCommunity(authorityManager) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/caches/AllowedRevocationUpdateRequestCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/caches/AllowedRevocationUpdateRequestCache.kt new file mode 100644 index 00000000..8c7e5123 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/caches/AllowedRevocationUpdateRequestCache.kt @@ -0,0 +1,22 @@ +package nl.tudelft.ipv8.attestation.revocation.caches + +import nl.tudelft.ipv8.Peer +import nl.tudelft.ipv8.attestation.common.RequestCache +import nl.tudelft.ipv8.attestation.wallet.caches.HashCache +import nl.tudelft.ipv8.attestation.wallet.caches.NumberCache +import java.math.BigInteger + +const val ALLOWED_REVOCATION_UPDATE_REQUEST_CACHE_PREFIX = "allowed-update-request" + +class AllowedRevocationUpdateRequestCache( + requestCache: RequestCache, + peer: Peer +) : + NumberCache(requestCache, ALLOWED_REVOCATION_UPDATE_REQUEST_CACHE_PREFIX, this.generateId(peer).second, timeout = 30) { + + companion object { + fun generateId(peer: Peer): Pair { + return HashCache.idFromHash(ALLOWED_REVOCATION_UPDATE_REQUEST_CACHE_PREFIX, peer.publicKey.keyToHash()) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/caches/PendingRevocationUpdateCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/caches/PendingRevocationUpdateCache.kt new file mode 100644 index 00000000..b061f31b --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/caches/PendingRevocationUpdateCache.kt @@ -0,0 +1,33 @@ +package nl.tudelft.ipv8.attestation.revocation.caches + +import nl.tudelft.ipv8.attestation.common.RequestCache +import nl.tudelft.ipv8.attestation.wallet.caches.HashCache +import nl.tudelft.ipv8.attestation.wallet.caches.NumberCache +import nl.tudelft.ipv8.util.sha1 +import nl.tudelft.ipv8.util.toByteArray +import java.math.BigInteger + +const val PENDING_REVOCATION_UPDATE_CACHE_PREFIX = "receive-revocation" + +class PendingRevocationUpdateCache( + requestCache: RequestCache, + senderHash: ByteArray, + signeeHash: ByteArray, + version: Long, + timeout: Int = 30 +) : + NumberCache( + requestCache, PENDING_REVOCATION_UPDATE_CACHE_PREFIX, this.generateId( + senderHash, signeeHash, version + ).second, timeout = timeout + ) { + + val revocationMap = hashMapOf() + + companion object { + fun generateId(senderHash: ByteArray, signeeHash: ByteArray, version: Long): Pair { + val hash = sha1(senderHash + signeeHash + version.toByteArray()) + return HashCache.idFromHash(PENDING_REVOCATION_UPDATE_CACHE_PREFIX, hash) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/datastructures/BloomFilter.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/datastructures/BloomFilter.kt new file mode 100644 index 00000000..4f26d3ba --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/datastructures/BloomFilter.kt @@ -0,0 +1,45 @@ +package nl.tudelft.ipv8.attestation.revocation.datastructures + +import java.math.BigInteger +import java.security.MessageDigest +import java.util.BitSet +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +/** + * Adapted from https://github.com/ackintosh/bloom-filter-kotlin. + */ +class BloomFilter(initialCapacity: Int) { + private val bits = BitSet(initialCapacity) + private val hash = Hash(initialCapacity) + private val lock = ReentrantReadWriteLock() + + fun add(value: ByteArray) { + lock.write { + for (seed in 1..3) { + bits.set(hash.call(seed, value)) + } + } + } + + fun probablyContains(value: ByteArray): Boolean { + lock.read { + for (seed in 1..3) { + if (!bits.get(hash.call(seed, value))) { + return false + } + } + } + return true + } + + private class Hash(val filterSize: Int) { + private val md5 = MessageDigest.getInstance("MD5") + + fun call(seed: Int, value: ByteArray) = + BigInteger(1, md5.digest(value + seed.toByte())) + .remainder(BigInteger.valueOf(filterSize.toLong())) + .toInt() + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdateChunkPayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdateChunkPayload.kt new file mode 100644 index 00000000..ba2604e9 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdateChunkPayload.kt @@ -0,0 +1,43 @@ +package nl.tudelft.ipv8.attestation.revocation.payloads + +import nl.tudelft.ipv8.messaging.* + +class RevocationUpdateChunkPayload( + val sequenceNumber: Int, + val payloadHash: ByteArray, + val authorityKeyHash: ByteArray, + val version: Long, + val data: ByteArray, +) : Serializable { + override fun serialize(): ByteArray { + return serializeUInt(sequenceNumber.toUInt()) + payloadHash + authorityKeyHash + serializeULong(version.toULong()) + serializeVarLen( + data) + } + + companion object Deserializer : Deserializable { + override fun deserialize(buffer: ByteArray, offset: Int): Pair { + var localOffset = offset + + val sequenceNumber = deserializeUInt(buffer, localOffset).toInt() + localOffset += SERIALIZED_UINT_SIZE + + val hash = buffer.copyOfRange(localOffset, localOffset + SERIALIZED_SHA1_HASH_SIZE) + localOffset += SERIALIZED_SHA1_HASH_SIZE + + val publicKeyHash = buffer.copyOfRange(localOffset, localOffset + SERIALIZED_SHA1_HASH_SIZE) + localOffset += SERIALIZED_SHA1_HASH_SIZE + + val version = deserializeULong(buffer, localOffset).toLong() + localOffset += SERIALIZED_ULONG_SIZE + + val (data, dataOffset) = deserializeVarLen(buffer, localOffset) + + return Pair( + RevocationUpdateChunkPayload(sequenceNumber, hash, publicKeyHash, version, data), + localOffset + dataOffset + ) + } + } + +} + diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdatePreviewPayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdatePreviewPayload.kt new file mode 100644 index 00000000..6716ba94 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdatePreviewPayload.kt @@ -0,0 +1,40 @@ +package nl.tudelft.ipv8.attestation.revocation.payloads + +import nl.tudelft.ipv8.messaging.* +import nl.tudelft.ipv8.util.* + +open class RevocationUpdatePreviewPayload( + val revocationRefs: Map, +) : Serializable { + override fun serialize(): ByteArray { + + var out = byteArrayOf() + revocationRefs.forEach { + out += it.key.bytes + out += serializeULong(it.value.toULong()) + } + return serializeUInt(revocationRefs.size.toUInt()) + out + } + + companion object Deserializer : Deserializable { + override fun deserialize(buffer: ByteArray, offset: Int): Pair { + var localOffset = offset + val size = deserializeUInt(buffer, localOffset).toInt() + localOffset += SERIALIZED_UINT_SIZE + + val refs = hashMapOf() + + for (i in 0 until size) { + val hash = buffer.copyOfRange(localOffset, localOffset + SERIALIZED_SHA1_HASH_SIZE) + localOffset += SERIALIZED_SHA1_HASH_SIZE + val version = deserializeULong(buffer, localOffset).toLong() + localOffset += SERIALIZED_ULONG_SIZE + refs[hash.toKey()] = version + } + + return Pair(RevocationUpdateRequestPayload(refs), localOffset) + } + } + +} + diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdateRequestPayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdateRequestPayload.kt new file mode 100644 index 00000000..ac5b6f8f --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/revocation/payloads/RevocationUpdateRequestPayload.kt @@ -0,0 +1,23 @@ +package nl.tudelft.ipv8.attestation.revocation.payloads + +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.messaging.* +import nl.tudelft.ipv8.util.ByteArrayKey +import nl.tudelft.ipv8.util.hexToBytes +import nl.tudelft.ipv8.util.toHex +import org.json.JSONObject + +class RevocationUpdateRequestPayload( + revocationRefs: Map +) : RevocationUpdatePreviewPayload(revocationRefs) { + + companion object Deserializer : Deserializable { + override fun deserialize(buffer: ByteArray, offset: Int): Pair { + @Suppress("UNCHECKED_CAST") + return RevocationUpdatePreviewPayload.deserialize(buffer, offset) as Pair + } + } + +} + diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/trustchain/TrustChainCommunity.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/trustchain/TrustChainCommunity.kt index d1ae269c..fd80d7b8 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/trustchain/TrustChainCommunity.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/trustchain/TrustChainCommunity.kt @@ -9,7 +9,6 @@ import nl.tudelft.ipv8.messaging.Packet import nl.tudelft.ipv8.util.random import nl.tudelft.ipv8.attestation.trustchain.payload.* import nl.tudelft.ipv8.attestation.trustchain.store.TrustChainStore -import nl.tudelft.ipv8.attestation.trustchain.validation.ValidationErrors import nl.tudelft.ipv8.keyvault.defaultCryptoProvider import nl.tudelft.ipv8.messaging.Address import nl.tudelft.ipv8.util.toHex diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationCommunity.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationCommunity.kt index 36024287..e0e4b4b2 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationCommunity.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationCommunity.kt @@ -1,67 +1,102 @@ package nl.tudelft.ipv8.attestation.wallet +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import mu.KotlinLogging import nl.tudelft.ipv8.Community import nl.tudelft.ipv8.IPv4Address import nl.tudelft.ipv8.Overlay import nl.tudelft.ipv8.Peer -import nl.tudelft.ipv8.attestation.WalletAttestation -import nl.tudelft.ipv8.attestation.IdentityAlgorithm -import nl.tudelft.ipv8.attestation.TrustedAuthorityManager -import nl.tudelft.ipv8.attestation.schema.* -import nl.tudelft.ipv8.attestation.wallet.AttestationCommunity.MessageId.ATTESTATION -import nl.tudelft.ipv8.attestation.wallet.AttestationCommunity.MessageId.ATTESTATION_REQUEST -import nl.tudelft.ipv8.attestation.wallet.AttestationCommunity.MessageId.CHALLENGE -import nl.tudelft.ipv8.attestation.wallet.AttestationCommunity.MessageId.CHALLENGE_RESPONSE -import nl.tudelft.ipv8.attestation.wallet.AttestationCommunity.MessageId.VERIFY_ATTESTATION_REQUEST +import nl.tudelft.ipv8.attestation.wallet.cryptography.IdentityAlgorithm +import nl.tudelft.ipv8.attestation.common.RequestCache +import nl.tudelft.ipv8.attestation.common.SchemaManager +import nl.tudelft.ipv8.attestation.common.consts.SchemaConstants.ID_METADATA import nl.tudelft.ipv8.attestation.wallet.caches.* +import nl.tudelft.ipv8.attestation.wallet.consts.Metadata.ATTRIBUTE +import nl.tudelft.ipv8.attestation.wallet.consts.Metadata.ID_FORMAT +import nl.tudelft.ipv8.attestation.wallet.consts.Metadata.PROPOSED_VALUE +import nl.tudelft.ipv8.attestation.wallet.consts.Metadata.PUBLIC_KEY import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey import nl.tudelft.ipv8.attestation.wallet.payloads.* -import nl.tudelft.ipv8.keyvault.PrivateKey -import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.attestation.wallet.consts.PayloadIds.ATTESTATION +import nl.tudelft.ipv8.attestation.wallet.consts.PayloadIds.ATTESTATION_REQUEST +import nl.tudelft.ipv8.attestation.wallet.consts.PayloadIds.CHALLENGE +import nl.tudelft.ipv8.attestation.wallet.consts.PayloadIds.CHALLENGE_RESPONSE +import nl.tudelft.ipv8.attestation.wallet.consts.PayloadIds.VERIFY_ATTESTATION_REQUEST +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation +import nl.tudelft.ipv8.attestation.wallet.store.AttestationStore +import nl.tudelft.ipv8.messaging.EndpointAggregator import nl.tudelft.ipv8.messaging.Packet +import nl.tudelft.ipv8.messaging.deserializeVarLen import nl.tudelft.ipv8.messaging.payload.GlobalTimeDistributionPayload +import nl.tudelft.ipv8.messaging.serializeVarLen +import nl.tudelft.ipv8.peerdiscovery.Network import nl.tudelft.ipv8.util.* import org.json.JSONObject import java.math.BigInteger import java.util.concurrent.locks.ReentrantLock -import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.random.Random import kotlin.random.nextUBytes - private val logger = KotlinLogging.logger {} private const val CHUNK_SIZE = 800 -class AttestationCommunity(val database: AttestationStore) : Community() { +/** + * Community for signing and verifying attestations. + */ +class AttestationCommunity(val database: AttestationStore) : + Community() { + override val serviceId = "e5d116f803a916a84850b9057cc0f662163f71f5" + @Suppress("JoinDeclarationAndAssignment", "JoinDeclarationAndAssignment", "LateinitVarOverridesLateinitVar") + override lateinit var myPeer: Peer + + @Suppress("JoinDeclarationAndAssignment", "JoinDeclarationAndAssignment", "LateinitVarOverridesLateinitVar") + override lateinit var endpoint: EndpointAggregator + + @Suppress("JoinDeclarationAndAssignment", "JoinDeclarationAndAssignment", "LateinitVarOverridesLateinitVar") + override lateinit var network: Network + + constructor( + myPeer: Peer, + endpoint: EndpointAggregator, + network: Network, + database: AttestationStore, + ) : this( + database + ) { + this.myPeer = myPeer + this.endpoint = endpoint + this.network = network + } + private val receiveBlockLock = ReentrantLock() val schemaManager = SchemaManager() - private lateinit var attestationRequestCallback: (peer: Peer, attributeName: String, metaData: String) -> ByteArray - private lateinit var attestationRequestCompleteCallback: (forPeer: Peer, attributeName: String, attestation: WalletAttestation, attestationHash: ByteArray, idFormat: String, fromPeer: Peer?, metaData: String?, signature: ByteArray?) -> Unit - private lateinit var verifyRequestCallback: (peer: Peer, attributeHash: ByteArray) -> Boolean + private lateinit var attestationRequestCallback: (peer: Peer, attributeName: String, metaData: String, proposedValue: String?) -> Deferred + private lateinit var attestationRequestCompleteCallback: (forPeer: Peer, attributeName: String, attestation: WalletAttestation, attestationHash: ByteArray, idFormat: String, fromPeer: Peer?, value: ByteArray?) -> Unit + private lateinit var verifyRequestCallback: (peer: Peer, attributeHash: ByteArray) -> Deferred private lateinit var attestationChunkCallback: (peer: Peer, sequenceNumber: Int) -> Unit - val attestationKeys: MutableMap> = mutableMapOf() + private val attestationKeys: MutableMap> = + mutableMapOf() - private val cachedAttestationBlobs = mutableMapOf() - private val allowedAttestations = mutableMapOf>() - - val trustedAuthorityManager = TrustedAuthorityManager(database) + private val cachedAttestationBlobs: MutableMap = mutableMapOf() + private val allowedAttestations: MutableMap> = mutableMapOf() val requestCache = RequestCache() init { - trustedAuthorityManager.loadAuthorities() schemaManager.registerDefaultSchemas() for (att in this.database.getAllAttestations()) { val hash = att.attestationHash val key = att.key val idFormat = att.idFormat - this.attestationKeys[ByteArrayKey(hash)] = Pair(this.getIdAlgorithm(idFormat).loadSecretKey(key), idFormat) + this.attestationKeys[hash.toKey()] = + Pair(this.getIdAlgorithm(idFormat).loadSecretKey(key), idFormat) } messageHandlers[VERIFY_ATTESTATION_REQUEST] = ::onVerifyAttestationRequestWrapper @@ -71,106 +106,32 @@ class AttestationCommunity(val database: AttestationStore) : Community() { messageHandlers[ATTESTATION_REQUEST] = ::onRequestAttestationWrapper } - private fun onRequestAttestationWrapper(packet: Packet) { - val (peer, dist, payload) = packet.getAuthPayloadWithDist(RequestAttestationPayload.Deserializer) - logger.info("Received RequestAttestation from ${peer.mid} for metadata ${payload.metadata}.") - this.onRequestAttestation(peer, dist, payload) - } - - private fun onChallengeResponseWrapper(packet: Packet) { - val (peer, _, payload) = packet.getAuthPayloadWithDist(ChallengeResponsePayload.Deserializer) - logger.info("Received ChallengeResponse from ${peer.mid} for hash ${String(payload.challengeHash)} with response ${ - String(payload.response) - }.") - this.onChallengeResponse(peer, payload) - } - - private fun onChallengeWrapper(packet: Packet) { - val (peer, _, payload) = packet.getAuthPayloadWithDist(ChallengePayload.Deserializer) - logger.info("Received Challenge from ${peer.mid} for hash ${String(payload.attestationHash)} with challenge ${ - String(payload.challenge) - }.") - this.onChallenge(peer, payload) - } - - private fun onAttestationChunkWrapper(packet: Packet) { - val (peer, dist, payload) = packet.getAuthPayloadWithDist(AttestationChunkPayload.Deserializer) - logger.info("Received AttestationChunk from ${peer.mid} with sequence number ${payload.sequenceNumber}, data ${ - String(payload.data) - } hash ${String(payload.hash)}.") - this.onAttestationChunk(peer, dist, payload) - } - - private fun onVerifyAttestationRequestWrapper(packet: Packet) { - val (peer, _, payload) = packet.getAuthPayloadWithDist(VerifyAttestationRequestPayload.Deserializer) - logger.info("Received VerifyAttestationRequest from ${peer.mid} for hash ${String(payload.hash)}.") - this.onVerifyAttestationRequest(peer, payload) - - } - - private fun getIdAlgorithm(idFormat: String): IdentityAlgorithm { - return this.schemaManager.getAlgorithmInstance(idFormat) - } - - fun setAttestationRequestCallback(f: (peer: Peer, attributeName: String, metaData: String) -> ByteArray) { - this.attestationRequestCallback = f - } - - fun setAttestationRequestCompleteCallback(f: (forPeer: Peer, attributeName: String, attestation: WalletAttestation, attributeHash: ByteArray, idFormat: String, fromPeer: Peer?, metaData: String?, Signature: ByteArray?) -> Unit) { - this.attestationRequestCompleteCallback = f - } - - fun setVerifyRequestCallback(f: (attributeName: Peer, attributeHash: ByteArray) -> Boolean) { - this.verifyRequestCallback = f - } - - fun setAttestationChunkCallback(f: (peer: Peer, sequenceNumber: Int) -> Unit) { - this.attestationChunkCallback = f - } - - @Suppress("UNUSED_PARAMETER") - fun dumbBlob(attributeName: String, idFormat: String, blob: ByteArray, metaData: String = "") { - // TODO: Implement this method. - throw NotImplementedError() - } - - fun verifyAttestationLocally( - peer: Peer, - attestationHash: ByteArray, - metadata: String, - signature: ByteArray, - attestorKey: PublicKey, - ): Boolean { - val parsedMetadata = JSONObject(metadata) - val attesteeKeyHash = parsedMetadata.optString("trustchain_address_hash") - attesteeKeyHash ?: return false - - val isTrusted = this.trustedAuthorityManager.contains(attestorKey.keyToHash().toHex()) - val isOwner = peer.publicKey.keyToHash().toHex() == attesteeKeyHash - val isSignatureValid = attestorKey.verify(signature, - sha1(attestationHash + metadata.toByteArray())) - - return isTrusted && isOwner && isSignatureValid - } - + /** + * Method for requesting attestation with name [attributeName] from peer [peer] with additional metadata [metadata]. + * The attestation will be signed for the public-key belong to [privateKey]. + * An additional proposed value [proposedValue] can also be specified. + */ fun requestAttestation( peer: Peer, attributeName: String, privateKey: BonehPrivateKey, metadata: HashMap = hashMapOf(), - signature: Boolean = false, + proposedValue: String? = null, ) { logger.info("Sending attestation request $attributeName to peer ${peer.mid}.") val publicKey = privateKey.publicKey() val inputMetadata = JSONObject(metadata) - val idFormat = inputMetadata.optString("id_format", "id_metadata") + val idFormat = (inputMetadata.remove(ID_FORMAT) ?: ID_METADATA) as String val metadataJson = JSONObject() - metadataJson.put("attribute", attributeName) - // Encode to UTF-8 - metadataJson.put("public_key", defaultEncodingUtils.encodeBase64ToString(publicKey.serialize())) - metadataJson.putOpt("id_format", idFormat) - metadataJson.putOpt("signature", signature) + metadataJson.put(ATTRIBUTE, attributeName) + metadataJson.put( + PUBLIC_KEY, + defaultEncodingUtils.encodeBase64ToString(publicKey.serialize()) + ) + metadataJson.put(ID_FORMAT, idFormat) + // Will not be added if null. + metadataJson.put(PROPOSED_VALUE, proposedValue) inputMetadata.keys().forEach { metadataJson.put(it, inputMetadata.get(it)) @@ -180,59 +141,91 @@ class AttestationCommunity(val database: AttestationStore) : Community() { val payload = RequestAttestationPayload(metadataJson.toString()) val gTimeStr = globalTime.toString().toByteArray() - this.requestCache.add(ReceiveAttestationRequestCache(this, - peer.mid + gTimeStr, - privateKey, - attributeName, - idFormat, - signature)) + this.requestCache.add( + ReceiveAttestationRequestCache( + this, + peer.mid + gTimeStr, + privateKey, + attributeName, + idFormat, + ) + ) this.allowedAttestations[peer.mid] = - (this.allowedAttestations[peer.mid] ?: emptyArray()) + arrayOf(gTimeStr) + (this.allowedAttestations[peer.mid] ?: emptyList()) + listOf(gTimeStr) val packet = - serializePacket(ATTESTATION_REQUEST, + serializePacket( + ATTESTATION_REQUEST, payload, prefix = this.prefix, - timestamp = globalTime) + timestamp = globalTime + ) endpoint.send(peer, packet) } - private fun onRequestAttestation( + /** + * Method for verifying an attestation with hash [attestationHash] belonging to a peer listening on the + * address [socketAddress]. The value we believe the attestation has is incorporated in [values], with the + * used schema [idFormat]. After verification [callback] is called. + */ + fun verifyAttestationValues( + socketAddress: IPv4Address, + attestationHash: ByteArray, + values: List, + callback: ((ByteArray, List) -> Unit), + idFormat: String, + ) { + val algorithm = this.getIdAlgorithm(idFormat) + + fun onComplete(attestationHash: ByteArray, relativityMap: HashMap) { + callback(attestationHash, values.map { algorithm.certainty(it, relativityMap) }) + } + + this.requestCache.add( + ProvingAttestationCache( + this, + attestationHash, + idFormat, + onComplete = ::onComplete + ) + ) + this.createVerifyAttestationRequest(socketAddress, attestationHash, idFormat) + } + + private suspend fun onRequestAttestation( peer: Peer, dist: GlobalTimeDistributionPayload, payload: RequestAttestationPayload, ) { val metadata = JSONObject(payload.metadata) - val attribute = metadata.getString("attribute") - var value = metadata.optString("value").toByteArray() - val pubkeyEncoded = metadata.getString("public_key") - val idFormat = metadata.getString("id_format") - val idAlgorithm = this.getIdAlgorithm(idFormat) - val shouldSign = metadata.optBoolean("signature", false) - - if (value.isEmpty()) { - value = this.attestationRequestCallback(peer, attribute, payload.metadata) + val attribute = metadata.remove(ATTRIBUTE) as String + val encodedPK = metadata.remove(PUBLIC_KEY) as String + val idFormat = metadata.remove(ID_FORMAT) as String + var proposedValue: String? = null + // We cannot cast null to string, hence member checking. + if (metadata.has(PROPOSED_VALUE)) { + proposedValue = metadata.remove(PROPOSED_VALUE) as String } - val stringifiedValue = when (idFormat) { - ID_METADATA_RANGE_18PLUS -> ID_METADATA_RANGE_18PLUS_PUBLIC_VALUE - ID_METADATA_RANGE_UNDERAGE -> ID_METADATA_RANGE_UNDERAGE_PUBLIC_VALUE - else -> String(value) - } + val metadataString = metadata.toString() + val idAlgorithm = this.getIdAlgorithm(idFormat) - metadata.put("value", stringifiedValue) - metadata.put("trustchain_address_hash", peer.publicKey.keyToHash().toHex()) + val value = + this.attestationRequestCallback(peer, attribute, metadataString, proposedValue).await() + if (value == null) { + logger.error("Failed to get value from callback") + return + } // Decode as UTF-8 ByteArray - val publicKey = idAlgorithm.loadPublicKey(defaultEncodingUtils.decodeBase64FromString(pubkeyEncoded)) + val publicKey = + idAlgorithm.loadPublicKey(defaultEncodingUtils.decodeBase64FromString(encodedPK)) + val attestationBlob = idAlgorithm.attest(publicKey, value) val attestation = idAlgorithm.deserialize(attestationBlob, idFormat) - val signableData = attestation.getHash() + metadata.toString().toByteArray() - val signature = (myPeer.key as PrivateKey).sign(sha1(signableData)) - if (this::attestationRequestCompleteCallback.isInitialized) { this.attestationRequestCompleteCallback( peer, @@ -241,16 +234,15 @@ class AttestationCommunity(val database: AttestationStore) : Community() { attestation.getHash(), idFormat, null, - metadata.toString(), - signature, + value ) } - this.sendAttestation(peer.address, - attestationBlob, + this.sendAttestation( + peer.address, + attestationBlob + serializeVarLen(value), dist.globalTime, - metadata.toString().toByteArray(), - if (shouldSign) signature else null) + ) } private fun onAttestationComplete( @@ -260,45 +252,28 @@ class AttestationCommunity(val database: AttestationStore) : Community() { name: String, attestationHash: ByteArray, idFormat: String, - metaData: String? = null, - signature: ByteArray? = null, + value: ByteArray? = null, ) { - this.attestationKeys[ByteArrayKey(attestationHash)] = Pair(privateKey, idFormat) - this.database.insertAttestation(deserialized, + this.attestationKeys[attestationHash.toKey()] = Pair(privateKey, idFormat) + this.database.insertAttestation( + deserialized, attestationHash, privateKey, idFormat, - metaData, - signature, - peer.publicKey) + value + ) if (this::attestationRequestCompleteCallback.isInitialized) { - this.attestationRequestCompleteCallback(this.myPeer, + this.attestationRequestCompleteCallback( + this.myPeer, name, deserialized, attestationHash, idFormat, peer, - metaData, - signature) - } - } - - fun verifyAttestationValues( - socketAddress: IPv4Address, - attestationHash: ByteArray, - values: ArrayList, - callback: ((ByteArray, List) -> Unit), - idFormat: String, - ) { - val algorithm = this.getIdAlgorithm(idFormat) - - fun onComplete(attestationHash: ByteArray, relativityMap: HashMap) { - callback(attestationHash, values.map { algorithm.certainty(it, relativityMap) }) + value + ) } - - this.requestCache.add(ProvingAttestationCache(this, attestationHash, idFormat, onComplete = ::onComplete)) - this.createVerifyAttestationRequest(socketAddress, attestationHash, idFormat) } private fun createVerifyAttestationRequest( @@ -313,30 +288,32 @@ class AttestationCommunity(val database: AttestationStore) : Community() { this.endpoint.send(socketAddress, packet) } - private fun onVerifyAttestationRequest( + private suspend fun onVerifyAttestationRequest( peer: Peer, payload: VerifyAttestationRequestPayload, ) { - val attestationBlob = this.database.getAttestationByHash(payload.hash) + val hash = stripSHA1Padding(payload.hash) + val attestationBlob = this.database.getAttestationBlobByHash(hash) if (attestationBlob == null) { - logger.warn("Dropping verification request of unknown hash ${payload.hash}!") + logger.warn("Dropping verification request of unknown hash ${payload.hash.toHex()}!") return } if (attestationBlob.isEmpty()) { - logger.warn("Attestation blob for verification is empty: ${payload.hash}!") + logger.warn("Attestation blob for verification is empty: ${payload.hash.toHex()}!") } - val value = verifyRequestCallback(peer, payload.hash) - if (!value) { - logger.info("Verify request callback returned false for $peer, ${payload.hash}") + val value = verifyRequestCallback(peer, hash).await() + if (value == null || !value) { + logger.info("Verify request callback returned false for $peer, ${payload.hash.toHex()}") return } - val (privateKey, idFormat) = this.attestationKeys[ByteArrayKey(payload.hash)]!! - val privateAttestation = schemaManager.deserializePrivate(privateKey, attestationBlob, idFormat) + val (privateKey, idFormat) = this.attestationKeys[hash.toKey()]!! + val privateAttestation = + schemaManager.deserializePrivate(privateKey, attestationBlob, idFormat) val publicAttestationBlob = privateAttestation.serialize() - this.cachedAttestationBlobs[ByteArrayKey(payload.hash)] = privateAttestation + this.cachedAttestationBlobs[hash.toKey()] = privateAttestation this.sendAttestation(peer.address, publicAttestationBlob) } @@ -344,8 +321,6 @@ class AttestationCommunity(val database: AttestationStore) : Community() { sockedAddress: IPv4Address, blob: ByteArray, globalTime: ULong? = null, - metaData: ByteArray? = null, - signature: ByteArray? = null, ) { var sequenceNumber = 0 for (i in blob.indices step CHUNK_SIZE) { @@ -354,34 +329,37 @@ class AttestationCommunity(val database: AttestationStore) : Community() { val blobChunk = blob.copyOfRange(i, endIndex) logger.info("Sending attestation chunk $sequenceNumber to $sockedAddress") - // Only send metadata and signature on final package to reduce overhead. - val payload = - if (i + CHUNK_SIZE > blob.size) - AttestationChunkPayload(sha1(blob), sequenceNumber, blobChunk, metaData, signature) - else - AttestationChunkPayload(sha1(blob), sequenceNumber, blobChunk) - val packet = serializePacket(ATTESTATION, payload, prefix = this.prefix, timestamp = globalTime) + val payload = AttestationChunkPayload(sha1(blob), sequenceNumber, blobChunk) + val packet = + serializePacket(ATTESTATION, payload, prefix = this.prefix, timestamp = globalTime) this.endpoint.send(sockedAddress, packet) sequenceNumber += 1 } } - private fun onAttestationChunk(peer: Peer, dist: GlobalTimeDistributionPayload, payload: AttestationChunkPayload) { + private fun onAttestationChunk( + peer: Peer, + dist: GlobalTimeDistributionPayload, + payload: AttestationChunkPayload + ) { if (this::attestationChunkCallback.isInitialized) { this.attestationChunkCallback(peer, payload.sequenceNumber) } val hashId = HashCache.idFromHash(ATTESTATION_VERIFY_PREFIX, payload.hash) - val (prefix, number) = hashId - val peerIds = arrayListOf>() - val allowedGlobs = this.allowedAttestations.get(peer.mid) ?: arrayOf() + val peerIds = mutableListOf>() + val allowedGlobs = this.allowedAttestations[peer.mid] ?: listOf() allowedGlobs.forEach { if (it.contentEquals(dist.globalTime.toString().toByteArray())) { - peerIds.add(PeerCache.idFromAddress(ATTESTATION_REQUEST_PREFIX, - peer.mid + it)) + peerIds.add( + PeerCache.idFromAddress( + ATTESTATION_REQUEST_PREFIX, + peer.mid + it + ) + ) } } - if (this.requestCache.has(prefix, number)) { - val cache = this.requestCache.get(prefix, number) as ReceiveAttestationVerifyCache + if (this.requestCache.has(hashId)) { + val cache = this.requestCache.get(hashId) as ReceiveAttestationVerifyCache cache.attestationMap.add(Pair(payload.sequenceNumber, payload.data)) var serialized = byteArrayOf() @@ -390,19 +368,24 @@ class AttestationCommunity(val database: AttestationStore) : Community() { } if (sha1(serialized).contentEquals(payload.hash)) { - val deserialized = schemaManager.deserialize(serialized, cache.idFormat) - this.requestCache.pop(prefix, number) - this.onReceivedAttestation(peer, - deserialized, - payload.hash) + val attestation = schemaManager.deserialize(serialized, cache.idFormat) + this.requestCache.pop(hashId) + this.onReceivedAttestation( + peer, + attestation, + payload.hash + ) } - logger.info("Received attestation chunk ${payload.sequenceNumber} for proving by $peer") + logger.info("Received attestation chunk ${payload.sequenceNumber} for proving by ${peer.mid}") } else { var handled = false for (peerId in peerIds) { if (this.requestCache.has(peerId.first, peerId.second)) { - var cache = this.requestCache.get(peerId.first, peerId.second) as ReceiveAttestationRequestCache + var cache = this.requestCache.get( + peerId.first, + peerId.second + ) as ReceiveAttestationRequestCache cache.attestationMap.add(Pair(payload.sequenceNumber, payload.data)) var serialized = byteArrayOf() @@ -411,26 +394,42 @@ class AttestationCommunity(val database: AttestationStore) : Community() { } if (sha1(serialized).contentEquals(payload.hash)) { - val deserialized = - schemaManager.deserializePrivate(cache.privateKey, serialized, cache.idFormat) - cache = (this.requestCache.pop(peerId.first, peerId.second) as ReceiveAttestationRequestCache) - - this.allowedAttestations[peer.mid] = this.allowedAttestations[peer.mid]!!.mapNotNull { - if (!it.contentEquals(dist.globalTime.toString().toByteArray())) it else null - }.toTypedArray() + val attestation = + schemaManager.deserializePrivate( + cache.privateKey, + serialized, + cache.idFormat + ) + val value = deserializeVarLen( + serialized.copyOfRange( + attestation.serialize().size, + serialized.size + ) + ).first + cache = (this.requestCache.pop( + peerId.first, + peerId.second + ) as ReceiveAttestationRequestCache) + + this.allowedAttestations[peer.mid] = + this.allowedAttestations[peer.mid]!!.mapNotNull { + if (!it.contentEquals( + dist.globalTime.toString().toByteArray() + ) + ) it else null + } if (this.allowedAttestations[peer.mid].isNullOrEmpty()) { this.allowedAttestations.remove(peer.mid) } this.onAttestationComplete( - deserialized, + attestation, cache.privateKey, peer, cache.name, - deserialized.getHash(), + attestation.getHash(), cache.idFormat, - if (payload.metadata != null) String(payload.metadata) else null, - if (payload.signature != null) payload.signature else null + value, ) } @@ -442,7 +441,6 @@ class AttestationCommunity(val database: AttestationStore) : Community() { logger.warn("Received Attestation chunk which we did not request!") } } - } private fun onReceivedAttestation( @@ -468,7 +466,7 @@ class AttestationCommunity(val database: AttestationStore) : Community() { cache.relativityMap = relativityMap cache.hashedChallenges = hashedChallenges cache.challenges = challenges - logger.info("Sending ${challenges.size} challenges to $peer.") + logger.info("Sending ${challenges.size} challenges to ${peer.mid}.") var remaining = 10 for (challenge in challenges) { @@ -480,32 +478,38 @@ class AttestationCommunity(val database: AttestationStore) : Community() { } remaining -= 1 - this.requestCache.add(PendingChallengesCache(this, sha1(challenge), cache, cache.idFormat)) + this.requestCache.add( + PendingChallengesCache( + this, + sha1(challenge), + cache, + cache.idFormat + ) + ) val payload = ChallengePayload(attestationHash, challenge) val packet = serializePacket(CHALLENGE, payload, prefix = this.prefix) this.endpoint.send(peer.address, packet) } - } - private fun onChallenge(peer: Peer, payload: ChallengePayload) { - if (!this.attestationKeys.containsKey(ByteArrayKey(payload.attestationHash))) { - logger.error("Received ChallengePayload $payload for unknown attestation hash ${payload.attestationHash}.") + if (!this.attestationKeys.containsKey(payload.attestationHash.toKey())) { + logger.error("Received ChallengePayload $payload for unknown attestation hash ${payload.attestationHash.toHex()}.") return } - val (privateKey, idFormat) = this.attestationKeys[ByteArrayKey(payload.attestationHash)]!! + val (privateKey, idFormat) = this.attestationKeys[payload.attestationHash.toKey()]!! val challengeHash = sha1(payload.challenge) val algorithm = this.getIdAlgorithm(idFormat) - val attestation = this.cachedAttestationBlobs[ByteArrayKey(payload.attestationHash)]!! + val attestation = this.cachedAttestationBlobs[payload.attestationHash.toKey()]!! - val outGoingPayload = ChallengeResponsePayload(challengeHash, - algorithm.createChallengeResponse(privateKey, attestation, payload.challenge)) + val outGoingPayload = ChallengeResponsePayload( + challengeHash, + algorithm.createChallengeResponse(privateKey, attestation, payload.challenge) + ) val packet = serializePacket(CHALLENGE_RESPONSE, outGoingPayload, prefix = this.prefix) this.endpoint.send(peer.address, packet) - } private fun onChallengeResponse( @@ -513,21 +517,31 @@ class AttestationCommunity(val database: AttestationStore) : Community() { payload: ChallengeResponsePayload, ) { synchronized(receiveBlockLock) { - val (prefix, number) = HashCache.idFromHash(PENDING_CHALLENGES_PREFIX, payload.challengeHash) + val (prefix, number) = HashCache.idFromHash( + PENDING_CHALLENGES_PREFIX, + payload.challengeHash + ) if (this.requestCache.has(prefix, number)) { val cache = this.requestCache.pop(prefix, number) as PendingChallengesCache val provingCache = cache.provingCache - val (provingCachePrefix, provingCacheId) = HashCache.idFromHash(PROVING_ATTESTATION_PREFIX, - provingCache.cacheHash) + val (provingCachePrefix, provingCacheId) = HashCache.idFromHash( + PROVING_ATTESTATION_PREFIX, + provingCache.cacheHash + ) var challenge: ByteArray? = null // TODO: Come up with more elegant solution for ByteArray checking. - val hashElement = provingCache.hashedChallenges.find { x -> x.contentEquals(payload.challengeHash) } + val hashElement = + provingCache.hashedChallenges.find { x -> x.contentEquals(payload.challengeHash) } if (hashElement != null) { provingCache.hashedChallenges.remove(hashElement) for (c in provingCache.challenges) { if (sha1(c).contentEquals(payload.challengeHash)) { - provingCache.challenges.remove(provingCache.challenges.first { x -> x.contentEquals(c) }) + provingCache.challenges.remove(provingCache.challenges.first { x -> + x.contentEquals( + c + ) + }) challenge = c break } @@ -535,77 +549,158 @@ class AttestationCommunity(val database: AttestationStore) : Community() { } val algorithm = this.getIdAlgorithm(provingCache.idFormat) if (cache.honestyCheck < 0) { - algorithm.processChallengeResponse(provingCache.relativityMap, challenge, payload.response) - } else if (!algorithm.processHonestyChallenge(cache.honestyCheck, payload.response)) { + algorithm.processChallengeResponse( + provingCache.relativityMap, + challenge, + payload.response + ) + } else if (!algorithm.processHonestyChallenge( + cache.honestyCheck, + payload.response + ) + ) { logger.error("${peer.address} attempted to cheat in the ZKP!") if (this.requestCache.has(provingCachePrefix, provingCacheId)) { this.requestCache.pop(provingCachePrefix, provingCacheId) } - provingCache.attestationCallbacks(provingCache.cacheHash, algorithm.createCertaintyAggregate(null)) + provingCache.attestationCallbacks( + provingCache.cacheHash, + algorithm.createCertaintyAggregate(null) + ) } if (provingCache.hashedChallenges.isEmpty()) { - logger.info("Completed attestation verification") + logger.info("Completed attestation verification.") // TODO: We can most likely directly call pop. if (this.requestCache.has(provingCachePrefix, provingCacheId)) { this.requestCache.pop(provingCachePrefix, provingCacheId) } - provingCache.attestationCallbacks(provingCache.cacheHash, provingCache.relativityMap) + provingCache.attestationCallbacks( + provingCache.cacheHash, + provingCache.relativityMap + ) } else { - // TODO: Add secure random. - val honestyCheck = algorithm.honestCheck && (Random.nextUBytes(1)[0].toInt() < 38) + val honestyCheck = + algorithm.honestCheck && (Random.nextUBytes(1)[0].toInt() < 38) var honestyCheckByte = if (honestyCheck) arrayOf(0, 1, 2).random() else -1 challenge = null if (honestyCheck) { - while (challenge == null || this.requestCache.has(HashCache.idFromHash(PENDING_CHALLENGES_PREFIX, - sha1(challenge))) + while (challenge == null || this.requestCache.has( + HashCache.idFromHash( + PENDING_CHALLENGES_PREFIX, + sha1(challenge) + ) + ) ) { - challenge = algorithm.createHonestyChallenge(provingCache.publicKey!!, honestyCheckByte) + challenge = algorithm.createHonestyChallenge( + provingCache.publicKey!!, + honestyCheckByte + ) } } - if (!honestyCheck || (challenge != null && this.requestCache.has(HashCache.idFromHash( - PENDING_CHALLENGES_PREFIX, - sha1(challenge)))) + if (!honestyCheck || (challenge != null && this.requestCache.has( + HashCache.idFromHash( + PENDING_CHALLENGES_PREFIX, + sha1(challenge) + ) + )) ) { honestyCheckByte = -1 challenge = null for (c in provingCache.challenges) { - if (!this.requestCache.has(HashCache.idFromHash(PENDING_CHALLENGES_PREFIX, - sha1(c))) + if (!this.requestCache.has( + HashCache.idFromHash( + PENDING_CHALLENGES_PREFIX, + sha1(c) + ) + ) ) { challenge = c break } } if (challenge == null) { - logger.info("No more bitpairs to challenge!") + logger.info("No more bit-pairs to challenge!") return } } logger.info("Sending challenge $honestyCheckByte (${provingCache.hashedChallenges.size}).") - this.requestCache.add(PendingChallengesCache(this, - sha1(challenge!!), - provingCache, - cache.idFormat, - honestyCheckByte)) + this.requestCache.add( + PendingChallengesCache( + this, + sha1(challenge!!), + provingCache, + cache.idFormat, + honestyCheckByte + ) + ) val outGoingPayload = ChallengePayload(provingCache.cacheHash, challenge) val packet = serializePacket(CHALLENGE, outGoingPayload, prefix = this.prefix) this.endpoint.send(peer.address, packet) } - } } + } + private fun onRequestAttestationWrapper(packet: Packet) { + val (peer, dist, payload) = packet.getAuthPayloadWithDist(RequestAttestationPayload.Deserializer) + logger.info("Received RequestAttestation from ${peer.mid} for metadata ${payload.metadata}.") + GlobalScope.launch { onRequestAttestation(peer, dist, payload) } } - object MessageId { - const val VERIFY_ATTESTATION_REQUEST = 1 - const val ATTESTATION = 2 - const val CHALLENGE = 3 - const val CHALLENGE_RESPONSE = 4 - const val ATTESTATION_REQUEST = 5; + private fun onChallengeResponseWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(ChallengeResponsePayload.Deserializer) + logger.info( + "Received ChallengeResponse from ${peer.mid} for hash ${payload.challengeHash.toHex()} with response ${ + String(payload.response) + }." + ) + this.onChallengeResponse(peer, payload) + } + + private fun onChallengeWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(ChallengePayload.Deserializer) + logger.info( + "Received Challenge from ${peer.mid} for hash ${payload.attestationHash.toHex()} with challenge ${ + String(payload.challenge) + }." + ) + this.onChallenge(peer, payload) + } + + private fun onAttestationChunkWrapper(packet: Packet) { + val (peer, dist, payload) = packet.getAuthPayloadWithDist(AttestationChunkPayload.Deserializer) + logger.info("Received AttestationChunk from ${peer.mid} with sequence number ${payload.sequenceNumber}, data ${payload.data.size} bytes, hash ${payload.hash.toHex()}.") + this.onAttestationChunk(peer, dist, payload) + } + + private fun onVerifyAttestationRequestWrapper(packet: Packet) { + val (peer, payload) = packet.getAuthPayload(VerifyAttestationRequestPayload.Deserializer) + logger.info("Received VerifyAttestationRequest from ${peer.mid} for hash ${payload.hash.toHex()}.") + GlobalScope.launch { onVerifyAttestationRequest(peer, payload) } + } + + fun setAttestationRequestCallback(f: (peer: Peer, attributeName: String, metaData: String, proposedValue: String?) -> Deferred) { + this.attestationRequestCallback = f + } + + fun setAttestationRequestCompleteCallback(f: (forPeer: Peer, attributeName: String, attestation: WalletAttestation, attributeHash: ByteArray, idFormat: String, fromPeer: Peer?, value: ByteArray?) -> Unit) { + this.attestationRequestCompleteCallback = f + } + + fun setVerifyRequestCallback(f: (attributeName: Peer, attributeHash: ByteArray) -> Deferred) { + this.verifyRequestCallback = f + } + + @Suppress("unused") + fun setAttestationChunkCallback(f: (peer: Peer, sequenceNumber: Int) -> Unit) { + this.attestationChunkCallback = f + } + + fun getIdAlgorithm(idFormat: String): IdentityAlgorithm { + return this.schemaManager.getAlgorithmInstance(idFormat) } class Factory( @@ -615,7 +710,6 @@ class AttestationCommunity(val database: AttestationStore) : Community() { return AttestationCommunity(database) } } - } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationSQLiteStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationSQLiteStore.kt deleted file mode 100644 index 89dda90b..00000000 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationSQLiteStore.kt +++ /dev/null @@ -1,98 +0,0 @@ -package nl.tudelft.ipv8.attestation.wallet - -import mu.KotlinLogging -import nl.tudelft.ipv8.attestation.Authority -import nl.tudelft.ipv8.attestation.WalletAttestation -import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey -import nl.tudelft.ipv8.keyvault.PublicKey -import nl.tudelft.ipv8.keyvault.defaultCryptoProvider -import nl.tudelft.ipv8.sqldelight.Database - - -private val attestationMapper: ( - ByteArray, - ByteArray, - ByteArray, - String, - String?, - ByteArray?, - ByteArray?, -) -> AttestationBlob = { hash, blob, key, id_format, metadata, signature, attestor_key -> - AttestationBlob( - hash, - blob, - key, - id_format, - metadata, - signature, - attestor_key?.let { defaultCryptoProvider.keyFromPublicBin(it) } - ) -} - -private val authorityMapper: ( - ByteArray, - String, -) -> Authority = { public_key, hash -> - Authority(defaultCryptoProvider.keyFromPublicBin(public_key), hash) -} - - -private val logger = KotlinLogging.logger {} - -class AttestationSQLiteStore(database: Database) : AttestationStore { - private val dao = database.dbAttestationQueries - - override fun getAllAttestations(): List { - return dao.getAllAttestations(attestationMapper).executeAsList() - } - - override fun insertAttestation( - attestation: WalletAttestation, - attestationHash: ByteArray, - privateKey: BonehPrivateKey, - idFormat: String, - metadata: String?, - signature: ByteArray?, - attestorKey: PublicKey?, - ) { - val blob = attestation.serializePrivate(privateKey.publicKey()) - logger.info(" *** Inserting to DB: $attestation: [${String(attestationHash)}, ${String(blob)}, ${privateKey.serialize()}, $idFormat]") - dao.insertAttestation(attestationHash, - blob, - privateKey.serialize(), - idFormat, - metadata, - signature, - attestorKey?.keyToBin()) - } - - override fun getAttestationByHash(attestationHash: ByteArray): ByteArray? { - return dao.getAttestationByHash(attestationHash).executeAsOneOrNull() - } - - override fun deleteAttestationByHash(attestationHash: ByteArray) { - return dao.deleteAttestationByHash(attestationHash) - } - - override fun getAllAuthorities(): List { - return dao.getAllAuthorities(authorityMapper).executeAsList() - } - - override fun insertAuthority(publicKey: PublicKey, hash: String) { - val keyBin = publicKey.keyToBin() - dao.insertAuthority(keyBin, hash) - } - - override fun getAuthorityByPublicKey(publicKey: PublicKey): Authority? { - return dao.getAuthorityByPublicKey(publicKey.keyToBin(), authorityMapper).executeAsOneOrNull() - } - - override fun getAuthorityByHash(hash: String): Authority? { - return dao.getAuthorityByHash(hash, authorityMapper).executeAsOneOrNull() - } - - override fun deleteAuthorityByHash(hash: String) { - return dao.deleteAuthorityByHash(hash) - } - -} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationStore.kt deleted file mode 100644 index 993bce9a..00000000 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/AttestationStore.kt +++ /dev/null @@ -1,44 +0,0 @@ -package nl.tudelft.ipv8.attestation.wallet - -import nl.tudelft.ipv8.attestation.Authority -import nl.tudelft.ipv8.attestation.WalletAttestation -import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey -import nl.tudelft.ipv8.keyvault.PublicKey - -class AttestationBlob( - val attestationHash: ByteArray, - val blob: ByteArray, - val key: ByteArray, - val idFormat: String, - val metadata: String?, - val signature: ByteArray?, - val attestorKey: PublicKey?, -) - -interface AttestationStore { - fun getAllAttestations(): List - - fun insertAttestation( - attestation: WalletAttestation, - attestationHash: ByteArray, - privateKey: BonehPrivateKey, - idFormat: String, - metadata: String? = null, - signature: ByteArray? = null, - attestorKey: PublicKey? = null, - ) - - fun getAttestationByHash(attestationHash: ByteArray): ByteArray? - - fun deleteAttestationByHash(attestationHash: ByteArray) - - fun getAllAuthorities(): List - - fun insertAuthority(publicKey: PublicKey, hash: String) - - fun getAuthorityByPublicKey(publicKey: PublicKey): Authority? - - fun getAuthorityByHash(hash: String): Authority? - - fun deleteAuthorityByHash(hash: String) -} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/HashCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/HashCache.kt index fa7ea109..d2eee63c 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/HashCache.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/HashCache.kt @@ -1,7 +1,6 @@ package nl.tudelft.ipv8.attestation.wallet.caches -import nl.tudelft.ipv8.attestation.wallet.RequestCache -import nl.tudelft.ipv8.messaging.deserializeUChar +import nl.tudelft.ipv8.attestation.common.RequestCache import java.math.BigInteger open class HashCache(requestCache: RequestCache, prefix: String, cacheHash: ByteArray, val idFormat: String) : @@ -11,14 +10,12 @@ open class HashCache(requestCache: RequestCache, prefix: String, cacheHash: Byte fun idFromHash(prefix: String, cacheHash: ByteArray): Pair { var number = BigInteger.ZERO for (i in cacheHash.indices) { - // TODO: Verify whether we can simply invoke cacheHash[i].toUByte(). - val b = deserializeUChar(cacheHash.copyOfRange(i, i + 1)) + val b = cacheHash[i].toUByte() number = number shl 8 number = number or BigInteger(b.toString()) } return Pair(prefix, number) } - } } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/NumberCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/NumberCache.kt index 010105dc..704c2341 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/NumberCache.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/NumberCache.kt @@ -1,9 +1,23 @@ package nl.tudelft.ipv8.attestation.wallet.caches -import nl.tudelft.ipv8.attestation.wallet.RequestCache +import kotlinx.coroutines.* +import mu.KotlinLogging +import nl.tudelft.ipv8.attestation.common.RequestCache import java.math.BigInteger -abstract class NumberCache(val requestCache: RequestCache, val prefix: String, val number: BigInteger) { +private val logger = KotlinLogging.logger {} + +const val DEFAULT_TIMEOUT = 180 +const val SECOND_IN_MILLISECONDS = 1000L + +abstract class NumberCache( + val requestCache: RequestCache, + val prefix: String, + val number: BigInteger, + open val timeout: Int = DEFAULT_TIMEOUT, + private val onTimeout: () -> Unit = {}, +) { + private lateinit var timeOutJob: Job init { if (requestCache.has(prefix, number)) { @@ -11,6 +25,29 @@ abstract class NumberCache(val requestCache: RequestCache, val prefix: String, v } } - // TODO: implement futures. + suspend fun start(overWrittenTimeout: Int? = null, calleeCallback: (() -> Unit)? = null) { + try { + val timeoutValue = (overWrittenTimeout ?: timeout) * SECOND_IN_MILLISECONDS + withTimeout(timeoutValue) { + timeOutJob = launch { + while (isActive) { + // Add some delta to ensure the timeout is triggered. + delay(timeoutValue + SECOND_IN_MILLISECONDS) + } + } + } + } catch (e: TimeoutCancellationException) { + logger.warn("Cache $prefix$number timed out") + requestCache.pop(prefix, number) + calleeCallback?.invoke() + onTimeout() + } + } + + fun stop() { + if (this::timeOutJob.isInitialized) { + this.timeOutJob.cancel() + } + } } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PeerCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PeerCache.kt index a0ba6d75..06277166 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PeerCache.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PeerCache.kt @@ -1,10 +1,10 @@ package nl.tudelft.ipv8.attestation.wallet.caches -import nl.tudelft.ipv8.attestation.wallet.RequestCache +import nl.tudelft.ipv8.attestation.common.RequestCache import java.math.BigInteger -open class PeerCache(val cacheHash: RequestCache, prefix: String, val mid: String, val idFormat: String) : - NumberCache(cacheHash, prefix, idFromAddress(prefix, mid).second) { +open class PeerCache(cache: RequestCache, prefix: String, val mid: String, val idFormat: String) : + NumberCache(cache, prefix, idFromAddress(prefix, mid).second) { companion object { fun idFromAddress(prefix: String, mid: String): Pair { diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PendingChallengesCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PendingChallengesCache.kt index 9d402c73..9c77a895 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PendingChallengesCache.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/PendingChallengesCache.kt @@ -7,7 +7,7 @@ const val PENDING_CHALLENGES_PREFIX = "proving-hash" class PendingChallengesCache( community: AttestationCommunity, - val cacheHash: ByteArray, + cacheHash: ByteArray, val provingCache: ProvingAttestationCache, idFormat: String, val honestyCheck: Int = -1, diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/ReceiveAttestationRequestCache.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/ReceiveAttestationRequestCache.kt index 08d71750..4081f21a 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/ReceiveAttestationRequestCache.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/caches/ReceiveAttestationRequestCache.kt @@ -11,7 +11,6 @@ class ReceiveAttestationRequestCache( val privateKey: BonehPrivateKey, val name: String, idFormat: String, - val signature: Boolean = false, ) : PeerCache(community.requestCache, ATTESTATION_REQUEST_PREFIX, mid, idFormat) { val attestationMap: MutableSet> = mutableSetOf() diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/CryptographyConsts.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/CryptographyConsts.kt new file mode 100644 index 00000000..73f1f440 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/CryptographyConsts.kt @@ -0,0 +1,13 @@ +package nl.tudelft.ipv8.attestation.wallet.consts + +object Cryptography { + const val KEY_SIZE = "key_size" + const val ALGORITHM = "algorithm" + const val ATTESTATION = "attestation" + const val MIN = "min" + const val MAX = "max" + const val HASH = "hash" + const val SHA256 = "sha256" + const val SHA256_4 = "sha256_4" + const val SHA512 = "sha512" +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/MetadataConsts.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/MetadataConsts.kt new file mode 100644 index 00000000..e31cb492 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/MetadataConsts.kt @@ -0,0 +1,8 @@ +package nl.tudelft.ipv8.attestation.wallet.consts + +object Metadata { + const val ID_FORMAT = "id_format" + const val ATTRIBUTE = "attribute" + const val PUBLIC_KEY = "public_key" + const val PROPOSED_VALUE = "proposed_value" +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/PayloadConsts.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/PayloadConsts.kt new file mode 100644 index 00000000..074f2751 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/consts/PayloadConsts.kt @@ -0,0 +1,9 @@ +package nl.tudelft.ipv8.attestation.wallet.consts + +object PayloadIds { + const val VERIFY_ATTESTATION_REQUEST = 1 + const val ATTESTATION = 2 + const val CHALLENGE = 3 + const val CHALLENGE_RESPONSE = 4 + const val ATTESTATION_REQUEST = 5 +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/IdentityFormats.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/IdentityAlgorithm.kt similarity index 67% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/IdentityFormats.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/IdentityAlgorithm.kt index 3877f2ae..d8643121 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/IdentityFormats.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/IdentityAlgorithm.kt @@ -1,9 +1,7 @@ -package nl.tudelft.ipv8.attestation +package nl.tudelft.ipv8.attestation.wallet.cryptography import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPublicKey -import nl.tudelft.ipv8.util.sha1 -import java.math.BigDecimal abstract class IdentityAlgorithm(idFormat: String, formats: HashMap>) { var honestCheck = false @@ -52,34 +50,3 @@ abstract class IdentityAlgorithm(idFormat: String, formats: HashMap>) : +class BonehExact( + val idFormat: String, + val formats: HashMap> +) : IdentityAlgorithm(idFormat, formats) { - private val keySize = formats[idFormat]?.get("key_size") as Int + private val keySize = formats[idFormat]?.get(KEY_SIZE) as Int private var attestationFunction: (BonehPublicKey, ByteArray) -> BonehAttestation private var aggregateReference: (ByteArray) -> HashMap @@ -26,30 +36,29 @@ class BonehExactAlgorithm(val idFormat: String, val formats: HashMap 512) { + if (this.keySize < MIN_KEY_SIZE || this.keySize > MAX_KEY_SIZE) { throw RuntimeException("Illegal key size specified!") } - when (val hashMode = format.get("hash")) { - "sha256" -> { + when (val hashMode = format[HASH]) { + SHA256 -> { this.attestationFunction = ::attestSHA256 this.aggregateReference = ::binaryRelativitySHA256 } - "sha256_4" -> { + SHA256_4 -> { this.attestationFunction = ::attestSHA256_4 this.aggregateReference = ::binaryRelativitySHA256_4 } - "sha512" -> { + SHA512 -> { this.attestationFunction = ::attestSHA512 this.aggregateReference = ::binaryRelativitySHA512 } else -> throw RuntimeException("Unknown hashing mode $hashMode") } - } override fun deserialize(serialized: ByteArray, idFormat: String): WalletAttestation { @@ -74,15 +83,22 @@ class BonehExactAlgorithm(val idFormat: String, val formats: HashMap): Double { @Suppress("UNCHECKED_CAST") - return binaryRelativityCertainty(this.aggregateReference(value), aggregate as HashMap) + return binaryRelativityCertainty( + this.aggregateReference(value), + aggregate as HashMap + ) } - override fun createChallenges(publicKey: BonehPublicKey, attestation: WalletAttestation): ArrayList { + override fun createChallenges( + publicKey: BonehPublicKey, + attestation: WalletAttestation + ): ArrayList { attestation as BonehAttestation val challenges = arrayListOf() for (bitPair in attestation.bitPairs) { val challenge = createChallenge(attestation.publicKey, bitPair) - val serialized = serializeVarLen(challenge.a.toByteArray()) + serializeVarLen(challenge.b.toByteArray()) + val serialized = + serializeVarLen(challenge.a.toByteArray()) + serializeVarLen(challenge.b.toByteArray()) challenges.add(serialized) } @@ -107,7 +123,10 @@ class BonehExactAlgorithm(val idFormat: String, val formats: HashMap, deserialized.toInt()) + return internalProcessChallengeResponse( + aggregate as HashMap, + deserialized.toInt() + ) } override fun createCertaintyAggregate(attestation: WalletAttestation?): HashMap { diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/BonehExactKeys.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/BonehExactKeys.kt index 5ceb7474..f97ac5af 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/BonehExactKeys.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/BonehExactKeys.kt @@ -1,8 +1,6 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value -import nl.tudelft.ipv8.messaging.SERIALIZED_UINT_SIZE -import nl.tudelft.ipv8.messaging.deserializeUInt +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value import nl.tudelft.ipv8.messaging.deserializeVarLen import nl.tudelft.ipv8.messaging.serializeVarLen import java.math.BigInteger diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/PrimitiveFunctions.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/PrimitiveFunctions.kt index 37e3c3bb..05e0d245 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/PrimitiveFunctions.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/PrimitiveFunctions.kt @@ -1,7 +1,7 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value -import nl.tudelft.ipv8.attestation.wallet.primitives.weilParing +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.weilParing import java.math.BigInteger import java.security.KeyPairGenerator import java.security.SecureRandom diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BitPairAttestation.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BitPairAttestation.kt index 3c1d4bad..a5d843cf 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BitPairAttestation.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BitPairAttestation.kt @@ -1,6 +1,6 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.attestations -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value import nl.tudelft.ipv8.messaging.deserializeVarLen import nl.tudelft.ipv8.messaging.serializeVarLen import java.math.BigInteger @@ -22,15 +22,15 @@ class BitPairAttestation(private val a: FP2Value, private val b: FP2Value, priva companion object { fun deserialize(serialized: ByteArray, prime: BigInteger): BitPairAttestation { var localOffset = 0 - val nums = arrayListOf() - while (serialized.isNotEmpty() && nums.size < 6) { + val numbers = arrayListOf() + while (serialized.isNotEmpty() && numbers.size < 6) { val unpacked = deserializeVarLen(serialized, localOffset) - nums.add(BigInteger(unpacked.first)) + numbers.add(BigInteger(unpacked.first)) localOffset += unpacked.second } - return BitPairAttestation(FP2Value(prime, nums[0], nums[1]), - FP2Value(prime, nums[2], nums[3]), - FP2Value(prime, nums[4], nums[5])) + return BitPairAttestation(FP2Value(prime, numbers[0], numbers[1]), + FP2Value(prime, numbers[2], numbers[3]), + FP2Value(prime, numbers[4], numbers[5])) } } } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BonehAttestation.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BonehAttestation.kt index f629eab2..d3518fd9 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BonehAttestation.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/attestations/BonehAttestation.kt @@ -1,8 +1,11 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.attestations -import nl.tudelft.ipv8.attestation.WalletAttestation +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPublicKey +import nl.tudelft.ipv8.messaging.SERIALIZED_USHORT_SIZE +import nl.tudelft.ipv8.messaging.deserializeUShort +import nl.tudelft.ipv8.messaging.serializeUShort class BonehAttestation( override val publicKey: BonehPublicKey, @@ -13,7 +16,7 @@ class BonehAttestation( override fun serialize(): ByteArray { var out = byteArrayOf() this.bitPairs.forEach { out += (it.serialize()) } - return this.publicKey.serialize() + out + return this.publicKey.serialize() + serializeUShort(this.bitPairs.size) + out } override fun deserialize(serialized: ByteArray, idFormat: String): WalletAttestation { @@ -25,12 +28,18 @@ class BonehAttestation( val publicKey = BonehPublicKey.deserialize(serialized)!! val bitPairs = arrayListOf() val pkSerialized = publicKey.serialize() - var rem = serialized.copyOfRange(pkSerialized.size, serialized.size) - while (rem.isNotEmpty()) { + + var offset = pkSerialized.size + val amountBitPairs = deserializeUShort(serialized, offset) + offset += SERIALIZED_USHORT_SIZE + + var rem = serialized.copyOfRange(offset, serialized.size) + for (i in 0 until amountBitPairs) { val attest = BitPairAttestation.deserialize(rem, publicKey.p) bitPairs.add(attest) rem = rem.copyOfRange(attest.serialize().size, rem.size) } + return BonehAttestation(publicKey, bitPairs, idFormat) } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/Boudot.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/Boudot.kt deleted file mode 100644 index 4ddda52c..00000000 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/Boudot.kt +++ /dev/null @@ -1,170 +0,0 @@ -package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange - -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value -import nl.tudelft.ipv8.messaging.* -import nl.tudelft.ipv8.util.sha256AsBigInt -import java.math.BigInteger -import java.security.SecureRandom -import kotlin.math.ceil -import kotlin.math.log - -fun secureRandomNumber(min: BigInteger, max: BigInteger): BigInteger { - val normalizedRange = max - min - // TODO: We lose precision due to double conversion. - val n = ceil(log(normalizedRange.toDouble(), 2.toDouble())) - val returnBytes = BigInteger(n.toInt(), SecureRandom()) - val returnValue = min + (returnBytes.mod(normalizedRange)) - return if (returnValue >= min && returnValue < max) returnValue else secureRandomNumber(min, max) -} - - -private fun pack(vararg numbers: BigInteger): ByteArray { - var signByte = 0 - var packed = byteArrayOf() - - for (n in numbers) { - signByte = signByte shl 1 - signByte = signByte or (if (n < BigInteger.ZERO) 1 else 0) - packed = serializeVarLen(if (n < BigInteger.ZERO) (-n).toByteArray() else n.toByteArray()) + packed - } - - return serializeUChar(signByte.toUByte()) + packed -} - -private fun unpack(serialized: ByteArray, amount: Int): Pair, ByteArray> { - val buffer = serialized.copyOfRange(1, serialized.size) - var offset = 0 - val numbers = arrayListOf() - var signByte = deserializeUChar(serialized.copyOfRange(0, 1)).toInt() - - while (offset <= buffer.size && numbers.size < amount) { - val (deserializedValue, localOffset) = deserializeVarLen(buffer, offset) - offset += localOffset - val value = BigInteger(deserializedValue) - val isNegative = ((signByte and 0x01) == 1) - signByte = signByte shr 1 - numbers.add(if (isNegative) -value else value) - } - return Pair(numbers.reversed(), buffer.copyOfRange(offset, buffer.size)) -} - -const val NUM_PARAMS = 4 - -class EL(val c: BigInteger, val d: BigInteger, val d1: BigInteger, val d2: BigInteger) { - - fun check(g1: FP2Value, h1: FP2Value, g2: FP2Value, h2: FP2Value, y1: FP2Value, y2: FP2Value): Boolean { - var cW1 = g1.bigIntPow(this.d) * h1.bigIntPow(this.d1) * y1.bigIntPow(-this.c) - var cW2 = g2.bigIntPow(this.d) * h2.bigIntPow(this.d2) * y2.bigIntPow(-this.c) - cW1 = (cW1.wpNominator() * cW1.wpDenomInverse()).normalize() - cW2 = (cW2.wpNominator() * cW2.wpDenomInverse()).normalize() - - return this.c == (sha256AsBigInt(cW1.a.toString().toByteArray() + cW1.b.toString() - .toByteArray() + cW2.a.toString().toByteArray() + cW2.b.toString().toByteArray())) - } - - fun serialize(): ByteArray { - return pack(this.c, this.d, this.d1, this.d2) - } - - companion object { - fun create( - x: BigInteger, - r1: BigInteger, - r2: BigInteger, - g1: FP2Value, - h1: FP2Value, - g2: FP2Value, - h2: FP2Value, - b: Int, - bitSpace: Int, - t: Int = 80, - l: Int = 40, - ): EL { - val maxRangeW = 2 xor (l + t) * b - 1 - val maxRangeN = BigInteger("2") xor (l + t + bitSpace).toBigInteger() * g1.mod - BigInteger.ONE - val w = secureRandomNumber(BigInteger.ONE, maxRangeW.toBigInteger()) - val n1 = secureRandomNumber(BigInteger.ONE, maxRangeN) - val n2 = secureRandomNumber(BigInteger.ONE, maxRangeN) - val w1 = g1.bigIntPow(w) * h1.bigIntPow(n1) - val w2 = g2.bigIntPow(w) * h2.bigIntPow(n2) - val cW1 = (w1.wpNominator() * w1.wpDenomInverse()).normalize() - val cW2 = (w2.wpNominator() * w2.wpDenomInverse()).normalize() - - val c = - sha256AsBigInt(cW1.a.toString().toByteArray() + cW1.b.toString().toByteArray() + cW2.a.toString() - .toByteArray() + cW2.b.toString().toByteArray()) - - val d = w + c * x - val d1 = n1 + c * r1 - val d2 = n2 + c * r2 - - return EL(c, d, d1, d2) - } - - fun deserialize(serialized: ByteArray): Pair { - val (values, remainder) = unpack(serialized, NUM_PARAMS) - return Pair(EL(values[0], values[1], values[2], values[3]), remainder) - } - } - - override fun equals(other: Any?): Boolean { - if (other !is EL) - return false - return (this.c == other.c && this.d == other.d && this.d1 == other.d1 && this.d2 == other.d2) - } - - override fun hashCode(): Int { - return 6976 - } - - override fun toString(): String { - return "EL<$c,$d,$d1,$d2>" - } -} - -class SQR(val f: FP2Value, val el: EL) { - - fun check(g: FP2Value, h: FP2Value, y: FP2Value): Boolean { - return this.el.check(g, h, this.f, h, this.f, y) - } - - fun serialize(): ByteArray { - val minF = this.f.wpCompress() - return serializeVarLen(minF.mod.toByteArray()) + serializeVarLen(minF.a.toByteArray()) + serializeVarLen(minF.b.toByteArray()) + serializeVarLen( - this.el.serialize()) - } - - companion object { - fun create(x: BigInteger, r1: BigInteger, g: FP2Value, h: FP2Value, b: Int, bitSpace: Int): SQR { - val r2 = secureRandomNumber(-BigInteger("2") xor bitSpace.toBigInteger() * g.mod + BigInteger.ONE, - BigInteger("2") xor bitSpace.toBigInteger() * g.mod - BigInteger.ONE) - val f = g.bigIntPow(x) * h.bigIntPow(r2) - val r3 = r1 - r2 * x - return SQR(f, EL.create(x, r2, r3, g, h, f, h, b, bitSpace)) - } - - fun deserialize(serialized: ByteArray): Pair { - val (deserialized, rem) = deserializeAmount(serialized, NUM_PARAMS) - val mod = BigInteger(deserialized[0]) - val a = BigInteger(deserialized[1]) - val b = BigInteger(deserialized[2]) - val (el, _) = EL.deserialize(deserialized[3]) - return Pair(SQR(FP2Value(mod, a, b), el), rem) - - } - } - - override fun equals(other: Any?): Boolean { - if (other !is SQR) - return false - return (this.f == other.f && this.el == other.el) - } - - override fun hashCode(): Int { - return 838182 - } - - override fun toString(): String { - return "SQR<$f,$el>" - } -} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/PengBao.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/PengBao.kt index aabc8378..de601b98 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/PengBao.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/PengBao.kt @@ -1,10 +1,17 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange -import nl.tudelft.ipv8.attestation.IdentityAlgorithm -import nl.tudelft.ipv8.attestation.WalletAttestation +import nl.tudelft.ipv8.attestation.common.consts.AlgorithmNames.PENG_BAO_RANGE +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.ALGORITHM +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.ATTESTATION +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.KEY_SIZE +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.MAX +import nl.tudelft.ipv8.attestation.wallet.consts.Cryptography.MIN +import nl.tudelft.ipv8.attestation.wallet.cryptography.IdentityAlgorithm +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPublicKey import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.generateKeypair +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.attestations.PengBaoAttestation import nl.tudelft.ipv8.messaging.deserializeBool import nl.tudelft.ipv8.messaging.deserializeRecursively import nl.tudelft.ipv8.messaging.serializeVarLen @@ -14,22 +21,10 @@ import java.math.BigInteger import java.security.SecureRandom const val LARGE_INTEGER = 32765 +const val MIN_KEY_SIZE = 32 +const val MAX_KEY_SIZE = 512 -private fun safeRandomNumber(keySize: Int, mod: BigInteger): BigInteger { - val random = SecureRandom() - val largeBigInteger = LARGE_INTEGER.toBigInteger() - fun randomNumber(): BigInteger { - return BigInteger(keySize, random).mod(mod) - } - - var out = randomNumber() - while (out < largeBigInteger) { - out = randomNumber() - } - return out -} - -class Pengbaorange(idFormat: String, formats: HashMap>) : +class PengBaoRange(idFormat: String, formats: HashMap>) : IdentityAlgorithm(idFormat, formats) { private val keySize: Int @@ -37,22 +32,22 @@ class Pengbaorange(idFormat: String, formats: HashMap 512) { + if (this.keySize < MIN_KEY_SIZE || this.keySize > MAX_KEY_SIZE) { throw RuntimeException("Illegal key size specified.") } - a = format.get("min") as Int - b = format.get("max") as Int + a = format[MIN] as Int + b = format[MAX] as Int } override fun generateSecretKey(): BonehPrivateKey { @@ -69,7 +64,13 @@ class Pengbaorange(idFormat: String, formats: HashMap): Double { @@ -83,11 +84,16 @@ class Pengbaorange(idFormat: String, formats: HashMap { + override fun createChallenges( + publicKey: BonehPublicKey, + attestation: WalletAttestation + ): ArrayList { val mod = publicKey.g.mod - BigInteger.ONE - return arrayListOf(serializeVarLen(safeRandomNumber(this.keySize, mod).toByteArray()) + serializeVarLen( - safeRandomNumber(this.keySize, mod).toByteArray())) - + return arrayListOf( + serializeVarLen(safeRandomNumber(this.keySize, mod).toByteArray()) + serializeVarLen( + safeRandomNumber(this.keySize, mod).toByteArray() + ) + ) } override fun createChallengeResponse( @@ -115,10 +121,9 @@ class Pengbaorange(idFormat: String, formats: HashMap { @Suppress("UNCHECKED_CAST") - return hashMapOf("attestation" to (attestation as PengBaoAttestation)) as HashMap + return hashMapOf(ATTESTATION to (attestation as PengBaoAttestation)) as HashMap } - override fun processChallengeResponse( aggregate: HashMap, challenge: ByteArray?, @@ -127,12 +132,12 @@ class Pengbaorange(idFormat: String, formats: HashMap { - val (deserialized, rem) = deserializeAmount(serialized, 2) - val a = BigInteger(deserialized[0]) - val b = BigInteger(deserialized[1]) - return Pair(FP2Value(mod, a, b), rem) -} - -const val PENG_BAO_COMMITMENT_NUM_PARAMS = 8 -const val PENG_BAO_PRIVATE_COMMITMENT_NUM_PARAMS = 6 - -class PengBaoCommitment( - val c: FP2Value, - val c1: FP2Value, - val c2: FP2Value, - val ca: FP2Value, - val ca1: FP2Value, - val ca2: FP2Value, - val ca3: FP2Value, - val caa: FP2Value, -) { - - fun serialize(): ByteArray { - return ( - serializeVarLen(this.c.mod.toByteArray()) + serializeFP2Value(this.c) + serializeFP2Value(this.c1) - + serializeFP2Value(this.c2) + serializeFP2Value(this.ca) + serializeFP2Value(this.ca1) - + serializeFP2Value(this.ca2) + serializeFP2Value(this.ca3) + serializeFP2Value(this.caa) - ) - } - - companion object { - fun deserialize(serialized: ByteArray): Pair { - val (deserializedMod, localOffset) = deserializeVarLen(serialized) - val mod = BigInteger(deserializedMod) - - var buffer = serialized.copyOfRange(localOffset, serialized.size) - val params = arrayListOf() - for (i in 0 until PENG_BAO_COMMITMENT_NUM_PARAMS) { - val (param, rem) = deserializeFP2Value(mod, buffer) - params.add(param) - buffer = rem - } - - return Pair(PengBaoCommitment(params[0], - params[1], - params[2], - params[3], - params[4], - params[5], - params[6], - params[7]), buffer) - - } - } -} - -class PengBaoCommitmentPrivate( - val m1: BigInteger, - val m2: BigInteger, - val m3: BigInteger, - val r1: BigInteger, - val r2: BigInteger, - val r3: BigInteger, -) { - - - fun generateResponse(s: BigInteger, t: BigInteger): List { - return listOf(s * this.m1 + this.m2 + this.m3, - this.m1 + t * this.m2 + this.m3, - s * this.r1 + this.r2 + this.r3, - this.r1 + t * this.r2 + this.r3) - } - - fun serialize(): ByteArray { - return serializeVarLen(this.m1.toByteArray()) + serializeVarLen(this.m2.toByteArray()) + serializeVarLen(this.m3.toByteArray()) + serializeVarLen( - this.r1.toByteArray()) + serializeVarLen(this.r2.toByteArray()) + serializeVarLen(this.r3.toByteArray()) - } - - fun encode(publicKey: BonehPublicKey): ByteArray { - val serialized = this.serialize() - val hexSerialized = serialized.toHex() - var serializedEncodings = serializeUChar((hexSerialized.length / 2).toUByte()) - for (i in hexSerialized.indices step 2) { - val intValue = Integer.parseInt(hexSerialized.substring(i, i + 2), 16) - serializedEncodings += serializeFP2Value(encode(publicKey, intValue.toBigInteger())) - } - return serializedEncodings - } - - companion object { - val MSG_SPACE = (0 until 256).toList().toTypedArray() - - fun deserialize(serialized: ByteArray): Pair { - val (values, rem) = deserializeAmount(serialized, PENG_BAO_PRIVATE_COMMITMENT_NUM_PARAMS) - val m1 = BigInteger(values[0]) - val m2 = BigInteger(values[1]) - val m3 = BigInteger(values[2]) - val r1 = BigInteger(values[3]) - val r2 = BigInteger(values[4]) - val r3 = BigInteger(values[5]) - - return Pair(PengBaoCommitmentPrivate(m1, m2, m3, r1, r2, r3), rem) - } - - fun decode(privateKey: BonehPrivateKey, serialized: ByteArray): PengBaoCommitmentPrivate { - var serialization = byteArrayOf() - val length = deserializeUChar(serialized.copyOfRange(0, 1)).toInt() - var rem = serialized.copyOfRange(1, serialized.size) - for (i in 0 until length) { - val (deserialized, localRem) = deserializeFP2Value(privateKey.g.mod, rem) - rem = localRem - var hexedRaw = decode(privateKey, MSG_SPACE, deserialized)!! - var hexed = hexedRaw.toString(16) - if (hexed.endsWith("L", true)) { - hexed = hexed.substring(0, hexed.lastIndex) - } - if (hexed.length % 2 == 1) { - hexed = "0$hexed" - } - serialization += hexed.hexToBytes() - } - return deserialize(serialization).first - } - } - -} - -class PengBaoPublicData( - val publicKey: BonehPublicKey, - val bitSpace: Int, - val commitment: PengBaoCommitment, - val el: EL, - val sqr1: SQR, - val sqr2: SQR, -) { - - fun check( - a: Int, - b: Int, - s: BigInteger, - t: BigInteger, - x: BigInteger, - y: BigInteger, - u: BigInteger, - v: BigInteger, - ): Boolean { - var out = this.el.check(this.publicKey.g, - this.publicKey.h, - this.commitment.c1, - this.publicKey.h, - this.commitment.c2, - this.commitment.ca) - out = out && this.sqr1.check(this.commitment.ca, this.publicKey.h, this.commitment.caa) - out = out && this.sqr2.check(this.publicKey.g, this.publicKey.h, this.commitment.ca3) - out = - out && this.commitment.c1 == this.commitment.c / this.publicKey.g.bigIntPow(a.toBigInteger() - BigInteger.ONE) - out = - out && this.commitment.c2 == this.publicKey.g.bigIntPow(b.toBigInteger() + BigInteger.ONE) / this.commitment.c - out = out && this.commitment.caa == this.commitment.ca1 * this.commitment.ca2 * this.commitment.ca3 - out = out && (this.publicKey.g.bigIntPow(x) * this.publicKey.h.bigIntPow(u) - == (this.commitment.ca1.bigIntPow(s) * this.commitment.ca2 * this.commitment.ca3)) - out = out && ((this.publicKey.g.bigIntPow(y) * this.publicKey.h.bigIntPow(v)) - == (this.commitment.ca1 * this.commitment.ca2.bigIntPow(t) * this.commitment.ca3)) - - return out && x > BigInteger.ZERO && y > BigInteger.ZERO - } - - fun serialize(): ByteArray { - return (this.publicKey.serialize() + serializeUChar(this.bitSpace.toUByte()) + this.commitment.serialize() - + this.el.serialize() + this.sqr1.serialize() + this.sqr2.serialize()) - } - - companion object { - fun deserialize(serialized: ByteArray): Pair { - val publicKey = BonehPublicKey.deserialize(serialized)!! - var rem = serialized.copyOfRange(publicKey.serialize().size, serialized.size) - val bitSpace = deserializeUChar(rem.copyOfRange(0, 1)).toInt() - rem = rem.copyOfRange(1, rem.size) - val (commitment, localRem) = PengBaoCommitment.deserialize(rem) - rem = localRem - val (el, localRem2) = EL.deserialize(rem) - rem = localRem2 - val (sqr1, localRem3) = SQR.deserialize(rem) - rem = localRem3 - val (sqr2, localRem4) = SQR.deserialize(rem) - rem = localRem4 - - return Pair(PengBaoPublicData(publicKey, bitSpace, commitment, el, sqr1, sqr2), rem) - } - } -} - -class PengBaoAttestation( - val publicData: PengBaoPublicData, - val privateData: PengBaoCommitmentPrivate?, - override val idFormat: String? = null, -) : WalletAttestation() { - - override val publicKey = publicData.publicKey - - override fun serialize(): ByteArray { - return this.publicData.serialize() - } - - override fun deserialize(serialized: ByteArray, idFormat: String): WalletAttestation { - return PengBaoAttestation.deserialize(serialized, idFormat) - } - - override fun serializePrivate(publicKey: BonehPublicKey): ByteArray { - return if (this.privateData != null) { - val publicData = this.publicData.serialize() - val privateData = this.privateData.encode(publicKey) - publicData + privateData - } else { - throw RuntimeException("Private data was null.") - } - } - - override fun deserializePrivate( - privateKey: BonehPrivateKey, - serialized: ByteArray, - idFormat: String?, - ): WalletAttestation { - return PengBaoAttestation.deserializePrivate(privateKey, serialized, idFormat) - } - - companion object { - fun deserialize(serialized: ByteArray, idFormat: String? = null): PengBaoAttestation { - val (pubicData, _) = PengBaoPublicData.deserialize(serialized) - return PengBaoAttestation(pubicData, null, idFormat) - } - - fun deserializePrivate( - privateKey: BonehPrivateKey, - serialized: ByteArray, - idFormat: String? = null, - ): PengBaoAttestation { - val (publicData, rem) = PengBaoPublicData.deserialize(serialized) - val privateData = PengBaoCommitmentPrivate.decode(privateKey, rem) - - return PengBaoAttestation(publicData, privateData, idFormat) - } - } -} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/attestations/PengBaoAttestation.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/attestations/PengBaoAttestation.kt new file mode 100644 index 00000000..82e7ed88 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/attestations/PengBaoAttestation.kt @@ -0,0 +1,60 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.attestations + +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPublicKey +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.commitments.PengBaoPrivateData +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.commitments.PengBaoPublicData + +class PengBaoAttestation( + val publicData: PengBaoPublicData, + val privateData: PengBaoPrivateData?, + override val idFormat: String? = null, +) : WalletAttestation() { + + override val publicKey = publicData.publicKey + + override fun serialize(): ByteArray { + return this.publicData.serialize() + } + + override fun deserialize(serialized: ByteArray, idFormat: String): WalletAttestation { + return PengBaoAttestation.deserialize(serialized, idFormat) + } + + override fun serializePrivate(publicKey: BonehPublicKey): ByteArray { + return if (this.privateData != null) { + val publicData = this.publicData.serialize() + val privateData = this.privateData.encode(publicKey) + publicData + privateData + } else { + throw RuntimeException("Private data was null.") + } + } + + override fun deserializePrivate( + privateKey: BonehPrivateKey, + serialized: ByteArray, + idFormat: String?, + ): WalletAttestation { + return PengBaoAttestation.deserializePrivate(privateKey, serialized, idFormat) + } + + companion object { + fun deserialize(serialized: ByteArray, idFormat: String? = null): PengBaoAttestation { + val (pubicData, _) = PengBaoPublicData.deserialize(serialized) + return PengBaoAttestation(pubicData, null, idFormat) + } + + fun deserializePrivate( + privateKey: BonehPrivateKey, + serialized: ByteArray, + idFormat: String? = null, + ): PengBaoAttestation { + val (publicData, rem) = PengBaoPublicData.deserialize(serialized) + val privateData = PengBaoPrivateData.decode(privateKey, rem) + + return PengBaoAttestation(publicData, privateData, idFormat) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/Util.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/Util.kt new file mode 100644 index 00000000..d736cf02 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/Util.kt @@ -0,0 +1,48 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot + +import nl.tudelft.ipv8.messaging.* +import java.math.BigInteger +import java.security.SecureRandom +import kotlin.math.ceil +import kotlin.math.log + +const val NUM_PARAMS = 4 + +fun secureRandomNumber(min: BigInteger, max: BigInteger): BigInteger { + val normalizedRange = max - min + // TODO: We lose precision due to double conversion. + val n = ceil(log(normalizedRange.toDouble(), 2.toDouble())) + val returnBytes = BigInteger(n.toInt(), SecureRandom()) + val returnValue = min + (returnBytes.mod(normalizedRange)) + return if (returnValue >= min && returnValue < max) returnValue else secureRandomNumber(min, max) +} + +fun pack(vararg numbers: BigInteger): ByteArray { + var signByte = 0 + var packed = byteArrayOf() + + for (n in numbers) { + signByte = signByte shl 1 + signByte = signByte or (if (n < BigInteger.ZERO) 1 else 0) + packed = serializeVarLen(if (n < BigInteger.ZERO) (-n).toByteArray() else n.toByteArray()) + packed + } + + return serializeUChar(signByte.toUByte()) + packed +} + +fun unpack(serialized: ByteArray, amount: Int): Pair, ByteArray> { + val buffer = serialized.copyOfRange(1, serialized.size) + var offset = 0 + val numbers = arrayListOf() + var signByte = deserializeUChar(serialized.copyOfRange(0, 1)).toInt() + + while (offset <= buffer.size && numbers.size < amount) { + val (deserializedValue, localOffset) = deserializeVarLen(buffer, offset) + offset += localOffset + val value = BigInteger(deserializedValue) + val isNegative = ((signByte and 0x01) == 1) + signByte = signByte shr 1 + numbers.add(if (isNegative) -value else value) + } + return Pair(numbers.reversed(), buffer.copyOfRange(offset, buffer.size)) +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/primitives/EL.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/primitives/EL.kt new file mode 100644 index 00000000..9bb890f9 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/primitives/EL.kt @@ -0,0 +1,85 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.primitives + +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.NUM_PARAMS +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.pack +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.secureRandomNumber +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.unpack +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value +import nl.tudelft.ipv8.util.sha256AsBigInt +import java.math.BigInteger + +class EL(val c: BigInteger, val d: BigInteger, val d1: BigInteger, val d2: BigInteger) { + + fun check(g1: FP2Value, h1: FP2Value, g2: FP2Value, h2: FP2Value, y1: FP2Value, y2: FP2Value): Boolean { + var cW1 = g1.bigIntPow(this.d) * h1.bigIntPow(this.d1) * y1.bigIntPow(-this.c) + var cW2 = g2.bigIntPow(this.d) * h2.bigIntPow(this.d2) * y2.bigIntPow(-this.c) + cW1 = (cW1.wpNominator() * cW1.wpDenomInverse()).normalize() + cW2 = (cW2.wpNominator() * cW2.wpDenomInverse()).normalize() + + return this.c == (sha256AsBigInt( + cW1.a.toString().toByteArray() + cW1.b.toString() + .toByteArray() + cW2.a.toString().toByteArray() + cW2.b.toString().toByteArray() + )) + } + + fun serialize(): ByteArray { + return pack(this.c, this.d, this.d1, this.d2) + } + + companion object { + fun create( + x: BigInteger, + r1: BigInteger, + r2: BigInteger, + g1: FP2Value, + h1: FP2Value, + g2: FP2Value, + h2: FP2Value, + b: Int, + bitSpace: Int, + t: Int = 80, + l: Int = 40, + ): EL { + val maxRangeW = 2 xor (l + t) * b - 1 + val maxRangeN = BigInteger("2") xor (l + t + bitSpace).toBigInteger() * g1.mod - BigInteger.ONE + val w = secureRandomNumber(BigInteger.ONE, maxRangeW.toBigInteger()) + val n1 = secureRandomNumber(BigInteger.ONE, maxRangeN) + val n2 = secureRandomNumber(BigInteger.ONE, maxRangeN) + val w1 = g1.bigIntPow(w) * h1.bigIntPow(n1) + val w2 = g2.bigIntPow(w) * h2.bigIntPow(n2) + val cW1 = (w1.wpNominator() * w1.wpDenomInverse()).normalize() + val cW2 = (w2.wpNominator() * w2.wpDenomInverse()).normalize() + + val c = + sha256AsBigInt( + cW1.a.toString().toByteArray() + cW1.b.toString().toByteArray() + cW2.a.toString() + .toByteArray() + cW2.b.toString().toByteArray() + ) + + val d = w + c * x + val d1 = n1 + c * r1 + val d2 = n2 + c * r2 + + return EL(c, d, d1, d2) + } + + fun deserialize(serialized: ByteArray): Pair { + val (values, remainder) = unpack(serialized, NUM_PARAMS) + return Pair(EL(values[0], values[1], values[2], values[3]), remainder) + } + } + + override fun equals(other: Any?): Boolean { + if (other !is EL) + return false + return (this.c == other.c && this.d == other.d && this.d1 == other.d1 && this.d2 == other.d2) + } + + override fun hashCode(): Int { + return 6976 + } + + override fun toString(): String { + return "EL<$c,$d,$d1,$d2>" + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/primitives/SQR.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/primitives/SQR.kt new file mode 100644 index 00000000..d5c4a247 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/boudot/primitives/SQR.kt @@ -0,0 +1,58 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.primitives + +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.NUM_PARAMS +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.secureRandomNumber +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value +import nl.tudelft.ipv8.messaging.deserializeAmount +import nl.tudelft.ipv8.messaging.serializeVarLen +import java.math.BigInteger + +class SQR(val f: FP2Value, val el: EL) { + + fun check(g: FP2Value, h: FP2Value, y: FP2Value): Boolean { + return this.el.check(g, h, this.f, h, this.f, y) + } + + fun serialize(): ByteArray { + val minF = this.f.wpCompress() + return serializeVarLen(minF.mod.toByteArray()) + serializeVarLen(minF.a.toByteArray()) + serializeVarLen(minF.b.toByteArray()) + serializeVarLen( + this.el.serialize() + ) + } + + companion object { + + fun create(x: BigInteger, r1: BigInteger, g: FP2Value, h: FP2Value, b: Int, bitSpace: Int): SQR { + val r2 = secureRandomNumber( + -BigInteger("2") xor bitSpace.toBigInteger() * g.mod + BigInteger.ONE, + BigInteger("2") xor bitSpace.toBigInteger() * g.mod - BigInteger.ONE + ) + val f = g.bigIntPow(x) * h.bigIntPow(r2) + val r3 = r1 - r2 * x + return SQR(f, EL.create(x, r2, r3, g, h, f, h, b, bitSpace)) + } + + fun deserialize(serialized: ByteArray): Pair { + val (deserialized, rem) = deserializeAmount(serialized, NUM_PARAMS) + val mod = BigInteger(deserialized[0]) + val a = BigInteger(deserialized[1]) + val b = BigInteger(deserialized[2]) + val (el, _) = EL.deserialize(deserialized[3]) + return Pair(SQR(FP2Value(mod, a, b), el), rem) + } + } + + override fun equals(other: Any?): Boolean { + if (other !is SQR) + return false + return (this.f == other.f && this.el == other.el) + } + + override fun hashCode(): Int { + return 838182 + } + + override fun toString(): String { + return "SQR<$f,$el>" + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PengBaoCommitment.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PengBaoCommitment.kt new file mode 100644 index 00000000..41e3928a --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PengBaoCommitment.kt @@ -0,0 +1,57 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.commitments + +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.util.PENG_BAO_COMMITMENT_NUM_PARAMS +import nl.tudelft.ipv8.attestation.wallet.cryptography.util.deserializeFP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.util.serializeFP2Value +import nl.tudelft.ipv8.messaging.deserializeVarLen +import nl.tudelft.ipv8.messaging.serializeVarLen +import java.math.BigInteger + +class PengBaoCommitment( + val c: FP2Value, + val c1: FP2Value, + val c2: FP2Value, + val ca: FP2Value, + val ca1: FP2Value, + val ca2: FP2Value, + val ca3: FP2Value, + val caa: FP2Value, +) { + + fun serialize(): ByteArray { + return ( + serializeVarLen(this.c.mod.toByteArray()) + serializeFP2Value(this.c) + serializeFP2Value(this.c1) + + serializeFP2Value(this.c2) + serializeFP2Value(this.ca) + serializeFP2Value(this.ca1) + + serializeFP2Value(this.ca2) + serializeFP2Value(this.ca3) + serializeFP2Value(this.caa) + ) + } + + companion object { + fun deserialize(serialized: ByteArray): Pair { + val (deserializedMod, localOffset) = deserializeVarLen(serialized) + val mod = BigInteger(deserializedMod) + + var buffer = serialized.copyOfRange(localOffset, serialized.size) + val params = arrayListOf() + for (i in 0 until PENG_BAO_COMMITMENT_NUM_PARAMS) { + val (param, rem) = deserializeFP2Value(mod, buffer) + params.add(param) + buffer = rem + } + + return Pair( + PengBaoCommitment( + params[0], + params[1], + params[2], + params[3], + params[4], + params[5], + params[6], + params[7] + ), buffer + ) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PrivatePengBaoCommitment.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PrivatePengBaoCommitment.kt new file mode 100644 index 00000000..5212da37 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PrivatePengBaoCommitment.kt @@ -0,0 +1,99 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.commitments + +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPublicKey +import nl.tudelft.ipv8.attestation.wallet.cryptography.util.PENG_BAO_PRIVATE_COMMITMENT_NUM_PARAMS +import nl.tudelft.ipv8.attestation.wallet.cryptography.util.deserializeFP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.util.serializeFP2Value +import nl.tudelft.ipv8.messaging.deserializeAmount +import nl.tudelft.ipv8.messaging.deserializeUChar +import nl.tudelft.ipv8.messaging.serializeUChar +import nl.tudelft.ipv8.messaging.serializeVarLen +import nl.tudelft.ipv8.util.hexToBytes +import nl.tudelft.ipv8.util.toHex +import java.math.BigInteger +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.decode as bonehExactDecode + +class PengBaoPrivateData( + val m1: BigInteger, + val m2: BigInteger, + val m3: BigInteger, + val r1: BigInteger, + val r2: BigInteger, + val r3: BigInteger, +) { + + fun generateResponse(s: BigInteger, t: BigInteger): List { + return listOf( + s * this.m1 + this.m2 + this.m3, + this.m1 + t * this.m2 + this.m3, + s * this.r1 + this.r2 + this.r3, + this.r1 + t * this.r2 + this.r3 + ) + } + + fun serialize(): ByteArray { + return serializeVarLen(this.m1.toByteArray()) + serializeVarLen(this.m2.toByteArray()) + + serializeVarLen(this.m3.toByteArray()) + serializeVarLen(this.r1.toByteArray()) + + serializeVarLen(this.r2.toByteArray()) + serializeVarLen(this.r3.toByteArray()) + } + + fun encode(publicKey: BonehPublicKey): ByteArray { + val serialized = this.serialize() + val hexSerialized = serialized.toHex() + var serializedEncodings = serializeUChar((hexSerialized.length / 2).toUByte()) + for (i in hexSerialized.indices step 2) { + val intValue = Integer.parseInt(hexSerialized.substring(i, i + 2), 16) + serializedEncodings += serializeFP2Value( + nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.encode( + publicKey, + intValue.toBigInteger() + ) + ) + } + return serializedEncodings + } + + companion object { + val MSG_SPACE = (0 until 256).toList().toTypedArray() + + fun deserialize(serialized: ByteArray): Pair { + val (values, rem) = deserializeAmount( + serialized, + PENG_BAO_PRIVATE_COMMITMENT_NUM_PARAMS + ) + val m1 = BigInteger(values[0]) + val m2 = BigInteger(values[1]) + val m3 = BigInteger(values[2]) + val r1 = BigInteger(values[3]) + val r2 = BigInteger(values[4]) + val r3 = BigInteger(values[5]) + + return Pair(PengBaoPrivateData(m1, m2, m3, r1, r2, r3), rem) + } + + fun decode(privateKey: BonehPrivateKey, serialized: ByteArray): PengBaoPrivateData { + var serialization = byteArrayOf() + val length = deserializeUChar(serialized.copyOfRange(0, 1)).toInt() + var rem = serialized.copyOfRange(1, serialized.size) + for (i in 0 until length) { + val (deserialized, localRem) = deserializeFP2Value(privateKey.g.mod, rem) + rem = localRem + val hexedRaw = bonehExactDecode( + privateKey, + MSG_SPACE, + deserialized + )!! + var hexed = hexedRaw.toString(16) + if (hexed.endsWith("L", true)) { + hexed = hexed.substring(0, hexed.lastIndex) + } + if (hexed.length % 2 == 1) { + hexed = "0$hexed" + } + serialization += hexed.hexToBytes() + } + return deserialize(serialization).first + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PublicPengBaoCommitment.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PublicPengBaoCommitment.kt new file mode 100644 index 00000000..3af21b81 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/commitments/PublicPengBaoCommitment.kt @@ -0,0 +1,75 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.commitments + +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPublicKey +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.primitives.EL +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.primitives.SQR +import nl.tudelft.ipv8.messaging.deserializeUChar +import nl.tudelft.ipv8.messaging.serializeUChar +import java.math.BigInteger + +class PengBaoPublicData( + val publicKey: BonehPublicKey, + val bitSpace: Int, + val commitment: PengBaoCommitment, + val el: EL, + val sqr1: SQR, + val sqr2: SQR, +) { + + fun check( + a: Int, + b: Int, + s: BigInteger, + t: BigInteger, + x: BigInteger, + y: BigInteger, + u: BigInteger, + v: BigInteger, + ): Boolean { + var out = this.el.check( + this.publicKey.g, + this.publicKey.h, + this.commitment.c1, + this.publicKey.h, + this.commitment.c2, + this.commitment.ca + ) + out = out && this.sqr1.check(this.commitment.ca, this.publicKey.h, this.commitment.caa) + out = out && this.sqr2.check(this.publicKey.g, this.publicKey.h, this.commitment.ca3) + out = + out && this.commitment.c1 == this.commitment.c / this.publicKey.g.bigIntPow(a.toBigInteger() - BigInteger.ONE) + out = + out && this.commitment.c2 == this.publicKey.g.bigIntPow(b.toBigInteger() + BigInteger.ONE) / this.commitment.c + out = out && this.commitment.caa == this.commitment.ca1 * this.commitment.ca2 * this.commitment.ca3 + out = out && (this.publicKey.g.bigIntPow(x) * this.publicKey.h.bigIntPow(u) + == (this.commitment.ca1.bigIntPow(s) * this.commitment.ca2 * this.commitment.ca3)) + out = out && ((this.publicKey.g.bigIntPow(y) * this.publicKey.h.bigIntPow(v)) + == (this.commitment.ca1 * this.commitment.ca2.bigIntPow(t) * this.commitment.ca3)) + + return out && x > BigInteger.ZERO && y > BigInteger.ZERO + } + + fun serialize(): ByteArray { + return (this.publicKey.serialize() + serializeUChar(this.bitSpace.toUByte()) + this.commitment.serialize() + + this.el.serialize() + this.sqr1.serialize() + this.sqr2.serialize()) + } + + companion object { + fun deserialize(serialized: ByteArray): Pair { + val publicKey = BonehPublicKey.deserialize(serialized)!! + var rem = serialized.copyOfRange(publicKey.serialize().size, serialized.size) + val bitSpace = deserializeUChar(rem.copyOfRange(0, 1)).toInt() + rem = rem.copyOfRange(1, rem.size) + val (commitment, localRem) = PengBaoCommitment.deserialize(rem) + rem = localRem + val (el, localRem2) = EL.deserialize(rem) + rem = localRem2 + val (sqr1, localRem3) = SQR.deserialize(rem) + rem = localRem3 + val (sqr2, localRem4) = SQR.deserialize(rem) + rem = localRem4 + + return Pair(PengBaoPublicData(publicKey, bitSpace, commitment, el, sqr1, sqr2), rem) + } + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/primitives/EC.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/EC.kt similarity index 98% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/primitives/EC.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/EC.kt index ea27a39e..cabb53cc 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/primitives/EC.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/EC.kt @@ -1,4 +1,4 @@ -package nl.tudelft.ipv8.attestation.wallet.primitives +package nl.tudelft.ipv8.attestation.wallet.cryptography.primitives import java.math.BigInteger diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/primitives/FP2Value.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/FP2Value.kt similarity index 99% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/primitives/FP2Value.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/FP2Value.kt index e7749dd2..a4abb701 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/primitives/FP2Value.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/FP2Value.kt @@ -1,4 +1,4 @@ -package nl.tudelft.ipv8.attestation.wallet.primitives +package nl.tudelft.ipv8.attestation.wallet.cryptography.primitives import java.math.BigInteger import kotlin.RuntimeException diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/AttestationHelperFunctions.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/util/AttestationHelperFunctions.kt similarity index 98% rename from ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/AttestationHelperFunctions.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/util/AttestationHelperFunctions.kt index f7982b5b..604c79c2 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/AttestationHelperFunctions.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/util/AttestationHelperFunctions.kt @@ -6,13 +6,11 @@ import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.attestations.B import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.attestations.BonehAttestation import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.decode import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.encode -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value import nl.tudelft.ipv8.util.sha256AsBigInt import nl.tudelft.ipv8.util.sha256_4_AsBigInt import nl.tudelft.ipv8.util.sha512AsBigInt -import java.math.BigDecimal import java.math.BigInteger -import java.math.RoundingMode import java.security.SecureRandom import java.util.* import kotlin.collections.ArrayList diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/util/Serialization.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/util/Serialization.kt new file mode 100644 index 00000000..8af1671f --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/cryptography/util/Serialization.kt @@ -0,0 +1,21 @@ +package nl.tudelft.ipv8.attestation.wallet.cryptography.util + +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value +import nl.tudelft.ipv8.messaging.deserializeAmount +import nl.tudelft.ipv8.messaging.serializeVarLen +import java.math.BigInteger + +const val PENG_BAO_COMMITMENT_NUM_PARAMS = 8 +const val PENG_BAO_PRIVATE_COMMITMENT_NUM_PARAMS = 6 + +fun serializeFP2Value(value: FP2Value): ByteArray { + val compressed = value.wpCompress() + return serializeVarLen(compressed.a.toByteArray()) + serializeVarLen(compressed.b.toByteArray()) +} + +fun deserializeFP2Value(mod: BigInteger, serialized: ByteArray): Pair { + val (deserialized, rem) = deserializeAmount(serialized, 2) + val a = BigInteger(deserialized[0]) + val b = BigInteger(deserialized[1]) + return Pair(FP2Value(mod, a, b), rem) +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/payloads/AttestationChunkPayload.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/payloads/AttestationChunkPayload.kt index bc6c4d7a..f9405d24 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/payloads/AttestationChunkPayload.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/payloads/AttestationChunkPayload.kt @@ -6,19 +6,10 @@ class AttestationChunkPayload( val hash: ByteArray, val sequenceNumber: Int, val data: ByteArray, - val metadata: ByteArray? = null, - val signature: ByteArray? = null, ) : Serializable { - private val msgId = 2 override fun serialize(): ByteArray { - return ( - hash + serializeUInt(sequenceNumber.toUInt()) + serializeVarLen(data) + - ( - if (metadata != null && signature != null) serializeVarLen(metadata) + serializeVarLen(signature) - else byteArrayOf() - ) - ) + return hash + serializeUInt(sequenceNumber.toUInt()) + serializeVarLen(data) } companion object Deserializer : Deserializable { @@ -28,8 +19,10 @@ class AttestationChunkPayload( ): Pair { var localoffset = 0 - val hash = buffer.copyOfRange(offset + localoffset, - offset + localoffset + SERIALIZED_SHA1_HASH_SIZE) + val hash = buffer.copyOfRange( + offset + localoffset, + offset + localoffset + SERIALIZED_SHA1_HASH_SIZE + ) localoffset += SERIALIZED_SHA1_HASH_SIZE val sequenceNumber = deserializeUInt(buffer, offset + localoffset) @@ -38,21 +31,8 @@ class AttestationChunkPayload( val (data, dataSize) = deserializeVarLen(buffer, offset + localoffset) localoffset += dataSize - return if (buffer.lastIndex > offset + localoffset) { - val (metadata, metadataSize) = deserializeVarLen(buffer, offset + localoffset) - localoffset += metadataSize - - val (signature, signatureSize) = deserializeVarLen(buffer, offset + localoffset) - localoffset += signatureSize - - val payload = AttestationChunkPayload(hash, sequenceNumber.toInt(), data, metadata, signature) - Pair(payload, localoffset) - } else { - val payload = AttestationChunkPayload(hash, sequenceNumber.toInt(), data) - Pair(payload, localoffset) - } - + val payload = AttestationChunkPayload(hash, sequenceNumber.toInt(), data) + return Pair(payload, localoffset) } - } } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/store/AttestationSQLiteStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/store/AttestationSQLiteStore.kt new file mode 100644 index 00000000..8b370fcb --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/store/AttestationSQLiteStore.kt @@ -0,0 +1,62 @@ +package nl.tudelft.ipv8.attestation.wallet.store + +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey +import nl.tudelft.ipv8.sqldelight.Database + +private val attestationMapper: ( + ByteArray, + ByteArray, + ByteArray, + String, + ByteArray?, +) -> AttestationBlob = { hash, blob, key, id_format, value -> + AttestationBlob( + hash, + blob, + key, + id_format, + value + ) +} + +class AttestationSQLiteStore(database: Database) : AttestationStore { + private val dao = database.dbAttestationQueries + + override fun getAllAttestations(): List { + return dao.getAllAttestations(attestationMapper).executeAsList() + } + + override fun insertAttestation( + attestation: WalletAttestation, + attestationHash: ByteArray, + privateKey: BonehPrivateKey, + idFormat: String, + value: ByteArray?, + ) { + val blob = attestation.serializePrivate(privateKey.publicKey()) + dao.insertAttestation( + attestationHash, + blob, + privateKey.serialize(), + idFormat, + value + ) + } + + override fun getAttestationBlobByHash(attestationHash: ByteArray): ByteArray? { + return dao.getAttestationByHash(attestationHash).executeAsOneOrNull() + } + + override fun getValueByHash(attestationHash: ByteArray): ByteArray? { + return dao.getValueByHash(attestationHash).executeAsOneOrNull()?.value + } + + override fun deleteAttestationByHash(attestationHash: ByteArray) { + return dao.deleteAttestationByHash(attestationHash) + } + + override fun deleteAttestations(attestationHashes: List) { + return dao.deleteAttestations(attestationHashes) + } +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/store/AttestationStore.kt b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/store/AttestationStore.kt new file mode 100644 index 00000000..b70a0e86 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/attestation/wallet/store/AttestationStore.kt @@ -0,0 +1,32 @@ +package nl.tudelft.ipv8.attestation.wallet.store + +import nl.tudelft.ipv8.attestation.wallet.cryptography.WalletAttestation +import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPrivateKey + +class AttestationBlob( + val attestationHash: ByteArray, + val blob: ByteArray, + val key: ByteArray, + val idFormat: String, + val value: ByteArray?, +) + +interface AttestationStore { + fun getAllAttestations(): List + + fun insertAttestation( + attestation: WalletAttestation, + attestationHash: ByteArray, + privateKey: BonehPrivateKey, + idFormat: String, + value: ByteArray? = null, + ) + + fun getAttestationBlobByHash(attestationHash: ByteArray): ByteArray? + + fun getValueByHash(attestationHash: ByteArray): ByteArray? + + fun deleteAttestationByHash(attestationHash: ByteArray) + + fun deleteAttestations(attestationHashes: List) +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/messaging/Serialization.kt b/ipv8/src/main/java/nl/tudelft/ipv8/messaging/Serialization.kt index de3edf4b..e0efe787 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/messaging/Serialization.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/messaging/Serialization.kt @@ -13,6 +13,7 @@ const val SERIALIZED_PUBLIC_KEY_SIZE = 74 const val HASH_SIZE = 32 const val SIGNATURE_SIZE = 64 const val SERIALIZED_SHA1_HASH_SIZE = 20 +const val SERIALIZED_SHA3_256_SIZE = 32 interface Serializable { fun serialize(): ByteArray @@ -101,14 +102,20 @@ fun deserializeUChar(buffer: ByteArray, offset: Int = 0): UByte { return ubuffer[offset] } +fun deserializeSHA3_256(buffer: ByteArray, offset: Int = 0): Pair { + return Pair(buffer.copyOfRange(offset, offset + SERIALIZED_SHA3_256_SIZE), SERIALIZED_SHA3_256_SIZE) +} + fun serializeVarLen(bytes: ByteArray): ByteArray { return serializeUInt(bytes.size.toUInt()) + bytes } fun deserializeVarLen(buffer: ByteArray, offset: Int = 0): Pair { val len = deserializeUInt(buffer, offset).toInt() - val payload = buffer.copyOfRange(offset + SERIALIZED_UINT_SIZE, - offset + SERIALIZED_UINT_SIZE + len) + val payload = buffer.copyOfRange( + offset + SERIALIZED_UINT_SIZE, + offset + SERIALIZED_UINT_SIZE + len + ) return Pair(payload, SERIALIZED_UINT_SIZE + len) } @@ -117,19 +124,31 @@ fun deserializeRecursively(buffer: ByteArray, offset: Int = 0): Array return arrayOf() } val len = deserializeUInt(buffer, offset).toInt() - val payload = buffer.copyOfRange(offset + SERIALIZED_UINT_SIZE, - offset + SERIALIZED_UINT_SIZE + len) - return arrayOf(payload) + deserializeRecursively(buffer.copyOfRange(offset + SERIALIZED_UINT_SIZE + len, - buffer.size), offset) -} - -fun deserializeAmount(buffer: ByteArray, amount: Int, offset: Int = 0): Pair, ByteArray> { + val payload = buffer.copyOfRange( + offset + SERIALIZED_UINT_SIZE, + offset + SERIALIZED_UINT_SIZE + len + ) + return arrayOf(payload) + deserializeRecursively( + buffer.copyOfRange( + offset + SERIALIZED_UINT_SIZE + len, + buffer.size + ), offset + ) +} + +fun deserializeAmount( + buffer: ByteArray, + amount: Int, + offset: Int = 0 +): Pair, ByteArray> { val returnValues = arrayListOf() var localOffset = offset for (i in 0 until amount) { val len = deserializeUInt(buffer, localOffset).toInt() - val payload = buffer.copyOfRange(localOffset + SERIALIZED_UINT_SIZE, - localOffset + SERIALIZED_UINT_SIZE + len) + val payload = buffer.copyOfRange( + localOffset + SERIALIZED_UINT_SIZE, + localOffset + SERIALIZED_UINT_SIZE + len + ) localOffset += SERIALIZED_UINT_SIZE + len returnValues.add(payload) } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/DiscoveryStrategy.kt b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/DiscoveryStrategy.kt index 26f60bec..0d893b6c 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/DiscoveryStrategy.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/DiscoveryStrategy.kt @@ -6,6 +6,8 @@ import nl.tudelft.ipv8.Overlay * Strategy for discovering peers in a network. */ interface DiscoveryStrategy { + val overlay: Overlay + /** * It is called when the IPv8 service is started. */ diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/PeriodicSimilarity.kt b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/PeriodicSimilarity.kt index 0c7cfdb4..79c570b6 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/PeriodicSimilarity.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/PeriodicSimilarity.kt @@ -6,7 +6,7 @@ import nl.tudelft.ipv8.peerdiscovery.DiscoveryCommunity * On every step, it sends a similarity request to a random peer. */ class PeriodicSimilarity( - private val overlay: DiscoveryCommunity + override val overlay: DiscoveryCommunity ) : DiscoveryStrategy { override fun takeStep() { val peer = overlay.network.getRandomPeer() diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomChurn.kt b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomChurn.kt index 25632140..402ac4c0 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomChurn.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomChurn.kt @@ -15,7 +15,7 @@ private val logger = KotlinLogging.logger {} * from the network graph. */ class RandomChurn( - private val overlay: PingOverlay, + override val overlay: PingOverlay, private val sampleSize: Int, private val pingInterval: Double, private val inactiveTime: Double, diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomWalk.kt b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomWalk.kt index 2130d68f..c8f5ab30 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomWalk.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/peerdiscovery/strategy/RandomWalk.kt @@ -12,7 +12,7 @@ private val logger = KotlinLogging.logger {} * an introduction to another community member. */ class RandomWalk( - private val overlay: Overlay, + override val overlay: Overlay, private val timeout: Double, private val windowSize: Int, private val resetChance: Int, diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/util/ArrayUtils.kt b/ipv8/src/main/java/nl/tudelft/ipv8/util/ByteArrayUtils.kt similarity index 53% rename from ipv8/src/main/java/nl/tudelft/ipv8/util/ArrayUtils.kt rename to ipv8/src/main/java/nl/tudelft/ipv8/util/ByteArrayUtils.kt index 4058d3d3..7e1322b5 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/util/ArrayUtils.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/util/ByteArrayUtils.kt @@ -1,10 +1,13 @@ package nl.tudelft.ipv8.util +import java.nio.Buffer +import java.nio.ByteBuffer + class ByteArrayKey(val bytes: ByteArray) { override fun equals(other: Any?): Boolean { // Note: this is the same as contentEquals. - return this.contentEquals(other) + return this.contentEquals(other) } fun contentEquals(other: Any?): Boolean { @@ -20,3 +23,21 @@ class ByteArrayKey(val bytes: ByteArray) { return bytes.contentToString() } } + +fun ByteArray.toKey(): ByteArrayKey { + return ByteArrayKey(this) +} + +const val BYTES = 8 +fun Long.toByteArray(): ByteArray { + val buffer: ByteBuffer = ByteBuffer.allocate(BYTES) + buffer.putLong(this) + return buffer.array() +} + +fun ByteArray.toLong(): Long { + val buffer: ByteBuffer = ByteBuffer.allocate(BYTES) + buffer.put(this) + (buffer as Buffer).flip() + return buffer.getLong() +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/util/CollectionExtensions.kt b/ipv8/src/main/java/nl/tudelft/ipv8/util/CollectionExtensions.kt index d647e71f..c6d3b2c9 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/util/CollectionExtensions.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/util/CollectionExtensions.kt @@ -1,6 +1,13 @@ package nl.tudelft.ipv8.util -fun Collection.random(maxSampleSize: Int): Collection { +import kotlin.random.Random + +fun Collection.random(maxSampleSize: Int, random: Random? = null): Collection { val sampleSize = kotlin.math.min(size, maxSampleSize) - return shuffled().subList(0, sampleSize) + return if (random == null) { + shuffled().subList(0, sampleSize) + } else { + shuffled(random).subList(0, sampleSize) + } + } diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/util/EncodingUtils.kt b/ipv8/src/main/java/nl/tudelft/ipv8/util/EncodingUtils.kt index 18304fb2..eac2612c 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/util/EncodingUtils.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/util/EncodingUtils.kt @@ -1,5 +1,9 @@ package nl.tudelft.ipv8.util +import org.json.JSONArray +import org.json.JSONObject +import java.util.ArrayList + interface EncodingUtils { fun encodeBase64(bin: ByteArray): ByteArray @@ -12,3 +16,38 @@ interface EncodingUtils { } var defaultEncodingUtils: EncodingUtils = JavaEncodingUtils + +fun JSONObject.asMap(): Map { + val results: MutableMap = HashMap() + for (key in this.keys()) { + val value1 = this[key] + val value: Any? = if (value1 == null || JSONObject.NULL == value1) { + null + } else if (value1 is JSONObject) { + value1.asMap() + } else if (value1 is JSONArray) { + value1.asList() + } else { + value1 + } + results[key] = value + } + return results +} + +fun JSONArray.asList(): List { + val results: MutableList = ArrayList(this.length()) + for (i in 0 until this.length()) { + val element = this.get(i) + if (element == null || JSONObject.NULL == element) { + results.add(null) + } else if (element is JSONArray) { + results.add(element.asList()) + } else if (element is JSONObject) { + results.add(element.asMap()) + } else { + results.add(element) + } + } + return results +} diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/util/HashUtils.kt b/ipv8/src/main/java/nl/tudelft/ipv8/util/HashUtils.kt index 0a81f401..12f8011d 100644 --- a/ipv8/src/main/java/nl/tudelft/ipv8/util/HashUtils.kt +++ b/ipv8/src/main/java/nl/tudelft/ipv8/util/HashUtils.kt @@ -1,5 +1,7 @@ package nl.tudelft.ipv8.util +import org.bouncycastle.jcajce.provider.digest.SHA3 +import org.bouncycastle.jcajce.provider.digest.SHA3.DigestSHA3 import java.math.BigInteger import java.security.MessageDigest @@ -7,6 +9,7 @@ private const val SHA1 = "SHA-1" private const val SHA256 = "SHA-256" private const val SHA512 = "SHA-512" private const val SHA3_256 = "SHA3-256" +private val SHA1_PADDING = "SHA-1".toByteArray() + byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) fun sha1(input: ByteArray): ByteArray { return MessageDigest @@ -27,10 +30,10 @@ fun sha512(input: ByteArray): ByteArray { } fun sha3_256(input: ByteArray): ByteArray { - return MessageDigest.getInstance(SHA3_256).digest(input) + val digestSHA3: DigestSHA3 = SHA3.Digest256() + return digestSHA3.digest(input) } - fun toASCII(value: String): ByteArray { return value.toByteArray(Charsets.US_ASCII) } @@ -64,3 +67,15 @@ fun sha512AsBigInt(input: ByteArray): BigInteger { } return out } + +fun stripSHA1Padding(input: ByteArray): ByteArray { + val prefix = input.copyOfRange(0, 12) + return if (prefix.contentEquals(SHA1_PADDING)) input.copyOfRange(12, input.size) else input +} + +fun padSHA1Hash(attributeHash: ByteArray): ByteArray { + return SHA1_PADDING + attributeHash +} + + + diff --git a/ipv8/src/main/java/nl/tudelft/ipv8/util/SettableDeferred.kt b/ipv8/src/main/java/nl/tudelft/ipv8/util/SettableDeferred.kt new file mode 100644 index 00000000..faeedf32 --- /dev/null +++ b/ipv8/src/main/java/nl/tudelft/ipv8/util/SettableDeferred.kt @@ -0,0 +1,33 @@ +package nl.tudelft.ipv8.util + +import kotlinx.coroutines.* + +class SettableDeferred { + + private val job = GlobalScope.async(start = CoroutineStart.LAZY) { + try { + while (isActive) { + delay(50) + } + } catch (e: CancellationException) { + // NO-OP + } + } + + private var result: T? = null + + suspend fun await(): T? { + // We are manually throwing an error and, thus, want to return in both cases. + @Suppress("ReturnInsideFinallyBlock") + try { + job.await() + } finally { + return result + } + } + + fun setResult(result: T) { + this.result = result + job.cancel(CancellationException("Value has been set.")) + } +} diff --git a/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbAttestation.sq b/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbAttestation.sq index b7a88a93..b52cb34b 100644 --- a/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbAttestation.sq +++ b/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbAttestation.sq @@ -3,39 +3,24 @@ CREATE TABLE attestations ( blob BLOB NOT NULL, key BLOB NOT NULL, id_format TEXT NOT NULL, - meta_data TEXT, - signature BLOB, - attestor_key BLOB -); - -CREATE TABLE authorities ( - public_key BLOB NOT NULL, - hash TEXT NOT NULL + value BLOB, + PRIMARY KEY(hash) ); getAllAttestations: SELECT * FROM attestations; insertAttestation: -INSERT INTO attestations (hash, blob, key, id_format, meta_data, signature, attestor_key) VALUES(?, ?, ?, ?, ?, ?, ?); +INSERT INTO attestations (hash, blob, key, id_format, value) VALUES(?, ?, ?, ?, ?); getAttestationByHash: SELECT blob FROM attestations WHERE hash = ?; +getValueByHash: +SELECT value FROM attestations WHERE hash = ?; + deleteAttestationByHash: DELETE FROM attestations WHERE hash = ?; -getAllAuthorities: -SELECT * FROM authorities; - -insertAuthority: -INSERT INTO authorities (public_key, hash) VALUES (?, ?); - -getAuthorityByPublicKey: -SELECT public_key, hash FROM authorities WHERE public_key = ?; - -getAuthorityByHash: -SELECT public_key, hash FROM authorities WHERE hash = ?; - -deleteAuthorityByHash: -DELETE FROM authorities WHERE hash = ?; +deleteAttestations: +DELETE FROM attestations WHERE hash IN ?; diff --git a/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbAuthority.sq b/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbAuthority.sq new file mode 100644 index 00000000..f6a0b09f --- /dev/null +++ b/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbAuthority.sq @@ -0,0 +1,112 @@ +CREATE TABLE revocations ( + authority_id INTEGER NOT NULL, + version_id INTEGER NOT NULL, + revoked_hash BLOB NOT NULL, + FOREIGN KEY(authority_id) REFERENCES authorities(authority_id), + FOREIGN KEY(version_id) REFERENCES versions(version_id), + PRIMARY KEY(authority_id, version_id, revoked_hash) +); + +CREATE TABLE versions ( + version_id INTEGER PRIMARY KEY, + authority_id INTEGER NOT NULL, + version_number INTEGER NOT NULL, + signature BLOB NOT NULL, + FOREIGN KEY(authority_id) REFERENCES authorities(authority_id) +); + +CREATE TABLE authorities ( + authority_id INTEGER PRIMARY KEY, + public_key BLOB, + public_key_hash BLOB UNIQUE NOT NULL, + latest_version_id INTEGER, + recognized INTEGER +); + +insertRevocation: +INSERT OR IGNORE INTO revocations (authority_id, version_id, revoked_hash) +VALUES (?, ?, ?); + +insertVersion: +INSERT INTO versions (authority_id, version_number, signature) +VALUES (?, ?, ?); + +insertAuthority: +INSERT INTO authorities (public_key, public_key_hash, latest_version_id, recognized) +VALUES (?, ?, ?, ?); + +getAllRecognizedAuthorities: +SELECT A.public_key, A.public_key_hash, V.version_number, A.recognized FROM authorities AS A +LEFT JOIN versions AS V ON A.latest_version_id = V.version_id +WHERE A.recognized = 1; + +getAllAuthorities: +SELECT A.public_key, A.public_key_hash, V.version_number, A.recognized FROM authorities AS A +LEFT JOIN versions AS V ON A.latest_version_id = V.version_id; + +getAuthorityIdByHash: +SELECT authority_id FROM authorities +WHERE authorities.public_key_hash = ? LIMIT 1; + +getAuthorityByHash: +SELECT A.public_key, A.public_key_hash, V.version_number, A.recognized FROM authorities AS A +LEFT JOIN versions AS V ON A.latest_version_id = V.version_id +WHERE public_key_hash = ?; + +recognizeAuthority: +UPDATE authorities SET recognized = 1 WHERE public_key_hash = ?; + +disregardAuthority: +UPDATE authorities SET recognized = 0 WHERE public_key_hash = ?; + +getRevocationsByAuthorityId: +SELECT revoked_hash FROM revocations WHERE authority_id = ?; + +getRevocationsByAuthorityIdAndVersionId: +SELECT revoked_hash FROM revocations WHERE authority_id = ? AND version_id = ? ORDER BY rowid; + +updateVersionFor: +UPDATE authorities SET latest_version_id = ? WHERE public_key_hash = ?; + +getVersionByAuthorityIDandVersionNumber: +SELECT * FROM versions WHERE authority_id = ? AND version_number = ?; + +getVersionsByAuthorityIDandVersionNumbers: +SELECT * FROM versions WHERE authority_id = ? AND version_number IN ?; + +getVersionsSince: +SELECT version_number FROM versions WHERE authority_id = ? AND version_number > ?; + +getAllRevocations: +SELECT A.public_key_hash, V.version_number, V.signature, R.revoked_hash FROM authorities AS A +LEFT JOIN versions AS V ON A.latest_version_id = V.version_id +LEFT JOIN revocations AS R ON A.authority_id = R.authority_id; + +getRevocations: +SELECT revoked_hash FROM revocations; + +getNumberOfRevocations: +SELECT COUNT(*) FROM revocations; + +getMissingVersionByAuthorityID: +SELECT MIN(A.version_number) FROM versions AS A +WHERE A.authority_id = ?1 AND +NOT EXISTS ( + SELECT B.version_number FROM versions AS B + WHERE B.authority_id = ?1 AND A.version_number + 1 = B.version_number); + +isRevoked: +SELECT 1 FROM revocations WHERE revoked_hash = ?; + +isRevokedBy: +SELECT 1 FROM revocations WHERE revoked_hash = ? AND authority_id = ?; + +clearRevocations: +DELETE FROM revocations; + +clearVersions: +DELETE FROM versions; + +clearAuthorityVersions: +UPDATE authorities +SET latest_version_id = NULL; \ No newline at end of file diff --git a/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbIdentity.sq b/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbIdentity.sq new file mode 100644 index 00000000..bc3e9a9e --- /dev/null +++ b/ipv8/src/main/sqldelight/nl/tudelft/ipv8/sqldelight/DbIdentity.sq @@ -0,0 +1,66 @@ +CREATE TABLE tokens ( + public_key BLOB NOT NULL, + previous_token_hash BLOB NOT NULL, + signature BLOB NOT NULL, + content_hash BLOB NOT NULL, + content BLOB, + PRIMARY KEY (public_key, previous_token_hash, content_hash) +); + +CREATE TABLE metadata ( + public_key BLOB NOT NULL, + token_pointer BLOB NOT NULL, + signature BLOB NOT NULL, + serialized_json BLOB NOT NULL, + PRIMARY KEY (public_key, token_pointer) +); + +CREATE TABLE identity_attestations ( + public_key BLOB NOT NULL, + authority_key BLOB NOT NULL, + metadata_pointer BLOB NOT NULL, + signature BLOB NOT NULL, + PRIMARY KEY (public_key, metadata_pointer) +); + +insertToken: +INSERT OR IGNORE INTO tokens (public_key, previous_token_hash, signature, content_hash, content) VALUES(?, ?, ?, ?, ?); + +insertMetadata: +INSERT OR IGNORE INTO metadata (public_key, token_pointer, signature, serialized_json) VALUES(?, ?, ?, ?); + +insertIdentityAttestation: +INSERT OR IGNORE INTO identity_attestations (public_key, authority_key, metadata_pointer, signature) VALUES(?, ?, ?, ?); + +getTokensFor: +SELECT previous_token_hash, signature, content_hash, content FROM tokens WHERE public_key = ?; + +getMetadataFor: +SELECT token_pointer, signature, serialized_json FROM metadata WHERE public_key = ?; + +getAttestationsFor: +SELECT metadata_pointer, signature FROM identity_attestations WHERE public_key = ?; + +getAttestationsBy: +SELECT metadata_pointer, signature FROM identity_attestations WHERE authority_key = ?; + +getAttestationsOver: +SELECT metadata_pointer, signature FROM identity_attestations WHERE metadata_pointer = ?; + +getAuthority: +SELECT authority_key FROM identity_attestations WHERE signature = ?; + +getKnownIdentities: +SELECT public_key FROM tokens; + +getKnownSubjects: +SELECT DISTINCT public_key FROM metadata; + +deleteTokensFor: +DELETE FROM tokens WHERE public_key = ?; + +deleteMetadataFor: +DELETE FROM metadata WHERE public_key = ?; + +deleteIdentityAttestationsFor: +DELETE FROM identity_attestations WHERE public_key = ?; diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/CommunityTest.kt b/ipv8/src/test/java/nl/tudelft/ipv8/CommunityTest.kt index e0d12c52..94e13dbf 100644 --- a/ipv8/src/test/java/nl/tudelft/ipv8/CommunityTest.kt +++ b/ipv8/src/test/java/nl/tudelft/ipv8/CommunityTest.kt @@ -1,16 +1,14 @@ package nl.tudelft.ipv8 -import com.goterl.lazysodium.LazySodiumJava -import com.goterl.lazysodium.SodiumJava import io.mockk.every import io.mockk.mockk import io.mockk.spyk import io.mockk.verify -import nl.tudelft.ipv8.keyvault.* +import nl.tudelft.ipv8.keyvault.JavaCryptoProvider +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider import nl.tudelft.ipv8.messaging.Address import nl.tudelft.ipv8.messaging.Packet -import nl.tudelft.ipv8.messaging.payload.BinMemberAuthenticationPayload -import nl.tudelft.ipv8.messaging.payload.GlobalTimeDistributionPayload import nl.tudelft.ipv8.messaging.payload.PunctureRequestPayload import nl.tudelft.ipv8.peerdiscovery.Network import nl.tudelft.ipv8.util.hexToBytes @@ -18,8 +16,6 @@ import nl.tudelft.ipv8.util.toHex import org.junit.Assert import org.junit.Test -private val lazySodium = LazySodiumJava(SodiumJava()) - class CommunityTest : BaseCommunityTest() { private fun getCommunity(): TestCommunity { val myPrivateKey = getPrivateKey() diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTest.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTest.kt new file mode 100644 index 00000000..3fc3dd38 --- /dev/null +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTest.kt @@ -0,0 +1,83 @@ +package nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree + +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class TokenTest { + + private lateinit var privateKey: PrivateKey + private lateinit var testPublicKey: PublicKey + private lateinit var testData: ByteArray + private lateinit var testDataHash: ByteArray + private lateinit var testSignature: ByteArray + + @Before + fun init() { + privateKey = defaultCryptoProvider.generateKey() + testData = "1234567890abcdefghijklmnopqrstuvwxyz".repeat(69).toByteArray() + + val sk = defaultCryptoProvider.generateKey() + testPublicKey = sk.pub() + + val token = Token("test_previous_token_hash".toByteArray(), content = testData, privateKey = sk) + testDataHash = token.contentHash + testSignature = token.signature + } + + @Test + fun testCreatePrivateToken() { + val token = + Token("test_previous_token_hash".toByteArray(), content = this.testData, privateKey = this.privateKey) + assertArrayEquals("test_previous_token_hash".toByteArray(), token.previousTokenHash) + assertArrayEquals(this.testData, token.content) + assertArrayEquals(this.testDataHash, token.contentHash) + assertTrue(token.verify(this.privateKey.pub())) + } + + @Test + fun testVerifyTokenIllegal() { + val token = + Token("test_previous_token_hash".toByteArray(), contentHash = this.testData, signature = this.testSignature) + val otherKey = defaultCryptoProvider.generateKey().pub() + assertFalse(token.verify(otherKey)) + } + + @Test + fun testUpdatePublicToken() { + val token = Token( + "test_previous_token_hash".toByteArray(), + contentHash = this.testDataHash, + signature = this.testSignature + ) + val returnValue = token.receiveContent(this.testData) + + assertTrue(returnValue) + assertArrayEquals("test_previous_token_hash".toByteArray(), token.previousTokenHash) + assertArrayEquals(this.testData, token.content) + assertArrayEquals(this.testDataHash, token.contentHash) + assertTrue(token.verify(this.testPublicKey)) + } + + @Test + fun testUpdatePublicTokenIllegal() { + val token = Token( + "test_previous_token_hash".toByteArray(), + contentHash = this.testDataHash, + signature = this.testSignature + ) + val returnValue = token.receiveContent("some other data".toByteArray()) + + assertFalse(returnValue) + assertArrayEquals("test_previous_token_hash".toByteArray(), token.previousTokenHash) + assertNull(token.content) + assertArrayEquals(this.testDataHash, token.contentHash) + assertTrue(token.verify(this.testPublicKey)) + } +} diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTreeTest.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTreeTest.kt new file mode 100644 index 00000000..864386c8 --- /dev/null +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/datastructures/tokenTree/TokenTreeTest.kt @@ -0,0 +1,175 @@ +package nl.tudelft.ipv8.attestation.identity.datastructures.tokenTree + +import nl.tudelft.ipv8.keyvault.PrivateKey +import nl.tudelft.ipv8.keyvault.PublicKey +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.util.hexToBytes +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class TokenTreeTest { + + private lateinit var privateKey: PrivateKey + private lateinit var publicKey: PublicKey + + @Before + fun init() { + privateKey = defaultCryptoProvider.generateKey() + publicKey = privateKey.pub() + } + + @Test + fun testCreateOwnEmpty() { + val tree = TokenTree(privateKey = privateKey) + assertEquals(0, tree.elements.size) + assertEquals(0, tree.unchained.size) + assertEquals(0, tree.getMissing().size) + } + + @Test + fun testCreateOtherEmpty() { + val tree = TokenTree(publicKey = publicKey) + assertEquals(0, tree.elements.size) + assertEquals(0, tree.unchained.size) + assertEquals(0, tree.getMissing().size) + } + + @Test + fun testOwnAdd() { + val tree = TokenTree(privateKey = privateKey) + val content = "test content".toByteArray() + val token = tree.add(content) + + assertEquals(1, tree.elements.size) + assertEquals(0, tree.unchained.size) + assertEquals(0, tree.getMissing().size) + assertTrue(tree.verify(token)) + assertArrayEquals(content, token.content) + } + + @Test + fun testOtherAddInSequence() { + val tree = TokenTree(privateKey = privateKey) + val actualToken = Token(tree.genesisHash, content = "some data".toByteArray(), privateKey = privateKey) + val publicToken = Token.deserialize(actualToken.getPlaintextSigned(), this.publicKey) + val token = tree.gatherToken(publicToken)!! + + assertEquals(1, tree.elements.size) + assertEquals(0, tree.unchained.size) + assertEquals(0, tree.getMissing().size) + assertTrue(tree.verify(publicToken)) + assertNull(token.content) + } + + @Test + fun testOtherAddOutSequence() { + val tree = TokenTree(publicKey = publicKey) + val actualToken = + Token("ab".repeat(16).toByteArray(), content = "some data".toByteArray(), privateKey = privateKey) + val publicToken = Token.deserialize(actualToken.getPlaintextSigned(), this.publicKey) + val token = tree.gatherToken(publicToken) + + assertEquals(0, tree.elements.size) + assertEquals(1, tree.unchained.size) + assertArrayEquals(arrayOf("ab".repeat(16).toByteArray()), tree.getMissing().toTypedArray()) + assertFalse(tree.verify(publicToken)) + assertNull(token) + } + + @Test + fun testOtherAddOutSequenceOverflow() { + val tree = TokenTree(publicKey = publicKey) + tree.unchainedLimit = 1 + + val realToken1 = + Token("ab".repeat(16).toByteArray(), content = "some data".toByteArray(), privateKey = privateKey) + val realToken2 = + Token("cd".repeat(16).toByteArray(), content = "some other data".toByteArray(), privateKey = privateKey) + val publicToken1 = Token.deserialize(realToken1.getPlaintextSigned(), this.publicKey) + val publicToken2 = Token.deserialize(realToken2.getPlaintextSigned(), this.publicKey) + tree.gatherToken(publicToken1) + tree.gatherToken(publicToken2) + + assertEquals(0, tree.elements.size) + assertEquals(1, tree.unchained.size) + assertArrayEquals(arrayOf("cd".repeat(16).toByteArray()), tree.getMissing().toTypedArray()) + assertFalse(tree.verify(publicToken1)) + assertFalse(tree.verify(publicToken2)) + } + + @Test + fun testOtherAddDuplicate() { + val tree = TokenTree(publicKey = publicKey) + val actualToken = Token(tree.genesisHash, content = "some data".toByteArray(), privateKey = privateKey) + val publicToken = Token.deserialize(actualToken.getPlaintextSigned(), this.publicKey) + val token = tree.gatherToken(publicToken)!! + + assertEquals(1, tree.elements.size) + assertEquals(0, tree.unchained.size) + assertEquals(0, tree.getMissing().size) + assertTrue(tree.verify(publicToken)) + assertNull(token.content) + } + + @Test + fun testOtherAddDuplicateContent() { + val tree = TokenTree(publicKey = publicKey) + val actualToken = Token(tree.genesisHash, content = "some data".toByteArray(), privateKey = privateKey) + val publicToken = Token.deserialize(actualToken.getPlaintextSigned(), this.publicKey) + val token = tree.gatherToken(publicToken)!! + tree.gatherToken(actualToken) + + assertEquals(1, tree.elements.size) + assertEquals(0, tree.unchained.size) + assertEquals(0, tree.getMissing().size) + assertTrue(tree.verify(publicToken)) + assertTrue(tree.verify(token)) + assertArrayEquals("some data".toByteArray(), token.content) + } + + @Test + fun testSerializePublic() { + val tree = TokenTree(privateKey = privateKey) + tree.add("data1".toByteArray()) + tree.add("data2".toByteArray()) + tree.add("data3".toByteArray()) + + assertEquals(128 * 3, tree.serializePublic().size) + } + + @Test + fun testSerializePublicPartial() { + val tree = TokenTree(privateKey = privateKey) + val token1 = tree.add("data1".toByteArray()) + val token2 = tree.add("data2".toByteArray(), token1) + tree.add("data3".toByteArray(), token2) + + assertEquals(128 * 2, tree.serializePublic(token2).size) + } + + @Test + fun testDeserializePublic() { + val publicKey = + defaultCryptoProvider.keyFromPublicBin("4c69624e61434c504b3ae2db18b81ae520ca5819d5a65c45edb3536ef7cbd86254145a5e96b6ceecd0779d6c7ab811bdb49e1d161b5c49fc29063b4684a0e2d987cd1c91a3fbdedf6005".hexToBytes()) + val serializedTree = + "c0332fa9b25cf0ae4f2456e9d1c38d41b990faae81e9a6a3610032e96a86ce01f3ab3ec93cbecf2ae8280f0d782111ab3202dfa6eef6797203b886e215fc7b6e3ce8676f8b395bd1e729fe2b2be9865c9d6b9dd7350989d85bf91e1522bc75b21574790b662b5cdcceb5168622707a3ba87fb60db8f89df5e089e7499cf89204c0332fa9b25cf0ae4f2456e9d1c38d41b990faae81e9a6a3610032e96a86ce011feff33a31fe7bd710f738ec96e95a8a8551a2047abd46309b7b2594e606359529e3b4d6f18432c4ac3a73c845a7c33a7d17f476b059689be65fe38bd59c48f4f108e3e4c32ab26e0deadd4fee83f2ff6ca1251fd6b2e0bbdef6fb4f8253260bc0332fa9b25cf0ae4f2456e9d1c38d41b990faae81e9a6a3610032e96a86ce014c75b310b769b0a0c8521a40968af43daf62cec5ab1a65dc658093192020897c82a3baf38bece1d113783a4aefc04e4baf40c8af53d23451823706857830b177db6980c9251072916445bab2b8430d917287d5f5f61496f30bd27dc6ce79d309".hexToBytes() + + val tree = TokenTree(publicKey = publicKey) + tree.deserializePublic(serializedTree) + + assertEquals(3, tree.elements.size) + + for (token in tree.elements.values) { + assertTrue(tree.verify(token)) + } + + for (content in listOf("data1".toByteArray(), "data2".toByteArray(), "data3".toByteArray())) { + assertTrue(tree.elements.values.any { it.receiveContent(content) }) + } + } +} diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/manager/IdentityManagerTest.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/manager/IdentityManagerTest.kt new file mode 100644 index 00000000..6803a819 --- /dev/null +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/identity/manager/IdentityManagerTest.kt @@ -0,0 +1,208 @@ +package nl.tudelft.ipv8.attestation.identity.manager + +import com.squareup.sqldelight.db.SqlDriver +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver +import nl.tudelft.ipv8.attestation.identity.store.Credential +import nl.tudelft.ipv8.attestation.identity.store.IdentitySQLiteStore +import nl.tudelft.ipv8.keyvault.defaultCryptoProvider +import nl.tudelft.ipv8.sqldelight.Database +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class IdentityManagerTest { + + private val privateKey = defaultCryptoProvider.generateKey() + private val publicKey = privateKey.pub() + private val authorityPrivateKey = defaultCryptoProvider.generateKey() + private val authorityPublicKey = authorityPrivateKey.pub() + private val manager: IdentityManager + + private var driver: SqlDriver + + init { + driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) + Database.Schema.create(driver) + val database = Database(driver) + val store = IdentitySQLiteStore(database) + this.manager = IdentityManager(store) + } + + private fun forgetIdentities() { + this.manager.pseudonyms.clear() + driver.close() + driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) + Database.Schema.create(driver) + val database = Database(driver) + this.manager.database = IdentitySQLiteStore(database) + } + + @Test + fun testCreateIdentity() { + val pseudonym = this.manager.getPseudonym(this.privateKey) + assertEquals(listOf(), pseudonym.getCredentials()) + } + + @Test + fun testSubstantiateEmpty() { + val (correct, pseudonym) = this.manager.substantiate( + this.publicKey, + byteArrayOf(), + byteArrayOf(), + byteArrayOf(), + byteArrayOf() + ) + + assertTrue(correct) + assertEquals(listOf(), pseudonym.getCredentials()) + } + + @Test + fun testCreateCredential() { + val pseudonym = this.manager.getPseudonym(this.privateKey) + pseudonym.createCredential("ab".repeat(16).toByteArray(), hashMapOf("some_key" to "some_value")) + assertEquals(1, pseudonym.getCredentials().size) + assertEquals(0, pseudonym.getCredentials()[0].attestations.size) + val expectedMD = JSONObject() + expectedMD.put("some_key", "some_value") + assertEquals(expectedMD.toString(), String(pseudonym.getCredentials()[0].metadata.serializedMetadata)) + } + + @Test + fun testSubstantiateCredentialUpdate() { + val pseudonym = this.manager.getPseudonym(this.privateKey) + pseudonym.createCredential("ab".repeat(16).toByteArray(), hashMapOf("some_key" to "some_value")) + val (metadata, tokens, attestations, authorities) = pseudonym.discloseCredentials( + pseudonym.getCredentials(), + setOf() + ) + this.manager.pseudonyms.clear() + val (correct, publicPseudonym) = this.manager.substantiate( + pseudonym.publicKey, + metadata, + tokens, + attestations, + authorities + ) + + assertTrue(correct) + assertEquals(1, publicPseudonym.getCredentials().size) + assertEquals(0, publicPseudonym.getCredentials()[0].attestations.size) + val expectedMD = JSONObject() + expectedMD.put("some_key", "some_value") + assertEquals(expectedMD.toString(), String(publicPseudonym.getCredentials()[0].metadata.serializedMetadata)) + } + + @Test + fun testSubstantiateCredentialWithoutMetadata() { + val pseudonym = this.manager.getPseudonym(this.privateKey) + pseudonym.createCredential("ab".repeat(16).toByteArray(), hashMapOf("some_key" to "some_value")) + val (_, tokens, attestations, authorities) = pseudonym.discloseCredentials( + pseudonym.getCredentials(), + setOf() + ) + this.forgetIdentities() + val (correct, publicPseudonym) = this.manager.substantiate( + pseudonym.publicKey, + byteArrayOf(), + tokens, + attestations, + authorities + ) + + assertTrue(correct) + assertEquals(0, publicPseudonym.getCredentials().size) + assertEquals(1, publicPseudonym.tree.elements.size) + } + + @Test + fun testSubstantiateCredentialWithMetadata() { + val pseudonym = this.manager.getPseudonym(this.privateKey) + pseudonym.createCredential("ab".repeat(16).toByteArray(), hashMapOf("some_key" to "some_value")) + val (metadata, tokens, attestations, authorities) = pseudonym.discloseCredentials( + pseudonym.getCredentials(), + setOf() + ) + this.forgetIdentities() + val (correct, publicPseudonym) = this.manager.substantiate( + pseudonym.publicKey, + metadata, + tokens, + attestations, + authorities + ) + + assertTrue(correct) + assertEquals(1, publicPseudonym.getCredentials().size) + assertEquals(0, publicPseudonym.getCredentials()[0].attestations.size) + val expectedMD = JSONObject() + expectedMD.put("some_key", "some_value") + assertEquals(expectedMD.toString(), String(publicPseudonym.getCredentials()[0].metadata.serializedMetadata)) + } + + @Test + fun testSubstantiateCredentialFull() { + val pseudonym = this.manager.getPseudonym(this.privateKey) + pseudonym.createCredential("ab".repeat(16).toByteArray(), hashMapOf("some_key" to "some_value")) + + val attestation = pseudonym.createAttestation(pseudonym.getCredentials()[0].metadata, this.authorityPrivateKey) + pseudonym.addAttestation(this.authorityPublicKey, attestation) + + val (metadata, tokens, attestations, authorities) = pseudonym.discloseCredentials( + pseudonym.getCredentials(), + setOf(attestation.hash) + ) + this.forgetIdentities() + + val (correct, publicPseudonym) = this.manager.substantiate( + pseudonym.publicKey, + metadata, + tokens, + attestations, + authorities + ) + + assertTrue(correct) + assertEquals(1, publicPseudonym.getCredentials().size) + assertEquals(1, publicPseudonym.getCredentials()[0].attestations.size) + val expectedMD = JSONObject() + expectedMD.put("some_key", "some_value") + assertEquals(expectedMD.toString(), String(publicPseudonym.getCredentials()[0].metadata.serializedMetadata)) + } + + @Test + fun testSubstantiateCredentialPartial() { + val pseudonym = this.manager.getPseudonym(this.privateKey) + pseudonym.createCredential("ab".repeat(16).toByteArray(), hashMapOf("some_key" to "some_value")) + + val attestation = pseudonym.createAttestation(pseudonym.getCredentials()[0].metadata, this.authorityPrivateKey) + pseudonym.addAttestation(this.authorityPublicKey, attestation) + + val attestation2 = pseudonym.createAttestation(pseudonym.getCredentials()[0].metadata, this.privateKey) + pseudonym.addAttestation(this.publicKey, attestation2) + + val (metadata, tokens, attestations, authorities) = pseudonym.discloseCredentials( + pseudonym.getCredentials(), + setOf(attestation.hash) + ) + this.forgetIdentities() + + val (correct, publicPseudonym) = this.manager.substantiate( + pseudonym.publicKey, + metadata, + tokens, + attestations, + authorities + ) + + assertTrue(correct) + assertEquals(1, publicPseudonym.getCredentials().size) + assertEquals(1, publicPseudonym.getCredentials()[0].attestations.size) + val expectedMD = JSONObject() + expectedMD.put("some_key", "some_value") + assertEquals(expectedMD.toString(), String(publicPseudonym.getCredentials()[0].metadata.serializedMetadata)) + } +} + + diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/AttestationHelperFunctionsTest.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/AttestationHelperFunctionsTest.kt index 7833b5ce..0da5c380 100644 --- a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/AttestationHelperFunctionsTest.kt +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/AttestationHelperFunctionsTest.kt @@ -13,7 +13,6 @@ class AttestationHelperFunctionsTest { private val privateKey = BonehPrivateKey.deserialize("0000000907a016081ab53e90850000000900edf0f06d18de2c400000000906b15c56c9a13f1d250000000902fa4b47c93f63a2a3000000090412c4d01c5d61ce3b000000082426557bc0fc6af9000000044405f5d7".hexToBytes())!! - @Test fun generateInverseGroupTest() { /* @@ -27,7 +26,6 @@ class AttestationHelperFunctionsTest { assertEquals(20, group.size) assertEquals(BigInteger.ZERO, sum.mod(p + BigInteger.ONE)) - } @Test @@ -45,13 +43,13 @@ class AttestationHelperFunctionsTest { val decoded = arrayOf(0, 1, 1, 2) for (i in attestations.indices) { - assertEquals(decoded[i], - decode(privateKey, arrayOf(0, 1, 2, 3), attestations[i].bitPairs.get(0).compress())) + assertEquals( + decoded[i], + decode(privateKey, arrayOf(0, 1, 2, 3), attestations[i].bitPairs.get(0).compress()) + ) } - } - @Test fun emptyRelativityMapTest() { val relMap = createEmptyRelativityMap() diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestBoneh.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestBoneh.kt index 8cf6261b..ac482579 100644 --- a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestBoneh.kt +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestBoneh.kt @@ -1,6 +1,6 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value import nl.tudelft.ipv8.util.hexToBytes import org.junit.Assert.* import org.junit.Test @@ -11,7 +11,6 @@ class TestBoneh { val privateKey = BonehPrivateKey.deserialize("000000193f66b1832ebda2377020c49d4efb1f02d06abd620a3bb1cef90000001930a3f4173587f326a01065403d695ef74cd43541aef197021900000019249746a549c80dc37771fc52591733507ed0eca7ef21a58eed0000001926b4c4866545b883aa6eec92de3ae08ddcca251c52d67fed6a0000001939e634b0c3815508b1281e680247bd10652b9d7573cd011a9f00000018491c79ac07c5eb7e32e2e756d79e1f25d6f7a0badb5e34d30000000c60bcec8cf857fc02e4885091".hexToBytes())!! - @Test fun generatePrimeTest() { /* @@ -25,14 +24,17 @@ class TestBoneh { /* * Check if a bilinear group can be created. */ - val x = bilinearGroup(BigInteger.valueOf(10), + val x = bilinearGroup( + BigInteger.valueOf(10), BigInteger.valueOf(29), BigInteger.valueOf(4), BigInteger.valueOf(5), BigInteger.valueOf(4), - BigInteger.valueOf(5)) + BigInteger.valueOf(5) + ) val y = FP2Value( - BigInteger.valueOf(29), BigInteger.valueOf(19), BigInteger.valueOf(5)) + BigInteger.valueOf(29), BigInteger.valueOf(19), BigInteger.valueOf(5) + ) assertEquals(x, y) } @@ -41,12 +43,16 @@ class TestBoneh { /* * Check if a bilinear group returns 0 if there is no possible pairing. */ - assertEquals(bilinearGroup(BigInteger.valueOf(10), - BigInteger.valueOf(29), - BigInteger.valueOf(2), - BigInteger.valueOf(3), - BigInteger.valueOf(2), - BigInteger.valueOf(3)), FP2Value(BigInteger.valueOf(29))) + assertEquals( + bilinearGroup( + BigInteger.valueOf(10), + BigInteger.valueOf(29), + BigInteger.valueOf(2), + BigInteger.valueOf(3), + BigInteger.valueOf(2), + BigInteger.valueOf(3) + ), FP2Value(BigInteger.valueOf(29)) + ) } @Test @@ -54,8 +60,12 @@ class TestBoneh { /* * Check if is_good_wp returns True for 26 + 17x with n = 10 */ - assertTrue(isGoodWp(BigInteger.valueOf(10), - FP2Value(BigInteger.valueOf(29), BigInteger.valueOf(26), BigInteger.valueOf(17)))) + assertTrue( + isGoodWp( + BigInteger.valueOf(10), + FP2Value(BigInteger.valueOf(29), BigInteger.valueOf(26), BigInteger.valueOf(17)) + ) + ) } @Test diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestKeys.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestKeys.kt index 805887e7..efb939bd 100644 --- a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestKeys.kt +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/bonehexact/TestKeys.kt @@ -1,6 +1,6 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact -import nl.tudelft.ipv8.attestation.schema.SchemaManager +import nl.tudelft.ipv8.attestation.common.SchemaManager import nl.tudelft.ipv8.util.toHex import org.junit.Assert.assertEquals import org.junit.Test @@ -23,5 +23,4 @@ class TestKeys { assertEquals(keyRecovHex, keyHex) assertEquals(key, keyRecov) } - } diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/TestBoudot.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/TestBoudot.kt index 2d054329..6e7461a6 100644 --- a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/TestBoudot.kt +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/pengbaorange/TestBoudot.kt @@ -1,7 +1,9 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange import nl.tudelft.ipv8.attestation.wallet.cryptography.bonehexact.BonehPublicKey -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.primitives.EL +import nl.tudelft.ipv8.attestation.wallet.cryptography.pengbaorange.boudot.primitives.SQR +import nl.tudelft.ipv8.attestation.wallet.cryptography.primitives.FP2Value import nl.tudelft.ipv8.util.hexToBytes import org.junit.Assert.* import org.junit.Test @@ -10,28 +12,31 @@ import java.math.BigInteger class TestBoudot { // Test params to be equal to the PyIPV8 tests. - val pk = BonehPublicKey( + private val pk = BonehPublicKey( p = BigInteger("1855855294356961364177"), - g = FP2Value(mod = BigInteger("1855855294356961364177"), + g = FP2Value( + mod = BigInteger("1855855294356961364177"), a = BigInteger("1640955591379995824691"), - b = BigInteger("322057703443002024702")), - h = FP2Value(mod = BigInteger("1855855294356961364177"), + b = BigInteger("322057703443002024702") + ), + h = FP2Value( + mod = BigInteger("1855855294356961364177"), a = BigInteger("706495420229569786251"), - b = BigInteger("156305917664511526783")) + b = BigInteger("156305917664511526783") + ) ) - val bitspace = 32 - val a = 0.toBigInteger() - val b = 18.toBigInteger() - val r = 2.toBigInteger() - val ra = 3.toBigInteger() - + private val bitSpace = 32 + private val a = 0.toBigInteger() + private val b = 18.toBigInteger() + private val r = 2.toBigInteger() + private val ra = 3.toBigInteger() // Commitment generated with above parameters for message `7` - val el = + private val el = EL.deserialize("020000002101c42b8cb90f9de7b77897d0df644410cf8581a7381392562164b5e0da43d3e47f00000021012d725dd0b513efcfa5ba8b3f982d608a59011a25626138c43fb537a3faf168ff000000210710ae32e43e779edde25f437d9110433e16069ce04e488d0c6c242fba6c50be7c000000210096b92ee85a89f7e7d2dd459fcc16b0452c808d12b130b66bb3adaea4de5c0fb6".hexToBytes()).first - val sqr = + private val sqr = SQR.deserialize("00000009649b2a7d7992b008d1000000092ef4b2b3890136f39d00000009300f2cb5896dcadc49000000a7010000002a0a16027dee86ae81c4cfc7f529f46261608252ab4daa517d68e21720790e4199f78a1929dbd013a105b60000002a0285809f7ba1aba0714188435457007cbe08eaaf87506267fdb8c0ad262dfb6c15ca968e206a9777949e0000002102af10fec9115c85e85cd7d7aef94a65d3cc6c7357642706e8a37bb0ecd804845b0000002100abc43fb24457217a1735f5ebbe529974f31b1cd5d909c1ba28deec3b36011fa3".hexToBytes()).first @Test @@ -39,10 +44,12 @@ class TestBoudot { /* * Check if Boudot equality checks are correctly serialized. */ - val el = EL(BigInteger("68174117739048401651990398043836840253231911962332258219191049320869958258614"), + val el = EL( + BigInteger("68174117739048401651990398043836840253231911962332258219191049320869958258614"), BigInteger("818089412868580819823884776526042083038782943547987098630292591850439499103868"), BigInteger("-136348235478096803303980796087673680506463823924664516192465696382710419908863"), - BigInteger("204522353217145204955971194131510520759695735886996774897791958142625032430719")) + BigInteger("204522353217145204955971194131510520759695735886996774897791958142625032430719") + ) assertEquals(this.el, el) } @@ -130,10 +137,14 @@ class TestBoudot { /* * Check if the Boudot commitment-is-square holds. */ - val sqr = SQR.create(4.toBigInteger(), 81.toBigInteger(), this.pk.g, this.pk.h, this.b.toInt(), this.bitspace) - assertTrue(sqr.check(this.pk.g, - this.pk.h, - this.pk.g.bigIntPow(16.toBigInteger()) * this.pk.h.bigIntPow(81.toBigInteger()))) + val sqr = SQR.create(4.toBigInteger(), 81.toBigInteger(), this.pk.g, this.pk.h, this.b.toInt(), this.bitSpace) + assertTrue( + sqr.check( + this.pk.g, + this.pk.h, + this.pk.g.bigIntPow(16.toBigInteger()) * this.pk.h.bigIntPow(81.toBigInteger()) + ) + ) } @Test @@ -141,11 +152,14 @@ class TestBoudot { /* * Check if the Boudot commitment-is-square fails if the commitment message changes. */ - val sqr = SQR.create(9.toBigInteger(), 81.toBigInteger(), this.pk.g, this.pk.h, this.b.toInt(), this.bitspace) - assertFalse(sqr.check(this.pk.g, - this.pk.h, - this.pk.g.bigIntPow(16.toBigInteger()) * this.pk.h.bigIntPow(81.toBigInteger()))) - + val sqr = SQR.create(9.toBigInteger(), 81.toBigInteger(), this.pk.g, this.pk.h, this.b.toInt(), this.bitSpace) + assertFalse( + sqr.check( + this.pk.g, + this.pk.h, + this.pk.g.bigIntPow(16.toBigInteger()) * this.pk.h.bigIntPow(81.toBigInteger()) + ) + ) } @Test @@ -153,10 +167,14 @@ class TestBoudot { /* * Check if the Boudot commitment-is-square fails if the shadow commitment message changes. */ - val sqr = SQR.create(4.toBigInteger(), 81.toBigInteger(), this.pk.g, this.pk.h, this.b.toInt(), this.bitspace) - assertFalse(sqr.check(this.pk.g, - this.pk.h, - this.pk.g.bigIntPow(9.toBigInteger()) * this.pk.h.bigIntPow(81.toBigInteger()))) + val sqr = SQR.create(4.toBigInteger(), 81.toBigInteger(), this.pk.g, this.pk.h, this.b.toInt(), this.bitSpace) + assertFalse( + sqr.check( + this.pk.g, + this.pk.h, + this.pk.g.bigIntPow(9.toBigInteger()) * this.pk.h.bigIntPow(81.toBigInteger()) + ) + ) } @Test @@ -164,10 +182,12 @@ class TestBoudot { /* * Check if Boudot commitment-is-square checks are correctly serialized. */ - val el = EL(BigInteger("77692238748522549651683873494184958254240308467748145113388211342290056191907"), + val el = EL( + BigInteger("77692238748522549651683873494184958254240308467748145113388211342290056191907"), BigInteger("310768954994090198606735493976739833016961233870992580453552845369160224769115"), BigInteger("1378784829646587776157777333351527905785206201874845030390922605077647796646962410317344630260602014"), - BigInteger("-5515139318586351104624816262067481296619038413746351139945096955324703586833572052593411867046643126")) + BigInteger("-5515139318586351104624816262067481296619038413746351139945096955324703586833572052593411867046643126") + ) val sqr = SQR(FP2Value(this.pk.g.mod, BigInteger("866182580282760557469"), BigInteger("886537163949459823689")), el) diff --git a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/TestEc.kt b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/TestEc.kt index b3d8b93b..ccdef515 100644 --- a/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/TestEc.kt +++ b/ipv8/src/test/java/nl/tudelft/ipv8/attestation/wallet/cryptography/primitives/TestEc.kt @@ -1,13 +1,9 @@ package nl.tudelft.ipv8.attestation.wallet.cryptography.primitives -import nl.tudelft.ipv8.attestation.wallet.primitives.FP2Value -import nl.tudelft.ipv8.attestation.wallet.primitives.eSum -import nl.tudelft.ipv8.attestation.wallet.primitives.weilParing import org.junit.Assert.assertEquals import org.junit.Test import java.math.BigInteger - class TestEC { @Test @@ -16,11 +12,13 @@ class TestEC { * Check if Weil pairing in E[4] mod 11 of (5, 4) and (5x, 4) with S=(7, 6) equals 9 + 7x */ val mod = BigInteger("11") - val wp = weilParing(mod, + val wp = weilParing( + mod, BigInteger("4"), Pair(FP2Value(mod, BigInteger("5")), FP2Value(mod, BigInteger("4"))), Pair(FP2Value(mod, b = BigInteger("5")), FP2Value(mod, BigInteger("4"))), - Pair(FP2Value(mod, BigInteger("7")), FP2Value(mod, BigInteger("6")))) + Pair(FP2Value(mod, BigInteger("7")), FP2Value(mod, BigInteger("6"))) + ) assertEquals(wp.a, BigInteger("9")) assertEquals(wp.b, BigInteger("7")) @@ -36,11 +34,13 @@ class TestEC { * Check if Weil pairing in E[408] mod 1223 of (764, 140) and (18x, 84) with S=(0, 1222) equals 438 + 50x */ val mod = BigInteger("1223") - val wp = weilParing(mod, + val wp = weilParing( + mod, BigInteger("408"), Pair(FP2Value(mod, BigInteger("764")), FP2Value(mod, BigInteger("140"))), Pair(FP2Value(mod, b = BigInteger("18")), FP2Value(mod, BigInteger("84"))), - Pair(FP2Value(mod, BigInteger.ZERO), FP2Value(mod, BigInteger("1222")))) + Pair(FP2Value(mod, BigInteger.ZERO), FP2Value(mod, BigInteger("1222"))) + ) assertEquals(wp.a, BigInteger("438")) assertEquals(wp.b, BigInteger("50")) diff --git a/tracker/src/main/java/nl/tudelft/ipv8/tracker/SimpleChurn.kt b/tracker/src/main/java/nl/tudelft/ipv8/tracker/SimpleChurn.kt index af0bd37c..ebe5f34b 100644 --- a/tracker/src/main/java/nl/tudelft/ipv8/tracker/SimpleChurn.kt +++ b/tracker/src/main/java/nl/tudelft/ipv8/tracker/SimpleChurn.kt @@ -8,7 +8,7 @@ import java.util.* private val logger = KotlinLogging.logger {} class SimpleChurn( - private val overlay: Overlay + override val overlay: Overlay ) : DiscoveryStrategy { override fun takeStep() { synchronized(overlay.network.graphLock) {