use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
use sha2::{Digest, Sha256};
use std::fmt;
const P_LE: [u8; 32] = [
0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F,
];
fn enc_is_canonical(enc: &[u8; 32]) -> bool {
let mut y = *enc;
y[31] &= 0x7F;
for i in (0..32).rev() {
if y[i] != P_LE[i] {
return y[i] < P_LE[i];
}
}
false }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum SignatureDomain {
Transaction = 0,
BlockHeader = 1,
Block = 2,
Gossip = 3,
NodeRecord = 4,
EoaCreate = 5,
EoaDelete = 6,
Msg = 7,
}
impl SignatureDomain {
pub fn dst(self) -> &'static [u8; 16] {
match self {
SignatureDomain::Transaction => b"tn_txn_sign_v1__",
SignatureDomain::BlockHeader => b"tn_blkhdr_sig_v1",
SignatureDomain::Block => b"tn_block_sig_v1_",
SignatureDomain::Gossip => b"tn_gossip_sig_v1",
SignatureDomain::NodeRecord => b"tn_node_rec_v1__",
SignatureDomain::EoaCreate => b"tn_eoa_create_v1",
SignatureDomain::EoaDelete => b"tn_eoa_delete_v1",
SignatureDomain::Msg => b"tn_msg_sign_v1__",
}
}
}
#[derive(Debug)]
pub enum TnSignatureError {
InvalidSignature,
InvalidPublicKey,
InvalidScalar,
}
impl fmt::Display for TnSignatureError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TnSignatureError::InvalidSignature => write!(f, "invalid signature"),
TnSignatureError::InvalidPublicKey => write!(f, "invalid public key"),
TnSignatureError::InvalidScalar => write!(f, "invalid scalar"),
}
}
}
impl std::error::Error for TnSignatureError {}
fn build_msg(domain: SignatureDomain, msg: &[u8]) -> Vec<u8> {
let dst = domain.dst();
match domain {
SignatureDomain::Transaction => {
let digest = Sha256::digest(msg);
let mut m = Vec::with_capacity(dst.len() + digest.len());
m.extend_from_slice(dst);
m.extend_from_slice(&digest);
m
}
_ => {
let mut m = Vec::with_capacity(dst.len() + msg.len());
m.extend_from_slice(dst);
m.extend_from_slice(msg);
m
}
}
}
pub fn sign(
domain: SignatureDomain,
msg: &[u8],
public_key: &[u8; 32],
private_key: &[u8; 32],
) -> Result<[u8; 64], TnSignatureError> {
let signing_key = SigningKey::from_bytes(private_key);
if signing_key.verifying_key().to_bytes() != *public_key {
return Err(TnSignatureError::InvalidPublicKey);
}
let m = build_msg(domain, msg);
let sig: Signature = signing_key.sign(&m);
Ok(sig.to_bytes())
}
pub fn verify(
domain: SignatureDomain,
msg: &[u8],
sig: &[u8; 64],
public_key: &[u8; 32],
) -> Result<(), TnSignatureError> {
if !enc_is_canonical(public_key) {
return Err(TnSignatureError::InvalidPublicKey);
}
let r_bytes: [u8; 32] = sig[..32].try_into().unwrap();
if !enc_is_canonical(&r_bytes) {
return Err(TnSignatureError::InvalidSignature);
}
let verifying_key =
VerifyingKey::from_bytes(public_key).map_err(|_| TnSignatureError::InvalidPublicKey)?;
let signature = Signature::from_bytes(sig);
let m = build_msg(domain, msg);
verifying_key
.verify_strict(&m, &signature)
.map_err(|_| TnSignatureError::InvalidSignature)
}
pub fn verify_message(
m: &[u8],
sig: &[u8; 64],
public_key: &[u8; 32],
) -> Result<(), TnSignatureError> {
if !enc_is_canonical(public_key) {
return Err(TnSignatureError::InvalidPublicKey);
}
let r_bytes: [u8; 32] = sig[..32].try_into().unwrap();
if !enc_is_canonical(&r_bytes) {
return Err(TnSignatureError::InvalidSignature);
}
let verifying_key =
VerifyingKey::from_bytes(public_key).map_err(|_| TnSignatureError::InvalidPublicKey)?;
verifying_key
.verify_strict(m, &Signature::from_bytes(sig))
.map_err(|_| TnSignatureError::InvalidSignature)
}
pub fn signing_message(domain: SignatureDomain, msg: &[u8]) -> Vec<u8> {
build_msg(domain, msg)
}
pub fn sign_transaction(
msg: &[u8],
public_key: &[u8; 32],
private_key: &[u8; 32],
) -> Result<[u8; 64], TnSignatureError> {
sign(SignatureDomain::Transaction, msg, public_key, private_key)
}
pub fn verify_transaction(
msg: &[u8],
sig: &[u8; 64],
public_key: &[u8; 32],
) -> Result<(), TnSignatureError> {
verify(SignatureDomain::Transaction, msg, sig, public_key)
}
pub fn sign_message(
msg: &[u8],
public_key: &[u8; 32],
private_key: &[u8; 32],
) -> Result<[u8; 64], TnSignatureError> {
sign(SignatureDomain::Msg, msg, public_key, private_key)
}
pub fn verify_wallet_message(
msg: &[u8],
sig: &[u8; 64],
public_key: &[u8; 32],
) -> Result<(), TnSignatureError> {
verify(SignatureDomain::Msg, msg, sig, public_key)
}
#[cfg(test)]
mod tests {
use super::*;
fn hex32(s: &str) -> [u8; 32] {
let b = hex_decode(s);
b.try_into().unwrap()
}
fn hex_decode(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn round_trip_and_domain_separation() {
let seed = [0xA0u8; 32];
let sk = SigningKey::from_bytes(&seed);
let pk = sk.verifying_key().to_bytes();
let msg = b"hello thru";
let sig = sign(SignatureDomain::Transaction, msg, &pk, &seed).unwrap();
assert!(verify(SignatureDomain::Transaction, msg, &sig, &pk).is_ok());
assert!(verify(SignatureDomain::Block, msg, &sig, &pk).is_err());
let mut bad = sig;
bad[40] ^= 1;
assert!(verify(SignatureDomain::Transaction, msg, &bad, &pk).is_err());
}
#[test]
fn golden_txn_matches_c() {
let seed = hex32("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
let pk_expected =
hex32("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");
let sig_expected = "8edfec88b713fce257ffa9da964d89b43d31b8d179aa1a467bbb32e024461c360bb5daf4f10cf774e439d4446b25297567cddb6bcd08ca931f6fb723b8fdfc0a";
let body = b"Thru golden1";
let sk = SigningKey::from_bytes(&seed);
assert_eq!(sk.verifying_key().to_bytes(), pk_expected);
let sig = sign(SignatureDomain::Transaction, body, &pk_expected, &seed).unwrap();
assert_eq!(sig.to_vec(), hex_decode(sig_expected), "Rust txn sig must match C golden");
assert!(verify(SignatureDomain::Transaction, body, &sig, &pk_expected).is_ok());
}
#[test]
fn rejects_non_canonical() {
assert!(!enc_is_canonical(&P_LE)); let mut z = [0u8; 32];
assert!(enc_is_canonical(&z)); z[31] = 0x80; assert!(enc_is_canonical(&z));
}
}