1use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
8use sha2::{Digest, Sha256};
9use std::fmt;
10
11const P_LE: [u8; 32] = [
13 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
14 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F,
15];
16
17fn enc_is_canonical(enc: &[u8; 32]) -> bool {
20 let mut y = *enc;
21 y[31] &= 0x7F;
22 for i in (0..32).rev() {
23 if y[i] != P_LE[i] {
24 return y[i] < P_LE[i];
25 }
26 }
27 false }
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33#[repr(u8)]
34pub enum SignatureDomain {
35 Transaction = 0,
36 BlockHeader = 1,
37 Block = 2,
38 Gossip = 3,
39 NodeRecord = 4,
40 EoaCreate = 5,
41 EoaDelete = 6,
42 Msg = 7,
44}
45
46impl SignatureDomain {
47 pub fn dst(self) -> &'static [u8; 16] {
53 match self {
54 SignatureDomain::Transaction => b"tn_txn_sign_v1__",
55 SignatureDomain::BlockHeader => b"tn_blkhdr_sig_v1",
56 SignatureDomain::Block => b"tn_block_sig_v1_",
57 SignatureDomain::Gossip => b"tn_gossip_sig_v1",
58 SignatureDomain::NodeRecord => b"tn_node_rec_v1__",
59 SignatureDomain::EoaCreate => b"tn_eoa_create_v1",
60 SignatureDomain::EoaDelete => b"tn_eoa_delete_v1",
61 SignatureDomain::Msg => b"tn_msg_sign_v1__",
62 }
63 }
64}
65
66#[derive(Debug)]
67pub enum TnSignatureError {
68 InvalidSignature,
69 InvalidPublicKey,
70 InvalidScalar,
71}
72
73impl fmt::Display for TnSignatureError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 TnSignatureError::InvalidSignature => write!(f, "invalid signature"),
77 TnSignatureError::InvalidPublicKey => write!(f, "invalid public key"),
78 TnSignatureError::InvalidScalar => write!(f, "invalid scalar"),
79 }
80 }
81}
82
83impl std::error::Error for TnSignatureError {}
84
85fn build_msg(domain: SignatureDomain, msg: &[u8]) -> Vec<u8> {
88 let dst = domain.dst();
89 match domain {
90 SignatureDomain::Transaction => {
92 let digest = Sha256::digest(msg);
93 let mut m = Vec::with_capacity(dst.len() + digest.len());
94 m.extend_from_slice(dst);
95 m.extend_from_slice(&digest);
96 m
97 }
98 _ => {
101 let mut m = Vec::with_capacity(dst.len() + msg.len());
102 m.extend_from_slice(dst);
103 m.extend_from_slice(msg);
104 m
105 }
106 }
107}
108
109pub fn sign(
113 domain: SignatureDomain,
114 msg: &[u8],
115 public_key: &[u8; 32],
116 private_key: &[u8; 32],
117) -> Result<[u8; 64], TnSignatureError> {
118 let signing_key = SigningKey::from_bytes(private_key);
119 if signing_key.verifying_key().to_bytes() != *public_key {
122 return Err(TnSignatureError::InvalidPublicKey);
123 }
124 let m = build_msg(domain, msg);
125 let sig: Signature = signing_key.sign(&m);
126 Ok(sig.to_bytes())
127}
128
129pub fn verify(
133 domain: SignatureDomain,
134 msg: &[u8],
135 sig: &[u8; 64],
136 public_key: &[u8; 32],
137) -> Result<(), TnSignatureError> {
138 if !enc_is_canonical(public_key) {
139 return Err(TnSignatureError::InvalidPublicKey);
140 }
141 let r_bytes: [u8; 32] = sig[..32].try_into().unwrap();
142 if !enc_is_canonical(&r_bytes) {
143 return Err(TnSignatureError::InvalidSignature);
144 }
145
146 let verifying_key =
147 VerifyingKey::from_bytes(public_key).map_err(|_| TnSignatureError::InvalidPublicKey)?;
148 let signature = Signature::from_bytes(sig);
149 let m = build_msg(domain, msg);
150 verifying_key
151 .verify_strict(&m, &signature)
152 .map_err(|_| TnSignatureError::InvalidSignature)
153}
154
155pub fn verify_message(
159 m: &[u8],
160 sig: &[u8; 64],
161 public_key: &[u8; 32],
162) -> Result<(), TnSignatureError> {
163 if !enc_is_canonical(public_key) {
164 return Err(TnSignatureError::InvalidPublicKey);
165 }
166 let r_bytes: [u8; 32] = sig[..32].try_into().unwrap();
167 if !enc_is_canonical(&r_bytes) {
168 return Err(TnSignatureError::InvalidSignature);
169 }
170 let verifying_key =
171 VerifyingKey::from_bytes(public_key).map_err(|_| TnSignatureError::InvalidPublicKey)?;
172 verifying_key
173 .verify_strict(m, &Signature::from_bytes(sig))
174 .map_err(|_| TnSignatureError::InvalidSignature)
175}
176
177pub fn signing_message(domain: SignatureDomain, msg: &[u8]) -> Vec<u8> {
180 build_msg(domain, msg)
181}
182
183pub fn sign_transaction(
184 msg: &[u8],
185 public_key: &[u8; 32],
186 private_key: &[u8; 32],
187) -> Result<[u8; 64], TnSignatureError> {
188 sign(SignatureDomain::Transaction, msg, public_key, private_key)
189}
190
191pub fn verify_transaction(
192 msg: &[u8],
193 sig: &[u8; 64],
194 public_key: &[u8; 32],
195) -> Result<(), TnSignatureError> {
196 verify(SignatureDomain::Transaction, msg, sig, public_key)
197}
198
199pub fn sign_message(
204 msg: &[u8],
205 public_key: &[u8; 32],
206 private_key: &[u8; 32],
207) -> Result<[u8; 64], TnSignatureError> {
208 sign(SignatureDomain::Msg, msg, public_key, private_key)
209}
210
211pub fn verify_wallet_message(
213 msg: &[u8],
214 sig: &[u8; 64],
215 public_key: &[u8; 32],
216) -> Result<(), TnSignatureError> {
217 verify(SignatureDomain::Msg, msg, sig, public_key)
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 fn hex32(s: &str) -> [u8; 32] {
225 let b = hex_decode(s);
226 b.try_into().unwrap()
227 }
228 fn hex_decode(s: &str) -> Vec<u8> {
229 (0..s.len())
230 .step_by(2)
231 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
232 .collect()
233 }
234
235 #[test]
236 fn round_trip_and_domain_separation() {
237 let seed = [0xA0u8; 32];
238 let sk = SigningKey::from_bytes(&seed);
239 let pk = sk.verifying_key().to_bytes();
240 let msg = b"hello thru";
241 let sig = sign(SignatureDomain::Transaction, msg, &pk, &seed).unwrap();
242 assert!(verify(SignatureDomain::Transaction, msg, &sig, &pk).is_ok());
243 assert!(verify(SignatureDomain::Block, msg, &sig, &pk).is_err());
245 let mut bad = sig;
247 bad[40] ^= 1;
248 assert!(verify(SignatureDomain::Transaction, msg, &bad, &pk).is_err());
249 }
250
251 #[test]
254 fn golden_txn_matches_c() {
255 let seed = hex32("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
256 let pk_expected =
257 hex32("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");
258 let sig_expected = "8edfec88b713fce257ffa9da964d89b43d31b8d179aa1a467bbb32e024461c360bb5daf4f10cf774e439d4446b25297567cddb6bcd08ca931f6fb723b8fdfc0a";
260 let body = b"Thru golden1";
261
262 let sk = SigningKey::from_bytes(&seed);
263 assert_eq!(sk.verifying_key().to_bytes(), pk_expected);
264
265 let sig = sign(SignatureDomain::Transaction, body, &pk_expected, &seed).unwrap();
266 assert_eq!(sig.to_vec(), hex_decode(sig_expected), "Rust txn sig must match C golden");
267 assert!(verify(SignatureDomain::Transaction, body, &sig, &pk_expected).is_ok());
268 }
269
270 #[test]
271 fn rejects_non_canonical() {
272 assert!(!enc_is_canonical(&P_LE)); let mut z = [0u8; 32];
274 assert!(enc_is_canonical(&z)); z[31] = 0x80; assert!(enc_is_canonical(&z));
277 }
278}