mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 13:23:10 +00:00
Merge branch 'main' into schouhy/change-authorization-mechanism
This commit is contained in:
commit
4a755a0c23
@ -41,6 +41,8 @@ ark-bn254 = "0.5.0"
|
||||
ark-ff = "0.5.0"
|
||||
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
|
||||
base64 = "0.22.1"
|
||||
bip39 = "2.2.0"
|
||||
hmac-sha512 = "1.1.7"
|
||||
chrono = "0.4.41"
|
||||
|
||||
rocksdb = { version = "0.21.0", default-features = false, features = [
|
||||
|
||||
@ -20,6 +20,7 @@ workspace = true
|
||||
|
||||
[dependencies.sequencer_core]
|
||||
path = "../sequencer_core"
|
||||
features = ["testnet"]
|
||||
|
||||
[dependencies.sequencer_runner]
|
||||
path = "../sequencer_runner"
|
||||
|
||||
@ -273,21 +273,10 @@ pub async fn test_success_two_transactions() {
|
||||
info!("Second TX Success!");
|
||||
}
|
||||
|
||||
pub async fn test_get_account_wallet_command() {
|
||||
let command = Command::GetAccount {
|
||||
addr: ACC_SENDER.to_string(),
|
||||
};
|
||||
|
||||
pub async fn test_get_account() {
|
||||
let wallet_config = fetch_config().unwrap();
|
||||
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
info!("Checking correct account");
|
||||
let account = seq_client
|
||||
.get_account(ACC_SENDER.to_string())
|
||||
.await
|
||||
@ -303,6 +292,50 @@ pub async fn test_get_account_wallet_command() {
|
||||
assert_eq!(account.nonce, 0);
|
||||
}
|
||||
|
||||
pub async fn test_pinata() {
|
||||
let pinata_addr = "cafe".repeat(16);
|
||||
let pinata_prize = 150;
|
||||
let solution = 989106;
|
||||
let command = Command::ClaimPinata {
|
||||
pinata_addr: pinata_addr.clone(),
|
||||
winner_addr: ACC_SENDER.to_string(),
|
||||
solution,
|
||||
};
|
||||
|
||||
let wallet_config = fetch_config().unwrap();
|
||||
|
||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
|
||||
|
||||
let pinata_balance_pre = seq_client
|
||||
.get_account_balance(pinata_addr.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.balance;
|
||||
|
||||
wallet::execute_subcommand(command).await.unwrap();
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
info!("Checking correct balance move");
|
||||
let pinata_balance_post = seq_client
|
||||
.get_account_balance(pinata_addr.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.balance;
|
||||
|
||||
let winner_balance_post = seq_client
|
||||
.get_account_balance(ACC_SENDER.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.balance;
|
||||
|
||||
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
|
||||
assert_eq!(winner_balance_post, 10000 + pinata_prize);
|
||||
|
||||
info!("Success!");
|
||||
}
|
||||
|
||||
macro_rules! test_cleanup_wrap {
|
||||
($home_dir:ident, $test_func:ident) => {{
|
||||
let res = pre_test($home_dir.clone()).await.unwrap();
|
||||
@ -336,17 +369,21 @@ pub async fn main_tests_runner() -> Result<()> {
|
||||
test_cleanup_wrap!(home_dir, test_failure);
|
||||
}
|
||||
"test_get_account_wallet_command" => {
|
||||
test_cleanup_wrap!(home_dir, test_get_account_wallet_command);
|
||||
test_cleanup_wrap!(home_dir, test_get_account);
|
||||
}
|
||||
"test_success_two_transactions" => {
|
||||
test_cleanup_wrap!(home_dir, test_success_two_transactions);
|
||||
}
|
||||
"test_pinata" => {
|
||||
test_cleanup_wrap!(home_dir, test_pinata);
|
||||
}
|
||||
"all" => {
|
||||
test_cleanup_wrap!(home_dir, test_success_move_to_another_account);
|
||||
test_cleanup_wrap!(home_dir, test_success);
|
||||
test_cleanup_wrap!(home_dir, test_failure);
|
||||
test_cleanup_wrap!(home_dir, test_success_two_transactions);
|
||||
test_cleanup_wrap!(home_dir, test_get_account_wallet_command);
|
||||
test_cleanup_wrap!(home_dir, test_pinata);
|
||||
test_cleanup_wrap!(home_dir, test_get_account);
|
||||
}
|
||||
_ => {
|
||||
anyhow::bail!("Unknown test name");
|
||||
|
||||
@ -15,6 +15,9 @@ elliptic-curve.workspace = true
|
||||
hex.workspace = true
|
||||
aes-gcm.workspace = true
|
||||
lazy_static.workspace = true
|
||||
bip39.workspace = true
|
||||
hmac-sha512.workspace = true
|
||||
nssa-core = { path = "../nssa/core", features = ["host"] }
|
||||
|
||||
[dependencies.common]
|
||||
path = "../common"
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
use elliptic_curve::{
|
||||
consts::{B0, B1},
|
||||
generic_array::GenericArray,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use sha2::digest::typenum::{UInt, UTerm};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref NULLIFIER_SECRET_CONST: [u8; 32] =
|
||||
hex::decode(std::env::var("NULLIFIER_SECRET_CONST").unwrap())
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
pub static ref VIEWING_SECRET_CONST: [u8; 32] =
|
||||
hex::decode(std::env::var("VIEWING_SECRET_CONST").unwrap())
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub type CipherText = Vec<u8>;
|
||||
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;
|
||||
@ -1,50 +1,52 @@
|
||||
use aes_gcm::{AeadCore, Aes256Gcm, KeyInit, aead::Aead};
|
||||
use elliptic_curve::PrimeField;
|
||||
use elliptic_curve::point::AffineCoordinates;
|
||||
use k256::{AffinePoint, FieldBytes, Scalar};
|
||||
use log::info;
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use nssa_core::{
|
||||
NullifierPublicKey, SharedSecretKey,
|
||||
encryption::{EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey},
|
||||
};
|
||||
use sha2::Digest;
|
||||
|
||||
use super::constants_types::{CipherText, Nonce};
|
||||
use crate::key_management::secret_holders::OutgoingViewingSecretKey;
|
||||
|
||||
#[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,
|
||||
ephemeral_secret_key: EphemeralSecretKey,
|
||||
}
|
||||
|
||||
impl EphemeralKeyHolder {
|
||||
pub fn new_os_random() -> Self {
|
||||
let mut bytes = FieldBytes::default();
|
||||
pub fn new(
|
||||
receiver_nullifier_public_key: NullifierPublicKey,
|
||||
sender_outgoing_viewing_secret_key: OutgoingViewingSecretKey,
|
||||
nonce: u64,
|
||||
) -> Self {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(receiver_nullifier_public_key);
|
||||
hasher.update(nonce.to_le_bytes());
|
||||
hasher.update([0; 24]);
|
||||
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let hash_recepient = hasher.finalize();
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(sender_outgoing_viewing_secret_key);
|
||||
hasher.update(hash_recepient);
|
||||
|
||||
Self {
|
||||
ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(),
|
||||
ephemeral_secret_key: hasher.finalize().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_ephemeral_public_key(&self) -> AffinePoint {
|
||||
(AffinePoint::GENERATOR * self.ephemeral_secret_key).into()
|
||||
pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey {
|
||||
EphemeralPublicKey::from_scalar(self.ephemeral_secret_key)
|
||||
}
|
||||
|
||||
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 shared_secret = self.calculate_shared_secret_sender(viewing_public_key_receiver);
|
||||
let cipher = Aes256Gcm::new(&shared_secret.x());
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
|
||||
(cipher.encrypt(&nonce, data).unwrap(), nonce)
|
||||
receiver_incoming_viewing_public_key: IncomingViewingPublicKey,
|
||||
) -> SharedSecretKey {
|
||||
SharedSecretKey::new(
|
||||
&self.ephemeral_secret_key,
|
||||
&receiver_incoming_viewing_public_key,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn log(&self) {
|
||||
|
||||
@ -1,29 +1,25 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
|
||||
use constants_types::{CipherText, Nonce};
|
||||
use elliptic_curve::point::AffineCoordinates;
|
||||
use k256::AffinePoint;
|
||||
use common::TreeHashType;
|
||||
use log::info;
|
||||
use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder};
|
||||
use nssa_core::{
|
||||
NullifierPublicKey, SharedSecretKey,
|
||||
encryption::{EphemeralPublicKey, IncomingViewingPublicKey},
|
||||
};
|
||||
use secret_holders::{PrivateKeyHolder, SecretSpendingKey, SeedHolder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
use crate::key_protocol_core::PublicKey;
|
||||
pub type PublicAccountSigningKey = [u8; 32];
|
||||
|
||||
pub mod constants_types;
|
||||
pub mod ephemeral_key_holder;
|
||||
pub mod secret_holders;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
///Entrypoint to key management
|
||||
pub struct KeyChain {
|
||||
top_secret_key_holder: TopSecretKeyHolder,
|
||||
pub utxo_secret_key_holder: UTXOSecretKeyHolder,
|
||||
///Map for all users accounts
|
||||
pub pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
|
||||
pub nullifer_public_key: PublicKey,
|
||||
pub viewing_public_key: PublicKey,
|
||||
secret_spending_key: SecretSpendingKey,
|
||||
pub private_key_holder: PrivateKeyHolder,
|
||||
pub nullifer_public_key: NullifierPublicKey,
|
||||
pub incoming_viewing_public_key: IncomingViewingPublicKey,
|
||||
}
|
||||
|
||||
impl KeyChain {
|
||||
@ -31,95 +27,61 @@ impl KeyChain {
|
||||
//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();
|
||||
let secret_spending_key = seed_holder.produce_top_secret_key_holder();
|
||||
|
||||
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
|
||||
let private_key_holder = secret_spending_key.produce_private_key_holder();
|
||||
|
||||
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
|
||||
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
|
||||
let nullifer_public_key = private_key_holder.generate_nullifier_public_key();
|
||||
let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key();
|
||||
|
||||
Self {
|
||||
top_secret_key_holder,
|
||||
utxo_secret_key_holder,
|
||||
secret_spending_key,
|
||||
private_key_holder,
|
||||
nullifer_public_key,
|
||||
viewing_public_key,
|
||||
pub_account_signing_keys: HashMap::new(),
|
||||
incoming_viewing_public_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_os_random_with_accounts(accounts: HashMap<nssa::Address, nssa::PrivateKey>) -> Self {
|
||||
//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();
|
||||
pub fn produce_user_address(&self) -> [u8; 32] {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
|
||||
hasher.update(&self.nullifer_public_key);
|
||||
hasher.update(self.incoming_viewing_public_key.to_bytes());
|
||||
|
||||
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
|
||||
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
|
||||
|
||||
Self {
|
||||
top_secret_key_holder,
|
||||
utxo_secret_key_holder,
|
||||
nullifer_public_key,
|
||||
viewing_public_key,
|
||||
pub_account_signing_keys: accounts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_new_private_key(&mut self) -> nssa::Address {
|
||||
let private_key = nssa::PrivateKey::new_os_random();
|
||||
let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key));
|
||||
|
||||
self.pub_account_signing_keys.insert(address, private_key);
|
||||
|
||||
address
|
||||
}
|
||||
|
||||
/// Returns the signing key for public transaction signatures
|
||||
pub fn get_pub_account_signing_key(
|
||||
&self,
|
||||
address: &nssa::Address,
|
||||
) -> Option<&nssa::PrivateKey> {
|
||||
self.pub_account_signing_keys.get(address)
|
||||
<TreeHashType>::from(hasher.finalize_fixed())
|
||||
}
|
||||
|
||||
pub fn calculate_shared_secret_receiver(
|
||||
&self,
|
||||
ephemeral_public_key_sender: AffinePoint,
|
||||
) -> AffinePoint {
|
||||
(ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into()
|
||||
}
|
||||
|
||||
pub fn decrypt_data(
|
||||
&self,
|
||||
ephemeral_public_key_sender: AffinePoint,
|
||||
ciphertext: CipherText,
|
||||
nonce: Nonce,
|
||||
) -> Result<Vec<u8>, aes_gcm::Error> {
|
||||
let shared_secret = self.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
let cipher = Aes256Gcm::new(&shared_secret.x());
|
||||
|
||||
cipher.decrypt(&nonce, ciphertext.as_slice())
|
||||
ephemeral_public_key_sender: EphemeralPublicKey,
|
||||
) -> SharedSecretKey {
|
||||
SharedSecretKey::new(
|
||||
&self
|
||||
.secret_spending_key
|
||||
.generate_incoming_viewing_secret_key(),
|
||||
&ephemeral_public_key_sender,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn log(&self) {
|
||||
info!(
|
||||
"Secret spending key is {:?}",
|
||||
hex::encode(
|
||||
serde_json::to_vec(&self.top_secret_key_holder.secret_spending_key).unwrap()
|
||||
),
|
||||
hex::encode(serde_json::to_vec(&self.secret_spending_key).unwrap()),
|
||||
);
|
||||
info!(
|
||||
"Nulifier secret key is {:?}",
|
||||
hex::encode(serde_json::to_vec(&self.private_key_holder.nullifier_secret_key).unwrap()),
|
||||
);
|
||||
info!(
|
||||
"Viewing secret key is {:?}",
|
||||
hex::encode(
|
||||
serde_json::to_vec(&self.utxo_secret_key_holder.nullifier_secret_key).unwrap()
|
||||
serde_json::to_vec(&self.private_key_holder.incoming_viewing_secret_key).unwrap()
|
||||
),
|
||||
);
|
||||
info!(
|
||||
"Viewing secret key is {:?}",
|
||||
hex::encode(
|
||||
serde_json::to_vec(&self.utxo_secret_key_holder.viewing_secret_key).unwrap()
|
||||
serde_json::to_vec(&self.private_key_holder.outgoing_viewing_secret_key).unwrap()
|
||||
),
|
||||
);
|
||||
info!(
|
||||
@ -128,25 +90,16 @@ impl KeyChain {
|
||||
);
|
||||
info!(
|
||||
"Viewing public key is {:?}",
|
||||
hex::encode(serde_json::to_vec(&self.viewing_public_key).unwrap()),
|
||||
hex::encode(serde_json::to_vec(&self.incoming_viewing_public_key).unwrap()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use aes_gcm::{
|
||||
Aes256Gcm,
|
||||
aead::{Aead, KeyInit, OsRng},
|
||||
};
|
||||
use constants_types::{CipherText, Nonce};
|
||||
use constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST};
|
||||
use elliptic_curve::ff::Field;
|
||||
use elliptic_curve::group::prime::PrimeCurveAffine;
|
||||
use elliptic_curve::point::AffineCoordinates;
|
||||
use k256::{AffinePoint, ProjectivePoint, Scalar};
|
||||
|
||||
use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use aes_gcm::aead::OsRng;
|
||||
use k256::AffinePoint;
|
||||
use rand::RngCore;
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -156,12 +109,7 @@ mod tests {
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Check that key holder fields are initialized with expected types
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.nullifer_public_key.is_identity()
|
||||
));
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.viewing_public_key.is_identity()
|
||||
));
|
||||
assert_ne!(address_key_holder.nullifer_public_key.as_ref(), &[0u8; 32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -169,176 +117,13 @@ mod tests {
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Generate a random ephemeral public key sender
|
||||
let scalar = Scalar::random(&mut OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine();
|
||||
let mut scalar = [0; 32];
|
||||
OsRng.fill_bytes(&mut scalar);
|
||||
let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar);
|
||||
|
||||
// Calculate shared secret
|
||||
let shared_secret =
|
||||
let _shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Ensure the shared secret is not an identity point (suggesting non-zero output)
|
||||
assert!(!Into::<bool>::into(shared_secret.is_identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_data() {
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Generate an ephemeral key and shared secret
|
||||
let ephemeral_public_key_sender =
|
||||
EphemeralKeyHolder::new_os_random().generate_ephemeral_public_key();
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Encrypt sample data
|
||||
let cipher = Aes256Gcm::new(&shared_secret.x());
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
let plaintext = b"Sensitive data";
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Attempt decryption
|
||||
let decrypted_data: Vec<u8> = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(ciphertext),
|
||||
*nonce,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify decryption is successful and matches original plaintext
|
||||
assert_eq!(decrypted_data, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_os_random_initialization() {
|
||||
// Ensure that KeyChain is initialized correctly
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Check that key holder fields are initialized with expected types and values
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.nullifer_public_key.is_identity()
|
||||
));
|
||||
assert!(!Into::<bool>::into(
|
||||
address_key_holder.viewing_public_key.is_identity()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_shared_secret_with_identity_point() {
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Use identity point as ephemeral public key
|
||||
let identity_point = AffinePoint::identity();
|
||||
|
||||
// Calculate shared secret
|
||||
let shared_secret = address_key_holder.calculate_shared_secret_receiver(identity_point);
|
||||
|
||||
// The shared secret with the identity point should also result in the identity point
|
||||
assert!(Into::<bool>::into(shared_secret.is_identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_decrypt_data_with_incorrect_nonce() {
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Generate ephemeral public key and shared secret
|
||||
let scalar = Scalar::random(OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine();
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Encrypt sample data with a specific nonce
|
||||
let cipher = Aes256Gcm::new(&shared_secret.x());
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
let plaintext = b"Sensitive data";
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Attempt decryption with an incorrect nonce
|
||||
let incorrect_nonce = Nonce::from_slice(b"wrong nonce");
|
||||
let decrypted_data = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(ciphertext.clone()),
|
||||
*incorrect_nonce,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The decryption should fail or produce incorrect output due to nonce mismatch
|
||||
assert_ne!(decrypted_data, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_decrypt_data_with_incorrect_ciphertext() {
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Generate ephemeral public key and shared secret
|
||||
let scalar = Scalar::random(OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine();
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
|
||||
// Encrypt sample data
|
||||
let cipher = Aes256Gcm::new(&shared_secret.x());
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
let plaintext = b"Sensitive data";
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Tamper with the ciphertext to simulate corruption
|
||||
let mut corrupted_ciphertext = ciphertext.clone();
|
||||
corrupted_ciphertext[0] ^= 1; // Flip a bit in the ciphertext
|
||||
|
||||
// Attempt decryption
|
||||
let result = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(corrupted_ciphertext),
|
||||
*nonce,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The decryption should fail or produce incorrect output due to tampered ciphertext
|
||||
assert_ne!(result, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_decryption_round_trip() {
|
||||
let address_key_holder = KeyChain::new_os_random();
|
||||
|
||||
// Generate ephemeral key and shared secret
|
||||
let scalar = Scalar::random(OsRng);
|
||||
let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine();
|
||||
|
||||
// Encrypt sample data
|
||||
let plaintext = b"Round-trip test data";
|
||||
let nonce = Nonce::from_slice(b"unique nonce");
|
||||
|
||||
let shared_secret =
|
||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
||||
let cipher = Aes256Gcm::new(&shared_secret.x());
|
||||
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext.as_ref())
|
||||
.expect("encryption failure");
|
||||
|
||||
// Decrypt the data using the `KeyChain` instance
|
||||
let decrypted_data = address_key_holder
|
||||
.decrypt_data(
|
||||
ephemeral_public_key_sender,
|
||||
CipherText::from(ciphertext),
|
||||
*nonce,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Verify the decrypted data matches the original plaintext
|
||||
assert_eq!(decrypted_data, plaintext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -346,10 +131,10 @@ mod tests {
|
||||
let seed_holder = SeedHolder::new_os_random();
|
||||
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
|
||||
|
||||
let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder();
|
||||
let utxo_secret_key_holder = top_secret_key_holder.produce_private_key_holder();
|
||||
|
||||
let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
|
||||
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
|
||||
let viewing_public_key = utxo_secret_key_holder.generate_incoming_viewing_public_key();
|
||||
|
||||
let pub_account_signing_key = nssa::PrivateKey::new_os_random();
|
||||
|
||||
@ -364,11 +149,6 @@ mod tests {
|
||||
"Group generator {:?}",
|
||||
hex::encode(serde_json::to_vec(&AffinePoint::GENERATOR).unwrap())
|
||||
);
|
||||
println!(
|
||||
"Nullifier constant {:?}",
|
||||
hex::encode(*NULLIFIER_SECRET_CONST)
|
||||
);
|
||||
println!("Viewing constatnt {:?}", hex::encode(*VIEWING_SECRET_CONST));
|
||||
println!();
|
||||
|
||||
println!("======Holders======");
|
||||
|
||||
@ -1,101 +1,157 @@
|
||||
use bip39::Mnemonic;
|
||||
use common::TreeHashType;
|
||||
use elliptic_curve::PrimeField;
|
||||
use k256::{AffinePoint, FieldBytes, Scalar};
|
||||
use nssa_core::{
|
||||
NullifierPublicKey, NullifierSecretKey,
|
||||
encryption::{IncomingViewingPublicKey, Scalar},
|
||||
};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
use super::constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST};
|
||||
|
||||
#[derive(Debug)]
|
||||
///Seed holder. Non-clonable to ensure that different holders use different seeds.
|
||||
/// Produces `TopSecretKeyHolder` objects.
|
||||
pub struct SeedHolder {
|
||||
seed: Scalar,
|
||||
//ToDo: Needs to be vec as serde derives is not implemented for [u8; 64]
|
||||
pub(crate) seed: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects.
|
||||
pub struct TopSecretKeyHolder {
|
||||
pub secret_spending_key: Scalar,
|
||||
}
|
||||
///Secret spending key object. Can produce `PrivateKeyHolder` objects.
|
||||
pub struct SecretSpendingKey(pub(crate) [u8; 32]);
|
||||
|
||||
pub type IncomingViewingSecretKey = Scalar;
|
||||
pub type OutgoingViewingSecretKey = Scalar;
|
||||
|
||||
#[derive(Serialize, Deserialize, 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,
|
||||
///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
|
||||
pub struct PrivateKeyHolder {
|
||||
pub(crate) nullifier_secret_key: NullifierSecretKey,
|
||||
pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey,
|
||||
pub(crate) outgoing_viewing_secret_key: OutgoingViewingSecretKey,
|
||||
}
|
||||
|
||||
impl SeedHolder {
|
||||
pub fn new_os_random() -> Self {
|
||||
let mut bytes = FieldBytes::default();
|
||||
let mut enthopy_bytes: [u8; 32] = [0; 32];
|
||||
OsRng.fill_bytes(&mut enthopy_bytes);
|
||||
|
||||
OsRng.fill_bytes(&mut bytes);
|
||||
let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap();
|
||||
let seed_wide = mnemonic.to_seed("mnemonic");
|
||||
|
||||
Self {
|
||||
seed: Scalar::from_repr(bytes).unwrap(),
|
||||
seed: seed_wide.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_secret_spending_key_hash(&self) -> TreeHashType {
|
||||
let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed");
|
||||
|
||||
for _ in 1..2048 {
|
||||
hash = hmac_sha512::HMAC::mac(hash, "NSSA_seed");
|
||||
}
|
||||
|
||||
//Safe unwrap
|
||||
*hash.first_chunk::<32>().unwrap()
|
||||
}
|
||||
|
||||
pub fn produce_top_secret_key_holder(&self) -> SecretSpendingKey {
|
||||
SecretSpendingKey(self.generate_secret_spending_key_hash())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretSpendingKey {
|
||||
pub fn generate_nullifier_secret_key(&self) -> NullifierSecretKey {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update(self.seed.to_bytes());
|
||||
hasher.update("NSSA_keys");
|
||||
hasher.update(self.0);
|
||||
hasher.update([1u8]);
|
||||
hasher.update([0u8; 22]);
|
||||
|
||||
<NullifierSecretKey>::from(hasher.finalize_fixed())
|
||||
}
|
||||
|
||||
pub fn generate_incoming_viewing_secret_key(&self) -> IncomingViewingSecretKey {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update("NSSA_keys");
|
||||
hasher.update(self.0);
|
||||
hasher.update([2u8]);
|
||||
hasher.update([0u8; 22]);
|
||||
|
||||
<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 {
|
||||
pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update(self.secret_spending_key.to_bytes());
|
||||
hasher.update(*NULLIFIER_SECRET_CONST);
|
||||
hasher.update("NSSA_keys");
|
||||
hasher.update(self.0);
|
||||
hasher.update([3u8]);
|
||||
hasher.update([0u8; 22]);
|
||||
|
||||
let hash = <TreeHashType>::from(hasher.finalize_fixed());
|
||||
|
||||
Scalar::from_repr(hash.into()).unwrap()
|
||||
<TreeHashType>::from(hasher.finalize_fixed())
|
||||
}
|
||||
|
||||
pub fn generate_viewing_secret_key(&self) -> Scalar {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
|
||||
hasher.update(self.secret_spending_key.to_bytes());
|
||||
hasher.update(*VIEWING_SECRET_CONST);
|
||||
|
||||
let hash = <TreeHashType>::from(hasher.finalize_fixed());
|
||||
|
||||
Scalar::from_repr(hash.into()).unwrap()
|
||||
}
|
||||
|
||||
pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder {
|
||||
UTXOSecretKeyHolder {
|
||||
pub fn produce_private_key_holder(&self) -> PrivateKeyHolder {
|
||||
PrivateKeyHolder {
|
||||
nullifier_secret_key: self.generate_nullifier_secret_key(),
|
||||
viewing_secret_key: self.generate_viewing_secret_key(),
|
||||
incoming_viewing_secret_key: self.generate_incoming_viewing_secret_key(),
|
||||
outgoing_viewing_secret_key: self.generate_outgoing_viewing_secret_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UTXOSecretKeyHolder {
|
||||
pub fn generate_nullifier_public_key(&self) -> AffinePoint {
|
||||
(AffinePoint::GENERATOR * self.nullifier_secret_key).into()
|
||||
impl PrivateKeyHolder {
|
||||
pub fn generate_nullifier_public_key(&self) -> NullifierPublicKey {
|
||||
(&self.nullifier_secret_key).into()
|
||||
}
|
||||
|
||||
pub fn generate_viewing_public_key(&self) -> AffinePoint {
|
||||
(AffinePoint::GENERATOR * self.viewing_secret_key).into()
|
||||
pub fn generate_incoming_viewing_public_key(&self) -> IncomingViewingPublicKey {
|
||||
IncomingViewingPublicKey::from_scalar(self.incoming_viewing_secret_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn seed_generation_test() {
|
||||
let seed_holder = SeedHolder::new_os_random();
|
||||
|
||||
assert_eq!(seed_holder.seed.len(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ssk_generation_test() {
|
||||
let seed_holder = SeedHolder::new_os_random();
|
||||
|
||||
assert_eq!(seed_holder.seed.len(), 64);
|
||||
|
||||
let _ = seed_holder.generate_secret_spending_key_hash();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ivs_generation_test() {
|
||||
let seed_holder = SeedHolder::new_os_random();
|
||||
|
||||
assert_eq!(seed_holder.seed.len(), 64);
|
||||
|
||||
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
|
||||
|
||||
let _ = top_secret_key_holder.generate_incoming_viewing_secret_key();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ovs_generation_test() {
|
||||
let seed_holder = SeedHolder::new_os_random();
|
||||
|
||||
assert_eq!(seed_holder.seed.len(), 64);
|
||||
|
||||
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
|
||||
|
||||
let _ = top_secret_key_holder.generate_outgoing_viewing_secret_key();
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,17 +10,14 @@ pub type PublicKey = AffinePoint;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct NSSAUserData {
|
||||
pub key_holder: KeyChain,
|
||||
///Map for all user public accounts
|
||||
pub pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
|
||||
///Map for all user private accounts
|
||||
user_private_accounts: HashMap<nssa::Address, KeyChain>,
|
||||
}
|
||||
|
||||
impl NSSAUserData {
|
||||
pub fn new() -> Self {
|
||||
let key_holder = KeyChain::new_os_random();
|
||||
|
||||
Self { key_holder }
|
||||
}
|
||||
|
||||
fn valid_key_transaction_pairing_check(
|
||||
fn valid_public_key_transaction_pairing_check(
|
||||
accounts_keys_map: &HashMap<nssa::Address, nssa::PrivateKey>,
|
||||
) -> bool {
|
||||
let mut check_res = true;
|
||||
@ -32,32 +29,82 @@ impl NSSAUserData {
|
||||
check_res
|
||||
}
|
||||
|
||||
fn valid_private_key_transaction_pairing_check(
|
||||
accounts_keys_map: &HashMap<nssa::Address, KeyChain>,
|
||||
) -> bool {
|
||||
let mut check_res = true;
|
||||
for (addr, key) in accounts_keys_map {
|
||||
if nssa::Address::new(key.produce_user_address()) != *addr {
|
||||
check_res = false;
|
||||
}
|
||||
}
|
||||
check_res
|
||||
}
|
||||
|
||||
pub fn new_with_accounts(
|
||||
accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
|
||||
accounts_key_chains: HashMap<nssa::Address, KeyChain>,
|
||||
) -> Result<Self> {
|
||||
if !Self::valid_key_transaction_pairing_check(&accounts_keys) {
|
||||
if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) {
|
||||
anyhow::bail!(
|
||||
"Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"
|
||||
);
|
||||
}
|
||||
|
||||
let key_holder = KeyChain::new_os_random_with_accounts(accounts_keys);
|
||||
if !Self::valid_private_key_transaction_pairing_check(&accounts_key_chains) {
|
||||
anyhow::bail!(
|
||||
"Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self { key_holder })
|
||||
Ok(Self {
|
||||
pub_account_signing_keys: accounts_keys,
|
||||
user_private_accounts: accounts_key_chains,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_new_account(&mut self) -> nssa::Address {
|
||||
self.key_holder.generate_new_private_key()
|
||||
/// Generated new private key for public transaction signatures
|
||||
///
|
||||
/// Returns the address of new account
|
||||
pub fn generate_new_public_transaction_private_key(&mut self) -> nssa::Address {
|
||||
let private_key = nssa::PrivateKey::new_os_random();
|
||||
let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key));
|
||||
|
||||
self.pub_account_signing_keys.insert(address, private_key);
|
||||
|
||||
address
|
||||
}
|
||||
|
||||
pub fn get_account_signing_key(&self, address: &nssa::Address) -> Option<&nssa::PrivateKey> {
|
||||
self.key_holder.get_pub_account_signing_key(address)
|
||||
/// Returns the signing key for public transaction signatures
|
||||
pub fn get_pub_account_signing_key(
|
||||
&self,
|
||||
address: &nssa::Address,
|
||||
) -> Option<&nssa::PrivateKey> {
|
||||
self.pub_account_signing_keys.get(address)
|
||||
}
|
||||
|
||||
/// Generated new private key for privacy preserving transactions
|
||||
///
|
||||
/// Returns the address of new account
|
||||
pub fn generate_new_privacy_preserving_transaction_key_chain(&mut self) -> nssa::Address {
|
||||
let key_chain = KeyChain::new_os_random();
|
||||
let address = nssa::Address::new(key_chain.produce_user_address());
|
||||
|
||||
self.user_private_accounts.insert(address, key_chain);
|
||||
|
||||
address
|
||||
}
|
||||
|
||||
/// Returns the signing key for public transaction signatures
|
||||
pub fn get_private_account_key_chain(&self, address: &nssa::Address) -> Option<&KeyChain> {
|
||||
self.user_private_accounts.get(address)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NSSAUserData {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
//Safe unwrap as maps are empty
|
||||
Self::new_with_accounts(HashMap::default(), HashMap::default()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,8 +114,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_new_account() {
|
||||
let mut user_data = NSSAUserData::new();
|
||||
let mut user_data = NSSAUserData::default();
|
||||
|
||||
let _addr = user_data.generate_new_account();
|
||||
let addr_pub = user_data.generate_new_public_transaction_private_key();
|
||||
let addr_private = user_data.generate_new_privacy_preserving_transaction_key_chain();
|
||||
|
||||
let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some();
|
||||
|
||||
assert!(is_private_key_generated);
|
||||
|
||||
let is_key_chain_generated = user_data
|
||||
.get_private_account_key_chain(&addr_private)
|
||||
.is_some();
|
||||
|
||||
assert!(is_key_chain_generated);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,11 @@ secp256k1 = "0.31.1"
|
||||
rand = "0.8"
|
||||
borsh = "1.5.7"
|
||||
hex = "0.4.3"
|
||||
k256 = "0.13.3"
|
||||
|
||||
[dev-dependencies]
|
||||
test-program-methods = { path = "test_program_methods" }
|
||||
hex-literal = "1.0.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@ -13,6 +13,8 @@ pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, Incoming
|
||||
|
||||
use crate::{Commitment, account::Account};
|
||||
|
||||
pub type Scalar = [u8; 32];
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct SharedSecretKey([u8; 32]);
|
||||
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use k256::{
|
||||
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, Scalar,
|
||||
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint,
|
||||
elliptic_curve::{
|
||||
PrimeField,
|
||||
sec1::{FromEncodedPoint, ToEncodedPoint},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::SharedSecretKey;
|
||||
use crate::{SharedSecretKey, encryption::Scalar};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct Secp256k1Point(pub(crate) Vec<u8>);
|
||||
|
||||
impl Secp256k1Point {
|
||||
pub fn from_scalar(value: [u8; 32]) -> Secp256k1Point {
|
||||
pub fn from_scalar(value: Scalar) -> Secp256k1Point {
|
||||
let x_bytes: FieldBytes = value.into();
|
||||
let x = Scalar::from_repr(x_bytes).unwrap();
|
||||
let x = k256::Scalar::from_repr(x_bytes).unwrap();
|
||||
|
||||
let p = ProjectivePoint::GENERATOR * x;
|
||||
let q = AffinePoint::from(p);
|
||||
@ -26,7 +26,7 @@ impl Secp256k1Point {
|
||||
}
|
||||
}
|
||||
|
||||
pub type EphemeralSecretKey = [u8; 32];
|
||||
pub type EphemeralSecretKey = Scalar;
|
||||
pub type EphemeralPublicKey = Secp256k1Point;
|
||||
pub type IncomingViewingPublicKey = Secp256k1Point;
|
||||
impl From<&EphemeralSecretKey> for EphemeralPublicKey {
|
||||
@ -36,8 +36,8 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey {
|
||||
}
|
||||
|
||||
impl SharedSecretKey {
|
||||
pub fn new(scalar: &[u8; 32], point: &Secp256k1Point) -> Self {
|
||||
let scalar = Scalar::from_repr((*scalar).into()).unwrap();
|
||||
pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self {
|
||||
let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap();
|
||||
let point: [u8; 33] = point.0.clone().try_into().unwrap();
|
||||
|
||||
let encoded = EncodedPoint::from_bytes(point).unwrap();
|
||||
|
||||
@ -18,6 +18,12 @@ impl From<&NullifierPublicKey> for AccountId {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for NullifierPublicKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&NullifierSecretKey> for NullifierPublicKey {
|
||||
fn from(value: &NullifierSecretKey) -> Self {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
@ -34,3 +34,4 @@ fn main() {
|
||||
|
||||
write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]);
|
||||
}
|
||||
|
||||
|
||||
70
nssa/program_methods/guest/src/bin/pinata.rs
Normal file
70
nssa/program_methods/guest/src/bin/pinata.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs};
|
||||
use risc0_zkvm::sha::{Impl, Sha256};
|
||||
|
||||
const PRIZE: u128 = 150;
|
||||
|
||||
type Instruction = u128;
|
||||
|
||||
struct Challenge {
|
||||
difficulty: u8,
|
||||
seed: [u8; 32],
|
||||
}
|
||||
|
||||
impl Challenge {
|
||||
fn new(bytes: &[u8]) -> Self {
|
||||
assert_eq!(bytes.len(), 33);
|
||||
let difficulty = bytes[0];
|
||||
assert!(difficulty <= 32);
|
||||
|
||||
let mut seed = [0; 32];
|
||||
seed.copy_from_slice(&bytes[1..]);
|
||||
Self { difficulty, seed }
|
||||
}
|
||||
|
||||
// Checks if the leftmost `self.difficulty` number of bytes of SHA256(self.data || solution) are
|
||||
// zero.
|
||||
fn validate_solution(&self, solution: Instruction) -> bool {
|
||||
let mut bytes = [0; 32 + 16];
|
||||
bytes[..32].copy_from_slice(&self.seed);
|
||||
bytes[32..].copy_from_slice(&solution.to_le_bytes());
|
||||
let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap();
|
||||
let difficulty = self.difficulty as usize;
|
||||
digest[..difficulty].iter().all(|&b| b == 0)
|
||||
}
|
||||
|
||||
fn next_data(self) -> [u8; 33] {
|
||||
let mut result = [0; 33];
|
||||
result[0] = self.difficulty;
|
||||
result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// A pinata program
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
// It is expected to receive only two accounts: [pinata_account, winner_account]
|
||||
let ProgramInput {
|
||||
pre_states,
|
||||
instruction: solution,
|
||||
} = read_nssa_inputs::<Instruction>();
|
||||
|
||||
let [pinata, winner] = match pre_states.try_into() {
|
||||
Ok(array) => array,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let data = Challenge::new(&pinata.account.data);
|
||||
|
||||
if !data.validate_solution(solution) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut pinata_post = pinata.account.clone();
|
||||
let mut winner_post = winner.account.clone();
|
||||
pinata_post.balance -= PRIZE;
|
||||
pinata_post.data = data.next_data().to_vec();
|
||||
winner_post.balance += PRIZE;
|
||||
|
||||
write_nssa_outputs(vec![pinata, winner], vec![pinata_post, winner_post]);
|
||||
}
|
||||
@ -151,7 +151,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn test_encrypted_account_data_constructor() {
|
||||
let npk = NullifierPublicKey::from(&[1; 32]);
|
||||
let ivk = IncomingViewingPublicKey::from(&[2; 32]);
|
||||
let ivk = IncomingViewingPublicKey::from_scalar([2; 32]);
|
||||
let account = Account::default();
|
||||
let commitment = Commitment::new(&npk, &account);
|
||||
let esk = [3; 32];
|
||||
|
||||
@ -2,7 +2,9 @@ use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::{InstructionData, ProgramId, ProgramOutput},
|
||||
};
|
||||
use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID};
|
||||
use program_methods::{
|
||||
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID,
|
||||
};
|
||||
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
|
||||
use serde::Serialize;
|
||||
|
||||
@ -75,6 +77,16 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||
impl Program {
|
||||
pub fn pinata() -> Self {
|
||||
Self {
|
||||
id: PINATA_ID,
|
||||
elf: PINATA_ELF,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
|
||||
|
||||
@ -205,6 +205,24 @@ impl V01State {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||
impl V01State {
|
||||
pub fn add_pinata_program(&mut self, address: Address) {
|
||||
self.insert_program(Program::pinata());
|
||||
|
||||
self.public_state.insert(
|
||||
address,
|
||||
Account {
|
||||
program_owner: Program::pinata().id(),
|
||||
balance: 1500,
|
||||
// Difficulty: 3
|
||||
data: vec![3; 33],
|
||||
nonce: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
|
||||
@ -225,7 +243,7 @@ pub mod tests {
|
||||
use nssa_core::{
|
||||
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||
encryption::{EphemeralPublicKey, IncomingViewingPublicKey},
|
||||
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
|
||||
};
|
||||
|
||||
fn transfer_transaction(
|
||||
@ -738,7 +756,7 @@ pub mod tests {
|
||||
|
||||
pub struct TestPrivateKeys {
|
||||
pub nsk: NullifierSecretKey,
|
||||
pub isk: [u8; 32],
|
||||
pub isk: Scalar,
|
||||
}
|
||||
|
||||
impl TestPrivateKeys {
|
||||
|
||||
@ -23,3 +23,7 @@ path = "../common"
|
||||
|
||||
[dependencies.nssa]
|
||||
path = "../nssa"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
testnet = []
|
||||
|
||||
@ -27,8 +27,16 @@ impl SequecerChainStore {
|
||||
.map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(feature = "testnet"))]
|
||||
let state = nssa::V01State::new_with_genesis_accounts(&init_accs);
|
||||
|
||||
#[cfg(feature = "testnet")]
|
||||
let state = {
|
||||
let mut this = nssa::V01State::new_with_genesis_accounts(&init_accs);
|
||||
this.add_pinata_program("cafe".repeat(16).parse().unwrap());
|
||||
this
|
||||
};
|
||||
|
||||
let mut data = [0; 32];
|
||||
let mut prev_block_hash = [0; 32];
|
||||
|
||||
|
||||
@ -20,14 +20,13 @@ impl WalletChainStore {
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
user_data: NSSAUserData::new_with_accounts(accounts_keys)?,
|
||||
user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?,
|
||||
wallet_config: config,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) {
|
||||
self.user_data
|
||||
.key_holder
|
||||
.pub_account_signing_keys
|
||||
.insert(acc_data.address, acc_data.pub_sign_key);
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ pub fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
|
||||
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> {
|
||||
let mut vec_for_storage = vec![];
|
||||
|
||||
for (addr, key) in &user_data.key_holder.pub_account_signing_keys {
|
||||
for (addr, key) in &user_data.pub_account_signing_keys {
|
||||
vec_for_storage.push(PersistentAccountData {
|
||||
address: *addr,
|
||||
pub_sign_key: key.clone(),
|
||||
|
||||
@ -73,7 +73,9 @@ impl WalletCore {
|
||||
}
|
||||
|
||||
pub fn create_new_account(&mut self) -> Address {
|
||||
self.storage.user_data.generate_new_account()
|
||||
self.storage
|
||||
.user_data
|
||||
.generate_new_public_transaction_private_key()
|
||||
}
|
||||
|
||||
pub fn search_for_initial_account(&self, acc_addr: Address) -> Option<Account> {
|
||||
@ -85,6 +87,24 @@ impl WalletCore {
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn claim_pinata(
|
||||
&self,
|
||||
pinata_addr: Address,
|
||||
winner_addr: Address,
|
||||
solution: u128,
|
||||
) -> Result<SendTxResponse, ExecutionFailureKind> {
|
||||
let addresses = vec![pinata_addr, winner_addr];
|
||||
let program_id = nssa::program::Program::pinata().id();
|
||||
let message =
|
||||
nssa::public_transaction::Message::try_new(program_id, addresses, vec![], solution)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||
|
||||
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||
}
|
||||
|
||||
pub async fn send_public_native_token_transfer(
|
||||
&self,
|
||||
from: Address,
|
||||
@ -110,7 +130,7 @@ impl WalletCore {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signing_key = self.storage.user_data.get_account_signing_key(&from);
|
||||
let signing_key = self.storage.user_data.get_pub_account_signing_key(&from);
|
||||
|
||||
let Some(signing_key) = signing_key else {
|
||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||
@ -199,6 +219,19 @@ pub enum Command {
|
||||
#[arg(short, long)]
|
||||
addr: String,
|
||||
},
|
||||
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
|
||||
// Claim piñata prize
|
||||
ClaimPinata {
|
||||
///pinata_addr - valid 32 byte hex string
|
||||
#[arg(long)]
|
||||
pinata_addr: String,
|
||||
///winner_addr - valid 32 byte hex string
|
||||
#[arg(long)]
|
||||
winner_addr: String,
|
||||
///solution - solution to pinata challenge
|
||||
#[arg(long)]
|
||||
solution: u128,
|
||||
},
|
||||
}
|
||||
|
||||
///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
|
||||
@ -262,6 +295,20 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
|
||||
let account: HumanReadableAccount = wallet_core.get_account(addr).await?.into();
|
||||
println!("{}", serde_json::to_string(&account).unwrap());
|
||||
}
|
||||
Command::ClaimPinata {
|
||||
pinata_addr,
|
||||
winner_addr,
|
||||
solution,
|
||||
} => {
|
||||
let res = wallet_core
|
||||
.claim_pinata(
|
||||
pinata_addr.parse().unwrap(),
|
||||
winner_addr.parse().unwrap(),
|
||||
solution,
|
||||
)
|
||||
.await?;
|
||||
info!("Results of tx send is {res:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user