Skip to main content

Link credentials

In many cases, it is necessary to create relationships between different credentials. For example, you might link a diploma credential to a professional license, or a national ID to a work visa. The Truvity SDK allows you to link credentials together to form a hierarchy of related data.

Linking credentials provides several benefits:

  • Data integrity: Maintain the integrity of related credentials, ensuring they are connected in a meaningful way.
  • Hierarchical relationships: Create relationships between credentials, such as educational qualifications and work history, to form a complete identity profile.
  • Simplified verification: Verifiers can easily check related credentials in one verification request.

To link one credential to another, use the LinkedCredential custom claim type available as part of the user-defined types (UDTs):

@VcContext({ name: "Passport", namespace: "https://www.w3.org/ns/credentials/issuer-dependent" })
class Passport {
@VcNotEmptyClaim
name!: string;
}

@VcContext({ name: "ClaimsModelExample", namespace: "https://www.w3.org/ns/credentials/issuer-dependent" })
class ClaimsModelExample {
@VcNotEmptyClaim
passport!: LinkedCredential<Passport>;
}

const key = await client.keys.keyGenerate({
data: {
type: 'P256',
},
});

const passport = client.createVcDecorator(Passport);

const passportVc = await passport.issue(key.id, {
claims: {
name: "John Smith"
}
});

const claimsModelExample = client.createVcDecorator(ClaimsModelExample);

const claimsModelExampleVc = await claimsModelExample.issue(key.id, {
claims: {
passport: passportVc.toLinkedCredential()
}
});

const claimsModelExampleClaims = await claimsModelExampleVc.getClaims();
const linkedPassport = await claimsModelExampleClaims.passport.dereferenceAs(PassportNL);
const linkedPassportClaims = await linkedPassport.getClaims();

linkedPassportClaims.name; // "John Smith"

A single claim is able to support multiple different types of linked credentials (one at a time). This can be useful when more than one type of document can be used for the same purpose. For example, different certificates, like birth or marriage certificates, can often fulfill the same role or, as another example, passports of different nationalities.

There are two approaches to linking multiple credential types on a single claim:

  • Listing the different types, such as LinkedCredential<PassportNL | PassportDE>, like the example below. This approach would be used in cases where the set of different linked credential types is statically known.
  • Using the LinkedCredential<unknown> type. This approach would be used when you're handling an arbitrary set of types that is not statically known.

Both approaches provide the same programming interface. The first approach, listing the types, provides better static type checking.

@VcContext({ name: "PassportNL", namespace: "https://www.w3.org/ns/credentials/issuer-dependent" })
class PassportNL {
@VcNotEmptyClaim
nameNL!: string;
}

@VcContext({ name: "PassportDE", namespace: "https://www.w3.org/ns/credentials/issuer-dependent" })
class PassportDE {
@VcNotEmptyClaim
nameDE!: string;
}

@VcContext({ name: "ClaimsModelExample", namespace: "https://www.w3.org/ns/credentials/issuer-dependent" })
class ClaimsModelExample {
@VcNotEmptyClaim
passport!: LinkedCredential<PassportNL | PassportDE>;
}

const key = await client.keys.keyGenerate({
data: {
type: 'P256',
},
});

const passport = client.createVcDecorator(PassportNL);

const passportVc = await passport.issue(key.id, { claims: { nameNL: "John Smith" } });

const claimsModelExample = client.createVcDecorator(ClaimsModelExample);

const claimsModelExampleVc = await claimsModelExample.issue(key.id, {
claims: {
passport: passportVc.toLinkedCredential()
}
});

const claimsModelExampleClaims = await claimsModelExampleVc.getClaims();

const passportLinkedCredential = await claimsModelExampleClaims.passport.dereference();

if (await passportLinkedCredential.canMap(PassportNL)) {
const linkedPassport = await passportLinkedCredential.map(PassportNL);
const linkedPassportClaims = await passportVc.getClaims();
linkedPassportClaims.nameNL; // "John Smith"
} else {
const linkedPassport = await passportLinkedCredential.map(PassportDE);
const linkedPassportClaims = await passportVc.getClaims();
linkedPassportClaims.nameDE;
}

Further reading