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