Skip to content

Commit b21c0b6

Browse files
authored
feat!: Add new major version for qr-link package (#655)
### Notes We are planning a new roll-out of the session validation flow: https://www.notion.so/worldcoin/Proposal-Eliminate-Session-Centric-Verification-Flow-2418614bdf8c802893d9c3fe43ebba81. This PR prepares a new version of qr codes where the orb knows the version of the QR code * A description of the new flow is written in lib * relies on the new orb relay message types specified in worldcoin/orb-relay-messages#71
1 parent a091c52 commit b21c0b6

File tree

7 files changed

+182
-79
lines changed

7 files changed

+182
-79
lines changed

Cargo.lock

Lines changed: 17 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

qr-link/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "orb-qr-link"
3-
version = "0.0.5"
3+
version = "0.1.0"
44
description = "Data link between Worldcoin App and Orb through QR-codes"
55
authors = ["Valentyn Valiaiev <[email protected]>"]
66
publish = false
@@ -23,6 +23,11 @@ serde = { version = "1.0.188", features = ["derive"] }
2323
thiserror = "1.0.57"
2424
uuid = { version = "1.4.1", features = ["v4"] }
2525

26+
[dependencies.orb-relay-messages]
27+
features = ["client"]
28+
git = "https://github.com/worldcoin/orb-relay-messages.git"
29+
rev = "2c2a97f963b6e3baea794a678f0450428876ec02"
30+
2631
[[test]]
2732
name = "verification"
2833
required-features = ["encode", "decode"]

qr-link/src/app_authenticated_data.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use blake3::Hasher;
2+
use orb_relay_messages::common::v1::AppAuthenticatedData;
3+
4+
// Define the constant needed by the implementation
5+
const PCP_VERSION_DEFAULT: u32 = 2;
6+
7+
/// Extension trait for [`AppAuthenticatedData`] that provides hashing and verification functionality.
8+
///
9+
/// This trait adds methods to hash and verify the authenticity of [`AppAuthenticatedData`]
10+
/// instances using BLAKE3 cryptographic hashing.
11+
pub trait AppAuthenticatedDataExt {
12+
/// Returns `true` if `hash` is a BLAKE3 hash of this [`AppAuthenticatedData`].
13+
///
14+
/// This method calculates its own hash of the same length as the input
15+
/// `hash` and checks if both hashes are identical.
16+
fn verify(&self, hash: impl AsRef<[u8]>) -> bool;
17+
18+
/// Calculates a BLAKE3 hash of the length `n`.
19+
fn hash(&self, n: usize) -> Vec<u8>;
20+
21+
/// Updates the provided BLAKE3 hasher with all fields of this [`AppAuthenticatedData`].
22+
///
23+
/// This method must hash every field in the struct to ensure complete validation.
24+
fn hasher_update(&self, hasher: &mut Hasher);
25+
}
26+
27+
// Implement the trait for AppAuthenticatedData
28+
impl AppAuthenticatedDataExt for AppAuthenticatedData {
29+
fn verify(&self, hash: impl AsRef<[u8]>) -> bool {
30+
let external_hash = hash.as_ref();
31+
let internal_hash = self.hash(external_hash.len());
32+
external_hash == internal_hash
33+
}
34+
35+
fn hash(&self, n: usize) -> Vec<u8> {
36+
let mut hasher = Hasher::new();
37+
self.hasher_update(&mut hasher);
38+
let mut output = vec![0; n];
39+
hasher.finalize_xof().fill(&mut output);
40+
output
41+
}
42+
43+
fn hasher_update(&self, hasher: &mut Hasher) {
44+
let Self {
45+
identity_commitment,
46+
self_custody_public_key,
47+
pcp_version,
48+
os_version,
49+
os,
50+
} = self;
51+
hasher.update(identity_commitment.as_bytes());
52+
hasher.update(self_custody_public_key.as_bytes());
53+
hasher.update(os_version.as_bytes());
54+
hasher.update(os.as_bytes());
55+
if *pcp_version != PCP_VERSION_DEFAULT {
56+
hasher.update(&pcp_version.to_le_bytes());
57+
}
58+
}
59+
}

qr-link/src/decode.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use data_encoding::BASE64_NOPAD;
22
use thiserror::Error;
33
use uuid::Uuid;
44

5-
/// QR-code decoding error returned by [`decode_qr`].
5+
/// QR-code decoding error returned by [`decode_qr_with_version`].
66
#[derive(Error, Debug)]
77
pub enum DecodeError {
88
/// Format version is unsupported.
@@ -16,17 +16,27 @@ pub enum DecodeError {
1616
Base64,
1717
}
1818

19-
/// Parses `session_id` and `user_data_hash` from a QR-code string.
20-
pub fn decode_qr(qr: &str) -> Result<(Uuid, Vec<u8>), DecodeError> {
19+
/// Parses `user_id` and `user_data_hash` from a QR-code string and return the version
20+
/// different logic on the orb can be done based on the version returned
21+
pub fn decode_qr_with_version(qr: &str) -> Result<(u8, Uuid, Vec<u8>), DecodeError> {
2122
let Some(version) = qr.bytes().next() else {
2223
return Err(DecodeError::Malformed);
2324
};
2425
match version {
25-
b'3' => decode_v3(qr),
26+
b'3' => {
27+
let (session_id, user_data_hash) = decode_v3(qr)?;
28+
Ok((3, session_id, user_data_hash))
29+
}
30+
b'4' => {
31+
let (orb_relay_id, app_authenticated_data_hash) = decode_v4(qr)?;
32+
Ok((4, orb_relay_id, app_authenticated_data_hash))
33+
}
2634
_ => Err(DecodeError::UnsupportedVersion),
2735
}
2836
}
2937

38+
// the `decode_v3` method is specifically to decode user sessions where the id is the `session_id` and the hash is the hash from `UserData`
39+
// can be deprecated once all apps have moved to V3
3040
fn decode_v3(qr: &str) -> Result<(Uuid, Vec<u8>), DecodeError> {
3141
let Ok(payload) = BASE64_NOPAD.decode(&qr.as_bytes()[1..]) else {
3242
return Err(DecodeError::Base64);
@@ -41,3 +51,19 @@ fn decode_v3(qr: &str) -> Result<(Uuid, Vec<u8>), DecodeError> {
4151
let session_id = Uuid::from_u128(session_id);
4252
Ok((session_id, user_data_hash.to_vec()))
4353
}
54+
55+
// the `decode_v4` method is specifically to decode static sessions where the id is the `orb_relay_id` and the hash is the hash from `AppAuthenticatedData`
56+
fn decode_v4(qr: &str) -> Result<(Uuid, Vec<u8>), DecodeError> {
57+
let Ok(payload) = BASE64_NOPAD.decode(&qr.as_bytes()[1..]) else {
58+
return Err(DecodeError::Base64);
59+
};
60+
let Some(orb_relay_id) = payload.get(0..16) else {
61+
return Err(DecodeError::Malformed);
62+
};
63+
let Some(app_authenticated_data_hash) = payload.get(16..) else {
64+
return Err(DecodeError::Malformed);
65+
};
66+
let orb_relay_id = u128::from_be_bytes(orb_relay_id.try_into().unwrap());
67+
let orb_relay_id = Uuid::from_u128(orb_relay_id);
68+
Ok((orb_relay_id, app_authenticated_data_hash.to_vec()))
69+
}

qr-link/src/encode.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use data_encoding::BASE64_NOPAD;
22
use uuid::Uuid;
33

4-
/// QR-code version prefix.
5-
pub const QR_VERSION: u8 = b'3';
4+
pub const QR_VERSION: u8 = b'4';
65

7-
/// Generates a QR-code string from `session_id` and `user_data_hash`.
8-
pub fn encode_qr(session_id: &Uuid, user_data_hash: impl AsRef<[u8]>) -> String {
6+
/// Generates a QR-code (V4) string from `orb relay id` and `app_authenticated_data`
7+
pub fn encode_static_qr(
8+
orb_relay_id: &Uuid,
9+
authenticated_data_hash: impl AsRef<[u8]>,
10+
) -> String {
911
let mut payload = Vec::new();
10-
payload.extend_from_slice(&session_id.as_u128().to_be_bytes());
11-
payload.extend_from_slice(user_data_hash.as_ref());
12+
payload.extend_from_slice(&orb_relay_id.as_u128().to_be_bytes());
13+
payload.extend_from_slice(authenticated_data_hash.as_ref());
1214

1315
let mut qr = String::new();
1416
qr.push(QR_VERSION.into());

qr-link/src/lib.rs

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,83 +10,73 @@
1010
//! backend for performance, and transfer a hash of the data via a QR-code for
1111
//! security.
1212
//!
13+
//! //! The library is shared between Worldcoin App and Orb Core.
1314
//! # Usage
14-
//!
15-
//! The library is shared between Worldcoin App and Orb Core.
16-
//!
1715
//! ## Encode (App)
1816
//!
19-
//! Worldcoin App uploads user data and generates a QR-code.
17+
//! Worldcoin App generates a QR-code using the orb relay ID and includes a hash of the app authenticated data that will be sent in the orb relay message.
2018
//!
2119
//! ```rust
22-
//! use orb_qr_link::{encode_qr, DataPolicy, UserData};
20+
//! use orb_qr_link::{encode_static_qr, AppAuthenticatedDataExt};
21+
//! use orb_relay_messages::common::v1::AppAuthenticatedData;
2322
//! use uuid::Uuid;
2423
//!
2524
//! // Generate a new session id and user data.
26-
//! let session_id = Uuid::new_v4();
27-
//! let sample_jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
28-
//! let user_data = UserData {
29-
//! identity_commitment: String::new(),
30-
//! self_custody_public_key: String::new(),
31-
//! data_policy: DataPolicy::OptOut,
32-
//! pcp_version: 2,
33-
//! user_centric_signup: true,
34-
//! orb_relay_app_id: Some("123123".to_string()),
35-
//! bypass_age_verification_token: Some(sample_jwt_token.to_string()),
25+
//! let orb_relay_id = Uuid::new_v4();
26+
//! let app_data = AppAuthenticatedData {
27+
//! identity_commitment: identity_commitment.to_string(),
28+
//! self_custody_public_key: self_custody_public_key.to_string(),
29+
//! pcp_version: 3,
30+
//! os: "Android".to_string(),
31+
//! os_version: "1.2.3".to_string(),
3632
//! };
3733
//!
38-
//! // Upload `user_data` to the backend by the `session_id` key.
39-
//! // ...
40-
//!
41-
//! // Calculate a variable-length hash of `user_data`.
42-
//! let user_data_hash = user_data.hash(16);
34+
//! // Calculate a variable-length hash of `app_data`.
35+
//! let app_data_hash = app_data.hash(16);
4336
//! // Encode a new QR-code.
44-
//! let qr = encode_qr(&session_id, user_data_hash);
37+
//! let qr = encode_static_qr(&orb_relay_id, app_data_hash);
4538
//!
4639
//! // Allow the Orb to scan the generated QR-code.
4740
//! // ...
4841
//! ```
4942
//!
5043
//! ## Decode (Orb)
5144
//!
52-
//! The Orb scans a QR-code and downloads the user data.
45+
//! The Orb scans a QR-code
5346
//!
5447
//! ```rust
55-
//! use orb_qr_link::{decode_qr, DataPolicy, UserData};
48+
//! use orb_qr_link::{decode_qr_with_version};
5649
//!
5750
//! // Scan QR-code generated by the App.
5851
//! let qr = "3WVd+tbAtSgyH0Ce9uiKT9i063t/xG2HxTIhuNa+gNnM";
5952
//!
6053
//! // Decode the QR-code string.
61-
//! let (session_id, user_data_hash) = decode_qr(qr).unwrap();
62-
//!
63-
//! // Download `user_data` from the backend by the `session_id` key.
64-
//! let user_data = UserData {
65-
//! identity_commitment: String::new(),
66-
//! self_custody_public_key: String::new(),
67-
//! data_policy: DataPolicy::OptOut,
68-
//! pcp_version: 2,
69-
//! user_centric_signup: true,
70-
//! orb_relay_app_id: Some("123123".to_string()),
71-
//! bypass_age_verification_token: None,
72-
//! };
54+
//! let (version, orb_relay_id, app_data_hash) = decode_qr_with_version(qr).unwrap();
55+
//!
56+
//! // connects to the app via orb relay using the orb_relay_id
57+
//! // if version is V4 then orb can do the logic for static sessions
58+
//!
59+
//! // Retrieves the app data from the AnnounceAppId Orb Relay Message
60+
//! let app_data = orb_relay_announce_id_message.app_authenticated_data;
7361
//!
74-
//! // Verify that the `user_data_hash` from the QR-code matches `user_data`
62+
//! // Verify that the `app_data_hash` from the QR-code matches `app_data`
7563
//! // from the backend.
76-
//! let success = user_data.verify(user_data_hash);
64+
//! let success = app_data.verify(app_data_hash);
7765
//! ```
7866
7967
#![forbid(unsafe_code)]
8068
#![warn(missing_docs)]
8169

70+
mod app_authenticated_data;
8271
#[cfg(feature = "decode")]
8372
mod decode;
8473
#[cfg(feature = "encode")]
8574
mod encode;
8675
mod user_data;
8776

77+
pub use app_authenticated_data::AppAuthenticatedDataExt;
8878
#[cfg(feature = "decode")]
89-
pub use decode::{decode_qr, DecodeError};
79+
pub use decode::{decode_qr_with_version, DecodeError};
9080
#[cfg(feature = "encode")]
91-
pub use encode::encode_qr;
81+
pub use encode::encode_static_qr;
9282
pub use user_data::{DataPolicy, UserData};

0 commit comments

Comments
 (0)