ergo_lib/wallet/
secret_key.rs

1//! Secret types
2
3use derive_more::From;
4use ergo_chain_types::EcPoint;
5use ergotree_interpreter::sigma_protocol::private_input::DhTupleProverInput;
6use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
7use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
8use ergotree_ir::chain::address::Address;
9use ergotree_ir::ergo_tree::ErgoTree;
10use ergotree_ir::mir::constant::Constant;
11use ergotree_ir::mir::expr::Expr;
12use ergotree_ir::serialization::SigmaSerializable;
13use thiserror::Error;
14
15/// Types of secrets
16#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "json", serde(untagged))]
18#[derive(PartialEq, Eq, Debug, Clone, From)]
19pub enum SecretKey {
20    /// Secret exponent of a group element, i.e. secret w such as h = g^^w, where g is group generator,
21    /// h is a public key.
22    DlogSecretKey(DlogProverInput),
23    /// Diffie-Hellman tuple and secret
24    /// Used in a proof that of equality of discrete logarithms (i.e., a proof of a Diffie-Hellman tuple):
25    /// given group elements g, h, u, v, the proof convinces a verifier that the prover knows `w` such
26    /// that `u = g^w` and `v = h^w`, without revealing `w`
27    DhtSecretKey(DhTupleProverInput),
28}
29
30impl SecretKey {
31    /// Generates random DlogProverInput
32    pub fn random_dlog() -> SecretKey {
33        SecretKey::DlogSecretKey(DlogProverInput::random())
34    }
35
36    /// Generates random DhTupleProverInput
37    pub fn random_dht() -> SecretKey {
38        SecretKey::DhtSecretKey(DhTupleProverInput::random())
39    }
40
41    /// Parse DlogSecretKey from bytes (SEC-1-encoded scalar)
42    pub fn dlog_from_bytes(bytes: &[u8; DlogProverInput::SIZE_BYTES]) -> Option<SecretKey> {
43        DlogProverInput::from_bytes(bytes).map(SecretKey::DlogSecretKey)
44    }
45
46    /// Parse DhtSecretKey  from bytes
47    /// expected 32(secret)+33(g)+33(h)+33(u)+33(v)=164 bytes
48    /// secret is expected as SEC-1-encoded scalar of 32 bytes,
49    /// g,h,u,v are expected as 33-byte compressed points
50    pub fn dht_from_bytes(bytes: &[u8; DhTupleProverInput::SIZE_BYTES]) -> Option<SecretKey> {
51        DhTupleProverInput::from_bytes(bytes).map(SecretKey::DhtSecretKey)
52    }
53
54    /// Parse from bytes
55    /// secret is expected as SEC-1-encoded scalar of 32 bytes,
56    /// g,h,u,v are expected as 33-byte compressed points
57    pub fn dht_from_bytes_fields(
58        w_bytes: &[u8; DlogProverInput::SIZE_BYTES],
59        g_bytes: &[u8; EcPoint::GROUP_SIZE],
60        h_bytes: &[u8; EcPoint::GROUP_SIZE],
61        u_bytes: &[u8; EcPoint::GROUP_SIZE],
62        v_bytes: &[u8; EcPoint::GROUP_SIZE],
63    ) -> Option<SecretKey> {
64        DhTupleProverInput::from_bytes_fields(w_bytes, g_bytes, h_bytes, u_bytes, v_bytes)
65            .map(SecretKey::DhtSecretKey)
66    }
67
68    /// Address from public image:
69    /// P2PK address for DlogSecretKey
70    /// P2S address with ProveDhTuple ergo tree for DhtSecretKey
71    #[allow(clippy::unwrap_used)]
72    pub fn get_address_from_public_image(&self) -> Address {
73        match self {
74            SecretKey::DlogSecretKey(dlog) => Address::P2Pk(dlog.public_image()),
75            SecretKey::DhtSecretKey(dht) => {
76                let expr: Expr = Constant::from(dht.public_image().clone()).into();
77                let ergo_tree: ErgoTree = expr.try_into().unwrap();
78                Address::P2S(ergo_tree.sigma_serialize_bytes().unwrap())
79            }
80        }
81    }
82
83    /// Encode from a serialized key
84    pub fn to_bytes(&self) -> Vec<u8> {
85        match self {
86            SecretKey::DlogSecretKey(dlog) => dlog.to_bytes().to_vec(),
87            SecretKey::DhtSecretKey(dht) => dht.to_bytes().to_vec(),
88        }
89    }
90
91    /// Parse secret key from bytes
92    /// expected 32 bytes for Dlog, 32(secret)+33(g)+33(h)+33(u)+33(v)=164 bytes for DHT
93    /// secret is expected as SEC-1-encoded scalar of 32 bytes,
94    /// g,h,u,v are expected as 33-byte compressed points
95    pub fn from_bytes(bytes: &[u8]) -> Result<SecretKey, SecretKeyParsingError> {
96        if bytes.len() == DlogProverInput::SIZE_BYTES {
97            let mut dlog_bytes = [0u8; DlogProverInput::SIZE_BYTES];
98            dlog_bytes.copy_from_slice(bytes);
99            Ok(SecretKey::dlog_from_bytes(&dlog_bytes)
100                .ok_or(SecretKeyParsingError::DlogParsingError)?)
101        } else if bytes.len() == DhTupleProverInput::SIZE_BYTES {
102            let mut dht_bytes = [0u8; DhTupleProverInput::SIZE_BYTES];
103            dht_bytes.copy_from_slice(bytes);
104            Ok(SecretKey::DhtSecretKey(
105                DhTupleProverInput::from_bytes(&dht_bytes)
106                    .ok_or(SecretKeyParsingError::DhtParsingError)?,
107            ))
108        } else {
109            Err(SecretKeyParsingError::InvalidLength)
110        }
111    }
112}
113
114/// Error type for SecretKey parsing
115#[allow(missing_docs)]
116#[derive(Error, Eq, PartialEq, Debug, Clone)]
117pub enum SecretKeyParsingError {
118    #[error("Dlog parsing error")]
119    DlogParsingError,
120    #[error("DHT secret parsing error")]
121    DhtParsingError,
122    #[error("Invalid length, expected either 32(Dlog) or 164(DHT) bytes")]
123    InvalidLength,
124}
125
126impl From<SecretKey> for PrivateInput {
127    fn from(s: SecretKey) -> Self {
128        match s {
129            SecretKey::DlogSecretKey(dlog) => PrivateInput::DlogProverInput(dlog),
130            SecretKey::DhtSecretKey(dht) => PrivateInput::DhTupleProverInput(dht),
131        }
132    }
133}
134
135#[cfg(test)]
136#[allow(clippy::unwrap_used)]
137mod tests {
138    use super::*;
139    use std::convert::TryInto;
140
141    #[test]
142    fn dlog_roundtrip() {
143        let sk = SecretKey::random_dlog();
144        let sk_copy1 =
145            SecretKey::dlog_from_bytes(&sk.to_bytes().as_slice().try_into().unwrap()).unwrap();
146        let sk_copy2 = SecretKey::from_bytes(sk.to_bytes().as_slice()).unwrap();
147        assert_eq!(sk, sk_copy1);
148        assert_eq!(sk, sk_copy2);
149    }
150
151    #[test]
152    fn dht_roundtrip() {
153        let sk = SecretKey::random_dht();
154        let sk_copy1 =
155            SecretKey::dht_from_bytes(&sk.to_bytes().as_slice().try_into().unwrap()).unwrap();
156        let sk_copy2 = SecretKey::from_bytes(sk.to_bytes().as_slice()).unwrap();
157        assert_eq!(sk, sk_copy1);
158        assert_eq!(sk, sk_copy2);
159    }
160}
161
162#[cfg(test)]
163#[allow(clippy::unwrap_used)]
164#[cfg(feature = "json")]
165mod json_tests {
166    use crate::wallet::secret_key::SecretKey;
167    use pretty_assertions::assert_eq;
168
169    #[test]
170    fn json_dlog_roundtrip() {
171        let sk = SecretKey::random_dlog();
172        let sk_json = serde_json::to_string(&sk).unwrap();
173        //dbg!(&sk_json);
174        let sk_copy: SecretKey = serde_json::from_str(&sk_json).unwrap();
175        assert_eq!(sk, sk_copy);
176    }
177
178    #[test]
179    fn json_dht_roundtrip() {
180        let sk = SecretKey::random_dht();
181        let sk_json = serde_json::to_string(&sk).unwrap();
182        //dbg!(&sk_json);
183        let sk_copy: SecretKey = serde_json::from_str(&sk_json).unwrap();
184        assert_eq!(sk, sk_copy);
185    }
186
187    #[test]
188    fn json_dht_golden() {
189        let sk_json = r#"{
190  "secret": "b2a93a9a37b4656c7abf4e259b9c066cd8bf4e02449d5956aaf453a73764bfeb",
191  "g": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
192  "h": "0288a812f57b66b4c68fd9e097c79a6e2847013fa4112a43c45cd41c9ba8c79b69",
193  "u": "0381fff110959bc06d99c5580c462c0196d8434bc5d470fb10a160019276e648c2",
194  "v": "02c6626ad387bb6b2eccc2fdd238c97ee4a81bfded401843bc8bb71f1cc7269924"
195}"#;
196        let sk: SecretKey = serde_json::from_str(sk_json).unwrap();
197        assert!(matches!(sk, SecretKey::DhtSecretKey(_)));
198        let sk_json_copy = serde_json::to_string_pretty(&sk).unwrap();
199        assert_eq!(sk_json, sk_json_copy);
200    }
201
202    #[test]
203    fn json_dlog_golden() {
204        let sk_json = r#""0cd81ce156fed4017520e561e9c492222027751ed0dd71b5a9b3a61da68b5850""#;
205        //dbg!(&sk_json);
206        let sk: SecretKey = serde_json::from_str(sk_json).unwrap();
207        assert!(matches!(sk, SecretKey::DlogSecretKey(_)));
208        let sk_json_copy = serde_json::to_string_pretty(&sk).unwrap();
209        assert_eq!(sk_json, sk_json_copy);
210    }
211}