Skip to main content

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:key
  • did:ebsi
  • did:web

Configuring a Wallet for a Trust Framework (TF)

Here are example Wallet configuration snippets for different trust environments:

{
"walletKeyIdentifier": "did",
"trustFramework": "NOOP"
}

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:

graph TD EBSI --> RTAO["RTAO (Root Trusted Accreditation Organisation)"] RTAO --> TAO["TAO (Trusted Accreditation Organisation)"] RTAO --> TI1["TI (Trusted Issuer)"] TAO --> TI2["TI (Trusted Issuer)"]

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 — issues VerifiableAuthorisationToOnboard credentials.
  • 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:

_, 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)
}

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:

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

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.

note

On the EBSI pilot network, the RTAO credential is provided as a signed JWT by EBSI support rather than through the conformance issuer flow.

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

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

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

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

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

Part D — TAO accepts the accreditation credential and calls TfAccreditAs

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

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

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

Part D — TI accepts the accreditation credential and calls TfAccreditAs

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

Creating a CSR and Importing an X509 Certificate

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

Issuing an X509 Certificate

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