Skip to main content

tronz_primitives/
signature.rs

1//! Recoverable secp256k1 signature in TRON's `r || s || v` wire form.
2
3use core::fmt;
4
5use k256::ecdsa::{RecoveryId, Signature};
6
7use crate::error::SignatureError;
8
9/// Length of the serialized signature: `r(32) || s(32) || v(1)`.
10pub const SIGNATURE_LEN: usize = 65;
11
12/// A secp256k1 ECDSA signature plus the recovery id needed to recover the
13/// signing public key.
14///
15/// Serializes to the 65-byte TRON wire format `r || s || v`, where `v` is the
16/// recovery id (`0` or `1`).
17#[derive(Clone, Copy, PartialEq, Eq, Hash)]
18pub struct RecoverableSignature {
19    r: [u8; 32],
20    s: [u8; 32],
21    v: u8,
22}
23
24impl RecoverableSignature {
25    /// Build from a non-recoverable [`Signature`] and its [`RecoveryId`].
26    pub fn from_signature(sig: &Signature, recovery_id: RecoveryId) -> Self {
27        let bytes = sig.to_bytes();
28        let mut r = [0u8; 32];
29        let mut s = [0u8; 32];
30        r.copy_from_slice(&bytes[..32]);
31        s.copy_from_slice(&bytes[32..]);
32        Self {
33            r,
34            s,
35            v: recovery_id.to_byte(),
36        }
37    }
38
39    /// Parse the 65-byte `r || s || v` representation.
40    ///
41    /// Accepts a `v` of `0`/`1` or the Ethereum-style `27`/`28`, normalising to
42    /// `0`/`1`.
43    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
44        if bytes.len() != SIGNATURE_LEN {
45            return Err(SignatureError::BadLength(bytes.len()));
46        }
47        let v = match bytes[64] {
48            v @ (0 | 1) => v,
49            27 => 0,
50            28 => 1,
51            other => return Err(SignatureError::BadRecoveryId(other)),
52        };
53        let mut r = [0u8; 32];
54        let mut s = [0u8; 32];
55        r.copy_from_slice(&bytes[..32]);
56        s.copy_from_slice(&bytes[32..64]);
57        Ok(Self { r, s, v })
58    }
59
60    /// The 32-byte `r` scalar.
61    pub fn r(&self) -> &[u8; 32] {
62        &self.r
63    }
64
65    /// The 32-byte `s` scalar.
66    pub fn s(&self) -> &[u8; 32] {
67        &self.s
68    }
69
70    /// The recovery id (`0` or `1`).
71    pub fn v(&self) -> u8 {
72        self.v
73    }
74
75    /// Serialize to the 65-byte `r || s || v` wire format.
76    pub fn to_bytes(&self) -> [u8; SIGNATURE_LEN] {
77        let mut out = [0u8; SIGNATURE_LEN];
78        out[..32].copy_from_slice(&self.r);
79        out[32..64].copy_from_slice(&self.s);
80        out[64] = self.v;
81        out
82    }
83
84    /// Recover the non-recoverable [`Signature`] and [`RecoveryId`] components.
85    pub fn split(&self) -> Result<(Signature, RecoveryId), SignatureError> {
86        let mut rs = [0u8; 64];
87        rs[..32].copy_from_slice(&self.r);
88        rs[32..].copy_from_slice(&self.s);
89        let sig = Signature::from_slice(&rs)?;
90        let recid = RecoveryId::from_byte(self.v).ok_or(SignatureError::BadRecoveryId(self.v))?;
91        Ok((sig, recid))
92    }
93}
94
95impl fmt::Debug for RecoverableSignature {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(
98            f,
99            "RecoverableSignature(0x{})",
100            hex::encode(self.to_bytes())
101        )
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use k256::ecdsa::{SigningKey, signature::hazmat::PrehashSigner};
108
109    use super::*;
110
111    #[test]
112    fn bytes_roundtrip() {
113        let mut bytes = [7u8; SIGNATURE_LEN];
114        bytes[64] = 1;
115        let sig = RecoverableSignature::from_bytes(&bytes).unwrap();
116        assert_eq!(sig.to_bytes(), bytes);
117        assert_eq!(sig.v(), 1);
118    }
119
120    #[test]
121    fn normalises_eth_v() {
122        let mut bytes = [3u8; SIGNATURE_LEN];
123        bytes[64] = 28;
124        let sig = RecoverableSignature::from_bytes(&bytes).unwrap();
125        assert_eq!(sig.v(), 1);
126    }
127
128    #[test]
129    fn bad_length_and_recid() {
130        assert!(matches!(
131            RecoverableSignature::from_bytes(&[0u8; 10]),
132            Err(SignatureError::BadLength(10))
133        ));
134        let mut bytes = [0u8; SIGNATURE_LEN];
135        bytes[64] = 5;
136        assert!(matches!(
137            RecoverableSignature::from_bytes(&bytes),
138            Err(SignatureError::BadRecoveryId(5))
139        ));
140    }
141
142    #[test]
143    fn from_signature_and_split() {
144        let signing = SigningKey::from_bytes(&[1u8; 32].into()).unwrap();
145        let (sig, recid): (Signature, RecoveryId) = signing.sign_prehash(&[9u8; 32]).unwrap();
146        let rec = RecoverableSignature::from_signature(&sig, recid);
147        let (sig2, recid2) = rec.split().unwrap();
148        assert_eq!(sig, sig2);
149        assert_eq!(recid.to_byte(), recid2.to_byte());
150    }
151}