1use defuse_crypto::{Curve, Ed25519};
2use impl_tools::autoimpl;
3
4#[cfg_attr(
6 feature = "serde",
7 derive(::serde::Serialize, ::serde::Deserialize),
8 cfg_attr(feature = "abi", derive(::schemars::JsonSchema)),
9 serde(rename_all = "snake_case")
10)]
11#[derive(Debug, Clone)]
12pub struct Sep53Payload {
13 pub payload: String,
14}
15
16impl Sep53Payload {
17 #[inline]
18 pub const fn new(payload: String) -> Self {
19 Self { payload }
20 }
21}
22
23impl defuse_crypto::Payload for Sep53Payload {
24 #[inline]
25 fn hash(&self) -> defuse_crypto::CryptoHash {
26 use defuse_digest::{Digest, sha2::Sha256};
27
28 Sha256::new_with_prefix(b"Stellar Signed Message:\n")
29 .chain_update(self.payload.as_bytes())
30 .finalize()
31 .into()
32 }
33}
34
35#[cfg_attr(
36 feature = "serde",
37 ::cfg_eval::cfg_eval,
38 ::serde_with::serde_as,
39 derive(::serde::Serialize, ::serde::Deserialize),
40 cfg_attr(feature = "abi", derive(::schemars::JsonSchema))
41)]
42#[autoimpl(Deref using self.payload)]
43#[derive(Debug, Clone)]
44pub struct SignedSep53Payload {
45 #[cfg_attr(feature = "serde", serde(flatten))]
46 pub payload: Sep53Payload,
47
48 #[cfg_attr(
49 feature = "serde",
50 serde_as(as = "defuse_crypto::serde::AsCurve<Ed25519>")
51 )]
52 pub public_key: <Ed25519 as Curve>::PublicKey,
53 #[cfg_attr(
54 feature = "serde",
55 serde_as(as = "defuse_crypto::serde::AsCurve<Ed25519>")
56 )]
57 pub signature: <Ed25519 as Curve>::Signature,
58}
59
60impl defuse_crypto::Payload for SignedSep53Payload {
61 #[inline]
62 fn hash(&self) -> defuse_crypto::CryptoHash {
63 self.payload.hash()
64 }
65}
66
67#[cfg(any(test, feature = "near-contract"))]
68impl defuse_crypto::SignedPayload for SignedSep53Payload {
69 type PublicKey = <Ed25519 as Curve>::PublicKey;
70
71 #[inline]
72 fn verify(&self) -> Option<Self::PublicKey> {
73 use defuse_crypto::{Payload, VerifiableCurve};
74 Ed25519::verify(&self.signature, &self.hash(), &self.public_key)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use crate::{Sep53Payload, SignedSep53Payload};
81 use base64::{Engine, engine::general_purpose::STANDARD};
82 use defuse_crypto::{Payload, SignedPayload};
83 use defuse_test_utils::random::{CryptoRng, gen_random_string, random_bytes, rng};
84 use defuse_test_utils::tamper::{tamper_bytes, tamper_string};
85 use ed25519_dalek::Verifier;
86 use ed25519_dalek::{SigningKey, ed25519::signature::SignerMut};
87 use near_sdk::base64;
88 use rstest::rstest;
89 use stellar_strkey::Strkey;
90
91 #[test]
92 fn reference_test_vectors() {
93 let seed = "SAKICEVQLYWGSOJS4WW7HZJWAHZVEEBS527LHK5V4MLJALYKICQCJXMW";
95 let raw_key = match Strkey::from_string(seed).unwrap() {
96 Strkey::PrivateKeyEd25519(pk) => pk.0,
97 _ => panic!("expected an Ed25519 seed"),
98 };
99
100 let mut signing_key = SigningKey::from_bytes(&raw_key);
102 let verifying_key = signing_key.verifying_key();
103
104 let vectors = [
105 (
106 "Hello, World!",
107 "fO5dbYhXUhBMhe6kId/cuVq/AfEnHRHEvsP8vXh03M1uLpi5e46yO2Q8rEBzu3feXQewcQE5GArp88u6ePK6BA==",
108 ),
109 (
110 "こんにちは、世界!",
111 "CDU265Xs8y3OWbB/56H9jPgUss5G9A0qFuTqH2zs2YDgTm+++dIfmAEceFqB7bhfN3am59lCtDXrCtwH2k1GBA==",
112 ),
113 ];
115
116 for (msg, expected_b64) in vectors {
118 let mut payload = "Stellar Signed Message:\n".to_string();
119 payload += msg;
120
121 let hash = near_sdk::env::sha256_array(payload.as_bytes());
122 let sig = signing_key.sign(hash.as_ref());
123 let actual_b64 = STANDARD.encode(sig.to_bytes());
124
125 assert_eq!(actual_b64, *expected_b64);
126 assert!(verifying_key.verify(hash.as_ref(), &sig).is_ok());
127 }
128
129 for (msg, expected_sig_b64) in vectors {
131 let payload = Sep53Payload::new(msg.to_string());
132
133 let hash = payload.hash();
134 let secret_key = near_crypto::SecretKey::ED25519(near_crypto::ED25519SecretKey(
135 signing_key
136 .as_bytes()
137 .iter()
138 .chain(verifying_key.as_bytes())
139 .copied()
140 .collect::<Vec<_>>()
141 .try_into()
142 .unwrap(),
143 ));
144 let generic_sig = secret_key.sign(hash.as_ref());
145 let sig = match generic_sig {
146 near_crypto::Signature::ED25519(signature) => signature,
147 near_crypto::Signature::SECP256K1(_) => unreachable!(),
148 };
149
150 let actual_sig_b64 = STANDARD.encode(sig.to_bytes());
151
152 assert_eq!(actual_sig_b64, *expected_sig_b64);
153 assert!(generic_sig.verify(hash.as_ref(), &secret_key.public_key()));
154
155 let signed_payload = SignedSep53Payload {
156 payload,
157 public_key: verifying_key.as_bytes().to_owned(),
158 signature: sig.to_bytes(),
159 };
160
161 assert_eq!(
162 signed_payload.verify(),
163 Some(verifying_key.as_bytes().to_owned())
164 );
165 }
166 }
167
168 fn make_ed25519_key(rng: &mut impl CryptoRng) -> near_crypto::SecretKey {
170 let key_len = ed25519_dalek::SECRET_KEY_LENGTH;
173 let bytes = random_bytes(key_len..=key_len, rng);
174 let signing_key = SigningKey::from_bytes(&bytes.try_into().unwrap());
175 let verifying_key = signing_key.verifying_key();
176
177 near_crypto::SecretKey::ED25519(near_crypto::ED25519SecretKey(
178 signing_key
179 .as_bytes()
180 .iter()
181 .chain(verifying_key.as_bytes())
182 .copied()
183 .collect::<Vec<_>>()
184 .try_into()
185 .unwrap(),
186 ))
187 }
188
189 #[rstest]
190 fn tampered_message_fails(mut rng: impl CryptoRng) {
191 let sk = make_ed25519_key(&mut rng);
192 let pk = sk.public_key();
193
194 let msg = gen_random_string(&mut rng, 100..1000);
195
196 let payload = Sep53Payload::new(msg.clone());
198 let hash = payload.hash();
199 let sig = match sk.sign(hash.as_ref()) {
200 near_crypto::Signature::ED25519(signature) => signature,
201 near_crypto::Signature::SECP256K1(_) => unreachable!(),
202 };
203
204 {
205 let signed_good = SignedSep53Payload {
206 payload,
207 public_key: pk.key_data().try_into().unwrap(),
208 signature: sig.to_bytes(),
209 };
210 assert!(signed_good.verify().is_some());
211 }
212
213 {
215 let tempered_message = tamper_string(&mut rng, &msg);
216
217 let bad_payload = Sep53Payload::new(tempered_message);
219 let signed_bad = SignedSep53Payload {
220 payload: bad_payload,
221 public_key: pk.key_data().try_into().unwrap(),
222 signature: sig.to_bytes(),
223 };
224 assert_eq!(signed_bad.verify(), None);
225 }
226 }
227
228 #[rstest]
229 fn tampered_signature_fails(mut rng: impl CryptoRng) {
230 let sk = make_ed25519_key(&mut rng);
231 let pk = sk.public_key();
232
233 let msg = gen_random_string(&mut rng, 100..1000);
234
235 let payload = Sep53Payload::new(msg);
237 let hash = payload.hash();
238 let sig = match sk.sign(hash.as_ref()) {
239 near_crypto::Signature::ED25519(signature) => signature,
240 near_crypto::Signature::SECP256K1(_) => unreachable!(),
241 };
242
243 {
244 let signed_good = SignedSep53Payload {
245 payload: payload.clone(),
246 public_key: pk.key_data().try_into().unwrap(),
247 signature: sig.into(),
248 };
249 assert!(signed_good.verify().is_some());
250 }
251
252 {
254 let bad_bytes = tamper_bytes(&mut rng, sig.to_bytes().as_ref(), false);
255
256 let signed_bad = SignedSep53Payload {
257 payload,
258 public_key: pk.key_data().try_into().unwrap(),
259 signature: bad_bytes.try_into().unwrap(),
260 };
261 assert!(signed_bad.verify().is_none());
262 }
263 }
264}