feat(emv): add nfc_emv_tvr_decode — EMV Terminal Verification Results (tag 95) field decode#71
Merged
Merged
Conversation
… (tag 95) field decode
Continues the nfc_emv_* decode family (Track-2, DOL, AFL, CVM List, AIP) to
the Terminal Verification Results (tag 95) — the terminal-side companion to
last release's card-side AIP. The TVR is the terminal's pass/fail verdict on
a transaction: a 5-byte bitfield where each set bit is an exception the
terminal flagged. It is exactly what you reconstruct when examining a declined
or fraud transaction — which offline-authentication, application-usage,
cardholder-verification, risk-management and issuer-script checks failed or
were skipped.
nfc_emv_tvr_decode cracks it. All five bytes are decoded per EMV 4.3 Book 3,
Annex C5 and grouped by EMV's own functional byte layout:
- byte 1 offline data authentication: SDA/DDA/CDA failed, ODA not performed,
ICC data missing, card on exception file, SDA selected
- byte 2 application usage: version mismatch, expired / not-yet-effective,
service not allowed, new card
- byte 3 cardholder verification: CVM not successful / unrecognised, PIN Try
Limit exceeded, PIN pad missing, PIN not entered, online PIN
- byte 4 terminal risk management: floor limit, lower/upper consecutive
offline limits, random/merchant-forced online
- byte 5 issuer-to-card script processing: default TDOL used, issuer
authentication failed, script failed before/after final GENERATE AC
A flat 'indications' list gives a one-glance read of everything flagged, and
'clean' reports an all-zero TVR (no exceptions). Unlike the AIP, all 40 TVR
bits are terminal-defined and stable across payment systems — no
contactless-kernel reinterpretation.
No confidently-wrong output: any RFU bit that is set is surfaced via a
per-byte note rather than named. Gated structurally to exactly 5 bytes.
Accepts either the 5-byte TVR value directly or a full EMV BER-TLV blob (tag
95 extracted automatically). Offline transform — reads bytes, transmits
nothing — so it is Low risk like the rest of the EMV decode family.
Verification: the decode is a direct mask of the published EMV bit table, so
the single-bit vectors (one bit per functional byte, in isolation) are
authoritative; plus a realistic offline-decline TVR (4000808000 = SDA failed +
CVM not successful + over floor limit), the all-zero clean case, the
RFU-note path, and the 5-byte structural gate. gofmt / go vet /
golangci-lint (0 issues) / go test -race (emv, risk, tools) all green.
Wired: internal/emv/{tvr.go,tvr_test.go}, internal/tools/emv_tvr.go,
risk.go classification (Low, consistency test green), registry_size_test.go
677 -> 678 (count + comment), and the gap-analysis EMV row.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
nfc_emv_tvr_decode, extending thenfc_emv_*decode family (Track-2 / DOL / AFL / CVM-List / AIP) to the Terminal Verification Results (tag 95) — the terminal-side companion to last release's card-side AIP.The TVR is the terminal's pass/fail verdict on a transaction: a 5-byte bitfield where each set bit is an exception the terminal flagged. It's exactly what you reconstruct when examining a declined or fraud transaction — which offline-auth, application-usage, cardholder-verification, risk-management and issuer-script checks failed or were skipped.
Decode (EMV 4.3 Book 3, Annex C5)
Grouped by EMV's own functional byte layout:
A flat
indicationslist gives a one-glance read of everything flagged;cleanreports an all-zero TVR. Unlike the AIP, all 40 bits are terminal-defined and stable — no contactless-kernel reinterpretation.No confidently-wrong output
Any set RFU bit is surfaced via a per-byte note, never named. Gated structurally to exactly 5 bytes. Accepts the 5-byte value directly or a full EMV BER-TLV blob (tag 95 auto-extracted). Offline transform, transmits nothing → Low risk.
Verification
Direct mask of the published EMV bit table, so the per-bit vectors are authoritative. Tests cover: one bit per functional byte in isolation; a realistic offline-decline TVR (
4000808000= SDA failed + CVM not successful + over floor limit); the all-zero clean case; the RFU-note path; and the 5-byte structural gate.gofmt/go vetcleangolangci-lint run ./...→ 0 issuesgo test -race ./internal/emv/ ./internal/risk/ ./internal/tools/→ allokWired
internal/emv/{tvr.go,tvr_test.go},internal/tools/emv_tvr.go,risk.go(Low),registry_size_test.go(count + comment), gap-analysis EMV row.