@@ -6,7 +6,8 @@ use crate::{
66 crypto:: { base64_decode, base64_encode, decrypt, encrypt} ,
77 error:: { AppError , Error , Result } ,
88 types:: {
9- AppId , BridgeResponseV1 , BridgeUrl , IDKitResult , ResponseItem , RpContext , VerificationLevel ,
9+ AppId , BridgeResponseV1 , BridgeUrl , IDKitResult , IdentityAttribute , ResponseItem ,
10+ RpContext , VerificationLevel ,
1011 } ,
1112 ConstraintNode , Signal ,
1213} ;
@@ -105,6 +106,11 @@ struct BridgeRequestPayload {
105106 #[ serde( skip_serializing_if = "Option::is_none" ) ]
106107 proof_request : Option < ProofRequest > ,
107108
109+ /// Optional identity attribute filters for identity-attestation presets.
110+ /// Only present for World ID 4.0 identity check requests.
111+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
112+ identity_attributes : Option < Vec < IdentityAttribute > > ,
113+
108114 /// Whether to accept legacy (v3) proofs as fallback.
109115 /// - `true`: Accept both v3 and v4 proofs. Use during migration.
110116 /// - `false`: Only accept v4 proofs. Use after migration cutoff or for new apps.
@@ -259,6 +265,8 @@ pub struct BridgeConnectionParams {
259265 pub return_to : Option < String > ,
260266 /// Optional environment override (defaults to Production when not specified)
261267 pub environment : Option < Environment > ,
268+ /// Present only on World ID 4.0 requests created from `IdentityCheck` presets
269+ pub identity_attributes : Option < Vec < IdentityAttribute > > ,
262270}
263271
264272/// A helper struct to cache the signal hashes of a request
@@ -416,6 +424,7 @@ pub fn build_request_payload(params: &BridgeConnectionParams) -> Result<serde_js
416424 action : action_str,
417425 action_description : params. action_description . clone ( ) ,
418426 proof_request,
427+ identity_attributes : params. identity_attributes . clone ( ) ,
419428 verification_level : params. legacy_verification_level ,
420429 signal : legacy_signal_hash,
421430 allow_legacy_proofs : params. allow_legacy_proofs ,
@@ -843,6 +852,7 @@ impl IDKitConfig {
843852 override_connect_base_url : config. override_connect_base_url . clone ( ) ,
844853 return_to : config. return_to . clone ( ) ,
845854 environment : config. environment ,
855+ identity_attributes : None ,
846856 } )
847857 }
848858 Self :: CreateSession ( config) => {
@@ -867,6 +877,7 @@ impl IDKitConfig {
867877 override_connect_base_url : config. override_connect_base_url . clone ( ) ,
868878 return_to : config. return_to . clone ( ) ,
869879 environment : config. environment ,
880+ identity_attributes : None ,
870881 } )
871882 }
872883 Self :: ProveSession { session_id, config } => {
@@ -893,6 +904,7 @@ impl IDKitConfig {
893904 override_connect_base_url : config. override_connect_base_url . clone ( ) ,
894905 return_to : config. return_to . clone ( ) ,
895906 environment : config. environment ,
907+ identity_attributes : None ,
896908 } )
897909 }
898910 }
@@ -911,7 +923,22 @@ impl IDKitConfig {
911923 } ) ;
912924 }
913925
914- let ( _constraints, legacy_verification_level, legacy_signal) = preset. to_bridge_params ( ) ;
926+ let ( constraints, legacy_verification_level, legacy_signal, identity_attributes) =
927+ preset. into_bridge_params ( ) ;
928+
929+ // Default to Device so existing World App versions can still parse
930+ // the bridge payload. V4 requests use `proof_request` for real
931+ // credential selection; this field is only for v3 backwards compat.
932+ let legacy_verification_level =
933+ legacy_verification_level. unwrap_or ( VerificationLevel :: Device ) ;
934+ let allow_legacy_proofs = if identity_attributes. is_some ( ) {
935+ false
936+ } else {
937+ match self {
938+ Self :: Request ( config) => config. allow_legacy_proofs ,
939+ Self :: CreateSession ( _) | Self :: ProveSession { .. } => false ,
940+ }
941+ } ;
915942
916943 match self {
917944 Self :: Request ( config) => {
@@ -927,16 +954,17 @@ impl IDKitConfig {
927954 kind : RequestKind :: Uniqueness {
928955 action : config. action . clone ( ) ,
929956 } ,
930- constraints : None ,
957+ constraints,
931958 rp_context : ( * config. rp_context ) . clone ( ) ,
932959 action_description : config. action_description . clone ( ) ,
933960 legacy_verification_level,
934961 legacy_signal : legacy_signal. unwrap_or_default ( ) ,
935962 bridge_url,
936- allow_legacy_proofs : config . allow_legacy_proofs ,
963+ allow_legacy_proofs,
937964 override_connect_base_url : config. override_connect_base_url . clone ( ) ,
938965 return_to : config. return_to . clone ( ) ,
939966 environment : config. environment ,
967+ identity_attributes,
940968 } )
941969 }
942970 Self :: CreateSession ( config) => {
@@ -950,7 +978,7 @@ impl IDKitConfig {
950978 Ok ( BridgeConnectionParams {
951979 app_id,
952980 kind : RequestKind :: CreateSession ,
953- constraints : None ,
981+ constraints,
954982 rp_context : ( * config. rp_context ) . clone ( ) ,
955983 action_description : config. action_description . clone ( ) ,
956984 legacy_verification_level,
@@ -960,6 +988,7 @@ impl IDKitConfig {
960988 override_connect_base_url : config. override_connect_base_url . clone ( ) ,
961989 return_to : config. return_to . clone ( ) ,
962990 environment : config. environment ,
991+ identity_attributes,
963992 } )
964993 }
965994 Self :: ProveSession { session_id, config } => {
@@ -975,7 +1004,7 @@ impl IDKitConfig {
9751004 kind : RequestKind :: ProveSession {
9761005 session_id : session_id. clone ( ) ,
9771006 } ,
978- constraints : None ,
1007+ constraints,
9791008 rp_context : ( * config. rp_context ) . clone ( ) ,
9801009 action_description : config. action_description . clone ( ) ,
9811010 legacy_verification_level,
@@ -985,6 +1014,7 @@ impl IDKitConfig {
9851014 override_connect_base_url : config. override_connect_base_url . clone ( ) ,
9861015 return_to : config. return_to . clone ( ) ,
9871016 environment : config. environment ,
1017+ identity_attributes,
9881018 } )
9891019 }
9901020 }
@@ -1291,6 +1321,7 @@ mod tests {
12911321 signal : String :: new ( ) ,
12921322 verification_level : VerificationLevel :: Device ,
12931323 proof_request : Some ( proof_request) ,
1324+ identity_attributes : None ,
12941325 allow_legacy_proofs : false ,
12951326 environment : Environment :: Production ,
12961327 } ;
@@ -1304,6 +1335,55 @@ mod tests {
13041335 assert ! ( json. contains( "allow_legacy_proofs" ) ) ;
13051336 }
13061337
1338+ #[ test]
1339+ fn test_build_request_payload_serializes_identity_attributes ( ) {
1340+ let app_id = AppId :: new ( "app_test" ) . unwrap ( ) ;
1341+ let rp_context = RpContext :: new (
1342+ "rp_1234567890abcdef" ,
1343+ "0x0000000000000000000000000000000000000000000000000000000000000001" ,
1344+ 1_700_000_000 ,
1345+ 1_700_003_600 ,
1346+ & ( "0x" . to_string ( ) + & "00" . repeat ( 64 ) + "1b" ) ,
1347+ )
1348+ . unwrap ( ) ;
1349+ let constraints = ConstraintNode :: any ( vec ! [
1350+ ConstraintNode :: item( CredentialRequest :: new( CredentialType :: Passport , None ) ) ,
1351+ ConstraintNode :: item( CredentialRequest :: new( CredentialType :: Mnc , None ) ) ,
1352+ ] ) ;
1353+
1354+ let params = BridgeConnectionParams {
1355+ app_id,
1356+ kind : RequestKind :: Uniqueness {
1357+ action : "test-action" . to_string ( ) ,
1358+ } ,
1359+ constraints : Some ( constraints) ,
1360+ rp_context,
1361+ action_description : Some ( "Identity check" . to_string ( ) ) ,
1362+ legacy_verification_level : VerificationLevel :: Device ,
1363+ legacy_signal : String :: new ( ) ,
1364+ bridge_url : None ,
1365+ allow_legacy_proofs : false ,
1366+ override_connect_base_url : None ,
1367+ return_to : None ,
1368+ environment : Some ( Environment :: Production ) ,
1369+ identity_attributes : Some ( vec ! [
1370+ IdentityAttribute :: MinimumAge ( 21 ) ,
1371+ IdentityAttribute :: Nationality ( "JPN" . to_string( ) ) ,
1372+ ] ) ,
1373+ } ;
1374+
1375+ let payload = build_request_payload ( & params) . unwrap ( ) ;
1376+
1377+ assert_eq ! (
1378+ payload[ "identity_attributes" ] ,
1379+ serde_json:: json!( [
1380+ { "type" : "minimum_age" , "value" : 21 } ,
1381+ { "type" : "nationality" , "value" : "JPN" }
1382+ ] )
1383+ ) ;
1384+ assert ! ( payload. get( "proof_request" ) . is_some( ) ) ;
1385+ }
1386+
13071387 #[ test]
13081388 fn test_session_id_sdk_round_trip_uses_protocol_format ( ) {
13091389 let session_id = SessionId :: default ( ) ;
@@ -1455,7 +1535,8 @@ mod tests {
14551535 #[ test]
14561536 fn test_selfie_check_legacy_preset_serializes_face_verification_level ( ) {
14571537 let preset = crate :: preset:: Preset :: selfie_check_legacy ( Some ( "face-signal" . to_string ( ) ) ) ;
1458- let ( constraints, legacy_verification_level, legacy_signal) = preset. to_bridge_params ( ) ;
1538+ let ( constraints, legacy_verification_level, legacy_signal, _identity_attributes) =
1539+ preset. into_bridge_params ( ) ;
14591540
14601541 let app_id = AppId :: new ( "app_test" ) . unwrap ( ) ;
14611542 let signature = "0x" . to_string ( ) + & "00" . repeat ( 64 ) + "1b" ;
@@ -1473,17 +1554,19 @@ mod tests {
14731554 kind : RequestKind :: Uniqueness {
14741555 action : "test-action" . to_string ( ) ,
14751556 } ,
1476- constraints : Some ( constraints ) ,
1557+ constraints,
14771558 rp_context,
14781559 action_description : Some ( "Selfie check" . to_string ( ) ) ,
1479- legacy_verification_level,
1560+ legacy_verification_level : legacy_verification_level
1561+ . expect ( "this preset should return legacy_verification_level" ) ,
14801562 legacy_signal : legacy_signal. unwrap_or_default ( ) ,
14811563 bridge_url : None ,
14821564 allow_legacy_proofs : false ,
14831565
14841566 override_connect_base_url : None ,
14851567 return_to : None ,
14861568 environment : Some ( Environment :: Production ) ,
1569+ identity_attributes : None ,
14871570 } ;
14881571
14891572 let payload = build_request_payload ( & params) . unwrap ( ) ;
@@ -1493,7 +1576,8 @@ mod tests {
14931576 #[ test]
14941577 fn test_device_legacy_preset_serializes_device_verification_level ( ) {
14951578 let preset = crate :: preset:: Preset :: device_legacy ( Some ( "device-signal" . to_string ( ) ) ) ;
1496- let ( constraints, legacy_verification_level, legacy_signal) = preset. to_bridge_params ( ) ;
1579+ let ( constraints, legacy_verification_level, legacy_signal, _identity_attributes) =
1580+ preset. into_bridge_params ( ) ;
14971581
14981582 let app_id = AppId :: new ( "app_test" ) . unwrap ( ) ;
14991583 let signature = "0x" . to_string ( ) + & "00" . repeat ( 64 ) + "1b" ;
@@ -1511,17 +1595,19 @@ mod tests {
15111595 kind : RequestKind :: Uniqueness {
15121596 action : "test-action" . to_string ( ) ,
15131597 } ,
1514- constraints : Some ( constraints ) ,
1598+ constraints,
15151599 rp_context,
15161600 action_description : Some ( "Device check" . to_string ( ) ) ,
1517- legacy_verification_level,
1601+ legacy_verification_level : legacy_verification_level
1602+ . expect ( "this preset should return legacy_verification_level" ) ,
15181603 legacy_signal : legacy_signal. unwrap_or_default ( ) ,
15191604 bridge_url : None ,
15201605 allow_legacy_proofs : false ,
15211606
15221607 override_connect_base_url : None ,
15231608 return_to : None ,
15241609 environment : Some ( Environment :: Production ) ,
1610+ identity_attributes : None ,
15251611 } ;
15261612
15271613 let payload = build_request_payload ( & params) . unwrap ( ) ;
@@ -1584,6 +1670,7 @@ mod tests {
15841670 override_connect_base_url : None ,
15851671 return_to : None ,
15861672 environment : None ,
1673+ identity_attributes : None ,
15871674 } ;
15881675
15891676 let payload = build_native_v1_payload ( & params) . unwrap ( ) ;
0 commit comments