Skip to main content

defuse_sep53/
lib.rs

1//! [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md) Signed Data Standard
2
3use defuse_crypto::{Curve, ed25519::Ed25519};
4use defuse_digest::{Digest, sha2::Sha256};
5
6/// [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md) Signed Data Standard
7pub struct Sep53;
8
9impl Sep53 {
10    /// Verify signature over a given message for given public key according to
11    /// [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md).
12    #[must_use = "check if verification passed"]
13    #[inline]
14    pub fn verify(
15        public_key: &<Ed25519 as Curve>::PublicKey,
16        msg: impl AsRef<[u8]>,
17        signature: &<Ed25519 as Curve>::Signature,
18    ) -> bool {
19        Ed25519::verify(public_key, &Self::prehash(msg.as_ref()), signature)
20    }
21
22    /// Derive prehash for signing according to following schema:
23    ///
24    /// ```text
25    /// <"Stellar Signed Message:\n"> <data to sign>
26    /// ```
27    #[inline]
28    pub fn prehash(msg: impl AsRef<[u8]>) -> [u8; 32] {
29        Sha256::new_with_prefix(b"Stellar Signed Message:\n")
30            // <data to sign>
31            .chain_update(msg)
32            .finalize()
33            .into()
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use defuse_crypto::ed25519::{Ed25519PublicKey, Ed25519Signature};
40    use hex_literal::hex;
41    use rstest::rstest;
42    use stellar_strkey::Strkey;
43
44    use super::*;
45
46    // https://github.com/stellar/stellar-protocol/blob/1b1c22e02fc0cec6fff1175c2d7d08ad83a828e1/ecosystem/sep-0053.md#test-cases
47    #[rstest]
48    #[case::ascii(
49        "GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L",
50        "Hello, World!",
51        hex!("7cee5d6d885752104c85eea421dfdcb95abf01f1271d11c4bec3fcbd7874dccd6e2e98b97b8eb23b643cac4073bb77de5d07b0710139180ae9f3cbba78f2ba04"),
52    )]
53    #[case::japanese(
54        "GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L",
55        "こんにちは、世界!",
56        hex!("083536eb95ecf32dce59b07fe7a1fd8cf814b2ce46f40d2a16e4ea1f6cecd980e04e6fbef9d21f98011c785a81edb85f3776a6e7d942b435eb0adc07da4d4604"),
57    )]
58    #[case::binary(
59        "GBXFXNDLV4LSWA4VB7YIL5GBD7BVNR22SGBTDKMO2SBZZHDXSKZYCP7L",
60        hex!("db36433f5b1ad415417cb3fb4de78c937b146dac4091484184388d76b92c685a"),
61        hex!("540d7eee179f370bf634a49c1fa9fe4a58e3d7990b0207be336c04edfcc539ff8bd0c31bb2c0359b07c9651cb2ae104e4504657b5d17d43c69c7e50e23811b0d"),
62    )]
63    fn verify_ok(
64        #[case] address: &str,
65        #[case] msg: impl AsRef<[u8]>,
66        #[case] signature: impl Into<Ed25519Signature>,
67    ) {
68        let Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(public_key)) =
69            address.parse().unwrap()
70        else {
71            panic!("invalid ed25519 public key address");
72        };
73
74        assert!(
75            Sep53::verify(
76                &Ed25519PublicKey(public_key).try_into().unwrap(),
77                msg,
78                &signature.into().into(),
79            ),
80            "invalid signature",
81        );
82    }
83}