Widen FPSAP exchange coverage to 151 vectors - #22
Conversation
TestFPSAPExchangeGoldenVectors covers seven payloads. This adds a corpus test alongside it, built the same way as TestFairPlaySAPHashCorpus: a deterministic payload generator and one aggregate SHA-256, so it costs one constant rather than a fixture file. The payloads are structural first -- one byte set on and around every 16-byte block boundary, one bit set in each of the eight blocks, all-ones with a single byte cleared -- then a pseudorandom tail from the same xorshift generator the SAP-hash corpus uses. The expected aggregate was produced by a separate implementation of this exchange, derived independently from Apple's binary, so the test compares the two against each other rather than comparing this package with itself. Both agree on all 151 payloads. Verified to fail when it should: perturbing the fpsapDigest32 IV by one changes the aggregate.
76eae64 to
13e1178
Compare
|
I'm not quite sure I understand this change. It adds additional tests, but the core implementation is still correct? |
13e1178 to
53b44d2
Compare
|
Correct — implementation's fine, this is test-only. All 151 pass against Since I opened it: this exchange path is now confirmed on real hardware. Three HomePods ( Why you'd want the corpus: One correction to my original description: I've since retired my own implementation for your closed form, so this is a self-consistency guard on your code, not two independent derivations. Weaker claim than I made. Your call entirely — it's test surface on a package you maintain, and no is a fine answer. |
What this adds
One test: a 151-payload corpus for the FPSAP exchange, next to the seven vectors in
TestFPSAPExchangeGoldenVectors.It follows
TestFairPlaySAPHashCorpus— deterministic payload generator, one aggregate SHA-256 — so it costs a single constant rather than a fixture file. No production code is touched.Where the expected value came from
Not from this package. I derived an implementation of the same exchange independently, by partial evaluation of an ARM64 execution trace, and
68f4a4d3…is what that produces. Two separate reverse engineerings landing on the same 151 outputs.One thing worth stating plainly: I have since retired that implementation in favour of your closed form — yours was smaller and I'd rather maintain one copy — so my tree now carries
fairplay_sap.goandfairplay_md5.goderived frominternal/airplay. The constant predates that swap and I've confirmed it is unchanged across it, but I can no longer re-derive it independently. Take it as a snapshot of agreement from when the two were genuinely separate.Your seven existing vectors were what I used to validate my implementation when I built it. They all passed, while the two were still independent.
What it covers
It fails when it should
Perturbing the
fpsapDigest32IV by one changes the aggregate:Restoring it passes.
go test ./internal/airplay/is green.While I was in here I also fuzzed the receiver-facing parsing —
exchangeM3,confirmM4,decryptFPSAPBody— for 10.3 million executions across the three, no crashes. Nothing to fix; just closing the loop in case it's useful to know.