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

payjoin / rust-payjoin / 18014757645

25 Sep 2025 04:53PM UTC coverage: 84.552% (+0.005%) from 84.547%
18014757645

Pull #1111

github

web-flow
Merge 6ebeec8ec into 4a598841d
Pull Request #1111: Remove the url crate dep from payjoin-test-utils

26 of 28 new or added lines in 6 files covered. (92.86%)

1 existing line in 1 file now uncovered.

8571 of 10137 relevant lines covered (84.55%)

482.72 hits per line

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

87.73
/payjoin-test-utils/src/lib.rs
1
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
2
use std::result::Result;
3
use std::str::FromStr;
4
use std::sync::Arc;
5
use std::time::Duration;
6

7
use bitcoin::{Amount, Psbt};
8
pub use corepc_node; // re-export for convenience
9
use corepc_node::AddressType;
10
use http::StatusCode;
11
use ohttp::hpke::{Aead, Kdf, Kem};
12
use ohttp::{KeyId, SymmetricSuite};
13
use once_cell::sync::{Lazy, OnceCell};
14
use payjoin::io::{fetch_ohttp_keys_with_cert, Error as IOError};
15
use payjoin::OhttpKeys;
16
use rcgen::Certificate;
17
use reqwest::{Client, ClientBuilder};
18
use rustls::pki_types::CertificateDer;
19
use rustls::RootCertStore;
20
use tempfile::tempdir;
21
use tokio::net::TcpListener;
22
use tokio::task::JoinHandle;
23
use tracing::Level;
24
use tracing_subscriber::{EnvFilter, FmtSubscriber};
25

26
pub type BoxError = Box<dyn std::error::Error + 'static>;
27
pub type BoxSendSyncError = Box<dyn std::error::Error + Send + Sync>;
28

29
pub use payjoin::persist::test_utils::InMemoryTestPersister;
30
pub use payjoin::persist::SessionPersister;
31

32
static INIT_TRACING: OnceCell<()> = OnceCell::new();
33

34
pub fn init_tracing() {
14✔
35
    INIT_TRACING.get_or_init(|| {
14✔
36
        let subscriber = FmtSubscriber::builder()
2✔
37
            .with_env_filter(EnvFilter::from_default_env())
2✔
38
            .with_test_writer()
2✔
39
            .finish();
2✔
40

41
        tracing::subscriber::set_global_default(subscriber)
2✔
42
            .expect("failed to set global default subscriber");
2✔
43
    });
2✔
44
}
14✔
45

46
pub struct TestServices {
47
    cert: Certificate,
48
    directory: (u16, Option<JoinHandle<Result<(), BoxSendSyncError>>>),
49
    ohttp_relay: (u16, Option<JoinHandle<Result<(), BoxSendSyncError>>>),
50
    http_agent: Arc<Client>,
51
}
52

53
impl TestServices {
54
    pub async fn initialize() -> Result<Self, BoxSendSyncError> {
7✔
55
        // TODO add a UUID, and cleanup guard to delete after on successful run
56
        let cert = local_cert_key();
7✔
57
        let cert_der = cert.cert.der().to_vec();
7✔
58
        let key_der = cert.signing_key.serialize_der();
7✔
59
        let cert_key = (cert_der.clone(), key_der);
7✔
60

61
        let mut root_store = RootCertStore::empty();
7✔
62
        root_store.add(CertificateDer::from(cert.cert.der().to_vec())).unwrap();
7✔
63

64
        let directory = init_directory(cert_key).await?;
7✔
65

66
        let gateway_origin =
7✔
67
            ohttp_relay::GatewayUri::from_str(&format!("https://localhost:{}", directory.0))?;
7✔
68
        let ohttp_relay = ohttp_relay::listen_tcp_on_free_port(gateway_origin, root_store).await?;
7✔
69
        let http_agent: Arc<Client> = Arc::new(http_agent(cert_der)?);
7✔
70

71
        Ok(Self {
7✔
72
            cert: cert.cert,
7✔
73
            directory: (directory.0, Some(directory.1)),
7✔
74
            ohttp_relay: (ohttp_relay.0, Some(ohttp_relay.1)),
7✔
75
            http_agent,
7✔
76
        })
7✔
77
    }
7✔
78

79
    pub fn cert(&self) -> Vec<u8> { self.cert.der().to_vec() }
7✔
80

81
    pub fn directory_url(&self) -> String { format!("https://localhost:{}", self.directory.0) }
18✔
82

83
    pub fn take_directory_handle(&mut self) -> JoinHandle<Result<(), BoxSendSyncError>> {
6✔
84
        self.directory.1.take().expect("directory handle not found")
6✔
85
    }
6✔
86

87
    pub fn ohttp_relay_url(&self) -> String { format!("http://localhost:{}", self.ohttp_relay.0) }
27✔
88

NEW
89
    pub fn ohttp_gateway_url(&self) -> String {
×
NEW
90
        format!("{}/.well-known/ohttp-gateway", self.directory_url())
×
UNCOV
91
    }
×
92

93
    pub fn take_ohttp_relay_handle(&mut self) -> JoinHandle<Result<(), BoxSendSyncError>> {
5✔
94
        self.ohttp_relay.1.take().expect("ohttp relay handle not found")
5✔
95
    }
5✔
96

97
    pub fn http_agent(&self) -> Arc<Client> { self.http_agent.clone() }
18✔
98

99
    pub async fn wait_for_services_ready(&self) -> Result<(), &'static str> {
7✔
100
        wait_for_service_ready(&self.ohttp_relay_url(), self.http_agent()).await?;
7✔
101
        wait_for_service_ready(&self.directory_url(), self.http_agent()).await?;
7✔
102
        Ok(())
7✔
103
    }
7✔
104

105
    pub async fn fetch_ohttp_keys(&self) -> Result<OhttpKeys, IOError> {
5✔
106
        fetch_ohttp_keys_with_cert(
5✔
107
            self.ohttp_relay_url().as_str(),
5✔
108
            self.directory_url().as_str(),
5✔
109
            self.cert(),
5✔
110
        )
5✔
111
        .await
5✔
112
    }
5✔
113
}
114

115
pub async fn init_directory(
7✔
116
    local_cert_key: (Vec<u8>, Vec<u8>),
7✔
117
) -> std::result::Result<
7✔
118
    (u16, tokio::task::JoinHandle<std::result::Result<(), BoxSendSyncError>>),
7✔
119
    BoxSendSyncError,
7✔
120
> {
7✔
121
    let timeout = Duration::from_secs(2);
7✔
122
    let ohttp_server = payjoin_directory::gen_ohttp_server_config()?;
7✔
123

124
    let metrics = payjoin_directory::metrics::Metrics::new();
7✔
125
    let tempdir = tempdir()?;
7✔
126
    let db = payjoin_directory::FilesDb::init(timeout, tempdir.path().to_path_buf()).await?;
7✔
127

128
    let service = payjoin_directory::Service::new(db, ohttp_server.into(), metrics);
7✔
129

130
    let listener = bind_free_port().await?;
7✔
131
    let port = listener.local_addr()?.port();
7✔
132

133
    let handle = tokio::spawn(async move {
7✔
134
        let _tempdir = tempdir; // keep the tempdir until the directory shuts down
7✔
135
        service.serve_tls(listener, local_cert_key).await
7✔
136
    });
×
137

138
    Ok((port, handle))
7✔
139
}
7✔
140

141
async fn bind_free_port() -> Result<tokio::net::TcpListener, std::io::Error> {
7✔
142
    let bind_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0);
7✔
143
    TcpListener::bind(bind_addr).await
7✔
144
}
7✔
145

146
/// generate or get a DER encoded localhost cert and key.
147
pub fn local_cert_key() -> rcgen::CertifiedKey<rcgen::KeyPair> {
10✔
148
    rcgen::generate_simple_self_signed(vec!["0.0.0.0".to_string(), "localhost".to_string()])
10✔
149
        .expect("Failed to generate cert")
10✔
150
}
10✔
151

152
pub fn init_bitcoind() -> Result<corepc_node::Node, BoxError> {
16✔
153
    let bitcoind_exe = corepc_node::exe_path()?;
16✔
154
    let mut conf = corepc_node::Conf::default();
16✔
155
    conf.view_stdout = tracing::enabled!(target: "corepc", Level::TRACE);
16✔
156
    let bitcoind = corepc_node::Node::with_conf(bitcoind_exe, &conf)?;
16✔
157
    Ok(bitcoind)
16✔
158
}
16✔
159

160
pub fn init_bitcoind_sender_receiver(
16✔
161
    sender_address_type: Option<AddressType>,
16✔
162
    receiver_address_type: Option<AddressType>,
16✔
163
) -> Result<(corepc_node::Node, corepc_node::Client, corepc_node::Client), BoxError> {
16✔
164
    let bitcoind = init_bitcoind()?;
16✔
165
    let mut wallets = create_and_fund_wallets(
16✔
166
        &bitcoind,
16✔
167
        vec![("receiver", receiver_address_type), ("sender", sender_address_type)],
16✔
168
    )?;
×
169
    let receiver = wallets.pop().expect("receiver to exist");
16✔
170
    let sender = wallets.pop().expect("sender to exist");
16✔
171

172
    Ok((bitcoind, receiver, sender))
16✔
173
}
16✔
174

175
fn create_and_fund_wallets<W: AsRef<str>>(
16✔
176
    bitcoind: &corepc_node::Node,
16✔
177
    wallets: Vec<(W, Option<AddressType>)>,
16✔
178
) -> Result<Vec<corepc_node::Client>, BoxError> {
16✔
179
    let mut funded_wallets = vec![];
16✔
180
    let funding_wallet = bitcoind.create_wallet("funding_wallet")?;
16✔
181
    let funding_address = funding_wallet.new_address()?;
16✔
182
    // 100 blocks would work here, we add a extra block to cover fees between transfers
183
    bitcoind.client.generate_to_address(101 + wallets.len(), &funding_address)?;
16✔
184
    for (wallet_name, address_type) in wallets {
48✔
185
        let wallet = bitcoind.create_wallet(wallet_name)?;
32✔
186
        let address = wallet.get_new_address(None, address_type)?.into_model()?.0.assume_checked();
32✔
187
        funding_wallet.send_to_address(&address, Amount::from_btc(50.0)?)?;
32✔
188
        funded_wallets.push(wallet);
32✔
189
    }
190
    // Mine the block which funds the different wallets
191
    bitcoind.client.generate_to_address(1, &funding_address)?;
16✔
192

193
    for wallet in funded_wallets.iter() {
32✔
194
        let balances = wallet.get_balances()?.into_model()?;
32✔
195
        assert_eq!(
32✔
196
            balances.mine.trusted,
197
            Amount::from_btc(50.0)?,
32✔
198
            "wallet doesn't have expected amount of bitcoin"
×
199
        );
200
    }
201

202
    Ok(funded_wallets)
16✔
203
}
16✔
204

205
pub fn http_agent(cert_der: Vec<u8>) -> Result<Client, BoxSendSyncError> {
7✔
206
    Ok(http_agent_builder(cert_der).build()?)
7✔
207
}
7✔
208

209
pub fn init_bitcoind_multi_sender_single_reciever(
×
210
    number_of_senders: usize,
×
211
) -> Result<(corepc_node::Node, Vec<corepc_node::Client>, corepc_node::Client), BoxError> {
×
212
    let bitcoind = init_bitcoind()?;
×
213
    let wallets_to_create =
×
214
        (0..number_of_senders + 1).map(|i| (format!("sender_{i}"), None)).collect::<Vec<_>>();
×
215
    let mut wallets = create_and_fund_wallets(&bitcoind, wallets_to_create)?;
×
216
    let receiver = wallets.pop().expect("receiver to exist");
×
217
    let senders = wallets;
×
218

219
    Ok((bitcoind, senders, receiver))
×
220
}
×
221

222
fn http_agent_builder(cert_der: Vec<u8>) -> ClientBuilder {
7✔
223
    ClientBuilder::new().use_rustls_tls().add_root_certificate(
7✔
224
        reqwest::tls::Certificate::from_der(cert_der.as_slice())
7✔
225
            .expect("cert_der should be a valid DER-encoded certificate"),
7✔
226
    )
227
}
7✔
228

229
const TESTS_TIMEOUT: Duration = Duration::from_secs(20);
230
const WAIT_SERVICE_INTERVAL: Duration = Duration::from_secs(3);
231

232
pub async fn wait_for_service_ready(
14✔
233
    service_url: &str,
14✔
234
    agent: Arc<Client>,
14✔
235
) -> Result<(), &'static str> {
14✔
236
    let health_url = format!("{}/health", service_url.trim_end_matches("/"));
14✔
237
    let start = std::time::Instant::now();
14✔
238

239
    while start.elapsed() < TESTS_TIMEOUT {
14✔
240
        let request_result =
14✔
241
            agent.get(health_url.clone()).send().await.map_err(|_| "Bad request")?;
14✔
242
        match request_result.status() {
14✔
243
            StatusCode::OK => return Ok(()),
14✔
244
            StatusCode::NOT_FOUND => return Err("Endpoint not found"),
×
245
            _ => std::thread::sleep(WAIT_SERVICE_INTERVAL),
×
246
        }
247
    }
248

249
    Err("Timeout waiting for service to be ready")
×
250
}
14✔
251

252
pub static EXAMPLE_URL: &str = "https://example.com";
253

254
pub const KEY_ID: KeyId = 1;
255
pub const KEM: Kem = Kem::K256Sha256;
256
pub const SYMMETRIC: &[SymmetricSuite] =
257
    &[ohttp::SymmetricSuite::new(Kdf::HkdfSha256, Aead::ChaCha20Poly1305)];
258

259
// https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#user-content-span_idtestvectorsspanTest_vectors
260
// | InputScriptType | Original PSBT Fee rate | maxadditionalfeecontribution | additionalfeeoutputindex|
261
// |-----------------|-----------------------|------------------------------|-------------------------|
262
// | P2SH-P2WPKH     |  2 sat/vbyte          | 0.00000182                   | 0                       |
263

264
pub const QUERY_PARAMS: &str = "maxadditionalfeecontribution=182&additionalfeeoutputindex=0";
265

266
/// From the BIP-78 test vector
267
pub const ORIGINAL_PSBT: &str = "cHNidP8BAHMCAAAAAY8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////AtyVuAUAAAAAF6kUHehJ8GnSdBUOOv6ujXLrWmsJRDCHgIQeAAAAAAAXqRR3QJbbz0hnQ8IvQ0fptGn+votneofTAAAAAAEBIKgb1wUAAAAAF6kU3k4ekGHKWRNbA1rV5tR5kEVDVNCHAQcXFgAUx4pFclNVgo1WWAdN1SYNX8tphTABCGsCRzBEAiB8Q+A6dep+Rz92vhy26lT0AjZn4PRLi8Bf9qoB/CMk0wIgP/Rj2PWZ3gEjUkTlhDRNAQ0gXwTO7t9n+V14pZ6oljUBIQMVmsAaoNWHVMS02LfTSe0e388LNitPa1UQZyOihY+FFgABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUAAA=";
268

269
/// From the BIP-174 test vector
270
pub const INVALID_PSBT: &str = "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA&#61;&#61;";
271

272
/// From the BIP-78 test vector
273
pub const PAYJOIN_PROPOSAL: &str = "cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcAAQEggIQeAAAAAAAXqRTI8sv5ymFHLIjkZNRrNXSEXZHY1YcBBxcWABRfgGZV5ZJMkgTC1RvlOU9L+e2iEAEIawJHMEQCIGe7e0DfJaVPRYEKWxddL2Pr0G37BoKz0lyNa02O2/tWAiB7ZVgBoF4s8MHocYWWmo4Q1cyV2wl7MX0azlqa8NBENAEhAmXWPPW0G3yE3HajBOb7gO7iKzHSmZ0o0w0iONowcV+tAAAA";
274

275
/// From the BIP-78 test vector
276
pub const PAYJOIN_PROPOSAL_WITH_SENDER_INFO: &str = "cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcBBBYAFMeKRXJTVYKNVlgHTdUmDV/LaYUwIgYDFZrAGqDVh1TEtNi300ntHt/PCzYrT2tVEGcjooWPhRYYSFzWUDEAAIABAACAAAAAgAEAAAAAAAAAAAEBIICEHgAAAAAAF6kUyPLL+cphRyyI5GTUazV0hF2R2NWHAQcXFgAUX4BmVeWSTJIEwtUb5TlPS/ntohABCGsCRzBEAiBnu3tA3yWlT0WBClsXXS9j69Bt+waCs9JcjWtNjtv7VgIge2VYAaBeLPDB6HGFlpqOENXMldsJezF9Gs5amvDQRDQBIQJl1jz1tBt8hNx2owTm+4Du4isx0pmdKNMNIjjaMHFfrQABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUIgICygvBWB5prpfx61y1HDAwo37kYP3YRJBvAjtunBAur3wYSFzWUDEAAIABAACAAAAAgAEAAAABAAAAAAA=";
277

278
/// Input contribution for the receiver, from the BIP78 test vector
279
pub const RECEIVER_INPUT_CONTRIBUTION: &str = "cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAAEBIICEHgAAAAAAF6kUyPLL+cphRyyI5GTUazV0hF2R2NWHAQcXFgAUX4BmVeWSTJIEwtUb5TlPS/ntohABCGsCRzBEAiBnu3tA3yWlT0WBClsXXS9j69Bt+waCs9JcjWtNjtv7VgIge2VYAaBeLPDB6HGFlpqOENXMldsJezF9Gs5amvDQRDQBIQJl1jz1tBt8hNx2owTm+4Du4isx0pmdKNMNIjjaMHFfrQAAAA==";
280

281
pub static PARSED_ORIGINAL_PSBT: Lazy<Psbt> =
282
    Lazy::new(|| Psbt::from_str(ORIGINAL_PSBT).expect("known psbt should parse"));
1✔
283

284
pub static PARSED_PAYJOIN_PROPOSAL: Lazy<Psbt> =
285
    Lazy::new(|| Psbt::from_str(PAYJOIN_PROPOSAL).expect("known psbt should parse"));
1✔
286

287
pub static PARSED_PAYJOIN_PROPOSAL_WITH_SENDER_INFO: Lazy<Psbt> = Lazy::new(|| {
1✔
288
    Psbt::from_str(PAYJOIN_PROPOSAL_WITH_SENDER_INFO).expect("known psbt should parse")
1✔
289
});
1✔
290

291
pub const MULTIPARTY_ORIGINAL_PSBT_ONE: &str = "cHNidP8BAMMCAAAAA9cTcqYjSnJdteUn3Re12vFuVb5xPypYheHze74WBW8EAAAAAAD+////6JHS6JgYdav7tr+Q5bWk95C2aA4AudqZnjeRF0hx33gBAAAAAP7///+d/1mRjJ6snVe9o0EfMM8bIExdlwfaig1j/JnUupqIWwAAAAAA/v///wJO4wtUAgAAABYAFGAEc6cWf7z5a97Xxulg+S7JZyg/kvEFKgEAAAAWABQexWFsGEdXxYNTJLJ1qBCO0nXELwAAAAAAAQCaAgAAAAKa/zFrWjicRDNVCehGj/2jdXGKuZLMzLNUo+uj7PBW8wAAAAAA/v///4HkRlmU9FA3sHckkCQMjGrabNpgAw37XaqUItNFjR5rAAAAAAD+////AgDyBSoBAAAAFgAUGPI/E9xCHmaL6DIsjkaD8LX2mpLg6QUqAQAAABYAFH6MyygysrAmk/nunEFuGGDLf244aAAAAAEBHwDyBSoBAAAAFgAUGPI/E9xCHmaL6DIsjkaD8LX2mpIBCGsCRzBEAiBLJUhV7tja6FdELXDcB6Q3Gd+BMBpkjdHpj3DLnbL6AAIgJmFl3kpWBzkO8yDDl73BMMalSlAOQvfY+hqFRos5DhQBIQLfeq7CCftNEUHcRLZniJOqcgKZEqPc1A40BnEiZHj3FQABAJoCAAAAAp3/WZGMnqydV72jQR8wzxsgTF2XB9qKDWP8mdS6mohbAQAAAAD+////1xNypiNKcl215SfdF7Xa8W5VvnE/KliF4fN7vhYFbwQBAAAAAP7///8CoNkFKgEAAAAWABQchAe9uxzqT1EjLx4jgx9u1Mn6QADyBSoBAAAAFgAUye8yXWX0MvouXYhAFb06xX5kADpoAAAAAQEfAPIFKgEAAAAWABTJ7zJdZfQy+i5diEAVvTrFfmQAOgEIawJHMEQCIF7ihY/YtUPUTOaEdJbg0/HiwKunK398BI67/LknPGqMAiBHBXmL6gTP8PxEGeWswk6T0tCI2Gvwq1zh+wd7h8QCWAEhApM0w2WFlw0eg64Zp3PeyRmxl/7WGHUED8Ul/aX1FiTBAAAAAA==";
292

293
pub const MULTIPARTY_ORIGINAL_PSBT_TWO: &str = "cHNidP8BAMMCAAAAA9cTcqYjSnJdteUn3Re12vFuVb5xPypYheHze74WBW8EAAAAAAD+////6JHS6JgYdav7tr+Q5bWk95C2aA4AudqZnjeRF0hx33gBAAAAAP7///+d/1mRjJ6snVe9o0EfMM8bIExdlwfaig1j/JnUupqIWwAAAAAA/v///wJO4wtUAgAAABYAFGAEc6cWf7z5a97Xxulg+S7JZyg/kvEFKgEAAAAWABQexWFsGEdXxYNTJLJ1qBCO0nXELwAAAAAAAAEAmgIAAAACnf9ZkYyerJ1XvaNBHzDPGyBMXZcH2ooNY/yZ1LqaiFsBAAAAAP7////XE3KmI0pyXbXlJ90XtdrxblW+cT8qWIXh83u+FgVvBAEAAAAA/v///wKg2QUqAQAAABYAFByEB727HOpPUSMvHiODH27UyfpAAPIFKgEAAAAWABTJ7zJdZfQy+i5diEAVvTrFfmQAOmgAAAABAR8A8gUqAQAAABYAFMnvMl1l9DL6Ll2IQBW9OsV+ZAA6AQhrAkcwRAIgXuKFj9i1Q9RM5oR0luDT8eLAq6crf3wEjrv8uSc8aowCIEcFeYvqBM/w/EQZ5azCTpPS0IjYa/CrXOH7B3uHxAJYASECkzTDZYWXDR6Drhmnc97JGbGX/tYYdQQPxSX9pfUWJMEAAQCaAgAAAAIKOB8lY4eoEupDnxviz0/nAuR2biNFKbdkvckiW5ioPAAAAAAA/v///w0g/mj67592vy29xhnZMGeVtEXN1jD4lU/SMZM8oStqAAAAAAD+////AgDyBSoBAAAAFgAUC3r4YzVSpsWp3knMxbgWIx2R36/g6QUqAQAAABYAFMGlw3hwcx1b+KQGWIfOUxzwrwWkaAAAAAEBHwDyBSoBAAAAFgAUC3r4YzVSpsWp3knMxbgWIx2R368BCGsCRzBEAiA//JH+jonzbzqnKI0Uti16iJcdsXI+6Zu0IAZKlOq6AwIgP0XawCCH73uXKilFqSXQQQrBvmi/Sx44D/A+/MQ/mJYBIQIekOyEpJKpFQd7eHuY6Vt4Qlf0+00Wp529I23hl/EpcQAAAA==";
294

295
/// sha256d of the empty string
296
#[rustfmt::skip]
297
pub const DUMMY32: [u8; 32] = [
298
    0x5d, 0xf6, 0xe0, 0xe2, 0x76, 0x13, 0x59, 0xd3,
299
    0x0a, 0x82, 0x75, 0x05, 0x8e, 0x29, 0x9f, 0xcc,
300
    0x03, 0x81, 0x53, 0x45, 0x45, 0xf5, 0x5c, 0xf4,
301
    0x3e, 0x41, 0x98, 0x3f, 0x5d, 0x4c, 0x94, 0x56,
302
];
303
/// hash160 of the empty string
304
#[rustfmt::skip]
305
pub const DUMMY20: [u8; 20] = [
306
    0xb4, 0x72, 0xa2, 0x66, 0xd0, 0xbd, 0x89, 0xc1, 0x37, 0x06,
307
    0xa4, 0x13, 0x2c, 0xcf, 0xb1, 0x6f, 0x7c, 0x3b, 0x9f, 0xcb,
308
];
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

© 2025 Coveralls, Inc