diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index 9de73ee..59a11e7 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -36,6 +36,21 @@ jobs: command: check test_nightly: + name: Nightly tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --release + + test_nightly_canon: name: Nightly tests runs-on: ubuntu-latest steps: @@ -50,6 +65,36 @@ jobs: command: test args: --release --features canon + test_nightly_nostd: + name: Nightly tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --release --no-default-features + + test_nightly_nostd_canon: + name: Nightly tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --release --no-default-features --features canon + fmt: name: Rustfmt runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..37c23cb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.0] - 17-11-20 +### Changed +- No-Std compatibility. diff --git a/Cargo.toml b/Cargo.toml index 6744e6b..ec2dca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,35 @@ [package] name = "dusk-pki" -version = "0.3.1" +version = "0.4.0" authors = ["zer0 ", "Victor Lopez for io::Error { - fn from(err: Error) -> io::Error { - io::Error::new(io::ErrorKind::Other, format!("{}", err)) +#[cfg(feature = "std")] +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Dusk PKI Error: {:?}", &self) } } diff --git a/src/lib.rs b/src/lib.rs index 1f73451..91739d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,10 +13,12 @@ //! This repository has been created so there's a unique library that holds the //! types and functions required to perform keys operations. +#![cfg_attr(not(feature = "std"), no_std)] #![deny(missing_docs)] #![allow(non_snake_case)] pub use decode::decode as jubjub_decode; + /// PKI Errors pub use errors::Error; /// Public Spend Key @@ -30,8 +32,8 @@ pub use view::ViewKey; mod decode; mod errors; +mod permutation; mod spend; -mod sponge; mod view; use dusk_jubjub::{JubJubAffine, JubJubExtended, JubJubScalar}; diff --git a/src/sponge.rs b/src/permutation.rs similarity index 53% rename from src/sponge.rs rename to src/permutation.rs index 7c2d098..47c1615 100644 --- a/src/sponge.rs +++ b/src/permutation.rs @@ -5,9 +5,21 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::{JubJubExtended, JubJubScalar}; -use poseidon252::sponge::sponge::sponge_hash; + +use dusk_bls12_381::BlsScalar; +use hades252::{ScalarStrategy, Strategy}; + +use core::cmp; /// Hashes a JubJub's ExtendedPoint into a JubJub's Scalar pub fn hash(p: &JubJubExtended) -> JubJubScalar { - JubJubScalar::from_raw(sponge_hash(&p.to_hash_inputs()).reduce().0) + let mut perm = [BlsScalar::zero(); hades252::WIDTH]; + let p = p.to_hash_inputs(); + + let n = cmp::min(hades252::WIDTH, p.len()); + + perm[0..n].copy_from_slice(&p[0..n]); + ScalarStrategy::new().perm(&mut perm); + + JubJubScalar::from_raw(perm[1].reduce().0) } diff --git a/src/spend/public.rs b/src/spend/public.rs index 18599fc..9a004f4 100644 --- a/src/spend/public.rs +++ b/src/spend/public.rs @@ -4,23 +4,26 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use super::secret::SecretKey; -use super::stealth::StealthAddress; - -use crate::sponge; +#[cfg(feature = "std")] +use crate::Error; use crate::{ - decode::decode, Error, JubJubAffine, JubJubExtended, JubJubScalar, + permutation, JubJubAffine, JubJubExtended, JubJubScalar, StealthAddress, }; +use super::secret::SecretKey; + #[cfg(feature = "canon")] use canonical::Canon; #[cfg(feature = "canon")] use canonical_derive::Canon; + use dusk_jubjub::GENERATOR_EXTENDED; -use std::convert::TryFrom; -use std::fmt; use subtle::{Choice, ConstantTimeEq}; +#[cfg(feature = "std")] +use core::convert::TryFrom; +use core::fmt; + /// Public pair of `a·G` and `b·G` #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "canon", derive(Canon))] @@ -52,7 +55,7 @@ impl PublicKey { let R = G * r; let rA = self.A * r; - let rA = sponge::hash(&rA); + let rA = permutation::hash(&rA); let rA = G * rA; let pk_r = rA + self.B; @@ -102,10 +105,13 @@ impl From<&PublicKey> for [u8; 64] { } } +#[cfg(feature = "std")] impl TryFrom for PublicKey { type Error = Error; fn try_from(s: String) -> Result { + use crate::decode::decode; + if s.len() != 128 { return Err(Error::BadLength { found: s.len(), diff --git a/src/spend/secret.rs b/src/spend/secret.rs index 2c72759..1036f44 100644 --- a/src/spend/secret.rs +++ b/src/spend/secret.rs @@ -4,39 +4,32 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use crate::{permutation, JubJubScalar, ViewKey}; + use super::public::PublicKey; use super::stealth::StealthAddress; -use crate::sponge; -use crate::{JubJubScalar, ViewKey}; - -use dusk_jubjub::GENERATOR_EXTENDED; - -use std::fmt; - #[cfg(feature = "canon")] use canonical::Canon; #[cfg(feature = "canon")] use canonical_derive::Canon; -use rand::rngs::StdRng; + +use dusk_jubjub::GENERATOR_EXTENDED; +use rand_core::{CryptoRng, RngCore}; + +use core::fmt; + +#[cfg(feature = "std")] use rand::SeedableRng; -use rand::{CryptoRng, RngCore}; -use sha2::{Digest, Sha256}; /// Secret pair of `a` and `b` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "canon", derive(Canon))] pub struct SecretKey { a: JubJubScalar, b: JubJubScalar, } -impl Default for SecretKey { - fn default() -> Self { - SecretKey::random(&mut rand::thread_rng()) - } -} - impl SecretKey { /// This method is used to construct a new `SecretKey` from the given secret /// pair of `a` and `b`. @@ -54,13 +47,6 @@ impl SecretKey { &self.b } - /// Generate a `sk_r = H(a · R) + b` - pub fn sk_r(&self, sa: &StealthAddress) -> JubJubScalar { - let aR = sa.R() * self.a; - let aR = sponge::hash(&aR); - aR + self.b - } - /// Deterministically create a new [`SecretKey`] from a random number /// generator pub fn random(rng: &mut R) -> Self { @@ -70,6 +56,14 @@ impl SecretKey { SecretKey::new(a, b) } + /// Generate a `sk_r = H(a · R) + b` + pub fn sk_r(&self, sa: &StealthAddress) -> JubJubScalar { + let aR = sa.R() * self.a; + let aR = permutation::hash(&aR); + + aR + self.b + } + /// Derive the secret to deterministically construct a [`PublicKey`] pub fn public_key(&self) -> PublicKey { let A = GENERATOR_EXTENDED * self.a; @@ -95,8 +89,12 @@ impl From<&SecretKey> for [u8; 64] { } } +#[cfg(feature = "std")] impl From<&[u8]> for SecretKey { fn from(bytes: &[u8]) -> Self { + use rand::rngs::StdRng; + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::default(); hasher.input(bytes); let bytes = hasher.result(); @@ -108,6 +106,7 @@ impl From<&[u8]> for SecretKey { } } +#[cfg(feature = "std")] impl From for SecretKey { fn from(s: String) -> Self { Self::from(s.into_bytes().as_slice()) diff --git a/src/spend/stealth.rs b/src/spend/stealth.rs index c866cfc..b0af333 100644 --- a/src/spend/stealth.rs +++ b/src/spend/stealth.rs @@ -10,16 +10,11 @@ use crate::{decode::decode, Error, JubJubAffine, JubJubExtended}; use canonical::Canon; #[cfg(feature = "canon")] use canonical_derive::Canon; -use std::convert::{TryFrom, TryInto}; -use std::fmt; + use subtle::{Choice, ConstantTimeEq}; -/// The trait `Ownable` is required by any type that wants to prove its -/// ownership. -pub trait Ownable { - /// Returns the associated `StealthAddress` - fn stealth_address(&self) -> &StealthAddress; -} +use core::convert::{TryFrom, TryInto}; +use core::fmt; //. To obfuscate the identity of the participants, we utilizes a Stealth Address //. system. @@ -32,12 +27,40 @@ pub struct StealthAddress { pub(crate) pk_r: JubJubExtended, } +/// The trait `Ownable` is required by any type that wants to prove its +/// ownership. +pub trait Ownable { + /// Returns the associated `StealthAddress` + fn stealth_address(&self) -> &StealthAddress; +} + impl Ownable for StealthAddress { fn stealth_address(&self) -> &StealthAddress { &self } } +impl From<&StealthAddress> for [u8; 64] { + fn from(sa: &StealthAddress) -> [u8; 64] { + let mut bytes = [0u8; 64]; + bytes[..32].copy_from_slice(&JubJubAffine::from(sa.R).to_bytes()[..]); + bytes[32..] + .copy_from_slice(&JubJubAffine::from(sa.pk_r).to_bytes()[..]); + bytes + } +} + +impl TryFrom<&[u8; 64]> for StealthAddress { + type Error = Error; + + fn try_from(bytes: &[u8; 64]) -> Result { + let R = JubJubExtended::from(decode::(&bytes[..32])?); + let pk_r = JubJubExtended::from(decode::(&bytes[32..])?); + + Ok(StealthAddress { R, pk_r }) + } +} + impl StealthAddress { /// Gets the random point `R` pub fn R(&self) -> &JubJubExtended { @@ -77,50 +100,6 @@ impl PartialEq for StealthAddress { } } -impl From<&StealthAddress> for [u8; 64] { - fn from(sa: &StealthAddress) -> [u8; 64] { - let mut bytes = [0u8; 64]; - bytes[..32].copy_from_slice(&JubJubAffine::from(sa.R).to_bytes()[..]); - bytes[32..] - .copy_from_slice(&JubJubAffine::from(sa.pk_r).to_bytes()[..]); - bytes - } -} - -impl TryFrom<&[u8; 64]> for StealthAddress { - type Error = Error; - - fn try_from(bytes: &[u8; 64]) -> Result { - let R = JubJubExtended::from(decode::(&bytes[..32])?); - let pk_r = JubJubExtended::from(decode::(&bytes[32..])?); - - Ok(StealthAddress { R, pk_r }) - } -} - -impl TryFrom for StealthAddress { - type Error = Error; - - fn try_from(s: String) -> Result { - if s.len() != 128 { - return Err(Error::BadLength { - found: s.len(), - expected: 128, - }); - } - - let s = s.as_str(); - - let R = hex::decode(&s[..64]).map_err(|_| Error::InvalidPoint)?; - let R = JubJubExtended::from(decode::(&R[..])?); - - let pk_r = hex::decode(&s[64..]).map_err(|_| Error::InvalidPoint)?; - let pk_r = JubJubExtended::from(decode::(&pk_r[..])?); - - Ok(StealthAddress { R, pk_r }) - } -} - impl fmt::LowerHex for StealthAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let bytes: [u8; 64] = self.into(); @@ -158,3 +137,27 @@ impl fmt::Display for StealthAddress { write!(f, "{:x}", self) } } + +#[cfg(feature = "std")] +impl TryFrom for StealthAddress { + type Error = Error; + + fn try_from(s: String) -> Result { + if s.len() != 128 { + return Err(Error::BadLength { + found: s.len(), + expected: 128, + }); + } + + let s = s.as_str(); + + let R = hex::decode(&s[..64]).map_err(|_| Error::InvalidPoint)?; + let R = JubJubExtended::from(decode::(&R[..])?); + + let pk_r = hex::decode(&s[64..]).map_err(|_| Error::InvalidPoint)?; + let pk_r = JubJubExtended::from(decode::(&pk_r[..])?); + + Ok(StealthAddress { R, pk_r }) + } +} diff --git a/src/view.rs b/src/view.rs index 4b200ab..461e7af 100644 --- a/src/view.rs +++ b/src/view.rs @@ -5,18 +5,20 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::spend::stealth; -use crate::sponge; - +#[cfg(feature = "std")] +use crate::Error; use crate::{ - decode::decode, Error, JubJubAffine, JubJubExtended, JubJubScalar, - PublicSpendKey, SecretSpendKey, + permutation, JubJubAffine, JubJubExtended, JubJubScalar, PublicSpendKey, + SecretSpendKey, }; use dusk_jubjub::GENERATOR_EXTENDED; -use std::convert::TryFrom; -use std::fmt; use subtle::{Choice, ConstantTimeEq}; +#[cfg(feature = "std")] +use core::convert::TryFrom; +use core::fmt; + /// Pair of a secret `a` and public `b·G` /// /// The notes are encrypted against secret a, so this is used to decrypt the @@ -29,6 +31,7 @@ pub struct ViewKey { impl ConstantTimeEq for ViewKey { fn ct_eq(&self, other: &Self) -> Choice { + // TODO - Why self.a is not checked? self.B.ct_eq(&other.B) } } @@ -76,7 +79,7 @@ impl ViewKey { let sa = owner.stealth_address(); let aR = sa.R() * self.a(); - let aR = sponge::hash(&aR); + let aR = permutation::hash(&aR); let aR = GENERATOR_EXTENDED * aR; let pk_r = aR + self.B(); @@ -105,10 +108,13 @@ impl From<&ViewKey> for [u8; 64] { } } +#[cfg(feature = "std")] impl TryFrom for ViewKey { type Error = Error; fn try_from(s: String) -> Result { + use crate::decode::decode; + if s.len() != 128 { return Err(Error::BadLength { found: s.len(), diff --git a/tests/tests.rs b/tests/tests.rs index 90e851b..129d530 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -4,56 +4,57 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use dusk_pki::{PublicSpendKey, SecretSpendKey, ViewKey}; +#[cfg(feature = "std")] +mod std_tests { + use core::convert::TryFrom; + use dusk_pki::{PublicSpendKey, SecretSpendKey, ViewKey}; -use std::convert::TryFrom; + #[test] + fn ssk_from_bytes() { + let bytes = b"some bytes".to_vec(); -#[test] -fn ssk_from_bytes() { - let bytes = b"some bytes".to_vec(); + let ssk_a = SecretSpendKey::from(&bytes[..]); + let ssk_b = SecretSpendKey::from(&bytes[..]); - let ssk_a = SecretSpendKey::from(&bytes[..]); - let ssk_b = SecretSpendKey::from(&bytes[..]); + assert_eq!(ssk_a, ssk_b); + } - assert_eq!(ssk_a, ssk_b); -} - -#[test] -fn keys_encoding() -> anyhow::Result<()> { - let bytes = b"some bytes".to_vec(); + #[test] + fn keys_encoding() { + let bytes = b"some bytes".to_vec(); - let ssk = SecretSpendKey::from(bytes.as_slice()); - let vk = ssk.view_key(); - let psk = ssk.public_key(); + let ssk = SecretSpendKey::from(bytes.as_slice()); + let vk = ssk.view_key(); + let psk = ssk.public_key(); - assert_eq!(vk, ViewKey::try_from(format!("{}", vk))?); - assert_eq!(psk, PublicSpendKey::try_from(format!("{}", psk))?); - Ok(()) -} + assert_eq!(vk, ViewKey::try_from(format!("{}", vk)).unwrap()); + assert_eq!(psk, PublicSpendKey::try_from(format!("{}", psk)).unwrap()); + } -#[test] -fn keys_consistency() { - use dusk_jubjub::{JubJubScalar, GENERATOR_EXTENDED}; + #[test] + fn keys_consistency() { + use dusk_jubjub::{JubJubScalar, GENERATOR_EXTENDED}; - let r = JubJubScalar::random(&mut rand::thread_rng()); - let ssk = SecretSpendKey::default(); - let psk = ssk.public_key(); - let vk = ssk.view_key(); - let sa = psk.gen_stealth_address(&r); + let r = JubJubScalar::random(&mut rand::thread_rng()); + let ssk = SecretSpendKey::random(&mut rand::thread_rng()); + let psk = ssk.public_key(); + let vk = ssk.view_key(); + let sa = psk.gen_stealth_address(&r); - assert!(vk.owns(&sa)); + assert!(vk.owns(&sa)); - let wrong_ssk = SecretSpendKey::default(); - let wrong_vk = wrong_ssk.view_key(); + let wrong_ssk = SecretSpendKey::random(&mut rand::thread_rng()); + let wrong_vk = wrong_ssk.view_key(); - assert_ne!(ssk, wrong_ssk); - assert_ne!(vk, wrong_vk); + assert_ne!(ssk, wrong_ssk); + assert_ne!(vk, wrong_vk); - assert!(!wrong_vk.owns(&sa)); + assert!(!wrong_vk.owns(&sa)); - let sk_r = ssk.sk_r(&sa); - let wrong_sk_r = wrong_ssk.sk_r(&sa); + let sk_r = ssk.sk_r(&sa); + let wrong_sk_r = wrong_ssk.sk_r(&sa); - assert_eq!(sa.pk_r(), &(GENERATOR_EXTENDED * &sk_r)); - assert_ne!(sa.pk_r(), &(GENERATOR_EXTENDED * &wrong_sk_r)); + assert_eq!(sa.pk_r(), &(GENERATOR_EXTENDED * &sk_r)); + assert_ne!(sa.pk_r(), &(GENERATOR_EXTENDED * &wrong_sk_r)); + } }