11//! Fingerprint generation from browser signals.
22
3+ use blake3:: Hasher ;
34use scrybe_core:: {
45 types:: { Fingerprint , FingerprintComponents , Session } ,
56 ScrybeError ,
@@ -12,22 +13,160 @@ pub struct FingerprintGenerator;
1213impl FingerprintGenerator {
1314 /// Generate a fingerprint from a session.
1415 ///
15- /// This creates a deterministic SHA-256 hash of all browser signals.
16+ /// This creates a deterministic composite hash of all browser signals.
17+ /// Uses SHA-256 for the main hash and BLAKE3 for component hashes.
1618 ///
1719 /// # Errors
1820 ///
1921 /// Returns `ScrybeError::EnrichmentError` if fingerprint generation fails.
20- pub fn generate ( _session : & Session ) -> Result < Fingerprint , ScrybeError > {
21- // TODO: Implement actual fingerprinting logic
22- // For now, return a placeholder
22+ pub fn generate ( session : & Session ) -> Result < Fingerprint , ScrybeError > {
23+ // Generate component hashes
24+ let components = FingerprintComponents {
25+ canvas : session. browser . canvas_hash . clone ( ) ,
26+ webgl : session. browser . webgl_hash . clone ( ) ,
27+ audio : session. browser . audio_hash . clone ( ) ,
28+ fonts : Some ( Self :: hash_fonts ( & session. browser . fonts ) ) ,
29+ plugins : Some ( Self :: hash_plugins ( & session. browser . plugins ) ) ,
30+ screen : Some ( Self :: hash_screen ( & session. browser . screen ) ) ,
31+ network : Some ( Self :: hash_network ( & session. network ) ) ,
32+ } ;
2333
24- let mut hasher = Sha256 :: new ( ) ;
25- hasher. update ( b"placeholder" ) ;
26- let hash = format ! ( "{:x}" , hasher. finalize( ) ) ;
34+ // Generate composite hash from all components
35+ let composite_hash = Self :: generate_composite_hash ( & components) ;
36+
37+ // Calculate confidence score based on available signals
38+ let confidence = Self :: calculate_confidence ( & components) ;
2739
28- Fingerprint :: new ( hash , FingerprintComponents :: default ( ) , 0.5 )
40+ Fingerprint :: new ( composite_hash , components , confidence as f64 )
2941 . ok_or_else ( || ScrybeError :: enrichment_error ( "fingerprint" , "invalid hash generated" ) )
3042 }
43+
44+ /// Generate composite hash from all fingerprint components.
45+ fn generate_composite_hash ( components : & FingerprintComponents ) -> String {
46+ let mut hasher = Sha256 :: new ( ) ;
47+
48+ if let Some ( ref canvas) = components. canvas {
49+ hasher. update ( canvas. as_bytes ( ) ) ;
50+ }
51+ if let Some ( ref webgl) = components. webgl {
52+ hasher. update ( webgl. as_bytes ( ) ) ;
53+ }
54+ if let Some ( ref audio) = components. audio {
55+ hasher. update ( audio. as_bytes ( ) ) ;
56+ }
57+ if let Some ( ref fonts) = components. fonts {
58+ hasher. update ( fonts. as_bytes ( ) ) ;
59+ }
60+ if let Some ( ref plugins) = components. plugins {
61+ hasher. update ( plugins. as_bytes ( ) ) ;
62+ }
63+ if let Some ( ref screen) = components. screen {
64+ hasher. update ( screen. as_bytes ( ) ) ;
65+ }
66+ if let Some ( ref network) = components. network {
67+ hasher. update ( network. as_bytes ( ) ) ;
68+ }
69+
70+ format ! ( "{:x}" , hasher. finalize( ) )
71+ }
72+
73+ /// Hash font list using BLAKE3.
74+ fn hash_fonts ( fonts : & [ String ] ) -> String {
75+ let mut hasher = Hasher :: new ( ) ;
76+ for font in fonts {
77+ hasher. update ( font. as_bytes ( ) ) ;
78+ }
79+ hasher. finalize ( ) . to_hex ( ) . to_string ( )
80+ }
81+
82+ /// Hash plugin list using BLAKE3.
83+ fn hash_plugins ( plugins : & [ String ] ) -> String {
84+ let mut hasher = Hasher :: new ( ) ;
85+ for plugin in plugins {
86+ hasher. update ( plugin. as_bytes ( ) ) ;
87+ }
88+ hasher. finalize ( ) . to_hex ( ) . to_string ( )
89+ }
90+
91+ /// Hash screen info using BLAKE3.
92+ fn hash_screen ( screen : & scrybe_core:: types:: ScreenInfo ) -> String {
93+ let mut hasher = Hasher :: new ( ) ;
94+ hasher. update ( & screen. width . to_le_bytes ( ) ) ;
95+ hasher. update ( & screen. height . to_le_bytes ( ) ) ;
96+ hasher. update ( & screen. color_depth . to_le_bytes ( ) ) ;
97+ hasher. update ( & screen. pixel_ratio . to_le_bytes ( ) ) ;
98+ hasher. finalize ( ) . to_hex ( ) . to_string ( )
99+ }
100+
101+ /// Hash network signals using BLAKE3.
102+ fn hash_network ( network : & scrybe_core:: types:: NetworkSignals ) -> String {
103+ let mut hasher = Hasher :: new ( ) ;
104+ hasher. update ( network. ip . to_string ( ) . as_bytes ( ) ) ;
105+ if let Some ( ref ja3) = network. ja3 {
106+ hasher. update ( ja3. as_bytes ( ) ) ;
107+ }
108+ if let Some ( ref ja4) = network. ja4 {
109+ hasher. update ( ja4. as_bytes ( ) ) ;
110+ }
111+ hasher. finalize ( ) . to_hex ( ) . to_string ( )
112+ }
113+
114+ /// Calculate confidence score based on available signals.
115+ ///
116+ /// Score ranges from 0.0 (low confidence) to 1.0 (high confidence).
117+ fn calculate_confidence ( components : & FingerprintComponents ) -> f32 {
118+ let mut signal_count = 0 ;
119+ let mut total_weight = 0.0 ;
120+
121+ // Canvas fingerprint (weight: 0.25)
122+ if components. canvas . is_some ( ) {
123+ signal_count += 1 ;
124+ total_weight += 0.25 ;
125+ }
126+
127+ // WebGL fingerprint (weight: 0.25)
128+ if components. webgl . is_some ( ) {
129+ signal_count += 1 ;
130+ total_weight += 0.25 ;
131+ }
132+
133+ // Audio fingerprint (weight: 0.15)
134+ if components. audio . is_some ( ) {
135+ signal_count += 1 ;
136+ total_weight += 0.15 ;
137+ }
138+
139+ // Fonts (weight: 0.15)
140+ if components. fonts . is_some ( ) {
141+ signal_count += 1 ;
142+ total_weight += 0.15 ;
143+ }
144+
145+ // Plugins (weight: 0.10)
146+ if components. plugins . is_some ( ) {
147+ signal_count += 1 ;
148+ total_weight += 0.10 ;
149+ }
150+
151+ // Screen (weight: 0.05)
152+ if components. screen . is_some ( ) {
153+ signal_count += 1 ;
154+ total_weight += 0.05 ;
155+ }
156+
157+ // Network (weight: 0.05)
158+ if components. network . is_some ( ) {
159+ signal_count += 1 ;
160+ total_weight += 0.05 ;
161+ }
162+
163+ // Normalize to 0.0-1.0 range
164+ if signal_count == 0 {
165+ 0.0
166+ } else {
167+ total_weight
168+ }
169+ }
31170}
32171
33172#[ cfg( test) ]
0 commit comments