diff --git a/pipeline.developer-examples/pipeline.developer-examples.fodid/README.md b/pipeline.developer-examples/pipeline.developer-examples.fodid/README.md index e7cb93bf7..62cd57a7b 100644 --- a/pipeline.developer-examples/pipeline.developer-examples.fodid/README.md +++ b/pipeline.developer-examples/pipeline.developer-examples.fodid/README.md @@ -6,7 +6,7 @@ This module holds the developer examples for the 51Did package | Program | What it shows | | --- | --- | -| `Main` | Reads a 51Did offline. Builds a sample identifier in process, parses it back with `FodId` and shows that the value is stable while the envelope changes on every issue. Needs no cloud access. | +| `Main` | Reads a 51Did offline. Builds a sample identifier in process, parses it back with `FodId` and shows that the match key is stable while the envelope changes on every issue. Needs no cloud access. | | `CreatorContextDemoServer` | Serves a small web page that creates a 51Did in the browser, verifies it from the browser, and redeems the encrypted creator context result on this server with `DidClient`, which is the only place the licence key lives. | ## Creator context diff --git a/pipeline.developer-examples/pipeline.developer-examples.fodid/src/main/java/pipeline/developerexamples/fodid/Main.java b/pipeline.developer-examples/pipeline.developer-examples.fodid/src/main/java/pipeline/developerexamples/fodid/Main.java index 1e98446fa..7e82805eb 100644 --- a/pipeline.developer-examples/pipeline.developer-examples.fodid/src/main/java/pipeline/developerexamples/fodid/Main.java +++ b/pipeline.developer-examples/pipeline.developer-examples.fodid/src/main/java/pipeline/developerexamples/fodid/Main.java @@ -37,8 +37,8 @@ * back with {@link FodId} and prints the three payload fields. *

* It also demonstrates the headline use case: a 51Did is re-issued fresh on - * every call (the envelope, hence the base64, changes), but the value (the - * Hash) is stable. Compare values, never envelopes. + * every call (the envelope, hence the base64, changes), but the match key + * is stable. Compare match keys, never envelopes. */ public class Main { @@ -62,28 +62,29 @@ public void run() throws Exception { System.out.println(" Flags : 0x" + Integer.toHexString(fodId.getFlags())); System.out.println(" LicenseId : " + fodId.getLicenseId()); - System.out.println(" Hash : " + toHex(fodId.getHash())); + System.out.println(" Match key : " + toHex(fodId.getMatchKey())); System.out.println(" Verifies : " + fodId.verify(crypto.publicKeyPem())); - // Issue the SAME payload again: a separate envelope, same value. + // Issue the SAME payload again: a separate envelope, same match + // key. FodId reissued = FodId.fromBase64(issue(creator, payload)); boolean sameEnvelope = fodId.asBase64().equals(reissued.asBase64()); - boolean sameValue = - Arrays.equals(fodId.getHash(), reissued.getHash()); + boolean sameMatchKey = + Arrays.equals(fodId.getMatchKey(), reissued.getMatchKey()); System.out.println(); System.out.println("Same payload, re-issued:"); System.out.println(" Same envelope (base64) : " + sameEnvelope); - System.out.println(" Same value (Hash) : " + sameValue); + System.out.println(" Same match key : " + sameMatchKey); - // The reader's whole purpose: the value is the stable, comparable - // part while the envelope is not. - if (sameEnvelope || !sameValue) { + // The reader's whole purpose: the match key is the stable, + // comparable part while the envelope is not. + if (sameEnvelope || !sameMatchKey) { throw new IllegalStateException( - "Expected a different envelope but the same value " - + "across reissues."); + "Expected a different envelope but the same match " + + "key across reissues."); } } @@ -99,7 +100,7 @@ private String issue(Creator creator, byte[] payload) /** * A canonical 37-byte Probabilistic payload: flags 0x00, License Id - * 0x12345678 (little-endian) and a 32-byte value 0x20..0x3F. + * 0x12345678 (little-endian) and a 32-byte match key 0x20..0x3F. */ private byte[] samplePayload() { byte[] payload = new byte[FodId.PAYLOAD_LENGTH]; diff --git a/pipeline.developer-examples/pipeline.developer-examples.fodid/src/test/java/pipeline/developerexamples/fodid/ExampleTests.java b/pipeline.developer-examples/pipeline.developer-examples.fodid/src/test/java/pipeline/developerexamples/fodid/ExampleTests.java index 9451e81fc..7f37bc31e 100644 --- a/pipeline.developer-examples/pipeline.developer-examples.fodid/src/test/java/pipeline/developerexamples/fodid/ExampleTests.java +++ b/pipeline.developer-examples/pipeline.developer-examples.fodid/src/test/java/pipeline/developerexamples/fodid/ExampleTests.java @@ -72,8 +72,8 @@ public void init() throws Exception { /** * The 51Did example is fully offline, so unlike the cloud examples it must * complete without throwing. {@code run()} also self-checks the - * value-stable / envelope-changes invariant and throws if it does not - * hold. + * invariant that the match key is stable while the envelope changes, + * and throws if it does not hold. */ @Test public void FodId_Example_Test() throws Exception { @@ -241,7 +241,7 @@ private static String keyList(Crypto crypto) { /** * A canonical 37-byte Probabilistic payload: flags 0x00, License Id - * 0x12345678 (little-endian) and a 32-byte value 0x20..0x3F. + * 0x12345678 (little-endian) and a 32-byte match key 0x20..0x3F. */ private static byte[] samplePayload() { byte[] payload = new byte[FodId.PAYLOAD_LENGTH]; diff --git a/pipeline.did/README.md b/pipeline.did/README.md index be047f66a..21b9f18f1 100644 --- a/pipeline.did/README.md +++ b/pipeline.did/README.md @@ -13,26 +13,27 @@ A 51Did is described at three levels, and the wording is deliberate. the version, domain, date, payload and signature. It changes byte-for-byte every time the cloud issues one, even for the same inputs, because the date and signature change on each call. -- The **value** is the stable, comparable part of the payload after the Flags - and License Id: a 32-byte SHA-256 for Probabilistic and HashedEmail +- The **match key** is the stable, comparable part of the payload after the + Flags and License Id, a 32-byte SHA-256 for Probabilistic and HashedEmail identifiers, or 16 GUID bytes for Random. Two 51Dids for the same inputs - share the same value even though their envelopes differ. + share the same match key even though their envelopes differ. -**Comparing two 51Dids means comparing their values, never their envelopes.** +**Comparing two 51Dids means comparing their match keys, never their +envelopes.** ## Payload layout -The header is shared by every identifier type; bits 6-7 of Flags select the -type and the length of the value that follows. +The header is shared by every identifier type. Bits 6-7 of Flags select the +type and the length of the match key that follows. | Offset | Length | Field | Type | |-------:|-------:|------------|-------------------------------------------------| | 0 | 1 | Flags | uint8: bits 0-2 usage, bits 6-7 identifier type | | 1 | 4 | LicenseId | uint32 (little-endian) | -| 5 | 16/32 | Value | SHA-256 (Probabilistic, HashedEmail) or GUID (Random) | +| 5 | 16/32 | Match key | SHA-256 (Probabilistic, HashedEmail) or GUID (Random) | | after | any | Context | Optional creator context section, readable only by 51Degrees | -| Bits 7-6 | `IdType` | Value length | Minimum payload | +| Bits 7-6 | `IdType` | Match key length | Minimum payload | |---------:|-----------------|-------------:|----------------:| | `00` | `PROBABILISTIC` | 32 | 37 | | `01` | `RANDOM` | 16 | 21 | @@ -172,7 +173,7 @@ FodId fodId = FodId.fromBase64(base64FromCloudService); int flags = fodId.getFlags(); IdType type = fodId.getType(); // PROBABILISTIC / RANDOM / HASHED_EMAIL long licenseId = fodId.getLicenseId(); -byte[] hash = fodId.getHash(); // SHA-256 or GUID bytes, see type +byte[] matchKey = fodId.getMatchKey(); // SHA-256 or GUID bytes, see type // Delegated OWID-level fields and operations. String domain = fodId.getDomain(); @@ -189,11 +190,13 @@ FodId a = FodId.fromBase64(idprobglobalA); FodId b = FodId.fromBase64(idprobglobalB); // The envelope (date, signature, base64) differs across reissues. -// The value inside the payload is stable - this is what you compare: -boolean sameValue = java.util.Arrays.equals(a.getHash(), b.getHash()); +// The match key inside the payload is stable - this is what you compare: +boolean sameMatchKey = java.util.Arrays.equals(a.getMatchKey(), b.getMatchKey()); ``` -Use `getHash()` as the cache / dedup key. +Use `getMatchKey()` as the cache / dedup key. `getHash()` remains as a +deprecated alias of `getMatchKey()`, returning the same bytes, and will be +removed in a future release. ## Verifying on your server @@ -342,4 +345,4 @@ signed envelopes there is no longer a copy made inside it. - **No creation of new 51Dids.** This is a reader and a verifier; new 51Dids are issued by the 51Degrees cloud / on-premise hashing engines. - **No upper bound on a payload.** The cloud owns the shape of anything past - the value, and this package does not second-guess it. + the match key, and this package does not second-guess it. diff --git a/pipeline.did/pom.xml b/pipeline.did/pom.xml index 76f140c31..4598505c4 100644 --- a/pipeline.did/pom.xml +++ b/pipeline.did/pom.xml @@ -37,8 +37,8 @@ 51Degrees :: Pipeline :: 51Did Strongly typed reader for the 51Did (51Degrees Identifier) value returned by the 51Degrees Cloud service. Parses the OWID - envelope and exposes the Flags, License Id and value (Hash) plus the - identifier type. Compare values, never envelopes. + envelope and exposes the Flags, License Id and match key plus the + identifier type. Compare match keys, never envelopes. https://51degrees.com?utm_source=maven&utm_medium=package&utm_campaign=pipeline-java&utm_content=pipeline.did-pom.xml&utm_term=url diff --git a/pipeline.did/src/main/java/fiftyone/pipeline/did/FodId.java b/pipeline.did/src/main/java/fiftyone/pipeline/did/FodId.java index 8fd6e23ca..5445809be 100644 --- a/pipeline.did/src/main/java/fiftyone/pipeline/did/FodId.java +++ b/pipeline.did/src/main/java/fiftyone/pipeline/did/FodId.java @@ -42,25 +42,26 @@ * A 51Did is described at three levels, and the wording here is deliberate. * The 51Did is the identifier as a whole. The envelope is the * signed {@link Owid} that carries it (version, domain, date, payload, - * signature), re-issued fresh on every call. The value is the stable, - * comparable part of the payload after the Flags and License Id, exposed as - * {@link #getHash()}. Two 51Dids for the same inputs share the same value even - * though their envelopes differ on every issue. Compare values, never - * envelopes. + * signature), re-issued fresh on every call. The match key is the + * stable, comparable part of the payload after the Flags and License Id, + * exposed as {@link #getMatchKey()}. Two 51Dids for the same inputs share the + * same match key even though their envelopes differ on every issue. + * Compare match keys, never envelopes. *

- * Payload layout. The header (offsets 0-4) is shared by every identifier type; - * bits 6-7 of Flags select the {@link IdType} and the length of the value that - * follows: + * Payload layout. The header (offsets 0-4) is shared by every identifier type. + * Bits 6-7 of Flags select the {@link IdType} and the length of the match key + * that follows: *

*

* Reading and verifying are two separate steps. {@link #tryFromBase64(String)} @@ -95,10 +96,10 @@ public final class FodId { /** Byte length of the License Id field. */ public static final int LICENSE_ID_LENGTH = 4; - /** Byte offset of the value (Hash) field within the payload. */ + /** Byte offset of the match key field within the payload. */ public static final int HASH_OFFSET = 5; - /** Byte length of the SHA-256 value. */ + /** Byte length of the match key field (SHA-256). */ public static final int HASH_LENGTH = 32; /** @@ -107,7 +108,7 @@ public final class FodId { */ public static final int HEADER_LENGTH = HASH_OFFSET; - /** Byte length of the GUID value carried by Random identifiers. */ + /** Byte length of the GUID match key carried by Random identifiers. */ public static final int GUID_LENGTH = 16; /** @@ -118,7 +119,7 @@ public final class FodId { /** * Minimum byte length of a Probabilistic or HashedEmail 51Did payload - * (Flags + License Id + Hash). Random payloads are shorter - see + * (Flags + License Id + match key). Random payloads are shorter - see * {@link #RANDOM_PAYLOAD_LENGTH}. */ public static final int PAYLOAD_LENGTH = HASH_OFFSET + HASH_LENGTH; @@ -132,18 +133,18 @@ public final class FodId { private final Owid owid; private final int flags; private final long licenseId; - private final byte[] hash; + private final byte[] matchKey; /** * Built only by {@link #read(Owid)} once the payload has passed the * 51Did rules, so an instance never exists for a payload that failed * them. */ - private FodId(Owid owid, int flags, long licenseId, byte[] hash) { + private FodId(Owid owid, int flags, long licenseId, byte[] matchKey) { this.owid = owid; this.flags = flags; this.licenseId = licenseId; - this.hash = hash; + this.matchKey = matchKey; } // ----- Reading without throwing ----- @@ -205,7 +206,8 @@ private static FodIdParseResult read(OwidParseResult envelope) { * The rules are lower bounds only. The header must be present before the * type can be read, and the type then sets the least the payload can * hold. Anything longer is accepted as it stands, because the bytes past - * the value are a creator context section whose shape the cloud judges. + * the match key are a creator context section whose shape the cloud + * judges. */ private static FodIdParseResult read(Owid owid) { byte[] payload = owid.getPayload(); @@ -213,21 +215,22 @@ private static FodIdParseResult read(Owid owid) { return FodIdParseResult.failed(FodIdParseStatus.PAYLOAD_TOO_SHORT); } int flags = payload[FLAGS_OFFSET] & 0xFF; - int valueLength; + int matchKeyLength; switch (IdType.fromFlags(flags)) { case RANDOM: - valueLength = GUID_LENGTH; + matchKeyLength = GUID_LENGTH; break; case RESERVED: // Not yet assigned, so read best-effort. The header fields - // are unpacked and whatever follows is exposed as the value. - valueLength = payload.length - HEADER_LENGTH; + // are unpacked and whatever follows is exposed as the + // match key. + matchKeyLength = payload.length - HEADER_LENGTH; break; default: - valueLength = HASH_LENGTH; + matchKeyLength = HASH_LENGTH; break; } - if (payload.length < HEADER_LENGTH + valueLength) { + if (payload.length < HEADER_LENGTH + matchKeyLength) { return FodIdParseResult.failed( FodIdParseStatus.INVALID_TYPE_PAYLOAD_LENGTH); } @@ -238,12 +241,13 @@ private static FodIdParseResult read(Owid owid) { | ((payload[LICENSE_ID_OFFSET + 1] & 0xFFL) << 8) | ((payload[LICENSE_ID_OFFSET + 2] & 0xFFL) << 16) | ((payload[LICENSE_ID_OFFSET + 3] & 0xFFL) << 24); - // The value is copied out so that mutating the array a caller gets - // back from getHash() can never reach the envelope's own bytes. - byte[] hash = Arrays.copyOfRange( - payload, HASH_OFFSET, HASH_OFFSET + valueLength); + // The match key is copied out so that mutating the array a caller + // gets back from getMatchKey() can never reach the envelope's own + // bytes. + byte[] matchKey = Arrays.copyOfRange( + payload, HASH_OFFSET, HASH_OFFSET + matchKeyLength); return FodIdParseResult.parsed( - new FodId(owid, flags, licenseId, hash)); + new FodId(owid, flags, licenseId, matchKey)); } // ----- Reading with exceptions ----- @@ -396,15 +400,31 @@ public long getLicenseId() { } /** - * Returns the value bytes from the payload (a 32-byte SHA-256 for + * Returns the match key from the payload (a 32-byte SHA-256 for * Probabilistic and HashedEmail identifiers, or 16 GUID bytes for Random). - * This is the stable, comparable part of the envelope - use it as the - * cache / dedup key. + * The match key is the stable, comparable part of the envelope. Two + * 51Dids for the same inputs share the same match key even though their + * envelopes (date, signature) differ on every issue, so use the match + * key as the cache / dedup key. * - * @return a defensive copy of the value bytes + * @return a defensive copy of the match key bytes */ + public byte[] getMatchKey() { + return matchKey.clone(); + } + + /** + * Deprecated alias for {@link #getMatchKey()}. The stable, comparable + * part of a 51Did is now called the match key, mirroring the Model Terms + * for Marketing vocabulary. This alias will be removed in a future + * release. + * + * @return the same bytes as {@link #getMatchKey()} + * @deprecated renamed to {@link #getMatchKey()} + */ + @Deprecated public byte[] getHash() { - return hash.clone(); + return getMatchKey(); } /** @return the OWID version. */ diff --git a/pipeline.did/src/main/java/fiftyone/pipeline/did/FodIdParseStatus.java b/pipeline.did/src/main/java/fiftyone/pipeline/did/FodIdParseStatus.java index a9f998561..447f1c07a 100644 --- a/pipeline.did/src/main/java/fiftyone/pipeline/did/FodIdParseStatus.java +++ b/pipeline.did/src/main/java/fiftyone/pipeline/did/FodIdParseStatus.java @@ -110,8 +110,8 @@ public enum FodIdParseStatus { * The envelope read and the header names a type, but the payload is * shorter than the minimum for that type. Random needs the header plus * 16 GUID bytes, and Probabilistic and HashedEmail need the header plus - * a 32 byte hash. A longer payload is never refused here, because - * anything past the value is a creator context section whose lengths + * a 32 byte match key. A longer payload is never refused here, because + * anything past the match key is a creator context section whose lengths * belong to the cloud. */ INVALID_TYPE_PAYLOAD_LENGTH; diff --git a/pipeline.did/src/main/java/fiftyone/pipeline/did/package-info.java b/pipeline.did/src/main/java/fiftyone/pipeline/did/package-info.java index 74d564420..fbe521ffa 100644 --- a/pipeline.did/src/main/java/fiftyone/pipeline/did/package-info.java +++ b/pipeline.did/src/main/java/fiftyone/pipeline/did/package-info.java @@ -30,11 +30,11 @@ * {@link fiftyone.pipeline.did.FodIdParseResult} instead of throwing, whose * {@link fiftyone.pipeline.did.FodIdParseStatus} names why an input is not * a 51Did, and the {@code from} readers make the same read and throw. A - * 51Did exposes the three payload fields (Flags, License Id and the value - * Hash) and the identifier {@link fiftyone.pipeline.did.IdType}, and + * 51Did exposes the three payload fields (Flags, License Id and the match + * key) and the identifier {@link fiftyone.pipeline.did.IdType}, and * delegates OWID-level concerns to the envelope it holds. Reading never - * checks the signature. Compare 51Dids by their value ({@code getHash()}), - * never by their envelopes. + * checks the signature. Compare 51Dids by their match key + * ({@code getMatchKey()}), never by their envelopes. *

* {@link fiftyone.pipeline.did.DidClient} is what a server uses against the * 51Degrees cloud: it fetches and holds the published signing keys, verifies diff --git a/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdParseTests.java b/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdParseTests.java index 61b3f758f..9335549bc 100644 --- a/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdParseTests.java +++ b/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdParseTests.java @@ -34,7 +34,7 @@ import java.util.Arrays; import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_FLAGS; -import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_HASH; +import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_MATCH_KEY; import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_LICENSE_ID; import static fiftyone.pipeline.did.FodIdTestFactory.TEST_DOMAIN; import static fiftyone.pipeline.did.FodIdTestFactory.canonicalPayload; @@ -77,7 +77,7 @@ public void tryFromBase64_ValidIdentifier_ParsedWithValue() FodId fodId = assertParsed(result); assertEquals(CANONICAL_FLAGS, fodId.getFlags()); assertEquals(CANONICAL_LICENSE_ID, fodId.getLicenseId()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertEquals(TEST_DOMAIN, fodId.getDomain()); } @@ -93,7 +93,7 @@ public void tryFromBase64_UrlSafeUnpaddedWithWhitespace_Parsed() FodId.tryFromBase64(" " + urlSafe + "\r\n")); assertArrayEquals(fromUrlSafe.asByteArray(), fromSpaced.asByteArray()); - assertArrayEquals(CANONICAL_HASH, fromUrlSafe.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fromUrlSafe.getMatchKey()); } @Test @@ -103,7 +103,7 @@ public void tryFromByteArray_ValidIdentifier_ParsedWithValue() FodId fodId = assertParsed(FodId.tryFromByteArray(bytes)); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertArrayEquals(bytes, fodId.asByteArray()); } @@ -126,15 +126,15 @@ public void tryFromBase64_LongerSelfHostedDomain_Parsed() @Test public void tryFromBase64_LongerContextSection_Parsed() throws Exception { - // A payload longer than the value carries a creator context section + // A payload longer than the match key carries a creator context section // whose shape belongs to the cloud, so an older reader accepts it - // and exposes it as the payload beyond the value. + // and exposes it as the payload beyond the match key. byte[] payload = canonicalPayloadWithSection(512); String base64 = factory.signedOwidAt(payload, DATE).asBase64(); FodId fodId = assertParsed(FodId.tryFromBase64(base64)); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertArrayEquals(payload, fodId.getPayload()); assertTrue(fodId.verify(factory.publicPem)); } @@ -150,7 +150,7 @@ public void tryFromByteArray_MuchLongerPayload_NotRejectedForLength() FodId fodId = assertParsed(FodId.tryFromByteArray(bytes)); assertEquals(payload.length, fodId.getPayload().length); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); } byte[] random = Arrays.copyOf(canonicalRandomPayload(), 700); assertParsed(FodId.tryFromByteArray( @@ -183,7 +183,7 @@ public void tryFromBase64_ReservedHeaderOnly_ParsedBestEffort() factory.signedOwidAt(payload, DATE).asBase64())); assertEquals(IdType.RESERVED, fodId.getType()); - assertEquals(0, fodId.getHash().length); + assertEquals(0, fodId.getMatchKey().length); } // ----- 51Did payload rules ----- diff --git a/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTestFactory.java b/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTestFactory.java index f02d6c5d3..e92f6e77e 100644 --- a/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTestFactory.java +++ b/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTestFactory.java @@ -53,7 +53,7 @@ final class FodIdTestFactory { static final long CANONICAL_LICENSE_ID = 0x12345678L; - static final byte[] CANONICAL_HASH = canonicalHash(); + static final byte[] CANONICAL_MATCH_KEY = canonicalMatchKey(); static final Instant DATE_ORIGIN = Instant.parse("2020-01-01T00:00:00Z"); @@ -73,12 +73,12 @@ final class FodIdTestFactory { this.creator = Creator.create(TEST_DOMAIN, crypto); } - private static byte[] canonicalHash() { - byte[] hash = new byte[FodId.HASH_LENGTH]; - for (int i = 0; i < hash.length; i++) { - hash[i] = (byte) (0x20 + i); + private static byte[] canonicalMatchKey() { + byte[] matchKey = new byte[FodId.HASH_LENGTH]; + for (int i = 0; i < matchKey.length; i++) { + matchKey[i] = (byte) (0x20 + i); } - return hash; + return matchKey; } static byte[] canonicalPayload() { @@ -86,7 +86,8 @@ static byte[] canonicalPayload() { payload[FodId.FLAGS_OFFSET] = (byte) CANONICAL_FLAGS; writeCanonicalLicenseId(payload); System.arraycopy( - CANONICAL_HASH, 0, payload, FodId.HASH_OFFSET, FodId.HASH_LENGTH); + CANONICAL_MATCH_KEY, 0, payload, + FodId.HASH_OFFSET, FodId.HASH_LENGTH); return payload; } diff --git a/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTests.java b/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTests.java index b78a276ee..9afdce4c9 100644 --- a/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTests.java +++ b/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdTests.java @@ -35,7 +35,7 @@ import java.util.Base64; import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_FLAGS; -import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_HASH; +import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_MATCH_KEY; import static fiftyone.pipeline.did.FodIdTestFactory.CANONICAL_LICENSE_ID; import static fiftyone.pipeline.did.FodIdTestFactory.TEST_DOMAIN; import static fiftyone.pipeline.did.FodIdTestFactory.canonicalPayload; @@ -85,7 +85,7 @@ public void constructor_FromBase64_UnpacksAllThreeFields() throws Exception { assertEquals(CANONICAL_FLAGS, fodId.getFlags()); assertEquals(CANONICAL_LICENSE_ID, fodId.getLicenseId()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertEquals(TEST_DOMAIN, fodId.getDomain()); } @@ -98,7 +98,7 @@ public void constructor_FromBytes_UnpacksAllThreeFields() throws Exception { assertEquals(CANONICAL_FLAGS, fodId.getFlags()); assertEquals(CANONICAL_LICENSE_ID, fodId.getLicenseId()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertEquals(TEST_DOMAIN, fodId.getDomain()); } @@ -110,7 +110,7 @@ public void constructor_FromOwid_UnpacksAllThreeFields() throws Exception { assertEquals(CANONICAL_FLAGS, fodId.getFlags()); assertEquals(CANONICAL_LICENSE_ID, fodId.getLicenseId()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertEquals(owid.getDomain(), fodId.getDomain()); assertEquals(owid.getDate(), fodId.getDate()); assertEquals(owid.getVersion(), fodId.getVersion()); @@ -185,16 +185,28 @@ public void flags_AllBitsSet_Exposed() throws Exception { } @Test - public void hash_IsDefensiveCopy() throws Exception { + public void matchKey_IsDefensiveCopy() throws Exception { FodId fodId = FodId.fromBase64(factory.signedOwidBase64(canonicalPayload())); - byte[] hash = fodId.getHash(); - hash[0] = 0x00; - hash[FodId.HASH_LENGTH - 1] = 0x00; + byte[] matchKey = fodId.getMatchKey(); + matchKey[0] = 0x00; + matchKey[FodId.HASH_LENGTH - 1] = 0x00; - // Neither the underlying payload nor a fresh getHash() is affected. - assertEquals(CANONICAL_HASH[0], fodId.getPayload()[FodId.HASH_OFFSET]); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + // Neither the underlying payload nor a fresh getMatchKey() is affected. + assertEquals( + CANONICAL_MATCH_KEY[0], fodId.getPayload()[FodId.HASH_OFFSET]); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); + } + + @Test + @SuppressWarnings("deprecation") + public void getHash_DeprecatedAlias_ReturnsMatchKey() throws Exception { + // getHash() stays as a deprecated alias so that callers written + // against the earlier name keep compiling and get the same bytes. + FodId fodId = FodId.fromBase64(factory.signedOwidBase64(canonicalPayload())); + + assertArrayEquals(fodId.getMatchKey(), fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getHash()); } @Test @@ -238,8 +250,8 @@ public void constructor_PayloadLargerThanSpec_UsesFirst37Bytes() throws Exceptio assertEquals(CANONICAL_FLAGS, fodId.getFlags()); assertEquals(CANONICAL_LICENSE_ID, fodId.getLicenseId()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); - assertEquals(FodId.HASH_LENGTH, fodId.getHash().length); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); + assertEquals(FodId.HASH_LENGTH, fodId.getMatchKey().length); } @Test @@ -262,7 +274,7 @@ public void constructor_LongContextSectionAndLongDomain_Parses() for (FodId fodId : new FodId[] { fromBytes, fromBase64, fromOwid }) { assertEquals(longDomain, fodId.getDomain()); assertEquals(CANONICAL_FLAGS, fodId.getFlags()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertArrayEquals(payload, fodId.getPayload()); assertTrue(fodId.verify(factory.publicPem)); } @@ -281,7 +293,7 @@ public void base64Roundtrip_PreservesAllFields() throws Exception { assertEquals(fodId1.getFlags(), fodId2.getFlags()); assertEquals(fodId1.getLicenseId(), fodId2.getLicenseId()); - assertArrayEquals(fodId1.getHash(), fodId2.getHash()); + assertArrayEquals(fodId1.getMatchKey(), fodId2.getMatchKey()); assertEquals(fodId1.getDomain(), fodId2.getDomain()); } @@ -311,12 +323,12 @@ public void constructor_RandomPayload21Bytes_Parses() throws Exception { FodId fodId = FodId.fromBase64(factory.signedOwidBase64(canonicalRandomPayload())); assertEquals(CANONICAL_LICENSE_ID, fodId.getLicenseId()); - assertEquals(FodId.GUID_LENGTH, fodId.getHash().length); + assertEquals(FodId.GUID_LENGTH, fodId.getMatchKey().length); byte[] expected = new byte[FodId.GUID_LENGTH]; for (int i = 0; i < expected.length; i++) { expected[i] = (byte) (0x40 + i); } - assertArrayEquals(expected, fodId.getHash()); + assertArrayEquals(expected, fodId.getMatchKey()); } @Test @@ -328,7 +340,7 @@ public void constructor_RandomPayloadOneByteShort_Throws() throws Exception { } @Test - public void constructor_RandomPayloadLargerThanSpec_UsesFirst16ValueBytes() + public void constructor_RandomPayloadLargerThanSpec_UsesFirst16MatchKeyBytes() throws Exception { byte[] payload = new byte[FodId.PAYLOAD_LENGTH]; System.arraycopy( @@ -340,7 +352,7 @@ public void constructor_RandomPayloadLargerThanSpec_UsesFirst16ValueBytes() FodId fodId = FodId.fromBase64(factory.signedOwidBase64(payload)); assertEquals(IdType.RANDOM, fodId.getType()); - assertEquals(FodId.GUID_LENGTH, fodId.getHash().length); + assertEquals(FodId.GUID_LENGTH, fodId.getMatchKey().length); } @Test @@ -360,13 +372,13 @@ public void constructor_ReservedHeaderOnly_Parses() throws Exception { FodId fodId = FodId.fromBase64(factory.signedOwidBase64(payload)); assertEquals(IdType.RESERVED, fodId.getType()); - assertEquals(0, fodId.getHash().length); + assertEquals(0, fodId.getMatchKey().length); } // ----- Gap tests (runbook section 6b) ----- @Test - public void compareTwo51Dids_SamePayload_SameValueDifferentEnvelopes() + public void compareTwo51Dids_SamePayload_SameMatchKeyDifferentEnvelopes() throws Exception { byte[] payload = canonicalPayload(); // The creator stamps "now" to the minute, so two reissues at @@ -379,8 +391,8 @@ public void compareTwo51Dids_SamePayload_SameValueDifferentEnvelopes() FodId fodA = FodId.fromBase64(a.asBase64()); FodId fodB = FodId.fromBase64(b.asBase64()); - // The value is stable across reissues... - assertArrayEquals(fodA.getHash(), fodB.getHash()); + // The match key is stable across reissues... + assertArrayEquals(fodA.getMatchKey(), fodB.getMatchKey()); // ...while the envelope differs. assertNotEquals(fodA.getDate(), fodB.getDate()); assertFalse(Arrays.equals(fodA.getSignature(), fodB.getSignature())); @@ -402,7 +414,7 @@ public void construction_DoesNotVerify() throws Exception { assertEquals(CANONICAL_FLAGS, fodId.getFlags()); assertEquals(CANONICAL_LICENSE_ID, fodId.getLicenseId()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); } @Test @@ -413,7 +425,7 @@ public void fromOwid_HoldsTheEnvelopeItWasGiven() throws Exception { FodId fodId = FodId.fromOwid(owid); assertEquals(CANONICAL_FLAGS, fodId.getFlags()); - assertArrayEquals(CANONICAL_HASH, fodId.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fodId.getMatchKey()); assertArrayEquals(owid.asByteArray(), fodId.asByteArray()); } @@ -434,7 +446,7 @@ public void roundtrip_ThroughBytesConstructor_PreservesAllFields() assertEquals(fodId1.getFlags(), fodId2.getFlags()); assertEquals(fodId1.getLicenseId(), fodId2.getLicenseId()); - assertArrayEquals(fodId1.getHash(), fodId2.getHash()); + assertArrayEquals(fodId1.getMatchKey(), fodId2.getMatchKey()); assertEquals(fodId1.getDomain(), fodId2.getDomain()); } @@ -456,7 +468,7 @@ public void fromBase64_AcceptsStandardUrlSafeAndUnpadded() throws Exception { assertArrayEquals(fromStandard.asByteArray(), fromUrlSafe.asByteArray()); assertArrayEquals(fromStandard.asByteArray(), fromUnpadded.asByteArray()); - assertArrayEquals(CANONICAL_HASH, fromUnpadded.getHash()); + assertArrayEquals(CANONICAL_MATCH_KEY, fromUnpadded.getMatchKey()); } @Test