nadra-digital-id
v0.1.5
Published
A JavaScript library for **decoding, decrypting, validating, and verifying NADRA Digital ID QR / VC (Verifiable Credential) data**.
Maintainers
Readme
📘 NADRA Digital ID
A JavaScript library for decoding, decrypting, validating, and verifying NADRA Digital ID QR / VC (Verifiable Credential) data.
It supports:
- Base45 + CBOR + GZIP decoding
- PIN-based AES decryption
- Time-window brute-range decryption
- SHA-256 hashing
- RSA signature verification (RS256)
- Text normalization (Urdu/RTL safe)
🚀 Installation
npm install nadra-digital-id(Or local import in your project)
📦 Main Exports
import nadraDigitalId from "nadra-digital-id"OR
import nadraDigitalId from "https://cdn.jsdelivr.net/npm/nadra-digital-id/+esm"OR
import {
decode,
decrypt,
verify,
sha256,
timeRange,
normalizeText
} from "nadra-digital-id"🧭 Typical Verification Flow
Step-by-step process
- Decode QR payload
- Verify PIN hash
- Generate time range
- Try decrypting VC + date
- Verify RSA signature
- Normalize text fields
🧩 API Reference
🔹 setDebug(value)
Enable or disable debug logging.
Parameters
| Name | Type | Description | | ----- | ------- | ----------------- | | value | boolean | Enable debug mode |
Example
nadraDigitalId.setDebug(true)🔹 decode(data)
Decodes NADRA Digital ID QR payload.
Process
Base45 → GZIP → CBOR → JSONParameters
| Name | Type | Description | | ---- | ------ | ----------- | | data | string | QR string |
Returns
{ error: string } OR { data: object }
Example
const { data: decoded, error } = decode(qrString)🔹 sha256(data)
Generates SHA-256 hash (hex format).
Used for PIN validation.
Parameters
| Name | Type | | ---- | ------ | | data | string |
Returns
{ error: string } OR { data: string }
Example
const { data: pinHash, error } = sha256("0000")🔹 timeRange(options?)
Generates possible time values used for decryption.
NADRA encryption uses time-based salt, so you must try a small range.
Parameters
options?: {
bounds?: {
start: Date
end: Date
}
step?: number // milliseconds
now?: Date
}Default Behavior
Generates 3 timestamps around current (now) time, with 5 minute step.
Returns
{ error: string } OR { data: Date[] }
Example
const { data: timeValues, error } = timeRange()🔹 decrypt(data, pin, date)
Decrypts encrypted NADRA fields.
Algorithm
Salt = formatted date (UTC)
Key = PBKDF2(SHA256, pin)
Cipher = AES-128-ECBParameters
| Name | Type | | ---- | --------------- | | data | string (base64) | | pin | string | | date | Date |
Returns
{ error: string } OR { data: string }
Example
const { data, error } = decrypt(encryptedVC, pin, time)🔹 verify(vc, options?)
Verifies Verifiable Credential RSA signature.
Supported Authorities
The library auto-selects public key based on VC type:
| VC Type | Authority | | -------------------- | --------- | | NATIONAL_ID / FRC | NADRA | | ARMS_LICENSE | MOI | | VEHICLE_REGISTRATION | ETD | | Others | NIMS |
Parameters
vc: object
options?: {
publicKeyPem?: string
}Returns
{ error: string } OR {}
Example
const { error } = await verify(vc)
if (!error) console.log("Signature is valid")🔹 normalizeText(text)
Removes invisible Unicode control characters.
Important for Urdu RTL text validation.
Cleans:
- RTL/LTR marks
- Arabic control chars
- Direction overrides
Returns
{ error: string } OR { data: string }
Example
const { data, error } = normalizeText(address)🔐 Security Notes
Encryption
- AES-128-ECB
- PBKDF2 iterations: 1000
- Salt: Time-derived
Signature
- RSASSA-PKCS1-v1_5
- SHA-256 hash
🧪 Full Usage Example
import nadraDigitalId from "nadra-digital-id"
async function main() {
// nadraDigitalId.setDebug(true)
const data = "..."
const pin = "0000"
const now = new Date("2026-01-01T00:00:00+05:00")
const { data: decoded, error: decodeError } = nadraDigitalId.decode(data)
if (decodeError) {
console.log(decodeError)
return
}
const { data: pinHash, error: pinHashError } = nadraDigitalId.sha256(pin)
if (pinHashError) {
console.log(pinHashError)
return
}
if (decoded.hash !== pinHash) {
console.log("Invalid PIN")
return
}
const { data: timeValues, error: timeRangeError } = nadraDigitalId.timeRange({
now
})
if (timeRangeError) {
console.log(timeRangeError)
return
}
let date = null
let vc = null
for (const time of timeValues) {
if (!date) {
const result = nadraDigitalId.decrypt(decoded.date, pin, time)
if (result.data) date = new Date(result.data + "Z")
}
if (!vc) {
const result = nadraDigitalId.decrypt(decoded.vc, pin, time)
if (result.data) vc = JSON.parse(result.data)
}
if (date && vc) break
}
if (!date || !vc) {
console.log("Failed to decrypt data")
return
}
console.log("Decrypted VC:", vc)
console.log("Decrypted Date:", date)
// Uncomment following lines to test forged VC scenario
// if (vc.credentialSubject?.name?.value) vc.credentialSubject.name.value += " "
// else console.log("Cannot forge VC. Field is missing. Try some other field.")
const { error: verificationError } = await nadraDigitalId.verify(vc)
if (verificationError) {
console.log(verificationError)
return
}
console.log("VC verification successful")
if (vc.credentialSubject?.temporaryAddress?.value) {
const { data: normalizedAddress, error: normalizationError } =
nadraDigitalId.normalizeText(vc.credentialSubject.temporaryAddress.value)
if (normalizationError) {
console.log(normalizationError)
return
}
console.log("Normalized Address:", normalizedAddress)
}
}
main()⚠️ Common Errors
| Error | Cause | | -------------------------- | ---------------------------------- | | Failed to decode data | Wrong Format / Corrupted data | | Failed to decrypt data | Wrong PIN / time | | Failed to verify signature | Tampered VC / Incorrect Public Key |
🏗️ Internal Architecture
QR DATA
↓
decode()
↓
verify PIN hash
↓
timeRange()
↓
decrypt()
↓
verify()Flow after QR Code Scan in PAK ID
📄 License
MIT
