diff --git a/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md b/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md
index 1edfdcc2..ebca87b2 100644
--- a/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md
+++ b/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md
@@ -22,18 +22,18 @@ Coverage legend:
| AWS operation | Coverage | Notes |
| --- | --- | --- |
-| `EncryptData` | `Direct` / `Partial` | Direct for AES, TDES, RSA. Partial for DUKPT and EMV-derived encryption. |
-| `DecryptData` | `Direct` / `Partial` | Direct for AES, TDES, RSA. Partial for DUKPT and EMV-derived decryption. |
-| `ReEncryptData` | `Direct` / `Partial` | Direct for plain decrypt-then-encrypt workflows. Partial for DUKPT re-encryption. |
+| `EncryptData` | `Direct` / `Partial` | Direct for AES, TDES, and the implemented DUKPT-TDES wrapper profiles. Partial for EMV-derived encryption and broader AWS attribute coverage. |
+| `DecryptData` | `Direct` / `Partial` | Direct for AES, TDES, and the implemented DUKPT-TDES wrapper profiles. Partial for EMV-derived decryption and broader AWS attribute coverage. |
+| `ReEncryptData` | `Direct` / `Partial` | Direct for plain decrypt-then-encrypt workflows, including the implemented payment-facing AES/TDES wrapper flows. Partial for DUKPT re-encryption breadth and AWS-specific metadata handling. |
| `GenerateMac` | `Direct` / `Partial` | Direct for static-key HMAC and CMAC, and direct for the implemented DUKPT CMAC wrapper modes. Partial for ISO 9797, EMV MAC, and AS2805 flows. |
| `VerifyMac` | `Direct` / `Partial` | Direct for static-key HMAC and CMAC, and direct for the implemented DUKPT CMAC wrapper modes. Partial for ISO 9797, EMV MAC, and AS2805 flows. |
-| `VerifyAuthRequestCryptogram` | `Partial` | Usable for AES-CMAC ARQC/ARPC-style checking when session key and preimage are already known. Dedicated ARQC and ARPC generators now exist for that constrained profile. |
+| `VerifyAuthRequestCryptogram` | `Partial` | Usable for AES-CMAC ARQC/ARPC-style checking when session key and preimage are already known. Dedicated ARQC, ARPC, and ARQC verify wrappers now exist for that constrained profile. |
| `TranslateKeyMaterial` | `Partial` | Useful for ECDH derivation and TR-31 inspection, not full HSM-side rewrap semantics. |
| `GenerateCardValidationData` | `Direct` | Direct for software CVV/CVV2/iCVV generation when the combined CVK pair is provided as clear hex. |
| `VerifyCardValidationData` | `Direct` | Direct for software CVV/CVV2/iCVV verification using the same clear-CVK assumptions as generation. |
-| `GeneratePinData` | `Partial` | Clear PIN-block build coverage now exists for ISO formats 0, 1, and 3. PVV, IBM3624, and encrypted-generation paths are still missing. |
-| `TranslatePinData` | `Partial` | Clear PIN-block parse and translate coverage now exists for ISO formats 0, 1, and 3. Encrypted PEK/BDK/ECDH translation is still missing. |
-| `VerifyPinData` | `Partial` | Clear PIN-block decoding exists, but PVV / IBM3624 verification behavior is still missing. |
+| `GeneratePinData` | `Partial` | Clear PIN-block wrapper coverage now exists for ISO formats 0, 1, and 3. PVV, IBM3624, and encrypted-generation paths are still missing. |
+| `TranslatePinData` | `Partial` | Clear PIN-block wrapper coverage now exists for ISO formats 0, 1, and 3. Encrypted PEK/BDK/ECDH translation is still missing. |
+| `VerifyPinData` | `Partial` | Clear PIN-block verification wrapper exists, but PVV / IBM3624 verification behavior is still missing. |
| `GenerateMacEmvPinChange` | `Not yet implemented` | Requires issuer-script PIN-change building blocks. |
| `GenerateAs2805KekValidation` | `Not yet implemented` | Requires AS2805-specific KEK-validation primitives. |
@@ -53,7 +53,16 @@ Notes:
- AWS documents `EncryptData` as supporting symmetric `TDES` and `AES`, asymmetric `RSA`, and derived `DUKPT` or `EMV` schemes.
- This starter directly covers only the non-derived AES, TDES, and RSA cases.
-## 2) AWS `DecryptData`: AES / TDES / RSA
+## 2) AWS `EncryptData`: Payment Wrapper
+Operations:
+- `Encrypt payment data`
+
+Suggested use:
+- Paste plaintext into the input field as hex.
+- Choose a payment-facing profile for AES, TDES, or the implemented DUKPT-TDES wrapper modes.
+- Provide the direct key or BDK plus KSN, and add the IV when required.
+
+## 3) AWS `DecryptData`: AES / TDES / RSA
Operations:
- `AES Decrypt` or `Triple DES Decrypt` or `RSA Decrypt`
@@ -63,7 +72,16 @@ Suggested use:
- Paste the key into the key argument using the correct format selector.
- Match the AWS algorithm and mode manually in the chosen CyberChef operation.
-## 3) AWS `ReEncryptData`: Symmetric Rewrap
+## 4) AWS `DecryptData`: Payment Wrapper
+Operations:
+- `Decrypt payment data`
+
+Suggested use:
+- Paste ciphertext into the input field as hex.
+- Choose a payment-facing profile for AES, TDES, or the implemented DUKPT-TDES wrapper modes.
+- Provide the direct key or BDK plus KSN, and add the IV when required.
+
+## 5) AWS `ReEncryptData`: Symmetric Rewrap
Operations:
- `AES Decrypt` or `Triple DES Decrypt`
- `AES Encrypt` or `Triple DES Encrypt`
@@ -77,7 +95,16 @@ Notes:
- This covers the software-visible decrypt-then-encrypt pattern.
- It does not model AWS wrapped-key handling or HSM-side key custody.
-## 4) AWS `GenerateMac`: HMAC
+## 6) AWS `ReEncryptData`: Payment Wrapper
+Operations:
+- `Re-encrypt payment data`
+
+Suggested use:
+- Paste source ciphertext into the input field as hex.
+- Define the source decrypt profile and the target encrypt profile in one operation.
+- Use this as the payment-facing version of the decrypt-then-encrypt recipe chain.
+
+## 7) AWS `GenerateMac`: HMAC
Operations:
- `From Hex`
- `HMAC`
@@ -89,7 +116,7 @@ Suggested use:
- Run `HMAC` with the appropriate key and hash function.
- If AWS truncates the MAC, use `Take bytes` to keep the leftmost bytes that match `MacLength`.
-## 5) AWS `GenerateMac`: CMAC
+## 8) AWS `GenerateMac`: CMAC
Operations:
- `From Hex`
- `CMAC`
@@ -101,7 +128,7 @@ Suggested use:
- Run `CMAC` with `Encryption algorithm` set to `AES` or `Triple DES`.
- Use `Take bytes` to match the requested `MacLength` if truncation is required.
-## 6) AWS `VerifyMac`: Recompute And Compare
+## 9) AWS `VerifyMac`: Recompute And Compare
Operations:
- `Verify payment MAC`
@@ -113,7 +140,7 @@ Notes:
- This covers the implemented static-key HMAC/CMAC and DUKPT-CMAC wrapper modes directly.
- ISO 9797, EMV MAC, and AS2805-specific verification are still partial gaps.
-## 7) AWS `GenerateMac`: Payment Wrapper
+## 10) AWS `GenerateMac`: Payment Wrapper
Operations:
- `Generate payment MAC`
@@ -125,7 +152,7 @@ Notes:
- This wrapper exists for usability so payment users can stay in the `Payments` category without needing to know which low-level primitive is underneath.
- It intentionally reuses the existing generic `HMAC` and `CMAC` implementations.
-## 8) AWS `GenerateCardValidationData`: CVV / CVV2 / iCVV
+## 11) AWS `GenerateCardValidationData`: CVV / CVV2 / iCVV
Operations:
- `Generate card validation data`
@@ -138,7 +165,7 @@ Notes:
- This directly covers software generation of CVV/CVV2/iCVV-style values.
- Assumption: CVV2 forces service code `000` and iCVV forces `999`.
-## 9) AWS `VerifyCardValidationData`: CVV / CVV2 / iCVV
+## 12) AWS `VerifyCardValidationData`: CVV / CVV2 / iCVV
Operations:
- `Verify card validation data`
@@ -152,7 +179,7 @@ Notes:
## Partial Recipe Starters
-## 10) AWS `EncryptData` / `DecryptData`: DUKPT-Derived Symmetric Flows
+## 13) AWS `EncryptData` / `DecryptData`: DUKPT-Derived Symmetric Flows
Operations:
- `Derive DUKPT key`
- `AES Encrypt` or `AES Decrypt` or `Triple DES Encrypt` or `Triple DES Decrypt`
@@ -165,20 +192,20 @@ Notes:
- This is useful for offline vector work.
- It does not claim one-to-one parity with every AWS DUKPT encryption attribute combination.
-## 11) AWS `VerifyAuthRequestCryptogram`: EMV ARQC Check
+## 14) AWS `VerifyAuthRequestCryptogram`: EMV ARQC Check
Operations:
-- `Generate EMV ARQC`
+- `Verify EMV ARQC`
Suggested use:
- Paste the already-assembled EMV authorization-request preimage into the input field as hex.
- Provide the already-derived AES session key and cryptogram length.
-- Compare the result to the incoming ARQC.
+- Provide the incoming ARQC and let the wrapper recompute and compare it.
Notes:
- This is only practical when the session key and exact preimage assembly are already known.
- It is a good fit for AES-CMAC-based profiles, not a full generic EMV verifier.
-## 12) AWS `TranslateKeyMaterial`: ECDH And Wrapped-Key Inspection
+## 15) AWS `TranslateKeyMaterial`: ECDH And Wrapped-Key Inspection
Operations:
- `Derive ECDH key material`
- `Parse TR-31 key block`
@@ -192,7 +219,7 @@ Notes:
- This helps with interoperability debugging.
- It does not recreate AWS’s HSM-side translate-and-rewrap behavior.
-## 13) AWS `GenerateMac`: EMV MAC Preimage Review
+## 16) AWS `GenerateMac`: EMV MAC Preimage Review
Operations:
- `From Hex`
- `CMAC`
@@ -205,9 +232,9 @@ Notes:
- AWS documents `GenerateMac` as supporting EMV MAC.
- This fork does not yet have a dedicated EMV MAC operation, so this remains a profile-specific starter rather than a generic implementation.
-## 14) AWS `GeneratePinData`: Clear PIN Block Build
+## 17) AWS `GeneratePinData`: Clear PIN Block Wrapper
Operations:
-- `Build PIN block`
+- `Generate payment PIN data`
Suggested use:
- Paste the clear PIN into the input field.
@@ -218,9 +245,9 @@ Notes:
- This is useful for software test harnesses that need deterministic clear PIN-block construction before encryption.
- It does not yet implement PVV generation, IBM 3624 offsets, or encrypted AWS response semantics.
-## 15) AWS `TranslatePinData`: Clear PIN Block Translation
+## 18) AWS `TranslatePinData`: Clear PIN Block Wrapper
Operations:
-- `Translate PIN block`
+- `Translate payment PIN data`
Suggested use:
- Paste the source clear PIN block into the input field as hex.
@@ -231,13 +258,13 @@ Notes:
- This is a software emulation helper for test-vector work.
- It does not yet emulate encrypted HSM-bound translation between PEK, BDK, or ECDH-derived keys.
-## 16) AWS `VerifyPinData`: Clear PIN Block Inspection
+## 19) AWS `VerifyPinData`: Clear PIN Block Wrapper
Operations:
-- `Parse PIN block`
+- `Verify payment PIN data`
Suggested use:
- Paste the clear PIN block into the input field as hex.
-- Decode the PIN-block structure and compare the recovered PIN to your expected test data.
+- Provide the expected clear PIN and let the wrapper decode and compare it.
Notes:
- This is only structural verification today.
diff --git a/PAYMENT_RECIPES.md b/PAYMENT_RECIPES.md
index 8c5ced0f..4ca2ee9a 100644
--- a/PAYMENT_RECIPES.md
+++ b/PAYMENT_RECIPES.md
@@ -32,7 +32,22 @@ Suggested use:
- Derive IPEK from BDK + KSN.
- Derive base session key and apply a variant mask (`PIN`, `MAC Request`, `MAC Response`, `Data`).
-## 6) PIN Block Build / Parse / Translate
+## 6) Payment Data Encrypt / Decrypt / Re-encrypt
+Operations:
+- `Encrypt payment data`
+- `Decrypt payment data`
+- `Re-encrypt payment data`
+
+Suggested use:
+- Paste the message or ciphertext into the input field as hex.
+- Choose a payment-facing cipher profile for AES, TDES, or DUKPT-derived TDES.
+- Provide the direct key or BDK plus KSN, then add the IV when the selected mode requires one.
+
+Scope note:
+- These wrappers currently cover AES CBC/CTR/ECB, TDES CBC/ECB, and DUKPT-derived TDES CBC/ECB.
+- They are intended for software test harnesses and intentionally reuse the existing generic cipher implementations underneath.
+
+## 7) PIN Block Build / Parse / Translate
Operations:
- `Build PIN block`
- `Parse PIN block`
@@ -47,7 +62,21 @@ Scope note:
- This starter currently covers clear software test blocks for ISO formats 0, 1, and 3.
- It does not yet generate PVV, IBM 3624 offsets, or encrypted PEK/BDK translation flows by itself.
-## 7) Card Validation Data (CVV / CVV2 / iCVV)
+## 8) Payment PIN Data Wrappers
+Operations:
+- `Generate payment PIN data`
+- `Translate payment PIN data`
+- `Verify payment PIN data`
+
+Suggested use:
+- Use these wrappers when you want AWS-style PIN-data naming instead of the lower-level PIN-block operations.
+- They currently cover clear ISO 9564 formats 0, 1, and 3 by delegating to the existing build, translate, and parse operations.
+
+Scope note:
+- This is still clear-PIN-block coverage only.
+- Encrypted PIN data, PVV, and IBM 3624 are still future additions.
+
+## 9) Card Validation Data (CVV / CVV2 / iCVV)
Operations:
- `Generate card validation data`
- `Verify card validation data`
@@ -62,7 +91,7 @@ Scope note:
- CVV2 forces service code `000` and iCVV forces `999`.
- It does not try to emulate scheme-specific dCVV, token CVV, or issuer-host formatting differences beyond the common decimalization flow.
-## 8) Payment MAC Generation And Verification
+## 10) Payment MAC Generation And Verification
Operations:
- `Generate payment MAC`
- `Verify payment MAC`
@@ -77,9 +106,10 @@ Scope note:
- Current DUKPT coverage derives TDES session keys and applies TDES-CMAC for request and response MAC variants.
- ISO 9797, EMV session-derivation MAC, and AS2805 are still future additions.
-## 9) EMV ARQC Generation (AES-CMAC Profile)
+## 11) EMV ARQC Generation And Verification (AES-CMAC Profile)
Operations:
- `Generate EMV ARQC`
+- `Verify EMV ARQC`
Suggested use:
- Paste the already-assembled ARQC input block into the input field as hex.
@@ -90,7 +120,7 @@ Scope note:
- This operation is intentionally limited to AES-CMAC-style EMV profiles.
- It does not derive EMV session keys or assemble CDOL/tag data for you.
-## 10) EMV ARPC Generation (AES-CMAC Response Profile)
+## 12) EMV ARPC Generation (AES-CMAC Response Profile)
Operations:
- `Generate EMV ARPC`
@@ -103,7 +133,7 @@ Scope note:
- This operation is intentionally limited to AES-CMAC response profiles where the issuer session key and exact preimage are already known.
- Legacy 3DES EMV ARQC/ARPC flows are not covered.
-## 11) Combined Message Triage
+## 13) Combined Message Triage
Operations:
- `Parse TR-34 B9 envelope`
- `Parse ASN.1 hex string`
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 72356ef5..24379574 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -581,15 +581,22 @@
"Calculate payment KCV",
"Derive ECDH key material",
"Derive DUKPT key",
+ "Encrypt payment data",
+ "Decrypt payment data",
+ "Re-encrypt payment data",
"Generate payment MAC",
"Verify payment MAC",
"Generate card validation data",
"Verify card validation data",
"Generate EMV ARQC",
"Generate EMV ARPC",
+ "Verify EMV ARQC",
"Build PIN block",
"Parse PIN block",
- "Translate PIN block"
+ "Translate PIN block",
+ "Generate payment PIN data",
+ "Translate payment PIN data",
+ "Verify payment PIN data"
]
},
{
diff --git a/src/core/lib/PaymentDataCipher.mjs b/src/core/lib/PaymentDataCipher.mjs
new file mode 100644
index 00000000..d32901bf
--- /dev/null
+++ b/src/core/lib/PaymentDataCipher.mjs
@@ -0,0 +1,209 @@
+/**
+ * @license Apache-2.0
+ */
+
+import OperationError from "../errors/OperationError.mjs";
+import AESEncrypt from "../operations/AESEncrypt.mjs";
+import AESDecrypt from "../operations/AESDecrypt.mjs";
+import TripleDESEncrypt from "../operations/TripleDESEncrypt.mjs";
+import TripleDESDecrypt from "../operations/TripleDESDecrypt.mjs";
+import DeriveDUKPTKey from "../operations/DeriveDUKPTKey.mjs";
+
+const PAYMENT_CIPHER_PROFILES = [
+ "AES CBC",
+ "AES CTR",
+ "AES ECB",
+ "TDES CBC",
+ "TDES ECB",
+ "DUKPT TDES CBC",
+ "DUKPT TDES ECB",
+];
+
+const DUKPT_DATA_VARIANTS = ["None", "Data"];
+
+/**
+ * Validates hex input.
+ *
+ * @param {string} value
+ * @param {string} name
+ * @param {boolean} allowEmpty
+ * @returns {string}
+ */
+function normalizeHex(value, name, allowEmpty=false) {
+ const normalized = (value || "").replace(/\s+/g, "").toUpperCase();
+ if (!normalized.length && allowEmpty) return "";
+ if (!/^[0-9A-F]+$/.test(normalized) || normalized.length % 2 !== 0) {
+ throw new OperationError(`${name} must be even-length hex.`);
+ }
+ return normalized;
+}
+
+/**
+ * Resolves the working key for the selected cipher profile.
+ *
+ * @param {string} profile
+ * @param {string} keyHex
+ * @param {string} ksn
+ * @param {string} dukptVariant
+ * @returns {{keyHex: string, keyContext: Object}}
+ */
+function resolveCipherKey(profile, keyHex, ksn, dukptVariant) {
+ if (!profile.startsWith("DUKPT ")) {
+ return {
+ keyHex: normalizeHex(keyHex, "Key"),
+ keyContext: { keySource: "Direct key input" }
+ };
+ }
+
+ const normalizedKey = normalizeHex(keyHex, "BDK");
+ const normalizedKsn = normalizeHex(ksn, "KSN");
+ const dukpt = new DeriveDUKPTKey();
+ const derivedKey = dukpt.run(normalizedKey, ["Derive Session Key", normalizedKsn, dukptVariant, false]);
+
+ return {
+ keyHex: derivedKey,
+ keyContext: {
+ keySource: "Derived from DUKPT BDK",
+ ksn: normalizedKsn,
+ dukptVariant
+ }
+ };
+}
+
+/**
+ * Encrypts payment data using the selected profile.
+ *
+ * @param {string} inputHex
+ * @param {string} profile
+ * @param {string} keyHex
+ * @param {string} ivHex
+ * @param {string} ksn
+ * @param {string} dukptVariant
+ * @returns {Object}
+ */
+function encryptPaymentData(inputHex, profile, keyHex, ivHex, ksn, dukptVariant) {
+ const plaintextHex = normalizeHex(inputHex, "Input data");
+ const normalizedIv = normalizeHex(ivHex, "IV", true);
+ const { keyHex: effectiveKeyHex, keyContext } = resolveCipherKey(profile, keyHex, ksn, dukptVariant);
+
+ let ciphertextHex;
+ if (profile.startsWith("AES ")) {
+ const aes = new AESEncrypt();
+ const mode = profile.substring(4);
+ ciphertextHex = aes.run(plaintextHex, [
+ { string: effectiveKeyHex, option: "Hex" },
+ { string: normalizedIv, option: "Hex" },
+ mode,
+ "Hex",
+ "Hex",
+ { string: "", option: "Hex" }
+ ]).toUpperCase();
+ } else {
+ const tdes = new TripleDESEncrypt();
+ const mode = profile.endsWith("CBC") ? "CBC" : "ECB";
+ ciphertextHex = tdes.run(plaintextHex, [
+ { string: effectiveKeyHex, option: "Hex" },
+ { string: normalizedIv, option: "Hex" },
+ mode,
+ "Hex",
+ "Hex"
+ ]).toUpperCase();
+ }
+
+ return {
+ profile,
+ plaintextHex,
+ ciphertextHex,
+ ivHex: normalizedIv,
+ ...keyContext
+ };
+}
+
+/**
+ * Decrypts payment data using the selected profile.
+ *
+ * @param {string} inputHex
+ * @param {string} profile
+ * @param {string} keyHex
+ * @param {string} ivHex
+ * @param {string} ksn
+ * @param {string} dukptVariant
+ * @returns {Object}
+ */
+function decryptPaymentData(inputHex, profile, keyHex, ivHex, ksn, dukptVariant) {
+ const ciphertextHex = normalizeHex(inputHex, "Input data");
+ const normalizedIv = normalizeHex(ivHex, "IV", true);
+ const { keyHex: effectiveKeyHex, keyContext } = resolveCipherKey(profile, keyHex, ksn, dukptVariant);
+
+ let plaintextHex;
+ if (profile.startsWith("AES ")) {
+ const aes = new AESDecrypt();
+ const mode = profile.substring(4);
+ plaintextHex = aes.run(ciphertextHex, [
+ { string: effectiveKeyHex, option: "Hex" },
+ { string: normalizedIv, option: "Hex" },
+ mode,
+ "Hex",
+ "Hex",
+ { string: "", option: "Hex" },
+ { string: "", option: "Hex" }
+ ]).toUpperCase();
+ } else {
+ const tdes = new TripleDESDecrypt();
+ const mode = profile.endsWith("CBC") ? "CBC" : "ECB";
+ plaintextHex = tdes.run(ciphertextHex, [
+ { string: effectiveKeyHex, option: "Hex" },
+ { string: normalizedIv, option: "Hex" },
+ mode,
+ "Hex",
+ "Hex"
+ ]).toUpperCase();
+ }
+
+ return {
+ profile,
+ ciphertextHex,
+ plaintextHex,
+ ivHex: normalizedIv,
+ ...keyContext
+ };
+}
+
+/**
+ * Re-encrypts payment data by decrypting under one profile and encrypting under another.
+ *
+ * @param {string} inputHex
+ * @param {Object} params
+ * @returns {Object}
+ */
+function reEncryptPaymentData(inputHex, params) {
+ const decrypted = decryptPaymentData(
+ inputHex,
+ params.sourceProfile,
+ params.sourceKeyHex,
+ params.sourceIvHex,
+ params.sourceKsn,
+ params.sourceDukptVariant
+ );
+ const encrypted = encryptPaymentData(
+ decrypted.plaintextHex,
+ params.targetProfile,
+ params.targetKeyHex,
+ params.targetIvHex,
+ params.targetKsn,
+ params.targetDukptVariant
+ );
+
+ return {
+ source: decrypted,
+ target: encrypted
+ };
+}
+
+export {
+ DUKPT_DATA_VARIANTS,
+ PAYMENT_CIPHER_PROFILES,
+ decryptPaymentData,
+ encryptPaymentData,
+ reEncryptPaymentData,
+};
diff --git a/src/core/operations/DecryptPaymentData.mjs b/src/core/operations/DecryptPaymentData.mjs
new file mode 100644
index 00000000..112196a2
--- /dev/null
+++ b/src/core/operations/DecryptPaymentData.mjs
@@ -0,0 +1,54 @@
+/**
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import { DUKPT_DATA_VARIANTS, PAYMENT_CIPHER_PROFILES, decryptPaymentData } from "../lib/PaymentDataCipher.mjs";
+
+/**
+ * Decrypt payment data operation.
+ */
+class DecryptPaymentData extends Operation {
+ /**
+ * DecryptPaymentData constructor.
+ */
+ constructor() {
+ super();
+
+ this.name = "Decrypt payment data";
+ this.module = "Payment";
+ this.description = "Paste ciphertext into the input field as hex and decrypt it using a payment-facing cipher wrapper.
Input: ciphertext hex.
Arguments: choose the cipher profile, provide a direct key or BDK, add IV where needed, and provide KSN plus DUKPT variant when using a DUKPT profile.";
+ this.inlineHelp = "Input: ciphertext hex.
Args: choose AES, TDES, or DUKPT-wrapped TDES, then provide key, IV, and optional KSN context.";
+ this.testDataSamples = [
+ {
+ name: "AES CBC sample",
+ input: "76D0627DA1D290436E21A4AF7FCA94B7177C1FC94173D442E36EE79D7CA0E461",
+ args: ["AES CBC", "00112233445566778899AABBCCDDEEFF", "000102030405060708090A0B0C0D0E0F", "", "Data", false]
+ }
+ ];
+ this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_DecryptData.html";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ { name: "Cipher profile", type: "option", value: PAYMENT_CIPHER_PROFILES, comment: "Select the payment-facing decryption profile. DUKPT profiles derive a session key first, then run TDES decryption." },
+ { name: "Key / BDK", type: "string", value: "", comment: "Provide the clear AES/TDES key for static profiles, or the clear BDK for DUKPT profiles." },
+ { name: "IV (hex)", type: "string", value: "", comment: "Initialization vector as hex. Leave blank for ECB. Use 16 bytes for AES CBC/CTR and 8 bytes for TDES CBC." },
+ { name: "KSN (DUKPT only)", type: "string", value: "", comment: "Required only for DUKPT profiles. Provide the full 10-byte KSN as hex." },
+ { name: "DUKPT variant", type: "option", value: DUKPT_DATA_VARIANTS, defaultIndex: 1, comment: "Applies only to DUKPT profiles. Use Data for the current data-key masking behavior in this fork." },
+ { name: "Output as JSON", type: "boolean", value: false, comment: "When enabled, returns the effective cipher context and plaintext." },
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [profile, keyHex, ivHex, ksn, dukptVariant, outputJson] = args;
+ const result = decryptPaymentData(input, profile, keyHex, ivHex, ksn, dukptVariant);
+ return outputJson ? JSON.stringify(result, null, 4) : result.plaintextHex;
+ }
+}
+
+export default DecryptPaymentData;
diff --git a/src/core/operations/EncryptPaymentData.mjs b/src/core/operations/EncryptPaymentData.mjs
new file mode 100644
index 00000000..aad4684f
--- /dev/null
+++ b/src/core/operations/EncryptPaymentData.mjs
@@ -0,0 +1,54 @@
+/**
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import { DUKPT_DATA_VARIANTS, PAYMENT_CIPHER_PROFILES, encryptPaymentData } from "../lib/PaymentDataCipher.mjs";
+
+/**
+ * Encrypt payment data operation.
+ */
+class EncryptPaymentData extends Operation {
+ /**
+ * EncryptPaymentData constructor.
+ */
+ constructor() {
+ super();
+
+ this.name = "Encrypt payment data";
+ this.module = "Payment";
+ this.description = "Paste plaintext into the input field as hex and encrypt it using a payment-facing cipher wrapper.
Input: plaintext hex.
Arguments: choose the cipher profile, provide a direct key or BDK, add IV where needed, and provide KSN plus DUKPT variant when using a DUKPT profile.";
+ this.inlineHelp = "Input: plaintext hex.
Args: choose AES, TDES, or DUKPT-wrapped TDES, then provide key, IV, and optional KSN context.";
+ this.testDataSamples = [
+ {
+ name: "AES CBC sample",
+ input: "00112233445566778899AABBCCDDEEFF",
+ args: ["AES CBC", "00112233445566778899AABBCCDDEEFF", "000102030405060708090A0B0C0D0E0F", "", "Data", false]
+ }
+ ];
+ this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_EncryptData.html";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ { name: "Cipher profile", type: "option", value: PAYMENT_CIPHER_PROFILES, comment: "Select the payment-facing encryption profile. DUKPT profiles derive a session key first, then run TDES encryption." },
+ { name: "Key / BDK", type: "string", value: "", comment: "Provide the clear AES/TDES key for static profiles, or the clear BDK for DUKPT profiles." },
+ { name: "IV (hex)", type: "string", value: "", comment: "Initialization vector as hex. Leave blank for ECB. Use 16 bytes for AES CBC/CTR and 8 bytes for TDES CBC." },
+ { name: "KSN (DUKPT only)", type: "string", value: "", comment: "Required only for DUKPT profiles. Provide the full 10-byte KSN as hex." },
+ { name: "DUKPT variant", type: "option", value: DUKPT_DATA_VARIANTS, defaultIndex: 1, comment: "Applies only to DUKPT profiles. Use Data for the current data-key masking behavior in this fork." },
+ { name: "Output as JSON", type: "boolean", value: false, comment: "When enabled, returns the effective cipher context and ciphertext." },
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [profile, keyHex, ivHex, ksn, dukptVariant, outputJson] = args;
+ const result = encryptPaymentData(input, profile, keyHex, ivHex, ksn, dukptVariant);
+ return outputJson ? JSON.stringify(result, null, 4) : result.ciphertextHex;
+ }
+}
+
+export default EncryptPaymentData;
diff --git a/src/core/operations/GeneratePaymentPINData.mjs b/src/core/operations/GeneratePaymentPINData.mjs
new file mode 100644
index 00000000..b1a97104
--- /dev/null
+++ b/src/core/operations/GeneratePaymentPINData.mjs
@@ -0,0 +1,54 @@
+/**
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import BuildPINBlock from "./BuildPINBlock.mjs";
+
+/**
+ * Generate payment PIN data operation.
+ */
+class GeneratePaymentPINData extends Operation {
+ /**
+ * GeneratePaymentPINData constructor.
+ */
+ constructor() {
+ super();
+
+ this.name = "Generate payment PIN data";
+ this.module = "Payment";
+ this.description = "Paste the clear PIN into the input field and generate clear PIN-block test data using an AWS-style payment wrapper.
Input: clear PIN digits.
Arguments: choose the PIN-block format, provide the PAN when required, and optionally return structured JSON.";
+ this.inlineHelp = "Input: clear PIN digits.
Args: choose the block format and provide the PAN for PAN-bound formats.";
+ this.testDataSamples = [
+ {
+ name: "Format 0 sample",
+ input: "1234",
+ args: ["ISO Format 0", "5432101234567890", false, false]
+ }
+ ];
+ this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_GeneratePinData.html";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ { name: "Format", type: "option", value: ["ISO Format 0", "ISO Format 1", "ISO Format 3"], comment: "Clear ISO 9564 format to generate. This wrapper currently supports only formats 0, 1, and 3." },
+ { name: "Primary account number", type: "string", value: "", comment: "Required for formats 0 and 3. Enter digits only." },
+ { name: "Randomize fill digits", type: "boolean", value: false, comment: "Affects only formats 1 and 3. Leave disabled for repeatable vectors." },
+ { name: "Output as JSON", type: "boolean", value: false, comment: "When enabled, returns the clear PIN block plus the source context." },
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [format, pan, randomizeFill, outputJson] = args;
+ const builder = new BuildPINBlock();
+ const pinBlockHex = builder.run(input, [format, pan, randomizeFill]);
+ const result = { format, pan, pinBlockHex };
+ return outputJson ? JSON.stringify(result, null, 4) : pinBlockHex;
+ }
+}
+
+export default GeneratePaymentPINData;
diff --git a/src/core/operations/ReEncryptPaymentData.mjs b/src/core/operations/ReEncryptPaymentData.mjs
new file mode 100644
index 00000000..8e2a51d3
--- /dev/null
+++ b/src/core/operations/ReEncryptPaymentData.mjs
@@ -0,0 +1,70 @@
+/**
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import { DUKPT_DATA_VARIANTS, PAYMENT_CIPHER_PROFILES, reEncryptPaymentData } from "../lib/PaymentDataCipher.mjs";
+
+/**
+ * Re-encrypt payment data operation.
+ */
+class ReEncryptPaymentData extends Operation {
+ /**
+ * ReEncryptPaymentData constructor.
+ */
+ constructor() {
+ super();
+
+ this.name = "Re-encrypt payment data";
+ this.module = "Payment";
+ this.description = "Paste ciphertext into the input field as hex, decrypt it under the source key context, then re-encrypt it under the target key context.
Input: source ciphertext hex.
Arguments: choose source and target profiles, provide the corresponding key or BDK material, add IVs, and supply KSN plus DUKPT variant when using DUKPT profiles.";
+ this.inlineHelp = "Input: source ciphertext hex.
Args: define the source decrypt context, then the target encrypt context.";
+ this.testDataSamples = [
+ {
+ name: "AES CBC to TDES CBC sample",
+ input: "76D0627DA1D290436E21A4AF7FCA94B7177C1FC94173D442E36EE79D7CA0E461",
+ args: ["AES CBC", "00112233445566778899AABBCCDDEEFF", "000102030405060708090A0B0C0D0E0F", "", "Data", "TDES CBC", "0123456789ABCDEFFEDCBA9876543210", "1234567890ABCDEF", "", "Data", false]
+ }
+ ];
+ this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_ReEncryptData.html";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ { name: "Source profile", type: "option", value: PAYMENT_CIPHER_PROFILES, comment: "How to decrypt the input ciphertext." },
+ { name: "Source key / BDK", type: "string", value: "", comment: "Source clear AES/TDES key or DUKPT BDK." },
+ { name: "Source IV (hex)", type: "string", value: "", comment: "Source IV as hex. Leave blank for ECB." },
+ { name: "Source KSN (DUKPT only)", type: "string", value: "", comment: "Required only for DUKPT source profiles." },
+ { name: "Source DUKPT variant", type: "option", value: DUKPT_DATA_VARIANTS, defaultIndex: 1, comment: "Applies only to DUKPT source profiles." },
+ { name: "Target profile", type: "option", value: PAYMENT_CIPHER_PROFILES, comment: "How to encrypt the recovered plaintext." },
+ { name: "Target key / BDK", type: "string", value: "", comment: "Target clear AES/TDES key or DUKPT BDK." },
+ { name: "Target IV (hex)", type: "string", value: "", comment: "Target IV as hex. Leave blank for ECB." },
+ { name: "Target KSN (DUKPT only)", type: "string", value: "", comment: "Required only for DUKPT target profiles." },
+ { name: "Target DUKPT variant", type: "option", value: DUKPT_DATA_VARIANTS, defaultIndex: 1, comment: "Applies only to DUKPT target profiles." },
+ { name: "Output as JSON", type: "boolean", value: false, comment: "When enabled, returns both the source decrypt and target encrypt contexts." },
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [sourceProfile, sourceKeyHex, sourceIvHex, sourceKsn, sourceDukptVariant, targetProfile, targetKeyHex, targetIvHex, targetKsn, targetDukptVariant, outputJson] = args;
+ const result = reEncryptPaymentData(input, {
+ sourceProfile,
+ sourceKeyHex,
+ sourceIvHex,
+ sourceKsn,
+ sourceDukptVariant,
+ targetProfile,
+ targetKeyHex,
+ targetIvHex,
+ targetKsn,
+ targetDukptVariant,
+ });
+ return outputJson ? JSON.stringify(result, null, 4) : result.target.ciphertextHex;
+ }
+}
+
+export default ReEncryptPaymentData;
diff --git a/src/core/operations/TranslatePaymentPINData.mjs b/src/core/operations/TranslatePaymentPINData.mjs
new file mode 100644
index 00000000..574c4c64
--- /dev/null
+++ b/src/core/operations/TranslatePaymentPINData.mjs
@@ -0,0 +1,52 @@
+/**
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import TranslatePINBlock from "./TranslatePINBlock.mjs";
+
+/**
+ * Translate payment PIN data operation.
+ */
+class TranslatePaymentPINData extends Operation {
+ /**
+ * TranslatePaymentPINData constructor.
+ */
+ constructor() {
+ super();
+
+ this.name = "Translate payment PIN data";
+ this.module = "Payment";
+ this.description = "Paste a clear PIN block into the input field as hex and translate it between supported clear ISO 9564 formats using an AWS-style wrapper.
Input: clear PIN block hex.
Arguments: choose source and target formats, provide PAN values when required, and optionally randomize target filler digits.";
+ this.inlineHelp = "Input: source clear PIN block hex.
Args: define source and target format plus PAN context.";
+ this.testDataSamples = [
+ {
+ name: "Format 0 to 1 sample",
+ input: "041215FEDCBA9876",
+ args: ["ISO Format 0", "5432101234567890", "ISO Format 1", "", false]
+ }
+ ];
+ this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_TranslatePinData.html";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ { name: "Source format", type: "option", value: ["ISO Format 0", "ISO Format 1", "ISO Format 3"], comment: "How to decode the input PIN block." },
+ { name: "Source PAN", type: "string", value: "", comment: "Required for source formats 0 and 3." },
+ { name: "Target format", type: "option", value: ["ISO Format 0", "ISO Format 1", "ISO Format 3"], defaultIndex: 1, comment: "Target clear PIN-block format." },
+ { name: "Target PAN", type: "string", value: "", comment: "Required for target formats 0 and 3." },
+ { name: "Randomize target fill digits", type: "boolean", value: false, comment: "Affects only target formats 1 and 3." },
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const translator = new TranslatePINBlock();
+ return translator.run(input, args);
+ }
+}
+
+export default TranslatePaymentPINData;
diff --git a/src/core/operations/VerifyEMVARQC.mjs b/src/core/operations/VerifyEMVARQC.mjs
new file mode 100644
index 00000000..798c612c
--- /dev/null
+++ b/src/core/operations/VerifyEMVARQC.mjs
@@ -0,0 +1,56 @@
+/**
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import { generateEmvAesCmacCryptogram } from "../lib/EmvCryptogram.mjs";
+
+/**
+ * Verify EMV ARQC operation.
+ */
+class VerifyEMVARQC extends Operation {
+ /**
+ * VerifyEMVARQC constructor.
+ */
+ constructor() {
+ super();
+
+ this.name = "Verify EMV ARQC";
+ this.module = "Payment";
+ this.description = "Paste the already-assembled EMV authorization-request input into the input field as hex and verify an AES-CMAC-based ARQC.
Input: preassembled ARQC input data as hex.
Arguments: provide the EMV session key, cryptogram length, and expected ARQC hex value.
This operation intentionally covers only AES-CMAC-style EMV profiles where the session key and preimage are already known.";
+ this.inlineHelp = "Input: preassembled ARQC data as hex.
Args: provide the AES session key and expected ARQC.";
+ this.testDataSamples = [
+ {
+ name: "AES-CMAC ARQC verification sample",
+ input: "000102030405060708090A0B0C0D0E0F",
+ args: ["00112233445566778899AABBCCDDEEFF", 8, "C1F732B52FB20CAA"]
+ }
+ ];
+ this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VerifyAuthRequestCryptogram.html";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ { name: "Session key (hex)", type: "string", value: "", comment: "Provide the already-derived EMV session key as hex. This wrapper does not derive EMV session keys." },
+ { name: "Cryptogram bytes", type: "number", value: 8, min: 1, max: 16, comment: "Number of leftmost CMAC bytes to compare." },
+ { name: "Expected ARQC (hex)", type: "string", value: "", comment: "Expected ARQC value as hex." },
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [sessionKeyHex, cryptogramBytes, expectedArqc] = args;
+ const generated = generateEmvAesCmacCryptogram(input, sessionKeyHex, cryptogramBytes);
+ const normalizedExpected = (expectedArqc || "").replace(/\s+/g, "").toUpperCase();
+ return JSON.stringify({
+ ...generated,
+ expectedArqcHex: normalizedExpected,
+ valid: generated.cryptogramHex === normalizedExpected
+ }, null, 4);
+ }
+}
+
+export default VerifyEMVARQC;
diff --git a/src/core/operations/VerifyPaymentPINData.mjs b/src/core/operations/VerifyPaymentPINData.mjs
new file mode 100644
index 00000000..3ac09bfd
--- /dev/null
+++ b/src/core/operations/VerifyPaymentPINData.mjs
@@ -0,0 +1,56 @@
+/**
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import ParsePINBlock from "./ParsePINBlock.mjs";
+
+/**
+ * Verify payment PIN data operation.
+ */
+class VerifyPaymentPINData extends Operation {
+ /**
+ * VerifyPaymentPINData constructor.
+ */
+ constructor() {
+ super();
+
+ this.name = "Verify payment PIN data";
+ this.module = "Payment";
+ this.description = "Paste a clear PIN block into the input field as hex and verify it against an expected PIN using an AWS-style wrapper.
Input: clear PIN block hex.
Arguments: choose the format, provide the PAN when required, and supply the expected clear PIN.";
+ this.inlineHelp = "Input: clear PIN block hex.
Args: define the PIN-block format, PAN context, and expected PIN.";
+ this.testDataSamples = [
+ {
+ name: "Format 0 verification sample",
+ input: "041215FEDCBA9876",
+ args: ["ISO Format 0", "5432101234567890", "1234"]
+ }
+ ];
+ this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VerifyPinData.html";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ { name: "Format", type: "option", value: ["ISO Format 0", "ISO Format 1", "ISO Format 3"], comment: "How to decode the input PIN block." },
+ { name: "Primary account number", type: "string", value: "", comment: "Required for formats 0 and 3." },
+ { name: "Expected PIN", type: "string", value: "", comment: "Clear PIN digits to compare against." },
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [format, pan, expectedPin] = args;
+ const parser = new ParsePINBlock();
+ const parsed = JSON.parse(parser.run(input, [format, pan]));
+ return JSON.stringify({
+ ...parsed,
+ expectedPin,
+ valid: parsed.pin === String(expectedPin || "")
+ }, null, 4);
+ }
+}
+
+export default VerifyPaymentPINData;
diff --git a/tests/operations/tests/Payment.mjs b/tests/operations/tests/Payment.mjs
index c5bf0847..6733b2a6 100644
--- a/tests/operations/tests/Payment.mjs
+++ b/tests/operations/tests/Payment.mjs
@@ -255,6 +255,57 @@ TestRegister.addTests([
}
]
},
+ {
+ name: "Verify EMV ARQC: AES-CMAC profile",
+ input: "000102030405060708090A0B0C0D0E0F",
+ expectedOutput: JSON.stringify({
+ inputHex: "000102030405060708090A0B0C0D0E0F",
+ outputBytes: 8,
+ fullMacHex: "C1F732B52FB20CAAB58D5B6C78CBD514",
+ cryptogramHex: "C1F732B52FB20CAA",
+ expectedArqcHex: "C1F732B52FB20CAA",
+ valid: true
+ }, null, 4),
+ recipeConfig: [
+ {
+ op: "Verify EMV ARQC",
+ args: ["00112233445566778899AABBCCDDEEFF", 8, "C1F732B52FB20CAA"]
+ }
+ ]
+ },
+ {
+ name: "Encrypt payment data: AES CBC",
+ input: "00112233445566778899AABBCCDDEEFF",
+ expectedOutput: "67423557CA0509243B9EE04A5DA3448AA397F6D29B5C8BCE065D9CDC936B7F9B",
+ recipeConfig: [
+ {
+ op: "Encrypt payment data",
+ args: ["AES CBC", "00112233445566778899AABBCCDDEEFF", "000102030405060708090A0B0C0D0E0F", "", "Data", false]
+ }
+ ]
+ },
+ {
+ name: "Decrypt payment data: AES CBC",
+ input: "67423557CA0509243B9EE04A5DA3448AA397F6D29B5C8BCE065D9CDC936B7F9B",
+ expectedOutput: "00112233445566778899AABBCCDDEEFF",
+ recipeConfig: [
+ {
+ op: "Decrypt payment data",
+ args: ["AES CBC", "00112233445566778899AABBCCDDEEFF", "000102030405060708090A0B0C0D0E0F", "", "Data", false]
+ }
+ ]
+ },
+ {
+ name: "Re-encrypt payment data: AES CBC to TDES CBC",
+ input: "67423557CA0509243B9EE04A5DA3448AA397F6D29B5C8BCE065D9CDC936B7F9B",
+ expectedOutput: "C47BC6E91A9D566F649D750BCE1CE9889FB5AE1489A16692",
+ recipeConfig: [
+ {
+ op: "Re-encrypt payment data",
+ args: ["AES CBC", "00112233445566778899AABBCCDDEEFF", "000102030405060708090A0B0C0D0E0F", "", "Data", "TDES CBC", "0123456789ABCDEFFEDCBA9876543210", "1234567890ABCDEF", "", "Data", false]
+ }
+ ]
+ },
{
name: "Generate payment MAC: AES-CMAC",
input: "1122334455667788",
@@ -309,6 +360,63 @@ TestRegister.addTests([
}
]
},
+ {
+ name: "Generate payment PIN data: ISO Format 0",
+ input: "1234",
+ expectedOutput: "041215FEDCBA9876",
+ recipeConfig: [
+ {
+ op: "Generate payment PIN data",
+ args: ["ISO Format 0", "5432101234567890", false, false]
+ }
+ ]
+ },
+ {
+ name: "Translate payment PIN data: ISO Format 0 to ISO Format 1",
+ input: "041215FEDCBA9876",
+ expectedOutput: JSON.stringify({
+ source: {
+ format: "ISO Format 0",
+ pin: "1234",
+ pinLength: 4,
+ pinFieldHex: "041234FFFFFFFFFF",
+ panFieldHex: "0000210123456789",
+ blockHex: "041215FEDCBA9876",
+ fillDigitsHex: "FFFFFFFFFF"
+ },
+ target: {
+ format: "ISO Format 1",
+ blockHex: "141234FFFFFFFFFF"
+ }
+ }, null, 4),
+ recipeConfig: [
+ {
+ op: "Translate payment PIN data",
+ args: ["ISO Format 0", "5432101234567890", "ISO Format 1", "", false]
+ }
+ ]
+ },
+ {
+ name: "Verify payment PIN data: ISO Format 0",
+ input: "041215FEDCBA9876",
+ expectedOutput: JSON.stringify({
+ format: "ISO Format 0",
+ pin: "1234",
+ pinLength: 4,
+ pinFieldHex: "041234FFFFFFFFFF",
+ panFieldHex: "0000210123456789",
+ blockHex: "041215FEDCBA9876",
+ fillDigitsHex: "FFFFFFFFFF",
+ expectedPin: "1234",
+ valid: true
+ }, null, 4),
+ recipeConfig: [
+ {
+ op: "Verify payment PIN data",
+ args: ["ISO Format 0", "5432101234567890", "1234"]
+ }
+ ]
+ },
{
name: "Derive ECDH key material: raw shared secret",
input: ecdhPrivateKey,