firs-irn-qr
v1.0.1
Published
IRN encryption + QR generation for FIRS e-Invoicing (Nigeria). Accepts public key + certificate strings, encrypts IRN+timestamp+certificate, outputs Base64 and QR PNG.
Maintainers
Readme
firs-irn-qr
A simple Node.js + TypeScript library and CLI for encrypting IRNs and generating QR codes based on the FIRS (Nigeria) e-Invoicing IRN Signing Guide.
This package implements the IRN signing and QR generation process described in the FIRS e-Invoicing documentation, providing a developer-friendly API for automation and integration.
✨ Features
- 🔐 Encrypt IRN + certificate using FIRS public key (RSA PKCS#1 v1.5)
- 🕒 Automatically appends UNIX timestamp to IRN (e.g.
INV001-123ABC.1731618237) - 📦 Outputs expected artifacts:
public_key.pemdata.jsonencrypted_data.binencrypted_data.txtqr_code.png
- 🧾 Generate QR codes from encrypted Base64 data
- ⚙️ Works both as a CLI tool and as a Node.js library
📦 Installation
npm install firs-irn-qror
yarn add firs-irn-qr
# or
pnpm add firs-irn-qr🚀 Usage
🖥️ CLI
The CLI lets you easily encrypt an IRN and generate a QR code.
Basic Example
export FIRS_PUBLIC_KEY="<Base64-of-PEM or full PEM string>"
export FIRS_CERTIFICATE="<Base64 certificate>"
npx firs-irn-qr sign --public-key "$FIRS_PUBLIC_KEY" --certificate "$FIRS_CERTIFICATE" --irn INV001-345SFG-20241011 --outdir ./out🗂 Output files in ./out/:
public_key.pem
data.json
encrypted_data.bin
encrypted_data.txt
qr_code.pngWith a custom timestamp
npx firs-irn-qr sign -p "$FIRS_PUBLIC_KEY" -c "$FIRS_CERTIFICATE" -i INV001-345SFG-20241011 -t 1731618237Skip QR generation
npx firs-irn-qr sign -p "$FIRS_PUBLIC_KEY" -c "$FIRS_CERTIFICATE" -i INV001-345SFG-20241011 --no-qrUsing a full IRN string that already includes the timestamp
npx firs-irn-qr sign -p "$FIRS_PUBLIC_KEY" -c "$FIRS_CERTIFICATE" -i "INV001-345SFG-20241011.1731618237"💻 Library API
Use the module in your Node.js/TypeScript project:
import { signAndEncode, writeQrPng } from 'firs-irn-qr';
const keys = {
public_key: process.env.FIRS_PUBLIC_KEY!, // Base64 or PEM string
certificate: process.env.FIRS_CERTIFICATE!, // Base64 string
};
const { encryptedBase64, irnWithTimestamp, publicKeyPem, dataJson } =
await signAndEncode({
keys,
irn: 'INV001-345SFG-20241011',
});
console.log('IRN:', irnWithTimestamp);
console.log('Encrypted data (Base64):', encryptedBase64);
// Optional: generate QR code
await writeQrPng(encryptedBase64, './qr_code.png');🧩 API Reference
signAndEncode({ keys, irn, timestamp? })
| Param | Type | Description |
| ------------------ | -------- | ---------------------------------------- |
| keys.public_key | string | FIRS public key (Base64 or PEM) |
| keys.certificate | string | Certificate (Base64 string) |
| irn | string | IRN stem (e.g. INV001-345SFG-20241011) |
| timestamp? | number | UNIX timestamp in seconds |
Returns:
{
publicKeyPem: string;
irnWithTimestamp: string;
dataJson: string;
encrypted: Buffer;
encryptedBase64: string;
}writeQrPng(base64CipherText, outFile)
Generates a QR code PNG from the Base64 encrypted string.
signAndQrDataUrl({ keys, irn, timestamp? })
| Param | Type | Description |
| ------------------ | -------- | ---------------------------------------- |
| keys.public_key | string | FIRS public key (Base64 or PEM) |
| keys.certificate | string | Certificate (Base64 string) |
| irn | string | IRN stem (e.g. INV001-345SFG-20241011) |
| timestamp? | number | UNIX timestamp in seconds |
Returns:
{
publicKeyPem: string;
irnWithTimestamp: string;
dataJson: string;
encrypted: Buffer;
encryptedBase64: string;
qrDataUrl: string;
}🔒 How it maps to the FIRS shell guide
| FIRS Step | Equivalent in this Package |
|------------|----------------------------|
| jq -r '.public_key' ... | openssl base64 -d -out public_key.pem | decodePublicKeyToPem() automatically decodes Base64 or PEM |
| jq --arg irn ... '{ irn: $irn, certificate: .certificate }' | makeDataJson() constructs the JSON |
| openssl pkeyutl -encrypt ... | Node crypto.publicEncrypt using RSA PKCS#1 v1.5 |
| base64 -i encrypted_data.bin -o encrypted_data.txt | Built-in Base64 conversion |
| qrencode -o qr_code.png -t PNG < encrypted_data.txt | writeQrPng() via the qrcode package |
⚠️ Notes & Assumptions
Uses RSA PKCS#1 v1.5 padding (same as
openssl pkeyutl -encrypt)Compatible with standard 2048-bit RSA public keys
Payload format:
{ "irn": "INV001-ABC123.1731618237", "certificate": "Base64string..." }Timestamps use UNIX seconds (not milliseconds)
Only public key encryption — no private key or decryption logic included
🧾 Example output
After successful execution, your output directory contains:
public_key.pem
data.json
encrypted_data.bin
encrypted_data.txt
qr_code.pngScan qr_code.png with your MBS360 app or FIRS verifier to validate.
🧠 Troubleshooting
| Error | Cause | Fix |
|-------|--------|-----|
| bad base64 | Invalid key or malformed string | Ensure your public key is valid Base64 or PEM |
| data too large for key size | Payload exceeds RSA key capacity | Use shorter IRN/certificate or a larger key |
| QR unreadable | Used binary instead of Base64 | Ensure you pass Base64 data to writeQrPng() |
📜 License
MIT © 2025 @itssadon
