mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 13:23:10 +00:00
public transactions wip
This commit is contained in:
parent
a06af6da0a
commit
aba8f3549f
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -2987,6 +2987,7 @@ dependencies = [
|
||||
"hex",
|
||||
"k256",
|
||||
"log",
|
||||
"nssa",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.11.27",
|
||||
"risc0-zkvm 2.3.1 (git+https://github.com/risc0/risc0.git?branch=release-2.3)",
|
||||
@ -3069,10 +3070,19 @@ dependencies = [
|
||||
name = "nssa"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nssa-core",
|
||||
"program-methods",
|
||||
"risc0-zkvm 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nssa-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"risc0-zkvm 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.3.3"
|
||||
|
||||
@ -5,4 +5,5 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = "2.2"
|
||||
nssa-core = {path="core"}
|
||||
program-methods = { path = "program_methods" }
|
||||
|
||||
8
nssa/core/Cargo.toml
Normal file
8
nssa/core/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "nssa-core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = "2.2"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
3
nssa/core/src/account/commitment.rs
Normal file
3
nssa/core/src/account/commitment.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(crate) struct Commitment {
|
||||
value: [u8; 32],
|
||||
}
|
||||
37
nssa/core/src/account/mod.rs
Normal file
37
nssa/core/src/account/mod.rs
Normal file
@ -0,0 +1,37 @@
|
||||
mod commitment;
|
||||
mod nullifier;
|
||||
|
||||
pub(crate) use commitment::Commitment;
|
||||
pub(crate) use nullifier::Nullifier;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::program::ProgramId;
|
||||
|
||||
pub type Nonce = u128;
|
||||
type Data = Vec<u8>;
|
||||
|
||||
/// Account to be used both in public and private contexts
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Account {
|
||||
pub program_owner: ProgramId,
|
||||
pub balance: u128,
|
||||
pub data: Data,
|
||||
pub nonce: Nonce,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct AccountWithMetadata {
|
||||
pub account: Account,
|
||||
pub is_authorized: bool,
|
||||
}
|
||||
|
||||
impl Default for Account {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
program_owner: [0; 8],
|
||||
balance: 0,
|
||||
data: vec![],
|
||||
nonce: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
3
nssa/core/src/account/nullifier.rs
Normal file
3
nssa/core/src/account/nullifier.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(crate) struct Nullifier {
|
||||
value: [u8; 32]
|
||||
}
|
||||
2
nssa/core/src/lib.rs
Normal file
2
nssa/core/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod account;
|
||||
pub mod program;
|
||||
63
nssa/core/src/program.rs
Normal file
63
nssa/core/src/program.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::account::{Account, AccountWithMetadata};
|
||||
|
||||
pub type ProgramId = [u32; 8];
|
||||
|
||||
/// A trait to be implemented by inner programs.
|
||||
pub trait Program {
|
||||
const PROGRAM_ID: ProgramId;
|
||||
const PROGRAM_ELF: &[u8];
|
||||
type InstructionData: Serialize + for<'de> Deserialize<'de>;
|
||||
}
|
||||
|
||||
/// Validates well-behaved program execution
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `pre_states`: The list of input accounts, each annotated with authorization metadata.
|
||||
/// - `post_states`: The list of resulting accounts after executing the program logic.
|
||||
/// - `executing_program_id`: The identifier of the program that was executed.
|
||||
pub fn validate_constraints(
|
||||
pre_states: &[AccountWithMetadata],
|
||||
post_states: &[Account],
|
||||
executing_program_id: ProgramId,
|
||||
) -> Result<(), ()> {
|
||||
// 1. Lengths must match
|
||||
if pre_states.len() != post_states.len() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
for (pre, post) in pre_states.iter().zip(post_states) {
|
||||
// 2. Nonce must remain unchanged
|
||||
if pre.account.nonce != post.nonce {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// 3. Ownership change only allowed from default accounts
|
||||
if pre.account.program_owner != post.program_owner && pre.account != Account::default() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// 4. Decreasing balance only allowed if owned by executing program
|
||||
if post.balance < pre.account.balance && pre.account.program_owner != executing_program_id {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// 5. Data changes only allowed if owned by executing program
|
||||
if pre.account.data != post.data
|
||||
&& (executing_program_id != pre.account.program_owner
|
||||
|| executing_program_id != post.program_owner)
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Total balance is preserved
|
||||
let total_balance_pre_states: u128 = pre_states.iter().map(|pre| pre.account.balance).sum();
|
||||
let total_balance_post_states: u128 = post_states.iter().map(|post| post.balance).sum();
|
||||
if total_balance_pre_states != total_balance_post_states {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1898
nssa/program_methods/guest/Cargo.lock
generated
1898
nssa/program_methods/guest/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,4 +7,5 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = { version = "2.2.0", default-features = false, features = ['std'] }
|
||||
nssa-core = {path = "../../core"}
|
||||
|
||||
|
||||
31
nssa/program_methods/guest/src/bin/authenticated_transfer.rs
Normal file
31
nssa/program_methods/guest/src/bin/authenticated_transfer.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use nssa_core::account::{Account, AccountWithMetadata};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
/// A transfer of balance program.
|
||||
/// To be used both in public and private contexts.
|
||||
fn main() {
|
||||
// Read input accounts.
|
||||
// It is expected to receive only two accounts: [sender_account, receiver_account]
|
||||
let input_accounts: Vec<AccountWithMetadata> = env::read();
|
||||
let balance_to_move: u128 = env::read();
|
||||
|
||||
// Unpack sender and receiver
|
||||
assert_eq!(input_accounts.len(), 2);
|
||||
let [sender, receiver] = input_accounts
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("Bad input"));
|
||||
|
||||
// Check sender has authorized this operation
|
||||
assert!(sender.is_authorized);
|
||||
|
||||
// Check sender has enough balance
|
||||
assert!(sender.account.balance >= balance_to_move);
|
||||
|
||||
// Create accounts post states, with updated balances
|
||||
let mut sender_post = sender.account.clone();
|
||||
let mut receiver_post = receiver.account.clone();
|
||||
sender_post.balance -= balance_to_move;
|
||||
receiver_post.balance += balance_to_move;
|
||||
|
||||
env::commit(&vec![sender_post, receiver_post]);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
use risc0_zkvm::guest::env;
|
||||
fn main() {
|
||||
let a: u32 = env::read();
|
||||
env::commit(&a);
|
||||
}
|
||||
|
||||
17
nssa/src/address.rs
Normal file
17
nssa/src/address.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::signature::PublicKey;
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct Address {
|
||||
pub(crate) value: [u8; 32],
|
||||
}
|
||||
|
||||
impl Address {
|
||||
pub(crate) fn new(value: [u8; 32]) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
pub(crate) fn from_public_key(public_key: &PublicKey) -> Self {
|
||||
// TODO: implement
|
||||
Address::new([public_key.0; 32])
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,47 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::{Program, ProgramId},
|
||||
};
|
||||
use program_methods::{AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID};
|
||||
use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor};
|
||||
|
||||
mod address;
|
||||
mod public_transaction;
|
||||
mod signature;
|
||||
pub mod state;
|
||||
|
||||
struct AuthenticatedTransferProgram;
|
||||
impl Program for AuthenticatedTransferProgram {
|
||||
const PROGRAM_ID: ProgramId = AUTHENTICATED_TRANSFER_ID;
|
||||
const PROGRAM_ELF: &[u8] = AUTHENTICATED_TRANSFER_ELF;
|
||||
type InstructionData = u128;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
/// Writes inputs to `env_builder` in the order expected by the programs
|
||||
fn write_inputs<P: Program>(
|
||||
pre_states: &[AccountWithMetadata],
|
||||
instruction_data: P::InstructionData,
|
||||
env_builder: &mut ExecutorEnvBuilder,
|
||||
) -> Result<(), ()> {
|
||||
let pre_states = pre_states.to_vec();
|
||||
env_builder.write(&pre_states).map_err(|_| ())?;
|
||||
env_builder.write(&instruction_data).map_err(|_| ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_public<P: Program>(
|
||||
pre_states: &[AccountWithMetadata],
|
||||
instruction_data: P::InstructionData,
|
||||
) -> Result<Vec<Account>, ()> {
|
||||
// Write inputs to the program
|
||||
let mut env_builder = ExecutorEnv::builder();
|
||||
write_inputs::<P>(pre_states, instruction_data, &mut env_builder)?;
|
||||
let env = env_builder.build().unwrap();
|
||||
|
||||
// Execute the program (without proving)
|
||||
let executor = default_executor();
|
||||
let session_info = executor.execute(env, P::PROGRAM_ELF).map_err(|_| ())?;
|
||||
|
||||
// Get (inputs and) outputs
|
||||
session_info.journal.decode().map_err(|_| ())
|
||||
}
|
||||
|
||||
85
nssa/src/public_transaction.rs
Normal file
85
nssa/src/public_transaction.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use nssa_core::{
|
||||
account::{Account, Nonce},
|
||||
program::ProgramId,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
address::Address,
|
||||
signature::{PrivateKey, PublicKey, Signature},
|
||||
};
|
||||
|
||||
pub(crate) struct Message {
|
||||
pub(crate) program_id: ProgramId,
|
||||
pub(crate) addresses: Vec<Address>,
|
||||
pub(crate) nonces: Vec<Nonce>,
|
||||
// TODO: change to Vec<u8> for general programs
|
||||
pub(crate) instruction_data: u128,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub(crate) fn new(
|
||||
program_id: ProgramId,
|
||||
addresses: Vec<Address>,
|
||||
nonces: Vec<Nonce>,
|
||||
instruction_data: u128,
|
||||
) -> Self {
|
||||
Self {
|
||||
program_id,
|
||||
addresses,
|
||||
nonces,
|
||||
instruction_data,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
//TODO: implement
|
||||
vec![0, 0]
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WitnessSet {
|
||||
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||
}
|
||||
|
||||
impl WitnessSet {
|
||||
pub(crate) fn for_message(message: &Message, private_keys: &[PrivateKey]) -> Self {
|
||||
let message_bytes = message.to_bytes();
|
||||
let signatures_and_public_keys = private_keys
|
||||
.iter()
|
||||
.map(|key| (Signature::new(key, &message_bytes), PublicKey::new(key)))
|
||||
.collect();
|
||||
Self {
|
||||
signatures_and_public_keys,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PublicTransaction {
|
||||
message: Message,
|
||||
witness_set: WitnessSet,
|
||||
}
|
||||
|
||||
impl PublicTransaction {
|
||||
pub(crate) fn message(&self) -> &Message {
|
||||
&self.message
|
||||
}
|
||||
|
||||
pub(crate) fn witness_set(&self) -> &WitnessSet {
|
||||
&self.witness_set
|
||||
}
|
||||
|
||||
pub(crate) fn signer_addresses(&self) -> Vec<Address> {
|
||||
self.witness_set
|
||||
.signatures_and_public_keys
|
||||
.iter()
|
||||
.map(|(_, public_key)| Address::from_public_key(public_key))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn new(message: Message, witness_set: WitnessSet) -> Self {
|
||||
Self {
|
||||
message,
|
||||
witness_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
26
nssa/src/signature.rs
Normal file
26
nssa/src/signature.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use crate::{address::Address, public_transaction::Message};
|
||||
|
||||
pub(crate) struct Signature;
|
||||
|
||||
// TODO: Dummy impl. Replace by actual private key.
|
||||
pub(crate) struct PrivateKey(pub(crate) u8);
|
||||
// TODO: Dummy impl. Replace by actual public key.
|
||||
pub(crate) struct PublicKey(pub(crate) u8);
|
||||
|
||||
impl PublicKey {
|
||||
pub(crate) fn new(key: &PrivateKey) -> Self {
|
||||
// TODO: implement
|
||||
Self(key.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub(crate) fn new(key: &PrivateKey, message: &[u8]) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub(crate) fn is_valid_for(&self, message: &Message, public_key: &PublicKey) -> bool {
|
||||
// TODO: implement
|
||||
true
|
||||
}
|
||||
}
|
||||
169
nssa/src/state.rs
Normal file
169
nssa/src/state.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use crate::{
|
||||
AuthenticatedTransferProgram, address::Address, execute_public,
|
||||
public_transaction::PublicTransaction,
|
||||
};
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::{Program, validate_constraints},
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
struct V01State {
|
||||
public_state: HashMap<Address, Account>,
|
||||
}
|
||||
|
||||
impl V01State {
|
||||
fn transition_from_public_transaction(&mut self, tx: PublicTransaction) -> Result<(), ()> {
|
||||
let state_diff = self
|
||||
.execute_and_verify_public_transaction(&tx)
|
||||
.map_err(|_| ())?;
|
||||
|
||||
for (address, post) in state_diff.into_iter() {
|
||||
let current_account = self.get_account_by_address_mut(address);
|
||||
*current_account = post;
|
||||
}
|
||||
|
||||
for address in tx.signer_addresses() {
|
||||
let current_account = self.get_account_by_address_mut(address);
|
||||
current_account.nonce += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_account_by_address_mut(&mut self, address: Address) -> &mut Account {
|
||||
self.public_state
|
||||
.entry(address)
|
||||
.or_insert_with(Account::default)
|
||||
}
|
||||
|
||||
fn get_account_by_address(&self, address: &Address) -> Account {
|
||||
self.public_state
|
||||
.get(address)
|
||||
.cloned()
|
||||
.unwrap_or(Account::default())
|
||||
}
|
||||
|
||||
fn execute_and_verify_public_transaction(
|
||||
&mut self,
|
||||
tx: &PublicTransaction,
|
||||
) -> Result<HashMap<Address, Account>, ()> {
|
||||
let message = tx.message();
|
||||
let witness_set = tx.witness_set();
|
||||
|
||||
// All addresses must be different
|
||||
if message.addresses.iter().collect::<HashSet<_>>().len() != message.addresses.len() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if message.nonces.len() != witness_set.signatures_and_public_keys.len() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut authorized_addresses = Vec::new();
|
||||
for ((signature, public_key), nonce) in witness_set
|
||||
.signatures_and_public_keys
|
||||
.iter()
|
||||
.zip(message.nonces.iter())
|
||||
{
|
||||
// Check the signature is valid
|
||||
if !signature.is_valid_for(message, public_key) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// Check the nonce corresponds to the current nonce on the public state.
|
||||
let address = Address::from_public_key(public_key);
|
||||
let current_nonce = self.get_account_by_address(&address).nonce;
|
||||
if current_nonce != *nonce {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
authorized_addresses.push(address);
|
||||
}
|
||||
|
||||
// Build pre_states for execution
|
||||
let pre_states: Vec<_> = message
|
||||
.addresses
|
||||
.iter()
|
||||
.map(|address| AccountWithMetadata {
|
||||
account: self.get_account_by_address(address),
|
||||
is_authorized: authorized_addresses.contains(address),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Check the `program_id` corresponds to a built-in program
|
||||
// Only allowed program so far is the authenticated transfer program
|
||||
if message.program_id != AuthenticatedTransferProgram::PROGRAM_ID {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// // Execute program
|
||||
let post_states =
|
||||
execute_public::<AuthenticatedTransferProgram>(&pre_states, message.instruction_data)
|
||||
.map_err(|_| ())?;
|
||||
|
||||
// Verify execution corresponds to a well-behaved program.
|
||||
// See the # Programs section for the definition of the `validate_constraints` method.
|
||||
validate_constraints(&pre_states, &post_states, message.program_id).map_err(|_| ())?;
|
||||
|
||||
if (post_states.len() != message.addresses.len()) {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(message
|
||||
.addresses
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(post_states.into_iter())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
public_transaction::{self, WitnessSet},
|
||||
signature::PrivateKey,
|
||||
};
|
||||
|
||||
fn genesis_state_for_tests() -> (V01State, Address) {
|
||||
let account_1 = {
|
||||
let mut this = Account::default();
|
||||
this.program_owner = AuthenticatedTransferProgram::PROGRAM_ID;
|
||||
this.balance = 100;
|
||||
this
|
||||
};
|
||||
let address_1 = Address::new([1; 32]);
|
||||
let public_state = [(address_1.clone(), account_1)].into_iter().collect();
|
||||
(V01State { public_state }, address_1)
|
||||
}
|
||||
|
||||
fn transfer_transaction_for_tests(
|
||||
from: Address,
|
||||
from_key: PrivateKey,
|
||||
to: Address,
|
||||
balance: u128,
|
||||
) -> PublicTransaction {
|
||||
let addresses = vec![from, to];
|
||||
let nonces = vec![0];
|
||||
let program_id = AuthenticatedTransferProgram::PROGRAM_ID;
|
||||
let message = public_transaction::Message::new(program_id, addresses, nonces, balance);
|
||||
let witness_set = WitnessSet::for_message(&message, &[from_key]);
|
||||
PublicTransaction::new(message, witness_set)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_1() {
|
||||
let (mut genesis_state, address) = genesis_state_for_tests();
|
||||
let from = address;
|
||||
let from_key = PrivateKey(1);
|
||||
let to = Address::new([2; 32]);
|
||||
let balance_to_move = 5;
|
||||
let tx = transfer_transaction_for_tests(from, from_key, to.clone(), 5);
|
||||
let _ = genesis_state.transition_from_public_transaction(tx);
|
||||
assert_eq!(
|
||||
genesis_state.get_account_by_address(&to).balance,
|
||||
balance_to_move
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user