Skip to main content

winterwallet_core/
privkey.rs

1use crate::{WinternitzError, WinternitzPubkey, WinternitzSignature};
2use zeroize::{Zeroize, ZeroizeOnDrop};
3
4/// Private Winternitz key: `N` message scalars and 2 checksum scalars, each 32
5/// bytes. Zeroized on drop.
6///
7/// **Security:** Winternitz is a *one-time* signature scheme. Signing two
8/// different messages with the same privkey scalars allows an attacker to
9/// forge signatures on a third message. Prefer
10/// [`crate::WinternitzKeypair::sign_and_increment`] which guarantees the
11/// keypair advances after every signature. Calling [`Self::sign`] directly
12/// places the burden of one-time-use enforcement on the caller.
13#[repr(C)]
14#[derive(Zeroize, ZeroizeOnDrop)]
15pub struct WinternitzPrivkey<const N: usize> {
16    scalars: [[u8; 32]; N],
17    checksum: [[u8; 32]; 2],
18}
19
20impl<const N: usize> WinternitzPrivkey<N> {
21    pub(crate) fn new(scalars: [[u8; 32]; N], checksum: [[u8; 32]; 2]) -> Self {
22        const { crate::assert_n::<N>() };
23        Self { scalars, checksum }
24    }
25
26    /// Return the privkey's `(N + 2) * 32` raw bytes (message scalars then
27    /// checksum scalars), with no copy.
28    pub fn as_bytes(&self) -> &[u8] {
29        // SAFETY: #[repr(C)], all fields are [u8; _] with align 1, no padding.
30        unsafe {
31            core::slice::from_raw_parts(
32                self as *const Self as *const u8,
33                core::mem::size_of::<Self>(),
34            )
35        }
36    }
37
38    /// Sign a message. Consumes `self` so the same privkey can't be reused
39    /// directly. Note that re-deriving from a `WinternitzKeypair` at the same
40    /// position produces an identical privkey — for replay-safe signing prefer
41    /// [`crate::WinternitzKeypair::sign_and_increment`].
42    ///
43    /// The message is supplied as a slice of byte slices (matching
44    /// `solana_sha256_hasher::hashv`), so callers can mix domain-separation
45    /// tags or context bytes with the payload.
46    pub fn sign(self, message: &[&[u8]]) -> WinternitzSignature<N> {
47        const { crate::assert_n::<N>() };
48        let h = Self::hash(message);
49        self.sign_prehashed(&h)
50    }
51
52    /// Sign a pre-hashed message. Consumes `self`; same caveat as [`Self::sign`].
53    #[inline(always)]
54    pub fn sign_prehashed(self, hash: &[u8; N]) -> WinternitzSignature<N> {
55        const { crate::assert_n::<N>() };
56        let mut sig_scalars = [[0u8; 32]; N];
57        let mut checksum_sum: u16 = 0;
58        for i in 0..N {
59            let b = hash[i];
60            sig_scalars[i] = crate::chain(&self.scalars[i], b);
61            checksum_sum += 255u16 - b as u16;
62        }
63        let sig_checksum = [
64            crate::chain(&self.checksum[0], (checksum_sum >> 8) as u8),
65            crate::chain(&self.checksum[1], checksum_sum as u8),
66        ];
67        WinternitzSignature::new(sig_scalars, sig_checksum)
68    }
69
70    /// Hash a message into the `N`-byte Winternitz digest used by signing.
71    /// Equivalent to truncating SHA-256 of the concatenated `message` slices
72    /// to `N` bytes.
73    #[inline(always)]
74    pub fn hash(message: &[&[u8]]) -> [u8; N] {
75        crate::hash::<N>(message)
76    }
77
78    /// Derive the corresponding [`WinternitzPubkey`] by chaining each scalar
79    /// 255 times under SHA-256.
80    pub fn to_pubkey(&self) -> WinternitzPubkey<N> {
81        const { crate::assert_n::<N>() };
82        let mut scalars = [[0u8; 32]; N];
83        for (out, sk) in scalars.iter_mut().zip(self.scalars.iter()) {
84            *out = crate::chain(sk, 255);
85        }
86        let checksum = [
87            crate::chain(&self.checksum[0], 255),
88            crate::chain(&self.checksum[1], 255),
89        ];
90        WinternitzPubkey::new(scalars, checksum)
91    }
92}
93
94impl<const N: usize> core::fmt::Display for WinternitzPrivkey<N> {
95    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        write!(f, "0x")?;
97        for s in self.scalars.iter().chain(self.checksum.iter()) {
98            for b in s {
99                write!(f, "{:02x}", b)?;
100            }
101        }
102        Ok(())
103    }
104}
105
106impl<const N: usize> From<WinternitzPrivkey<N>> for WinternitzPubkey<N> {
107    fn from(sk: WinternitzPrivkey<N>) -> Self {
108        const { crate::assert_n::<N>() };
109        sk.to_pubkey()
110    }
111}
112
113impl<const N: usize> From<&WinternitzPrivkey<N>> for WinternitzPubkey<N> {
114    fn from(sk: &WinternitzPrivkey<N>) -> Self {
115        const { crate::assert_n::<N>() };
116        sk.to_pubkey()
117    }
118}
119
120impl<'a, const N: usize> TryFrom<&'a [u8]> for &'a WinternitzPrivkey<N> {
121    type Error = WinternitzError;
122
123    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
124        const { crate::assert_n::<N>() };
125        if value.len() != (N + 2) * 32 {
126            return Err(WinternitzError::InvalidLength);
127        }
128        // SAFETY: length verified; alignment is 1; every bit pattern is valid.
129        Ok(unsafe { &*value.as_ptr().cast::<WinternitzPrivkey<N>>() })
130    }
131}