Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: Cache
- name: Restore gradle global directory
uses: actions/cache/restore@v4
id: cache
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
path: |
~/.gradle/
.gradle/
key: global-gradle-
restore-keys: global-gradle-

- name: set up JDK 17
uses: actions/setup-java@v2
Expand Down
14 changes: 7 additions & 7 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ plugins {

val projectGroupId = "com.simprints.biometrics"
val projectArtifactId = "polyprotect"
val projectVersion = "2024.4.2"
val projectVersion = "2025.1.1"

android {
namespace = "$projectGroupId.$projectArtifactId"
compileSdk = 34
compileSdk = 35
defaultConfig {
minSdk = 24
minSdk = 23
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "17"
}
}

dependencies {
testImplementation(libs.junit)
testImplementation(libs.testing.junit)
}

publishing {
Expand Down
16 changes: 3 additions & 13 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
[versions]
agp = "8.6.1"
kotlin = "1.9.0"
coreKtx = "1.13.1"
agp = "8.9.0"
kotlin = "2.0.20"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
testing-junit = { group = "junit", name = "junit", version.ref = "junit" }


[plugins]
Expand Down
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Thu Oct 17 16:27:57 CEST 2024
#Thu Mar 20 12:18:10 EET 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -1,52 +1,32 @@
package com.simprints.biometrics.polyprotect

import java.nio.ByteBuffer
import java.nio.ByteOrder

object ArrayConverter {
internal object ArrayConverter {
/**
* Converts a DoubleArray to a ByteArray.
* Converts a FloatArray to a ByteArray.
*
* @param doubleArray The DoubleArray to convert.
* @return A ByteArray representing the DoubleArray.
* @param floatArray The float array to convert.
* @return A byte stream representing the contents of float array.
*/
fun doubleArrayToByteArray(doubleArray: DoubleArray): ByteArray {
val byteBuffer = ByteBuffer.allocate(doubleArray.size * 8).order(ByteOrder.nativeOrder())
byteBuffer.asDoubleBuffer().put(doubleArray)
internal fun floatArrayToByteArray(floatArray: FloatArray): ByteArray {
val byteBuffer =
ByteBuffer.allocate(floatArray.size * Float.SIZE_BYTES).order(ByteOrder.nativeOrder())
byteBuffer.asFloatBuffer().put(floatArray)
return byteBuffer.array()
}

/**
* Converts a ByteArray back to a DoubleArray.
* Converts a ByteArray back to a FloatArray.
*
* @param byteArray The ByteArray to convert.
* @return A DoubleArray reconstructed from the ByteArray.
* @param byteArray The byte stream representation of float array
* @return A float array reconstructed from the byte stream.
*/
fun byteArrayToDoubleArray(byteArray: ByteArray): DoubleArray {
internal fun byteArrayToFloatArray(byteArray: ByteArray): FloatArray {
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()
val floatBuffer = byteBuffer.asFloatBuffer()
return FloatArray(floatBuffer.remaining()).apply { floatBuffer.get(this) }
}

/**
* 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) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ package com.simprints.biometrics.polyprotect
* @param exponents of a polynomial function
*/
data class AuxData(
val coefficients: ByteArray,
val exponents: ByteArray,
val coefficients: IntArray,
val exponents: IntArray,
)
29 changes: 14 additions & 15 deletions src/main/kotlin/com/simprints/biometrics/polyprotect/PolyProtect.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class PolyProtect(
*
* **NOTE**: The size of the protected templates depends on `polynomialDegree` and `overlap`.
*
* @param unprotectedTemplateByteArray
* @param unprotectedTemplate byte stream representation of template's array of FLOAT32 values
* @param auxData a set of coefficients and exponents associated with specific biometric record
*
* @return protected template
Expand All @@ -48,33 +48,32 @@ class PolyProtect(
): ByteArray {
val (coefficients, exponents) = auxData // For convenience
require(exponents.size == coefficients.size) { "Auxiliary data sizes must be equal." }
require(ArrayConverter.byteArrayToIntArray(exponents).size == polynomialDegree) {
require(exponents.size == polynomialDegree) {
"Auxiliary data sizes must be equal to polynomial degree."
}

// Converting from ByteArray
val unprotectedTemplateDoubleArray = ArrayConverter.byteArrayToDoubleArray(unprotectedTemplate)
val coefficientsIntArray = ArrayConverter.byteArrayToIntArray(coefficients)
val exponentsIntArray = ArrayConverter.byteArrayToIntArray(exponents)
// Converting from ByteArray to Floats (as per template spec) and to doubles for more precision during calculations
val unprotectedTemplateDoubleArray =
ArrayConverter.byteArrayToFloatArray(unprotectedTemplate).map { it.toDouble() }

val stepSize = exponentsIntArray.size - overlap
val stepSize = exponents.size - overlap

val protectedTemplate = mutableListOf<Double>()
val protectedTemplate = mutableListOf<Float>()
for (templateIndex in 0..(unprotectedTemplateDoubleArray.lastIndex - overlap) step stepSize) {
val s = exponentsIntArray.indices.map { i ->
val s = exponents.indices.sumOf { 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 > unprotectedTemplateDoubleArray.lastIndex) {
0.0
} else {
unprotectedTemplateDoubleArray[templateIndex + i]
.pow(exponentsIntArray[i])
.times(coefficientsIntArray[i])
.pow(exponents[i])
.times(coefficients[i])
}
}.sum()
protectedTemplate.add(s)
}
protectedTemplate.add(s.toFloat()) // We can lose some precision now
}
return ArrayConverter.doubleArrayToByteArray(protectedTemplate.toDoubleArray())
return ArrayConverter.floatArrayToByteArray(protectedTemplate.toFloatArray())
}

/**
Expand All @@ -95,7 +94,7 @@ class PolyProtect(
// Shuffle the list randomly
val exponents = exponentRange.shuffled().toIntArray()

return AuxData(ArrayConverter.intArrayToByteArray(coefficients), ArrayConverter.intArrayToByteArray(exponents))
return AuxData(coefficients, exponents)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import kotlin.math.sqrt
/**
* Calculates a similarity score based on cosine distance.
*
* @param array1 first biometric template
* @param array2 another biometric template
* @param template1 byte stream representation of the first template's array of FLOAT32 values
* @param template2 byte stream representation of the second template's array of FLOAT32 values
*
* @return a value in in the `[0,1]` range
*/
fun computeSimilarityScore(
array1ByteArray: ByteArray,
array2ByteArray: ByteArray,
template1: ByteArray,
template2: ByteArray,
): Double {
require(array1ByteArray.size == array2ByteArray.size) { "Arrays must be of the same size." }
require(template1.size == template2.size) { "Arrays must be of the same size." }

val array1 = ArrayConverter.byteArrayToDoubleArray(array1ByteArray)
val array2 = ArrayConverter.byteArrayToDoubleArray(array2ByteArray)
val array1 = ArrayConverter.byteArrayToFloatArray(template1)
val array2 = ArrayConverter.byteArrayToFloatArray(template2)

// Calculate the dot product of array1 and array2
val dotProduct = array1.zip(array2) { x, y -> x * y }.sum()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ class ComputeSimilarityScoreTest {
@Test
fun `similarity between two protected templates should be between 0 and 1`() {
// Creation of two unprotected templates (randomly generated)
val unprotectedTemplateDoubleArray1 = DoubleArray(512) { Random.nextDouble() - 0.5 }
val unprotectedTemplateDoubleArray2 = DoubleArray(512) { Random.nextDouble() - 0.5 }
val unprotectedTemplateArray1 = FloatArray(512) { Random.nextFloat() - 0.5f }
val unprotectedTemplateArray2 = FloatArray(512) { Random.nextFloat() - 0.5f }

val unprotectedTemplate1 = ArrayConverter.doubleArrayToByteArray(unprotectedTemplateDoubleArray1)
val unprotectedTemplate2 = ArrayConverter.doubleArrayToByteArray(unprotectedTemplateDoubleArray2)
val unprotectedTemplate1 = ArrayConverter.floatArrayToByteArray(unprotectedTemplateArray1)
val unprotectedTemplate2 = ArrayConverter.floatArrayToByteArray(unprotectedTemplateArray2)


// Custom parameters
Expand Down
Loading