diff --git a/README.md b/README.md index b79524c..534fad1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The output of this transformation is the protected template. # Workflow -In this section we report the sequence diagrams are included for integrating PolyProtect in the opserations of enrolment, verification, and indentification. +In this section we report the sequence diagrams are included for integrating PolyProtect in the operations of enrolment, verification, and identification. ## Enrolment diff --git a/gradle.properties b/gradle.properties index 20e2a01..132244e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true diff --git a/images/Enrolment.png b/images/Enrolment.png index 893b524..1219ab0 100644 Binary files a/images/Enrolment.png and b/images/Enrolment.png differ diff --git a/images/Enrolment.txt b/images/Enrolment.txt index 3bf1e0d..66b3cde 100644 --- a/images/Enrolment.txt +++ b/images/Enrolment.txt @@ -6,10 +6,9 @@ database AuxData database RefData -note over PolyProtect:Pre-configured parameters (with getter/setter):\n\n""private var **_OVERLAP**: Int = 2\nprivate var **_COEFFICIENTABSMAX**: Int = 100\nprivate var **_POLYNOMIALDEGREE**: Int = 7""\n\nData classes:""\n\ndata class **secretAuxDataRecord**(\nval subjectId: String,\nval coefficients: IntArray,\nval exponents: IntArray)\n\ndata class **protectedTemplateRecord**(\nval subjectId: String,\nval protectedTemplate: DoubleArray)\n""\nMethods:""\n\nfun **generateSecretAuxDataRecord**(\nsubjectId: String):\nsecretAuxDataRecord\n\nfun **transformTemplate**(\nunprotectedTemplate: DoubleArray,\nsubjectSecretAuxData: secretAuxDataRecord):\n protectedTemplateRecord\n\nfun **computeScore**(array1: DoubleArray,\narray2: DoubleArray): Double"" - -Unprotected Biometric System->PolyProtect:""**generateSecretAuxDataRecord**(subjectId)"" -PolyProtect-->Unprotected Biometric System:""**return**\nsecretAuxDataRecord(subjectId, coefficients, exponents)"" +note over PolyProtect:Pre-configured parameters (with getter/setter):\n\n""private var **_OVERLAP**: Int = 2\nprivate var **_COEFFICIENTABSMAX**: Int = 100\nprivate var **_POLYNOMIALDEGREE**: Int = 7""\n\nData classes:""\n\ndata class **AuxData**(\nval coefficients: ByteArray,\nval exponents: ByteArray)\n""\nMethods:""\n\nfun **generateAuxData**():\n AuxData\n\nfun **transformTemplate**(\nunprotectedTemplate: ByteArray,\nauxData: AuxData):\n ByteArray\n\nfun **computeSimilarityScore**(array1: ByteArray,\narray2: ByteArray):\n Double"" +Unprotected Biometric System->PolyProtect:""**generateAuxData**()"" +PolyProtect-->Unprotected Biometric System:""**return**\n AuxData(coefficients, exponents)"" group #2f2e7b While Loop #white [condition: check#1 == true or check#2 == true] @@ -20,16 +19,16 @@ AuxData-->Unprotected Biometric System:check#1: ""true"" or Unprotected Biometric System->AuxData:check#2: ""exponents"" already assigned to previously enrolled subject? AuxData-->Unprotected Biometric System:check#2: ""true"" or ""false"" -Unprotected Biometric System->PolyProtect:""**generateSecretAuxDataRecord**(subjectId)"" -PolyProtect-->Unprotected Biometric System:""**return**\nsecretAuxDataRecord(subjectId, coefficients, exponents)"" +Unprotected Biometric System->PolyProtect:""**generateAuxData**()"" +PolyProtect-->Unprotected Biometric System:""**return**\n AuxData"" end -Unprotected Biometric System->AuxData:Save ""secretAuxDataRecord(subjectId, coefficients, exponents)"" +Unprotected Biometric System->AuxData:Save ""AuxData"" -Unprotected Biometric System->PolyProtect:""**transformTemplate**(unprotectedTemplate,\nsubjectSecretAuxData) +Unprotected Biometric System->PolyProtect:""**transformTemplate**(unprotectedTemplate, AuxData) -PolyProtect-->Unprotected Biometric System:""**return**\nprotectedTemplateRecord(subjectId, protectedTemplate)"" +PolyProtect-->Unprotected Biometric System:""**return**\n ByteArray"" (protected template) -Unprotected Biometric System->RefData:Save ""protectedTemplateRecord(subjectId, protectedTemplate)"" \ No newline at end of file +Unprotected Biometric System->RefData:Save protected template \ No newline at end of file diff --git a/images/Identification.png b/images/Identification.png index d27d97a..1219ab0 100644 Binary files a/images/Identification.png and b/images/Identification.png differ diff --git a/images/Identification.txt b/images/Identification.txt index da53da3..8e725d8 100644 --- a/images/Identification.txt +++ b/images/Identification.txt @@ -5,30 +5,27 @@ participant PolyProtect database AuxData database RefData - -note over PolyProtect:Pre-configured parameters (with getter/setter):\n\n""private var **_OVERLAP**: Int = 2\nprivate var **_COEFFICIENTABSMAX**: Int = 100\nprivate var **_POLYNOMIALDEGREE**: Int = 7""\n\nData classes:""\n\ndata class **secretAuxDataRecord**(\nval subjectId: String,\nval coefficients: IntArray,\nval exponents: IntArray)\n\ndata class **protectedTemplateRecord**(\nval subjectId: String,\nval protectedTemplate: DoubleArray)\n""\nMethods:""\n\nfun **generateSecretAuxDataRecord**(\nsubjectId: String):\nsecretAuxDataRecord\n\nfun **transformTemplate**(\nunprotectedTemplate: DoubleArray,\nsubjectSecretAuxData: secretAuxDataRecord):\n protectedTemplateRecord\n\nfun **computeScore**(array1: DoubleArray,\narray2: DoubleArray): Double"" - - -note over Unprotected Biometric System: Create list\n""identificationList""\nfor identification\nscores +note over PolyProtect:Pre-configured parameters (with getter/setter):\n\n""private var **_OVERLAP**: Int = 2\nprivate var **_COEFFICIENTABSMAX**: Int = 100\nprivate var **_POLYNOMIALDEGREE**: Int = 7""\n\nData classes:""\n\ndata class **AuxData**(\nval coefficients: ByteArray,\nval exponents: ByteArray)\n""\nMethods:""\n\nfun **generateAuxData**():\n AuxData\n\nfun **transformTemplate**(\nunprotectedTemplate: ByteArray,\nauxData: AuxData):\n ByteArray\n\nfun **computeSimilarityScore**(array1: ByteArray,\narray2: ByteArray):\n Double"" +note over Unprotected Biometric System: Create empty list\n""identificationList""\nfor identification\nscores group #2f2e7b For Loop #white [condition: for subjectId in enrolledSubjectIds] -Unprotected Biometric System->AuxData: Given the ""subjectId"" to be verified, retrieve the corresponding ""secretAuxDataRecord"" +Unprotected Biometric System->AuxData: Given the ""subjectId"" to be compared, retrieve the corresponding ""AuxData"" -AuxData-->Unprotected Biometric System: **return**\n""secretAuxDataRecord(subjectId, coefficients, exponents)"" +AuxData-->Unprotected Biometric System: **return**\n""AuxData"" -Unprotected Biometric System->PolyProtect:""**transformTemplate**(unprotectedQueryTemplate,\nsubjectSecretAuxData) +Unprotected Biometric System->PolyProtect:""**transformTemplate**(unprotectedQueryTemplate, AuxData) -PolyProtect-->Unprotected Biometric System:""**return**\nprotectedTemplateRecord(subjectId, protectedQueryTemplate)"" +PolyProtect-->Unprotected Biometric System:""**return**\nByteArray"" (protected query template) -Unprotected Biometric System->RefData: Given the ""subjectId"" to be verified, retrieve the corresponding ""protectedTemplateRecord"" +Unprotected Biometric System->RefData: Given the ""subjectId"" to be compared, retrieve the corresponding protected reference template -RefData-->Unprotected Biometric System: **return**\n""protectedTemplateRecord(subjectId, protectedTemplate)"" +RefData-->Unprotected Biometric System: **return**\n""ByteArray"" (protected reference template) -Unprotected Biometric System->PolyProtect:""**computeScore**(protectedQueryTemplate,\nprotectedReferenceTemplate) +Unprotected Biometric System->PolyProtect:""**computeSimilarityScore**(ByteArray, ByteArray)""\n (corresponding to protected query and reference templates -PolyProtect-->Unprotected Biometric System:""**return**\nDouble"" +PolyProtect-->Unprotected Biometric System:""**return**\nDouble"" (score) note over Unprotected Biometric System: append\n""[subjectId, score]""\nto ""identificationList"" diff --git a/images/Verification.png b/images/Verification.png index 4b2dc1d..5b43a1f 100644 Binary files a/images/Verification.png and b/images/Verification.png differ diff --git a/images/Verification.txt b/images/Verification.txt index 35f7498..7fcf1f2 100644 --- a/images/Verification.txt +++ b/images/Verification.txt @@ -6,22 +6,21 @@ database AuxData database RefData -note over PolyProtect:Pre-configured parameters (with getter/setter):\n\n""private var **_OVERLAP**: Int = 2\nprivate var **_COEFFICIENTABSMAX**: Int = 100\nprivate var **_POLYNOMIALDEGREE**: Int = 7""\n\nData classes:""\n\ndata class **secretAuxDataRecord**(\nval subjectId: String,\nval coefficients: IntArray,\nval exponents: IntArray)\n\ndata class **protectedTemplateRecord**(\nval subjectId: String,\nval protectedTemplate: DoubleArray)\n""\nMethods:""\n\nfun **generateSecretAuxDataRecord**(\nsubjectId: String):\nsecretAuxDataRecord\n\nfun **transformTemplate**(\nunprotectedTemplate: DoubleArray,\nsubjectSecretAuxData: secretAuxDataRecord):\n protectedTemplateRecord\n\nfun **computeScore**(array1: DoubleArray,\narray2: DoubleArray): Double"" +note over PolyProtect:Pre-configured parameters (with getter/setter):\n\n""private var **_OVERLAP**: Int = 2\nprivate var **_COEFFICIENTABSMAX**: Int = 100\nprivate var **_POLYNOMIALDEGREE**: Int = 7""\n\nData classes:""\n\ndata class **AuxData**(\nval coefficients: ByteArray,\nval exponents: ByteArray)\n""\nMethods:""\n\nfun **generateAuxData**():\n AuxData\n\nfun **transformTemplate**(\nunprotectedTemplate: ByteArray,\nauxData: AuxData):\n ByteArray\n\nfun **computeSimilarityScore**(array1: ByteArray,\narray2: ByteArray):\n Double"" -Unprotected Biometric System->AuxData: Given the ""subjectId"" to be verified, retrieve the corresponding ""secretAuxDataRecord"". +Unprotected Biometric System->AuxData: Given the ""subjectId"" to be verified,\n retrieve the corresponding ""AuxData"". -AuxData-->Unprotected Biometric System: **return**\n""secretAuxDataRecord(subjectId, coefficients, exponents)"" +AuxData-->Unprotected Biometric System: **return**\n""AuxData"" -Unprotected Biometric System->PolyProtect:""**transformTemplate**(unprotectedQueryTemplate,\nsubjectSecretAuxData) +Unprotected Biometric System->PolyProtect:""**transformTemplate**(unprotectedQueryTemplate,\nAuxData) -PolyProtect-->Unprotected Biometric System:""**return**\nprotectedTemplateRecord(subjectId, protectedQueryTemplate)"" +PolyProtect-->Unprotected Biometric System:""**return** ByteArray"" (protected query template) -Unprotected Biometric System->RefData: Given the ""subjectId"" to be verified, retrieve the corresponding ""protectedTemplateRecord"". - -RefData-->Unprotected Biometric System: **return**\n""protectedTemplateRecord(subjectId, protectedTemplate)"" +Unprotected Biometric System->RefData: Given the ""subjectId"" to be verified,\nretrieve the corresponding\nprotected reference template. +RefData-->Unprotected Biometric System: **return**\n""ByteArray"" (protected reference template) Unprotected Biometric System->PolyProtect:""**computeScore**(protectedQueryTemplate,\nprotectedReferenceTemplate) diff --git a/src/main/kotlin/com/simprints/biometrics/polyprotect/AuxData.kt b/src/main/kotlin/com/simprints/biometrics/polyprotect/AuxData.kt index 11138a1..9ddbba3 100644 --- a/src/main/kotlin/com/simprints/biometrics/polyprotect/AuxData.kt +++ b/src/main/kotlin/com/simprints/biometrics/polyprotect/AuxData.kt @@ -8,6 +8,6 @@ package com.simprints.biometrics.polyprotect * @param exponents of a polynomial function */ data class AuxData( - val coefficients: IntArray, - val exponents: IntArray, + val coefficients: ByteArray, + val exponents: ByteArray, ) diff --git a/src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt b/src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt index c0ba861..0b5a02e 100644 --- a/src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt +++ b/src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt @@ -37,39 +37,44 @@ class PolyProtect( * * **NOTE**: The size of the protected templates depends on `polynomialDegree` and `overlap`. * - * @param unprotectedTemplate + * @param unprotectedTemplateByteArray * @param auxData a set of coefficients and exponents associated with specific biometric record * * @return protected template */ fun transformTemplate( - unprotectedTemplate: DoubleArray, + unprotectedTemplate: ByteArray, auxData: AuxData - ): DoubleArray { + ): ByteArray { val (coefficients, exponents) = auxData // For convenience require(exponents.size == coefficients.size) { "Auxiliary data sizes must be equal." } - require(exponents.size == polynomialDegree) { + require(Utils.byteArrayToIntArray(exponents).size == polynomialDegree) { "Auxiliary data sizes must be equal to polynomial degree." } - val stepSize = exponents.size - overlap + // Converting from ByteArray + val unprotectedTemplateDoubleArray = Utils.byteArrayToDoubleArray(unprotectedTemplate) + val coefficientsIntArray = Utils.byteArrayToIntArray(coefficients) + val exponentsIntArray = Utils.byteArrayToIntArray(exponents) + + val stepSize = exponentsIntArray.size - overlap val protectedTemplate = mutableListOf() - for (templateIndex in 0..(unprotectedTemplate.lastIndex - overlap) step stepSize) { - val s = exponents.indices.map { i -> + for (templateIndex in 0..(unprotectedTemplateDoubleArray.lastIndex - overlap) step stepSize) { + val s = exponentsIntArray.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) { + if (templateIndex + i > unprotectedTemplateDoubleArray.lastIndex) { 0.0 } else { - unprotectedTemplate[templateIndex + i] - .pow(exponents[i]) - .times(coefficients[i]) + unprotectedTemplateDoubleArray[templateIndex + i] + .pow(exponentsIntArray[i]) + .times(coefficientsIntArray[i]) } }.sum() protectedTemplate.add(s) } - return protectedTemplate.toDoubleArray() + return Utils.doubleArrayToByteArray(protectedTemplate.toDoubleArray()) } /** @@ -90,7 +95,7 @@ class PolyProtect( // Shuffle the list randomly val exponents = exponentRange.shuffled().toIntArray() - return AuxData(coefficients, exponents) + return AuxData(Utils.intArrayToByteArray(coefficients), Utils.intArrayToByteArray(exponents)) } companion object { diff --git a/src/main/kotlin/com/simprints/biometrics/polyprotect/SimilarityScore.kt b/src/main/kotlin/com/simprints/biometrics/polyprotect/SimilarityScore.kt index ec207ca..71ac394 100644 --- a/src/main/kotlin/com/simprints/biometrics/polyprotect/SimilarityScore.kt +++ b/src/main/kotlin/com/simprints/biometrics/polyprotect/SimilarityScore.kt @@ -11,10 +11,13 @@ import kotlin.math.sqrt * @return a value in in the `[0,1]` range */ fun computeSimilarityScore( - array1: DoubleArray, - array2: DoubleArray, + array1ByteArray: ByteArray, + array2ByteArray: ByteArray, ): Double { - require(array1.size == array2.size) { "Arrays must be of the same size." } + require(array1ByteArray.size == array2ByteArray.size) { "Arrays must be of the same size." } + + val array1 = Utils.byteArrayToDoubleArray(array1ByteArray) + val array2 = Utils.byteArrayToDoubleArray(array2ByteArray) // Calculate the dot product of array1 and array2 val dotProduct = array1.zip(array2) { x, y -> x * y }.sum() diff --git a/src/main/kotlin/com/simprints/biometrics/polyprotect/Utils.kt b/src/main/kotlin/com/simprints/biometrics/polyprotect/Utils.kt new file mode 100644 index 0000000..73f1dc8 --- /dev/null +++ b/src/main/kotlin/com/simprints/biometrics/polyprotect/Utils.kt @@ -0,0 +1,52 @@ +import java.nio.ByteBuffer +import java.nio.ByteOrder + +object Utils { + /** + * Converts a DoubleArray to a ByteArray. + * + * @param doubleArray The DoubleArray to convert. + * @return A ByteArray representing the DoubleArray. + */ + fun doubleArrayToByteArray(doubleArray: DoubleArray): ByteArray { + val byteBuffer = ByteBuffer.allocate(doubleArray.size * 8).order(ByteOrder.nativeOrder()) + byteBuffer.asDoubleBuffer().put(doubleArray) + return byteBuffer.array() + } + + /** + * Converts a ByteArray back to a DoubleArray. + * + * @param byteArray The ByteArray to convert. + * @return A DoubleArray reconstructed from the ByteArray. + */ + fun byteArrayToDoubleArray(byteArray: ByteArray): DoubleArray { + val byteBuffer = ByteBuffer.wrap(byteArray).order(ByteOrder.nativeOrder()) + val doubleBuffer = byteBuffer.asDoubleBuffer() + return DoubleArray(doubleBuffer.remaining()).apply { doubleBuffer.get(this) } + } + + /** + * Converts an IntArray to a ByteArray. + * + * @param intArray The IntArray to convert. + * @return A ByteArray representing the IntArray. + */ + fun intArrayToByteArray(intArray: IntArray): ByteArray { + val byteBuffer = ByteBuffer.allocate(intArray.size * 4).order(ByteOrder.nativeOrder()) + byteBuffer.asIntBuffer().put(intArray) + return byteBuffer.array() + } + + /** + * Converts a ByteArray back to an IntArray. + * + * @param byteArray The ByteArray to convert. + * @return An IntArray reconstructed from the ByteArray. + */ + fun byteArrayToIntArray(byteArray: ByteArray): IntArray { + val byteBuffer = ByteBuffer.wrap(byteArray).order(ByteOrder.nativeOrder()) + val intBuffer = byteBuffer.asIntBuffer() + return IntArray(intBuffer.remaining()).apply { intBuffer.get(this) } + } +} diff --git a/src/test/java/com/simprints/biometrics/polyprotect/ComputeSimilarityScoreTest.kt b/src/test/java/com/simprints/biometrics/polyprotect/ComputeSimilarityScoreTest.kt index 4cccb62..51f3f80 100644 --- a/src/test/java/com/simprints/biometrics/polyprotect/ComputeSimilarityScoreTest.kt +++ b/src/test/java/com/simprints/biometrics/polyprotect/ComputeSimilarityScoreTest.kt @@ -10,8 +10,12 @@ 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 } + val unprotectedTemplateDoubleArray1 = DoubleArray(512) { Random.nextDouble() - 0.5 } + val unprotectedTemplateDoubleArray2 = DoubleArray(512) { Random.nextDouble() - 0.5 } + + val unprotectedTemplate1 = Utils.doubleArrayToByteArray(unprotectedTemplateDoubleArray1) + val unprotectedTemplate2 = Utils.doubleArrayToByteArray(unprotectedTemplateDoubleArray2) + // Custom parameters val polyProtect = PolyProtect( diff --git a/src/test/java/com/simprints/biometrics/polyprotect/PolyProtectUnitTest.kt b/src/test/java/com/simprints/biometrics/polyprotect/PolyProtectUnitTest.kt index 0354103..eab2dbe 100644 --- a/src/test/java/com/simprints/biometrics/polyprotect/PolyProtectUnitTest.kt +++ b/src/test/java/com/simprints/biometrics/polyprotect/PolyProtectUnitTest.kt @@ -21,19 +21,19 @@ class PolyProtectUnitTest { val secrets1 = polyProtect.generateAuxData() assertEquals( - "Exponents count is same as provided polynomial degree", 7, secrets1.exponents.size + "Exponents count is same as provided polynomial degree", 7, Utils.byteArrayToIntArray(secrets1.exponents).size ) assertTrue( "The exponents should include values from 1 to PolyProtect.POLYNOMIALDEGREE.", - secrets1.exponents.sortedArray().contentEquals(IntArray(7) { it + 1 }) + Utils.byteArrayToIntArray(secrets1.exponents).sortedArray().contentEquals(IntArray(7) { it + 1 }) ) assertEquals( - "Coefficient count is same as provided polynomial degree", 7, secrets1.coefficients.size + "Coefficient count is same as provided polynomial degree", 7, Utils.byteArrayToIntArray(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 }) + Utils.byteArrayToIntArray(secrets1.coefficients).all { it in -100..100 }) + assertTrue("The coefficient values must not be 0", Utils.byteArrayToIntArray(secrets1.coefficients).none { it == 0 }) } @@ -45,7 +45,7 @@ class PolyProtectUnitTest { val exponents = intArrayOf(1, 5, 3, 4, 6, 2, 7) val subjectSecretAuxData = AuxData( - coefficients = coefficients, exponents = exponents + coefficients = Utils.intArrayToByteArray(coefficients), exponents = Utils.intArrayToByteArray(exponents) ) // Custom parameters (relevant to the generation of secret auxiliary data record) @@ -56,9 +56,12 @@ class PolyProtectUnitTest { ) // Creation of two unprotected templates (randomly generated) - val unprotectedTemplate = DoubleArray(512) { Random.nextDouble() - 0.5 } + val unprotectedTemplateDoubleArray = DoubleArray(512) { Random.nextDouble() - 0.5 } - // PolyProtect trandformation: the unprotected templates are transformed using the + // Transformation of unprotected template into ByteArray + val unprotectedTemplate = Utils.doubleArrayToByteArray(unprotectedTemplateDoubleArray) + + // PolyProtect transformation: the unprotected templates are transformed using the // subject-specific secret auxiliary data val protectedTemplate = polyProtect.transformTemplate( unprotectedTemplate = unprotectedTemplate, auxData = subjectSecretAuxData