Merge pull request #19 from vacp2p/Pravdyvy/accounts-core

Accounts core
This commit is contained in:
Pravdyvy 2024-10-31 10:51:11 +02:00 committed by GitHub
commit 2cf1817ca1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 431 additions and 156 deletions

108
Cargo.lock generated
View File

@ -6,6 +6,7 @@ version = 4
name = "accounts"
version = "0.1.0"
dependencies = [
"aes-gcm",
"anyhow",
"elliptic-curve",
"env_logger",
@ -17,6 +18,7 @@ dependencies = [
"serde_json",
"sha2 0.10.8",
"storage",
"utxo",
]
[[package]]
@ -248,6 +250,41 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array 0.14.7",
]
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if 1.0.0",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle 2.6.1",
]
[[package]]
name = "ahash"
version = "0.3.8"
@ -511,6 +548,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
@ -596,6 +643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array 0.14.7",
"rand_core 0.6.4",
"typenum",
]
@ -609,6 +657,15 @@ dependencies = [
"subtle 1.0.0",
]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]]
name = "der"
version = "0.7.9"
@ -885,6 +942,16 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "ghash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
dependencies = [
"opaque-debug 0.3.1",
"polyval",
]
[[package]]
name = "gimli"
version = "0.31.0"
@ -1028,6 +1095,15 @@ dependencies = [
"hashbrown 0.14.5",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "is-terminal"
version = "0.4.13"
@ -1441,6 +1517,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -1529,6 +1611,18 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "polyval"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug 0.3.1",
"universal-hash",
]
[[package]]
name = "portable-atomic"
version = "1.9.0"
@ -1909,7 +2003,7 @@ dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug",
"opaque-debug 0.2.3",
]
[[package]]
@ -1933,7 +2027,7 @@ dependencies = [
"byte-tools",
"digest 0.8.1",
"keccak",
"opaque-debug",
"opaque-debug 0.2.3",
]
[[package]]
@ -2198,6 +2292,16 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
"crypto-common",
"subtle 2.6.1",
]
[[package]]
name = "url"
version = "2.5.2"

View File

@ -36,6 +36,7 @@ rs_merkle = "1.4"
sha2 = "0.10.8"
monotree = "0.1.5"
hex = "0.4.3"
aes-gcm = "0.10.3"
rocksdb = { version = "0.21.0", default-features = false, features = [
"snappy",

View File

@ -14,6 +14,10 @@ sha2.workspace = true
rand.workspace = true
elliptic-curve.workspace = true
hex.workspace = true
aes-gcm.workspace = true
[dependencies.storage]
path = "../storage"
[dependencies.utxo]
path = "../utxo"

View File

@ -0,0 +1,101 @@
use std::collections::HashMap;
use anyhow::Result;
use k256::AffinePoint;
use serde::Serialize;
use storage::{merkle_tree_public::TreeHashType, nullifier::UTXONullifier};
use utxo::{
utxo_core::{UTXOPayload, UTXO},
utxo_tree::UTXOSparseMerkleTree,
};
use crate::key_management::{
constants_types::{CipherText, Nonce},
ephemeral_key_holder::EphemeralKeyHolder,
AddressKeyHolder,
};
pub struct Account {
pub key_holder: AddressKeyHolder,
pub address: TreeHashType,
pub balance: u64,
pub utxo_tree: UTXOSparseMerkleTree,
}
impl Account {
pub fn new() -> Self {
let key_holder = AddressKeyHolder::new_os_random();
let address = key_holder.address;
let balance = 0;
let utxo_tree = UTXOSparseMerkleTree::new();
Self {
key_holder,
address,
balance,
utxo_tree,
}
}
pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder {
self.key_holder.produce_ephemeral_key_holder()
}
pub fn encrypt_data(
ephemeral_key_holder: &EphemeralKeyHolder,
viewing_public_key_receiver: AffinePoint,
data: &[u8],
) -> (CipherText, Nonce) {
ephemeral_key_holder.encrypt_data(viewing_public_key_receiver, data)
}
pub fn decrypt_data(
&self,
ephemeral_public_key_sender: AffinePoint,
ciphertext: CipherText,
nonce: Nonce,
) -> Vec<u8> {
self.key_holder
.decrypt_data(ephemeral_public_key_sender, ciphertext, nonce)
}
pub fn mark_spent_utxo(
&mut self,
utxo_nullifier_map: HashMap<TreeHashType, UTXONullifier>,
) -> Result<()> {
for (hash, nullifier) in utxo_nullifier_map {
if let Some(utxo_entry) = self.utxo_tree.store.get_mut(&hash) {
utxo_entry.consume_utxo(nullifier)?;
}
}
Ok(())
}
pub fn add_new_utxo_outputs(&mut self, utxos: Vec<UTXO>) -> Result<()> {
Ok(self.utxo_tree.insert_items(utxos)?)
}
pub fn update_public_balance(&mut self, new_balance: u64) {
self.balance = new_balance;
}
pub fn add_asset<Asset: Serialize>(&mut self, asset: Asset) -> Result<()> {
let payload_with_asset = UTXOPayload {
owner: self.address,
asset: serde_json::to_vec(&asset)?,
};
let asset_utxo = UTXO::create_utxo_from_payload(payload_with_asset);
self.utxo_tree.insert_item(asset_utxo)?;
Ok(())
}
}
impl Default for Account {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,17 @@
use elliptic_curve::{
consts::{B0, B1},
generic_array::GenericArray,
};
use sha2::digest::typenum::{UInt, UTerm};
pub const NULLIFIER_SECRET_CONST: [u8; 32] = [
38, 29, 97, 210, 148, 172, 75, 220, 36, 249, 27, 111, 73, 14, 250, 38, 55, 87, 164, 169, 95,
101, 135, 28, 212, 241, 107, 46, 162, 60, 59, 93,
];
pub const VIEVING_SECRET_CONST: [u8; 32] = [
97, 23, 175, 117, 11, 48, 215, 162, 150, 103, 46, 195, 179, 178, 93, 52, 137, 190, 202, 60,
254, 87, 112, 250, 57, 242, 117, 206, 195, 149, 213, 206,
];
pub type CipherText = Vec<u8>;
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;

View File

@ -0,0 +1,53 @@
use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit};
use elliptic_curve::group::GroupEncoding;
use elliptic_curve::PrimeField;
use k256::{AffinePoint, FieldBytes, Scalar};
use rand::{rngs::OsRng, RngCore};
use super::constants_types::{CipherText, Nonce};
#[derive(Debug)]
///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender.
pub struct EphemeralKeyHolder {
ephemeral_secret_key: Scalar,
}
impl EphemeralKeyHolder {
pub fn new_os_random() -> Self {
let mut bytes = FieldBytes::default();
OsRng.fill_bytes(&mut bytes);
Self {
ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(),
}
}
pub fn generate_ephemeral_public_key(&self) -> AffinePoint {
(AffinePoint::GENERATOR * self.ephemeral_secret_key).into()
}
pub fn calculate_shared_secret_sender(
&self,
viewing_public_key_receiver: AffinePoint,
) -> AffinePoint {
(viewing_public_key_receiver * self.ephemeral_secret_key).into()
}
pub fn encrypt_data(
&self,
viewing_public_key_receiver: AffinePoint,
data: &[u8],
) -> (CipherText, Nonce) {
let key_point = self.calculate_shared_secret_sender(viewing_public_key_receiver);
let key_raw = key_point.to_bytes();
let key_raw_adjust: [u8; 32] = key_raw.as_slice().try_into().unwrap();
let key: Key<Aes256Gcm> = key_raw_adjust.into();
let cipher = Aes256Gcm::new(&key);
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
(cipher.encrypt(&nonce, data).unwrap(), nonce)
}
}

View File

@ -1,161 +1,21 @@
use k256::elliptic_curve::group::GroupEncoding;
use k256::{elliptic_curve::PrimeField, AffinePoint, FieldBytes, Scalar};
use rand::{rngs::OsRng, RngCore};
use sha2::{digest::FixedOutput, Digest};
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit};
use constants_types::{CipherText, Nonce};
use elliptic_curve::group::GroupEncoding;
use ephemeral_key_holder::EphemeralKeyHolder;
use k256::AffinePoint;
use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder};
use storage::merkle_tree_public::TreeHashType;
pub const NULLIFIER_SECRET_CONST: [u8; 32] = [
38, 29, 97, 210, 148, 172, 75, 220, 36, 249, 27, 111, 73, 14, 250, 38, 55, 87, 164, 169, 95,
101, 135, 28, 212, 241, 107, 46, 162, 60, 59, 93,
];
pub const VIEVING_SECRET_CONST: [u8; 32] = [
97, 23, 175, 117, 11, 48, 215, 162, 150, 103, 46, 195, 179, 178, 93, 52, 137, 190, 202, 60,
254, 87, 112, 250, 57, 242, 117, 206, 195, 149, 213, 206,
];
#[derive(Debug)]
///Seed holder. Non-clonable to ensure that different holders use different seeds.
/// Produces `TopSecretKeyHolder` objects.
pub struct SeedHolder {
seed: Scalar,
}
#[derive(Debug, Clone)]
///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects.
pub struct TopSecretKeyHolder {
secret_spending_key: Scalar,
}
#[derive(Debug, Clone)]
///Nullifier secret key and viewing secret key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
pub struct UTXOSecretKeyHolder {
nullifier_secret_key: Scalar,
viewing_secret_key: Scalar,
}
impl SeedHolder {
pub fn new_os_random() -> Self {
let mut bytes = FieldBytes::default();
OsRng.fill_bytes(&mut bytes);
Self {
seed: Scalar::from_repr(bytes).unwrap(),
}
}
pub fn generate_secret_spending_key_hash(&self) -> TreeHashType {
let mut hasher = sha2::Sha256::new();
hasher.update(self.seed.to_bytes());
<TreeHashType>::from(hasher.finalize_fixed())
}
pub fn generate_secret_spending_key_scalar(&self) -> Scalar {
let hash = self.generate_secret_spending_key_hash();
Scalar::from_repr(hash.into()).unwrap()
}
pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder {
TopSecretKeyHolder {
secret_spending_key: self.generate_secret_spending_key_scalar(),
}
}
}
impl TopSecretKeyHolder {
pub fn generate_nullifier_secret_key(&self) -> Scalar {
let mut hasher = sha2::Sha256::new();
hasher.update(self.secret_spending_key.to_bytes());
hasher.update(NULLIFIER_SECRET_CONST);
let hash = <TreeHashType>::from(hasher.finalize_fixed());
Scalar::from_repr(hash.into()).unwrap()
}
pub fn generate_viewing_secret_key(&self) -> Scalar {
let mut hasher = sha2::Sha256::new();
hasher.update(self.secret_spending_key.to_bytes());
hasher.update(VIEVING_SECRET_CONST);
let hash = <TreeHashType>::from(hasher.finalize_fixed());
Scalar::from_repr(hash.into()).unwrap()
}
pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder {
UTXOSecretKeyHolder {
nullifier_secret_key: self.generate_nullifier_secret_key(),
viewing_secret_key: self.generate_viewing_secret_key(),
}
}
}
impl UTXOSecretKeyHolder {
pub fn generate_nullifier_public_key(&self) -> AffinePoint {
(AffinePoint::GENERATOR * self.nullifier_secret_key).into()
}
pub fn generate_viewing_public_key(&self) -> AffinePoint {
(AffinePoint::GENERATOR * self.viewing_secret_key).into()
}
pub fn generate_address(&self) -> TreeHashType {
let npk = self.generate_nullifier_public_key();
let vpk = self.generate_viewing_public_key();
let mut hasher = sha2::Sha256::new();
hasher.update(npk.to_bytes());
hasher.update(vpk.to_bytes());
<TreeHashType>::from(hasher.finalize_fixed())
}
}
#[derive(Debug)]
///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender.
pub struct EphemeralKeyHolder {
ephemeral_secret_key: Scalar,
}
impl EphemeralKeyHolder {
pub fn new_os_random() -> Self {
let mut bytes = FieldBytes::default();
OsRng.fill_bytes(&mut bytes);
Self {
ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(),
}
}
pub fn generate_ephemeral_public_key(&self) -> AffinePoint {
(AffinePoint::GENERATOR * self.ephemeral_secret_key).into()
}
pub fn calculate_shared_secret_sender(
&self,
viewing_public_key_receiver: AffinePoint,
) -> AffinePoint {
(viewing_public_key_receiver * self.ephemeral_secret_key).into()
}
pub fn encrypt_data(&self) {
//ToDo: Implement that
//Need clarification on exact symmetric encoding, which we want to use for ECIES
todo!()
}
}
pub mod constants_types;
pub mod ephemeral_key_holder;
pub mod secret_holders;
#[derive(Debug)]
///Entrypoint to key management
pub struct AddressKeyHolder {
//Will be useful in future
#[allow(dead_code)]
top_secret_key_holder: TopSecretKeyHolder,
utxo_secret_key_holder: UTXOSecretKeyHolder,
pub address: TreeHashType,
pub nullifer_public_key: AffinePoint,
@ -164,8 +24,8 @@ pub struct AddressKeyHolder {
impl AddressKeyHolder {
pub fn new_os_random() -> Self {
//Currently dropping SeedHolder and TopSecretKeyHolder at the end of initialization.
//Now entirely sure if we need them in the future.
//Currently dropping SeedHolder at the end of initialization.
//Now entirely sure if we need it in the future.
let seed_holder = SeedHolder::new_os_random();
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
@ -176,6 +36,7 @@ impl AddressKeyHolder {
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
Self {
top_secret_key_holder,
utxo_secret_key_holder,
address,
nullifer_public_key,
@ -193,10 +54,30 @@ impl AddressKeyHolder {
pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder {
EphemeralKeyHolder::new_os_random()
}
pub fn decrypt_data(
&self,
ephemeral_public_key_sender: AffinePoint,
ciphertext: CipherText,
nonce: Nonce,
) -> Vec<u8> {
let key_point = self.calculate_shared_secret_receiver(ephemeral_public_key_sender);
let key_raw = key_point.to_bytes();
let key_raw_adjust: [u8; 32] = key_raw.as_slice().try_into().unwrap();
let key: Key<Aes256Gcm> = key_raw_adjust.into();
let cipher = Aes256Gcm::new(&key);
cipher.decrypt(&nonce, ciphertext.as_slice()).unwrap()
}
}
#[cfg(test)]
mod tests {
use constants_types::{NULLIFIER_SECRET_CONST, VIEVING_SECRET_CONST};
use elliptic_curve::group::GroupEncoding;
use super::*;
#[test]

View File

@ -0,0 +1,113 @@
use elliptic_curve::group::GroupEncoding;
use elliptic_curve::PrimeField;
use k256::{AffinePoint, FieldBytes, Scalar};
use rand::{rngs::OsRng, RngCore};
use sha2::{digest::FixedOutput, Digest};
use storage::merkle_tree_public::TreeHashType;
use super::constants_types::{NULLIFIER_SECRET_CONST, VIEVING_SECRET_CONST};
#[derive(Debug)]
///Seed holder. Non-clonable to ensure that different holders use different seeds.
/// Produces `TopSecretKeyHolder` objects.
pub struct SeedHolder {
seed: Scalar,
}
#[derive(Debug, Clone)]
///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects.
pub struct TopSecretKeyHolder {
secret_spending_key: Scalar,
}
#[derive(Debug, Clone)]
///Nullifier secret key and viewing secret key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
pub struct UTXOSecretKeyHolder {
pub nullifier_secret_key: Scalar,
pub viewing_secret_key: Scalar,
}
impl SeedHolder {
pub fn new_os_random() -> Self {
let mut bytes = FieldBytes::default();
OsRng.fill_bytes(&mut bytes);
Self {
seed: Scalar::from_repr(bytes).unwrap(),
}
}
pub fn generate_secret_spending_key_hash(&self) -> TreeHashType {
let mut hasher = sha2::Sha256::new();
hasher.update(self.seed.to_bytes());
<TreeHashType>::from(hasher.finalize_fixed())
}
pub fn generate_secret_spending_key_scalar(&self) -> Scalar {
let hash = self.generate_secret_spending_key_hash();
Scalar::from_repr(hash.into()).unwrap()
}
pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder {
TopSecretKeyHolder {
secret_spending_key: self.generate_secret_spending_key_scalar(),
}
}
}
impl TopSecretKeyHolder {
pub fn generate_nullifier_secret_key(&self) -> Scalar {
let mut hasher = sha2::Sha256::new();
hasher.update(self.secret_spending_key.to_bytes());
hasher.update(NULLIFIER_SECRET_CONST);
let hash = <TreeHashType>::from(hasher.finalize_fixed());
Scalar::from_repr(hash.into()).unwrap()
}
pub fn generate_viewing_secret_key(&self) -> Scalar {
let mut hasher = sha2::Sha256::new();
hasher.update(self.secret_spending_key.to_bytes());
hasher.update(VIEVING_SECRET_CONST);
let hash = <TreeHashType>::from(hasher.finalize_fixed());
Scalar::from_repr(hash.into()).unwrap()
}
pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder {
UTXOSecretKeyHolder {
nullifier_secret_key: self.generate_nullifier_secret_key(),
viewing_secret_key: self.generate_viewing_secret_key(),
}
}
}
impl UTXOSecretKeyHolder {
pub fn generate_nullifier_public_key(&self) -> AffinePoint {
(AffinePoint::GENERATOR * self.nullifier_secret_key).into()
}
pub fn generate_viewing_public_key(&self) -> AffinePoint {
(AffinePoint::GENERATOR * self.viewing_secret_key).into()
}
pub fn generate_address(&self) -> TreeHashType {
let npk = self.generate_nullifier_public_key();
let vpk = self.generate_viewing_public_key();
let mut hasher = sha2::Sha256::new();
hasher.update(npk.to_bytes());
hasher.update(vpk.to_bytes());
<TreeHashType>::from(hasher.finalize_fixed())
}
}

View File

@ -1 +1,2 @@
pub mod account_core;
pub mod key_management;