Skip to main content

vss_client_ng/headers/
sigs_auth.rs

1//! Provides the [`SigsAuthProvider`].
2
3use crate::headers::{VssHeaderProvider, VssHeaderProviderError};
4use async_trait::async_trait;
5use bitcoin::hashes::sha256::Hash as Sha256;
6use bitcoin::hashes::Hash as _;
7use bitcoin::secp256k1::{Message, Secp256k1, SecretKey, SignOnly};
8use std::collections::HashMap;
9use std::fmt::Write as _;
10use std::io::Write as _;
11use std::time::SystemTime;
12
13/// A 64-byte constant which, after appending the public key, is signed in order to prove knowledge
14/// of the corresponding private key.
15pub const SIGNING_CONSTANT: &'static [u8] =
16	b"VSS Signature Authorizer Signing Salt Constant..................";
17
18fn build_token(secret_key: &SecretKey, secp_ctx: &Secp256k1<SignOnly>) -> String {
19	let pubkey = secret_key.public_key(secp_ctx);
20	let old_time = "System time must be at least Jan 1, 1970";
21	let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect(old_time).as_secs();
22
23	// 2^64 serialized as a string is 20 bytes.
24	let mut buffer = [0u8; SIGNING_CONSTANT.len() + 33 + 20];
25	let mut stream = &mut buffer[..];
26	stream.write_all(SIGNING_CONSTANT).unwrap();
27	stream.write_all(&pubkey.serialize()).unwrap();
28	write!(stream, "{now}").unwrap();
29	let bytes_remaining = stream.len();
30	let bytes_to_sign = &buffer[..buffer.len() - bytes_remaining];
31
32	let hash = Sha256::hash(&bytes_to_sign);
33	let sig = secp_ctx.sign_ecdsa(&Message::from_digest(hash.to_byte_array()), secret_key);
34	let mut out = String::with_capacity((33 + 64 + 20) * 2);
35	write!(&mut out, "{pubkey:x}").unwrap();
36	for c in sig.serialize_compact() {
37		write!(&mut out, "{:02x}", c).unwrap();
38	}
39	write!(&mut out, "{now}").unwrap();
40	out
41}
42
43/// A simple auth provider which simply proves knowledge of a private key.
44///
45/// It provides a good default authentication mechanism for testing, or in the case that
46/// denial-of-service protection against new-account-flooding is mitigated at another layer
47/// (e.g. via Apple DeviceCheck or similar remote attestation technologies).
48pub struct SigsAuthProvider {
49	key: SecretKey,
50	secp_ctx: Secp256k1<SignOnly>,
51	default_headers: HashMap<String, String>,
52}
53
54impl SigsAuthProvider {
55	/// Creates a new auth provider which simply proves knowledge of a private key.
56	///
57	/// This provides an incredibly simple authentication scheme and allows the server to ensure
58	/// data for separate clients is kept separate, without any application-specific logic.
59	///
60	/// In addition to the automatically-added `Authorization` header, any headers provided in
61	/// `default_headers` (except an `Authorization` header) will be added to the headers list.
62	pub fn new(key: SecretKey, default_headers: HashMap<String, String>) -> Self {
63		SigsAuthProvider { secp_ctx: Secp256k1::signing_only(), key, default_headers }
64	}
65}
66
67#[async_trait]
68impl VssHeaderProvider for SigsAuthProvider {
69	async fn get_headers(
70		&self, _request: &[u8],
71	) -> Result<HashMap<String, String>, VssHeaderProviderError> {
72		// TODO: We might consider not re-signing on every request, but its cheap enough that it
73		// doesn't really matter
74		let mut headers = self.default_headers.clone();
75		headers.insert("Authorization".to_owned(), build_token(&self.key, &self.secp_ctx));
76		Ok(headers)
77	}
78}