mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-03 22:03:06 +00:00
Merge branch 'schouhy/change-authorization-mechanism' into schouhy/add-token-program
This commit is contained in:
commit
bedcccb633
@ -41,6 +41,9 @@ ark-bn254 = "0.5.0"
|
|||||||
ark-ff = "0.5.0"
|
ark-ff = "0.5.0"
|
||||||
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
|
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
|
||||||
base64 = "0.22.1"
|
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 = [
|
rocksdb = { version = "0.21.0", default-features = false, features = [
|
||||||
"snappy",
|
"snappy",
|
||||||
|
|||||||
@ -1,41 +1,56 @@
|
|||||||
|
use rs_merkle::Hasher;
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
|
|
||||||
use rs_merkle::Hasher;
|
use crate::{OwnHasher, transaction::EncodedTransaction};
|
||||||
|
|
||||||
use crate::merkle_tree_public::hasher::OwnHasher;
|
|
||||||
use nssa;
|
|
||||||
|
|
||||||
pub type BlockHash = [u8; 32];
|
pub type BlockHash = [u8; 32];
|
||||||
pub type BlockId = u64;
|
pub type BlockId = u64;
|
||||||
|
pub type TimeStamp = u64;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BlockHeader {
|
||||||
|
pub block_id: BlockId,
|
||||||
|
pub prev_block_hash: BlockHash,
|
||||||
|
pub hash: BlockHash,
|
||||||
|
pub timestamp: TimeStamp,
|
||||||
|
pub signature: nssa::Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BlockBody {
|
||||||
|
pub transactions: Vec<EncodedTransaction>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
pub block_id: BlockId,
|
pub header: BlockHeader,
|
||||||
pub prev_block_id: BlockId,
|
pub body: BlockBody,
|
||||||
pub prev_block_hash: BlockHash,
|
|
||||||
pub hash: BlockHash,
|
|
||||||
pub transactions: Vec<nssa::PublicTransaction>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct HashableBlockData {
|
pub struct HashableBlockData {
|
||||||
pub block_id: BlockId,
|
pub block_id: BlockId,
|
||||||
pub prev_block_id: BlockId,
|
|
||||||
pub prev_block_hash: BlockHash,
|
pub prev_block_hash: BlockHash,
|
||||||
pub transactions: Vec<nssa::PublicTransaction>,
|
pub timestamp: TimeStamp,
|
||||||
|
pub transactions: Vec<EncodedTransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HashableBlockData> for Block {
|
impl HashableBlockData {
|
||||||
fn from(value: HashableBlockData) -> Self {
|
pub fn into_block(self, signing_key: &nssa::PrivateKey) -> Block {
|
||||||
let data = value.to_bytes();
|
let data_bytes = self.to_bytes();
|
||||||
let hash = OwnHasher::hash(&data);
|
let signature = nssa::Signature::new(signing_key, &data_bytes);
|
||||||
|
let hash = OwnHasher::hash(&data_bytes);
|
||||||
Self {
|
Block {
|
||||||
block_id: value.block_id,
|
header: BlockHeader {
|
||||||
prev_block_id: value.prev_block_id,
|
block_id: self.block_id,
|
||||||
hash,
|
prev_block_hash: self.prev_block_hash,
|
||||||
transactions: value.transactions,
|
hash,
|
||||||
prev_block_hash: value.prev_block_hash,
|
timestamp: self.timestamp,
|
||||||
|
signature,
|
||||||
|
},
|
||||||
|
body: BlockBody {
|
||||||
|
transactions: self.transactions,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,10 +58,10 @@ impl From<HashableBlockData> for Block {
|
|||||||
impl From<Block> for HashableBlockData {
|
impl From<Block> for HashableBlockData {
|
||||||
fn from(value: Block) -> Self {
|
fn from(value: Block) -> Self {
|
||||||
Self {
|
Self {
|
||||||
block_id: value.block_id,
|
block_id: value.header.block_id,
|
||||||
prev_block_id: value.prev_block_id,
|
prev_block_hash: value.header.prev_block_hash,
|
||||||
prev_block_hash: value.prev_block_hash,
|
timestamp: value.header.timestamp,
|
||||||
transactions: value.transactions,
|
transactions: value.body.transactions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,11 +70,15 @@ impl HashableBlockData {
|
|||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
bytes.extend_from_slice(&self.block_id.to_le_bytes());
|
bytes.extend_from_slice(&self.block_id.to_le_bytes());
|
||||||
bytes.extend_from_slice(&self.prev_block_id.to_le_bytes());
|
|
||||||
bytes.extend_from_slice(&self.prev_block_hash);
|
bytes.extend_from_slice(&self.prev_block_hash);
|
||||||
|
bytes.extend_from_slice(&self.timestamp.to_le_bytes());
|
||||||
let num_transactions: u32 = self.transactions.len() as u32;
|
let num_transactions: u32 = self.transactions.len() as u32;
|
||||||
bytes.extend_from_slice(&num_transactions.to_le_bytes());
|
bytes.extend_from_slice(&num_transactions.to_le_bytes());
|
||||||
for tx in &self.transactions {
|
for tx in &self.transactions {
|
||||||
|
let transaction_bytes = tx.to_bytes();
|
||||||
|
let num_transaction_bytes: u32 = transaction_bytes.len() as u32;
|
||||||
|
|
||||||
|
bytes.extend_from_slice(&num_transaction_bytes.to_le_bytes());
|
||||||
bytes.extend_from_slice(&tx.to_bytes());
|
bytes.extend_from_slice(&tx.to_bytes());
|
||||||
}
|
}
|
||||||
bytes
|
bytes
|
||||||
@ -70,37 +89,47 @@ impl HashableBlockData {
|
|||||||
let mut cursor = Cursor::new(data);
|
let mut cursor = Cursor::new(data);
|
||||||
|
|
||||||
let block_id = u64_from_cursor(&mut cursor);
|
let block_id = u64_from_cursor(&mut cursor);
|
||||||
let prev_block_id = u64_from_cursor(&mut cursor);
|
|
||||||
|
|
||||||
let mut prev_block_hash = [0u8; 32];
|
let mut prev_block_hash = [0u8; 32];
|
||||||
cursor.read_exact(&mut prev_block_hash).unwrap();
|
cursor.read_exact(&mut prev_block_hash).unwrap();
|
||||||
|
|
||||||
|
let timestamp = u64_from_cursor(&mut cursor);
|
||||||
|
|
||||||
let num_transactions = u32_from_cursor(&mut cursor) as usize;
|
let num_transactions = u32_from_cursor(&mut cursor) as usize;
|
||||||
|
|
||||||
let mut transactions = Vec::with_capacity(num_transactions);
|
let mut transactions = Vec::with_capacity(num_transactions);
|
||||||
for _ in 0..num_transactions {
|
for _ in 0..num_transactions {
|
||||||
let tx = nssa::PublicTransaction::from_cursor(&mut cursor).unwrap();
|
let tx_len = u32_from_cursor(&mut cursor) as usize;
|
||||||
|
let mut tx_bytes = Vec::with_capacity(tx_len);
|
||||||
|
|
||||||
|
for _ in 0..tx_len {
|
||||||
|
let mut buff = [0; 1];
|
||||||
|
cursor.read_exact(&mut buff).unwrap();
|
||||||
|
tx_bytes.push(buff[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx = EncodedTransaction::from_bytes(tx_bytes);
|
||||||
transactions.push(tx);
|
transactions.push(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
block_id,
|
block_id,
|
||||||
prev_block_id,
|
|
||||||
prev_block_hash,
|
prev_block_hash,
|
||||||
|
timestamp,
|
||||||
transactions,
|
transactions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Improve error handling. Remove unwraps.
|
// TODO: Improve error handling. Remove unwraps.
|
||||||
fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 {
|
pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 {
|
||||||
let mut word_buf = [0u8; 4];
|
let mut word_buf = [0u8; 4];
|
||||||
cursor.read_exact(&mut word_buf).unwrap();
|
cursor.read_exact(&mut word_buf).unwrap();
|
||||||
u32::from_le_bytes(word_buf)
|
u32::from_le_bytes(word_buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Improve error handling. Remove unwraps.
|
// TODO: Improve error handling. Remove unwraps.
|
||||||
fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 {
|
pub fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 {
|
||||||
let mut word_buf = [0u8; 8];
|
let mut word_buf = [0u8; 8];
|
||||||
cursor.read_exact(&mut word_buf).unwrap();
|
cursor.read_exact(&mut word_buf).unwrap();
|
||||||
u64::from_le_bytes(word_buf)
|
u64::from_le_bytes(word_buf)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::merkle_tree_public::CommitmentHashType;
|
use crate::CommitmentHashType;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
|
||||||
pub struct Commitment {
|
pub struct Commitment {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::merkle_tree_public::TreeHashType;
|
use crate::TreeHashType;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct PublicNativeTokenSend {
|
pub struct PublicNativeTokenSend {
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
use merkle_tree_public::TreeHashType;
|
use rs_merkle::Hasher;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use sha2::{Digest, Sha256, digest::FixedOutput};
|
||||||
|
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod commitment;
|
pub mod commitment;
|
||||||
pub mod execution_input;
|
pub mod execution_input;
|
||||||
pub mod merkle_tree_public;
|
|
||||||
pub mod nullifier;
|
pub mod nullifier;
|
||||||
pub mod rpc_primitives;
|
pub mod rpc_primitives;
|
||||||
pub mod sequencer_client;
|
pub mod sequencer_client;
|
||||||
@ -16,6 +16,25 @@ pub mod test_utils;
|
|||||||
|
|
||||||
use rpc_primitives::errors::RpcError;
|
use rpc_primitives::errors::RpcError;
|
||||||
|
|
||||||
|
pub type TreeHashType = [u8; 32];
|
||||||
|
pub type CommitmentHashType = Vec<u8>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
///Our own hasher.
|
||||||
|
/// Currently it is SHA256 hasher wrapper. May change in a future.
|
||||||
|
pub struct OwnHasher {}
|
||||||
|
|
||||||
|
impl Hasher for OwnHasher {
|
||||||
|
type Hash = TreeHashType;
|
||||||
|
|
||||||
|
fn hash(data: &[u8]) -> TreeHashType {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
|
||||||
|
hasher.update(data);
|
||||||
|
<TreeHashType>::from(hasher.finalize_fixed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///Account id on blockchain
|
///Account id on blockchain
|
||||||
pub type AccountId = TreeHashType;
|
pub type AccountId = TreeHashType;
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
use rs_merkle::Hasher;
|
|
||||||
use sha2::{Digest, Sha256, digest::FixedOutput};
|
|
||||||
|
|
||||||
use super::TreeHashType;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
///Our own hasher.
|
|
||||||
/// Currently it is SHA256 hasher wrapper. May change in a future.
|
|
||||||
pub struct OwnHasher {}
|
|
||||||
|
|
||||||
impl Hasher for OwnHasher {
|
|
||||||
type Hash = TreeHashType;
|
|
||||||
|
|
||||||
fn hash(data: &[u8]) -> TreeHashType {
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
|
|
||||||
hasher.update(data);
|
|
||||||
<TreeHashType>::from(hasher.finalize_fixed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
use std::{collections::HashMap, fmt, marker::PhantomData};
|
|
||||||
|
|
||||||
use rs_merkle::{MerkleProof, MerkleTree};
|
|
||||||
use serde::{
|
|
||||||
Deserialize, Deserializer, Serialize,
|
|
||||||
de::{SeqAccess, Visitor},
|
|
||||||
ser::SerializeSeq,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment};
|
|
||||||
|
|
||||||
use super::{TreeHashType, hasher::OwnHasher, tree_leav_item::TreeLeavItem};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct HashStorageMerkleTree<Leav: TreeLeavItem + Clone> {
|
|
||||||
leaves: HashMap<usize, Leav>,
|
|
||||||
hash_to_id_map: HashMap<TreeHashType, usize>,
|
|
||||||
tree: MerkleTree<OwnHasher>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Leav: TreeLeavItem + Clone + Serialize> Serialize for HashStorageMerkleTree<Leav> {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let mut vector = self.leaves.iter().collect::<Vec<_>>();
|
|
||||||
vector.sort_by(|a, b| a.0.cmp(b.0));
|
|
||||||
|
|
||||||
let mut seq = serializer.serialize_seq(Some(self.leaves.len()))?;
|
|
||||||
for element in vector.iter() {
|
|
||||||
seq.serialize_element(element.1)?;
|
|
||||||
}
|
|
||||||
seq.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HashStorageMerkleTreeDeserializer<Leav: TreeLeavItem + Clone> {
|
|
||||||
marker: PhantomData<fn() -> HashStorageMerkleTree<Leav>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Leaf: TreeLeavItem + Clone> HashStorageMerkleTreeDeserializer<Leaf> {
|
|
||||||
fn new() -> Self {
|
|
||||||
HashStorageMerkleTreeDeserializer {
|
|
||||||
marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> Visitor<'de>
|
|
||||||
for HashStorageMerkleTreeDeserializer<Leav>
|
|
||||||
{
|
|
||||||
type Value = HashStorageMerkleTree<Leav>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
formatter.write_str("HashStorageMerkleTree key value sequence.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut vector = vec![];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let opt_key = seq.next_element::<Leav>()?;
|
|
||||||
if let Some(value) = opt_key {
|
|
||||||
vector.push(value);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HashStorageMerkleTree::new(vector))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> serde::Deserialize<'de>
|
|
||||||
for HashStorageMerkleTree<Leav>
|
|
||||||
{
|
|
||||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
||||||
deserializer.deserialize_seq(HashStorageMerkleTreeDeserializer::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type PublicTransactionMerkleTree = HashStorageMerkleTree<Transaction>;
|
|
||||||
|
|
||||||
pub type UTXOCommitmentsMerkleTree = HashStorageMerkleTree<UTXOCommitment>;
|
|
||||||
|
|
||||||
impl<Leav: TreeLeavItem + Clone> HashStorageMerkleTree<Leav> {
|
|
||||||
pub fn new(leaves_vec: Vec<Leav>) -> Self {
|
|
||||||
let mut leaves_map = HashMap::new();
|
|
||||||
let mut hash_to_id_map = HashMap::new();
|
|
||||||
|
|
||||||
let leaves_hashed: Vec<TreeHashType> = leaves_vec
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(id, tx)| {
|
|
||||||
leaves_map.insert(id, tx.clone());
|
|
||||||
hash_to_id_map.insert(tx.hash(), id);
|
|
||||||
tx.hash()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Self {
|
|
||||||
leaves: leaves_map,
|
|
||||||
hash_to_id_map,
|
|
||||||
tree: MerkleTree::from_leaves(&leaves_hashed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_tx(&self, hash: TreeHashType) -> Option<&Leav> {
|
|
||||||
self.hash_to_id_map
|
|
||||||
.get(&hash)
|
|
||||||
.and_then(|id| self.leaves.get(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_root(&self) -> Option<TreeHashType> {
|
|
||||||
self.tree.root()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_proof(&self, hash: TreeHashType) -> Option<MerkleProof<OwnHasher>> {
|
|
||||||
self.hash_to_id_map
|
|
||||||
.get(&hash)
|
|
||||||
.map(|id| self.tree.proof(&[*id]))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_proof_multiple(&self, hashes: &[TreeHashType]) -> Option<MerkleProof<OwnHasher>> {
|
|
||||||
let ids_opt: Vec<Option<&usize>> = hashes
|
|
||||||
.iter()
|
|
||||||
.map(|hash| self.hash_to_id_map.get(hash))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let is_valid = ids_opt.iter().all(|el| el.is_some());
|
|
||||||
|
|
||||||
if is_valid {
|
|
||||||
let ids: Vec<usize> = ids_opt.into_iter().map(|el| *el.unwrap()).collect();
|
|
||||||
|
|
||||||
Some(self.tree.proof(&ids))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_tx(&mut self, tx: &Leav) {
|
|
||||||
let last = self.leaves.len();
|
|
||||||
|
|
||||||
self.leaves.insert(last, tx.clone());
|
|
||||||
self.hash_to_id_map.insert(tx.hash(), last);
|
|
||||||
|
|
||||||
self.tree.insert(tx.hash());
|
|
||||||
|
|
||||||
self.tree.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_tx_multiple(&mut self, txs: Vec<Leav>) {
|
|
||||||
for tx in txs.iter() {
|
|
||||||
let last = self.leaves.len();
|
|
||||||
|
|
||||||
self.leaves.insert(last, tx.clone());
|
|
||||||
self.hash_to_id_map.insert(tx.hash(), last);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tree
|
|
||||||
.append(&mut txs.iter().map(|tx| tx.hash()).collect());
|
|
||||||
|
|
||||||
self.tree.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// Mock implementation of TreeLeavItem trait for testing
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
||||||
struct MockTransaction {
|
|
||||||
pub hash: TreeHashType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TreeLeavItem for MockTransaction {
|
|
||||||
fn hash(&self) -> TreeHashType {
|
|
||||||
self.hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_first_32_bytes(s: &str) -> [u8; 32] {
|
|
||||||
let mut buffer = [0u8; 32];
|
|
||||||
let bytes = s.as_bytes();
|
|
||||||
let len = std::cmp::min(32, bytes.len());
|
|
||||||
|
|
||||||
buffer[..len].copy_from_slice(&bytes[..len]);
|
|
||||||
buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_merkle_tree() {
|
|
||||||
let tx1 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx1"),
|
|
||||||
};
|
|
||||||
let tx2 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx2"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
|
|
||||||
|
|
||||||
assert_eq!(tree.leaves.len(), 2);
|
|
||||||
assert!(tree.get_root().is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_merkle_tree_serialize() {
|
|
||||||
let tx1 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx1"),
|
|
||||||
};
|
|
||||||
let tx2 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx2"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
|
|
||||||
|
|
||||||
let binding = serde_json::to_vec(&tree).unwrap();
|
|
||||||
|
|
||||||
let obj: HashStorageMerkleTree<MockTransaction> = serde_json::from_slice(&binding).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(tree.leaves, obj.leaves);
|
|
||||||
assert_eq!(tree.hash_to_id_map, obj.hash_to_id_map);
|
|
||||||
assert_eq!(tree.tree.root(), obj.tree.root());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_tx() {
|
|
||||||
let tx1 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx1"),
|
|
||||||
};
|
|
||||||
let tx2 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx2"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
|
|
||||||
|
|
||||||
assert_eq!(tree.get_tx(tx1.hash()), Some(&tx1));
|
|
||||||
assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_proof() {
|
|
||||||
let tx1 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx1"),
|
|
||||||
};
|
|
||||||
let tx2 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx2"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
|
|
||||||
|
|
||||||
let proof = tree.get_proof(tx1.hash());
|
|
||||||
assert!(proof.is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_tx() {
|
|
||||||
let tx1 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx1"),
|
|
||||||
};
|
|
||||||
let tx2 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx2"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]);
|
|
||||||
|
|
||||||
tree.add_tx(&tx2);
|
|
||||||
assert_eq!(tree.leaves.len(), 2);
|
|
||||||
assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_tx_multiple() {
|
|
||||||
let tx1 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx1"),
|
|
||||||
};
|
|
||||||
let tx2 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx2"),
|
|
||||||
};
|
|
||||||
let tx3 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx3"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]);
|
|
||||||
tree.add_tx_multiple(vec![tx2.clone(), tx3.clone()]);
|
|
||||||
|
|
||||||
assert_eq!(tree.leaves.len(), 3);
|
|
||||||
assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2));
|
|
||||||
assert_eq!(tree.get_tx(tx3.hash()), Some(&tx3));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_proof_multiple() {
|
|
||||||
let tx1 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx1"),
|
|
||||||
};
|
|
||||||
let tx2 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx2"),
|
|
||||||
};
|
|
||||||
let tx3 = MockTransaction {
|
|
||||||
hash: get_first_32_bytes("tx3"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone(), tx3.clone()]);
|
|
||||||
let proof = tree.get_proof_multiple(&[tx1.hash(), tx2.hash()]);
|
|
||||||
|
|
||||||
assert!(proof.is_some());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
pub mod hasher;
|
|
||||||
pub mod merkle_tree;
|
|
||||||
pub mod tree_leav_item;
|
|
||||||
|
|
||||||
pub type TreeHashType = [u8; 32];
|
|
||||||
pub type CommitmentHashType = Vec<u8>;
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment};
|
|
||||||
|
|
||||||
use super::TreeHashType;
|
|
||||||
|
|
||||||
pub trait TreeLeavItem {
|
|
||||||
fn hash(&self) -> TreeHashType;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TreeLeavItem for Transaction {
|
|
||||||
fn hash(&self) -> TreeHashType {
|
|
||||||
self.body().hash()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TreeLeavItem for UTXOCommitment {
|
|
||||||
fn hash(&self) -> TreeHashType {
|
|
||||||
self.hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::merkle_tree_public::TreeHashType;
|
use crate::TreeHashType;
|
||||||
|
|
||||||
//ToDo: Update Nullifier model, when it is clear
|
//ToDo: Update Nullifier model, when it is clear
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use crate::rpc_primitives::requests::{
|
|||||||
GetTransactionByHashRequest, GetTransactionByHashResponse,
|
GetTransactionByHashRequest, GetTransactionByHashResponse,
|
||||||
};
|
};
|
||||||
use crate::sequencer_client::json::AccountInitialData;
|
use crate::sequencer_client::json::AccountInitialData;
|
||||||
|
use crate::transaction::{EncodedTransaction, NSSATransaction};
|
||||||
use crate::{SequencerClientError, SequencerRpcError};
|
use crate::{SequencerClientError, SequencerRpcError};
|
||||||
|
|
||||||
pub mod json;
|
pub mod json;
|
||||||
@ -142,10 +143,12 @@ impl SequencerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///Send transaction to sequencer
|
///Send transaction to sequencer
|
||||||
pub async fn send_tx(
|
pub async fn send_tx_public(
|
||||||
&self,
|
&self,
|
||||||
transaction: nssa::PublicTransaction,
|
transaction: nssa::PublicTransaction,
|
||||||
) -> Result<SendTxResponse, SequencerClientError> {
|
) -> Result<SendTxResponse, SequencerClientError> {
|
||||||
|
let transaction = EncodedTransaction::from(NSSATransaction::Public(transaction));
|
||||||
|
|
||||||
let tx_req = SendTxRequest {
|
let tx_req = SendTxRequest {
|
||||||
transaction: transaction.to_bytes(),
|
transaction: transaction.to_bytes(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
use nssa;
|
use crate::{
|
||||||
|
block::{Block, HashableBlockData},
|
||||||
|
transaction::{EncodedTransaction, NSSATransaction},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::block::{Block, HashableBlockData};
|
//Helpers
|
||||||
|
|
||||||
|
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
|
||||||
|
nssa::PrivateKey::try_new([37; 32]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
//Dummy producers
|
//Dummy producers
|
||||||
|
|
||||||
@ -10,23 +17,23 @@ use crate::block::{Block, HashableBlockData};
|
|||||||
///
|
///
|
||||||
/// `prev_hash` - hash of previous block, provide None for genesis
|
/// `prev_hash` - hash of previous block, provide None for genesis
|
||||||
///
|
///
|
||||||
/// `transactions` - vector of `Transaction` objects
|
/// `transactions` - vector of `EncodedTransaction` objects
|
||||||
pub fn produce_dummy_block(
|
pub fn produce_dummy_block(
|
||||||
id: u64,
|
id: u64,
|
||||||
prev_hash: Option<[u8; 32]>,
|
prev_hash: Option<[u8; 32]>,
|
||||||
transactions: Vec<nssa::PublicTransaction>,
|
transactions: Vec<EncodedTransaction>,
|
||||||
) -> Block {
|
) -> Block {
|
||||||
let block_data = HashableBlockData {
|
let block_data = HashableBlockData {
|
||||||
block_id: id,
|
block_id: id,
|
||||||
prev_block_id: id.saturating_sub(1),
|
|
||||||
prev_block_hash: prev_hash.unwrap_or_default(),
|
prev_block_hash: prev_hash.unwrap_or_default(),
|
||||||
|
timestamp: id * 100,
|
||||||
transactions,
|
transactions,
|
||||||
};
|
};
|
||||||
|
|
||||||
block_data.into()
|
block_data.into_block(&sequencer_sign_key_for_testing())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction {
|
pub fn produce_dummy_empty_transaction() -> EncodedTransaction {
|
||||||
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
||||||
let addresses = vec![];
|
let addresses = vec![];
|
||||||
let nonces = vec![];
|
let nonces = vec![];
|
||||||
@ -36,7 +43,10 @@ pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
|
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]);
|
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]);
|
||||||
nssa::PublicTransaction::new(message, witness_set)
|
|
||||||
|
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
EncodedTransaction::from(NSSATransaction::Public(nssa_tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_transaction_native_token_transfer(
|
pub fn create_transaction_native_token_transfer(
|
||||||
@ -45,7 +55,7 @@ pub fn create_transaction_native_token_transfer(
|
|||||||
to: [u8; 32],
|
to: [u8; 32],
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
signing_key: nssa::PrivateKey,
|
signing_key: nssa::PrivateKey,
|
||||||
) -> nssa::PublicTransaction {
|
) -> EncodedTransaction {
|
||||||
let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)];
|
let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)];
|
||||||
let nonces = vec![nonce];
|
let nonces = vec![nonce];
|
||||||
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
let program_id = nssa::program::Program::authenticated_transfer_program().id();
|
||||||
@ -53,5 +63,8 @@ pub fn create_transaction_native_token_transfer(
|
|||||||
nssa::public_transaction::Message::try_new(program_id, addresses, nonces, balance_to_move)
|
nssa::public_transaction::Message::try_new(program_id, addresses, nonces, balance_to_move)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]);
|
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]);
|
||||||
nssa::PublicTransaction::new(message, witness_set)
|
|
||||||
|
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
EncodedTransaction::from(NSSATransaction::Public(nssa_tx))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,34 @@
|
|||||||
use k256::ecdsa::{
|
use k256::ecdsa::{Signature, SigningKey, VerifyingKey};
|
||||||
Signature, SigningKey, VerifyingKey,
|
|
||||||
signature::{Signer, Verifier},
|
|
||||||
};
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use secp256k1_zkp::{PedersenCommitment, Tweak};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use sha2::{Digest, digest::FixedOutput};
|
use sha2::{Digest, digest::FixedOutput};
|
||||||
|
|
||||||
use crate::merkle_tree_public::TreeHashType;
|
|
||||||
|
|
||||||
use elliptic_curve::{
|
use elliptic_curve::{
|
||||||
consts::{B0, B1},
|
consts::{B0, B1},
|
||||||
generic_array::GenericArray,
|
generic_array::GenericArray,
|
||||||
};
|
};
|
||||||
use sha2::digest::typenum::{UInt, UTerm};
|
use sha2::digest::typenum::{UInt, UTerm};
|
||||||
|
|
||||||
use crate::TransactionSignatureError;
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum NSSATransaction {
|
||||||
|
Public(nssa::PublicTransaction),
|
||||||
|
PrivacyPreserving(nssa::PrivacyPreservingTransaction),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<nssa::PublicTransaction> for NSSATransaction {
|
||||||
|
fn from(value: nssa::PublicTransaction) -> Self {
|
||||||
|
Self::Public(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<nssa::PrivacyPreservingTransaction> for NSSATransaction {
|
||||||
|
fn from(value: nssa::PrivacyPreservingTransaction) -> Self {
|
||||||
|
Self::PrivacyPreserving(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::TreeHashType;
|
||||||
|
|
||||||
pub type CipherText = Vec<u8>;
|
pub type CipherText = Vec<u8>;
|
||||||
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;
|
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;
|
||||||
@ -25,39 +37,45 @@ pub type Tag = u8;
|
|||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum TxKind {
|
pub enum TxKind {
|
||||||
Public,
|
Public,
|
||||||
Private,
|
PrivacyPreserving,
|
||||||
Shielded,
|
|
||||||
Deshielded,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
///General transaction object
|
///General transaction object
|
||||||
pub struct TransactionBody {
|
pub struct EncodedTransaction {
|
||||||
pub tx_kind: TxKind,
|
pub tx_kind: TxKind,
|
||||||
///Tx input data (public part)
|
|
||||||
pub execution_input: Vec<u8>,
|
|
||||||
///Tx output data (public_part)
|
|
||||||
pub execution_output: Vec<u8>,
|
|
||||||
///Tx input utxo commitments
|
|
||||||
pub utxo_commitments_spent_hashes: Vec<TreeHashType>,
|
|
||||||
///Tx output utxo commitments
|
|
||||||
pub utxo_commitments_created_hashes: Vec<TreeHashType>,
|
|
||||||
///Tx output nullifiers
|
|
||||||
pub nullifier_created_hashes: Vec<TreeHashType>,
|
|
||||||
///Execution proof (private part)
|
|
||||||
pub execution_proof_private: String,
|
|
||||||
///Encoded blobs of data
|
///Encoded blobs of data
|
||||||
pub encoded_data: Vec<(CipherText, Vec<u8>, Tag)>,
|
pub encoded_transaction_data: Vec<u8>,
|
||||||
///Transaction senders ephemeral pub key
|
}
|
||||||
pub ephemeral_pub_key: Vec<u8>,
|
|
||||||
///Public (Pedersen) commitment
|
impl From<NSSATransaction> for EncodedTransaction {
|
||||||
pub commitment: Vec<PedersenCommitment>,
|
fn from(value: NSSATransaction) -> Self {
|
||||||
///tweak
|
match value {
|
||||||
pub tweak: Tweak,
|
NSSATransaction::Public(tx) => Self {
|
||||||
///secret_r
|
tx_kind: TxKind::Public,
|
||||||
pub secret_r: [u8; 32],
|
encoded_transaction_data: tx.to_bytes(),
|
||||||
///Hex-encoded address of a smart contract account called
|
},
|
||||||
pub sc_addr: String,
|
NSSATransaction::PrivacyPreserving(tx) => Self {
|
||||||
|
tx_kind: TxKind::PrivacyPreserving,
|
||||||
|
encoded_transaction_data: tx.to_bytes(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&EncodedTransaction> for NSSATransaction {
|
||||||
|
type Error = nssa::error::NssaError;
|
||||||
|
|
||||||
|
fn try_from(value: &EncodedTransaction) -> Result<Self, Self::Error> {
|
||||||
|
match value.tx_kind {
|
||||||
|
TxKind::Public => nssa::PublicTransaction::from_bytes(&value.encoded_transaction_data)
|
||||||
|
.map(|tx| tx.into()),
|
||||||
|
TxKind::PrivacyPreserving => {
|
||||||
|
nssa::PrivacyPreservingTransaction::from_bytes(&value.encoded_transaction_data)
|
||||||
|
.map(|tx| tx.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@ -153,7 +171,7 @@ impl ActionData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionBody {
|
impl EncodedTransaction {
|
||||||
/// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`.
|
/// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`.
|
||||||
pub fn hash(&self) -> TreeHashType {
|
pub fn hash(&self) -> TreeHashType {
|
||||||
let bytes_to_hash = self.to_bytes();
|
let bytes_to_hash = self.to_bytes();
|
||||||
@ -162,180 +180,48 @@ impl TransactionBody {
|
|||||||
TreeHashType::from(hasher.finalize_fixed())
|
TreeHashType::from(hasher.finalize_fixed())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
// TODO: Remove `unwrap` by implementing a `to_bytes` method
|
// TODO: Remove `unwrap` by implementing a `to_bytes` method
|
||||||
// that deterministically encodes all transaction fields to bytes
|
// that deterministically encodes all transaction fields to bytes
|
||||||
// and guarantees serialization will succeed.
|
// and guarantees serialization will succeed.
|
||||||
serde_json::to_vec(&self).unwrap()
|
serde_json::to_vec(&self).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: Vec<u8>) -> Self {
|
||||||
|
serde_json::from_slice(&bytes).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn log(&self) {
|
pub fn log(&self) {
|
||||||
info!("Transaction hash is {:?}", hex::encode(self.hash()));
|
info!("Transaction hash is {:?}", hex::encode(self.hash()));
|
||||||
info!("Transaction tx_kind is {:?}", self.tx_kind);
|
info!("Transaction tx_kind is {:?}", self.tx_kind);
|
||||||
info!("Transaction execution_input is {:?}", {
|
|
||||||
if let Ok(action) = serde_json::from_slice::<ActionData>(&self.execution_input) {
|
|
||||||
action.into_hexed_print()
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
info!("Transaction execution_output is {:?}", {
|
|
||||||
if let Ok(action) = serde_json::from_slice::<ActionData>(&self.execution_output) {
|
|
||||||
action.into_hexed_print()
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
info!(
|
|
||||||
"Transaction utxo_commitments_spent_hashes is {:?}",
|
|
||||||
self.utxo_commitments_spent_hashes
|
|
||||||
.iter()
|
|
||||||
.map(|val| hex::encode(*val))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
info!(
|
|
||||||
"Transaction utxo_commitments_created_hashes is {:?}",
|
|
||||||
self.utxo_commitments_created_hashes
|
|
||||||
.iter()
|
|
||||||
.map(|val| hex::encode(*val))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
info!(
|
|
||||||
"Transaction nullifier_created_hashes is {:?}",
|
|
||||||
self.nullifier_created_hashes
|
|
||||||
.iter()
|
|
||||||
.map(|val| hex::encode(*val))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
info!(
|
|
||||||
"Transaction encoded_data is {:?}",
|
|
||||||
self.encoded_data
|
|
||||||
.iter()
|
|
||||||
.map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone())))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
info!(
|
|
||||||
"Transaction ephemeral_pub_key is {:?}",
|
|
||||||
hex::encode(self.ephemeral_pub_key.clone())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransactionHash = [u8; 32];
|
|
||||||
pub type TransactionSignature = Signature;
|
pub type TransactionSignature = Signature;
|
||||||
pub type SignaturePublicKey = VerifyingKey;
|
pub type SignaturePublicKey = VerifyingKey;
|
||||||
pub type SignaturePrivateKey = SigningKey;
|
pub type SignaturePrivateKey = SigningKey;
|
||||||
|
|
||||||
/// A container for a transaction body with a signature.
|
|
||||||
/// Meant to be sent through the network to the sequencer
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Transaction {
|
|
||||||
body: TransactionBody,
|
|
||||||
pub signature: TransactionSignature,
|
|
||||||
pub public_key: VerifyingKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transaction {
|
|
||||||
/// Returns a new transaction signed with the provided `private_key`.
|
|
||||||
/// The signature is generated over the hash of the body as computed by `body.hash()`
|
|
||||||
pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction {
|
|
||||||
let signature: TransactionSignature = private_key.sign(&body.to_bytes());
|
|
||||||
let public_key = VerifyingKey::from(&private_key);
|
|
||||||
Self {
|
|
||||||
body,
|
|
||||||
signature,
|
|
||||||
public_key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts the transaction into an `AuthenticatedTransaction` by verifying its signature.
|
|
||||||
/// Returns an error if the signature verification fails.
|
|
||||||
pub fn into_authenticated(self) -> Result<AuthenticatedTransaction, TransactionSignatureError> {
|
|
||||||
let hash = self.body.hash();
|
|
||||||
|
|
||||||
self.public_key
|
|
||||||
.verify(&self.body.to_bytes(), &self.signature)
|
|
||||||
.map_err(|_| TransactionSignatureError::InvalidSignature)?;
|
|
||||||
|
|
||||||
Ok(AuthenticatedTransaction {
|
|
||||||
hash,
|
|
||||||
transaction: self,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the body of the transaction
|
|
||||||
pub fn body(&self) -> &TransactionBody {
|
|
||||||
&self.body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A transaction with a valid signature over the hash of its body.
|
|
||||||
/// Can only be constructed from an `Transaction`
|
|
||||||
/// if the signature is valid
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct AuthenticatedTransaction {
|
|
||||||
hash: TransactionHash,
|
|
||||||
transaction: Transaction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthenticatedTransaction {
|
|
||||||
/// Returns the underlying transaction
|
|
||||||
pub fn transaction(&self) -> &Transaction {
|
|
||||||
&self.transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_transaction(self) -> Transaction {
|
|
||||||
self.transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the precomputed hash over the body of the transaction
|
|
||||||
pub fn hash(&self) -> &TransactionHash {
|
|
||||||
&self.hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use k256::{FieldBytes, ecdsa::signature::Signer};
|
|
||||||
use secp256k1_zkp::{Tweak, constants::SECRET_KEY_SIZE};
|
|
||||||
use sha2::{Digest, digest::FixedOutput};
|
use sha2::{Digest, digest::FixedOutput};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
merkle_tree_public::TreeHashType,
|
TreeHashType,
|
||||||
transaction::{Transaction, TransactionBody, TxKind},
|
transaction::{EncodedTransaction, TxKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn test_transaction_body() -> TransactionBody {
|
fn test_transaction_body() -> EncodedTransaction {
|
||||||
TransactionBody {
|
EncodedTransaction {
|
||||||
tx_kind: TxKind::Public,
|
tx_kind: TxKind::Public,
|
||||||
execution_input: vec![1, 2, 3, 4],
|
encoded_transaction_data: vec![1, 2, 3, 4],
|
||||||
execution_output: vec![5, 6, 7, 8],
|
|
||||||
utxo_commitments_spent_hashes: vec![[9; 32], [10; 32], [11; 32], [12; 32]],
|
|
||||||
utxo_commitments_created_hashes: vec![[13; 32]],
|
|
||||||
nullifier_created_hashes: vec![[0; 32], [1; 32], [2; 32], [3; 32]],
|
|
||||||
execution_proof_private: "loremipsum".to_string(),
|
|
||||||
encoded_data: vec![(vec![255, 255, 255], vec![254, 254, 254], 1)],
|
|
||||||
ephemeral_pub_key: vec![5; 32],
|
|
||||||
commitment: vec![],
|
|
||||||
tweak: Tweak::from_slice(&[7; SECRET_KEY_SIZE]).unwrap(),
|
|
||||||
secret_r: [8; 32],
|
|
||||||
sc_addr: "someAddress".to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_transaction() -> Transaction {
|
|
||||||
let body = test_transaction_body();
|
|
||||||
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
||||||
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
|
||||||
Transaction::new(body, private_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transaction_hash_is_sha256_of_json_bytes() {
|
fn test_transaction_hash_is_sha256_of_json_bytes() {
|
||||||
let body = test_transaction_body();
|
let body = test_transaction_body();
|
||||||
let expected_hash = {
|
let expected_hash = {
|
||||||
let data = serde_json::to_vec(&body).unwrap();
|
let data = body.to_bytes();
|
||||||
let mut hasher = sha2::Sha256::new();
|
let mut hasher = sha2::Sha256::new();
|
||||||
hasher.update(&data);
|
hasher.update(&data);
|
||||||
TreeHashType::from(hasher.finalize_fixed())
|
TreeHashType::from(hasher.finalize_fixed())
|
||||||
@ -347,83 +233,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transaction_constructor() {
|
fn test_to_bytes_from_bytes() {
|
||||||
let body = test_transaction_body();
|
let body = test_transaction_body();
|
||||||
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
||||||
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
|
||||||
let transaction = Transaction::new(body.clone(), private_key.clone());
|
|
||||||
assert_eq!(
|
|
||||||
transaction.public_key,
|
|
||||||
SignaturePublicKey::from(&private_key)
|
|
||||||
);
|
|
||||||
assert_eq!(transaction.body, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
let body_bytes = body.to_bytes();
|
||||||
fn test_transaction_body_getter() {
|
let body_new = EncodedTransaction::from_bytes(body_bytes);
|
||||||
let body = test_transaction_body();
|
|
||||||
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
||||||
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
|
||||||
let transaction = Transaction::new(body.clone(), private_key.clone());
|
|
||||||
assert_eq!(transaction.body(), &body);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
assert_eq!(body, body_new);
|
||||||
fn test_into_authenticated_succeeds_for_valid_signature() {
|
|
||||||
let transaction = test_transaction();
|
|
||||||
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
|
|
||||||
|
|
||||||
let signature = authenticated_tx.transaction().signature;
|
|
||||||
let hash = authenticated_tx.hash();
|
|
||||||
|
|
||||||
assert_eq!(authenticated_tx.transaction(), &transaction);
|
|
||||||
assert_eq!(hash, &transaction.body.hash());
|
|
||||||
assert!(
|
|
||||||
authenticated_tx
|
|
||||||
.transaction()
|
|
||||||
.public_key
|
|
||||||
.verify(&transaction.body.to_bytes(), &signature)
|
|
||||||
.is_ok()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_into_authenticated_fails_for_invalid_signature() {
|
|
||||||
let body = test_transaction_body();
|
|
||||||
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
||||||
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
|
||||||
let transaction = {
|
|
||||||
let mut this = Transaction::new(body, private_key.clone());
|
|
||||||
// Modify the signature to make it invalid
|
|
||||||
// We do this by changing it to the signature of something else
|
|
||||||
this.signature = private_key.sign(b"deadbeef");
|
|
||||||
this
|
|
||||||
};
|
|
||||||
|
|
||||||
matches!(
|
|
||||||
transaction.into_authenticated(),
|
|
||||||
Err(TransactionSignatureError::InvalidSignature)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_authenticated_transaction_getter() {
|
|
||||||
let transaction = test_transaction();
|
|
||||||
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
|
|
||||||
assert_eq!(authenticated_tx.transaction(), &transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_authenticated_transaction_hash_getter() {
|
|
||||||
let transaction = test_transaction();
|
|
||||||
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
|
|
||||||
assert_eq!(authenticated_tx.hash(), &transaction.body.hash());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_authenticated_transaction_into_transaction() {
|
|
||||||
let transaction = test_transaction();
|
|
||||||
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
|
|
||||||
assert_eq!(authenticated_tx.into_transaction(), transaction);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::merkle_tree_public::TreeHashType;
|
use crate::TreeHashType;
|
||||||
|
|
||||||
//ToDo: Update UTXO Commitment model, when it is clear
|
//ToDo: Update UTXO Commitment model, when it is clear
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|||||||
@ -21,6 +21,7 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies.sequencer_core]
|
[dependencies.sequencer_core]
|
||||||
path = "../sequencer_core"
|
path = "../sequencer_core"
|
||||||
|
features = ["testnet"]
|
||||||
|
|
||||||
[dependencies.sequencer_runner]
|
[dependencies.sequencer_runner]
|
||||||
path = "../sequencer_runner"
|
path = "../sequencer_runner"
|
||||||
|
|||||||
@ -15,5 +15,7 @@
|
|||||||
"addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
|
"addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
|
||||||
"balance": 20000
|
"balance": 20000
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"signing_key": [37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
|
||||||
|
37, 37, 37, 37, 37, 37]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -273,21 +273,10 @@ pub async fn test_success_two_transactions() {
|
|||||||
info!("Second TX Success!");
|
info!("Second TX Success!");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_get_account_wallet_command() {
|
pub async fn test_get_account() {
|
||||||
let command = Command::GetAccount {
|
|
||||||
addr: ACC_SENDER.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let wallet_config = fetch_config().unwrap();
|
let wallet_config = fetch_config().unwrap();
|
||||||
|
|
||||||
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).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
|
let account = seq_client
|
||||||
.get_account(ACC_SENDER.to_string())
|
.get_account(ACC_SENDER.to_string())
|
||||||
.await
|
.await
|
||||||
@ -444,6 +433,50 @@ pub async fn test_success_token_program() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
macro_rules! test_cleanup_wrap {
|
||||||
($home_dir:ident, $test_func:ident) => {{
|
($home_dir:ident, $test_func:ident) => {{
|
||||||
let res = pre_test($home_dir.clone()).await.unwrap();
|
let res = pre_test($home_dir.clone()).await.unwrap();
|
||||||
@ -479,19 +512,23 @@ pub async fn main_tests_runner() -> Result<()> {
|
|||||||
"test_failure" => {
|
"test_failure" => {
|
||||||
test_cleanup_wrap!(home_dir, test_failure);
|
test_cleanup_wrap!(home_dir, test_failure);
|
||||||
}
|
}
|
||||||
"test_get_account_wallet_command" => {
|
"test_get_account" => {
|
||||||
test_cleanup_wrap!(home_dir, test_get_account_wallet_command);
|
test_cleanup_wrap!(home_dir, test_get_account);
|
||||||
}
|
}
|
||||||
"test_success_two_transactions" => {
|
"test_success_two_transactions" => {
|
||||||
test_cleanup_wrap!(home_dir, test_success_two_transactions);
|
test_cleanup_wrap!(home_dir, test_success_two_transactions);
|
||||||
}
|
}
|
||||||
|
"test_pinata" => {
|
||||||
|
test_cleanup_wrap!(home_dir, test_pinata);
|
||||||
|
}
|
||||||
"all" => {
|
"all" => {
|
||||||
test_cleanup_wrap!(home_dir, test_success_move_to_another_account);
|
test_cleanup_wrap!(home_dir, test_success_move_to_another_account);
|
||||||
test_cleanup_wrap!(home_dir, test_success);
|
test_cleanup_wrap!(home_dir, test_success);
|
||||||
test_cleanup_wrap!(home_dir, test_failure);
|
test_cleanup_wrap!(home_dir, test_failure);
|
||||||
test_cleanup_wrap!(home_dir, test_success_two_transactions);
|
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_success_token_program);
|
test_cleanup_wrap!(home_dir, test_success_token_program);
|
||||||
|
test_cleanup_wrap!(home_dir, test_pinata);
|
||||||
|
test_cleanup_wrap!(home_dir, test_get_account);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
anyhow::bail!("Unknown test name");
|
anyhow::bail!("Unknown test name");
|
||||||
|
|||||||
@ -15,6 +15,9 @@ elliptic-curve.workspace = true
|
|||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
aes-gcm.workspace = true
|
aes-gcm.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
|
bip39.workspace = true
|
||||||
|
hmac-sha512.workspace = true
|
||||||
|
nssa-core = { path = "../nssa/core", features = ["host"] }
|
||||||
|
|
||||||
[dependencies.common]
|
[dependencies.common]
|
||||||
path = "../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 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)]
|
#[derive(Debug)]
|
||||||
///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender.
|
///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 {
|
pub struct EphemeralKeyHolder {
|
||||||
ephemeral_secret_key: Scalar,
|
ephemeral_secret_key: EphemeralSecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EphemeralKeyHolder {
|
impl EphemeralKeyHolder {
|
||||||
pub fn new_os_random() -> Self {
|
pub fn new(
|
||||||
let mut bytes = FieldBytes::default();
|
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 {
|
Self {
|
||||||
ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(),
|
ephemeral_secret_key: hasher.finalize().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_ephemeral_public_key(&self) -> AffinePoint {
|
pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey {
|
||||||
(AffinePoint::GENERATOR * self.ephemeral_secret_key).into()
|
EphemeralPublicKey::from_scalar(self.ephemeral_secret_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_shared_secret_sender(
|
pub fn calculate_shared_secret_sender(
|
||||||
&self,
|
&self,
|
||||||
viewing_public_key_receiver: AffinePoint,
|
receiver_incoming_viewing_public_key: IncomingViewingPublicKey,
|
||||||
) -> AffinePoint {
|
) -> SharedSecretKey {
|
||||||
(viewing_public_key_receiver * self.ephemeral_secret_key).into()
|
SharedSecretKey::new(
|
||||||
}
|
&self.ephemeral_secret_key,
|
||||||
|
&receiver_incoming_viewing_public_key,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log(&self) {
|
pub fn log(&self) {
|
||||||
|
|||||||
@ -1,29 +1,25 @@
|
|||||||
use std::collections::HashMap;
|
use common::TreeHashType;
|
||||||
|
|
||||||
use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
|
|
||||||
use constants_types::{CipherText, Nonce};
|
|
||||||
use elliptic_curve::point::AffineCoordinates;
|
|
||||||
use k256::AffinePoint;
|
|
||||||
use log::info;
|
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 serde::{Deserialize, Serialize};
|
||||||
|
use sha2::{Digest, digest::FixedOutput};
|
||||||
|
|
||||||
use crate::key_protocol_core::PublicKey;
|
|
||||||
pub type PublicAccountSigningKey = [u8; 32];
|
pub type PublicAccountSigningKey = [u8; 32];
|
||||||
|
|
||||||
pub mod constants_types;
|
|
||||||
pub mod ephemeral_key_holder;
|
pub mod ephemeral_key_holder;
|
||||||
pub mod secret_holders;
|
pub mod secret_holders;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
///Entrypoint to key management
|
///Entrypoint to key management
|
||||||
pub struct KeyChain {
|
pub struct KeyChain {
|
||||||
top_secret_key_holder: TopSecretKeyHolder,
|
secret_spending_key: SecretSpendingKey,
|
||||||
pub utxo_secret_key_holder: UTXOSecretKeyHolder,
|
pub private_key_holder: PrivateKeyHolder,
|
||||||
///Map for all users accounts
|
pub nullifer_public_key: NullifierPublicKey,
|
||||||
pub pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
|
pub incoming_viewing_public_key: IncomingViewingPublicKey,
|
||||||
pub nullifer_public_key: PublicKey,
|
|
||||||
pub viewing_public_key: PublicKey,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyChain {
|
impl KeyChain {
|
||||||
@ -31,95 +27,61 @@ impl KeyChain {
|
|||||||
//Currently dropping SeedHolder at the end of initialization.
|
//Currently dropping SeedHolder at the end of initialization.
|
||||||
//Now entirely sure if we need it in the future.
|
//Now entirely sure if we need it in the future.
|
||||||
let seed_holder = SeedHolder::new_os_random();
|
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 nullifer_public_key = private_key_holder.generate_nullifier_public_key();
|
||||||
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
|
let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
top_secret_key_holder,
|
secret_spending_key,
|
||||||
utxo_secret_key_holder,
|
private_key_holder,
|
||||||
nullifer_public_key,
|
nullifer_public_key,
|
||||||
viewing_public_key,
|
incoming_viewing_public_key,
|
||||||
pub_account_signing_keys: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_os_random_with_accounts(accounts: HashMap<nssa::Address, nssa::PrivateKey>) -> Self {
|
pub fn produce_user_address(&self) -> [u8; 32] {
|
||||||
//Currently dropping SeedHolder at the end of initialization.
|
let mut hasher = sha2::Sha256::new();
|
||||||
//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 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();
|
<TreeHashType>::from(hasher.finalize_fixed())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_shared_secret_receiver(
|
pub fn calculate_shared_secret_receiver(
|
||||||
&self,
|
&self,
|
||||||
ephemeral_public_key_sender: AffinePoint,
|
ephemeral_public_key_sender: EphemeralPublicKey,
|
||||||
) -> AffinePoint {
|
) -> SharedSecretKey {
|
||||||
(ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into()
|
SharedSecretKey::new(
|
||||||
}
|
&self
|
||||||
|
.secret_spending_key
|
||||||
pub fn decrypt_data(
|
.generate_incoming_viewing_secret_key(),
|
||||||
&self,
|
&ephemeral_public_key_sender,
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log(&self) {
|
pub fn log(&self) {
|
||||||
info!(
|
info!(
|
||||||
"Secret spending key is {:?}",
|
"Secret spending key is {:?}",
|
||||||
hex::encode(
|
hex::encode(serde_json::to_vec(&self.secret_spending_key).unwrap()),
|
||||||
serde_json::to_vec(&self.top_secret_key_holder.secret_spending_key).unwrap()
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
info!(
|
info!(
|
||||||
"Nulifier secret key is {:?}",
|
"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(
|
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!(
|
info!(
|
||||||
"Viewing secret key is {:?}",
|
"Viewing secret key is {:?}",
|
||||||
hex::encode(
|
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!(
|
info!(
|
||||||
@ -128,25 +90,16 @@ impl KeyChain {
|
|||||||
);
|
);
|
||||||
info!(
|
info!(
|
||||||
"Viewing public key is {:?}",
|
"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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use aes_gcm::{
|
use aes_gcm::aead::OsRng;
|
||||||
Aes256Gcm,
|
use k256::AffinePoint;
|
||||||
aead::{Aead, KeyInit, OsRng},
|
use rand::RngCore;
|
||||||
};
|
|
||||||
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 super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -156,12 +109,7 @@ mod tests {
|
|||||||
let address_key_holder = KeyChain::new_os_random();
|
let address_key_holder = KeyChain::new_os_random();
|
||||||
|
|
||||||
// Check that key holder fields are initialized with expected types
|
// Check that key holder fields are initialized with expected types
|
||||||
assert!(!Into::<bool>::into(
|
assert_ne!(address_key_holder.nullifer_public_key.as_ref(), &[0u8; 32]);
|
||||||
address_key_holder.nullifer_public_key.is_identity()
|
|
||||||
));
|
|
||||||
assert!(!Into::<bool>::into(
|
|
||||||
address_key_holder.viewing_public_key.is_identity()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -169,176 +117,13 @@ mod tests {
|
|||||||
let address_key_holder = KeyChain::new_os_random();
|
let address_key_holder = KeyChain::new_os_random();
|
||||||
|
|
||||||
// Generate a random ephemeral public key sender
|
// Generate a random ephemeral public key sender
|
||||||
let scalar = Scalar::random(&mut OsRng);
|
let mut scalar = [0; 32];
|
||||||
let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine();
|
OsRng.fill_bytes(&mut scalar);
|
||||||
|
let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar);
|
||||||
|
|
||||||
// Calculate shared secret
|
// Calculate shared secret
|
||||||
let shared_secret =
|
let _shared_secret =
|
||||||
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
|
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]
|
#[test]
|
||||||
@ -346,10 +131,10 @@ mod tests {
|
|||||||
let seed_holder = SeedHolder::new_os_random();
|
let seed_holder = SeedHolder::new_os_random();
|
||||||
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder();
|
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 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();
|
let pub_account_signing_key = nssa::PrivateKey::new_os_random();
|
||||||
|
|
||||||
@ -364,11 +149,6 @@ mod tests {
|
|||||||
"Group generator {:?}",
|
"Group generator {:?}",
|
||||||
hex::encode(serde_json::to_vec(&AffinePoint::GENERATOR).unwrap())
|
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!();
|
||||||
|
|
||||||
println!("======Holders======");
|
println!("======Holders======");
|
||||||
|
|||||||
@ -1,101 +1,157 @@
|
|||||||
use common::merkle_tree_public::TreeHashType;
|
use bip39::Mnemonic;
|
||||||
use elliptic_curve::PrimeField;
|
use common::TreeHashType;
|
||||||
use k256::{AffinePoint, FieldBytes, Scalar};
|
use nssa_core::{
|
||||||
|
NullifierPublicKey, NullifierSecretKey,
|
||||||
|
encryption::{IncomingViewingPublicKey, Scalar},
|
||||||
|
};
|
||||||
use rand::{RngCore, rngs::OsRng};
|
use rand::{RngCore, rngs::OsRng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, digest::FixedOutput};
|
use sha2::{Digest, digest::FixedOutput};
|
||||||
|
|
||||||
use super::constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
///Seed holder. Non-clonable to ensure that different holders use different seeds.
|
///Seed holder. Non-clonable to ensure that different holders use different seeds.
|
||||||
/// Produces `TopSecretKeyHolder` objects.
|
/// Produces `TopSecretKeyHolder` objects.
|
||||||
pub struct SeedHolder {
|
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)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects.
|
///Secret spending key object. Can produce `PrivateKeyHolder` objects.
|
||||||
pub struct TopSecretKeyHolder {
|
pub struct SecretSpendingKey(pub(crate) [u8; 32]);
|
||||||
pub secret_spending_key: Scalar,
|
|
||||||
}
|
pub type IncomingViewingSecretKey = Scalar;
|
||||||
|
pub type OutgoingViewingSecretKey = Scalar;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[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.
|
///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
|
||||||
pub struct UTXOSecretKeyHolder {
|
pub struct PrivateKeyHolder {
|
||||||
pub nullifier_secret_key: Scalar,
|
pub(crate) nullifier_secret_key: NullifierSecretKey,
|
||||||
pub viewing_secret_key: Scalar,
|
pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey,
|
||||||
|
pub(crate) outgoing_viewing_secret_key: OutgoingViewingSecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SeedHolder {
|
impl SeedHolder {
|
||||||
pub fn new_os_random() -> Self {
|
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 {
|
Self {
|
||||||
seed: Scalar::from_repr(bytes).unwrap(),
|
seed: seed_wide.to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_secret_spending_key_hash(&self) -> TreeHashType {
|
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();
|
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())
|
<TreeHashType>::from(hasher.finalize_fixed())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_secret_spending_key_scalar(&self) -> Scalar {
|
pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey {
|
||||||
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();
|
let mut hasher = sha2::Sha256::new();
|
||||||
|
|
||||||
hasher.update(self.secret_spending_key.to_bytes());
|
hasher.update("NSSA_keys");
|
||||||
hasher.update(*NULLIFIER_SECRET_CONST);
|
hasher.update(self.0);
|
||||||
|
hasher.update([3u8]);
|
||||||
|
hasher.update([0u8; 22]);
|
||||||
|
|
||||||
let hash = <TreeHashType>::from(hasher.finalize_fixed());
|
<TreeHashType>::from(hasher.finalize_fixed())
|
||||||
|
|
||||||
Scalar::from_repr(hash.into()).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_viewing_secret_key(&self) -> Scalar {
|
pub fn produce_private_key_holder(&self) -> PrivateKeyHolder {
|
||||||
let mut hasher = sha2::Sha256::new();
|
PrivateKeyHolder {
|
||||||
|
|
||||||
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 {
|
|
||||||
nullifier_secret_key: self.generate_nullifier_secret_key(),
|
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 {
|
impl PrivateKeyHolder {
|
||||||
pub fn generate_nullifier_public_key(&self) -> AffinePoint {
|
pub fn generate_nullifier_public_key(&self) -> NullifierPublicKey {
|
||||||
(AffinePoint::GENERATOR * self.nullifier_secret_key).into()
|
(&self.nullifier_secret_key).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_viewing_public_key(&self) -> AffinePoint {
|
pub fn generate_incoming_viewing_public_key(&self) -> IncomingViewingPublicKey {
|
||||||
(AffinePoint::GENERATOR * self.viewing_secret_key).into()
|
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)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct NSSAUserData {
|
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 {
|
impl NSSAUserData {
|
||||||
pub fn new() -> Self {
|
fn valid_public_key_transaction_pairing_check(
|
||||||
let key_holder = KeyChain::new_os_random();
|
|
||||||
|
|
||||||
Self { key_holder }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn valid_key_transaction_pairing_check(
|
|
||||||
accounts_keys_map: &HashMap<nssa::Address, nssa::PrivateKey>,
|
accounts_keys_map: &HashMap<nssa::Address, nssa::PrivateKey>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut check_res = true;
|
let mut check_res = true;
|
||||||
@ -32,32 +29,82 @@ impl NSSAUserData {
|
|||||||
check_res
|
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(
|
pub fn new_with_accounts(
|
||||||
accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
|
accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
|
||||||
|
accounts_key_chains: HashMap<nssa::Address, KeyChain>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
if !Self::valid_key_transaction_pairing_check(&accounts_keys) {
|
if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"
|
"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 {
|
/// Generated new private key for public transaction signatures
|
||||||
self.key_holder.generate_new_private_key()
|
///
|
||||||
|
/// 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> {
|
/// Returns the signing key for public transaction signatures
|
||||||
self.key_holder.get_pub_account_signing_key(address)
|
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 {
|
impl Default for NSSAUserData {
|
||||||
fn default() -> Self {
|
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]
|
#[test]
|
||||||
fn test_new_account() {
|
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"
|
rand = "0.8"
|
||||||
borsh = "1.5.7"
|
borsh = "1.5.7"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
k256 = "0.13.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-program-methods = { path = "test_program_methods" }
|
test-program-methods = { path = "test_program_methods" }
|
||||||
hex-literal = "1.0.0"
|
hex-literal = "1.0.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|||||||
@ -78,6 +78,7 @@ mod tests {
|
|||||||
assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID);
|
assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "host")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_account_with_metadata_constructor() {
|
fn test_account_with_metadata_constructor() {
|
||||||
let account = Account {
|
let account = Account {
|
||||||
|
|||||||
@ -13,6 +13,8 @@ pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, Incoming
|
|||||||
|
|
||||||
use crate::{Commitment, account::Account};
|
use crate::{Commitment, account::Account};
|
||||||
|
|
||||||
|
pub type Scalar = [u8; 32];
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct SharedSecretKey([u8; 32]);
|
pub struct SharedSecretKey([u8; 32]);
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use k256::{
|
use k256::{
|
||||||
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, Scalar,
|
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint,
|
||||||
elliptic_curve::{
|
elliptic_curve::{
|
||||||
PrimeField,
|
PrimeField,
|
||||||
sec1::{FromEncodedPoint, ToEncodedPoint},
|
sec1::{FromEncodedPoint, ToEncodedPoint},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::SharedSecretKey;
|
use crate::{SharedSecretKey, encryption::Scalar};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub struct Secp256k1Point(pub(crate) Vec<u8>);
|
pub struct Secp256k1Point(pub(crate) Vec<u8>);
|
||||||
|
|
||||||
impl Secp256k1Point {
|
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_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 p = ProjectivePoint::GENERATOR * x;
|
||||||
let q = AffinePoint::from(p);
|
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 EphemeralPublicKey = Secp256k1Point;
|
||||||
pub type IncomingViewingPublicKey = Secp256k1Point;
|
pub type IncomingViewingPublicKey = Secp256k1Point;
|
||||||
impl From<&EphemeralSecretKey> for EphemeralPublicKey {
|
impl From<&EphemeralSecretKey> for EphemeralPublicKey {
|
||||||
@ -36,8 +36,8 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SharedSecretKey {
|
impl SharedSecretKey {
|
||||||
pub fn new(scalar: &[u8; 32], point: &Secp256k1Point) -> Self {
|
pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self {
|
||||||
let scalar = Scalar::from_repr((*scalar).into()).unwrap();
|
let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap();
|
||||||
let point: [u8; 33] = point.0.clone().try_into().unwrap();
|
let point: [u8; 33] = point.0.clone().try_into().unwrap();
|
||||||
|
|
||||||
let encoded = EncodedPoint::from_bytes(point).unwrap();
|
let encoded = EncodedPoint::from_bytes(point).unwrap();
|
||||||
|
|||||||
@ -9,7 +9,18 @@ pub struct NullifierPublicKey(pub(super) [u8; 32]);
|
|||||||
|
|
||||||
impl From<&NullifierPublicKey> for AccountId {
|
impl From<&NullifierPublicKey> for AccountId {
|
||||||
fn from(value: &NullifierPublicKey) -> Self {
|
fn from(value: &NullifierPublicKey) -> Self {
|
||||||
AccountId::new(value.0)
|
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Private/\x00\x00\x00";
|
||||||
|
|
||||||
|
let mut bytes = [0; 64];
|
||||||
|
bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX);
|
||||||
|
bytes[32..].copy_from_slice(&value.0);
|
||||||
|
AccountId::new(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for NullifierPublicKey {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.0.as_slice()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +91,8 @@ mod tests {
|
|||||||
];
|
];
|
||||||
let npk = NullifierPublicKey::from(&nsk);
|
let npk = NullifierPublicKey::from(&nsk);
|
||||||
let expected_account_id = AccountId::new([
|
let expected_account_id = AccountId::new([
|
||||||
202, 120, 42, 189, 194, 218, 78, 244, 31, 6, 108, 169, 29, 61, 22, 221, 69, 138, 197,
|
69, 160, 50, 67, 12, 56, 150, 116, 62, 145, 17, 161, 17, 45, 24, 53, 33, 167, 83, 178,
|
||||||
161, 241, 39, 142, 242, 242, 50, 188, 201, 99, 28, 176, 238,
|
47, 114, 111, 233, 251, 30, 54, 244, 184, 22, 100, 236,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let account_id = AccountId::from(&npk);
|
let account_id = AccountId::from(&npk);
|
||||||
|
|||||||
@ -34,3 +34,4 @@ fn main() {
|
|||||||
|
|
||||||
write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]);
|
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]);
|
||||||
|
}
|
||||||
@ -32,11 +32,13 @@ fn main() {
|
|||||||
|
|
||||||
// Check that the program is well behaved.
|
// Check that the program is well behaved.
|
||||||
// See the # Programs section for the definition of the `validate_execution` method.
|
// See the # Programs section for the definition of the `validate_execution` method.
|
||||||
validate_execution(&pre_states, &post_states, program_id);
|
if !validate_execution(&pre_states, &post_states, program_id) {
|
||||||
|
panic!("Bad behaved program");
|
||||||
|
}
|
||||||
|
|
||||||
let n_accounts = pre_states.len();
|
let n_accounts = pre_states.len();
|
||||||
if visibility_mask.len() != n_accounts {
|
if visibility_mask.len() != n_accounts {
|
||||||
panic!();
|
panic!("Invalid visibility mask length");
|
||||||
}
|
}
|
||||||
|
|
||||||
// These lists will be the public outputs of this circuit
|
// These lists will be the public outputs of this circuit
|
||||||
@ -59,7 +61,9 @@ fn main() {
|
|||||||
public_pre_states.push(pre_states[i].clone());
|
public_pre_states.push(pre_states[i].clone());
|
||||||
|
|
||||||
let mut post = post_states[i].clone();
|
let mut post = post_states[i].clone();
|
||||||
post.nonce += 1;
|
if pre_states[i].is_authorized {
|
||||||
|
post.nonce += 1;
|
||||||
|
}
|
||||||
if post.program_owner == DEFAULT_PROGRAM_ID {
|
if post.program_owner == DEFAULT_PROGRAM_ID {
|
||||||
// Claim account
|
// Claim account
|
||||||
post.program_owner = program_id;
|
post.program_owner = program_id;
|
||||||
@ -140,7 +144,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if private_keys_iter.next().is_some() {
|
if private_keys_iter.next().is_some() {
|
||||||
panic!("Too many private accounts keys.");
|
panic!("Too many private account keys.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if private_auth_iter.next().is_some() {
|
if private_auth_iter.next().is_some() {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use std::{fmt::Display, str::FromStr};
|
|||||||
|
|
||||||
use nssa_core::account::AccountId;
|
use nssa_core::account::AccountId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::signature::PublicKey;
|
use crate::signature::PublicKey;
|
||||||
|
|
||||||
@ -84,7 +85,12 @@ impl<'de> Deserialize<'de> for Address {
|
|||||||
|
|
||||||
impl From<&Address> for AccountId {
|
impl From<&Address> for AccountId {
|
||||||
fn from(address: &Address) -> Self {
|
fn from(address: &Address) -> Self {
|
||||||
AccountId::new(address.value)
|
const PUBLIC_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.1/AccountId/Public/\x00\x00\x00\x00";
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(PUBLIC_ACCOUNT_ID_PREFIX);
|
||||||
|
hasher.update(address.value);
|
||||||
|
AccountId::new(hasher.finalize().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +131,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_account_id_from_address() {
|
fn test_account_id_from_address() {
|
||||||
let address: Address = "37".repeat(32).parse().unwrap();
|
let address: Address = "37".repeat(32).parse().unwrap();
|
||||||
let expected_account_id = AccountId::new([55; 32]);
|
let expected_account_id = AccountId::new([
|
||||||
|
93, 223, 66, 245, 78, 230, 157, 188, 110, 161, 134, 255, 137, 177, 220, 88, 37, 44,
|
||||||
|
243, 91, 236, 4, 36, 147, 185, 112, 21, 49, 234, 4, 107, 185,
|
||||||
|
]);
|
||||||
|
|
||||||
let account_id = AccountId::from(&address);
|
let account_id = AccountId::from(&address);
|
||||||
|
|
||||||
|
|||||||
2
nssa/src/encoding/mod.rs
Normal file
2
nssa/src/encoding/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod privacy_preserving_transaction;
|
||||||
|
pub mod public_transaction;
|
||||||
@ -7,16 +7,20 @@ use nssa_core::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Address, error::NssaError, privacy_preserving_transaction::message::EncryptedAccountData,
|
Address, PrivacyPreservingTransaction, PublicKey, Signature,
|
||||||
|
error::NssaError,
|
||||||
|
privacy_preserving_transaction::{
|
||||||
|
circuit::Proof,
|
||||||
|
message::{EncryptedAccountData, Message},
|
||||||
|
witness_set::WitnessSet,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::message::Message;
|
|
||||||
|
|
||||||
const MESSAGE_ENCODING_PREFIX_LEN: usize = 22;
|
const MESSAGE_ENCODING_PREFIX_LEN: usize = 22;
|
||||||
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/";
|
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/";
|
||||||
|
|
||||||
impl EncryptedAccountData {
|
impl EncryptedAccountData {
|
||||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
let mut bytes = self.ciphertext.to_bytes();
|
let mut bytes = self.ciphertext.to_bytes();
|
||||||
bytes.extend_from_slice(&self.epk.to_bytes());
|
bytes.extend_from_slice(&self.epk.to_bytes());
|
||||||
bytes.push(self.view_tag);
|
bytes.push(self.view_tag);
|
||||||
@ -168,3 +172,86 @@ impl Message {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WitnessSet {
|
||||||
|
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
let size = self.signatures_and_public_keys().len() as u32;
|
||||||
|
bytes.extend_from_slice(&size.to_le_bytes());
|
||||||
|
for (signature, public_key) in self.signatures_and_public_keys() {
|
||||||
|
bytes.extend_from_slice(signature.to_bytes());
|
||||||
|
bytes.extend_from_slice(public_key.to_bytes());
|
||||||
|
}
|
||||||
|
bytes.extend_from_slice(&self.proof.to_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
|
||||||
|
let num_signatures: u32 = {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
cursor.read_exact(&mut buf)?;
|
||||||
|
u32::from_le_bytes(buf)
|
||||||
|
};
|
||||||
|
let mut signatures_and_public_keys = Vec::with_capacity(num_signatures as usize);
|
||||||
|
for _i in 0..num_signatures {
|
||||||
|
let signature = Signature::from_cursor(cursor)?;
|
||||||
|
let public_key = PublicKey::from_cursor(cursor)?;
|
||||||
|
signatures_and_public_keys.push((signature, public_key))
|
||||||
|
}
|
||||||
|
let proof = Proof::from_cursor(cursor)?;
|
||||||
|
Ok(Self {
|
||||||
|
signatures_and_public_keys,
|
||||||
|
proof,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrivacyPreservingTransaction {
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut bytes = self.message().to_bytes();
|
||||||
|
bytes.extend_from_slice(&self.witness_set().to_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, NssaError> {
|
||||||
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
Self::from_cursor(&mut cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
|
||||||
|
let message = Message::from_cursor(cursor)?;
|
||||||
|
let witness_set = WitnessSet::from_cursor(cursor)?;
|
||||||
|
Ok(PrivacyPreservingTransaction::new(message, witness_set))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Proof {
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
let proof_len = self.0.len() as u32;
|
||||||
|
bytes.extend_from_slice(&proof_len.to_le_bytes());
|
||||||
|
bytes.extend_from_slice(&self.0);
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
|
||||||
|
let proof_len = u32_from_cursor(cursor) as usize;
|
||||||
|
let mut proof = Vec::with_capacity(proof_len);
|
||||||
|
|
||||||
|
for _ in 0..proof_len {
|
||||||
|
let mut one_byte_buf = [0u8];
|
||||||
|
|
||||||
|
cursor.read_exact(&mut one_byte_buf)?;
|
||||||
|
|
||||||
|
proof.push(one_byte_buf[0]);
|
||||||
|
}
|
||||||
|
Ok(Self(proof))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Improve error handling. Remove unwraps.
|
||||||
|
pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 {
|
||||||
|
let mut word_buf = [0u8; 4];
|
||||||
|
cursor.read_exact(&mut word_buf).unwrap();
|
||||||
|
u32::from_le_bytes(word_buf)
|
||||||
|
}
|
||||||
@ -45,4 +45,7 @@ pub enum NssaError {
|
|||||||
|
|
||||||
#[error("Invalid privacy preserving execution circuit proof")]
|
#[error("Invalid privacy preserving execution circuit proof")]
|
||||||
InvalidPrivacyPreservingProof,
|
InvalidPrivacyPreservingProof,
|
||||||
|
|
||||||
|
#[error("Circuit proving error")]
|
||||||
|
CircuitProvingError(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
pub mod address;
|
pub mod address;
|
||||||
|
pub mod encoding;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod merkle_tree;
|
mod merkle_tree;
|
||||||
mod privacy_preserving_transaction;
|
mod privacy_preserving_transaction;
|
||||||
|
|||||||
@ -12,15 +12,7 @@ use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT
|
|||||||
|
|
||||||
/// Proof of the privacy preserving execution circuit
|
/// Proof of the privacy preserving execution circuit
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Proof(Vec<u8>);
|
pub struct Proof(pub(crate) Vec<u8>);
|
||||||
|
|
||||||
impl Proof {
|
|
||||||
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
|
|
||||||
let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap();
|
|
||||||
let receipt = Receipt::new(inner, circuit_output.to_bytes());
|
|
||||||
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates a proof of the execution of a NSSA program inside the privacy preserving execution
|
/// Generates a proof of the execution of a NSSA program inside the privacy preserving execution
|
||||||
/// circuit
|
/// circuit
|
||||||
@ -55,7 +47,9 @@ pub fn execute_and_prove(
|
|||||||
env_builder.write(&circuit_input).unwrap();
|
env_builder.write(&circuit_input).unwrap();
|
||||||
let env = env_builder.build().unwrap();
|
let env = env_builder.build().unwrap();
|
||||||
let prover = default_prover();
|
let prover = default_prover();
|
||||||
let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap();
|
let prove_info = prover
|
||||||
|
.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF)
|
||||||
|
.map_err(|e| NssaError::CircuitProvingError(e.to_string()))?;
|
||||||
|
|
||||||
let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?);
|
let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?);
|
||||||
|
|
||||||
@ -86,6 +80,14 @@ fn execute_and_prove_program(
|
|||||||
.receipt)
|
.receipt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Proof {
|
||||||
|
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
|
||||||
|
let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap();
|
||||||
|
let receipt = Receipt::new(inner, circuit_output.to_bytes());
|
||||||
|
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
@ -110,6 +112,7 @@ mod tests {
|
|||||||
let program = Program::authenticated_transfer_program();
|
let program = Program::authenticated_transfer_program();
|
||||||
let sender = AccountWithMetadata::new(
|
let sender = AccountWithMetadata::new(
|
||||||
Account {
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
balance: 100,
|
balance: 100,
|
||||||
..Account::default()
|
..Account::default()
|
||||||
},
|
},
|
||||||
@ -177,6 +180,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prove_privacy_preserving_execution_circuit_fully_private() {
|
fn prove_privacy_preserving_execution_circuit_fully_private() {
|
||||||
|
let program = Program::authenticated_transfer_program();
|
||||||
let sender_keys = test_private_account_keys_1();
|
let sender_keys = test_private_account_keys_1();
|
||||||
let recipient_keys = test_private_account_keys_2();
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
|
||||||
@ -184,7 +188,8 @@ mod tests {
|
|||||||
Account {
|
Account {
|
||||||
balance: 100,
|
balance: 100,
|
||||||
nonce: 0xdeadbeef,
|
nonce: 0xdeadbeef,
|
||||||
..Account::default()
|
program_owner: program.id(),
|
||||||
|
data: vec![],
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
AccountId::from(&sender_keys.npk()),
|
AccountId::from(&sender_keys.npk()),
|
||||||
|
|||||||
@ -151,7 +151,7 @@ pub mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_encrypted_account_data_constructor() {
|
fn test_encrypted_account_data_constructor() {
|
||||||
let npk = NullifierPublicKey::from(&[1; 32]);
|
let npk = NullifierPublicKey::from(&[1; 32]);
|
||||||
let ivk = IncomingViewingPublicKey::from(&[2; 32]);
|
let ivk = IncomingViewingPublicKey::from_scalar([2; 32]);
|
||||||
let account = Account::default();
|
let account = Account::default();
|
||||||
let commitment = Commitment::new(&npk, &account);
|
let commitment = Commitment::new(&npk, &account);
|
||||||
let esk = [3; 32];
|
let esk = [3; 32];
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
mod encoding;
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
pub mod witness_set;
|
pub mod witness_set;
|
||||||
|
|||||||
@ -170,3 +170,42 @@ fn n_unique<T: Eq + Hash>(data: &[T]) -> usize {
|
|||||||
let set: HashSet<&T> = data.iter().collect();
|
let set: HashSet<&T> = data.iter().collect();
|
||||||
set.len()
|
set.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
Address, PrivacyPreservingTransaction, PrivateKey, PublicKey,
|
||||||
|
privacy_preserving_transaction::{
|
||||||
|
circuit::Proof, message::tests::message_for_tests, witness_set::WitnessSet,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn keys_for_tests() -> (PrivateKey, PrivateKey, Address, Address) {
|
||||||
|
let key1 = PrivateKey::try_new([1; 32]).unwrap();
|
||||||
|
let key2 = PrivateKey::try_new([2; 32]).unwrap();
|
||||||
|
let addr1 = Address::from(&PublicKey::new_from_private_key(&key1));
|
||||||
|
let addr2 = Address::from(&PublicKey::new_from_private_key(&key2));
|
||||||
|
(key1, key2, addr1, addr2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proof_for_tests() -> Proof {
|
||||||
|
Proof(vec![1, 2, 3, 4, 5])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transaction_for_tests() -> PrivacyPreservingTransaction {
|
||||||
|
let (key1, key2, _, _) = keys_for_tests();
|
||||||
|
|
||||||
|
let message = message_for_tests();
|
||||||
|
|
||||||
|
let witness_set = WitnessSet::for_message(&message, proof_for_tests(), &[&key1, &key2]);
|
||||||
|
PrivacyPreservingTransaction::new(message, witness_set)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_privacy_preserving_transaction_encoding_bytes_roundtrip() {
|
||||||
|
let tx = transaction_for_tests();
|
||||||
|
let bytes = tx.to_bytes();
|
||||||
|
let tx_from_bytes = PrivacyPreservingTransaction::from_bytes(&bytes).unwrap();
|
||||||
|
assert_eq!(tx, tx_from_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,8 +5,8 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct WitnessSet {
|
pub struct WitnessSet {
|
||||||
pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||||
pub(super) proof: Proof,
|
pub(crate) proof: Proof,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WitnessSet {
|
impl WitnessSet {
|
||||||
|
|||||||
@ -2,7 +2,10 @@ use nssa_core::{
|
|||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{InstructionData, ProgramId, ProgramOutput},
|
program::{InstructionData, ProgramId, ProgramOutput},
|
||||||
};
|
};
|
||||||
use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, TOKEN_ELF, TOKEN_ID};
|
use program_methods::{
|
||||||
|
AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, PINATA_ELF, PINATA_ID, TOKEN_ELF,
|
||||||
|
TOKEN_ID,
|
||||||
|
};
|
||||||
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
|
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@ -82,6 +85,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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
|
use nssa_core::account::{Account, AccountId, AccountWithMetadata};
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
mod encoding;
|
|
||||||
mod message;
|
mod message;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
mod witness_set;
|
mod witness_set;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message};
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct WitnessSet {
|
pub struct WitnessSet {
|
||||||
pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WitnessSet {
|
impl WitnessSet {
|
||||||
|
|||||||
@ -146,12 +146,6 @@ impl V01State {
|
|||||||
*current_account = post;
|
*current_account = post;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 5. Increment nonces
|
|
||||||
for address in tx.signer_addresses() {
|
|
||||||
let current_account = self.get_account_by_address_mut(address);
|
|
||||||
current_account.nonce += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +206,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)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
|
|
||||||
@ -220,6 +232,7 @@ pub mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
Address, PublicKey, PublicTransaction, V01State,
|
Address, PublicKey, PublicTransaction, V01State,
|
||||||
error::NssaError,
|
error::NssaError,
|
||||||
|
execute_and_prove,
|
||||||
privacy_preserving_transaction::{
|
privacy_preserving_transaction::{
|
||||||
PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet,
|
PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet,
|
||||||
},
|
},
|
||||||
@ -230,8 +243,8 @@ pub mod tests {
|
|||||||
|
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||||
account::{Account, AccountWithMetadata, Nonce},
|
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||||
encryption::{EphemeralPublicKey, IncomingViewingPublicKey},
|
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn transfer_transaction(
|
fn transfer_transaction(
|
||||||
@ -749,7 +762,7 @@ pub mod tests {
|
|||||||
|
|
||||||
pub struct TestPrivateKeys {
|
pub struct TestPrivateKeys {
|
||||||
pub nsk: NullifierSecretKey,
|
pub nsk: NullifierSecretKey,
|
||||||
pub isk: [u8; 32],
|
pub isk: Scalar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestPrivateKeys {
|
impl TestPrivateKeys {
|
||||||
@ -940,6 +953,13 @@ pub mod tests {
|
|||||||
&state,
|
&state,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let expected_sender_post = {
|
||||||
|
let mut this = state.get_account_by_address(&sender_keys.address());
|
||||||
|
this.balance -= balance_to_move;
|
||||||
|
this.nonce += 1;
|
||||||
|
this
|
||||||
|
};
|
||||||
|
|
||||||
let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap();
|
let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap();
|
||||||
assert!(!state.private_state.0.contains(&expected_new_commitment));
|
assert!(!state.private_state.0.contains(&expected_new_commitment));
|
||||||
|
|
||||||
@ -947,6 +967,8 @@ pub mod tests {
|
|||||||
.transition_from_privacy_preserving_transaction(&tx)
|
.transition_from_privacy_preserving_transaction(&tx)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let sender_post = state.get_account_by_address(&sender_keys.address());
|
||||||
|
assert_eq!(sender_post, expected_sender_post);
|
||||||
assert!(state.private_state.0.contains(&expected_new_commitment));
|
assert!(state.private_state.0.contains(&expected_new_commitment));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -1039,6 +1061,12 @@ pub mod tests {
|
|||||||
|
|
||||||
let balance_to_move = 37;
|
let balance_to_move = 37;
|
||||||
|
|
||||||
|
let expected_recipient_post = {
|
||||||
|
let mut this = state.get_account_by_address(&recipient_keys.address());
|
||||||
|
this.balance += balance_to_move;
|
||||||
|
this
|
||||||
|
};
|
||||||
|
|
||||||
let tx = deshielded_balance_transfer_for_tests(
|
let tx = deshielded_balance_transfer_for_tests(
|
||||||
&sender_keys,
|
&sender_keys,
|
||||||
&sender_private_account,
|
&sender_private_account,
|
||||||
@ -1069,6 +1097,8 @@ pub mod tests {
|
|||||||
.transition_from_privacy_preserving_transaction(&tx)
|
.transition_from_privacy_preserving_transaction(&tx)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let recipient_post = state.get_account_by_address(&recipient_keys.address());
|
||||||
|
assert_eq!(recipient_post, expected_recipient_post);
|
||||||
assert!(state.private_state.0.contains(&sender_pre_commitment));
|
assert!(state.private_state.0.contains(&sender_pre_commitment));
|
||||||
assert!(state.private_state.0.contains(&expected_new_commitment));
|
assert!(state.private_state.0.contains(&expected_new_commitment));
|
||||||
assert!(state.private_state.1.contains(&expected_new_nullifier));
|
assert!(state.private_state.1.contains(&expected_new_nullifier));
|
||||||
@ -1079,4 +1109,823 @@ pub mod tests {
|
|||||||
recipient_initial_balance + balance_to_move
|
recipient_initial_balance + balance_to_move
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_burner_program_should_fail_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::burner();
|
||||||
|
let public_account = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_minter_program_should_fail_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::minter();
|
||||||
|
let public_account = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::nonce_changer_program();
|
||||||
|
let public_account = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account],
|
||||||
|
&Program::serialize_instruction(()).unwrap(),
|
||||||
|
&[0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::data_changer();
|
||||||
|
let public_account = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account],
|
||||||
|
&Program::serialize_instruction(()).unwrap(),
|
||||||
|
&[0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::extra_output_program();
|
||||||
|
let public_account = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account],
|
||||||
|
&Program::serialize_instruction(()).unwrap(),
|
||||||
|
&[0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::missing_output_program();
|
||||||
|
let public_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let public_account_2 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account_1, public_account_2],
|
||||||
|
&Program::serialize_instruction(()).unwrap(),
|
||||||
|
&[0, 0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::program_owner_changer();
|
||||||
|
let public_account = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account],
|
||||||
|
&Program::serialize_instruction(()).unwrap(),
|
||||||
|
&[0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let public_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let public_account_2 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account_1, public_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[0, 0],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let public_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let public_account_2 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 0,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setting only one visibility mask for a circuit execution with two pre_state accounts.
|
||||||
|
let visibility_mask = [0];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account_1, public_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&visibility_mask,
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_fails_if_insufficient_nonces_are_provided() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
// Setting only one nonce for an execution with two private accounts.
|
||||||
|
let private_account_nonces = [0xdeadbeef1];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&private_account_nonces,
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_fails_if_insufficient_keys_are_provided() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
// Setting only one key for an execution with two private accounts.
|
||||||
|
let private_account_keys = [(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
)];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&private_account_keys,
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_fails_if_insufficient_auth_keys_are_provided() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
// Setting no auth key for an execution with one non default private accounts.
|
||||||
|
let private_account_auth = [];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&private_account_auth,
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_fails_if_invalid_auth_keys_are_provided() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
let private_account_keys = [
|
||||||
|
// First private account is the sender
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
// Second private account is the recipient
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let private_account_auth = [
|
||||||
|
// Setting the recipient key to authorize the sender.
|
||||||
|
// This should be set to the sender private account in
|
||||||
|
// a normal circumstance. The recipient can't authorize this.
|
||||||
|
(recipient_keys.nsk, (0, vec![])),
|
||||||
|
];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&private_account_keys,
|
||||||
|
&private_account_auth,
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
// Non default balance
|
||||||
|
balance: 1,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided()
|
||||||
|
{
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
// Non default program_owner
|
||||||
|
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
// Non default data
|
||||||
|
data: b"hola mundo".to_vec(),
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
// Non default nonce
|
||||||
|
nonce: 0xdeadbeef,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized()
|
||||||
|
{
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 = AccountWithMetadata::new(
|
||||||
|
Account::default(),
|
||||||
|
// This should be set to false in normal circumstances
|
||||||
|
true,
|
||||||
|
AccountId::new([1; 32]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_with_invalid_visibility_mask_value() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let public_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let public_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
let visibility_mask = [0, 3];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[public_account_1, public_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&visibility_mask,
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_with_too_many_nonces() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
// Setting three new private account nonces for a circuit execution with only two private
|
||||||
|
// accounts.
|
||||||
|
let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&private_account_nonces,
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_with_too_many_private_account_keys() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
// Setting three private account keys for a circuit execution with only two private
|
||||||
|
// accounts.
|
||||||
|
let private_account_keys = [
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[57; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&[1, 2],
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&private_account_keys,
|
||||||
|
&[(sender_keys.nsk, (0, vec![]))],
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circuit_should_fail_with_too_many_private_account_auth_keys() {
|
||||||
|
let program = Program::simple_balance_transfer();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let recipient_keys = test_private_account_keys_2();
|
||||||
|
let private_account_1 = AccountWithMetadata::new(
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
AccountId::new([0; 32]),
|
||||||
|
);
|
||||||
|
let private_account_2 =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
|
||||||
|
|
||||||
|
// Setting two private account keys for a circuit execution with only one non default
|
||||||
|
// private account (visibility mask equal to 1 means that auth keys are expected).
|
||||||
|
let visibility_mask = [1, 2];
|
||||||
|
let private_account_auth = [
|
||||||
|
(sender_keys.nsk, (0, vec![])),
|
||||||
|
(recipient_keys.nsk, (1, vec![])),
|
||||||
|
];
|
||||||
|
let result = execute_and_prove(
|
||||||
|
&[private_account_1, private_account_2],
|
||||||
|
&Program::serialize_instruction(10u128).unwrap(),
|
||||||
|
&visibility_mask,
|
||||||
|
&[0xdeadbeef1, 0xdeadbeef2],
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
sender_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
recipient_keys.npk(),
|
||||||
|
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&private_account_auth,
|
||||||
|
&program,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,12 @@ type Instruction = ();
|
|||||||
fn main() {
|
fn main() {
|
||||||
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
|
||||||
|
|
||||||
let [pre1, _] = match pre_states.try_into() {
|
let [pre1, pre2] = match pre_states.try_into() {
|
||||||
Ok(array) => array,
|
Ok(array) => array,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let account_pre1 = pre1.account.clone();
|
let account_pre1 = pre1.account.clone();
|
||||||
|
|
||||||
write_nssa_outputs(vec![pre1], vec![account_pre1]);
|
write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ anyhow.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
||||||
[dependencies.storage]
|
[dependencies.storage]
|
||||||
@ -22,3 +23,7 @@ path = "../common"
|
|||||||
|
|
||||||
[dependencies.nssa]
|
[dependencies.nssa]
|
||||||
path = "../nssa"
|
path = "../nssa"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
testnet = []
|
||||||
|
|||||||
@ -27,4 +27,6 @@ pub struct SequencerConfig {
|
|||||||
pub port: u16,
|
pub port: u16,
|
||||||
///List of initial accounts data
|
///List of initial accounts data
|
||||||
pub initial_accounts: Vec<AccountInitialData>,
|
pub initial_accounts: Vec<AccountInitialData>,
|
||||||
|
///Sequencer own signing key
|
||||||
|
pub signing_key: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use common::{block::HashableBlockData, merkle_tree_public::TreeHashType};
|
use common::{
|
||||||
|
TreeHashType,
|
||||||
|
block::HashableBlockData,
|
||||||
|
transaction::{EncodedTransaction, NSSATransaction},
|
||||||
|
};
|
||||||
use config::SequencerConfig;
|
use config::SequencerConfig;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use mempool::MemPool;
|
use mempool::MemPool;
|
||||||
@ -13,7 +17,7 @@ pub mod sequencer_store;
|
|||||||
|
|
||||||
pub struct SequencerCore {
|
pub struct SequencerCore {
|
||||||
pub store: SequecerChainStore,
|
pub store: SequecerChainStore,
|
||||||
pub mempool: MemPool<nssa::PublicTransaction>,
|
pub mempool: MemPool<EncodedTransaction>,
|
||||||
pub sequencer_config: SequencerConfig,
|
pub sequencer_config: SequencerConfig,
|
||||||
pub chain_height: u64,
|
pub chain_height: u64,
|
||||||
}
|
}
|
||||||
@ -51,6 +55,7 @@ impl SequencerCore {
|
|||||||
config.genesis_id,
|
config.genesis_id,
|
||||||
config.is_genesis_random,
|
config.is_genesis_random,
|
||||||
&config.initial_accounts,
|
&config.initial_accounts,
|
||||||
|
nssa::PrivateKey::try_new(config.signing_key).unwrap(),
|
||||||
),
|
),
|
||||||
mempool: MemPool::default(),
|
mempool: MemPool::default(),
|
||||||
chain_height: config.genesis_id,
|
chain_height: config.genesis_id,
|
||||||
@ -60,20 +65,37 @@ impl SequencerCore {
|
|||||||
|
|
||||||
pub fn transaction_pre_check(
|
pub fn transaction_pre_check(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: nssa::PublicTransaction,
|
tx: NSSATransaction,
|
||||||
) -> Result<nssa::PublicTransaction, TransactionMalformationErrorKind> {
|
) -> Result<NSSATransaction, TransactionMalformationErrorKind> {
|
||||||
// Stateless checks here
|
// Stateless checks here
|
||||||
if tx.witness_set().is_valid_for(tx.message()) {
|
match tx {
|
||||||
Ok(tx)
|
NSSATransaction::Public(tx) => {
|
||||||
} else {
|
if tx.witness_set().is_valid_for(tx.message()) {
|
||||||
Err(TransactionMalformationErrorKind::InvalidSignature)
|
Ok(NSSATransaction::Public(tx))
|
||||||
|
} else {
|
||||||
|
Err(TransactionMalformationErrorKind::InvalidSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSSATransaction::PrivacyPreserving(tx) => {
|
||||||
|
if tx.witness_set().signatures_are_valid_for(tx.message()) {
|
||||||
|
Ok(NSSATransaction::PrivacyPreserving(tx))
|
||||||
|
} else {
|
||||||
|
Err(TransactionMalformationErrorKind::InvalidSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_tx_into_mempool_pre_check(
|
pub fn push_tx_into_mempool_pre_check(
|
||||||
&mut self,
|
&mut self,
|
||||||
transaction: nssa::PublicTransaction,
|
transaction: EncodedTransaction,
|
||||||
) -> Result<(), TransactionMalformationErrorKind> {
|
) -> Result<(), TransactionMalformationErrorKind> {
|
||||||
|
let transaction = NSSATransaction::try_from(&transaction).map_err(|_| {
|
||||||
|
TransactionMalformationErrorKind::FailedToDecode {
|
||||||
|
tx: transaction.hash(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
let mempool_size = self.mempool.len();
|
let mempool_size = self.mempool.len();
|
||||||
if mempool_size >= self.sequencer_config.max_num_tx_in_block {
|
if mempool_size >= self.sequencer_config.max_num_tx_in_block {
|
||||||
return Err(TransactionMalformationErrorKind::MempoolFullForRound);
|
return Err(TransactionMalformationErrorKind::MempoolFullForRound);
|
||||||
@ -83,19 +105,29 @@ impl SequencerCore {
|
|||||||
.transaction_pre_check(transaction)
|
.transaction_pre_check(transaction)
|
||||||
.inspect_err(|err| warn!("Error at pre_check {err:#?}"))?;
|
.inspect_err(|err| warn!("Error at pre_check {err:#?}"))?;
|
||||||
|
|
||||||
self.mempool.push_item(authenticated_tx);
|
self.mempool.push_item(authenticated_tx.into());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_check_transaction_on_state(
|
fn execute_check_transaction_on_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: nssa::PublicTransaction,
|
tx: NSSATransaction,
|
||||||
) -> Result<nssa::PublicTransaction, nssa::error::NssaError> {
|
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
||||||
self.store
|
match &tx {
|
||||||
.state
|
NSSATransaction::Public(tx) => {
|
||||||
.transition_from_public_transaction(&tx)
|
self.store
|
||||||
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
.state
|
||||||
|
.transition_from_public_transaction(tx)
|
||||||
|
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
||||||
|
}
|
||||||
|
NSSATransaction::PrivacyPreserving(tx) => {
|
||||||
|
self.store
|
||||||
|
.state
|
||||||
|
.transition_from_privacy_preserving_transaction(tx)
|
||||||
|
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(tx)
|
Ok(tx)
|
||||||
}
|
}
|
||||||
@ -104,29 +136,41 @@ impl SequencerCore {
|
|||||||
pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result<u64> {
|
pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result<u64> {
|
||||||
let new_block_height = self.chain_height + 1;
|
let new_block_height = self.chain_height + 1;
|
||||||
|
|
||||||
let transactions = self
|
let mut num_valid_transactions_in_block = 0;
|
||||||
.mempool
|
let mut valid_transactions = vec![];
|
||||||
.pop_size(self.sequencer_config.max_num_tx_in_block);
|
|
||||||
|
|
||||||
let valid_transactions: Vec<_> = transactions
|
while let Some(tx) = self.mempool.pop_last() {
|
||||||
.into_iter()
|
let nssa_transaction = NSSATransaction::try_from(&tx)
|
||||||
.filter_map(|tx| self.execute_check_transaction_on_state(tx).ok())
|
.map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx.hash() })?;
|
||||||
.collect();
|
|
||||||
|
if let Ok(valid_tx) = self.execute_check_transaction_on_state(nssa_transaction) {
|
||||||
|
valid_transactions.push(valid_tx.into());
|
||||||
|
|
||||||
|
num_valid_transactions_in_block += 1;
|
||||||
|
|
||||||
|
if num_valid_transactions_in_block >= self.sequencer_config.max_num_tx_in_block {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let prev_block_hash = self
|
let prev_block_hash = self
|
||||||
.store
|
.store
|
||||||
.block_store
|
.block_store
|
||||||
.get_block_at_id(self.chain_height)?
|
.get_block_at_id(self.chain_height)?
|
||||||
|
.header
|
||||||
.hash;
|
.hash;
|
||||||
|
|
||||||
|
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
|
||||||
|
|
||||||
let hashable_data = HashableBlockData {
|
let hashable_data = HashableBlockData {
|
||||||
block_id: new_block_height,
|
block_id: new_block_height,
|
||||||
prev_block_id: self.chain_height,
|
|
||||||
transactions: valid_transactions,
|
transactions: valid_transactions,
|
||||||
prev_block_hash,
|
prev_block_hash,
|
||||||
|
timestamp: curr_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
let block = hashable_data.into();
|
let block = hashable_data.into_block(&self.store.block_store.signing_key);
|
||||||
|
|
||||||
self.store.block_store.put_block_at_id(block)?;
|
self.store.block_store.put_block_at_id(block)?;
|
||||||
|
|
||||||
@ -138,10 +182,18 @@ impl SequencerCore {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use common::test_utils::sequencer_sign_key_for_testing;
|
||||||
|
|
||||||
use crate::config::AccountInitialData;
|
use crate::config::AccountInitialData;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn parse_unwrap_tx_body_into_nssa_tx(tx_body: EncodedTransaction) -> NSSATransaction {
|
||||||
|
NSSATransaction::try_from(&tx_body)
|
||||||
|
.map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx_body.hash() })
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_sequencer_config_variable_initial_accounts(
|
fn setup_sequencer_config_variable_initial_accounts(
|
||||||
initial_accounts: Vec<AccountInitialData>,
|
initial_accounts: Vec<AccountInitialData>,
|
||||||
) -> SequencerConfig {
|
) -> SequencerConfig {
|
||||||
@ -157,6 +209,7 @@ mod tests {
|
|||||||
block_create_timeout_millis: 1000,
|
block_create_timeout_millis: 1000,
|
||||||
port: 8080,
|
port: 8080,
|
||||||
initial_accounts,
|
initial_accounts,
|
||||||
|
signing_key: *sequencer_sign_key_for_testing().value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,7 +351,7 @@ mod tests {
|
|||||||
common_setup(&mut sequencer);
|
common_setup(&mut sequencer);
|
||||||
|
|
||||||
let tx = common::test_utils::produce_dummy_empty_transaction();
|
let tx = common::test_utils::produce_dummy_empty_transaction();
|
||||||
let result = sequencer.transaction_pre_check(tx);
|
let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
@ -324,7 +377,7 @@ mod tests {
|
|||||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||||
acc1, 0, acc2, 10, sign_key1,
|
acc1, 0, acc2, 10, sign_key1,
|
||||||
);
|
);
|
||||||
let result = sequencer.transaction_pre_check(tx);
|
let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
@ -352,7 +405,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Signature is valid, stateless check pass
|
// Signature is valid, stateless check pass
|
||||||
let tx = sequencer.transaction_pre_check(tx).unwrap();
|
let tx = sequencer
|
||||||
|
.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Signature is not from sender. Execution fails
|
// Signature is not from sender. Execution fails
|
||||||
let result = sequencer.execute_check_transaction_on_state(tx);
|
let result = sequencer.execute_check_transaction_on_state(tx);
|
||||||
@ -385,7 +440,7 @@ mod tests {
|
|||||||
acc1, 0, acc2, 10000000, sign_key1,
|
acc1, 0, acc2, 10000000, sign_key1,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = sequencer.transaction_pre_check(tx);
|
let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
|
||||||
|
|
||||||
//Passed pre-check
|
//Passed pre-check
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -421,7 +476,9 @@ mod tests {
|
|||||||
acc1, 0, acc2, 100, sign_key1,
|
acc1, 0, acc2, 100, sign_key1,
|
||||||
);
|
);
|
||||||
|
|
||||||
sequencer.execute_check_transaction_on_state(tx).unwrap();
|
sequencer
|
||||||
|
.execute_check_transaction_on_state(parse_unwrap_tx_body_into_nssa_tx(tx))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let bal_from = sequencer
|
let bal_from = sequencer
|
||||||
.store
|
.store
|
||||||
@ -528,7 +585,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Only one should be included in the block
|
// Only one should be included in the block
|
||||||
assert_eq!(block.transactions, vec![tx.clone()]);
|
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -563,7 +620,7 @@ mod tests {
|
|||||||
.block_store
|
.block_store
|
||||||
.get_block_at_id(current_height)
|
.get_block_at_id(current_height)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(block.transactions, vec![tx.clone()]);
|
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||||
|
|
||||||
// Add same transaction should fail
|
// Add same transaction should fail
|
||||||
sequencer.mempool.push_item(tx);
|
sequencer.mempool.push_item(tx);
|
||||||
@ -575,6 +632,6 @@ mod tests {
|
|||||||
.block_store
|
.block_store
|
||||||
.get_block_at_id(current_height)
|
.get_block_at_id(current_height)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(block.transactions.is_empty());
|
assert!(block.body.transactions.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::{collections::HashMap, path::Path};
|
use std::{collections::HashMap, path::Path};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use common::{block::Block, merkle_tree_public::TreeHashType};
|
use common::{TreeHashType, block::Block, transaction::EncodedTransaction};
|
||||||
use storage::RocksDBIO;
|
use storage::RocksDBIO;
|
||||||
|
|
||||||
pub struct SequecerBlockStore {
|
pub struct SequecerBlockStore {
|
||||||
@ -9,6 +9,7 @@ pub struct SequecerBlockStore {
|
|||||||
// TODO: Consider adding the hashmap to the database for faster recovery.
|
// TODO: Consider adding the hashmap to the database for faster recovery.
|
||||||
tx_hash_to_block_map: HashMap<TreeHashType, u64>,
|
tx_hash_to_block_map: HashMap<TreeHashType, u64>,
|
||||||
pub genesis_id: u64,
|
pub genesis_id: u64,
|
||||||
|
pub signing_key: nssa::PrivateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SequecerBlockStore {
|
impl SequecerBlockStore {
|
||||||
@ -16,7 +17,11 @@ impl SequecerBlockStore {
|
|||||||
/// Creates files if necessary.
|
/// Creates files if necessary.
|
||||||
///
|
///
|
||||||
/// ATTENTION: Will overwrite genesis block.
|
/// ATTENTION: Will overwrite genesis block.
|
||||||
pub fn open_db_with_genesis(location: &Path, genesis_block: Option<Block>) -> Result<Self> {
|
pub fn open_db_with_genesis(
|
||||||
|
location: &Path,
|
||||||
|
genesis_block: Option<Block>,
|
||||||
|
signing_key: nssa::PrivateKey,
|
||||||
|
) -> Result<Self> {
|
||||||
let tx_hash_to_block_map = if let Some(block) = &genesis_block {
|
let tx_hash_to_block_map = if let Some(block) = &genesis_block {
|
||||||
block_to_transactions_map(block)
|
block_to_transactions_map(block)
|
||||||
} else {
|
} else {
|
||||||
@ -31,16 +36,17 @@ impl SequecerBlockStore {
|
|||||||
dbio,
|
dbio,
|
||||||
genesis_id,
|
genesis_id,
|
||||||
tx_hash_to_block_map,
|
tx_hash_to_block_map,
|
||||||
|
signing_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
///Reopening existing database
|
///Reopening existing database
|
||||||
pub fn open_db_restart(location: &Path) -> Result<Self> {
|
pub fn open_db_restart(location: &Path, signing_key: nssa::PrivateKey) -> Result<Self> {
|
||||||
SequecerBlockStore::open_db_with_genesis(location, None)
|
SequecerBlockStore::open_db_with_genesis(location, None, signing_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_block_at_id(&self, id: u64) -> Result<Block> {
|
pub fn get_block_at_id(&self, id: u64) -> Result<Block> {
|
||||||
Ok(self.dbio.get_block(id)?)
|
Ok(self.dbio.get_block(id)?.into_block(&self.signing_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_block_at_id(&mut self, block: Block) -> Result<()> {
|
pub fn put_block_at_id(&mut self, block: Block) -> Result<()> {
|
||||||
@ -51,11 +57,11 @@ impl SequecerBlockStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
|
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
|
||||||
pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option<nssa::PublicTransaction> {
|
pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option<EncodedTransaction> {
|
||||||
let block_id = self.tx_hash_to_block_map.get(&hash);
|
let block_id = self.tx_hash_to_block_map.get(&hash);
|
||||||
let block = block_id.map(|&id| self.get_block_at_id(id));
|
let block = block_id.map(|&id| self.get_block_at_id(id));
|
||||||
if let Some(Ok(block)) = block {
|
if let Some(Ok(block)) = block {
|
||||||
for transaction in block.transactions.into_iter() {
|
for transaction in block.body.transactions.into_iter() {
|
||||||
if transaction.hash() == hash {
|
if transaction.hash() == hash {
|
||||||
return Some(transaction);
|
return Some(transaction);
|
||||||
}
|
}
|
||||||
@ -67,9 +73,10 @@ impl SequecerBlockStore {
|
|||||||
|
|
||||||
fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
|
fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
|
||||||
block
|
block
|
||||||
|
.body
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|transaction| (transaction.hash(), block.block_id))
|
.map(|transaction| (transaction.hash(), block.header.block_id))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,22 +84,28 @@ fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_transaction_by_hash() {
|
fn test_get_transaction_by_hash() {
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let path = temp_dir.path();
|
let path = temp_dir.path();
|
||||||
let genesis_block = Block {
|
|
||||||
|
let signing_key = sequencer_sign_key_for_testing();
|
||||||
|
|
||||||
|
let genesis_block_hashable_data = HashableBlockData {
|
||||||
block_id: 0,
|
block_id: 0,
|
||||||
prev_block_id: 0,
|
|
||||||
prev_block_hash: [0; 32],
|
prev_block_hash: [0; 32],
|
||||||
hash: [1; 32],
|
timestamp: 0,
|
||||||
transactions: vec![],
|
transactions: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let genesis_block = genesis_block_hashable_data.into_block(&signing_key);
|
||||||
// Start an empty node store
|
// Start an empty node store
|
||||||
let mut node_store =
|
let mut node_store =
|
||||||
SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block)).unwrap();
|
SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block), signing_key)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let tx = common::test_utils::produce_dummy_empty_transaction();
|
let tx = common::test_utils::produce_dummy_empty_transaction();
|
||||||
let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]);
|
let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]);
|
||||||
|
|||||||
@ -20,14 +20,23 @@ impl SequecerChainStore {
|
|||||||
genesis_id: u64,
|
genesis_id: u64,
|
||||||
is_genesis_random: bool,
|
is_genesis_random: bool,
|
||||||
initial_accounts: &[AccountInitialData],
|
initial_accounts: &[AccountInitialData],
|
||||||
|
signing_key: nssa::PrivateKey,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let init_accs: Vec<(Address, u128)> = initial_accounts
|
let init_accs: Vec<(Address, u128)> = initial_accounts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance))
|
.map(|acc_data| (acc_data.addr.parse().unwrap(), acc_data.balance))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "testnet"))]
|
||||||
let state = nssa::V01State::new_with_genesis_accounts(&init_accs);
|
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 data = [0; 32];
|
||||||
let mut prev_block_hash = [0; 32];
|
let mut prev_block_hash = [0; 32];
|
||||||
|
|
||||||
@ -36,20 +45,23 @@ impl SequecerChainStore {
|
|||||||
OsRng.fill_bytes(&mut prev_block_hash);
|
OsRng.fill_bytes(&mut prev_block_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
|
||||||
|
|
||||||
let hashable_data = HashableBlockData {
|
let hashable_data = HashableBlockData {
|
||||||
block_id: genesis_id,
|
block_id: genesis_id,
|
||||||
prev_block_id: genesis_id.saturating_sub(1),
|
|
||||||
transactions: vec![],
|
transactions: vec![],
|
||||||
prev_block_hash,
|
prev_block_hash,
|
||||||
|
timestamp: curr_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
let genesis_block = hashable_data.into();
|
let genesis_block = hashable_data.into_block(&signing_key);
|
||||||
|
|
||||||
//Sequencer should panic if unable to open db,
|
//Sequencer should panic if unable to open db,
|
||||||
//as fixing this issue may require actions non-native to program scope
|
//as fixing this issue may require actions non-native to program scope
|
||||||
let block_store = SequecerBlockStore::open_db_with_genesis(
|
let block_store = SequecerBlockStore::open_db_with_genesis(
|
||||||
&home_dir.join("rocksdb"),
|
&home_dir.join("rocksdb"),
|
||||||
Some(genesis_block),
|
Some(genesis_block),
|
||||||
|
signing_key,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ use sequencer_core::config::AccountInitialData;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
|
TreeHashType,
|
||||||
block::HashableBlockData,
|
block::HashableBlockData,
|
||||||
merkle_tree_public::TreeHashType,
|
|
||||||
rpc_primitives::{
|
rpc_primitives::{
|
||||||
errors::RpcError,
|
errors::RpcError,
|
||||||
message::{Message, Request},
|
message::{Message, Request},
|
||||||
@ -18,6 +18,7 @@ use common::{
|
|||||||
GetTransactionByHashResponse,
|
GetTransactionByHashResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
transaction::EncodedTransaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
use common::rpc_primitives::requests::{
|
use common::rpc_primitives::requests::{
|
||||||
@ -74,8 +75,7 @@ impl JsonHandler {
|
|||||||
|
|
||||||
async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> {
|
async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> {
|
||||||
let send_tx_req = SendTxRequest::parse(Some(request.params))?;
|
let send_tx_req = SendTxRequest::parse(Some(request.params))?;
|
||||||
let tx = nssa::PublicTransaction::from_bytes(&send_tx_req.transaction)
|
let tx = EncodedTransaction::from_bytes(send_tx_req.transaction);
|
||||||
.map_err(|e| RpcError::serialization_error(&e.to_string()))?;
|
|
||||||
let tx_hash = hex::encode(tx.hash());
|
let tx_hash = hex::encode(tx.hash());
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -273,7 +273,10 @@ mod tests {
|
|||||||
|
|
||||||
use crate::{JsonHandler, rpc_handler};
|
use crate::{JsonHandler, rpc_handler};
|
||||||
use base64::{Engine, engine::general_purpose};
|
use base64::{Engine, engine::general_purpose};
|
||||||
use common::rpc_primitives::RpcPollingConfig;
|
use common::{
|
||||||
|
rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing,
|
||||||
|
transaction::EncodedTransaction,
|
||||||
|
};
|
||||||
|
|
||||||
use sequencer_core::{
|
use sequencer_core::{
|
||||||
SequencerCore,
|
SequencerCore,
|
||||||
@ -317,14 +320,11 @@ mod tests {
|
|||||||
block_create_timeout_millis: 1000,
|
block_create_timeout_millis: 1000,
|
||||||
port: 8080,
|
port: 8080,
|
||||||
initial_accounts,
|
initial_accounts,
|
||||||
|
signing_key: *sequencer_sign_key_for_testing().value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn components_for_tests() -> (
|
fn components_for_tests() -> (JsonHandler, Vec<AccountInitialData>, EncodedTransaction) {
|
||||||
JsonHandler,
|
|
||||||
Vec<AccountInitialData>,
|
|
||||||
nssa::PublicTransaction,
|
|
||||||
) {
|
|
||||||
let config = sequencer_config_for_tests();
|
let config = sequencer_config_for_tests();
|
||||||
let mut sequencer_core = SequencerCore::start_from_config(config);
|
let mut sequencer_core = SequencerCore::start_from_config(config);
|
||||||
let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone();
|
let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone();
|
||||||
|
|||||||
@ -7,6 +7,13 @@
|
|||||||
"block_create_timeout_millis": 10000,
|
"block_create_timeout_millis": 10000,
|
||||||
"port": 3040,
|
"port": 3040,
|
||||||
"initial_accounts": [
|
"initial_accounts": [
|
||||||
|
{
|
||||||
|
"addr": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
|
||||||
|
"balance": 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
|
||||||
|
"balance": 20000
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,7 +77,9 @@ pub async fn main_runner() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//ToDo: Add restart on failures
|
//ToDo: Add restart on failures
|
||||||
let (_, _) = startup_sequencer(app_config).await?;
|
let (_, main_loop_handle) = startup_sequencer(app_config).await?;
|
||||||
|
|
||||||
|
main_loop_handle.await??;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,7 +92,7 @@ impl RocksDBIO {
|
|||||||
if is_start_set {
|
if is_start_set {
|
||||||
Ok(dbio)
|
Ok(dbio)
|
||||||
} else if let Some(block) = start_block {
|
} else if let Some(block) = start_block {
|
||||||
let block_id = block.block_id;
|
let block_id = block.header.block_id;
|
||||||
dbio.put_meta_first_block_in_db(block)?;
|
dbio.put_meta_first_block_in_db(block)?;
|
||||||
dbio.put_meta_is_first_block_set()?;
|
dbio.put_meta_is_first_block_set()?;
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ impl RocksDBIO {
|
|||||||
.put_cf(
|
.put_cf(
|
||||||
&cf_meta,
|
&cf_meta,
|
||||||
DB_META_FIRST_BLOCK_IN_DB_KEY.as_bytes(),
|
DB_META_FIRST_BLOCK_IN_DB_KEY.as_bytes(),
|
||||||
block.block_id.to_be_bytes(),
|
block.header.block_id.to_be_bytes(),
|
||||||
)
|
)
|
||||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
@ -233,22 +233,22 @@ impl RocksDBIO {
|
|||||||
if !first {
|
if !first {
|
||||||
let last_curr_block = self.get_meta_last_block_in_db()?;
|
let last_curr_block = self.get_meta_last_block_in_db()?;
|
||||||
|
|
||||||
if block.block_id > last_curr_block {
|
if block.header.block_id > last_curr_block {
|
||||||
self.put_meta_last_block_in_db(block.block_id)?;
|
self.put_meta_last_block_in_db(block.header.block_id)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db
|
self.db
|
||||||
.put_cf(
|
.put_cf(
|
||||||
&cf_block,
|
&cf_block,
|
||||||
block.block_id.to_be_bytes(),
|
block.header.block_id.to_be_bytes(),
|
||||||
HashableBlockData::from(block).to_bytes(),
|
HashableBlockData::from(block).to_bytes(),
|
||||||
)
|
)
|
||||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_block(&self, block_id: u64) -> DbResult<Block> {
|
pub fn get_block(&self, block_id: u64) -> DbResult<HashableBlockData> {
|
||||||
let cf_block = self.block_column();
|
let cf_block = self.block_column();
|
||||||
let res = self
|
let res = self
|
||||||
.db
|
.db
|
||||||
@ -256,7 +256,7 @@ impl RocksDBIO {
|
|||||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||||
|
|
||||||
if let Some(data) = res {
|
if let Some(data) = res {
|
||||||
Ok(HashableBlockData::from_bytes(&data).into())
|
Ok(HashableBlockData::from_bytes(&data))
|
||||||
} else {
|
} else {
|
||||||
Err(DbError::db_interaction_error(
|
Err(DbError::db_interaction_error(
|
||||||
"Block on this id not found".to_string(),
|
"Block on this id not found".to_string(),
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree;
|
|
||||||
use key_protocol::key_protocol_core::NSSAUserData;
|
use key_protocol::key_protocol_core::NSSAUserData;
|
||||||
|
|
||||||
use crate::config::{PersistentAccountData, WalletConfig};
|
use crate::config::{PersistentAccountData, WalletConfig};
|
||||||
|
|
||||||
pub struct WalletChainStore {
|
pub struct WalletChainStore {
|
||||||
pub user_data: NSSAUserData,
|
pub user_data: NSSAUserData,
|
||||||
pub utxo_commitments_store: UTXOCommitmentsMerkleTree,
|
|
||||||
pub wallet_config: WalletConfig,
|
pub wallet_config: WalletConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,18 +19,14 @@ impl WalletChainStore {
|
|||||||
.map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key))
|
.map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
user_data: NSSAUserData::new_with_accounts(accounts_keys)?,
|
user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?,
|
||||||
utxo_commitments_store,
|
|
||||||
wallet_config: config,
|
wallet_config: config,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) {
|
pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) {
|
||||||
self.user_data
|
self.user_data
|
||||||
.key_holder
|
|
||||||
.pub_account_signing_keys
|
.pub_account_signing_keys
|
||||||
.insert(acc_data.address, acc_data.pub_sign_key);
|
.insert(acc_data.address, acc_data.pub_sign_key);
|
||||||
}
|
}
|
||||||
@ -94,11 +88,6 @@ mod tests {
|
|||||||
|
|
||||||
let config = create_sample_wallet_config(path.to_path_buf());
|
let config = create_sample_wallet_config(path.to_path_buf());
|
||||||
|
|
||||||
let store = WalletChainStore::new(config.clone()).unwrap();
|
let _ = WalletChainStore::new(config.clone()).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
store.utxo_commitments_store.get_root().unwrap_or([0; 32]),
|
|
||||||
[0; 32]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ pub fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
|
|||||||
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> {
|
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> {
|
||||||
let mut vec_for_storage = vec![];
|
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 {
|
vec_for_storage.push(PersistentAccountData {
|
||||||
address: *addr,
|
address: *addr,
|
||||||
pub_sign_key: key.clone(),
|
pub_sign_key: key.clone(),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
|||||||
use common::{
|
use common::{
|
||||||
ExecutionFailureKind,
|
ExecutionFailureKind,
|
||||||
sequencer_client::{SequencerClient, json::SendTxResponse},
|
sequencer_client::{SequencerClient, json::SendTxResponse},
|
||||||
|
transaction::{EncodedTransaction, NSSATransaction},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@ -72,7 +73,9 @@ impl WalletCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_new_account(&mut self) -> Address {
|
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> {
|
pub fn search_for_initial_account(&self, acc_addr: Address) -> Option<Account> {
|
||||||
@ -84,6 +87,24 @@ impl WalletCore {
|
|||||||
None
|
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(
|
pub async fn send_public_native_token_transfer(
|
||||||
&self,
|
&self,
|
||||||
from: Address,
|
from: Address,
|
||||||
@ -109,7 +130,7 @@ impl WalletCore {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.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 {
|
let Some(signing_key) = signing_key else {
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||||
@ -120,7 +141,7 @@ impl WalletCore {
|
|||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
Ok(self.sequencer_client.send_tx(tx).await?)
|
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||||
} else {
|
} else {
|
||||||
Err(ExecutionFailureKind::InsufficientFundsError)
|
Err(ExecutionFailureKind::InsufficientFundsError)
|
||||||
}
|
}
|
||||||
@ -147,7 +168,7 @@ impl WalletCore {
|
|||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
Ok(self.sequencer_client.send_tx(tx).await?)
|
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_transfer_token_transaction(
|
pub async fn send_transfer_token_transaction(
|
||||||
@ -172,7 +193,7 @@ impl WalletCore {
|
|||||||
let Some(signing_key) = self
|
let Some(signing_key) = self
|
||||||
.storage
|
.storage
|
||||||
.user_data
|
.user_data
|
||||||
.get_account_signing_key(&sender_address)
|
.get_pub_account_signing_key(&sender_address)
|
||||||
else {
|
else {
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||||
};
|
};
|
||||||
@ -181,7 +202,7 @@ impl WalletCore {
|
|||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
Ok(self.sequencer_client.send_tx(tx).await?)
|
Ok(self.sequencer_client.send_tx_public(tx).await?)
|
||||||
}
|
}
|
||||||
///Get account balance
|
///Get account balance
|
||||||
pub async fn get_account_balance(&self, acc: Address) -> Result<u128> {
|
pub async fn get_account_balance(&self, acc: Address) -> Result<u128> {
|
||||||
@ -208,15 +229,12 @@ impl WalletCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///Poll transactions
|
///Poll transactions
|
||||||
pub async fn poll_public_native_token_transfer(
|
pub async fn poll_public_native_token_transfer(&self, hash: String) -> Result<NSSATransaction> {
|
||||||
&self,
|
|
||||||
hash: String,
|
|
||||||
) -> Result<nssa::PublicTransaction> {
|
|
||||||
let transaction_encoded = self.poller.poll_tx(hash).await?;
|
let transaction_encoded = self.poller.poll_tx(hash).await?;
|
||||||
let tx_base64_decode = BASE64.decode(transaction_encoded)?;
|
let tx_base64_decode = BASE64.decode(transaction_encoded)?;
|
||||||
let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?;
|
let pub_tx = EncodedTransaction::from_bytes(tx_base64_decode);
|
||||||
|
|
||||||
Ok(pub_tx)
|
Ok(NSSATransaction::try_from(&pub_tx)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +296,19 @@ pub enum Command {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
balance_to_move: u128,
|
balance_to_move: u128,
|
||||||
},
|
},
|
||||||
|
// 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
|
///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
|
||||||
@ -376,6 +407,20 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
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(())
|
Ok(())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user