oasis_runtime_sdk/crypto/signature/
context.rs

1//! Domain separation context helpers.
2use std::sync::Mutex;
3
4use once_cell::sync::Lazy;
5
6use oasis_core_runtime::common::{crypto::hash::Hash, namespace::Namespace};
7
8const CHAIN_CONTEXT_SEPARATOR: &[u8] = b" for chain ";
9
10static CHAIN_CONTEXT: Lazy<Mutex<Option<Vec<u8>>>> = Lazy::new(Default::default);
11
12/// Return the globally configured chain domain separation context.
13///
14/// The returned domain separation context is computed as:
15///
16/// ```plain
17/// <base> || " for chain " || <chain-context>
18/// ```
19///
20/// # Panics
21///
22/// This function will panic in case the global chain domain separation context was not previously
23/// set using `set_chain_context`.
24///
25pub fn get_chain_context_for(base: &[u8]) -> Vec<u8> {
26    let guard = CHAIN_CONTEXT.lock().unwrap();
27    let chain_context = match guard.as_ref() {
28        Some(cc) => cc,
29        None => {
30            drop(guard); // Avoid poisioning the global lock.
31            panic!("chain domain separation context must be configured");
32        }
33    };
34
35    let mut ctx = vec![0; base.len() + CHAIN_CONTEXT_SEPARATOR.len() + chain_context.len()];
36    ctx[..base.len()].copy_from_slice(base);
37    ctx[base.len()..base.len() + CHAIN_CONTEXT_SEPARATOR.len()]
38        .copy_from_slice(CHAIN_CONTEXT_SEPARATOR);
39    ctx[base.len() + CHAIN_CONTEXT_SEPARATOR.len()..].copy_from_slice(chain_context);
40    ctx
41}
42
43/// Configure the global chain domain separation context.
44///
45/// The domain separation context is computed as:
46///
47/// ```plain
48/// Base-16(H(<runtime-id> || <consensus-chain-context>))
49/// ```
50///
51/// # Panics
52///
53/// This function will panic in case the global chain domain separation context was already set.
54///
55pub fn set_chain_context(runtime_id: Namespace, consensus_chain_context: &str) {
56    let ctx = hex::encode(Hash::digest_bytes_list(&[
57        runtime_id.as_ref(),
58        consensus_chain_context.as_bytes(),
59    ]));
60    let mut guard = CHAIN_CONTEXT.lock().unwrap();
61    if let Some(ref existing) = *guard {
62        if cfg!(any(test, feature = "test")) && existing == ctx.as_bytes() {
63            return;
64        }
65        let ex = String::from_utf8(existing.clone()).unwrap();
66        drop(guard); // Avoid poisioning the global lock.
67        panic!("chain domain separation context already set: {ex}");
68    }
69    *guard = Some(ctx.into_bytes());
70}
71
72/// Test helper to serialize unit tests using the global chain context. The chain context is reset
73/// when this method is called.
74///
75/// # Example
76///
77/// ```rust
78/// # use oasis_runtime_sdk::crypto::signature::context::test_using_chain_context;
79/// let _guard = test_using_chain_context();
80/// // ... rest of the test code follows ...
81/// ```
82#[cfg(any(test, feature = "test"))]
83pub fn test_using_chain_context() -> std::sync::MutexGuard<'static, ()> {
84    static TEST_USING_CHAIN_CONTEXT: Lazy<Mutex<()>> = Lazy::new(Default::default);
85    let guard = TEST_USING_CHAIN_CONTEXT.lock().unwrap();
86    *CHAIN_CONTEXT.lock().unwrap() = None;
87
88    guard
89}
90
91#[cfg(test)]
92mod test {
93    use super::*;
94
95    #[test]
96    fn test_chain_context() {
97        let _guard = test_using_chain_context();
98        set_chain_context(
99            "8000000000000000000000000000000000000000000000000000000000000000".into(),
100            "643fb06848be7e970af3b5b2d772eb8cfb30499c8162bc18ac03df2f5e22520e",
101        );
102
103        let ctx = get_chain_context_for(b"oasis-runtime-sdk/tx: v0");
104        assert_eq!(&String::from_utf8(ctx).unwrap(), "oasis-runtime-sdk/tx: v0 for chain ca4842870b97a6d5c0d025adce0b6a0dec94d2ba192ede70f96349cfbe3628b9");
105    }
106
107    #[test]
108    fn test_chain_context_not_configured() {
109        let _guard = test_using_chain_context();
110
111        let result = std::panic::catch_unwind(|| get_chain_context_for(b"test"));
112        assert!(result.is_err());
113    }
114
115    #[test]
116    fn test_chain_context_already_configured() {
117        let _guard = test_using_chain_context();
118        set_chain_context(
119            "8000000000000000000000000000000000000000000000000000000000000000".into(),
120            "643fb06848be7e970af3b5b2d772eb8cfb30499c8162bc18ac03df2f5e22520e",
121        );
122
123        let result = std::panic::catch_unwind(|| {
124            set_chain_context(
125                "8000000000000000000000000000000000000000000000000000000000000001".into(),
126                "643fb06848be7e970af3b5b2d772eb8cfb30499c8162bc18ac03df2f5e22520e",
127            )
128        });
129        assert!(result.is_err());
130    }
131}