Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ pom.xml.versionsBackup
.project
.settings/
*.classpath
bin/
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[submodule "pipeline.javascriptbuilder/src/main/resources/fiftyone/pipeline/javascriptbuilder/templates"]
path = pipeline.javascriptbuilder/src/main/resources/fiftyone/pipeline/javascriptbuilder/templates
url = ../javascript-templates.git
[submodule "owid-java"]
path = owid-java
url = ../owid-java.git
branch = main
1 change: 1 addition & 0 deletions owid-java
Submodule owid-java added at cb0397
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This Original Work is copyright of 51 Degrees Mobile Experts Limited.
~ Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
~ Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
~
~ This Original Work is licensed under the European Union Public Licence
~ (EUPL) v.1.2 and is subject to its terms as set out below.
~
~ If a copy of the EUPL was not distributed with this file, You can obtain
~ one at https://opensource.org/licenses/EUPL-1.2.
~
~ The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
~ amended by the European Commission) shall be deemed incompatible for
~ the purposes of the Work and the provisions of the compatibility
~ clause in Article 5 of the EUPL shall not apply.
~
~ If using the Work as, or as part of, a network application, by
~ including the attribution notice(s) required under Article 5 of the EUPL
~ in the end user terms of the application under an appropriate heading,
~ such notice(s) shall fulfill the requirements of that article.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>pipeline.developer-examples</artifactId>
<groupId>com.51degrees</groupId>
<version>4.5.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>pipeline.developerexamples.fodid</artifactId>
<name>51Degrees :: Pipeline :: Developer Examples :: 51Did</name>
<url>https://51degrees.com?utm_source=maven&amp;utm_medium=package&amp;utm_campaign=pipeline-java&amp;utm_content=pipeline.developer-examples-pipeline.developer-examples.fodid-pom.xml&amp;utm_term=url</url>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
<artifactId>pipeline.did</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */

package pipeline.developerexamples.fodid;

import com.swancommunity.owid.Creator;
import com.swancommunity.owid.Crypto;
import com.swancommunity.owid.Owid;
import fiftyone.pipeline.did.FodId;

import java.time.Instant;
import java.util.Arrays;

/**
* Offline example for the 51Did ({@link FodId}) reader.
* <p>
* The 51Degrees Cloud service issues real 51Dids. To keep this example
* self-contained and offline, it builds a sample 51Did in process - generate
* an ECDSA P-256 key pair, sign a canonical 37-byte payload - then parses it
* back with {@link FodId} and prints the three payload fields.
* <p>
* 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. <b>Compare values, never envelopes.</b>
*/
public class Main {

private static final String DOMAIN = "51degrees.com";

public static class Example {

public void run() throws Exception {
// Generate a key pair and a signer entirely in process.
Crypto crypto = Crypto.generate();
Creator creator = Creator.create(DOMAIN, crypto);

byte[] payload = samplePayload();

// Issue a 51Did over the payload and parse it back.
FodId fodId = FodId.fromBase64(issue(creator, payload));

System.out.println("51Did parsed from base64:");
System.out.println(" Domain : " + fodId.getDomain());
System.out.println(" Type : " + fodId.getType());
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(" Verifies : "
+ fodId.verify(crypto.publicKeyPem()));

// Issue the SAME payload again: a separate envelope, same value.
FodId reissued = FodId.fromBase64(issue(creator, payload));
boolean sameEnvelope =
fodId.asBase64().equals(reissued.asBase64());
boolean sameValue =
Arrays.equals(fodId.getHash(), reissued.getHash());

System.out.println();
System.out.println("Same payload, re-issued:");
System.out.println(" Same envelope (base64) : " + sameEnvelope);
System.out.println(" Same value (Hash) : " + sameValue);

// The reader's whole purpose: the value is the stable, comparable
// part while the envelope is not.
if (sameEnvelope || !sameValue) {
throw new IllegalStateException(
"Expected a different envelope but the same value "
+ "across reissues.");
}
}

/** Issues (signs) a 51Did over the payload and returns it as base64. */
private String issue(Creator creator, byte[] payload)
throws Exception {
Owid owid = new Owid(DOMAIN, Instant.now(), payload);
creator.sign(owid);
return owid.asBase64();
}

/**
* A canonical 37-byte Probabilistic payload: flags 0x00, License Id
* 0x12345678 (little-endian) and a 32-byte value 0x20..0x3F.
*/
private byte[] samplePayload() {
byte[] payload = new byte[FodId.PAYLOAD_LENGTH];
payload[FodId.FLAGS_OFFSET] = 0x00;
payload[FodId.LICENSE_ID_OFFSET] = 0x78;
payload[FodId.LICENSE_ID_OFFSET + 1] = 0x56;
payload[FodId.LICENSE_ID_OFFSET + 2] = 0x34;
payload[FodId.LICENSE_ID_OFFSET + 3] = 0x12;
for (int i = 0; i < FodId.HASH_LENGTH; i++) {
payload[FodId.HASH_OFFSET + i] = (byte) (0x20 + i);
}
return payload;
}

private static String toHex(byte[] bytes) {
StringBuilder builder = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
builder.append(String.format("%02x", b & 0xFF));
}
return builder.toString();
}
}

public static void main(String[] args) throws Exception {
new Example().run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */

package pipeline.developerexamples.fodid;

import org.junit.jupiter.api.Test;

public class ExampleTests {

/**
* 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.
*/
@Test
public void FodId_Example_Test() throws Exception {
new Main.Example().run();
}
}
1 change: 1 addition & 0 deletions pipeline.developer-examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<module>pipeline.developer-examples.clientside-element</module>
<module>pipeline.developer-examples.clientside-element-mvc</module>
<module>pipeline.developer-examples.usage-sharing</module>
<module>pipeline.developer-examples.fodid</module>
</modules>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
Expand Down
104 changes: 104 additions & 0 deletions pipeline.did/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# pipeline.did

Strongly typed Java reader for the 51Did (51Degrees Identifier) returned by
the 51Degrees Cloud service. Mirrors the .NET `FiftyOne.Did` package.

## Terminology

A 51Did is described at three levels, and the wording is deliberate.

- The **51Did** (51Degrees Identifier) is the identifier as a whole.
- The **envelope** is the data model that carries it: a signed OWID holding
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
identifiers, or 16 GUID bytes for Random. Two 51Dids for the same inputs
share the same value even though their envelopes differ.

**Comparing two 51Dids means comparing their values, 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.

| 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) |

| Bits 7-6 | `IdType` | Value length | Minimum payload |
|---------:|-----------------|-------------:|----------------:|
| `00` | `PROBABILISTIC` | 32 | 37 |
| `01` | `RANDOM` | 16 | 21 |
| `10` | `HASHED_EMAIL` | 32 | 37 |
| `11` | `RESERVED` | remainder | 5 |

Identifiers issued before the type tag existed have bits 6-7 zeroed and decode
as `PROBABILISTIC`.

## OWID dependency

`FodId` builds on the OWID envelope library
([SWAN-community/owid-java](https://github.com/SWAN-community/owid-java),
package `com.swancommunity.owid`). Because that library's `Owid` type is
`final`, `FodId` **composes** an OWID (holds one and delegates OWID-level
concerns to it) rather than inheriting from it.

The OWID source is consumed from a git submodule of the 51Degrees fork at the
repository root (`owid-java/`, mirroring how `pipeline-dotnet` carries the
`owid-dotnet` submodule) and compiled into this module at its Java 8 level, so
there is no separate runtime dependency. The vendored OWID sources keep their
Apache-2.0 headers; the 51Did sources are EUPL-1.2.

### Bundled third-party licence

Because the OWID (`com.swancommunity.owid.*`) code is compiled into
`pipeline.did.jar`, the jar ships Apache-2.0 code alongside the EUPL-1.2 51Did
code. As required by Apache-2.0, the jar carries the full Apache licence text
and an attribution: see `META-INF/LICENSE-owid.txt` and `META-INF/NOTICE.txt`
(OWID is © 51 Degrees Mobile Experts Limited, from
[SWAN-community/owid-java](https://github.com/SWAN-community/owid-java),
Apache-2.0).

## Usage

```java
import fiftyone.pipeline.did.FodId;
import fiftyone.pipeline.did.IdType;

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

// Delegated OWID-level fields and operations.
String domain = fodId.getDomain();
boolean verified = fodId.verify(publicKeyPem);
String base64 = fodId.asBase64();
```

## Comparing two 51Dids

```java
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());
```

Use `getHash()` as the cache / dedup key.

## Non-goals

- **No signature verification on construction.** Constructing a `FodId` does
not check the signature. Call `verify(publicKeyPem)` when needed.
- **No creation of new 51Dids.** This is a parser; new 51Dids are issued by the
51Degrees cloud / on-premise hashing engines.
Loading
Loading