ssi_jwk/
aleo.rs

1//! Functionality related to [Aleo] blockchain network.
2//!
3//! Required crate feature: `aleosig`
4//!
5//! [Aleo]: https://developer.aleo.org/testnet/getting_started/overview#the-network
6//!
7//! This module provides [sign] and [verify] functions for Aleo signatures
8//! using static parameters ([struct@COM_PARAMS], [struct@ENC_PARAMS], [struct@SIG_PARAMS])
9//! and a [JWK-based keypair representation](OKP_CURVE).
10
11use crate::{Base64urlUInt, OctetParams, Params, JWK};
12use thiserror::Error;
13
14use blake2::Blake2s;
15use snarkvm_algorithms::{
16    commitment::{PedersenCommitmentParameters, PedersenCompressedCommitment},
17    encryption::{GroupEncryption, GroupEncryptionParameters},
18    signature::{Schnorr, SchnorrParameters, SchnorrSignature},
19};
20use snarkvm_curves::edwards_bls12::{EdwardsAffine, EdwardsProjective};
21use snarkvm_dpc::{
22    account::{Address, PrivateKey, ViewKey},
23    testnet1::instantiated::Components,
24};
25use snarkvm_parameters::{
26    global::{
27        AccountCommitmentParameters, AccountEncryptionParameters, AccountSignatureParameters,
28    },
29    Parameter,
30};
31use snarkvm_utilities::{FromBytes, ToBytes};
32use std::str::FromStr;
33
34/// An error resulting from attempting to [sign a message using an Aleo private key](sign).
35#[derive(Error, Debug)]
36pub enum AleoSignError {
37    #[error("Unable to convert JWK to Aleo private key: {0}")]
38    JWKToPrivateKey(#[source] ParsePrivateKeyError),
39    #[error("Unable to convert Aleo private key to view key: {0}")]
40    ViewKeyFromPrivateKey(#[source] snarkvm_dpc::AccountError),
41    #[error("Unable to sign with view key: {0}")]
42    Sign(#[source] snarkvm_dpc::AccountError),
43    #[error("Unable to write signture as bytes: {0}")]
44    WriteSignature(#[source] std::io::Error),
45}
46
47/// An error resulting from attempting to [verify a signature from an Aleo account](verify).
48#[derive(Error, Debug)]
49pub enum AleoVerifyError {
50    #[error("Invalid signature over message")]
51    InvalidSignature,
52    #[error("Unable to verify signature: {0}")]
53    VerifySignature(#[source] snarkvm_dpc::AccountError),
54    #[error("Unable to deserialize account address: {0}")]
55    AddressFromStr(#[source] snarkvm_dpc::AccountError),
56    #[error("Unable to read signature bytes: {0}")]
57    ReadSignature(#[source] std::io::Error),
58}
59
60/// An error resulting from attempting to [generate a JWK Aleo private key](generate_private_key_jwk).
61#[derive(Error, Debug)]
62pub enum AleoGeneratePrivateKeyError {
63    #[error("Unable to generate new key: {0}")]
64    NewKey(#[source] snarkvm_dpc::AccountError),
65    #[error("Unable to base58-decode new key: {0}")]
66    DecodePrivateKey(#[source] bs58::decode::Error),
67    #[error("Unable to convert private key to account address: {0}")]
68    PrivateKeyToAddress(#[source] snarkvm_dpc::AccountError),
69    #[error("Unable to write account address as bytes: {0}")]
70    WriteAddress(#[source] std::io::Error),
71}
72
73/// An error resulting from attempting to convert a [JWK] to an Aleo private key.
74///
75/// The expected JWK format is described in [OKP_CURVE].
76#[derive(Error, Debug)]
77pub enum ParsePrivateKeyError {
78    #[error("Unexpected JWK OKP curve: {0}")]
79    UnexpectedCurve(String),
80    #[error("Unexpected JWK key type. Expected \"OKP\"")]
81    ExpectedOKP,
82    #[error("Missing private key (\"d\") OKP JWK parameter")]
83    MissingPrivateKey,
84    #[error("Unable to deserialize private key: {0}")]
85    PrivateKeyFromStr(#[source] snarkvm_dpc::AccountError),
86    #[error("Unable to convert JWK to account address: {0}")]
87    JWKToAddress(#[source] ParseAddressError),
88    #[error("Unable to convert private key to account address: {0}")]
89    PrivateKeyToAddress(#[source] snarkvm_dpc::AccountError),
90    #[error("Address mismatch. Computed: {}, expected: {}", .computed, .expected)]
91    AddressMismatch {
92        computed: Box<Address<Components>>,
93        expected: Box<Address<Components>>,
94    },
95}
96
97/// An error resulting from attempting to convert a [JWK] to an Aleo account address.
98///
99/// The expected JWK format is described in [OKP_CURVE].
100#[derive(Error, Debug)]
101pub enum ParseAddressError {
102    #[error("Unexpected JWK OKP curve: {0}")]
103    UnexpectedCurve(String),
104    #[error("Unexpected JWK key type. Expected \"OKP\"")]
105    ExpectedOKP,
106    #[error("Unable to read address from bytes: {0}")]
107    ReadAddress(#[source] std::io::Error),
108}
109
110lazy_static::lazy_static! {
111    /// Aleo account signature parameters
112    pub static ref SIG_PARAMS: Schnorr<EdwardsAffine, Blake2s> = {
113        SchnorrParameters::read_le(AccountSignatureParameters::load_bytes().unwrap().as_slice())
114            .unwrap()
115            .into()
116    };
117
118    /// Aleo account commitment parameters
119    pub static ref COM_PARAMS: PedersenCompressedCommitment<EdwardsProjective, 8, 192> = {
120            let com_params_bytes = AccountCommitmentParameters::load_bytes().unwrap();
121        PedersenCommitmentParameters::read_le(com_params_bytes.as_slice())
122            .unwrap()
123            .into()
124    };
125
126    /// Aleo account encryption parameters
127    pub static ref ENC_PARAMS: GroupEncryption<EdwardsProjective, EdwardsAffine, Blake2s> = {
128        let enc_params_bytes = AccountEncryptionParameters::load_bytes()
129                .unwrap();
130        GroupEncryptionParameters::read_le(
131            enc_params_bytes
132                .as_slice(),
133        )
134        .unwrap()
135        .into()
136    };
137}
138
139/// Unregistered JWK OKP curve for Aleo private keys in Aleo Testnet 1
140///
141/// OKP key type is defined in [RFC 8037].
142///
143/// [RFC 8037]: https://datatracker.ietf.org/doc/html/rfc8037
144///
145/// This curve type is intended to be used for Aleo private keys as follows:
146///
147/// - key type ("kty"): "OKP"
148/// - private key ("d") parameter: base64url-encoded Aleo private key (without Base58 encoding)
149/// - public key ("x") parameter: base64url-encoded Aleo account address (without Base58 encoding)
150///
151/// An Aleo private key JWK is expected to contain an account address in the public key ("x")
152/// parameter that corresponds to the private key ("d") parameter,
153/// using [struct@SIG_PARAMS], [struct@COM_PARAMS] and [struct@ENC_PARAMS].
154///
155/// An Aleo public key JWK contains the public key ("x") parameter and MUST not contain a private
156/// key ("d") parameter. An Aleo public key JWK is usable for verification of signatures using
157/// [struct@ENC_PARAMS].
158pub const OKP_CURVE: &str = "AleoTestnet1Key";
159
160/// Generate an Aleo private key in [unofficial JWK format][OKP_CURVE]. **CPU-intensive (slow)**.
161///
162/// Uses [struct@SIG_PARAMS], [struct@COM_PARAMS], and [struct@ENC_PARAMS].
163pub fn generate_private_key_jwk() -> Result<JWK, AleoGeneratePrivateKeyError> {
164    let mut rng = rand::rngs::OsRng {};
165    let sig_params = SIG_PARAMS.clone();
166    let com_params = COM_PARAMS.clone();
167    let enc_params = ENC_PARAMS.clone();
168    let private_key = PrivateKey::<Components>::new(&sig_params, &com_params, &mut rng)
169        .map_err(AleoGeneratePrivateKeyError::NewKey)?;
170    let private_key_bytes = bs58::decode(private_key.to_string())
171        .into_vec()
172        .map_err(AleoGeneratePrivateKeyError::DecodePrivateKey)?;
173    let address = Address::from_private_key(&sig_params, &com_params, &enc_params, &private_key)
174        .map_err(AleoGeneratePrivateKeyError::PrivateKeyToAddress)?;
175    let mut public_key_bytes = Vec::new();
176    address
177        .write_le(&mut public_key_bytes)
178        .map_err(AleoGeneratePrivateKeyError::WriteAddress)?;
179    Ok(JWK::from(Params::OKP(OctetParams {
180        curve: OKP_CURVE.to_string(),
181        public_key: Base64urlUInt(public_key_bytes),
182        private_key: Some(Base64urlUInt(private_key_bytes)),
183    })))
184}
185
186/// Convert JWK private key to Aleo private key
187///
188/// Uses [struct@SIG_PARAMS], [struct@COM_PARAMS], and [struct@ENC_PARAMS] to compute the account address.
189fn aleo_jwk_to_private_key(jwk: &JWK) -> Result<PrivateKey<Components>, ParsePrivateKeyError> {
190    let params = match &jwk.params {
191        Params::OKP(ref okp_params) => {
192            if okp_params.curve != OKP_CURVE {
193                return Err(ParsePrivateKeyError::UnexpectedCurve(
194                    okp_params.curve.to_string(),
195                ));
196            }
197            okp_params
198        }
199        _ => return Err(ParsePrivateKeyError::ExpectedOKP),
200    };
201    let private_key_bytes = params
202        .private_key
203        .as_ref()
204        .ok_or(ParsePrivateKeyError::MissingPrivateKey)?;
205    let private_key_base58 = bs58::encode(&private_key_bytes.0).into_string();
206    let address = aleo_jwk_to_address(jwk).map_err(ParsePrivateKeyError::JWKToAddress)?;
207    let private_key = PrivateKey::<Components>::from_str(&private_key_base58)
208        .map_err(ParsePrivateKeyError::PrivateKeyFromStr)?;
209    let address_computed = Address::from_private_key(
210        &SIG_PARAMS.clone(),
211        &COM_PARAMS.clone(),
212        &ENC_PARAMS.clone(),
213        &private_key,
214    )
215    .map_err(ParsePrivateKeyError::PrivateKeyToAddress)?;
216    if address_computed != address {
217        return Err(ParsePrivateKeyError::AddressMismatch {
218            computed: Box::new(address_computed),
219            expected: Box::new(address),
220        });
221    }
222    Ok(private_key)
223}
224
225fn aleo_jwk_to_address(jwk: &JWK) -> Result<Address<Components>, ParseAddressError> {
226    let params = match &jwk.params {
227        Params::OKP(ref okp_params) => {
228            if okp_params.curve != OKP_CURVE {
229                return Err(ParseAddressError::UnexpectedCurve(
230                    okp_params.curve.to_string(),
231                ));
232            }
233            okp_params
234        }
235        _ => return Err(ParseAddressError::ExpectedOKP),
236    };
237    let public_key_bytes = &params.public_key.0;
238    let address = Address::<Components>::read_le(&**public_key_bytes)
239        .map_err(ParseAddressError::ReadAddress)?;
240    Ok(address)
241}
242
243/// Create an Aleo signature.
244///
245/// The message is signed using [struct@ENC_PARAMS] and a View Key derived from the given JWK private key with [struct@SIG_PARAMS] and [struct@COM_PARAMS].
246///
247/// The JWK private key `key` is expected to use key type `OKP` with curve according to
248/// [OKP_CURVE].
249pub fn sign(msg: &[u8], key: &JWK) -> Result<Vec<u8>, AleoSignError> {
250    let private_key = aleo_jwk_to_private_key(key).map_err(AleoSignError::JWKToPrivateKey)?;
251    let enc_params = ENC_PARAMS.clone();
252    let sig_params = SIG_PARAMS.clone();
253    let com_params = COM_PARAMS.clone();
254    let view_key = ViewKey::<Components>::from_private_key(&sig_params, &com_params, &private_key)
255        .map_err(AleoSignError::ViewKeyFromPrivateKey)?;
256    let mut rng = rand::rngs::OsRng {};
257    let sig = view_key
258        .sign(&enc_params, msg, &mut rng)
259        .map_err(AleoSignError::Sign)?;
260    let mut sig_bytes = Vec::new();
261    sig.write_le(&mut sig_bytes)
262        .map_err(AleoSignError::WriteSignature)?;
263    Ok(sig_bytes)
264}
265
266/// Verify an Aleo signature by an Aleo address as a string.
267///
268/// Verification uses [struct@ENC_PARAMS].
269pub fn verify(msg: &[u8], address: &str, sig: &[u8]) -> Result<(), AleoVerifyError> {
270    let address =
271        Address::<Components>::from_str(address).map_err(AleoVerifyError::AddressFromStr)?;
272    let sig =
273        SchnorrSignature::<EdwardsAffine>::read_le(sig).map_err(AleoVerifyError::ReadSignature)?;
274    let enc_params = ENC_PARAMS.clone();
275    let valid = address
276        .verify_signature(&enc_params, msg, &sig)
277        .map_err(AleoVerifyError::VerifySignature)?;
278    if !valid {
279        return Err(AleoVerifyError::InvalidSignature);
280    }
281    Ok(())
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn parse_private_key_jwk() {
290        let key: JWK =
291            serde_json::from_str(include_str!("../../../tests/aleotestnet1-2021-11-22.json"))
292                .unwrap();
293        let private_key = aleo_jwk_to_private_key(&key).unwrap();
294        let private_key_str = private_key.to_string();
295        assert_eq!(
296            private_key_str,
297            "APrivateKey1w7oJWmo86D26Efs6hBfz8xK7M4ww2jmA5WT3QdmYefVnZdS"
298        );
299        let address = Address::from_private_key(
300            &SIG_PARAMS.clone(),
301            &COM_PARAMS.clone(),
302            &ENC_PARAMS.clone(),
303            &private_key,
304        )
305        .unwrap();
306        assert_eq!(
307            address.to_string(),
308            "aleo1al8unplh8vtsuwna0h6u2t6g0hvr7t0tnfkem2we5gj7t70aeuxsd94hsy"
309        );
310    }
311
312    #[test]
313    fn aleo_jwk_sign_verify() {
314        let private_key: JWK =
315            serde_json::from_str(include_str!("../../../tests/aleotestnet1-2021-11-22.json"))
316                .unwrap();
317
318        let public_key = private_key.to_public();
319        let msg1 = b"asdf";
320        let msg2 = b"asdfg";
321        let sig = sign(msg1, &private_key).unwrap();
322        let address = aleo_jwk_to_address(&public_key).unwrap();
323        let address_string = format!("{}", &address);
324        verify(msg1, &address_string, &sig).unwrap();
325        verify(msg2, &address_string, &sig).unwrap_err();
326    }
327}