This project adheres to a Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [email protected].
How Can I Contribute?
🐛 Reporting Bugs
Before creating bug reports, please check existing issues to avoid duplicates.
# Clone the repository
git clone https://github.com/keikworld/NoTap-SDK.git
cd NoTap-SDK
# Note: This is a documentation repository
# For SDK source code, request access to the private repository
git checkout -b feature/your-feature-name
# or
git checkout -b fix/issue-description
git commit -m "feat: Add support for new authentication factor"
git commit -m "fix: Resolve timing attack in PIN validation"
git commit -m "docs: Update integration guide with examples"
// Good
class FactorValidator(
private val digestComparator: DigestComparator
) {
fun validate(input: String, storedDigest: ByteArray): Boolean {
val inputDigest = hashInput(input)
return digestComparator.constantTimeEquals(inputDigest, storedDigest)
}
}
// Bad - timing attack vulnerable
fun validate(input: String, storedDigest: ByteArray): Boolean {
return hashInput(input).contentEquals(storedDigest) // Not constant-time!
}
// Good
async function validateFactor(input, storedDigest) {
const inputDigest = await hashInput(input);
return constantTimeCompare(inputDigest, storedDigest);
}
// Bad - timing attack vulnerable
function validateFactor(input, storedDigest) {
return hashInput(input) === storedDigest; // Not constant-time!
}
// ✅ CORRECT - Constant-time comparison
fun constantTimeEquals(a: ByteArray, b: ByteArray): Boolean {
if (a.size != b.size) return false
var result = 0
for (i in a.indices) {
result = result or (a[i].toInt() xor b[i].toInt())
}
return result == 0
}
// ❌ WRONG - Timing attack vulnerable
fun equals(a: ByteArray, b: ByteArray): Boolean {
return a.contentEquals(b) // Short-circuits on first difference!
}
fun processPin(pin: String) {
val pinBytes = pin.toByteArray()
try {
// Process PIN
val digest = hashPin(pinBytes)
// ... use digest ...
} finally {
// ✅ ALWAYS wipe sensitive data
pinBytes.fill(0)
}
}
// ✅ CORRECT
val secureRandom = SecureRandom()
val nonce = ByteArray(32)
secureRandom.nextBytes(nonce)
// ❌ WRONG
val random = Random()
val nonce = random.nextBytes(32) // Not cryptographically secure!
fun validatePin(pin: String): Boolean {
// ✅ Validate length
if (pin.length !in 4..12) return false
// ✅ Validate characters
if (!pin.all { it.isDigit() }) return false
// ✅ Limit size to prevent DoS
if (pin.length > MAX_PIN_LENGTH) return false
return true
}
@Test
fun `validatePin should use constant-time comparison`() {
val validator = PinValidator()
val correctPin = "1234"
val wrongPin = "1235"
// Timing should be similar for correct and incorrect PINs
val correctTime = measureNanoTime {
validator.validate(correctPin, storedDigest)
}
val wrongTime = measureNanoTime {
validator.validate(wrongPin, storedDigest)
}
// Times should be within 10% of each other
assertTrue((correctTime - wrongTime).absoluteValue < correctTime * 0.1)
}
@Test
fun `enrollment flow should complete successfully`() = runTest {
val sdk = NoTapSDK(testConfig)
val result = sdk.enrollment.enroll(
factors = listOf(Factor.PIN, Factor.PATTERN),
paymentProvider = PaymentProvider.STRIPE
)
assertTrue(result is EnrollmentResult.Success)
assertNotNull(result.uuid)
}
/**
* Validates a PIN using constant-time comparison to prevent timing attacks.
*
* @param pin The user-provided PIN (4-12 digits)
* @param storedDigest The SHA-256 digest of the enrolled PIN
* @return true if PIN matches, false otherwise
*
* Security: Uses constant-time comparison to prevent timing side-channel attacks.
* The comparison time is independent of where the first mismatch occurs.
*/
fun validatePin(pin: String, storedDigest: ByteArray): Boolean {
// Implementation...
}