diff --git a/.gitignore b/.gitignore
index 4dab7bb..e91d8d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.idea
.gradle
+build
local.properties
\ No newline at end of file
diff --git a/README.md b/README.md
index dbe3853..b79524c 100644
--- a/README.md
+++ b/README.md
@@ -4,44 +4,15 @@ In a nutshell, PolyProtect combines subject-specific secret information (also ca
The auxiliary data consist of two parts: the coefficients (an array of signed integers excluding zero) and the exponents (an array of positive signed integers). The two arrays have the same length and they are used to perform a polynomial transformation on several slices of the input (unprotected) face template.
The output of this transformation is the protected template.
-# Repository tree directory
+# PolyProtect configuration
-The ```src``` folder contains two subfolders:
-- ```src/main```, in which the ```PolyProtect``` class is defined.
-- ```src/test/java/com/simprints/biometrics_simpolyprotect```, which contains a simple unit test with dummy templates.
+- ```polynomialDegree: Int = 7```: degree of the polynomial used in the PolyProtect transformation. The minimum value which would provide any security is 5, since there is no closed form algebraic expression for solving polynomials of degree 5 or higher with arbitrary coefficients.
+ This variable has an important impact on the system design as it defines the maximum number of subjects that can be enrolled with different sets of exponents, which are *half* of the secret on which PolyProtect relies (the other half would be given by the polynomial coefficients). Such number is computed as the factorial of ```polynomialDegree```. For example, if ```polynomialDegree = 7```, then ```polynomialDegree!``` = 5040. That is, with ```polynomialDegree``` being 7 the maximum number of subjects that can be enrolled in the database is 5040. The maximum value for ```polynomialDegree``` is 14. Although, the upper limit of ```polynomialDegree``` is bound by the fact that the embeddings produced by NN-based feature extractors often consist of floating point values smaller than 1, which would cause large powers to effectively obliterate certain embedding elements during the PolyProtect transformation. However, 14! = 87,178,291,200 which would cover over ten times the entire world population.
+ At the bottom of this page it is possible to find a [table of factorials](#table-of-factorials) for the [5,14] range. In the original paper, ```polynomialDegree``` is named *m*.
-# Class overview
-
-In this section we provide a description of the ```PolyProtect``` class.
-
-## Data classes
-
-- ```secretAuxDataRecord(val subjectId: String, val coefficients: IntArray, val exponents: IntArray)```: simple data class to handle the subject-specific secret parameters. The first element represents the subject's ID, the second and third element are subject-specific secret parameters used in the PolyProtect transformation, respectively corresponding to the coefficients and exponents of a polynomial function.
-
-- ```protectedTemplateRecord(val subjectId: String, val protectedTemplate: DoubleArray)```: simple data class to handle the protected templates. The first element represents the subject's ID, and the second element is the protected template, _i.e._ output of the PolyProtect transformation.
-
-## Methods
-
-- ```transformTemplate(subjectId: String, unprotectedTemplate: DoubleArray, subjectSecretAuxData: secretAuxDataRecord): protectedTemplateRecord```: method to transform the unprotected template using the PolyProtect algorithm. Internally, the method uses the ```_OVERLAP``` variable.
-
-- ```generateSecretAuxDataRecord(subjectId: String): secretAuxDataRecord```: method to randomly generate secret coefficients and exponents (auxiiliary data). Internally, the method uses the ```_POLYNOMIALDEGREE``` and ```_COEFFICIENTABSMAX``` variables.
-
-- ```computeScore(array1: DoubleArray, array2: DoubleArray): Double```: method to generate a score in the [0,1] range based on cosine distance.
-
-## Variables
-
-- ```_POLYNOMIALDEGREE: Int = 7```: degree of the polynomial used in the PolyProtect transformation. The minimum value which would provide any security is 5, since there is no closed form algebraic expression for solving polynomials of degree 5 or higher with arbitrary coefficients.
- This variable has an important impact on the system design as it defines the maximum number of subjects that can be enrolled with different sets of exponents, which are *half* of the secret on which PolyProtect relies (the other half would be given by the polynomial coefficients). Such number is computed as the factorial of ```_POLYNOMIALDEGREE```. For example, if ```_POLYNOMIALDEGREE = 7```, then ```_POLYNOMIALDEGREE!``` = 5040. That is, with ```_POLYNOMIALDEGREE``` being 7 the maximum number of subjects that can be enrolled in the database is 5040. The maximum value for ```_POLYNOMIALDEGREE``` is 14. Although required with an ```assert``` in the setter, the upper limit of ```_POLYNOMIALDEGREE``` is bound by the fact that the embeddings produced by NN-based feature extractors often consist of floating point values smaller than 1, which would cause large powers to effectively obliterate certain embedding elements during the PolyProtect transformation. However, 14! = 87,178,291,200 which would cover over ten times the entire world population.
- At the bottom of this page it is possible to find a [table of factorials](#table-of-factorials) for the [5,14] range. In the original paper, ```_POLYNOMIALDEGREE``` is named *m*.
-
-- ```_COEFFICIENTABSMAX: Int = 100```: range limit for the coefficients of the polynomial used in the PolyProtect transformation. The coefficients will be drawn from the [-```_COEFFICIENTABSMAX```, ```_COEFFICIENTABSMAX```] range excluding 0.
-
-- ```_OVERLAP: Int = 2```: overlap of the intervals of the unprotected template to transform using the PolyProtect polynomial transformation. It has an impact on the security _vs._ recognition accuracy tradeoff. Specifically, as you increase the ```_OVERLAP```, the accuracy of the system improves and the security of protected template decreases. It can go from 0 to ```_POLYNOMIALDEGREE``` - 1. The safest options would be: {0, 1, 2, 3}.
-
-**NOTE 1**: All three variables are provided with getter/setter methods.
-
-**NOTE 2**: The size of the protected templates depends on ```_POLYNOMIALDEGREE``` and ```_OVERLAP```.
+- ```coefficientAbsMax: Int = 100```: range limit for the coefficients of the polynomial used in the PolyProtect transformation. The coefficients will be drawn from the [-```coefficientAbsMax```, ```coefficientAbsMax```] range excluding 0.
+- ```overlap: Int = 2```: overlap of the intervals of the unprotected template to transform using the PolyProtect polynomial transformation. It has an impact on the security _vs._ recognition accuracy tradeoff. Specifically, as you increase the ```overlap```, the accuracy of the system improves and the security of protected template decreases. It can go from 0 to ```polynomialDegree``` - 1. The safest options would be: {0, 1, 2, 3}.
# Workflow
@@ -49,7 +20,7 @@ In this section we report the sequence diagrams are included for integrating Pol
## Enrolment
-During a subject's enrolment, firstly, a set of coefficients and exponents (```secretAuxDataRecord``` data class) are randomly generated by the ```generateSecretAuxDataRecord``` method.
+During a subject's enrolment, firstly, a set of coefficients and exponents (```AuxData``` data class) are randomly generated by the ```generateAuxData``` method.
Secondly, it is necessary to check that the same coefficients and exponents have not been assigned to previously enrolled subjects by reading the auxiliary data database, where they are stored.
Once this check is completed, the auxiliary data can be saved in the database. Then, the unprotected template can be transformed with PolyProtect using the ```transformTemplate``` method. The output of this operation is the protected template, which can be saved in a separate database for protected templates.
The unprotected template should NOT be stored.
@@ -60,33 +31,33 @@ The unprotected template should NOT be stored.
## Verification
-For verification, the ```subjectId``` of the subject whose identity is about to be verified is known. Consequently, we read the auxiliary data database to obtain the corresponding ```secretAuxDataRecord```.
-Then, we transform the unprotected query template so that it can be compared directly with the protected reference (enrolment) template with the ```transformTemplate``` method. Consequently, we read the protected template database and we return the ```protectedTemplateRecord```.
-The matching of the two templates is carried out using the ```computeScore``` method, which returns a score in the [0, 1] range, being 1 a perfect match.
+For verification, the ```AuxData``` of the subject whose identity is about to be verified is known. Consequently, we read the auxiliary data database to obtain the corresponding ```AuxData```.
+Then, we transform the unprotected query template so that it can be compared directly with the protected reference (enrolment) template with the ```transformTemplate``` method. Consequently, we read the protected template database and we return the protected template.
+The matching of the two templates is carried out using the ```computeSimilarityScore``` function, which returns a score in the [0, 1] range, being 1 a perfect match.

## Identification
-For identification, the ```subjectId``` of the subject whose identity is about to be verified is not known. Consequently, it is necessary to consider all ```secretAuxDataRecord``` stored. For this reason, we implement a for loop in the sequence diagram below. First, we initialise a list in which the scores and the corresponding ```subjectIds``` can be stored. In each iteration of the for loop, one enrolled subject per time is considered.
-Their ```secretAuxDataRecord``` is obtained from the database and used to transform the unprotected query template.
-Then, the ```protectedTemplateRecord``` corresponding to the ```secretAuxDataRecord``` of this iteration is retrieved so that it can be compared directly with the protected query template.
-The matching of the two templates is carried out using the ```computeScore``` method, which returns a score in the [0, 1] range, being 1 a perfect match. In each iteration, the score is appended to the previously created list together with the ```subjectId```.
+For identification, the ```AuxData``` of the subject whose identity is about to be verified is not known. Consequently, it is necessary to consider all ```AuxData``` stored. For this reason, we implement a for loop in the sequence diagram below. First, we initialise a list in which the scores and the corresponding ```subjectIds``` can be stored. In each iteration of the for loop, one enrolled subject per time is considered.
+Their ```AuxData``` is obtained from the database and used to transform the unprotected query template.
+Then, the protected template corresponding to the ```AuxData``` of this iteration is retrieved so that it can be compared directly with the protected query template.
+The matching of the two templates is carried out using the ```computeSimilarityScore``` method, which returns a score in the [0, 1] range, being 1 a perfect match. In each iteration, the score is appended to the previously created list together with the ```subjectId```.
Finally, the list is sorted by the score and the N most similar matches are considered.

# Table of factorials
-| *x* (corresponding to ```_POLYNOMIALDEGREE```) | *x!* (corresponding to maximum number of enrolled subjects) |
-| :------: | :----: |
-| 5 | 120 |
-| 6 | 720 |
-| 7 | 5,040 |
-| 8 | 40,320 |
-| 9 | 362,880 |
-| 10 | 3,628,800 |
-| 11 | 39,916,800 |
-| 12 | 479,001,600 |
-| 13 | 6,227,020,800 |
-| 14 | 87,178,291,200 |
+| *x* (corresponding to ```polynomialDegree```) | *x!* (corresponding to maximum number of enrolled subjects) |
+|:---------------------------------------------:|:-----------------------------------------------------------:|
+| 5 | 120 |
+| 6 | 720 |
+| 7 | 5,040 |
+| 8 | 40,320 |
+| 9 | 362,880 |
+| 10 | 3,628,800 |
+| 11 | 39,916,800 |
+| 12 | 479,001,600 |
+| 13 | 6,227,020,800 |
+| 14 | 87,178,291,200 |
diff --git a/build.gradle.kts b/build.gradle.kts
index 1d0ef06..d1facee 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,12 +1,11 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
- id("org.jetbrains.kotlin.plugin.parcelize") version "2.1.0-RC"
`maven-publish`
}
-val projectGroupId = "com.simprints"
-val projectArtifactId = "biometrics_simpolyprotect"
+val projectGroupId = "com.simprints.biometrics"
+val projectArtifactId = "polyprotect"
val projectVersion = "2024.4.0"
android {
@@ -25,12 +24,7 @@ android {
}
dependencies {
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.appcompat)
- implementation(libs.material)
testImplementation(libs.junit)
- androidTestImplementation(libs.androidx.junit)
- androidTestImplementation(libs.androidx.espresso.core)
}
publishing {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 9c416e4..40add2a 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,12 +1,6 @@
pluginManagement {
repositories {
- google {
- content {
- includeGroupByRegex("com\\.android.*")
- includeGroupByRegex("com\\.google.*")
- includeGroupByRegex("androidx.*")
- }
- }
+ google()
mavenCentral()
gradlePluginPortal()
}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 9b65eb0..f55de8c 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/src/main/kotlin/biometrics_simpolyprotect/PolyProtect.kt b/src/main/kotlin/biometrics_simpolyprotect/PolyProtect.kt
deleted file mode 100644
index 6c795a9..0000000
--- a/src/main/kotlin/biometrics_simpolyprotect/PolyProtect.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-import kotlin.math.pow
-import android.os.Parcelable
-import kotlinx.parcelize.Parcelize
-
-
-class PolyProtect {
-
- @Parcelize
- data class secretAuxDataRecord(
- val subjectId: String,
- val coefficients: IntArray,
- val exponents: IntArray,
- ) : Parcelable
-
- @Parcelize
- data class protectedTemplateRecord(
- val subjectId: String,
- val protectedTemplate: DoubleArray
- ) : Parcelable
-
-
- companion object {
-
- private var _COEFFICIENTABSMAX: Int = 100
- private var _POLYNOMIALDEGREE: Int = 7
- private var _OVERLAP: Int = 2
-
- var POLYNOMIALDEGREE: Int
- get() = _POLYNOMIALDEGREE
- set(value) {
- assert((value <= 14) and (value >= 5))
- {"Assertion failed: the degree of the polynomial should be >=5 and <=14."}
- _POLYNOMIALDEGREE = value
- }
-
- var COEFFICIENTABSMAX: Int
- get() = _COEFFICIENTABSMAX
- set(value) {
- assert((value > 0))
- {"Assertion failed: the maximum coefficient value should be a positive integer."}
- _COEFFICIENTABSMAX = value
- }
-
- var OVERLAP: Int
- get() = _OVERLAP
- set(value) {
- assert((value >= 0) and (value <= _POLYNOMIALDEGREE-1))
- {"Assertion failed: the overlap value should be >=0 and <= polynomial degree - 1."}
- _OVERLAP = value
- }
-
- fun transformTemplate(unprotectedTemplate: DoubleArray,
- subjectSecretAuxData: secretAuxDataRecord
- ): protectedTemplateRecord {
-
- val (_, c, e) = subjectSecretAuxData // For convenience
- assert(e.size == c.size) { "Auxiliary data sizes must be equal." }
-
- val stepSize = e.size - OVERLAP
- val eIndices = e.indices
-
- val protectedTemplate = mutableListOf()
- for (templateIndex in 0..(unprotectedTemplate.lastIndex - OVERLAP) step stepSize) {
- val s = eIndices
- .map { i ->
- // If the target element is out of bounds, consider it 0 since 0^n==0
- // This would be the same as padding the provided array up to certain size
- if (templateIndex + i > unprotectedTemplate.lastIndex) {
- 0.0
- } else {
- unprotectedTemplate[templateIndex + i].pow(e[i]).times(c[i])
- }
- }
- .sum()
- protectedTemplate.add(s)
- }
- return protectedTemplateRecord(subjectSecretAuxData.subjectId, protectedTemplate.toDoubleArray())
- }
-
- fun generateSecretAuxDataRecord(subjectId: String): secretAuxDataRecord {
- // Create a list that excludes 0, combining the ranges (-coefficientAbsMax to -1) and (1 to coefficientAbsMax)
- val coefficientRange = (-COEFFICIENTABSMAX until 0).toList() + (1..COEFFICIENTABSMAX).toList()
- // Shuffle the list randomly
- val shuffledList = coefficientRange.shuffled()
- // Return a sublist of the first 'polynomialDegree' elements
- val coefficients = shuffledList.take(POLYNOMIALDEGREE).toIntArray()
-
- // Create a list of exponents (1 to polynomialDegree)
- val exponentRange = (1..POLYNOMIALDEGREE)
- // Shuffle the list randomly
- val exponents = exponentRange.shuffled().toIntArray()
-
- return secretAuxDataRecord(subjectId, coefficients, exponents)
- }
-
- fun computeScore(array1: DoubleArray, array2: DoubleArray): Double {
- require(array1.size == array2.size) { "Arrays must be of the same size." }
-
- // Calculate the dot product of array1 and array2
- val dotProduct = array1.zip(array2) { x, y -> x * y }.sum()
- // Calculate the magnitudes of array1 and array2
- val magnitudeA = Math.sqrt(array1.map { it * it }.sum())
- val magnitudeB = Math.sqrt(array2.map { it * it }.sum())
- // Check to avoid division by zero
- if (magnitudeA == 0.0 || magnitudeB == 0.0) {
- throw IllegalArgumentException("Arrays must not be zero vectors.")
- }
- // Calculate cosine similarity
- val cosineSimilarity = dotProduct / (magnitudeA * magnitudeB)
- // Calculate cosine distance
- return (2.0-1.0 * cosineSimilarity)/2
- }
- }
-}
diff --git a/src/main/kotlin/com/simprints/biometrics/polyprotect/AuxData.kt b/src/main/kotlin/com/simprints/biometrics/polyprotect/AuxData.kt
new file mode 100644
index 0000000..11138a1
--- /dev/null
+++ b/src/main/kotlin/com/simprints/biometrics/polyprotect/AuxData.kt
@@ -0,0 +1,13 @@
+package com.simprints.biometrics.polyprotect
+
+/**
+ * Simple data class to handle the template-specific secret parameters used in the PolyProtect
+ * transformation, namely the coefficients and exponents of a polynomial function.
+ *
+ * @param coefficients of a polynomial function
+ * @param exponents of a polynomial function
+ */
+data class AuxData(
+ val coefficients: IntArray,
+ val exponents: IntArray,
+)
diff --git a/src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt b/src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt
new file mode 100644
index 0000000..c0ba861
--- /dev/null
+++ b/src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt
@@ -0,0 +1,102 @@
+package com.simprints.biometrics.polyprotect
+
+import kotlin.math.pow
+
+/**
+ * The main entry point and configuration holder for the PolyProtect algorithm.
+ *
+ * @param polynomialDegree degree of the polynomial used in the PolyProtect transformation. Must be in `[5;14]` range.
+ *
+ * @param coefficientAbsMax range limit for the coefficients of the polynomial used in the PolyProtect transformation.
+ * The coefficients will be drawn from the `[-coefficientAbsMax, coefficientAbsMax]` range excluding 0.
+ *
+ * @param overlap of the intervals of the unprotected template to transform using the PolyProtect polynomial transformation.
+ * It can go from 0 to `polynomialDegree - 1`. The safest options would be: {0, 1, 2, 3}.
+ */
+class PolyProtect(
+ private val polynomialDegree: Int = POLYNOMIAL_DEGREE_DEFAULT,
+ private val coefficientAbsMax: Int = COEFFICIENT_ABS_MAX_DEFAULT,
+ private val overlap: Int = OVERLAP_DEFAULT,
+) {
+
+ init {
+ require(polynomialDegree in 5..14) {
+ "Assertion failed: the degree of the polynomial should be >=5 and <=14."
+ }
+ require(coefficientAbsMax > 0) {
+ "Assertion failed: the maximum coefficient value should be a positive integer."
+ }
+ require(overlap in 0..(polynomialDegree - 1)) {
+ "Assertion failed: the overlap value should be >=0 and <= polynomial degree - 1."
+ }
+ }
+
+ /**
+ * Applies PolyProtect transformation to the provided unprotected template
+ * using the accompanying `AuxData` values
+ *
+ * **NOTE**: The size of the protected templates depends on `polynomialDegree` and `overlap`.
+ *
+ * @param unprotectedTemplate
+ * @param auxData a set of coefficients and exponents associated with specific biometric record
+ *
+ * @return protected template
+ */
+ fun transformTemplate(
+ unprotectedTemplate: DoubleArray,
+ auxData: AuxData
+ ): DoubleArray {
+ val (coefficients, exponents) = auxData // For convenience
+ require(exponents.size == coefficients.size) { "Auxiliary data sizes must be equal." }
+ require(exponents.size == polynomialDegree) {
+ "Auxiliary data sizes must be equal to polynomial degree."
+ }
+
+ val stepSize = exponents.size - overlap
+
+ val protectedTemplate = mutableListOf()
+ for (templateIndex in 0..(unprotectedTemplate.lastIndex - overlap) step stepSize) {
+ val s = exponents.indices.map { i ->
+ // If the target element is out of bounds, consider it 0 since 0^n==0
+ // This would be the same as padding the provided array up to certain size
+ if (templateIndex + i > unprotectedTemplate.lastIndex) {
+ 0.0
+ } else {
+ unprotectedTemplate[templateIndex + i]
+ .pow(exponents[i])
+ .times(coefficients[i])
+ }
+ }.sum()
+ protectedTemplate.add(s)
+ }
+ return protectedTemplate.toDoubleArray()
+ }
+
+ /**
+ * @return Randomly generated coefficients and exponents (auxiliary data) according
+ * to the configured `polynomialDegree` and `coefficientAbsMax` values.
+ */
+ fun generateAuxData(): AuxData {
+ // Create a list that excludes 0, combining the ranges (-coefficientAbsMax to -1) and (1 to coefficientAbsMax)
+ val coefficientRange =
+ (-coefficientAbsMax until 0).toList() + (1..coefficientAbsMax).toList()
+ // Shuffle the list randomly
+ val shuffledList = coefficientRange.shuffled()
+ // Return a sublist of the first 'polynomialDegree' elements
+ val coefficients = shuffledList.take(polynomialDegree).toIntArray()
+
+ // Create a list of exponents (1 to polynomialDegree)
+ val exponentRange = (1..polynomialDegree)
+ // Shuffle the list randomly
+ val exponents = exponentRange.shuffled().toIntArray()
+
+ return AuxData(coefficients, exponents)
+ }
+
+ companion object {
+
+ private const val COEFFICIENT_ABS_MAX_DEFAULT: Int = 100
+ private const val POLYNOMIAL_DEGREE_DEFAULT: Int = 7
+ private const val OVERLAP_DEFAULT: Int = 2
+ }
+}
diff --git a/src/main/kotlin/com/simprints/biometrics/polyprotect/SimilarityScore.kt b/src/main/kotlin/com/simprints/biometrics/polyprotect/SimilarityScore.kt
new file mode 100644
index 0000000..ec207ca
--- /dev/null
+++ b/src/main/kotlin/com/simprints/biometrics/polyprotect/SimilarityScore.kt
@@ -0,0 +1,32 @@
+package com.simprints.biometrics.polyprotect
+
+import kotlin.math.sqrt
+
+/**
+ * Calculates a similarity score based on cosine distance.
+ *
+ * @param array1 first biometric template
+ * @param array2 another biometric template
+ *
+ * @return a value in in the `[0,1]` range
+ */
+fun computeSimilarityScore(
+ array1: DoubleArray,
+ array2: DoubleArray,
+): Double {
+ require(array1.size == array2.size) { "Arrays must be of the same size." }
+
+ // Calculate the dot product of array1 and array2
+ val dotProduct = array1.zip(array2) { x, y -> x * y }.sum()
+ // Calculate the magnitudes of array1 and array2
+ val magnitudeA = sqrt(array1.map { it * it }.sum())
+ val magnitudeB = sqrt(array2.map { it * it }.sum())
+ // Check to avoid division by zero
+ check(magnitudeA > 0.0 && magnitudeB > 0.0) { "Arrays must not be zero vectors." }
+ // Calculate cosine similarity
+ val cosineSimilarity = dotProduct / (magnitudeA * magnitudeB)
+ // Calculate cosine distance
+ val cosineDistance = 1 - cosineSimilarity
+
+ return (2.0 - 1.0 * cosineDistance) / 2
+}
diff --git a/src/test/java/com/simprints/biometrics/polyprotect/ComputeSimilarityScoreTest.kt b/src/test/java/com/simprints/biometrics/polyprotect/ComputeSimilarityScoreTest.kt
new file mode 100644
index 0000000..4cccb62
--- /dev/null
+++ b/src/test/java/com/simprints/biometrics/polyprotect/ComputeSimilarityScoreTest.kt
@@ -0,0 +1,41 @@
+package com.simprints.biometrics.polyprotect
+
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import kotlin.random.Random
+
+
+class ComputeSimilarityScoreTest {
+
+ @Test
+ fun `similarity between two protected templates should be between 0 and 1`() {
+ // Creation of two unprotected templates (randomly generated)
+ val unprotectedTemplate1 = DoubleArray(512) { Random.nextDouble() - 0.5 }
+ val unprotectedTemplate2 = DoubleArray(512) { Random.nextDouble() - 0.5 }
+
+ // Custom parameters
+ val polyProtect = PolyProtect(
+ polynomialDegree = 7,
+ coefficientAbsMax = 100,
+ overlap = 2,
+ )
+
+ // Generation of subject-specific secret parameters (auxiliary data)
+ val secrets1 = polyProtect.generateAuxData()
+ val secrets2 = polyProtect.generateAuxData()
+
+ // PolyProtect transformation: the unprotected templates are transformed using the
+ // subject-specific secret auxiliary data
+ val protectedRecord1 = polyProtect.transformTemplate(
+ unprotectedTemplate = unprotectedTemplate1, auxData = secrets1
+ )
+ val protectedRecord2 = polyProtect.transformTemplate(
+ unprotectedTemplate = unprotectedTemplate2, auxData = secrets2
+ )
+
+ // Score in the [0, 1] range based on cosine similarity: 1 = perfect match
+ val score = computeSimilarityScore(protectedRecord1, protectedRecord2)
+
+ assertTrue("Score should be between 0 and 1.", score in 0.0..1.0)
+ }
+}
diff --git a/src/test/java/com/simprints/biometrics/polyprotect/PolyProtectUnitTest.kt b/src/test/java/com/simprints/biometrics/polyprotect/PolyProtectUnitTest.kt
new file mode 100644
index 0000000..0354103
--- /dev/null
+++ b/src/test/java/com/simprints/biometrics/polyprotect/PolyProtectUnitTest.kt
@@ -0,0 +1,69 @@
+package com.simprints.biometrics.polyprotect
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import kotlin.random.Random
+
+
+class PolyProtectUnitTest {
+
+ @Test
+ fun `test generate secret auxiliary data record`() {
+ // Custom parameters (relevant to the generation of secret auxiliary data record)
+ val polyProtect = PolyProtect(
+ polynomialDegree = 7,
+ coefficientAbsMax = 100,
+ )
+
+ // Generation of subject-specific secret parameters (auxiliary data)
+ val secrets1 = polyProtect.generateAuxData()
+
+ assertEquals(
+ "Exponents count is same as provided polynomial degree", 7, secrets1.exponents.size
+ )
+ assertTrue(
+ "The exponents should include values from 1 to PolyProtect.POLYNOMIALDEGREE.",
+ secrets1.exponents.sortedArray().contentEquals(IntArray(7) { it + 1 })
+ )
+
+ assertEquals(
+ "Coefficient count is same as provided polynomial degree", 7, secrets1.coefficients.size
+ )
+ assertTrue("The coefficient values should not be outside the allowed range",
+ secrets1.coefficients.all { it in -100..100 })
+ assertTrue("The coefficient values must not be 0", secrets1.coefficients.none { it == 0 })
+
+ }
+
+ @Test
+ fun `test transform a template`() {
+ // Dummy coefficients
+ val coefficients = intArrayOf(18, -26, 30, 4, 59, 10, -91)
+ // Dummy exponents
+ val exponents = intArrayOf(1, 5, 3, 4, 6, 2, 7)
+
+ val subjectSecretAuxData = AuxData(
+ coefficients = coefficients, exponents = exponents
+ )
+
+ // Custom parameters (relevant to the generation of secret auxiliary data record)
+ val polyProtect = PolyProtect(
+ polynomialDegree = 7,
+ coefficientAbsMax = 100,
+ overlap = 2,
+ )
+
+ // Creation of two unprotected templates (randomly generated)
+ val unprotectedTemplate = DoubleArray(512) { Random.nextDouble() - 0.5 }
+
+ // PolyProtect trandformation: the unprotected templates are transformed using the
+ // subject-specific secret auxiliary data
+ val protectedTemplate = polyProtect.transformTemplate(
+ unprotectedTemplate = unprotectedTemplate, auxData = subjectSecretAuxData
+ )
+
+ assertFalse(unprotectedTemplate.contentEquals(protectedTemplate))
+ }
+}
diff --git a/src/test/java/com/simprints/biometrics_simpolyprotect/ExampleUnitTest.kt b/src/test/java/com/simprints/biometrics_simpolyprotect/ExampleUnitTest.kt
deleted file mode 100644
index c39e1d6..0000000
--- a/src/test/java/com/simprints/biometrics_simpolyprotect/ExampleUnitTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.simprints.biometrics_simpolyprotect
-import PolyProtect
-import org.junit.Test
-import kotlin.random.Random
-
-
-class ExampleUnitTest {
- @Test
-
- fun exampleUsage() {
-
- val subjectId1 = "myUniqueId1"
- val subjectId2 = "myUniqueId2"
-
- // Creation of two unprotected templates (randomly generated)
- val unprotectedTemplate1 = DoubleArray(512) { Random.nextDouble() - 0.5 }
- val unprotectedTemplate2 = DoubleArray(512) { Random.nextDouble() - 0.5 }
-
- // Custom parameters
- PolyProtect.POLYNOMIALDEGREE = 7
- PolyProtect.COEFFICIENTABSMAX = 100
- PolyProtect.OVERLAP = 2
-
- // Generation of subject-specific secret parameters (auxiliary data)
- val secrets1 = PolyProtect.generateSecretAuxDataRecord(subjectId = subjectId1)
- val secrets2 = PolyProtect.generateSecretAuxDataRecord(subjectId = subjectId2)
-
- assert(secrets1::class.simpleName == "secretAuxDataRecord")
- {"Secrets should be objects of the secretAuxDataRecord class."}
- assert(secrets1.subjectId::class.simpleName == "String")
- {"The subjectId should be a String."}
- assert(secrets1.coefficients::class.simpleName == "IntArray")
- {"The coefficients should be an IntArray."}
- assert(secrets1.exponents::class.simpleName == "IntArray")
- {"The exponents should be an IntArray."}
-
- assert(unprotectedTemplate1::class.simpleName == "DoubleArray")
- {"The unprotected template should be a DoubleArray."}
-
- /*
- PolyProtect tranformation: the unprotected templates are transformed using the
- subject-specific secret auxiliary data
- */
- val protectedTemplate1 = PolyProtect.transformTemplate(unprotectedTemplate = unprotectedTemplate1,
- subjectSecretAuxData = secrets1)
- val protectedTemplate2 = PolyProtect.transformTemplate(unprotectedTemplate = unprotectedTemplate2,
- subjectSecretAuxData = secrets2)
-
- // Score in the [0, 1] range based on cosine similarity: 1 = perfect match
- val score = PolyProtect.computeScore(protectedTemplate1.protectedTemplate,
- protectedTemplate2.protectedTemplate)
-
- }
-}
\ No newline at end of file