03 Trust Infrastructure Onboarding
Triveria Wallet currently supports multiple trust environments.
A Wallet's key pair can be identified either by a did or an x509 certificate chain.
Triveria wallet currently supports the following DID methods:
did:keydid:ebsidid:web
Configuring a Wallet for a Trust Framework (TF)
Here are example Wallet configuration snippets for different trust environments:
- No TF, DID
- No TF, X509 Certificates
- ETSI TL, X509 Certificates
- EBSI, DID
{
"walletKeyIdentifier": "did",
"trustFramework": "NOOP"
}
{
"walletKeyIdentifier": "x509",
"trustFramework": "NOOP"
}
{
"walletKeyIdentifier": "x509",
"EUDI": {
"tslUrl": "https://ewc-consortium.github.io/ewc-trust-list/EWC-TL"
},
"trustFramework": "EUDI"
}
{
"EBSI": {
"configuration": {
"conformanceClientId": "https://api-conformance.ebsi.eu/conformance/v3/issuer-mock",
"authorization": "https://api-conformance.ebsi.eu/authorisation/v4",
"didRegistry": "https://api-conformance.ebsi.eu/did-registry/v5",
"trustedIssuersRegistry": "https://api-conformance.ebsi.eu/trusted-issuers-registry/v5",
"trustedSchemasRegistry": "https://api-conformance.ebsi.eu/trusted-schemas-registry/v3",
"mainSchema": "https://api-pilot.ebsi.eu/trusted-schemas-registry/v2/schemas/z3MgUFUkb722uq4x3dv5yAJmnNmzDFeK5UC8x83QoeLJM"
}
},
"trustFramework": "EBSI",
"walletKeyIdentifier": "did"
}
Note: Before using a wallet configured either with a non-NOOP Trust Framework for Issuing Credentials, you need to onboard your wallet to it. When using X509 certificates, you need to firstly import them
Onboarding to EBSI
For interactive onboarding via the Triveria Web UI, refer to the EBSI Vector User Guide.
The sections below document the full trust chain setup via the API. The EBSI trust chain follows this hierarchy:
Every wallet must be onboarded (its DID registered in the EBSI DID Registry) before it can be accredited (granted the right to act as RTAO, TAO, or TI). These are two separate steps.
Reserved issuer IDs used throughout this guide:
ebsi_issuer_authorization_to_onboard— issuesVerifiableAuthorisationToOnboardcredentials.ebsi_issuer_accreditation_to_attest— issues accreditation credentials.
Step 1: Onboarding the Wallet DID to EBSI
All wallets — RTAO, TAO, and TI — go through the same DID onboarding step. The difference is where the VerifiableAuthorisationToOnboard credential comes from:
- RTAO wallets obtain it directly from the EBSI conformance issuer.
- TAO and TI wallets receive it from their parent RTAO or TAO wallet (see Step 3 and Step 4).
For RTAO, request the onboarding credential from the EBSI conformance issuer, then onboard:
- Go
- TypeScript
_, err := client.CredentialRequestInitWithResponse(ctx,
&wallet.CredentialRequestInitParams{WalletId: rtaoWalletId},
wallet.CredentialRequest{
Types: "VerifiableCredential,VerifiableAttestation,VerifiableAuthorisationToOnboard",
Format: "jwt_vc_vcdm",
Url: "https://api-conformance.ebsi.eu/conformance/v3/issuer-mock",
})
if err != nil {
log.Panicf("failed to request onboarding credential: %v", err)
}
// Onboard the wallet DID to EBSI. This call may take ~30 seconds.
onboardResp, err := client.TfOnboardWithResponse(ctx,
&wallet.TfOnboardParams{WalletId: rtaoWalletId}, wallet.OnboardingRequest{})
if err != nil {
log.Panicf("failed to onboard: %v", err)
}
if onboardResp.StatusCode() != 200 {
log.Panicf("error onboarding: %d %s", onboardResp.StatusCode(), onboardResp.Body)
}
await client.credentialRequestInit(rtaoWalletId, {
types: 'VerifiableCredential,VerifiableAttestation,VerifiableAuthorisationToOnboard',
format: 'jwt_vc_vcdm',
url: 'https://api-conformance.ebsi.eu/conformance/v3/issuer-mock',
})
// Onboard the wallet DID to EBSI. This call may take ~30 seconds.
await client.tfOnboard(rtaoWalletId)
For TAO and TI wallets, after receiving the onboarding credential offer from the parent (see steps 3 and 4), the accept and onboard flow is:
- Go
- TypeScript
// offerUrl and pin are provided by the parent RTAO/TAO wallet.
authReqResp, err := client.HolderOfferPassAuthInfoWithResponse(ctx,
&wallet.HolderOfferPassAuthInfoParams{WalletId: walletId},
wallet.VcOffer{Url: offerUrl})
if err != nil {
log.Panicf("failed to pass auth info: %v", err)
}
interactionId := authReqResp.JSON200.InteractionId
_, err = client.HolderOfferProcessAfterConsentWithResponse(ctx,
&wallet.HolderOfferProcessAfterConsentParams{WalletId: walletId},
interactionId,
wallet.InteractionAuthorizationConsent{Pin: &pin})
if err != nil {
log.Panicf("failed to process offer: %v", err)
}
// Onboard the wallet DID to EBSI. This call may take ~30 seconds.
onboardResp, err := client.TfOnboardWithResponse(ctx,
&wallet.TfOnboardParams{WalletId: walletId}, wallet.OnboardingRequest{})
if err != nil {
log.Panicf("failed to onboard: %v", err)
}
if onboardResp.StatusCode() != 200 {
log.Panicf("error onboarding: %d %s", onboardResp.StatusCode(), onboardResp.Body)
}
// offerUrl and pin are provided by the parent RTAO/TAO wallet.
const { data: { interactionId } } = await client.holderOfferPassAuthInfo(
walletId, { Url: offerUrl })
await client.holderOfferProcessAfterConsent(walletId, interactionId, { pin })
// Onboard the wallet DID to EBSI. This call may take ~30 seconds.
await client.tfOnboard(walletId)
Step 2: Onboarding as RTAO
After the wallet DID is onboarded, request the VerifiableAuthorisationForTrustChain credential from the EBSI conformance issuer. This credential is issued as a deferred credential — it becomes available approximately 10 seconds after the request. Poll the DeferredStatus endpoint until it is complete, then call TfAccreditAs.
On the EBSI pilot network, the RTAO credential is provided as a signed JWT by EBSI support rather than through the conformance issuer flow.
- Go
- TypeScript
credReqResp, err := client.CredentialRequestInitWithResponse(ctx,
&wallet.CredentialRequestInitParams{WalletId: rtaoWalletId},
wallet.CredentialRequest{
Types: "VerifiableCredential,VerifiableAttestation,VerifiableAuthorisationForTrustChain",
Format: "jwt_vc_vcdm",
Url: "https://api-conformance.ebsi.eu/conformance/v3/issuer-mock",
})
if err != nil {
log.Panicf("failed to request RTAO credential: %v", err)
}
// The credential is deferred — poll until it is ready.
deferredId := credReqResp.JSON200[0]
for {
deferredResp, err := client.DeferredStatusWithResponse(ctx, deferredId,
&wallet.DeferredStatusParams{WalletId: rtaoWalletId})
if err != nil {
log.Panicf("deferred status error: %v", err)
}
if deferredResp.JSON200.Status == wallet.Completed {
break
}
time.Sleep(3 * time.Second)
}
// Accredit the wallet as RTAO.
accreditResp, err := client.TfAccreditAsWithResponse(ctx,
&wallet.TfAccreditAsParams{WalletId: rtaoWalletId},
wallet.AccreditationRequest{Type: wallet.RootTrustedAccreditationOrganisation})
if err != nil {
log.Panicf("failed to accredit as RTAO: %v", err)
}
if accreditResp.StatusCode() != 200 {
log.Panicf("error accrediting as RTAO: %d %s", accreditResp.StatusCode(), accreditResp.Body)
}
const [deferredId] = (await client.credentialRequestInit(rtaoWalletId, {
types: 'VerifiableCredential,VerifiableAttestation,VerifiableAuthorisationForTrustChain',
format: 'jwt_vc_vcdm',
url: 'https://api-conformance.ebsi.eu/conformance/v3/issuer-mock',
})).data
// The credential is deferred — poll until it is ready.
while (true) {
const { data: deferred } = await client.deferredStatus(deferredId, rtaoWalletId)
if (deferred.Status === 'Completed') break
await new Promise(resolve => setTimeout(resolve, 3000))
}
// Accredit the wallet as RTAO.
await client.tfAccreditAs(rtaoWalletId, { type: 'RootTrustedAccreditationOrganisation' })
Step 3: Onboarding as TAO
A TAO is onboarded and accredited by an RTAO. The process has two parts: the RTAO issues two credentials to the TAO (an onboarding credential and an accreditation credential), and the TAO accepts each and calls the corresponding endpoint.
Part A — RTAO issues the onboarding credential
- Go
- TypeScript
taoDid := "did:ebsi:<tao-wallet-did>"
// Register the onboarding request for the TAO wallet.
_, err := client.TfOnboardRequestWithResponse(ctx,
&wallet.TfOnboardRequestParams{WalletId: rtaoWalletId},
wallet.EntityOnboardingRequest{
Did: taoDid,
ValidUntil: "2026-12-31T00:00:00Z",
})
if err != nil {
log.Panicf("failed to register onboarding request: %v", err)
}
// Create a pre-authorized offer for the TAO wallet to claim the onboarding credential.
offerResp, err := client.IssuerInitiatePreauthOfferWithResponse(ctx,
&wallet.IssuerInitiatePreauthOfferParams{WalletId: rtaoWalletId},
wallet.InitPreAuthOffer{
ClientId: &taoDid,
IssuerId: "ebsi_issuer_authorization_to_onboard",
})
if err != nil {
log.Panicf("failed to create onboarding offer: %v", err)
}
onboardingOfferUrl := offerResp.JSON200.Offer
onboardingPin := offerResp.JSON200.Pin
const taoDid = 'did:ebsi:<tao-wallet-did>'
// Register the onboarding request for the TAO wallet.
await client.tfOnboardRequest(rtaoWalletId, {
did: taoDid,
validUntil: '2026-12-31T00:00:00Z',
})
// Create a pre-authorized offer for the TAO wallet to claim the onboarding credential.
const { data: { offer: onboardingOfferUrl, pin: onboardingPin } } =
await client.issuerInitiatePreauthOffer(rtaoWalletId, {
clientId: taoDid,
issuerId: 'ebsi_issuer_authorization_to_onboard',
})
Part B — TAO accepts and calls TfOnboard
The TAO wallet accepts the onboarding credential offer, then calls TfOnboard as shown in Step 1.
Part C — RTAO issues the accreditation credential
- Go
- TypeScript
accreditedFor := base64.StdEncoding.EncodeToString([]byte("[]"))
// Register the accreditation request for the TAO wallet.
_, err := client.TfAccreditRequestWithResponse(ctx,
&wallet.TfAccreditRequestParams{WalletId: rtaoWalletId},
wallet.EntityAccreditationRequest{
Did: taoDid,
Type: wallet.TrustedAccreditationOrganisation,
AccreditedFor: accreditedFor,
ValidUntil: "2026-12-31T00:00:00Z",
})
if err != nil {
log.Panicf("failed to register accreditation request: %v", err)
}
// Issue the accreditation credential as a pre-authorized offer.
offerResp, err := client.IssuerInitiatePreauthOfferWithResponse(ctx,
&wallet.IssuerInitiatePreauthOfferParams{WalletId: rtaoWalletId},
wallet.InitPreAuthOffer{
ClientId: &taoDid,
IssuerId: "ebsi_issuer_accreditation_to_attest",
})
if err != nil {
log.Panicf("failed to create accreditation offer: %v", err)
}
accreditationOfferUrl := offerResp.JSON200.Offer
accreditationPin := offerResp.JSON200.Pin
const accreditedFor = btoa('[]')
// Register the accreditation request for the TAO wallet.
await client.tfAccreditRequest(rtaoWalletId, {
did: taoDid,
type: 'TrustedAccreditationOrganisation',
accreditedFor,
validUntil: '2026-12-31T00:00:00Z',
})
// Issue the accreditation credential as a pre-authorized offer.
const { data: { offer: accreditationOfferUrl, pin: accreditationPin } } =
await client.issuerInitiatePreauthOffer(rtaoWalletId, {
clientId: taoDid,
issuerId: 'ebsi_issuer_accreditation_to_attest',
})
Part D — TAO accepts the accreditation credential and calls TfAccreditAs
- Go
- TypeScript
authReqResp, err := client.HolderOfferPassAuthInfoWithResponse(ctx,
&wallet.HolderOfferPassAuthInfoParams{WalletId: taoWalletId},
wallet.VcOffer{Url: accreditationOfferUrl})
if err != nil {
log.Panicf("failed to pass auth info: %v", err)
}
interactionId := authReqResp.JSON200.InteractionId
_, err = client.HolderOfferProcessAfterConsentWithResponse(ctx,
&wallet.HolderOfferProcessAfterConsentParams{WalletId: taoWalletId},
interactionId,
wallet.InteractionAuthorizationConsent{Pin: &accreditationPin})
if err != nil {
log.Panicf("failed to process offer: %v", err)
}
// Accredit the wallet as TAO.
accreditResp, err := client.TfAccreditAsWithResponse(ctx,
&wallet.TfAccreditAsParams{WalletId: taoWalletId},
wallet.AccreditationRequest{Type: wallet.TrustedAccreditationOrganisation})
if err != nil {
log.Panicf("failed to accredit as TAO: %v", err)
}
if accreditResp.StatusCode() != 200 {
log.Panicf("error accrediting as TAO: %d %s", accreditResp.StatusCode(), accreditResp.Body)
}
const { data: { interactionId } } = await client.holderOfferPassAuthInfo(
taoWalletId, { Url: accreditationOfferUrl })
await client.holderOfferProcessAfterConsent(taoWalletId, interactionId, { pin: accreditationPin })
// Accredit the wallet as TAO.
await client.tfAccreditAs(taoWalletId, { type: 'TrustedAccreditationOrganisation' })
Step 4: Onboarding as TI
A TI is onboarded and accredited by an RTAO or TAO. The process is identical to Step 3 — the parent issues an onboarding credential followed by an accreditation credential. The only difference is the accreditation type used in TfAccreditRequest and TfAccreditAs.
Part A — RTAO/TAO issues the onboarding credential
Identical to Step 3 Part A, using the TI wallet's DID as clientId.
Part B — TI accepts and calls TfOnboard
Identical to Step 1 (TAO/TI variant).
Part C — RTAO/TAO issues the accreditation credential
- Go
- TypeScript
tiDid := "did:ebsi:<ti-wallet-did>"
accreditedFor := base64.StdEncoding.EncodeToString([]byte("[]"))
// Register the accreditation request for the TI wallet.
_, err := client.TfAccreditRequestWithResponse(ctx,
&wallet.TfAccreditRequestParams{WalletId: accreditorWalletId},
wallet.EntityAccreditationRequest{
Did: tiDid,
Type: wallet.TrustedIssuer,
AccreditedFor: accreditedFor,
ValidUntil: "2026-12-31T00:00:00Z",
})
if err != nil {
log.Panicf("failed to register accreditation request: %v", err)
}
// Issue the accreditation credential as a pre-authorized offer.
offerResp, err := client.IssuerInitiatePreauthOfferWithResponse(ctx,
&wallet.IssuerInitiatePreauthOfferParams{WalletId: accreditorWalletId},
wallet.InitPreAuthOffer{
ClientId: &tiDid,
IssuerId: "ebsi_issuer_accreditation_to_attest",
})
if err != nil {
log.Panicf("failed to create accreditation offer: %v", err)
}
accreditationOfferUrl := offerResp.JSON200.Offer
accreditationPin := offerResp.JSON200.Pin
const tiDid = 'did:ebsi:<ti-wallet-did>'
const accreditedFor = btoa('[]')
// Register the accreditation request for the TI wallet.
await client.tfAccreditRequest(accreditorWalletId, {
did: tiDid,
type: 'TrustedIssuer',
accreditedFor,
validUntil: '2026-12-31T00:00:00Z',
})
// Issue the accreditation credential as a pre-authorized offer.
const { data: { offer: accreditationOfferUrl, pin: accreditationPin } } =
await client.issuerInitiatePreauthOffer(accreditorWalletId, {
clientId: tiDid,
issuerId: 'ebsi_issuer_accreditation_to_attest',
})
Part D — TI accepts the accreditation credential and calls TfAccreditAs
- Go
- TypeScript
authReqResp, err := client.HolderOfferPassAuthInfoWithResponse(ctx,
&wallet.HolderOfferPassAuthInfoParams{WalletId: tiWalletId},
wallet.VcOffer{Url: accreditationOfferUrl})
if err != nil {
log.Panicf("failed to pass auth info: %v", err)
}
interactionId := authReqResp.JSON200.InteractionId
_, err = client.HolderOfferProcessAfterConsentWithResponse(ctx,
&wallet.HolderOfferProcessAfterConsentParams{WalletId: tiWalletId},
interactionId,
wallet.InteractionAuthorizationConsent{Pin: &accreditationPin})
if err != nil {
log.Panicf("failed to process offer: %v", err)
}
// Accredit the wallet as TI.
accreditResp, err := client.TfAccreditAsWithResponse(ctx,
&wallet.TfAccreditAsParams{WalletId: tiWalletId},
wallet.AccreditationRequest{Type: wallet.TrustedIssuer})
if err != nil {
log.Panicf("failed to accredit as TI: %v", err)
}
if accreditResp.StatusCode() != 200 {
log.Panicf("error accrediting as TI: %d %s", accreditResp.StatusCode(), accreditResp.Body)
}
const { data: { interactionId } } = await client.holderOfferPassAuthInfo(
tiWalletId, { Url: accreditationOfferUrl })
await client.holderOfferProcessAfterConsent(tiWalletId, interactionId, { pin: accreditationPin })
// Accredit the wallet as TI.
await client.tfAccreditAs(tiWalletId, { type: 'TrustedIssuer' })
Creating a CSR and Importing an X509 Certificate
- Go
- TypeScript
csrResp, err := client.WalletX509CSRCreateWithResponse(ctx, walletId, csrCreateRequest)
if err != nil {
log.Panicf("failed to create a csr: %v", err)
}
if csrResp.StatusCode() != 200 {
log.Panicf("error while creating csr: %d %s", csrResp.StatusCode(), csrResp.Body)
}
csrPem := csrResp.JSON200.Csr
// CSR is provided to Certification Authority & an X509 certificate is issued
x509Cert := "<CERTIFICATE IN PEM FORMAT>"
certImportReq := wallet.CertificateImportRequest{
Certificate: x509Cert,
}
importResp, err := client.WalletX509CertificateImportWithResponse(ctx, walletId, certImportReq)
if err != nil {
log.Panicf("failed to import cert: %v", err)
}
if importResp.StatusCode() != 200 {
log.Panicf("error while importing cert: %d %s", importResp.StatusCode(), importResp.Body)
}
const { data: { csr: csrPem } } = await client.walletX509CSRCreate(walletId, csrCreateRequest)
// CSR is provided to Certification Authority & an X509 certificate is issued
const x509Cert = '<CERTIFICATE IN PEM FORMAT>'
await client.walletX509CertificateImport(walletId, { certificate: x509Cert })
Issuing an X509 Certificate
- Go
- TypeScript
ca := false
certIssueResp, err := client.TfX509CertificateIssueWithResponse(ctx, walletId, wallet.CertificateIssueRequest{
Ca: &ca,
Csr: csrPem,
ExpirationDate: time.Now().AddDate(3, 0, 0),
})
if err != nil {
log.Panicf("failed to issue certificate: %v", err)
}
if certIssueResp.StatusCode() != 200 {
log.Panicf("error while issuing certificate: %d %s", certIssueResp.StatusCode(), certIssueResp.Body)
}
certPem := certIssueResp.JSON200.Certificate
const expirationDate = new Date()
expirationDate.setFullYear(expirationDate.getFullYear() + 3)
const { data: { certificate: certPem } } = await client.tfX509CertificateIssue(walletId, {
ca: false,
csr: csrPem,
expirationDate: expirationDate.toISOString(),
})