8000 feat!: did peer strict peer0 v2 by oleksiipiliugin · Pull Request #285 · affinidi/affinidi-ssi-dart · GitHub
[go: up one dir, main page]

Skip to content
< 8000 /div>

feat!: did peer strict peer0 v2 - #285

Merged
linn-affinidi merged 4 commits into
v4-releasefrom
feat/did-peer-strict-peer0-v2
Jul 22, 2026
Merged

linn-affinidi merged 4 commits into
v4-releasefrom
feat/did-peer-strict-peer0-v2

Conversation

@oleksiipiliugin
@oleksiipiliugin oleksiipiliugin commented Apr 27, 2026 •
Copy link
Copy Markdown
Contributor

BREAKING(4.X.X release candidate)

BREAKING CHANGE:

  • introduced preferredNumalgo (did:peer:2 by default)
  • deduplication of Verification Method for did:peer:0 as per spec(following did:key behavior)

This pull request introduces a formal mechanism for selecting the DID peer algorithm ("numalgo") when generating did:peer identifiers, providing strict support for both did:peer:2 (default) and did:peer:0 semantics. The changes ensure that did:peer:0 is only produced when explicitly requested and only under its strict invariants (single key, no services), while did:peer:2 remains the default and supports multiple keys and service endpoints. The update also includes comprehensive tests to verify correct behavior for both modes and prevent unintentional fallback to did:peer:0.

DID Peer Algorithm Selection and Enforcement

  • Added a preferredNumalgo parameter to DidPeerManager, defaulting to DidPeerType.peer2. When set to DidPeerType.peer0, the manager enforces strict did:peer:0 invariants (single wallet key, no service endpoints) and throws exceptions if violated. (lib/src/did/did_manager/did_peer_manager.dart)
  • Updated DidPeer.getDid to accept a preferredNumalgo parameter. Now, did:peer:0 is only generated if preferredNumalgo is set to peer0 and the input meets the constraints; otherwise, it always produces did:peer:2. (lib/src/did/did_peer.dart) (F9928bebL394R409)

Manager and Encoder Logic Updates

  • Modified DidPeerManager.getDidDocument to use the new preferredNumalgo logic and added a fast path for collapsing to did:peer:0 when appropriate. (lib/src/did/did_manager/did_peer_manager.dart) [1] [2]

Test Suite Enhancements

  • Updated and expanded tests to:
    • Explicitly test both did:peer:2 (default) and strict did:peer:0 scenarios
    • Ensure no silent fallback to did:peer:0 in the default mode
    • Verify that strict did:peer:0 mode rejects multiple keys and service endpoints, but allows multiple relationships for the same key
    • Add regression tests for pre-PR and post-PR behaviors (test/did/did_manager/did_peer_manager_test.dart) [1] [2] [3] [4] [5] [6] [7] [8]
    • Update sample usage of DidPeer.getDid to specify preferredNumalgo (test/did/did_peer_test.dart)

Documentation and Imports

  • Improved documentation for the new parameter and updated imports to support the changes. (lib/src/did/did_manager/did_peer_manager.dart, lib/src/did/did_peer.dart) [1] [2]

These changes make DID peer algorithm selection explicit, robust, and predictable, preventing accidental generation of did:peer:0 identifiers and aligning with the latest specifications and design plans.

@oleksiipiliugin
oleksiipiliugin requested a review from a team as a code owner April 27, 2026 07:41
Base automatically changed from feat/did-manager-multi-key-support-did-web-did-peer-v2 to main April 28, 2026 08:19
@oleksiipiliugin
oleksiipiliugin force-pushed the feat/did-peer-strict-peer0-v2 branch from 25f5222 to 93bf51a Compare April 28, 2026 12:12
@oleksiipiliugin oleksiipiliugin changed the title feat: did peer strict peer0 v2 feat!: did peer strict peer0 v2 Apr 28, 2026
@github-actions
Copy link
Copy Markdown

Coverage after merging feat/did-peer-strict-peer0-v2 into main will be

79.34%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
lib/src
   types.dart70.83%100%100%70.83%122, 159–160, 172, 174, 189, 244
   digest_utils.dart100%100%100%100%
   utility.dart100%100%100%100%
lib/src/credentials/jwt
   jwt_data_model_v1.dart100%100%100%100%
   jwt_dm_v1_suite.dart86.27%100%100%86.27%100, 102, 39, 41, 59, 74, 76
lib/src/credentials/linked_data
   ld_base_suite.dart93.44%100%100%93.44%120, 122, 168, 171
   ld_dm_v1_suite.dart100%100%100%100%
   ld_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/models
   sdjwt_signer_adapter.dart100%100%100%100%
lib/src/credentials/models/field_types
   issuer.dart100%100%100%100%
   context.dart85.96%100%100%85.96%171, 173, 206–211
   credential_schema.dart100%100%100%100%
   credential_subject.dart72.97%100%100%72.97%38, 55, 57–58, 61, 64–65, 70–71, 94
   evidence.dart76.47%100%100%76.47%50–52, 54
   holder.dart100%100%100%100%
   terms_of_use.dart75%100%100%75%46–48, 50
lib/src/credentials/models/field_types/credential_status
   revocation_list_2020.dart20.90%100%100%20.90%101–104, 107, 110–112, 114–116, 118–120, 122–124, 14, 171, 173, 175–182, 22–27, 65, 68–75, 81, 84–85, 87–88, 90–91, 93–94, 99
   v1.dart72.73%100%100%72.73%60–61, 71, 73–76, 78, 82–83, 88–89
   v2.dart72.73%100%100%72.73%59–60, 70, 72–75, 77, 81–82, 87–88
lib/src/credentials/models/field_types/refresh_service
   v1.dart76.47%100%100%76.47%46–48, 50
   v2.dart18.18%100%100%18.18%35–36, 38, 55–56, 61, 66–67, 69
lib/src/credentials/models/v1
   mutable_vc_data_model_v1.dart88.14%100%100%88.14%154, 202–203, 209–210, 216, 222
   vc_data_model_v1.dart91.27%100%100%91.27%160, 162–163, 187–189, 212–215, 218
lib/src/credentials/models/v2
   mutable_vc_data_model_v2.dart84.75%100%100%84.75%179, 185–186, 196–197, 204–205, 211, 217
   vc_data_model_v2.dart90.76%100%100%90.76%165–167, 190–193, 196, 316, 322, 326
lib/src/credentials/parsers
   jwt_parser.dart88.89%100%100%88.89%51, 53
   ld_parser.dart85.71%100%100%85.71%26
   sdjwt_parser.dart70%100%100%70%40, 56, 58
lib/src/credentials/presentations/linked_data
   ld_vp_dm_v1_suite.dart100%100%100%100%
   ld_vp_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/presentations/models
   vc_parse_present.dart57.14%100%100%57.14%101, 104, 109–110, 35–37, 62–64, 88–89, 92–97
lib/src/credentials/presentations/models/v1
   mutable_vp_data_model_v1.dart48.15%100%100%48.15%102, 75, 81–82, 84–86, 88, 90, 92, 94–95, 98–99
   vp_data_model_v1.dart88.52%100%100%88.52%111, 113, 138, 143, 84, 86–87
lib/src/credentials/presentations/models/v2
   vp_data_model_v2.dart88.06%100%100%88.06%116, 118, 142, 147, 205, 89, 91–92
   mutable_vp_data_model_v2.dart46.88%100%100%46.88%101–102, 105–106, 109, 111–112, 115, 81, 87–88, 90, 92–93, 95, 97, 99
lib/src/credentials/presentations/suites
   universal_presentation_parser.dart42.86%100%100%42.86%21–22, 24–25
   universal_presentation_verifier.dart90.63%100%100%90.63%106–108
   vp_suites.dart61.54%100%100%61.54%31, 33, 53–55
lib/src/credentials/presentations/verification
   delegation_vc_verifier.dart100%100%100%100%
   holder_binding_verifier.dart94.87%100%100%94.87%64–65
   vp_expiry_verifier.dart100%100%100%100%
   vp_integrity_verifier.dart100%100%100%100%
   vp_domain_challenge_verifier.dart75%100%100%75%36, 53–54, 62–63
lib/src/credentials/proof
   data_integrity_ecdsa_suite.dart77.98%100%100%77.98%143, 152, 155, 158, 161, 164, 171, 174, 181, 268, 272, 274–275, 350, 355, 384, 386, 404, 408, 410–411, 52, 54–55
   data_integrity_eddsa_suite.dart80%100%100%80%137, 146, 149, 152, 155, 158, 165, 168, 175, 181, 49, 51–52
   ecdsa_secp256k1_signature2019_suite.dart75%100%100%75%125, 130–131, 133, 135–136, 138, 140–141, 143, 145–146, 148, 152–153, 155, 158, 76, 78, 81
   embedded_proof_suite.dart100%100%100%100%
   jcs_utils.dart49.06%100%100%49.06%17, 193, 196, 199–202, 221, 225, 229, 240–241, 249, 252, 42, 44–45, 53, 55, 61, 64, 72–73, 75, 79, 81, 84
   embedded_proof.dart75%100%100%75%107–108, 110, 117, 158–159, 165–167, 169, 183, 191, 197, 199, 91, 96–97
   base_data_integrity_verifier.dart63.50%100%100%63.50%101–102, 116–119, 123–127, 154–159, 183–184, 211, 213, 215–216, 219, 222–223, 227–228, 302, 304–305, 339, 344–345, 347, 349–350, 352, 354–355, 357, 359–360, 362, 365–366, 368, 371, 87–88
   proof_validation_utils.dart100%100%100%100%
   base_jcs_generator.dart100%100%100%100%
   data_integrity_context_util.dart60%100%100%60%14, 23, 25, 31, 35, 37
   base_jcs_verifier.dart72.73%100%100%72.73%101, 103–104, 115, 117–118, 153–154, 184–187, 189,

@github-actions
Copy link
Copy Markdown

Coverage after merging feat/did-peer-strict-peer0-v2 into main will be

79.24%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
lib/src
   types.dart70.83%100%100%70.83%122, 159–160, 172, 174, 189, 244
   digest_utils.dart100%100%100%100%
   utility.dart100%100%100%100%
lib/src/credentials/jwt
   jwt_data_model_v1.dart100%100%100%100%
   jwt_dm_v1_suite.dart86.27%100%100%86.27%100, 102, 39, 41, 59, 74, 76
lib/src/credentials/linked_data
   ld_base_suite.dart93.44%100%100%93.44%120, 122, 168, 171
   ld_dm_v1_suite.dart100%100%100%100%
   ld_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/models
   sdjwt_signer_adapter.dart100%100%100%100%
lib/src/credentials/models/field_types
   issuer.dart100%100%100%100%
   context.dart85.96%100%100%85.96%171, 173, 206–211
   credential_schema.dart100%100%100%100%
   credential_subject.dart72.97%100%100%72.97%38, 55, 57–58, 61, 64–65, 70–71, 94
   evidence.dart76.47%100%100%76.47%50–52, 54
   holder.dart100%100%100%100%
   terms_of_use.dart75%100%100%75%46–48, 50
lib/src/credentials/models/field_types/credential_status
   revocation_list_2020.dart20.90%100%100%20.90%101–104, 107, 110–112, 114–116, 118–120, 122–124, 14, 171, 173, 175–182, 22–27, 65, 68–75, 81, 84–85, 87–88, 90–91, 93–94, 99
   v1.dart72.73%100%100%72.73%60–61, 71, 73–76, 78, 82–83, 88–89
   v2.dart72.73%100%100%72.73%59–60, 70, 72–75, 77, 81–82, 87–88
lib/src/credentials/models/field_types/refresh_service
   v1.dart76.47%100%100%76.47%46–48, 50
   v2.dart18.18%100%100%18.18%35–36, 38, 55–56, 61, 66–67, 69
lib/src/credentials/models/v1
   mutable_vc_data_model_v1.dart88.14%100%100%88.14%154, 202–203, 209–210, 216, 222
   vc_data_model_v1.dart91.27%100%100%91.27%160, 162–163, 187–189, 212–215, 218
lib/src/credentials/models/v2
   mutable_vc_data_model_v2.dart84.75%100%100%84.75%179, 185–186, 196–197, 204–205, 211, 217
   vc_data_model_v2.dart90.76%100%100%90.76%165–167, 190–193, 196, 316, 322, 326
lib/src/credentials/parsers
   jwt_parser.dart88.89%100%100%88.89%51, 53
   ld_parser.dart85.71%100%100%85.71%26
   sdjwt_parser.dart70%100%100%70%40, 56, 58
lib/src/credentials/presentations/linked_data
   ld_vp_dm_v1_suite.dart100%100%100%100%
   ld_vp_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/presentations/models
   vc_parse_present.dart57.14%100%100%57.14%101, 104, 109–110, 35–37, 62–64, 88–89, 92–97
lib/src/credentials/presentations/models/v1
   mutable_vp_data_model_v1.dart48.15%100%100%48.15%102, 75, 81–82, 84–86, 88, 90, 92, 94–95, 98–99
   vp_data_model_v1.dart88.52%100%100%88.52%111, 113, 138, 143, 84, 86–87
lib/src/credentials/presentations/models/v2
   vp_data_model_v2.dart88.06%100%100%88.06%116, 118, 142, 147, 205, 89, 91–92
   mutable_vp_data_model_v2.dart46.88%100%100%46.88%101–102, 105–106, 109, 111–112, 115, 81, 87–88, 90, 92–93, 95, 97, 99
lib/src/credentials/presentations/suites
   universal_presentation_parser.dart42.86%100%100%42.86%21–22, 24–25
   universal_presentation_verifier.dart90.63%100%100%90.63%106–108
   vp_suites.dart61.54%100%100%61.54%31, 33, 53–55
lib/src/credentials/presentations/verification
   delegation_vc_verifier.dart100%100%100%100%
   holder_binding_verifier.dart94.87%100%100%94.87%64–65
   vp_expiry_verifier.dart100%100%100%100%
   vp_integrity_verifier.dart100%100%100%100%
   vp_domain_challenge_verifier.dart75%100%100%75%36, 53–54, 62–63
lib/src/credentials/proof
   data_integrity_ecdsa_suite.dart77.98%100%100%77.98%143, 152, 155, 158, 161, 164, 171, 174, 181, 268, 272, 274–275, 350, 355, 384, 386, 404, 408, 410–411, 52, 54–55
   data_integrity_eddsa_suite.dart80%100%100%80%137, 146, 149, 152, 155, 158, 165, 168, 175, 181, 49, 51–52
   ecdsa_secp256k1_signature2019_suite.dart75.61%100%100%75.61%125, 130–131, 133, 135–136, 138, 140–141, 143, 145–146, 148, 152–153, 155, 158, 76, 78, 81
   embedded_proof_suite.dart100%100%100%100%
   jcs_utils.dart49.06%100%100%49.06%17, 193, 196, 199–202, 221, 225, 229, 240–241, 249, 252, 42, 44–45, 53, 55, 61, 64, 72–73, 75, 79, 81, 84
   embedded_proof.dart75%100%100%75%107–108, 110, 117, 158–159, 165–167, 169, 183, 191, 197, 199, 91, 96–97
   base_data_integrity_verifier.dart63.50%100%100%63.50%101–102, 116–119, 123–127, 154–159, 183–184, 211, 213, 215–216, 219, 222–223, 227–228, 302, 304–305, 339, 344–345, 347, 349–350, 352, 354–355, 357, 359–360, 362, 365–366, 368, 371, 87–88
   proof_validation_utils.dart100%100%100%100%
   base_jcs_generator.dart100%100%100%100%
   data_integrity_context_util.dart60%100%100%60%14, 23, 25, 31, 35, 37
   base_jcs_verifier.dart72.73%100%100%72.73%101, 103–104, 115, 117–118, 153–154, 184–187, 189,

@oleksiipiliugin
oleksiipiliugin force-pushed the feat/did-peer-strict-peer0-v2 branch from 262fd1c to 89a4602 Compare June 30, 2026 15:27
@github-actions
Copy link
Copy Markdown

Coverage after merging feat/did-peer-strict-peer0-v2 into main will be

79.24%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
lib/src
   types.dart70.83%100%100%70.83%122, 159–160, 172, 174, 189, 244
   digest_utils.dart100%100%100%100%
   utility.dart100%100%100%100%
lib/src/credentials/jwt
   jwt_data_model_v1.dart100%100%100%100%
   jwt_dm_v1_suite.dart86.27%100%100%86.27%100, 102, 39, 41, 59, 74, 76
lib/src/credentials/linked_data
   ld_base_suite.dart93.44%100%100%93.44%120, 122, 168, 171
   ld_dm_v1_suite.dart100%100%100%100%
   ld_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/models
   sdjwt_signer_adapter.dart100%100%100%100%
lib/src/credentials/models/field_types
   issuer.dart100%100%100%100%
   context.dart85.96%100%100%85.96%171, 173, 206–211
   credential_schema.dart100%100%100%100%
   credential_subject.dart72.97%100%100%72.97%38, 55, 57–58, 61, 64–65, 70–71, 94
   evidence.dart76.47%100%100%76.47%50–52, 54
   holder.dart100%100%100%100%
   terms_of_use.dart75%100%100%75%46–48, 50
lib/src/credentials/models/field_types/credential_status
   revocation_list_2020.dart20.90%100%100%20.90%101–104, 107, 110–112, 114–116, 118–120, 122–124, 14, 171, 173, 175–182, 22–27, 65, 68–75, 81, 84–85, 87–88, 90–91, 93–94, 99
   v1.dart72.73%100%100%72.73%60–61, 71, 73–76, 78, 82–83, 88–89
   v2.dart72.73%100%100%72.73%59–60, 70, 72–75, 77, 81–82, 87–88
lib/src/credentials/models/field_types/refresh_service
   v1.dart76.47%100%100%76.47%46–48, 50
   v2.dart18.18%100%100%18.18%35–36, 38, 55–56, 61, 66–67, 69
lib/src/credentials/models/v1
   mutable_vc_data_model_v1.dart88.14%100%100%88.14%154, 202–203, 209–210, 216, 222
   vc_data_model_v1.dart91.27%100%100%91.27%160, 162–163, 187–189, 212–215, 218
lib/src/credentials/models/v2
   mutable_vc_data_model_v2.dart84.75%100%100%84.75%179, 185–186, 196–197, 204–205, 211, 217
   vc_data_model_v2.dart90.76%100%100%90.76%165–167, 190–193, 196, 316, 322, 326
lib/src/credentials/parsers
   jwt_parser.dart88.89%100%100%88.89%51, 53
   ld_parser.dart85.71%100%100%85.71%26
   sdjwt_parser.dart70%100%100%70%40, 56, 58
lib/src/credentials/presentations/linked_data
   ld_vp_dm_v1_suite.dart100%100%100%100%
   ld_vp_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/presentations/models
   vc_parse_present.dart57.14%100%100%57.14%101, 104, 109–110, 35–37, 62–64, 88–89, 92–97
lib/src/credentials/presentations/models/v1
   mutable_vp_data_model_v1.dart48.15%100%100%48.15%102, 75, 81–82, 84–86, 88, 90, 92, 94–95, 98–99
   vp_data_model_v1.dart88.52%100%100%88.52%111, 113, 138, 143, 84, 86–87
lib/src/credentials/presentations/models/v2
   vp_data_model_v2.dart88.06%100%100%88.06%116, 118, 142, 147, 205, 89, 91–92
   mutable_vp_data_model_v2.dart46.88%100%100%46.88%101–102, 105–106, 109, 111–112, 115, 81, 87–88, 90, 92–93, 95, 97, 99
lib/src/credentials/presentations/suites
   universal_presentation_parser.dart42.86%100%100%42.86%21–22, 24–25
   universal_presentation_verifier.dart90.63%100%100%90.63%106–108
   vp_suites.dart61.54%100%100%61.54%31, 33, 53–55
lib/src/credentials/presentations/verification
   delegation_vc_verifier.dart100%100%100%100%
   holder_binding_verifier.dart94.87%100%100%94.87%64–65
   vp_expiry_verifier.dart100%100%100%100%
   vp_integrity_verifier.dart100%100%100%100%
   vp_domain_challenge_verifier.dart75%100%100%75%36, 53–54, 62–63
lib/src/credentials/proof
   data_integrity_ecdsa_suite.dart77.98%100%100%77.98%143, 152, 155, 158, 161, 164, 171, 174, 181, 268, 272, 274–275, 350, 355, 384, 386, 404, 408, 410–411, 52, 54–55
   data_integrity_eddsa_suite.dart80%100%100%80%137, 146, 149, 152, 155, 158, 165, 168, 175, 181, 49, 51–52
   ecdsa_secp256k1_signature2019_suite.dart75.61%100%100%75.61%125, 130–131, 133, 135–136, 138, 140–141, 143, 145–146, 148, 152–153, 155, 158, 76, 78, 81
   embedded_proof_suite.dart100%100%100%100%
   jcs_utils.dart49.06%100%100%49.06%17, 193, 196, 199–202, 221, 225, 229, 240–241, 249, 252, 42, 44–45, 53, 55, 61, 64, 72–73, 75, 79, 81, 84
   embedded_proof.dart75%100%100%75%107–108, 110, 117, 158–159, 165–167, 169, 183, 191, 197, 199, 91, 96–97
   base_data_integrity_verifier.dart63.50%100%100%63.50%101–102, 116–119, 123–127, 154–159, 183–184, 211, 213, 215–216, 219, 222–223, 227–228, 302, 304–305, 339, 344–345, 347, 349–350, 352, 354–355, 357, 359–360, 362, 365–366, 368, 371, 87–88
   proof_validation_utils.dart100%100%100%100%
   base_jcs_generator.dart100%100%100%100%
   data_integrity_context_util.dart60%100%100%60%14, 23, 25, 31, 35, 37
   base_jcs_verifier.dart72.73%100%100%72.73%101, 103–104, 115, 117–118, 153–154, 184–187, 189,

…tDid

Backports the `preferredNumalgo` parameter from upstream commit 342c0a0,
defaulting to DidPeerType.peer2. With this default, callers that previously
relied on the implicit "single key + no service => did:peer:0" auto-pick
must now opt in explicitly with `preferredNumalgo: DidPeerType.peer0`.

The dedup-shaped `internalAddVerificationMethod` from b12ef59 is
intentionally NOT included; the per-relationship VM creation in
DidPeerManager remains identical to v3.6.2 / v3.8.0 to keep alignment
with the Affinidi Rust mediator (affinidi-tdk-rs/.../resolve.rs) which
cannot currently resolve did:peer:0 correctly.

Tests updated to opt into peer0 explicitly:
- DidPeerManager DID signer integration: normalizes fragment-only verificationMethodId to full DID URL and uses full ID in proofs
- DidPeerManager Key retrieval: should retrieve DID key pair
- DidPeerManager DID generation: generates a valid did:peer:0 document
- DidPeerManager DID generation: generates a valid did:peer:0 document with secp256k1 key
When DidPeerManager is constructed with `preferredNumalgo: DidPeerType.peer0`
the manager now enforces the same single-key, no-services constraints that
DidKeyManager enforces, instead of silently falling back to `did:peer:2`
when those constraints are violated.

This treats `did:peer:0` as the `did:key`-equivalent encoding it is per
spec: one multibase-encoded key, no services. The peer:2 default behavior
is unchanged.

Changes (all in lib/src/did/did_manager/did_peer_manager.dart):
- Override `addVerificationMethod`: when `preferredNumalgo == peer0`,
  scan existing VMs in the store, look up their wallet keys, and reject
  with `tooManyVerificationMethods` if a *different* wallet key is
  already present. Same-key re-entry is allowed so multi-relationship
  adds and the ed25519→x25519 keyAgreement derivation continue to work.
- Override `addServiceEndpoint`: when `preferredNumalgo == peer0`,
  throw `UnsupportedError` (mirror DidKeyManager.addServiceEndpoint).
- Modify `getDidDocument`: when `preferredNumalgo == peer0` and there
  are no services, collapse multiple stored VMs that share a single
  wallet key (e.g. ed25519 + derived x25519) down to the single primary
  key before calling `DidPeer.getDid`. The `_buildEDDoc` resolver
  derives the keyAgreement entry the same way `did:key` does.

`internalAddVerificationMethod` is unchanged — peer:2 remains identical
to v3.6.2/v3.8.0, preserving alignment with the Affinidi Rust mediator.
The `isDid0` gate in `DidPeer.getDid` is also unchanged so the static
public API stays safe for direct callers.

Adds 6 new tests in test/did/did_manager/did_peer_manager_test.dart
(group: "Strict did:peer:0 semantics"):
- rejects a second wallet key with tooManyVerificationMethods
- allows re-entry for the SAME wallet key (multi-relationship adds)
- rejects addServiceEndpoint with UnsupportedError
- single ed25519 + keyAgreement still produces did:peer:0 (mirrors did:key)
- single p256 + multiple relationships produces did:peer:0
- default (peer2) manager still allows multiple keys and services
…hapes

Adds three regression tests in the existing
DidPeerManager > "DID generation" group to lock in that the default
peer:2 manager never auto-collapses single-key/no-service shapes down to
did:peer:0. Pre-PR, the static `DidPeer.getDid` would have done so for
the same inputs (b12ef59 / upstream-without-`preferredNumalgo`), so this
guards the intent of the new `preferredNumalgo: DidPeerType.peer2`
default at the manager level.

Cases covered:
- 1 key (p256) + 1 relationship (authentication) + 0 services
  -> did:peer:2 with a `.Vz` key entry (not did:peer:0).
- 1 ed25519 key + auth+keyAgreement + 0 services
  -> did:peer:2 with `.Vz` and `.Ez` entries and 2 VMs (not collapsed
  to a did:key-style ed25519+derived-x25519 did:peer:0 doc).
- 1 key + empty relationship set -> manager throws SsiException with
  code `invalidDidDocument`, asserting the manager neither silently
  produces a relationships-less did:peer:0 nor a relationships-less
  did:peer:2.
@oleksiipiliugin
oleksiipiliugin force-pushed the feat/did-peer-strict-peer0-v2 branch from 89a4602 to 0744924 Compare June 30, 2026 15:36
@oleksiipiliugin
oleksiipiliugin changed the base branch from main to v4-release June 30, 2026 15:38
@github-actions
Copy link
Copy Markdown

Coverage after merging feat/did-peer-strict-peer0-v2 into main will be

79.24%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
lib/src
   types.dart70.83%100%100%70.83%122, 159–160, 172, 174, 189, 244
   digest_utils.dart100%100%100%100%
   utility.dart100%100%100%100%
lib/src/credentials/jwt
   jwt_data_model_v1.dart100%100%100%100%
   jwt_dm_v1_suite.dart86.27%100%100%86.27%100, 102, 39, 41, 59, 74, 76
lib/src/credentials/linked_data
   ld_base_suite.dart93.44%100%100%93.44%120, 122, 168, 171
   ld_dm_v1_suite.dart100%100%100%100%
   ld_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/models
   sdjwt_signer_adapter.dart100%100%100%100%
lib/src/credentials/models/field_types
   issuer.dart100%100%100%100%
   context.dart85.96%100%100%85.96%171, 173, 206–211
   credential_schema.dart100%100%100%100%
   credential_subject.dart72.97%100%100%72.97%38, 55, 57–58, 61, 64–65, 70–71, 94
   evidence.dart76.47%100%100%76.47%50–52, 54
   holder.dart100%100%100%100%
   terms_of_use.dart75%100%100%75%46–48, 50
lib/src/credentials/models/field_types/credential_status
   revocation_list_2020.dart20.90%100%100%20.90%101–104, 107, 110–112, 114–116, 118–120, 122–124, 14, 171, 173, 175–182, 22–27, 65, 68–75, 81, 84–85, 87–88, 90–91, 93–94, 99
   v1.dart72.73%100%100%72.73%60–61, 71, 73–76, 78, 82–83, 88–89
   v2.dart72.73%100%100%72.73%59–60, 70, 72–75, 77, 81–82, 87–88
lib/src/credentials/models/field_types/refresh_service
   v1.dart76.47%100%100%76.47%46–48, 50
   v2.dart18.18%100%100%18.18%35–36, 38, 55–56, 61, 66–67, 69
lib/src/credentials/models/v1
   mutable_vc_data_model_v1.dart88.14%100%100%88.14%154, 202–203, 209–210, 216, 222
   vc_data_model_v1.dart91.27%100%100%91.27%160, 162–163, 187–189, 212–215, 218
lib/src/credentials/models/v2
   mutable_vc_data_model_v2.dart84.75%100%100%84.75%179, 185–186, 196–197, 204–205, 211, 217
   vc_data_model_v2.dart90.76%100%100%90.76%165–167, 190–193, 196, 316, 322, 326
lib/src/credentials/parsers
   jwt_parser.dart88.89%100%100%88.89%51, 53
   ld_parser.dart85.71%100%100%85.71%26
   sdjwt_parser.dart70%100%100%70%40, 56, 58
lib/src/credentials/presentations/linked_data
   ld_vp_dm_v1_suite.dart100%100%100%100%
   ld_vp_dm_v2_suite.dart100%100%100%100%
lib/src/credentials/presentations/models
   vc_parse_present.dart57.14%100%100%57.14%101, 104, 109–110, 35–37, 62–64, 88–89, 92–97
lib/src/credentials/presentations/models/v1
   mutable_vp_data_model_v1.dart48.15%100%100%48.15%102, 75, 81–82, 84–86, 88, 90, 92, 94–95, 98–99
   vp_data_model_v1.dart88.52%100%100%88.52%111, 113, 138, 143, 84, 86–87
lib/src/credentials/presentations/models/v2
   vp_data_model_v2.dart88.06%100%100%88.06%116, 118, 142, 147, 205, 89, 91–92
   mutable_vp_data_model_v2.dart46.88%100%100%46.88%101–102, 105–106, 109, 111–112, 115, 81, 87–88, 90, 92–93, 95, 97, 99
lib/src/credentials/presentations/suites
   universal_presentation_parser.dart42.86%100%100%42.86%21–22, 24–25
   universal_presentation_verifier.dart90.63%100%100%90.63%106–108
   vp_suites.dart61.54%100%100%61.54%31, 33, 53–55
lib/src/credentials/presentations/verification
   delegation_vc_verifier.dart100%100%100%100%
   holder_binding_verifier.dart94.87%100%100%94.87%64–65
   vp_expiry_verifier.dart100%100%100%100%
   vp_integrity_verifier.dart100%100%100%100%
   vp_domain_challenge_verifier.dart75%100%100%75%36, 53–54, 62–63
lib/src/credentials/proof
   data_integrity_ecdsa_suite.dart77.98%100%100%77.98%143, 152, 155, 158, 161, 164, 171, 174, 181, 268, 272, 274–275, 350, 355, 384, 386, 404, 408, 410–411, 52, 54–55
   data_integrity_eddsa_suite.dart80%100%100%80%137, 146, 149, 152, 155, 158, 165, 168, 175, 181, 49, 51–52
   ecdsa_secp256k1_signature2019_suite.dart75.61%100%100%75.61%125, 130–131, 133, 135–136, 138, 140–141, 143, 145–146, 148, 152–153, 155, 158, 76, 78, 81
   embedded_proof_suite.dart100%100%100%100%
   jcs_utils.dart49.06%100%100%49.06%17, 193, 196, 199–202, 221, 225, 229, 240–241, 249, 252, 42, 44–45, 53, 55, 61, 64, 72–73, 75, 79, 81, 84
   embedded_proof.dart75%100%100%75%107–108, 110, 117, 158–159, 165–167, 169, 183, 191, 197, 199, 91, 96–97
   base_data_integrity_verifier.dart63.50%100%100%63.50%101–102, 116–119, 123–127, 154–159, 183–184, 211, 213, 215–216, 219, 222–223, 227–228, 302, 304–305, 339, 344–345, 347, 349–350, 352, 354–355, 357, 359–360, 362, 365–366, 368, 371, 87–88
   proof_validation_utils.dart100%100%100%100%
   base_jcs_generator.dart100%100%100%100%
   data_integrity_context_util.dart60%100%100%60%14, 23, 25, 31, 35, 37
   base_jcs_verifier.dart72.73%100%100%72.73%101, 103–104, 115, 117–118, 153–154, 184–187, 189,

@linn-affinidi
linn-affinidi merged commit e6dc8e3 into v4-release Jul 22, 2026
4 checks passed
@linn-affinidi
linn-affinidi deleted the feat/did-peer-strict-peer0-v2 branch July 22, 2026 13:52
DenisPopov15 pushed a commit that referenced this pull request Jul 23, 2026
* feat: add preferredNumalgo selector for DidPeerManager and DidPeer.getDid

Backports the `preferredNumalgo` parameter from upstream commit 342c0a0,
defaulting to DidPeerType.peer2. With this default, callers that previously
relied on the implicit "single key + no service => did:peer:0" auto-pick
must now opt in explicitly with `preferredNumalgo: DidPeerType.peer0`.

The dedup-shaped `internalAddVerificationMethod` from b12ef59 is
intentionally NOT included; the per-relationship VM creation in
DidPeerManager remains identical to v3.6.2 / v3.8.0 to keep alignment
with the Affinidi Rust mediator (affinidi-tdk-rs/.../resolve.rs) which
cannot currently resolve did:peer:0 correctly.

Tests updated to opt into peer0 explicitly:
- DidPeerManager DID signer integration: normalizes fragment-only verificationMethodId to full DID URL and uses full ID in proofs
- DidPeerManager Key retrieval: should retrieve DID key pair
- DidPeerManager DID generation: generates a valid did:peer:0 document
- DidPeerManager DID generation: generates a valid did:peer:0 document with secp256k1 key

* feat: enforce strict did:peer:0 invariants in DidPeerManager

When DidPeerManager is constructed with `preferredNumalgo: DidPeerType.peer0`
the manager now enforces the same single-key, no-services constraints that
DidKeyManager enforces, instead of silently falling back to `did:peer:2`
when those constraints are violated.

This treats `did:peer:0` as the `did:key`-equivalent encoding it is per
spec: one multibase-encoded key, no services. The peer:2 default behavior
is unchanged.

Changes (all in lib/src/did/did_manager/did_peer_manager.dart):
- Override `addVerificationMethod`: when `preferredNumalgo == peer0`,
  scan existing VMs in the store, look up their wallet keys, and reject
  with `tooManyVerificationMethods` if a *different* wallet key is
  already present. Same-key re-entry is allowed so multi-relationship
  adds and the ed25519→x25519 keyAgreement derivation continue to work.
- Override `addServiceEndpoint`: when `preferredNumalgo == peer0`,
  throw `UnsupportedError` (mirror DidKeyManager.addServiceEndpoint).
- Modify `getDidDocument`: when `preferredNumalgo == peer0` and there
  are no services, collapse multiple stored VMs that share a single
  wallet key (e.g. ed25519 + derived x25519) down to the single primary
  key before calling `DidPeer.getDid`. The `_buildEDDoc` resolver
  derives the keyAgreement entry the same way `did:key` does.

`internalAddVerificationMethod` is unchanged — peer:2 remains identical
to v3.6.2/v3.8.0, preserving alignment with the Affinidi Rust mediator.
The `isDid0` gate in `DidPeer.getDid` is also unchanged so the static
public API stays safe for direct callers.

Adds 6 new tests in test/did/did_manager/did_peer_manager_test.dart
(group: "Strict did:peer:0 semantics"):
- rejects a second wallet key with tooManyVerificationMethods
- allows re-entry for the SAME wallet key (multi-relationship adds)
- rejects addServiceEndpoint with UnsupportedError
- single ed25519 + keyAgreement still produces did:peer:0 (mirrors did:key)
- single p256 + multiple relationships produces did:peer:0
- default (peer2) manager still allows multiple keys and services

* test: assert default DidPeerManager produces did:peer:2 for minimal shapes

Adds three regression tests in the existing
DidPeerManager > "DID generation" group to lock in that the default
peer:2 manager never auto-collapses single-key/no-service shapes down to
did:peer:0. Pre-PR, the static `DidPeer.getDid` would have done so for
the same inputs (b12ef59 / upstream-without-`preferredNumalgo`), so this
guards the intent of the new `preferredNumalgo: DidPeerType.peer2`
default at the manager level.

Cases covered:
- 1 key (p256) + 1 relationship (authentication) + 0 services
  -> did:peer:2 with a `.Vz` key entry (not did:peer:0).
- 1 ed25519 key + auth+keyAgreement + 0 services
  -> did:peer:2 with `.Vz` and `.Ez` entries and 2 VMs (not collapsed
  to a did:key-style ed25519+derived-x25519 did:peer:0 doc).
- 1 key + empty relationship set -> manager throws SsiException with
  code `invalidDidDocument`, asserting the manager neither silently
  produces a relationships-less did:peer:0 nor a relationships-less
  did:peer:2.

* feat: update DidPeerManager to throw SsiException for unsupported operations and improve wallet key retrieval
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0