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