Skip to content

Upgrade: bifold 3.0.16 / credo-ts 0.6.3 / React 19 + DTG VC 2.0 + eddsa-rdfc-2022 Data Integrity & witnessed exchange#31

Open
albertoleon7794 wants to merge 2259 commits into
mainfrom
upgrade/consolidated-vc2-di
Open

Upgrade: bifold 3.0.16 / credo-ts 0.6.3 / React 19 + DTG VC 2.0 + eddsa-rdfc-2022 Data Integrity & witnessed exchange#31
albertoleon7794 wants to merge 2259 commits into
mainfrom
upgrade/consolidated-vc2-di

Conversation

@albertoleon7794

@albertoleon7794 albertoleon7794 commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Consolidates the bifold-side upgrade + Data Integrity work for review.
Supersedes #25 and #26 (closed in favor of this).

Companion app PR: berkmancenter/keyring-wallet#17

Note: this branch is a fork-swap onto bifold-3.x — most of the commit count is
upstream fork history; the reviewable delta is the VRC/witness/DI + attestation code.

github-actions Bot and others added 30 commits June 26, 2025 08:52
Signed-off-by: "github-actions[bot]" <"41898282+github-actions[bot]@users.noreply.github.com">
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Travis Lee <travislee2k15@gmail.com>
Signed-off-by: "github-actions[bot]" <"41898282+github-actions[bot]@users.noreply.github.com">
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: ehanoc <ehanoc@protonmail.com>
Signed-off-by: Christian Lippa <christian.lippa@ontario.ca>
Co-authored-by: Mostafa Gamal <46829557+MosCD3@users.noreply.github.com>
Signed-off-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Co-authored-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Co-authored-by: Al Rosenthal <alfred.k.rosenthal@gmail.com>
Signed-off-by: Bryce McMath <bryce.j.mcmath@gmail.com>
Signed-off-by: Bryce McMath <32586431+bryce-mcmath@users.noreply.github.com>
Signed-off-by: "github-actions[bot]" <"41898282+github-actions[bot]@users.noreply.github.com">
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: ehanoc <ehanoc@protonmail.com>
Signed-off-by: Travis Lee <travislee2k15@gmail.com>
Signed-off-by: traviskhl <travislee2k15@gmail.com>
Co-authored-by: Al Rosenthal <alfred.k.rosenthal@gmail.com>
Signed-off-by: Bryce McMath <bryce.j.mcmath@gmail.com>
…2337 (#1581)

Signed-off-by: Mac Deluca <Mac.Deluca@quartech.com>
… (#1583)

Signed-off-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Co-authored-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Signed-off-by: traviskhl <travislee2k15@gmail.com>
Co-authored-by: Al Rosenthal <alfred.k.rosenthal@gmail.com>
Signed-off-by: "github-actions[bot]" <"41898282+github-actions[bot]@users.noreply.github.com">
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Mac Deluca <Mac.Deluca@quartech.com>
Signed-off-by: traviskhl <travislee2k15@gmail.com>
Signed-off-by: Bryce McMath <bryce.j.mcmath@gmail.com>
Signed-off-by: "github-actions[bot]" <"41898282+github-actions[bot]@users.noreply.github.com">
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Co-authored-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Signed-off-by: Mac Deluca <Mac.Deluca@quartech.com>
Signed-off-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Co-authored-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Signed-off-by: Mac Deluca <Mac.Deluca@quartech.com>
Signed-off-by: Mac Deluca <Mac.Deluca@quartech.com>
Signed-off-by: "github-actions[bot]" <"41898282+github-actions[bot]@users.noreply.github.com">
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: traviskhl <travislee2k15@gmail.com>
Signed-off-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
Co-authored-by: Matthew Golshani <matthew.golshani@gov.bc.ca>
…ue #2587) (#1602)

Signed-off-by: Mac Deluca <Mac.Deluca@quartech.com>
Signed-off-by: "github-actions[bot]" <"41898282+github-actions[bot]@users.noreply.github.com">
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Bryce McMath <bryce.j.mcmath@gmail.com>
albertoleon7794 and others added 22 commits July 4, 2026 23:14
…ream v3.0.16

Cherry-picked the full Keyring delta (v2.7.4..keyring HEAD) onto the
upstream bifold-wallet v3.0.16 tree. Conflicts resolved by unioning
Keyring features (VRC module, witness/rCard state, chat redesign,
hardware-signing attestation) with upstream 3.x structure (credo 0.6
API, React 19 refs, ScreenWrapper layouts).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
- Rewire agent construction: connections/credentials/proofs/mediation
  config now lives under DidCommModule; transports register via
  agent.modules.didcomm
- Adopt renamed DIDComm types and events (DidCommConnectionRecord,
  DidCommCredentialExchangeRecord, DidCommProofState, etc.)
- Replace wallet.createKey with kms.importKey / dids.create createKey
  option (JWK types); deterministic witness DID derives Ed25519 public
  key via node:crypto
- Switch askar to @openwallet-foundation/askar-* store config
- W3cCredentialRecord: use .encoded instead of private .credential
- witness-server + vrc-reference: full source and test migration,
  jest ESM transforms for credo 0.6 (.mjs) dependencies
- Fix lint debt surfaced by upstream flat eslint config; update
  React 19 snapshots

All workspaces: build, lint, prettier, and tests green
(155 suites / 1398 tests).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Ties the fork history to the upstream openwallet-foundation/bifold-wallet
v3.0.16 base so the Phase 3 branch can be reviewed and merged as a PR.
The tree is taken entirely from the Phase 3 branch (upstream 3.0.16 +
Keyring delta + credo-ts 0.6.3 migration).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
The absolute 'components/views/Banner' specifier only resolves with core's
own baseUrl and breaks consumers that compile @bifold/core from source
(e.g. the Keyring app workspace).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
With the New Architecture, codegen derives an abstract base class from the
TS spec on both platforms, so generateKey/sha256/appleKeyAttestation/
getAppStoreReceipt/attestHardwareSigningKey must exist on Android too.
They reject with UNSUPPORTED. Old-arch spec mirrors them so both source
sets stay interchangeable. Also refresh the android gradle lockfile for
RN 0.81 / compileSdk 36 hosts.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
The merge onto upstream v3.0.16 reverted the customized Onboarding screen
to the plain upstream pager. Restore the card-style carousel with arrow
overlays, the onComplete prop, and the Get Started button, and wire them
back up in OnboardingStack. Also replace the stale react-native-argon2
jest mock with an Argon2 mock on askar-react-native (crypto.ts migrated).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Co-authored-by: Cursor <cursoragent@cursor.com>
- vrc-manager + InAppMessageNotifier: subscribe with
  DidCommBasicMessageEventTypes.DidCommBasicMessageStateChanged (credo 0.6
  renamed the event; the old 0.5 string silently never fired, stalling VRC
  issuance after connection)
- chat: swap RN TouchableOpacity for react-native-gesture-handler touchables
  in ChatMessage (YES/NO offer actions) and chat-messages details links —
  RN touchables miss taps inside GiftedChat's inverted list on Android with
  the New Architecture
- react-hooks: key useCredentialByState/useProofByState (and NotInState
  variants) memo on state contents instead of array identity — callers pass
  fresh array literals each render, producing new filtered arrays every
  render and an infinite re-render loop under React 19 ("Maximum update
  depth exceeded") that starved the credential exchange
- vrc-manager: backdate issuanceDate/validFrom by 5 min to tolerate clock
  skew between devices (holder rejects VCs issued "in the future")
- Splash: log full agent-init error chain (cause + stack)

Signed-off-by: Alberto L <aleon@law.harvard.edu>
…ormat

R-Card template records stored by credo-0.5 builds have no `proof`, but
credo 0.6 parses stored credentials as W3cJsonLdVerifiableCredential whose
class validation requires one — `record.getTags()` threw and crashed the
OpenID credential provider on first launch after an app upgrade over an
old askar store.

Add migrateRCardTemplateProofs(): a one-time fixup that adds the same
placeholder proof new templates get to any legacy RCardTemplate record
missing one. Verified end-to-end by the askar 0.2->0.6 store-migration
E2E (old release apk populated with a real VRC, `adb install -r` upgrade,
wallet + contact + credential survive).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
…tests)

react-native-gesture-handler's bundled jest mocks render buttons without
children (RawButton discards them), so labels inside gesture-handler
touchables disappear under test and the ChatMessage credential-offer
YES/NO suite could not find its buttons. Map the touchables to their
react-native equivalents in jestSetup so children render normally.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Align the VRC module with the ToIP DTG credential spec and move issuance
to the W3C VC Data Model 2.0, with negotiated backward compatibility.

RCard/VRC separation (spec hygiene):
- VRC issuer is a bare DID string for v2 peers; contact info moves into a
  separate exchanged RelationshipCard credential (subject = counterparty,
  credentialSubject.card = jCard) issued right after the VRC, auto-accepted
  by the receiver and hidden from chat/notifications/wallet list
- new RCard JSON-LD context with @type: @JSON so the jCard survives RDF
  canonicalization as a JCS JSON literal
- contact display resolves from the newest received RCard, falling back to
  legacy VRC issuer objects (rcardDisplayUtils)

VWC digest:
- witness digest now uses JCS (RFC 8785) canonicalization instead of a
  top-level-only key sort (jcsCanonicalize in @bifold/vrc-contexts)

VC 2.0 verification (credo yarn patch):
- patch @credo-ts/core 0.6.3: accept https://www.w3.org/ns/credentials/v2
  as first @context and make issuanceDate optional (2.0 uses validFrom)
- bundle the VCDM 2.0 context document and pin credo's standard security
  contexts into the shared document loader for offline sign/verify
- upstream issue filed: openwallet-foundation/credo-ts#2864

VC 2.0 issuance flip with RCE v2 negotiation:
- handshake announces vrc:rceVersion:2; peer version stored on
  RelationshipDidRecord (absent = v1)
- v2 peers get VCDM 2.0 VRC/RCard/VWC (v2 context, validFrom/validUntil);
  the Ed25519Signature2018 suite context is included at build time because
  the v2 base context doesn't define the proof terms and jsonld-signatures
  would otherwise mutate @context during signing, failing credo's
  holder-side offer/credential equality check
- v1 peers keep the full legacy exchange: VCDM 1.1 VRC with the embedded
  issuer object (name/email/org) and no RCard offer
- witness-server mirrors the observed VRC's data model for the VWC and
  widens the freshness window to cover the wallets' clock-skew backdating

Gates: core jest 159 suites / 1434 tests green; conformance sign/verify
round-trips for v1.1 and v2; two-device E2E (new/new) and backward-compat
E2E against a Phase-4 baseline build both green.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Embed all three Google attestation roots, validate chains against them
only (no self-as-anchor), and add Android/TS regression tests.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Avoid RN Modal presentation failures on iPhone (camera disclosure as view,
dismiss QR sheet before Scan nav). Make Settings Secure Exchanges row
actionable under VoiceOver/Appium. Log slim IssuedCredentialJSON for E2E.
Keep Ed25519Signature2018 intentionally (DI deferred — see keyring docs).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Registers a W3C VC Data Integrity (eddsa-rdfc-2022) signature suite in
Credo's SignatureSuiteRegistry from plain module code — SignatureSuiteToken
is a multi-injection token, so no @credo-ts/core patch is needed for
agent.w3cCredentials sign/verify. The suite adapter bridges Credo's
KMS-backed KmsKeyPair.signer() to @digitalcredentials/data-integrity's
signer contract (id + algorithm glue); the verify path needs no key and
translates Ed25519VerificationKey2018/2020 methods to Multikey inside the
cryptosuite verifier.

Also ships a dev-only on-device self-test (sign + verify + tamper-reject a
VCDM 2.0 credential from a did:peer:0 issuer) to prove expo-crypto SHA-256,
askar KMS signing, and RDFC canonicalization on Hermes.

DIDComm issuance stays on Ed25519Signature2018 until the format-service
work lands. Ported from the validated Level 0/1 spikes; plan and decision
record in keyring-wallet docs/CRYPTO_SUITE_FOLLOWUP.md.

- rdf-canonize resolved to ^5.0.0 (single canonicalizer copy)
- jest: transform base58-universal/base64url-universal (ESM-only deps)

Signed-off-by: Alberto L <aleon@law.harvard.edu>
…ching

Adds the effort's first didcomm patch (Decision 5, Option B):

- DidCommJsonLdCredentialDetailOptions gains an optional `cryptosuite`
  field so offers/requests can pin the concrete DI algorithm alongside
  proofType: 'DataIntegrityProof'.
- verifyReceivedCredentialMatchesRequest enforces the pinned cryptosuite
  against the received proof; when the option is absent (the
  Ed25519Signature2018 path) matching is unchanged type-only logic.
- Type declarations extended so offerCredential calls type-check.

The sign path needs no changes: acceptRequest resolves the suite by
proofType through the SignatureSuiteRegistry, which maps
DataIntegrityProof to the eddsa-rdfc-2022 suite module.

Proven by a two-agent offer→request→issue exchange over HTTP DIDComm
(spike in keyring-wallet docs/spikes/di-level3-didcomm; plan in
docs/CRYPTO_SUITE_FOLLOWUP.md).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Bumps RCE_PROTOCOL_VERSION to 3 (Data Integrity capable; implies VC 2.0)
and gates the two peer-to-peer offer sites on the counterparty's announced
version (Decision 6, docs/CRYPTO_SUITE_FOLLOWUP.md):

- counterpartySpeaksDi() mirrors counterpartySpeaksVc20() (>= 3)
- getVrcJsonLdProofOptions() selects DataIntegrityProof/eddsa-rdfc-2022
  for v3 peers and Ed25519Signature2018 otherwise (fail-safe to 2018);
  used by both the VRC and RCard offers
- buildVrcCredential/buildRCardCredential drop the Ed25519 suite context
  on the DI path — credentials/v2 already defines the DataIntegrityProof
  terms so nothing is appended during signing (Level 0 spike check 4)
- v1/v2 peers receive byte-identical shapes and proofs as before
- witnessed-exchange signatures stay 2018 intentionally until the
  witness-server dual-verifies DI (comments updated to say so)

Verification remains dual-stack with no downgrade rejection (Decision 7).
New diIssuanceFlip tests cover the gate, fail-safe, and context shapes.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
The witnessed-exchange VP submission had two latent failures introduced
with the VC 2.0/credo 0.6 work, both crashing createAndSubmitVP before
any presentation reached the witness:

1. The VP wrapper was pinned to the credentials/v1 context while wrapping
   a VCDM 2.0 VRC — JSON-LD expansion fails at signing time with "tried
   to redefine a protected term" (v1 and v2 both @Protect the core terms
   with different definitions). The VP context now follows the wrapped
   credential's data model.
2. proofPurpose was passed as the string 'authentication', but credo
   hands it straight to jsonld-signatures, which requires a ProofPurpose
   instance — "purpose.update is not a function". Omitting it lets the
   vc layer build an AuthenticationProofPurpose from challenge + domain.
   (credo 0.6 TYPES the field as a required string, hence the cast.)

Regression coverage lives in vrc-reference diConformance.test.ts
(2018-signed v2 VP wrapping a v2 VRC, challenge-bound, verifies).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Adds the canonical Node-side eddsa-rdfc-2022 suite to @bifold/vrc-shared
(same adapter as @bifold/core's RN copy — keep in sync) and registers it
in the witness-server and vrc-reference agents, so both now verify
DataIntegrityProof/eddsa-rdfc-2022 credentials alongside
Ed25519Signature2018 (docs/CRYPTO_SUITE_FOLLOWUP.md §10 layer 7).
Witness ISSUANCE stays 2018 until the peer matrix is proven.

diConformance integration tests cover: DI sign (no context mutation),
verify + tamper rejection, 2018 dual-stack on the same agent, the mixed
witness Identity Check case (2018-signed challenge-bound v2 VP wrapping a
DI-signed VRC), and the fixed witnessed-flow 2018 shape as regression.

- vrc-reference jest: transform base58-universal/base64url-universal

Signed-off-by: Alberto L <aleon@law.harvard.edu>
…v2 definition

The witnessed-exchange context mapped `name` to http://schema.org/name
while the credentials/v2 base context @Protects `name` as
"https://schema.org/name". Redefining a protected term is only legal when
the definitions are byte-identical; the 2018 signing path ran JSON-LD in
non-safe mode and silently tolerated the mismatch, but Data Integrity
signing runs in safe mode where it is fatal ("tried to redefine a
protected term") — blocking any DI-signed VWC.

Back-compat note: VWCs signed before this change canonicalized the term
differently, so their signatures no longer verify. Acceptable: VWCs
expire after 7 days and this branch is unreleased.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
…irroring

Completes Data Integrity for the witnessed exchange
(docs/CRYPTO_SUITE_FOLLOWUP.md):

- App: the VRC signed into the witness VP submission is now
  capability-gated like the peer-to-peer offers (the VRC's subject IS the
  counterparty relationship DID, so getVrcJsonLdProofOptions applies
  directly). The VP's outer proof stays Ed25519Signature2018 (witness-
  facing, flips later if ever needed).
- Witness issuance MIRRORS the observed proof family via
  getMirroredJsonLdProofOptions (new in @bifold/vrc-shared): a DI-signed
  VRC implies its counterparty — the VWC's cross-distributed recipient —
  announced RCE v3, so the witness can issue a DI VWC with no
  witness<->app version negotiation. Anything else (2018 proof, unknown
  cryptosuite, malformed) fails safe to Ed25519Signature2018. DI VWCs
  drop the Ed25519 suite context URL (credentials/v2 defines the DI
  terms); applied to both witness-server and the vrc-reference witness.
- Digest semantics unchanged: the VWC digest fingerprints the VP-submitted
  VRC bytes and is only consumed by the witness registry.

Tests: mirror-helper unit tests (DI/2018/unknown-suite/proof-set/
fail-safe) and a DI VWC round-trip in diConformance (witness-mirrored
shape signs and verifies; @context stays two URLs).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
The integration batch had been failing wholesale (all suites green
standalone, everything red together) since the credo 0.6 upgrade, for
three stacked reasons:

1. askar-nodejs's FFI struct registry is process-global while jest
   re-executes modules per test file — the second suite in any shared
   process dies at import ("Duplicate type name 'ByteBuffer'"). Jest's
   sandboxing (fresh module registry, globalThis AND process per file)
   defeats every in-process singleton workaround, so test:integration now
   runs one jest process per suite (scripts/run-integration.mjs);
   test:ci updated accordingly.
2. The integration tests still used the credo 0.5 API surface
   (agent.oob/connections/credentials/proofs/basicMessages) — migrated
   ~100 call sites to agent.modules.didcomm.*.
3. Participant re-imported its per-connection did:peer:0 WITHOUT the
   DidRecord keys[] mapping, which credo 0.6's import wipes — signing
   then fell back to the legacy base58 key id and failed with "Key not
   found in backend 'askar'". The re-import now carries the keys through
   (also fixes the interactive demo, not just tests).

Gate state: connectionFlow 6/6, diConformance 6/6, vc20Conformance 3/3,
mediator suites skip cleanly. Still red (pre-existing, deeper 0.5-era
flow orchestration: autoAcceptCredentials Never + no acceptance handlers
under the 0.6 event API): credentialIssuance/Storage/Structure/
Verification, proofExchange, witnessedFlow — tracked as follow-up.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
- ContactDetails: testIDs on the witness section (WitnessSection /
  WitnessRecord) so the e2e can assert a VWC was stored without relying on
  localized text.
- witness-server: import @openwallet-foundation/askar-nodejs before any
  @credo-ts module so the native askar binding registers first. Loading
  @credo-ts/askar first left NativeAskar.instance undefined and agent
  init died with "Cannot read properties of undefined (reading
  'keyGetJwkSecret')" — same import-order constraint the DI Level 1 spike
  hit. Fixes `yarn start` for the witness server.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
Two dynamic `await import('@credo-ts/core')` calls in the witnessed flow
(witnessed-vrc-manager createAndSubmitVP, vrc-manager VWC-routing metadata)
made Metro serve those symbols as on-demand lazy chunks. On the dev build
that fetch fails mid-witnessed-exchange with "LoadBundleFromServerError:
Could not load bundle", dropping the VP submission (and the VWC), so the
exchange silently falls back to an unwitnessed VRC. Import the classes
statically at module top — more reliable in dev and prod.

Also adds SecureExchangeBadge / WitnessedBadge testIDs to ContactDetails so
the e2e can assert both shields (device attestation + witnessed) on a
contact.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
CI (Linux) failed 3 tests that pass locally: the upstream Settings and
CameraDisclosureModal "Renders correctly" snapshot tests trip React 19's
stricter act() checks.

- The shared test wrapper (__tests__/helpers/app.tsx) mounts
  VrcNameCacheProvider, whose async buildCache resolves after the mounting
  test tears down and leaks an act() update into whatever test runs next.
  Mock the provider (passthrough) in jestSetup — it only supplies display-
  name lookups, not behavior under test.
- The render/snapshot tests didn't flush the modal/screen entrance-animation
  state update; add `await act(async () => {})` after render and refresh the
  snapshots to the settled output.

Pre-existing React 19 upgrade test debt, unrelated to the DI work. Full
suite green (161 suites, 1448 tests).

Signed-off-by: Alberto L <aleon@law.harvard.edu>
…s green on CI

setInterval resolves to `number` (not NodeJS.Timeout) in the CI compile
context, so the four interval fields typed NodeJS.Timeout failed with
TS2322. That broke witness-server's tsc build, and because the build is
`--topological-dev`, the whole bifold build aborted before remote-logs
was compiled — leaving @bifold/remote-logs without type declarations and
failing the keyring app typecheck (TS2307). Use
`ReturnType<typeof setInterval>` so the fields adapt to whichever lib the
compiler resolves.

Signed-off-by: Alberto L <aleon@law.harvard.edu>
@albertoleon7794
albertoleon7794 force-pushed the upgrade/consolidated-vc2-di branch from 88b4e2c to 1755fbb Compare July 19, 2026 00:30
@albertoleon7794
albertoleon7794 marked this pull request as ready for review July 20, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet