• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

payjoin / rust-payjoin / 14646658670

24 Apr 2025 04:26PM UTC coverage: 81.905% (+0.07%) from 81.835%
14646658670

Pull #667

github

web-flow
Merge c9850f1a2 into 3b23dcaf5
Pull Request #667: Fix uninline format clippy violations

50 of 147 new or added lines in 30 files covered. (34.01%)

5 existing lines in 3 files now uncovered.

5323 of 6499 relevant lines covered (81.9%)

711.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.33
/payjoin/src/ohttp.rs
1
use std::ops::{Deref, DerefMut};
2
use std::{error, fmt};
3

4
use bitcoin::bech32::{self, EncodeError};
5
use bitcoin::key::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE;
6

7
use crate::directory::ENCAPSULATED_MESSAGE_BYTES;
8

9
const N_ENC: usize = UNCOMPRESSED_PUBLIC_KEY_SIZE;
10
const N_T: usize = crate::hpke::POLY1305_TAG_SIZE;
11
const OHTTP_REQ_HEADER_BYTES: usize = 7;
12
pub const PADDED_BHTTP_REQ_BYTES: usize =
13
    ENCAPSULATED_MESSAGE_BYTES - (N_ENC + N_T + OHTTP_REQ_HEADER_BYTES);
14

15
pub fn ohttp_encapsulate(
43✔
16
    ohttp_keys: &mut ohttp::KeyConfig,
43✔
17
    method: &str,
43✔
18
    target_resource: &str,
43✔
19
    body: Option<&[u8]>,
43✔
20
) -> Result<([u8; ENCAPSULATED_MESSAGE_BYTES], ohttp::ClientResponse), OhttpEncapsulationError> {
43✔
21
    use std::fmt::Write;
22

23
    let ctx = ohttp::ClientRequest::from_config(ohttp_keys)?;
43✔
24
    let url = url::Url::parse(target_resource)?;
43✔
25
    let authority_bytes = url.host().map_or_else(Vec::new, |host| {
43✔
26
        let mut authority = host.to_string();
43✔
27
        if let Some(port) = url.port() {
43✔
28
            write!(authority, ":{port}").unwrap();
41✔
29
        }
41✔
30
        authority.into_bytes()
43✔
31
    });
43✔
32
    let mut bhttp_message = bhttp::Message::request(
43✔
33
        method.as_bytes().to_vec(),
43✔
34
        url.scheme().as_bytes().to_vec(),
43✔
35
        authority_bytes,
43✔
36
        url.path().as_bytes().to_vec(),
43✔
37
    );
43✔
38
    // None of our messages include headers, so we don't add them
39
    if let Some(body) = body {
43✔
40
        bhttp_message.write_content(body);
29✔
41
    }
29✔
42

43
    let mut bhttp_req = [0u8; PADDED_BHTTP_REQ_BYTES];
43✔
44
    bhttp_message.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_req.as_mut_slice())?;
43✔
45
    let (encapsulated, ohttp_ctx) = ctx.encapsulate(&bhttp_req)?;
43✔
46

47
    let mut buffer = [0u8; ENCAPSULATED_MESSAGE_BYTES];
43✔
48
    let len = encapsulated.len().min(ENCAPSULATED_MESSAGE_BYTES);
43✔
49
    buffer[..len].copy_from_slice(&encapsulated[..len]);
43✔
50
    Ok((buffer, ohttp_ctx))
43✔
51
}
43✔
52

53
/// decapsulate ohttp, bhttp response and return http response body and status code
54
pub fn ohttp_decapsulate(
37✔
55
    res_ctx: ohttp::ClientResponse,
37✔
56
    ohttp_body: &[u8; ENCAPSULATED_MESSAGE_BYTES],
37✔
57
) -> Result<http::Response<Vec<u8>>, OhttpEncapsulationError> {
37✔
58
    let bhttp_body = res_ctx.decapsulate(ohttp_body)?;
37✔
59
    let mut r = std::io::Cursor::new(bhttp_body);
37✔
60
    let m: bhttp::Message = bhttp::Message::read_bhttp(&mut r)?;
37✔
61
    let mut builder = http::Response::builder();
37✔
62
    for field in m.header().iter() {
37✔
63
        builder = builder.header(field.name(), field.value());
×
64
    }
×
65
    builder
37✔
66
        .status(m.control().status().unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR.into()))
37✔
67
        .body(m.content().to_vec())
37✔
68
        .map_err(OhttpEncapsulationError::Http)
37✔
69
}
37✔
70

71
/// Error from de/encapsulating an Oblivious HTTP request or response.
72
#[derive(Debug)]
73
pub enum OhttpEncapsulationError {
74
    Http(http::Error),
75
    Ohttp(ohttp::Error),
76
    Bhttp(bhttp::Error),
77
    ParseUrl(url::ParseError),
78
}
79

80
impl From<http::Error> for OhttpEncapsulationError {
81
    fn from(value: http::Error) -> Self { Self::Http(value) }
×
82
}
83

84
impl From<ohttp::Error> for OhttpEncapsulationError {
85
    fn from(value: ohttp::Error) -> Self { Self::Ohttp(value) }
×
86
}
87

88
impl From<bhttp::Error> for OhttpEncapsulationError {
89
    fn from(value: bhttp::Error) -> Self { Self::Bhttp(value) }
×
90
}
91

92
impl From<url::ParseError> for OhttpEncapsulationError {
93
    fn from(value: url::ParseError) -> Self { Self::ParseUrl(value) }
×
94
}
95

96
impl fmt::Display for OhttpEncapsulationError {
97
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
98
        use OhttpEncapsulationError::*;
99

100
        match &self {
×
101
            Http(e) => e.fmt(f),
×
102
            Ohttp(e) => e.fmt(f),
×
103
            Bhttp(e) => e.fmt(f),
×
104
            ParseUrl(e) => e.fmt(f),
×
105
        }
106
    }
×
107
}
108

109
impl error::Error for OhttpEncapsulationError {
110
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
×
111
        use OhttpEncapsulationError::*;
112

113
        match &self {
×
114
            Http(e) => Some(e),
×
115
            Ohttp(e) => Some(e),
×
116
            Bhttp(e) => Some(e),
×
117
            ParseUrl(e) => Some(e),
×
118
        }
119
    }
×
120
}
121

122
#[derive(Debug, Clone)]
123
pub struct OhttpKeys(pub ohttp::KeyConfig);
124

125
impl OhttpKeys {
126
    /// Decode an OHTTP KeyConfig
127
    pub fn decode(bytes: &[u8]) -> Result<Self, ohttp::Error> {
11✔
128
        ohttp::KeyConfig::decode(bytes).map(Self)
11✔
129
    }
11✔
130
}
131

132
const KEM_ID: &[u8] = b"\x00\x16"; // DHKEM(secp256k1, HKDF-SHA256)
133
const SYMMETRIC_LEN: &[u8] = b"\x00\x04"; // 4 bytes
134
const SYMMETRIC_KDF_AEAD: &[u8] = b"\x00\x01\x00\x03"; // KDF(HKDF-SHA256), AEAD(ChaCha20Poly1305)
135

136
impl fmt::Display for OhttpKeys {
137
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20✔
138
        let bytes = self.encode().map_err(|_| fmt::Error)?;
20✔
139
        let key_id = bytes[0];
20✔
140
        let pubkey = &bytes[3..68];
20✔
141

142
        let compressed_pubkey =
20✔
143
            bitcoin::secp256k1::PublicKey::from_slice(pubkey).map_err(|_| fmt::Error)?.serialize();
20✔
144

20✔
145
        let mut buf = vec![key_id];
20✔
146
        buf.extend_from_slice(&compressed_pubkey);
20✔
147

20✔
148
        let oh_hrp: bech32::Hrp = bech32::Hrp::parse("OH").unwrap();
20✔
149

20✔
150
        crate::bech32::nochecksum::encode_to_fmt(f, oh_hrp, &buf).map_err(|e| match e {
20✔
151
            EncodeError::Fmt(e) => e,
×
152
            _ => fmt::Error,
×
153
        })
20✔
154
    }
20✔
155
}
156

157
impl TryFrom<&[u8]> for OhttpKeys {
158
    type Error = ParseOhttpKeysError;
159

160
    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
28✔
161
        let key_id = *bytes.first().ok_or(ParseOhttpKeysError::InvalidFormat)?;
28✔
162
        let compressed_pk = bytes.get(1..34).ok_or(ParseOhttpKeysError::InvalidFormat)?;
28✔
163

164
        let pubkey = bitcoin::secp256k1::PublicKey::from_slice(compressed_pk)
28✔
165
            .map_err(|_| ParseOhttpKeysError::InvalidPublicKey)?;
28✔
166

167
        let mut buf = vec![key_id];
28✔
168
        buf.extend_from_slice(KEM_ID);
28✔
169
        buf.extend_from_slice(&pubkey.serialize_uncompressed());
28✔
170
        buf.extend_from_slice(SYMMETRIC_LEN);
28✔
171
        buf.extend_from_slice(SYMMETRIC_KDF_AEAD);
28✔
172

28✔
173
        ohttp::KeyConfig::decode(&buf).map(Self).map_err(ParseOhttpKeysError::DecodeKeyConfig)
28✔
174
    }
28✔
175
}
176

177
impl std::str::FromStr for OhttpKeys {
178
    type Err = ParseOhttpKeysError;
179

180
    /// Parses a base64URL-encoded string into OhttpKeys.
181
    /// The string format is: key_id || compressed_public_key
182
    fn from_str(s: &str) -> Result<Self, Self::Err> {
29✔
183
        // TODO extract to utility function
29✔
184
        let oh_hrp: bech32::Hrp = bech32::Hrp::parse("OH").unwrap();
29✔
185

186
        let (hrp, bytes) =
28✔
187
            crate::bech32::nochecksum::decode(s).map_err(ParseOhttpKeysError::DecodeBech32)?;
29✔
188

189
        if hrp != oh_hrp {
28✔
190
            return Err(ParseOhttpKeysError::InvalidFormat);
×
191
        }
28✔
192

28✔
193
        Self::try_from(&bytes[..])
28✔
194
    }
29✔
195
}
196

197
impl PartialEq for OhttpKeys {
198
    fn eq(&self, other: &Self) -> bool {
2✔
199
        match (self.encode(), other.encode()) {
2✔
200
            (Ok(self_encoded), Ok(other_encoded)) => self_encoded == other_encoded,
2✔
201
            // If OhttpKeys::encode(&self) is Err, return false
202
            _ => false,
×
203
        }
204
    }
2✔
205
}
206

207
impl Eq for OhttpKeys {}
208

209
impl Deref for OhttpKeys {
210
    type Target = ohttp::KeyConfig;
211

212
    fn deref(&self) -> &Self::Target { &self.0 }
30✔
213
}
214

215
impl DerefMut for OhttpKeys {
216
    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
43✔
217
}
218

219
impl<'de> serde::Deserialize<'de> for OhttpKeys {
220
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3✔
221
    where
3✔
222
        D: serde::Deserializer<'de>,
3✔
223
    {
3✔
224
        let bytes = Vec::<u8>::deserialize(deserializer)?;
3✔
225
        OhttpKeys::decode(&bytes).map_err(serde::de::Error::custom)
3✔
226
    }
3✔
227
}
228

229
impl serde::Serialize for OhttpKeys {
230
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2✔
231
    where
2✔
232
        S: serde::Serializer,
2✔
233
    {
2✔
234
        let bytes = self.encode().map_err(serde::ser::Error::custom)?;
2✔
235
        bytes.serialize(serializer)
2✔
236
    }
2✔
237
}
238

239
#[derive(Debug)]
240
pub enum ParseOhttpKeysError {
241
    InvalidFormat,
242
    InvalidPublicKey,
243
    DecodeBech32(bech32::primitives::decode::CheckedHrpstringError),
244
    DecodeKeyConfig(ohttp::Error),
245
}
246

247
impl std::fmt::Display for ParseOhttpKeysError {
248
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
249
        match self {
×
250
            ParseOhttpKeysError::InvalidFormat => write!(f, "Invalid format"),
×
251
            ParseOhttpKeysError::InvalidPublicKey => write!(f, "Invalid public key"),
×
NEW
252
            ParseOhttpKeysError::DecodeBech32(e) => write!(f, "Failed to decode base64: {e}"),
×
NEW
253
            ParseOhttpKeysError::DecodeKeyConfig(e) => write!(f, "Failed to decode KeyConfig: {e}"),
×
254
        }
255
    }
×
256
}
257

258
impl std::error::Error for ParseOhttpKeysError {
259
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
×
260
        match self {
×
261
            ParseOhttpKeysError::DecodeBech32(e) => Some(e),
×
262
            ParseOhttpKeysError::DecodeKeyConfig(e) => Some(e),
×
263
            ParseOhttpKeysError::InvalidFormat | ParseOhttpKeysError::InvalidPublicKey => None,
×
264
        }
265
    }
×
266
}
267

268
#[cfg(test)]
269
mod test {
270
    use super::*;
271

272
    #[test]
273
    fn test_ohttp_keys_roundtrip() {
1✔
274
        use std::str::FromStr;
275

276
        use ohttp::hpke::{Aead, Kdf, Kem};
277
        use ohttp::{KeyId, SymmetricSuite};
278
        const KEY_ID: KeyId = 1;
279
        const KEM: Kem = Kem::K256Sha256;
280
        const SYMMETRIC: &[SymmetricSuite] =
281
            &[ohttp::SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305)];
282
        let keys = OhttpKeys(ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).unwrap());
1✔
283
        let serialized = &keys.to_string();
1✔
284
        let deserialized = OhttpKeys::from_str(serialized).unwrap();
1✔
285
        assert_eq!(keys.encode().unwrap(), deserialized.encode().unwrap());
1✔
286
    }
1✔
287
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc