diff --git a/wallet-core/Cargo.toml b/wallet-core/Cargo.toml new file mode 100644 index 0000000000..f4e3630552 --- /dev/null +++ b/wallet-core/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "wallet-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +dusk-bytes = "0.1" +bytecheck = { version = "0.6", default-features = false } +execution-core = { version = "0.1", path = "../execution-core/" } +zeroize = { version = "1", default-features = false, features = ["derive"] } +rand_chacha = { version = "0.3", default-features = false } +sha2 = { version = "0.10", default-features = false } + +[dev-dependencies] + +[features] diff --git a/wallet-core/Makefile b/wallet-core/Makefile new file mode 100644 index 0000000000..fffa5c6726 --- /dev/null +++ b/wallet-core/Makefile @@ -0,0 +1,29 @@ +TARGET_DIR:="../../target/dusk" + +all: ## Build the ABI + cargo build + +help: ## Display this help screen + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' + +test: + cargo test --release + + +wasm: ## Build the WASM files + @RUSTFLAGS="$(RUSTFLAGS) --remap-path-prefix $(HOME)= -C link-args=-zstack-size=65536" \ + CARGO_TARGET_DIR=$(TARGET_DIR) \ + cargo +dusk build \ + --release \ + --color=always \ + -Z build-std=core,alloc \ + --target wasm64-unknown-unknown + +clippy: ## Run clippy + @cargo clippy --release -- -D warnings + @cargo clippy --no-default-features --release -- -D warnings + +doc: ## Run doc gen + @cargo doc --release + +.PHONY: all help test diff --git a/wallet-core/src/keys.rs b/wallet-core/src/keys.rs new file mode 100644 index 0000000000..8933040240 --- /dev/null +++ b/wallet-core/src/keys.rs @@ -0,0 +1,98 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! Utilities to derive keys from the seed. + +use rand_chacha::{rand_core::SeedableRng, ChaCha12Rng}; +use sha2::{Digest, Sha256}; + +use execution_core::{ + signatures::bls::SecretKey as BlsSecretKey, + transfer::phoenix::{ + PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + ViewKey as PhoenixViewKey, + }, +}; +use zeroize::Zeroize; + +use crate::RNG_SEED; + +/// Generates a [`BlsSecretKey`] from a seed and index that can be used for +/// staking. +/// +/// The randomness is generated using [`rng_with_index`]. +#[must_use] +pub fn derive_bls_sk(seed: &[u8; RNG_SEED], index: u64) -> BlsSecretKey { + // note that if we change the string used for the rng, all previously + // generated keys will become invalid + BlsSecretKey::random(&mut rng_with_index(seed, index, b"BSK")) +} + +/// Generates a [`PhoenixSecretKey`] from a seed and index. +/// +/// The randomness is generated using [`rng_with_index`]. +#[must_use] +pub fn derive_phoenix_sk( + seed: &[u8; RNG_SEED], + index: u64, +) -> PhoenixSecretKey { + // note that if we change the string used for the rng, all previously + // generated keys will become invalid + PhoenixSecretKey::random(&mut rng_with_index(seed, index, b"PSK")) +} + +/// Generates a [`PheonixPublicKey`] from its seed and index. +/// +/// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`], then +/// the public key is generated from it and the secret key is erased from +/// memory. +#[must_use] +pub fn derive_phoenix_pk( + seed: &[u8; RNG_SEED], + index: u64, +) -> PhoenixPublicKey { + let mut sk = derive_phoenix_sk(seed, index); + let pk = PhoenixPublicKey::from(&sk); + sk.zeroize(); + + pk +} + +/// Generates a [`PhoenixViewKey`] from its seed and index. +/// +/// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`], then +/// the view key is generated from it and the secret key is erased from memory. +#[must_use] +pub fn derive_phoenix_vk(seed: &[u8; RNG_SEED], index: u64) -> PhoenixViewKey { + let mut sk = derive_phoenix_sk(seed, index); + let vk = PhoenixViewKey::from(&sk); + sk.zeroize(); + + vk +} + +/// Creates a secure RNG from a seed with embedded index and termination +/// constant. +/// +/// First the `seed` and then the little-endian representation of the key's +/// `index` are passed through SHA-256. A constant is then mixed in and the +/// resulting hash is then used to seed a `ChaCha12` CSPRNG, which is +/// subsequently used to generate the key. +#[must_use] +pub fn rng_with_index( + seed: &[u8; RNG_SEED], + index: u64, + termination: &[u8], +) -> ChaCha12Rng { + let mut hash = Sha256::new(); + + hash.update(seed); + hash.update(index.to_le_bytes()); + hash.update(termination); + + let hash = hash.finalize().into(); + ChaCha12Rng::from_seed(hash) +} diff --git a/wallet-core/src/lib.rs b/wallet-core/src/lib.rs new file mode 100644 index 0000000000..85f0d38093 --- /dev/null +++ b/wallet-core/src/lib.rs @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! Types used for interacting with Dusk's transfer and stake contracts. + +#![cfg_attr(target_family = "wasm", no_std)] +#![deny(missing_docs)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(clippy::pedantic)] + +pub mod keys; + +/// Length of the seed of the generated rng. +pub const RNG_SEED: usize = 64; diff --git a/wallet-core/tests/keys_derive.rs b/wallet-core/tests/keys_derive.rs new file mode 100644 index 0000000000..c80c36efbe --- /dev/null +++ b/wallet-core/tests/keys_derive.rs @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use dusk_bytes::Serializable; +use wallet_core::keys::{ + derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk, derive_phoenix_vk, +}; + +const SEED: [u8; 64] = [0; 64]; +const INDEX: u64 = 42; + +#[test] +fn test_derive_phoenix_sk() { + // it is important that we always derive the same key from a fixed seed + let sk_bytes = [ + 192, 40, 206, 139, 173, 105, 38, 165, 46, 165, 183, 119, 137, 5, 199, + 231, 100, 116, 106, 20, 186, 4, 219, 4, 166, 176, 224, 205, 194, 144, + 29, 4, 178, 57, 113, 176, 185, 83, 92, 81, 64, 162, 29, 109, 62, 46, + 187, 89, 67, 203, 201, 29, 226, 183, 28, 242, 72, 0, 125, 186, 224, + 122, 118, 8, + ]; + assert_eq!(derive_phoenix_sk(&SEED, INDEX).to_bytes(), sk_bytes); +} + +#[test] +fn test_derive_phoenix_pk() { + // it is important that we always derive the same key from a fixed seed + let pk_bytes = [ + 41, 66, 141, 44, 149, 148, 109, 187, 53, 86, 81, 73, 144, 160, 221, 82, + 102, 38, 97, 140, 252, 41, 202, 58, 239, 18, 147, 255, 52, 192, 236, + 22, 168, 125, 132, 85, 254, 91, 12, 47, 95, 1, 145, 82, 53, 84, 63, + 140, 110, 187, 85, 142, 241, 70, 3, 40, 102, 246, 30, 186, 24, 146, 4, + 85, + ]; + assert_eq!(derive_phoenix_pk(&SEED, INDEX).to_bytes(), pk_bytes); +} + +#[test] +fn test_derive_phoenix_vk() { + // it is important that we always derive the same key from a fixed seed + let vk_bytes = [ + 192, 40, 206, 139, 173, 105, 38, 165, 46, 165, 183, 119, 137, 5, 199, + 231, 100, 116, 106, 20, 186, 4, 219, 4, 166, 176, 224, 205, 194, 144, + 29, 4, 168, 125, 132, 85, 254, 91, 12, 47, 95, 1, 145, 82, 53, 84, 63, + 140, 110, 187, 85, 142, 241, 70, 3, 40, 102, 246, 30, 186, 24, 146, 4, + 85, + ]; + assert_eq!(derive_phoenix_vk(&SEED, INDEX).to_bytes(), vk_bytes); +} + +#[test] +fn test_derive_bls_sk() { + // it is important that we always derive the same key from a fixed seed + let sk_bytes = [ + 42, 224, 186, 247, 133, 35, 202, 126, 255, 156, 236, 121, 214, 145, + 228, 32, 100, 62, 129, 203, 16, 27, 213, 244, 143, 5, 123, 23, 202, 17, + 162, 90, + ]; + assert_eq!(derive_bls_sk(&SEED, INDEX).to_bytes(), sk_bytes); +}