@@ -87,7 +87,7 @@ pub fn domain_verification_token(
8787 hasher. update ( instance. id . as_ref ( ) ) ;
8888 hasher. update ( domain. as_bytes ( ) ) ;
8989
90- let mut output = Vec :: new ( ) ;
90+ let mut output = [ 0u8 ; 32 ] ; // TupleHash::v256 → 256-bit token (matches keymanager.rs).
9191 hasher. finalize ( & mut output) ;
9292 BASE64_STANDARD . encode ( output)
9393}
@@ -113,4 +113,179 @@ mod test {
113113 let dec = cbor:: from_slice ( & data) . unwrap ( ) ;
114114 assert_eq ! ( permissions, dec) ;
115115 }
116+
117+ mod domain_verification_token {
118+ use base64:: prelude:: * ;
119+ use tiny_keccak:: { Hasher , TupleHash } ;
120+
121+ use oasis_runtime_sdk:: {
122+ modules:: rofl:: app_id:: AppId , testing:: keys, types:: address:: Address ,
123+ } ;
124+ use oasis_runtime_sdk_rofl_market:: types:: { Deployment , Instance } ;
125+
126+ use super :: super :: { domain_verification_token, DOMAIN_VERIFICATION_TOKEN_CONTEXT } ;
127+
128+ fn instance ( provider : Address , id : u64 ) -> Instance {
129+ Instance {
130+ provider,
131+ id : id. into ( ) ,
132+ ..Default :: default ( )
133+ }
134+ }
135+
136+ fn deployment ( app : & str ) -> Deployment {
137+ Deployment {
138+ app_id : AppId :: from_global_name ( app) ,
139+ ..Default :: default ( )
140+ }
141+ }
142+
143+ /// Re-derive the token straight from the documented spec, independently of the function
144+ /// under test. If the function were a stub/constant, this would diverge.
145+ fn independent ( inst : & Instance , dep : & Deployment , domain : & str ) -> String {
146+ let mut h = TupleHash :: v256 ( DOMAIN_VERIFICATION_TOKEN_CONTEXT ) ;
147+ h. update ( dep. app_id . as_ref ( ) ) ;
148+ h. update ( inst. provider . as_ref ( ) ) ;
149+ h. update ( inst. id . as_ref ( ) ) ;
150+ h. update ( domain. as_bytes ( ) ) ;
151+ let mut out = [ 0u8 ; 32 ] ;
152+ h. finalize ( & mut out) ;
153+ BASE64_STANDARD . encode ( out)
154+ }
155+
156+ #[ test]
157+ fn not_empty_and_correct_shape ( ) {
158+ let inst = instance ( keys:: alice:: address ( ) , 1 ) ;
159+ let dep = deployment ( "app1" ) ;
160+ let t = domain_verification_token ( & inst, & dep, "example.com" ) ;
161+
162+ assert ! (
163+ !t. is_empty( ) ,
164+ "token must not be empty (regression for the empty-buffer bug)"
165+ ) ;
166+ assert_eq ! ( t. len( ) , 44 , "base64 of 32 bytes is 44 chars" ) ;
167+
168+ let raw = BASE64_STANDARD
169+ . decode ( & t)
170+ . expect ( "token must be valid base64" ) ;
171+ assert_eq ! ( raw. len( ) , 32 , "token must decode to a 256-bit digest" ) ;
172+ assert_ne ! ( raw, [ 0u8 ; 32 ] , "token must not be all zeroes" ) ;
173+ }
174+
175+ #[ test]
176+ fn matches_independent_derivation_not_a_stub ( ) {
177+ // Cross-check the real function against an independent TupleHash computation.
178+ let cases = [
179+ (
180+ instance ( keys:: alice:: address ( ) , 1 ) ,
181+ deployment ( "app1" ) ,
182+ "example.com" ,
183+ ) ,
184+ (
185+ instance ( keys:: bob:: address ( ) , 42 ) ,
186+ deployment ( "other" ) ,
187+ "sub.example.org" ,
188+ ) ,
189+ (
190+ instance ( keys:: dave:: address ( ) , u64:: MAX ) ,
191+ deployment ( "x" ) ,
192+ "" ,
193+ ) ,
194+ ] ;
195+ for ( inst, dep, domain) in & cases {
196+ assert_eq ! (
197+ domain_verification_token( inst, dep, domain) ,
198+ independent( inst, dep, domain) ,
199+ "function output must equal the documented TupleHash derivation" ,
200+ ) ;
201+ }
202+ }
203+
204+ #[ test]
205+ fn deterministic ( ) {
206+ let inst = instance ( keys:: alice:: address ( ) , 7 ) ;
207+ let dep = deployment ( "app1" ) ;
208+ assert_eq ! (
209+ domain_verification_token( & inst, & dep, "example.com" ) ,
210+ domain_verification_token( & inst, & dep, "example.com" ) ,
211+ "same inputs must yield the same token" ,
212+ ) ;
213+ }
214+
215+ #[ test]
216+ fn bound_to_domain ( ) {
217+ let inst = instance ( keys:: alice:: address ( ) , 1 ) ;
218+ let dep = deployment ( "app1" ) ;
219+ assert_ne ! (
220+ domain_verification_token( & inst, & dep, "a.example.com" ) ,
221+ domain_verification_token( & inst, & dep, "b.example.com" ) ,
222+ ) ;
223+ }
224+
225+ #[ test]
226+ fn bound_to_instance_id ( ) {
227+ let dep = deployment ( "app1" ) ;
228+ assert_ne ! (
229+ domain_verification_token(
230+ & instance( keys:: alice:: address( ) , 1 ) ,
231+ & dep,
232+ "example.com"
233+ ) ,
234+ domain_verification_token(
235+ & instance( keys:: alice:: address( ) , 2 ) ,
236+ & dep,
237+ "example.com"
238+ ) ,
239+ ) ;
240+ }
241+
242+ #[ test]
243+ fn bound_to_provider ( ) {
244+ let dep = deployment ( "app1" ) ;
245+ assert_ne ! (
246+ domain_verification_token(
247+ & instance( keys:: alice:: address( ) , 1 ) ,
248+ & dep,
249+ "example.com"
250+ ) ,
251+ domain_verification_token( & instance( keys:: bob:: address( ) , 1 ) , & dep, "example.com" ) ,
252+ ) ;
253+ }
254+
255+ #[ test]
256+ fn bound_to_app_id ( ) {
257+ let inst = instance ( keys:: alice:: address ( ) , 1 ) ;
258+ assert_ne ! (
259+ domain_verification_token( & inst, & deployment( "app1" ) , "example.com" ) ,
260+ domain_verification_token( & inst, & deployment( "app2" ) , "example.com" ) ,
261+ ) ;
262+ }
263+
264+ #[ test]
265+ fn no_prefix_or_substring_collisions ( ) {
266+ // The hashed domain must be treated atomically: similar/related domain strings,
267+ // including a trailing-dot and sub/parent relationships, must all differ.
268+ let inst = instance ( keys:: alice:: address ( ) , 1 ) ;
269+ let dep = deployment ( "app1" ) ;
270+ let domains = [
271+ "example.com" ,
272+ "example.com." ,
273+ "sub.example.com" ,
274+ "example.co" ,
275+ "xexample.com" ,
276+ "" ,
277+ ] ;
278+ let mut tokens: Vec < String > = domains
279+ . iter ( )
280+ . map ( |d| domain_verification_token ( & inst, & dep, d) )
281+ . collect ( ) ;
282+ tokens. sort ( ) ;
283+ tokens. dedup ( ) ;
284+ assert_eq ! (
285+ tokens. len( ) ,
286+ domains. len( ) ,
287+ "all related domains must yield distinct tokens"
288+ ) ;
289+ }
290+ }
116291}
0 commit comments