Skip to content

Commit e25df36

Browse files
committed
derive non-empty domain verification token
1 parent 3e00a16 commit e25df36

1 file changed

Lines changed: 176 additions & 1 deletion

File tree

rofl-scheduler/src/types.rs

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)