Skip to main content

bsv/wallet/
types.rs

1//! Semantic type aliases and core types for the wallet module.
2//!
3//! Mirrors the Go SDK wallet/wallet.go and wallet/interfaces.go type
4//! definitions, providing strongly-typed aliases for protocol parameters.
5
6use crate::primitives::private_key::PrivateKey;
7use crate::primitives::public_key::PublicKey;
8
9// ---------------------------------------------------------------------------
10// Semantic type aliases
11// ---------------------------------------------------------------------------
12
13/// Hex-encoded public key string.
14pub type PubKeyHex = String;
15
16/// Satoshi value (unsigned 64-bit integer).
17pub type SatoshiValue = u64;
18
19/// Outpoint string in "txid.index" format.
20pub type OutpointString = String;
21
22/// Transaction ID as a hex string.
23pub type TXIDHexString = String;
24
25/// Description string (5 to 50 bytes).
26pub type DescriptionString5to50Bytes = String;
27
28/// Basket name string (under 300 bytes).
29pub type BasketStringUnder300Bytes = String;
30
31/// Output tag string (under 300 bytes).
32pub type OutputTagStringUnder300Bytes = String;
33
34/// Label string (under 300 bytes).
35pub type LabelStringUnder300Bytes = String;
36
37/// Key ID string (under 800 bytes).
38pub type KeyIDStringUnder800Bytes = String;
39
40/// Originator domain name string (under 250 bytes).
41pub type OriginatorDomainNameStringUnder250Bytes = String;
42
43/// Certificate field name (under 50 bytes).
44pub type CertificateFieldNameUnder50Bytes = String;
45
46/// Base64-encoded string.
47pub type Base64String = String;
48
49/// Hex-encoded string.
50pub type HexString = String;
51
52/// Boolean that defaults to true when None.
53///
54/// A newtype around `Option<bool>` that dereferences to `true` when the inner
55/// value is `None` or `Some(true)`. Serializes transparently as `Option<bool>`
56/// on the wire and in JSON.
57#[derive(Clone, Debug, PartialEq)]
58#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
59#[cfg_attr(feature = "network", serde(transparent))]
60pub struct BooleanDefaultTrue(pub Option<bool>);
61
62impl Default for BooleanDefaultTrue {
63    fn default() -> Self {
64        BooleanDefaultTrue(Some(true))
65    }
66}
67
68impl std::ops::Deref for BooleanDefaultTrue {
69    type Target = bool;
70    fn deref(&self) -> &bool {
71        static TRUE: bool = true;
72        static FALSE: bool = false;
73        match self.0 {
74            Some(true) | None => &TRUE,
75            Some(false) => &FALSE,
76        }
77    }
78}
79
80impl From<BooleanDefaultTrue> for Option<bool> {
81    fn from(v: BooleanDefaultTrue) -> Self {
82        v.0
83    }
84}
85
86impl From<Option<bool>> for BooleanDefaultTrue {
87    fn from(v: Option<bool>) -> Self {
88        BooleanDefaultTrue(v)
89    }
90}
91
92impl BooleanDefaultTrue {
93    /// Returns true if the inner value is None.
94    /// Used by serde skip_serializing_if.
95    pub fn is_none(&self) -> bool {
96        self.0.is_none()
97    }
98}
99
100/// Boolean that defaults to false when None.
101///
102/// A newtype around `Option<bool>` that dereferences to `false` when the inner
103/// value is `None` or `Some(false)`. Serializes transparently as `Option<bool>`
104/// on the wire and in JSON.
105#[derive(Clone, Debug, PartialEq)]
106#[cfg_attr(feature = "network", derive(serde::Serialize, serde::Deserialize))]
107#[cfg_attr(feature = "network", serde(transparent))]
108pub struct BooleanDefaultFalse(pub Option<bool>);
109
110impl Default for BooleanDefaultFalse {
111    fn default() -> Self {
112        BooleanDefaultFalse(Some(false))
113    }
114}
115
116impl std::ops::Deref for BooleanDefaultFalse {
117    type Target = bool;
118    fn deref(&self) -> &bool {
119        static TRUE: bool = true;
120        static FALSE: bool = false;
121        match self.0 {
122            Some(true) => &TRUE,
123            Some(false) | None => &FALSE,
124        }
125    }
126}
127
128impl From<BooleanDefaultFalse> for Option<bool> {
129    fn from(v: BooleanDefaultFalse) -> Self {
130        v.0
131    }
132}
133
134impl From<Option<bool>> for BooleanDefaultFalse {
135    fn from(v: Option<bool>) -> Self {
136        BooleanDefaultFalse(v)
137    }
138}
139
140impl BooleanDefaultFalse {
141    /// Returns true if the inner value is None.
142    /// Used by serde skip_serializing_if.
143    pub fn is_none(&self) -> bool {
144        self.0.is_none()
145    }
146}
147
148/// Positive integer defaulting to 10, max 10000.
149pub type PositiveIntegerDefault10Max10000 = Option<u32>;
150
151/// Positive integer or zero.
152pub type PositiveIntegerOrZero = u32;
153
154// ---------------------------------------------------------------------------
155// Protocol
156// ---------------------------------------------------------------------------
157
158/// Defines a protocol with its security level and name.
159///
160/// The security level determines how strictly the wallet enforces
161/// user confirmation:
162/// - 0: Silent (no user interaction)
163/// - 1: Every app (user confirms per app)
164/// - 2: Every app and counterparty (user confirms per app + counterparty)
165///
166/// Serializes as a JSON array `[securityLevel, "protocolName"]` matching
167/// the Go SDK encoding.
168#[derive(Clone, Debug)]
169pub struct Protocol {
170    pub security_level: u8,
171    pub protocol: String,
172}
173
174#[cfg(feature = "network")]
175impl serde::Serialize for Protocol {
176    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
177        use serde::ser::SerializeSeq;
178        let mut seq = serializer.serialize_seq(Some(2))?;
179        seq.serialize_element(&self.security_level)?;
180        seq.serialize_element(&self.protocol)?;
181        seq.end()
182    }
183}
184
185#[cfg(feature = "network")]
186impl<'de> serde::Deserialize<'de> for Protocol {
187    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
188        use serde::de::{self, SeqAccess, Visitor};
189        use std::fmt;
190
191        struct ProtocolVisitor;
192
193        impl<'de> Visitor<'de> for ProtocolVisitor {
194            type Value = Protocol;
195
196            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
197                formatter.write_str("an array [securityLevel, protocolName]")
198            }
199
200            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Protocol, A::Error> {
201                let security_level: u8 = seq
202                    .next_element()?
203                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
204                let protocol: String = seq
205                    .next_element()?
206                    .ok_or_else(|| de::Error::invalid_length(1, &self))?;
207                Ok(Protocol {
208                    security_level,
209                    protocol,
210                })
211            }
212        }
213
214        deserializer.deserialize_seq(ProtocolVisitor)
215    }
216}
217
218// ---------------------------------------------------------------------------
219// Counterparty
220// ---------------------------------------------------------------------------
221
222/// The type of counterparty in a cryptographic operation.
223#[derive(Clone, Debug, PartialEq)]
224pub enum CounterpartyType {
225    /// Uninitialized / unknown.
226    Uninitialized,
227    /// The wallet itself.
228    Self_,
229    /// The special "anyone" key (PrivateKey(1)).
230    Anyone,
231    /// A specific other party identified by public key.
232    Other,
233}
234
235/// Represents the other party in a cryptographic operation.
236///
237/// Can be a specific public key, or one of the special values
238/// "self" or "anyone".
239///
240/// Serializes as a JSON string: "anyone", "self", or DER hex public key,
241/// matching the Go SDK encoding.
242#[derive(Clone, Debug)]
243pub struct Counterparty {
244    pub counterparty_type: CounterpartyType,
245    pub public_key: Option<PublicKey>,
246}
247
248impl Default for Counterparty {
249    /// Default to `Uninitialized` — the sentinel that `ProtoWallet::default_counterparty()`
250    /// substitutes with the correct per-op default (`self` for most crypto ops,
251    /// `anyone` for `createSignature`). Returning a concrete value here would bypass
252    /// that per-op dispatch and silently mis-derive keys across SDKs.
253    fn default() -> Self {
254        Self {
255            counterparty_type: CounterpartyType::Uninitialized,
256            public_key: None,
257        }
258    }
259}
260
261#[cfg(feature = "network")]
262impl serde::Serialize for Counterparty {
263    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
264        match self.counterparty_type {
265            CounterpartyType::Anyone => serializer.serialize_str("anyone"),
266            CounterpartyType::Self_ => serializer.serialize_str("self"),
267            CounterpartyType::Other => {
268                if let Some(ref pk) = self.public_key {
269                    serializer.serialize_str(&pk.to_der_hex())
270                } else {
271                    serializer.serialize_none()
272                }
273            }
274            CounterpartyType::Uninitialized => serializer.serialize_str(""),
275        }
276    }
277}
278
279#[cfg(feature = "network")]
280impl<'de> serde::Deserialize<'de> for Counterparty {
281    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
282        // Accept both a missing field (handled by `#[serde(default)]` at the
283        // call site) and an explicit `null` as equivalent: both map to
284        // `Uninitialized`, which `ProtoWallet::default_counterparty()` then
285        // resolves to the correct per-op default. Without the `Option` layer,
286        // an explicit `"counterparty": null` would raise `invalid type: null,
287        // expected a string`, producing asymmetric behavior from omission.
288        let s: Option<String> = Option::<String>::deserialize(deserializer)?;
289        let s = match s {
290            None => {
291                return Ok(Counterparty {
292                    counterparty_type: CounterpartyType::Uninitialized,
293                    public_key: None,
294                })
295            }
296            Some(s) => s,
297        };
298        match s.as_str() {
299            "anyone" => Ok(Counterparty {
300                counterparty_type: CounterpartyType::Anyone,
301                public_key: None,
302            }),
303            "self" => Ok(Counterparty {
304                counterparty_type: CounterpartyType::Self_,
305                public_key: None,
306            }),
307            "" => Ok(Counterparty {
308                counterparty_type: CounterpartyType::Uninitialized,
309                public_key: None,
310            }),
311            hex_str => {
312                let pk = PublicKey::from_string(hex_str).map_err(serde::de::Error::custom)?;
313                Ok(Counterparty {
314                    counterparty_type: CounterpartyType::Other,
315                    public_key: Some(pk),
316                })
317            }
318        }
319    }
320}
321
322// ---------------------------------------------------------------------------
323// Anyone key helper
324// ---------------------------------------------------------------------------
325
326/// Returns the "anyone" public key: the public key corresponding to
327/// PrivateKey(1), which is the generator point G.
328///
329/// This is used when no specific counterparty is specified, making
330/// operations available to anyone who knows the protocol.
331pub fn anyone_pubkey() -> PublicKey {
332    let priv_key = PrivateKey::from_bytes(&{
333        let mut buf = [0u8; 32];
334        buf[31] = 1;
335        buf
336    })
337    // SAFETY: PrivateKey(1) is always valid -- 1 is within the secp256k1 scalar range.
338    .expect("PrivateKey(1) is always valid");
339    priv_key.to_public_key()
340}
341
342/// Returns the "anyone" private key: PrivateKey(1).
343pub fn anyone_private_key() -> PrivateKey {
344    PrivateKey::from_bytes(&{
345        let mut buf = [0u8; 32];
346        buf[31] = 1;
347        buf
348    })
349    // SAFETY: PrivateKey(1) is always valid -- 1 is within the secp256k1 scalar range.
350    .expect("PrivateKey(1) is always valid")
351}
352
353#[cfg(all(test, feature = "network"))]
354mod serde_tests {
355    //! Serde-level regression tests for `Counterparty` and its interaction
356    //! with `#[serde(default)]` on BRC-100 arg structs.
357
358    use super::*;
359    use crate::wallet::interfaces::{CreateSignatureArgs, EncryptArgs};
360
361    #[test]
362    fn counterparty_default_is_uninitialized() {
363        // C1 guard at the type level: `#[serde(default)]` on arg structs calls
364        // `Counterparty::default()`, which MUST yield `Uninitialized` so that
365        // `ProtoWallet::default_counterparty()` can substitute the correct
366        // per-op default ('anyone' for createSignature, 'self' for all others).
367        let cp = Counterparty::default();
368        assert_eq!(cp.counterparty_type, CounterpartyType::Uninitialized);
369        assert!(cp.public_key.is_none());
370    }
371
372    #[test]
373    fn create_signature_args_omit_counterparty_is_uninitialized() {
374        // C1 wire-format guard: a TS client that omits `counterparty` on a
375        // createSignature request must deserialize into an `Uninitialized`
376        // value so that `create_signature_sync` can route it to `Anyone`.
377        // Before the fix, this deserialized to `Self_` and silently derived
378        // against the wrong key.
379        let json = serde_json::json!({
380            "protocolID": [0, "cross-sdk test"],
381            "keyID": "x",
382            "data": [1, 2, 3],
383        });
384        let args: CreateSignatureArgs = serde_json::from_value(json).unwrap();
385        assert_eq!(
386            args.counterparty.counterparty_type,
387            CounterpartyType::Uninitialized,
388            "omitted counterparty must deserialize to Uninitialized, not Self_ — \
389             otherwise createSignature will derive against the wrong key"
390        );
391    }
392
393    #[test]
394    fn encrypt_args_omit_counterparty_is_uninitialized() {
395        // Non-createSignature ops also yield Uninitialized at the serde layer;
396        // the 'self' default is applied downstream by `default_counterparty()`.
397        let json = serde_json::json!({
398            "protocolID": [0, "cross-sdk test"],
399            "keyID": "x",
400            "plaintext": [1, 2, 3],
401        });
402        let args: EncryptArgs = serde_json::from_value(json).unwrap();
403        assert_eq!(
404            args.counterparty.counterparty_type,
405            CounterpartyType::Uninitialized
406        );
407    }
408
409    #[test]
410    fn counterparty_explicit_null_is_uninitialized() {
411        // M3: an explicit `"counterparty": null` must behave the same as
412        // omitting the field (both -> Uninitialized). Before the fix, null
413        // raised `invalid type: null, expected a string`, producing
414        // asymmetric behavior from omission for no useful reason.
415        let json = serde_json::json!({
416            "protocolID": [0, "cross-sdk test"],
417            "keyID": "x",
418            "data": [1, 2, 3],
419            "counterparty": serde_json::Value::Null,
420        });
421        let args: CreateSignatureArgs = serde_json::from_value(json).unwrap();
422        assert_eq!(
423            args.counterparty.counterparty_type,
424            CounterpartyType::Uninitialized
425        );
426    }
427
428    #[test]
429    fn counterparty_explicit_self_and_anyone_parse() {
430        // Sanity: the null/omission handling in the custom Deserialize impl
431        // must not break the pre-existing string-tag parsing.
432        let self_cp: Counterparty = serde_json::from_str("\"self\"").unwrap();
433        assert_eq!(self_cp.counterparty_type, CounterpartyType::Self_);
434
435        let anyone_cp: Counterparty = serde_json::from_str("\"anyone\"").unwrap();
436        assert_eq!(anyone_cp.counterparty_type, CounterpartyType::Anyone);
437
438        let empty_cp: Counterparty = serde_json::from_str("\"\"").unwrap();
439        assert_eq!(empty_cp.counterparty_type, CounterpartyType::Uninitialized);
440    }
441}