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