Fix VISA PVV decimalization: use two-pass algorithm per Visa spec
decimalizePvv() was using a single-pass that immediately mapped A-F to 0-5. The Visa PVV spec (matching ANSI X9.8 and jPOS behavior) requires two-pass: collect all decimal digits (0-9) first; only then re-scan mapping A=0 B=1 C=2 D=3 E=4 F=5. This matches decimalizeCvvHex() which was already correct. Bug produced wrong PVV whenever a hex letter appeared before the first decimal digit in the encrypted output. Test vectors updated from "6077" (single-pass result) to "6776" (correct two-pass result) for the encrypted PVV hex 6A77E65CFE349D60. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a2880a4dfc
commit
e864259c1e
@ -168,21 +168,30 @@ function verifyIbm3624Pin(pvkHex, decimalizationTable, pinValidationData, padCha
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decimalizes a PVV candidate using the common numeric-first rule.
|
* Decimalizes a PVV candidate using the standard two-pass rule:
|
||||||
|
* pass 1 collects decimal digits (0-9); pass 2 maps A=0 B=1 C=2 D=3 E=4 F=5.
|
||||||
*
|
*
|
||||||
* @param {string} hex
|
* @param {string} hex
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function decimalizePvv(hex) {
|
function decimalizePvv(hex) {
|
||||||
|
const upper = hex.toUpperCase();
|
||||||
let out = "";
|
let out = "";
|
||||||
for (const ch of hex.toUpperCase()) {
|
|
||||||
|
for (const ch of upper) {
|
||||||
if (/\d/.test(ch)) {
|
if (/\d/.test(ch)) {
|
||||||
out += ch;
|
out += ch;
|
||||||
} else {
|
|
||||||
out += String((ch.charCodeAt(0) - "A".charCodeAt(0)) % 10);
|
|
||||||
}
|
|
||||||
if (out.length >= 4) return out.substring(0, 4);
|
if (out.length >= 4) return out.substring(0, 4);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ch of upper) {
|
||||||
|
if (/[A-F]/.test(ch)) {
|
||||||
|
out += String(ch.charCodeAt(0) - "A".charCodeAt(0));
|
||||||
|
if (out.length >= 4) return out.substring(0, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return out.substring(0, 4);
|
return out.substring(0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class VerifyVISAPVV extends Operation {
|
|||||||
this.testDataSamples = [
|
this.testDataSamples = [
|
||||||
{
|
{
|
||||||
name: "VISA PVV verify sample",
|
name: "VISA PVV verify sample",
|
||||||
input: "6077",
|
input: "6776",
|
||||||
args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, "1234", true]
|
args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, "1234", true]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@ -812,7 +812,7 @@ TestRegister.addTests([
|
|||||||
pin: "1234",
|
pin: "1234",
|
||||||
pvvInput: "1012345678911234",
|
pvvInput: "1012345678911234",
|
||||||
encryptedPvvInputHex: "6A77E65CFE349D60",
|
encryptedPvvInputHex: "6A77E65CFE349D60",
|
||||||
pvv: "6077"
|
pvv: "6776"
|
||||||
}, null, 4),
|
}, null, 4),
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
@ -823,7 +823,7 @@ TestRegister.addTests([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "VISA PVV Verify: known sample",
|
name: "VISA PVV Verify: known sample",
|
||||||
input: "6077",
|
input: "6776",
|
||||||
expectedOutput: JSON.stringify({
|
expectedOutput: JSON.stringify({
|
||||||
pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210",
|
pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210",
|
||||||
pan: "5432101234567890",
|
pan: "5432101234567890",
|
||||||
@ -831,8 +831,8 @@ TestRegister.addTests([
|
|||||||
pin: "1234",
|
pin: "1234",
|
||||||
pvvInput: "1012345678911234",
|
pvvInput: "1012345678911234",
|
||||||
encryptedPvvInputHex: "6A77E65CFE349D60",
|
encryptedPvvInputHex: "6A77E65CFE349D60",
|
||||||
pvv: "6077",
|
pvv: "6776",
|
||||||
expectedPvv: "6077",
|
expectedPvv: "6776",
|
||||||
valid: true
|
valid: true
|
||||||
}, null, 4),
|
}, null, 4),
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
@ -913,8 +913,8 @@ TestRegister.addTests([
|
|||||||
pin: "1234",
|
pin: "1234",
|
||||||
pvvInput: "1012345678911234",
|
pvvInput: "1012345678911234",
|
||||||
encryptedPvvInputHex: "6A77E65CFE349D60",
|
encryptedPvvInputHex: "6A77E65CFE349D60",
|
||||||
pvv: "6077",
|
pvv: "6776",
|
||||||
expectedPvv: "6077",
|
expectedPvv: "6776",
|
||||||
valid: true
|
valid: true
|
||||||
}, null, 4),
|
}, null, 4),
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user