diff --git a/zkvm/src/blockchain/block.rs b/zkvm/src/blockchain/block.rs index 91d56f52d..d4b8b3a98 100644 --- a/zkvm/src/blockchain/block.rs +++ b/zkvm/src/blockchain/block.rs @@ -1,7 +1,6 @@ use merlin::Transcript; -use super::super::utreexo; -use crate::{MerkleTree, Tx, TxID}; +use crate::{utreexo, MerkleTree, Predicate, Tx, TxID}; /// Identifier of the block, computed as a hash of the `BlockHeader`. #[derive(Clone, Copy, PartialEq)] @@ -33,6 +32,8 @@ pub struct BlockHeader { pub struct Block { /// Block header. pub header: BlockHeader, + /// List of nit allocations + pub nits: Vec<(u64, Predicate)>, /// List of transactions. pub txs: Vec, /// UTXO proofs diff --git a/zkvm/src/blockchain/errors.rs b/zkvm/src/blockchain/errors.rs index 393d3df30..8778aa317 100644 --- a/zkvm/src/blockchain/errors.rs +++ b/zkvm/src/blockchain/errors.rs @@ -8,6 +8,10 @@ pub enum BlockchainError { #[fail(display = "Inconsistent data in the block header.")] InconsistentHeader, + /// Occurs when the block allocates incorrect amount of nits. + #[fail(display = "Invalid allocation of nits in the block.")] + InvalidNitAllocation, + /// Occurs when extension field is non-empty in v1 blocks. #[fail(display = "Extension field must be empty in v1 blocks.")] IllegalExtension, diff --git a/zkvm/src/blockchain/mod.rs b/zkvm/src/blockchain/mod.rs index e47154424..3821934cb 100644 --- a/zkvm/src/blockchain/mod.rs +++ b/zkvm/src/blockchain/mod.rs @@ -2,6 +2,7 @@ mod block; mod errors; +pub mod nits; mod state; #[cfg(test)] diff --git a/zkvm/src/blockchain/nits.rs b/zkvm/src/blockchain/nits.rs new file mode 100644 index 000000000..e8a1a91ca --- /dev/null +++ b/zkvm/src/blockchain/nits.rs @@ -0,0 +1,112 @@ +//! "Nonce units", aka "nits" are built-in assets used to provide uniqueness anchors to the ZkVM transactions. +//! Nits have a special all-zero flavor to domain-separate them from all issued assets. +//! Nits are introduced gradually, so they can always be available +//! to new users for mixing into their transactions. +//! +//! Issuance is 1 nit per second, using an exponentially decreasing schedule +//! in order to cap the total amount to fit under u64. +//! 1 millionth of a nit is simply called "micro nit" or "unit". + +use curve25519_dalek::scalar::Scalar; +use merlin::Transcript; + +use crate::{Anchor, Commitment, Contract, PortableItem, Predicate, Value}; + +/// Interval of halving the amount of issued nits, in ms. +pub const HALVING_INTERVAL: u64 = 4 * 365 * 24 * 3600 * 1000; + +/// Number of units issued per millisecond, before halvings. +/// This translates to 1 nit per second. +pub const UNITS_PER_MS: u64 = 1000; + +/// Returns amount eligible for circulation between +/// the initial time and the current time, in units. +pub fn circulation(initial_time_ms: u64, current_time_ms: u64) -> u64 { + if current_time_ms < initial_time_ms { + return 0; + } + + let mut interval_ms = current_time_ms - initial_time_ms; + let mut circulation = 0u64; + let mut halvings = 0u64; + + while interval_ms > 0 { + let increment_ms = if interval_ms > HALVING_INTERVAL { + interval_ms -= HALVING_INTERVAL; + HALVING_INTERVAL + } else { + let tmp = interval_ms; + interval_ms = 0; + tmp + }; + if halvings < 64 { + circulation += (increment_ms * UNITS_PER_MS) >> halvings; + halvings += 1; + } + } + circulation +} + +/// Returns amount eligible for the block created between +/// the `prev_time_ms` and `new_time_ms`. +pub fn block_allowance(initial_time_ms: u64, prev_time_ms: u64, new_time_ms: u64) -> u64 { + circulation(initial_time_ms, new_time_ms) - circulation(initial_time_ms, prev_time_ms) +} + +/// Creates a new contract with a given quantity of nits. +pub fn make_nit_contract( + qty: u64, + predicate: Predicate, + anchoring_transcript: &mut Transcript, +) -> Contract { + Contract::new( + predicate, + vec![PortableItem::Value(Value { + qty: Commitment::unblinded(Scalar::from(qty)), + flv: Commitment::unblinded(Scalar::zero()), + })], + Anchor::nit_anchor(anchoring_transcript), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zero_circulation() { + assert_eq!(circulation(0, 0), 0); + assert_eq!(circulation(2, 1), 0); + } + + #[test] + fn simple_circulation() { + assert_eq!(circulation(0, 1), UNITS_PER_MS); + assert_eq!(circulation(100, 101), UNITS_PER_MS); + } + + #[test] + fn halving() { + assert_eq!(block_allowance(0, 100, 101), UNITS_PER_MS); + assert_eq!( + block_allowance(0, HALVING_INTERVAL, HALVING_INTERVAL + 1), + UNITS_PER_MS / 2 + ); + assert_eq!( + block_allowance(0, HALVING_INTERVAL + 1, HALVING_INTERVAL + 2), + UNITS_PER_MS / 2 + ); + assert_eq!( + block_allowance(0, 2 * HALVING_INTERVAL, 2 * HALVING_INTERVAL + 1), + UNITS_PER_MS / 4 + ); + assert_eq!( + block_allowance(0, 3 * HALVING_INTERVAL, 3 * HALVING_INTERVAL + 1), + UNITS_PER_MS / 8 + ); + assert_eq!( + block_allowance(0, 4 * HALVING_INTERVAL, 4 * HALVING_INTERVAL + 1), + UNITS_PER_MS / 16 + ); + } +} diff --git a/zkvm/src/blockchain/state.rs b/zkvm/src/blockchain/state.rs index bb3e0981d..b6cbd4f48 100644 --- a/zkvm/src/blockchain/state.rs +++ b/zkvm/src/blockchain/state.rs @@ -3,14 +3,17 @@ use core::borrow::Borrow; use super::block::{Block, BlockHeader, BlockID}; use super::errors::BlockchainError; +use super::nits; use crate::utreexo::{self, Catchup, Forest, WorkForest}; -use crate::{ContractID, MerkleTree, Tx, TxEntry, TxHeader, VerifiedTx, Verifier}; +use crate::{ + Anchor, ContractID, MerkleTree, Predicate, Tx, TxEntry, TxHeader, VerifiedTx, Verifier, +}; /// State of the blockchain node. #[derive(Clone)] pub struct BlockchainState { - /// Initial block of the given network. - pub initial_id: BlockID, + /// Initial block header of the given network. + pub initial: BlockHeader, /// Latest block header in the chain. pub tip: BlockHeader, /// The utreexo state. @@ -44,7 +47,7 @@ impl BlockchainState { let tip = BlockHeader::make_initial(timestamp_ms, utreexo.root()); let state = BlockchainState { - initial_id: tip.id(), + initial: tip.clone(), tip, utreexo, catchup, @@ -55,14 +58,27 @@ impl BlockchainState { /// Applies the block to the current state and returns a new one. pub fn apply_block( - &mut self, + &self, block: &Block, bp_gens: &BulletproofGens, ) -> Result { check_block_header(&block.header, &self.tip)?; + check_nit_allocation( + self, + block.header.timestamp_ms, + block.nits.iter().map(|(q, _)| *q), + )?; + let mut work_forest = self.utreexo.work_forest(); + // Apply nit contracts to the utreexo + let nit_proofs = apply_nits( + block.header.prev, + block.nits.iter().map(|(qty, pred)| (*qty, pred.clone())), + &mut work_forest, + ); + let txroot = apply_txs( block.header.version, block.header.timestamp_ms, @@ -83,7 +99,7 @@ impl BlockchainState { } Ok(BlockchainState { - initial_id: self.initial_id, + initial: self.initial.clone(), tip: block.header.clone(), utreexo: new_forest, catchup: new_catchup, @@ -97,6 +113,7 @@ impl BlockchainState { block_version: u64, timestamp_ms: u64, ext: Vec, + nits: Vec<(u64, Predicate)>, txs: Vec, utxo_proofs: Vec, bp_gens: &BulletproofGens, @@ -109,9 +126,16 @@ impl BlockchainState { timestamp_ms > self.tip.timestamp_ms, BlockchainError::InconsistentHeader, )?; + check_nit_allocation(self, timestamp_ms, nits.iter().map(|(q, _)| *q))?; let mut work_forest = self.utreexo.work_forest(); + let nit_proofs = apply_nits( + self.tip.id(), + nits.iter().map(|(qty, pred)| (*qty, pred.clone())), + &mut work_forest, + ); + let txroot = apply_txs( block_version, timestamp_ms, @@ -123,6 +147,12 @@ impl BlockchainState { let (new_forest, new_catchup) = work_forest.normalize(); + // TBD: update nit proofs with new_catchup + // Need to keep around contract ids + // let nit_proofs = nit_proofs.into_iter().map(|proof| { + // new_catchup.update_proof() + // }) + let utxoroot = new_forest.root(); let new_block = Block { @@ -135,12 +165,13 @@ impl BlockchainState { utxoroot, ext, }, + nits, txs, all_utxo_proofs: utxo_proofs, }; let new_state = BlockchainState { - initial_id: self.initial_id, + initial: self.initial.clone(), tip: new_block.header.clone(), utreexo: new_forest, catchup: new_catchup, @@ -188,6 +219,23 @@ fn apply_tx>( Ok(verified_tx) } +/// Applies a list of new nit allocations to the state and +/// returns an iterator of the corresponding utxo proofs. +fn apply_nits( + prev_id: BlockID, + nits: impl IntoIterator, + mut work_forest: &mut WorkForest, +) -> Vec { + let mut transcript = Anchor::create_nit_anchoring_transcript(prev_id.0); + + nits.into_iter() + .map(|(qty, pred)| { + let contract = nits::make_nit_contract(qty, pred, &mut transcript); + work_forest.insert(&contract.id()) + }) + .collect::>() +} + /// Applies a list of transactions to the state and returns the txroot. fn apply_txs, P: Borrow>( block_version: u64, @@ -217,6 +265,28 @@ fn apply_txs, P: Borrow>( Ok(MerkleTree::root(b"ZkVM.txroot", &txids)) } +/// Checks that nits allocation is not exceeding the limit for the current blockchain state. +fn check_nit_allocation( + state: &BlockchainState, + new_timestamp_ms: u64, + allocations: impl Iterator, +) -> Result<(), BlockchainError> { + let allowance = nits::block_allowance( + state.initial.timestamp_ms, + state.tip.timestamp_ms, + new_timestamp_ms, + ); + // Note: we are checking each individual value before adding them up to prevent overflow. + let total = allocations.fold(Ok(0u64), |total, qty| { + total.and_then(|total| { + check(qty <= allowance, BlockchainError::InvalidNitAllocation)?; + Ok(total + qty) + }) + })?; + check(total <= allowance, BlockchainError::InvalidNitAllocation)?; + Ok(()) +} + /// Verifies consistency of the block header with respect to the previous block header. fn check_block_header( block_header: &BlockHeader, diff --git a/zkvm/src/blockchain/tests.rs b/zkvm/src/blockchain/tests.rs index 5a16f9c26..b2bc10b5c 100644 --- a/zkvm/src/blockchain/tests.rs +++ b/zkvm/src/blockchain/tests.rs @@ -68,7 +68,7 @@ fn test_state_machine() { }; let (block, future_state) = state - .make_block(1, 1, Vec::new(), vec![tx], proofs, &bp_gens) + .make_block(1, 1, Vec::new(), Vec::new(), vec![tx], proofs, &bp_gens) .unwrap(); // Apply the block to the state diff --git a/zkvm/src/contract.rs b/zkvm/src/contract.rs index cc2514382..b88e495ab 100644 --- a/zkvm/src/contract.rs +++ b/zkvm/src/contract.rs @@ -162,6 +162,20 @@ impl Anchor { t.challenge_bytes(b"new", &mut self.0); self } + + /// Create a transcript for creating anchors for nit-containing contracts. + pub fn create_nit_anchoring_transcript(seed: [u8; 32]) -> Transcript { + let mut t = Transcript::new(b"ZkVM.nits-anchoring"); + t.commit_bytes(b"seed", &seed); + t + } + + /// Create another anchor + pub fn nit_anchor(transcript: &mut Transcript) -> Self { + let mut buf = [0u8; 32]; + transcript.challenge_bytes(b"nit-anchor", &mut buf); + Self(buf) + } } impl ContractID {