essential_signer/
lib.rs

1//! # Essential Signer
2//!
3//! This crate provides a simple API for signing data with various cryptographic schemes.
4//! It is designed to be used with the Essential protocol.
5//! There is functionality to make word aligning data for use in decision variables easy.
6//!
7//! Note that all hashing in this crate is done with sha256.
8//! This may change in the future to allow more types of hashing.
9//! You can use `sign_hash` if you wish to hash the data with a different algorithm.
10
11#![deny(missing_docs)]
12#![deny(unsafe_code)]
13
14use anyhow::ensure;
15use clap::ValueEnum;
16use essential_types::{convert::word_4_from_u8_32, Hash, Word};
17use serde::{Deserialize, Serialize};
18use sha2::Digest;
19
20pub use ed25519_dalek;
21pub use secp256k1;
22
23#[derive(ValueEnum, Clone, Copy, Debug)]
24/// The encoding to use when decoding or encoding a string.
25pub enum Encoding {
26    /// The data is encoded as a json sting of bytes.
27    /// For example `"[104, 22, 33]"`.
28    /// This is not the most efficient encoding but easy to write by hand.
29    Bytes,
30    /// Hexadecimal encoding.
31    Hex,
32    /// Uppercase hexadecimal encoding.
33    HexUpper,
34    /// Standard base64 encoding.
35    Base64,
36    /// Base64 encoding with URL safe characters and no padding.
37    /// Note this means no padding characters are used not
38    /// that the data is not word aligned.
39    Base64UrlNoPad,
40}
41
42#[derive(ValueEnum, Clone, Copy, Debug)]
43/// Where to pad the data to make it word aligned.
44pub enum Padding {
45    /// Pad the start of the data.
46    Start,
47    /// Pad the end of the data.
48    End,
49}
50
51#[derive(Clone, Copy)]
52/// Different types of private keys that can be used for signing.
53pub enum Key {
54    /// A secp256k1 key.
55    Secp256k1(secp256k1::SecretKey),
56    /// An ed25519 key.
57    Ed25519(ed25519_dalek::SecretKey),
58}
59
60#[derive(Clone, Copy, Debug)]
61/// Different types of public keys.
62pub enum PublicKey {
63    /// A secp256k1 key.
64    Secp256k1(secp256k1::PublicKey),
65    /// An ed25519 key.
66    Ed25519(ed25519_dalek::VerifyingKey),
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
70/// Different types of signatures that can be produced.
71pub enum Signature {
72    /// A secp256k1 signature.
73    Secp256k1(secp256k1::ecdsa::RecoverableSignature),
74    /// An ed25519 signature.
75    Ed25519(ed25519_dalek::Signature),
76}
77
78/// Sign data by serializing it using postcard and then hashing and signing the hash.
79///
80/// This does **not** pad the data to be word aligned.
81pub fn sign_postcard<T: Serialize>(data: &T, private_key: &Key) -> anyhow::Result<Signature> {
82    let data = postcard_bytes(data)?;
83    let hash = hash_bytes(&data)?;
84    sign_hash(hash, private_key)
85}
86
87/// Sign data by serializing it using postcard and then hashing and signing the hash.
88///
89/// This pads the data to be word aligned.
90pub fn sign_postcard_with_padding<T: Serialize>(
91    data: &T,
92    padding: Padding,
93    private_key: &Key,
94) -> anyhow::Result<Signature> {
95    let data = postcard_bytes_with_padding(data, padding)?;
96    let hash = hash_bytes(&data)?;
97    sign_hash(hash, private_key)
98}
99
100/// Sign a slice of words by hashing and signing the hash.
101pub fn sign_words(data: &[Word], private_key: &Key) -> anyhow::Result<Signature> {
102    let hash = hash_words(data);
103    sign_hash(hash, private_key)
104}
105
106/// Sign the data by padding it to be word aligned and then hashing and signing the hash.
107///
108/// If the data is already word aligned no padding will occur.
109pub fn sign_bytes_with_padding(
110    data: Vec<u8>,
111    padding: Padding,
112    private_key: &Key,
113) -> anyhow::Result<Signature> {
114    let data = align_to_word(data, padding);
115    let hash = hash_bytes(&data)?;
116    sign_hash(hash, private_key)
117}
118
119/// Sign already word aligned data by hashing and signing the hash.
120///
121/// If the data is not word aligned an error will be returned.
122pub fn sign_aligned_bytes(data: &[u8], private_key: &Key) -> anyhow::Result<Signature> {
123    ensure!(is_word_aligned(data), "Data is not word aligned");
124    let hash = hash_bytes(data)?;
125    sign_hash(hash, private_key)
126}
127
128/// Sign the data by hashing and signing the hash.
129///
130/// This does **not** check if the data is word aligned.
131pub fn sign_bytes_unchecked(data: &[u8], private_key: &Key) -> anyhow::Result<Signature> {
132    let hash = hash_bytes(data)?;
133    sign_hash(hash, private_key)
134}
135
136/// Sign a already hashed data.
137pub fn sign_hash(hash: Hash, private_key: &Key) -> anyhow::Result<Signature> {
138    match private_key {
139        Key::Secp256k1(private_key) => {
140            let sig = essential_sign::sign_hash(hash, private_key);
141            let sig = secp256k1::ecdsa::RecoverableSignature::from_compact(
142                &sig.0,
143                secp256k1::ecdsa::RecoveryId::try_from(sig.1 as i32)?,
144            )?;
145            Ok(Signature::Secp256k1(sig))
146        }
147        Key::Ed25519(_) => todo!(),
148    }
149}
150
151/// Read a file into a vector of bytes.
152pub fn read_file(path: &std::path::Path) -> anyhow::Result<Vec<u8>> {
153    use std::io::Read;
154    let mut file = std::fs::File::open(path)?;
155    let mut data = Vec::new();
156    file.read_to_end(&mut data)?;
157    Ok(data)
158}
159
160#[derive(Deserialize, Serialize)]
161struct Bytes(#[serde(with = "serde_bytes")] Vec<u8>);
162
163/// Decode a string into a vector of bytes using the given encoding.
164pub fn decode_str(data: String, encoding: Encoding) -> anyhow::Result<Vec<u8>> {
165    match encoding {
166        Encoding::Bytes => {
167            let Bytes(data) = serde_json::from_str(&data)?;
168            Ok(data)
169        }
170        Encoding::Hex | Encoding::HexUpper => Ok(hex::decode(data)?),
171        Encoding::Base64 => {
172            use base64::engine::general_purpose::STANDARD;
173            use base64::Engine;
174            Ok(STANDARD.decode(data.as_bytes())?)
175        }
176        Encoding::Base64UrlNoPad => {
177            use base64::engine::general_purpose::URL_SAFE_NO_PAD;
178            use base64::Engine;
179            Ok(URL_SAFE_NO_PAD.decode(data.as_bytes())?)
180        }
181    }
182}
183
184/// Encode a vector of bytes into a string using the given encoding.
185pub fn encode_str(data: Vec<u8>, encoding: Encoding) -> anyhow::Result<String> {
186    match encoding {
187        Encoding::Bytes => Ok(serde_json::to_string(&Bytes(data))?),
188        Encoding::Hex => Ok(hex::encode(data)),
189        Encoding::HexUpper => Ok(hex::encode_upper(data)),
190        Encoding::Base64 => {
191            use base64::engine::general_purpose::STANDARD;
192            use base64::Engine;
193            Ok(STANDARD.encode(data))
194        }
195        Encoding::Base64UrlNoPad => {
196            use base64::engine::general_purpose::URL_SAFE_NO_PAD;
197            use base64::Engine;
198            Ok(URL_SAFE_NO_PAD.encode(data))
199        }
200    }
201}
202
203/// Align and convert the data to words.
204pub fn into_words(data: Vec<u8>, padding: Padding) -> Vec<Word> {
205    let data = align_to_word(data, padding);
206    data.chunks(8)
207        .map(|chunk| {
208            essential_types::convert::word_from_bytes(
209                chunk.try_into().expect("This can't fail because of chunks"),
210            )
211        })
212        .collect::<Vec<Word>>()
213}
214
215/// Align the data to be word aligned.
216/// This will pad the data with zeros at the start or end depending on the padding.
217pub fn align_to_word(data: Vec<u8>, padding: Padding) -> Vec<u8> {
218    if is_word_aligned(&data) {
219        data
220    } else {
221        pad_bytes(data, padding)
222    }
223}
224
225/// Check if the data is word aligned.
226pub fn is_word_aligned(data: &[u8]) -> bool {
227    data.len() % 8 == 0
228}
229
230/// Pad the data to be word aligned.
231///
232/// Note it's cheaper to use `align_to_word` if the data might already be word aligned.
233pub fn pad_bytes(mut data: Vec<u8>, padding: Padding) -> Vec<u8> {
234    match padding {
235        Padding::Start => {
236            let len = data.len();
237            let pad = 8 - len % 8;
238            let mut padded = vec![0; pad];
239            padded.extend(data);
240            padded
241        }
242        Padding::End => {
243            let len = data.len();
244            let pad = 8 - len % 8;
245            data.extend(std::iter::repeat(0).take(pad));
246            data
247        }
248    }
249}
250
251/// Hash the data using sha256.
252///
253/// This does **not** pad or check if the data is word aligned.
254pub fn hash_bytes(data: &[u8]) -> anyhow::Result<Hash> {
255    let mut hasher = <sha2::Sha256 as sha2::Digest>::new();
256    hasher.update(data);
257    Ok(hasher.finalize().into())
258}
259
260/// Hash the words using sha256.
261pub fn hash_words(data: &[Word]) -> Hash {
262    essential_hash::hash_words(data)
263}
264
265/// Turn a secp256k1 signature into an essential signature.
266pub fn to_essential_signature(
267    sig: secp256k1::ecdsa::RecoverableSignature,
268) -> anyhow::Result<essential_types::Signature> {
269    let (rec_id, data) = sig.serialize_compact();
270    Ok(essential_types::Signature(
271        data,
272        i32::from(rec_id).try_into()?,
273    ))
274}
275
276/// Turn any supported signature into bytes that are padded to be word aligned.
277///
278/// This is the same layout that the `essential-constraint-vm` expects.
279pub fn signature_to_aligned_bytes(sig: &Signature) -> Vec<u8> {
280    match sig {
281        Signature::Secp256k1(sig) => essential_sign::encode::signature_as_bytes(sig).to_vec(),
282        Signature::Ed25519(sig) => sig.to_bytes().to_vec(),
283    }
284}
285
286/// Turn any supported signature into bytes.
287///
288/// This is **not** padded to be word aligned.
289pub fn signature_to_bytes(sig: &Signature) -> anyhow::Result<Vec<u8>> {
290    match sig {
291        Signature::Secp256k1(sig) => {
292            let (rec_id, data) = sig.serialize_compact();
293            let mut bytes = data.to_vec();
294            let rec_id: i32 = rec_id.into();
295            let rec_id: u8 = rec_id.try_into()?;
296            bytes.push(rec_id);
297            Ok(bytes)
298        }
299        Signature::Ed25519(sig) => Ok(sig.to_bytes().to_vec()),
300    }
301}
302
303/// Serialize a signed contract to json bytes.
304///
305/// This can be directly submitted to the api.
306pub fn signed_set_to_bytes(
307    signed_set: &essential_types::contract::SignedContract,
308) -> anyhow::Result<Vec<u8>> {
309    Ok(serde_json::to_vec(signed_set)?)
310}
311
312/// Turn any supported signature into words.
313pub fn signature_to_words(sig: &Signature) -> Vec<Word> {
314    match sig {
315        Signature::Secp256k1(sig) => essential_sign::encode::signature(sig).to_vec(),
316        Signature::Ed25519(_) => todo!(),
317    }
318}
319
320/// Turn any supported public key into bytes that are padded to be word aligned.
321///
322/// This is the same layout that the `essential-constraint-vm` expects.
323pub fn public_key_to_words(key: &PublicKey) -> Vec<Word> {
324    match key {
325        PublicKey::Secp256k1(key) => essential_sign::encode::public_key(key).to_vec(),
326        PublicKey::Ed25519(key) => word_4_from_u8_32(key.to_bytes()).to_vec(),
327    }
328}
329
330/// Serialize data using postcard and then pad it to be word aligned.
331pub fn postcard_bytes_with_padding<T: Serialize>(
332    data: &T,
333    padding: Padding,
334) -> anyhow::Result<Vec<u8>> {
335    let data = postcard::to_allocvec(data)?;
336    Ok(align_to_word(data, padding))
337}
338
339/// Serialize data using postcard.
340pub fn postcard_bytes<T: Serialize>(data: &T) -> anyhow::Result<Vec<u8>> {
341    Ok(postcard::to_allocvec(data)?)
342}
343
344/// Get the public key from a private key.
345pub fn public_key(private_key: &Key) -> PublicKey {
346    match private_key {
347        Key::Secp256k1(key) => {
348            let secp = secp256k1::Secp256k1::new();
349            PublicKey::Secp256k1(key.public_key(&secp))
350        }
351        Key::Ed25519(key) => {
352            let key = ed25519_dalek::SigningKey::from_bytes(key);
353            PublicKey::Ed25519(key.verifying_key())
354        }
355    }
356}