GithubHelp home page GithubHelp logo

stellar / slingshot Goto Github PK

View Code? Open in Web Editor NEW
405.0 62.0 61.0 7.26 MB

A new blockchain architecture under active development, with a strong focus on scalability, privacy and safety

License: Apache License 2.0

Rust 97.64% Ruby 0.80% CSS 0.39% JavaScript 0.31% HTML 0.86%

slingshot's Introduction

Project Slingshot

Accelerating trajectory into interstellar space.

Slingshot is a new blockchain architecture under active development, with a strong focus on scalability, privacy and safety.

The Slingshot project consists of the following components:

Demo node where one can create transactions and inspect the blockchain.

ZkVM is a transaction format with cloaked assets and zero-knowledge smart contracts.

Abstract blockchain state machine for the ZkVM transactions.

Interstellar’s implementation of Cloak, a confidential assets protocol based on the Bulletproofs zero-knowledge circuit proof system.

A pure Rust implementation of the Schnorr signature scheme based on ristretto255.

A pure Rust implementation of the Simple Schnorr Multi-Signatures by Maxwell, Poelstra, Seurin and Wuille.

A key blinding scheme for deriving hierarchies of public keys for Ristretto-based signatures.

A Merkle tree API for computing Merkle roots, making and verifying Merkle proofs. Used for ZkVM transaction IDs, Taproot implementation and Utreexo commitments.

Based on RFC 6962 Section 2.1 and implemented using Merlin.

API for managing accounts and receivers. This is a building block for various payment protocols.

Small p2p networking library that implements peer management logic with pluggable application logic. Implements symmetric DH handshake with forward secrecy.

Simple encoding/decoding and reading/writing traits and utilities for blockchain data structures.

slingshot's People

Contributors

bobg avatar brahman81 avatar cathieyun avatar cyberbono3 avatar debnil avatar dltmd6262 avatar hdevalence avatar kevaundray avatar leighmcculloch avatar oleganza avatar p0lunin avatar tessr avatar vickiniu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

slingshot's Issues

zkvm: integer-preserving arithmetic on ScalarWitness

We want to preserve ScalarWitness through a number of arithmetic operations in case it ends up in a rangeproof that requires an in-range integer. Example: you compute a payment amount in a contract, and then use it for qty to be paid. The VM will insert a rangeproof, and (on the prover side) it won't like to receive a Scalar instead of a u64. So we need to preserve this and catch it early on the prover side when u64 overflows.

Suggestion:

  • Implement Neg, Add and Mul traits for ScalarWitness.
  • In Add and Mul traits check for overflows of signed integer and upgrade it to ScalarWitness::Scalar in such case.
  • Review the existing code to detect cases where we do not preserve integers, but can.

accounts: abstraction for keeping track of balances

The low-level API is pretty general-purpose (because it exposes all ZkVM concepts directly, even if with nice structured types), so any higher-level API can be more opinionated. Account API is somewhat general-purpose, yet has some very specific features:

  1. Compute the balance
  2. Select UTXOs for spending
  3. Derive keys for locking funds
  4. Derive blinding factors
  5. Create a payment request
  6. Verify a payment proof
  7. Update balances

Statefulness

Accounts are necessarily stateful, but we are designing a protocol: so we want to model the state with a clean API to allow plugging in a database w/o leaking the protocol specifics into the outer application that only needs to keep track of stuff for the protocol.

Features

There are a number of features that we may need in this protocol (e.g. "proof of reserves"), but we don't yet have a good clue. We should neither try to prematurely add them, nor attempt to provide a API to permit bolting them on later. Let's just implement a limited self-consistent functionality and extend/tweak it as we see fit over time.

FFI

This is a sufficiently high-level protocol, plus it requires tracking state and do I/O, to consider the FFI layer for interop with other languages (e.g. Go). The idea would be to have a minimal interface with few opaque data items to be passed in and out of the protocol, so that the host application can follow a few simple rules to keep track of things for us (store/load things from DB, make network requests) without being exposed to critical pieces of the protocol.

Components
Some of these things will require creating more general-purpose components (like proofs of txlog membership), but we can first keep them for accounts only, and if they become needed in other protocols, we'll move them over to ZkVM utils. But if we do so prematurely, we will become oxygen-deprived architecture astronauts (1, 2).

zkvm: implement `alloc` instruction

alloc instruction creates a free variable in R1CS without an individual Pedersen commitment.

  1. Change Instruction::Alloc to Instruction::Alloc(Option<ScalarWitness>) to let the prover specify the secret assignment.
  2. When parsing bytecode (on the verifier's end) use Alloc(None).
  3. Implement alloc per specification by allocating a low-level variable in R1CS and using the provided optional assignment.

zkvm: program builder API

To make use of low-level API nicer, we can use a builder pattern:

// ops.rs
pub struct Program(Vec<Instruction>);
// some_test.rs
use ops::Program;
...
let instructions: Vec<Instruction> = Program::new()
.push(Input)
.input()
.signtx()
.push(predicate)
.issue()
.signtx()
...
.to_vec(); // converts to Vec<Instruction>

This allows us to avoid mouthful Instruction:: prefixes and having to do .into() all the time, as we can define generic push<T: Into<Data>>(T) and call .into() inside the push().

We can also pass &mut program to the helper functions just like we pass &mut transcript: the helper will simply add more instructions to the instance, w/o worrying who and when instantiated it.

We can also add "macros" via extension traits, e.g. a hypothetical .spend_input(Input) may do .push(Input).input().signtx(). We don't have to worry about this now, we'll see later which macros make sense and declutter the code, and which add unnecessary cognitive burden.

(based on the comment: https://github.com/interstellar/slingshot/pull/85/files#r259096201)

zkvm: API guide

The ZkVM spec covers verification of the ZkVM transactions. However, the implementation also implements a Prover API that reuses VM logic in order to construct a transaction signature, program bytecode and R1CS proof. Prover uses a lot of structured data types such as FrozenContract, CommitmentWitness and others that remain opaque on the Verifier's side. We need a document that provides and overview of this architecture, in addition to the core specification.

zkvm: UTXO indexing protocol [WIP]

Here's an outline of the UTXO indexing loop that should be suitable to a variety of scenarios (so it's sufficiently unassuming):

1. Requesting a payment

When the transaction is composed or a payment is requested, the user stores the expected Output object. It is not spendable because it's not a UTXO because there's no txid. Even if the same module/party composes the entire transaction, it's txid is not reliable until the transaction is finalized.

The user needs to store enough information to reconstruct a zkvm::Output with witness-bearing predicate and items (specifically, the blinding factors for the Values). Note: various secrets may be derived on the fly from some root key and therefore do not need to be stored. That is, it is probably not a good idea to provide serialization API for the ZkVM witness types.

2. Confirming a payment

It's good to decouple both blockchain and transaction validation from confirming the payment. The module that tracks UTXOs only needs a merkle proof that a txlog entry with an expected output is a part of some txid, and that txid is published in a certain block.

  1. Merkle proof item->txid should be provided immediately by the party/module that composes the whole tx, as a response to all the contributing parties/modules.
  2. Merkle proof txid->block should be provided by the "network module" that submits a tx a returns such "publication receipt" ("proof of publication") synchronously or asynchronously to the party that submitted the transaction.

The module that tracks UTXOs updates the pending payment in a lockstep: receives both proofs, verifies them, stores txid and the new blockheader, advancing it's vision of the "current state of blockchain" (w/o tracking the whole state - that's performed by a blockchain verification module).

3. Selecting UTXOs for spending

TBD: specify how the utxos are selected for spending - randomly, in FIFO order or else?

For each loaded UTXO, derive blinding factors and signing keys (if they are derivable and not stored), and construct Output objects with items consisting of Data and Value items with appropriate witness data. Construct Input objects with these outputs and appropriate txids and insert them in a new transaction.

blockchain: design and implement blockchain state machine protocol

ZkVM is stateless: its output is a transaction log that contains instructions for an upstream protocol to delete and insert pieces of data (utxos and nonces). That protocol is a blockchain state machine: it takes in blocks of transactions, creates a ZkVM instance for each transaction, verifies them and applies changes to the state. Invalid transactions are rejected.

We want to implement this protocol with an abstract interface, w/o specifying exactly the serialization for blocks, or the consensus protocol. If we take out these parts from the TxVM blockchain specification and adjust the merkle tree / patricia tree definitions to the Transcript-based versions, we'd get exactly what we need for ZkVM blockchain: rules for updating a set of utxos, nonces and checking time bounds.

We need:

  • Rules for committing to the tx list, utxo set and nonce set per block
  • Rules to validate individual transaction
  • Rules to validate & apply a block of transactions
  • Interface to storing/loading the state, so it can be implemented with in-memory storage or an arbitrary database.

zkvm: enum for const expressions

I think it makes sense to make operations on constant expressions formalized with an enum:

pub type ExpressionTerm = (r1cs::Variable, Scalar);
pub enum Expression { 
  Constant(ScalarWitness), 
  Terms(Vec<ExpressionTerm>, Option<ScalarWitness>)
}

Then, we'd need to define Neg, Add traits for these and use them in neg and add instructions. The idea is to preserve Expression::Constant if possible. mul instruction is stateful for non-constant case, so it can only be implemented directly in VM.

See also #91.

zkvm: implement `borrow` instruction

borrow creates positive and negative values on the stack.

This is a good learning exercise: all the facilities (including add_range_proof) are already there, but things should be carefully put together per specification.

ZkVM checklist

This is a tracking issue for ZkVM implementation.

  • vm: Predicate tree: disjunction and program commitment: interstellar/zkvm#4
  • vm: aggregated signature protocol: interstellar/zkvm#4, interstellar/zkvm#8
  • vm: batched verification of point operations: interstellar/zkvm#16
  • vm: VM state with basic input+signtx+cloak+output flow: interstellar/zkvm#4
  • vm: encoding inputs/outputs: interstellar/zkvm#4
  • vm: merkle tree for txid: interstellar/zkvm#4
  • api: prover: low-level API: convert a stream of Instructions into a program bytecode, tx sig and r1cs proof. PR: #69, #79
  • api: unit tests for end-to-end tx creation/verification using low-level API: #100
  • api: prover: high-level input-output txbuilder on top of the low-level API.
  • api: prover: issuance txbuilder: add optional nonce if no inputs are present
  • api: prover: retirement (add an API for destroying a value)
  • api: prover: make txid-inclusion proof for chosen outputs
  • vm: left/right instructions
  • vm: delegate/call instructions
  • vm: contract instruction
  • vm: log instruction interstellar/zkvm#20
  • api: txbuilder: add data-only (log) entry API
  • api: payment channel protocol
  • api: vault protocol
  • vm: mintime/maxtime instructions
  • vm: qty/flavor instructions
  • vm: var/const instructions
  • vm: neg/add/mul instructions: #88, #101, #102
  • vm: eq/and/or/verify instructions: #102, #147
  • vm: alloc instruction
  • vm: import/export instructions
  • vm: blind/reblind/unblind instructions

keytree: serialization for xpub, xprv

For both Xprv and Xpub types:

  • ::to_bytes(&self) -> [u8; 64]
  • ::from_bytes(&[u8]) -> Option<Self> (checking that slice is exactly 64-byte-long)

For later: implement Serde on top of these (example)

Note: the from_bytes won't benefit from using fixed-length array ([u8; 64]) because at least Xprv would still have to check if scalar is canonically encoded. So we might as well make decoding of all of these failable and take byteslices to let user not to make extra 64-byte copies.

musig: tracking issue

About MuSig

For multi-signature accounts and multi-party contracts we need to implement a n-of-n MuSig protocol (overview, paper).

The verification construction is very similar to the aggregated transaction signature except the key aggregation happens offline — the verifier is exposed only to the single pubkey.

The proving construction is a 3-round MuSig procedure (see the MuSig paper).

We'll also need to extend the ZkVM witness types and Prover API to handle the multi-party signing: we'd need to produce verifiable "signing instructions" that every co-signer can check before computing their part of the signature. It's not clear yet how that should be structured - this remains to be designed.

Checklist

  • Multi-party API for plain single-message MuSig. PR: #190
  • implement counterparty state machines, update error type. PR: #229
  • Pass &mut Transcript to the signing parties. PR: #245
  • Single-signer API on top of multi-signer PR: #237
  • Make Musig its separate crate: #253
  • ZkVM uses Musig crate (convert from MusigError, MusigPointOp types)
  • Multi-message verification API. design doc: #260
  • Recursive API to nest musig aggregation as participants in a higher-level musig: #236
  • Scalar-adjusted API, so we can have aggregate(n-of-n) + constant for predicate tree usage when n-of-n is inside the left L branch of a disjunction and the constant is an adjustment factor hash(L,R). #234
  • Multi-message API for use with signtx mitigating the Russel's attack. #176
  • Integrate Multikey with Predicate Tree: #235
  • Plug the correct multi-message API for ZkVM's signtx: #146.
  • Design the framework for signing rules for use with signtx: #146.
  • Move MuSig into its own crate: #239.
  • Defer verification of signatures: #243

FAQ

What's the role of Merlin transcripts?

MuSig can be specified more cleanly with transcripts rather than with nested hash constructions: e.g. instead of making a delinearization factor as H(my key, H(all keys)) you'd just commit all pubkeys into a transcript, then squeeze all factors one by one from it. Also, no need to specify message hashing and padding - just have the user prepare the signature transcript however they please and then do the schnorr proof with it w/o worrying about the message.

How do we do 2-of-3 and other thresholds?

Via predicate tree: happy path would be the most-expected 2-of-2 (Alice+Bob), while other combinations (Alice+Carol, Bob+Carol) tucked into deeper branches. This does not scale well to large number of participants, but works fine for typically small sizes.

Why not a threshold scheme?

Threshold signatures (m-of-n, m ≤ n) use a very similar cryptographic construction outlined in MuSig paper, yet have two important practical differences:

  1. The secret keys have to be interactively created. N-of-N aggregation allows every party to generate or even reuse their existing keys (e.g. derived from a Keytree #87) w/o having to make a multi-round protocol to compute some synthetic keys and have them stored.
  2. Threshold signatures are more compact than a merkle tree of combinations, but not accountable: you can never learn who were exact parties that signed off the particular message. For some protocols it's important to know whether "cold storage key" or "escrow agent" was involved or not. Combining n-of-n keys with the predicate tree allows seeing who actually signed, while threshold signatures erase this information. Note: accountability is available only to the parties in-the-know, for the rest of the network all the keys look random.

We'd eventually need threshold scheme too, especially for large thresholds or for cases where accountability is not necessary and we only need redundancy.

References

zkvm: TxHeader type

Add a TxHeader type to encapsulate commonly-going-together version and time bounds:

/// Header metadata for the transaction
pub struct TxHeader {
    /// Version of the transaction
    pub version: u64,

    /// Timestamp before which tx is invalid (sec)
    pub mintime: u64,

    /// Timestamp after which tx is invalid (sec)
    pub maxtime: u64
}

Use it in: Tx, VerifiedTx, txlog::Entry::Header and Prover::build_tx.

zkvm: should timestamps be in milliseconds vs seconds

@bobg raised a valid question regarding time bounds that are currently specced as UNIX timestamp in seconds. TxVM uses milliseconds. Which one should we use?

Why seconds

  1. Biggest reason: this is more "standard", therefore less potential confusion or mistakes by accident. Bitcoin/Ethereum/Stellar use seconds.
  2. Precision may be just enough for a wide range of applications.
  3. Lesser reason: inequality checks for time intervals can have cheaper range proofs. E.g. 1 month requires 20-bit rangeproof with seconds, but 30-bit one with milliseconds.

Why milliseconds

@bobg argues:

  1. It'll be better to have them and not need them than need them and not have them.
  2. http://www.rinkworks.com/said/predictions.shtml

ZkVM: API layering proposal

Low-level API

  • Takes a stream of structured instructions bearing witness data (secret values, secret blinding factors, private keys, etc.).
  • Passes that stream through a generic VM implementation that performs the same stack operations as the verifier, and constructs a transaction log and a constraint system.
  • Generates a Tx object that contains:
    • serialized program bytecode,
    • aggregated signature over txid (see signtx instruction)
    • R1CS proof for the constraint system

High-level API

This is defined in terms of particular actions: issue funds, spend funds, etc. This itself may be layered, like key derivation and utxo selection for accounts could be a separate layer on top of more agnostic spend/send API.

Here's a sketch of things we want to implement at this level:

  1. Asset issuance API (issue/retire)
  2. Account API (spend/send)
  3. Pegging (import/export)
  4. Vault protocols (multi-key storage + timelock)
  5. Payment channels and payment routing (Lightning)

musig: multi-signer API

Problem

For multi-key/multi-party signing we need an API that handles both individual signatures (for delegate) and aggregated per-tx signature (signtx).

For delegate, ZkVM already expects a signature produced externally, and its signing logic is tied to a specific protocol (e.g. a payment channel), so we don't need to focus on it right now.

For signtx, ZkVM provides a list of UTXOs to be signed, along with the txid. And we do not want to keep all secret keys in one machine that composes the transaction, yet we need a way to produce a joint Tx instance with a complete signature. So we'll focus on that problem.

Prerequisites

  • Removing signing secrets from Predicate::Key witness: PR #142
  • Core MuSig implementation: #92
  • Integration of Multikey with Predicate tree: #235
  • Nested Multikeys: #236
  • Multi-message API: #176

Design ideas

First, the ZkVM on a prover side cannot immediately produce a fully signed Tx object. Instead, it needs to pause when R1CS proof is done, but signature is not, and returns an UnsignedTx instance to let the multiple signers produce and aggregate their signatures.

If there's only one signer, there could be a convenience API to signed the UnsignedTx and produce Tx with just a single private key.

For multiple signers, there are two ways the signers could proceed:

  1. They could replay the ZkVM transaction per their own rules. Each will receive some txid as a result, and sign the UTXOs that they can spend (have keys for), ignoring additional UTXOs that might be thrown in by other parties (multi-message protocol #176 makes this safe to do). If they all agree on the rules and txid is the same for all signers, their signatures will all agree. This is a use case for various multi-factor hardware wallets: think your desktop and phone both have the same view of your wallet and sign the same high-level "payment request" per identical rules.
  2. Alternatively, signers can receive only a txid, their utxoids to sign, and a proof of the specific outputs that they need to verify (merkle path to txid + witness data for commitments). Then, they can avoid replaying the entire tx, and it could have been assembled by someone else entirely. For instance, if the policy is "spend $10 at a time to allowed addresses", the signer could see that they are spending a few utxos with total worth of $30, then they'll need a merkle path to an output with $X≤$10 to an allowed address, and a merkle path to an output to a change address (derived from the same Keytree) with the ($30-$X) as amount. They can then safely ignore the rest of the transaction.

Any top-level MuSig party can complete the UnsignedTx with an aggregated signature and produce and publish fully-signed Tx.

ZkVM: decode input string into Input object

Currently we decode input string into a Contract object directly, and have to do it inside the VM because the payload Values need variables to be allocated.

However, we can split this work in two parts:

  1. decode input string into Input object (containing FrozenContract, FrozenValues) outside VM.
  2. convert FrozenContract into Contract and FrozenValue into Value inside VM.

This would allow us to group encoding/decoding of contracts (cf. #69) together in one place, while reusing the code that converts FrozenContract to Contract for both the prover and verifier.

zkvm: safer parsing API with subslices

Problem

We currently have a few places where we are supposed to parse a vector of bytes, but do not check whether we consumed all the bytes:

  1. decoding a point:
    https://github.com/interstellar/slingshot/blob/2ed9fc02d722c205e619c96947c6df0e57b0d953/zkvm/src/types.rs#L300
  2. decoding another point:
    https://github.com/interstellar/slingshot/blob/2ed9fc02d722c205e619c96947c6df0e57b0d953/zkvm/src/types.rs#L313
  3. parsing an input:
    https://github.com/interstellar/slingshot/blob/ad114c0a4ddc8318b91ee167411ee6431772ccd5/zkvm/src/contract.rs#L73-L78 (checking, but it's not enforced by the API)

We also allow arbitrary conversion of a subslice state into a &[u8] which, when the line is reordered, completely changes the content (because the subslice is mutable):
https://github.com/interstellar/slingshot/blob/ad114c0a4ddc8318b91ee167411ee6431772ccd5/zkvm/src/contract.rs#L91-L93

Suggestion

  1. Rename Subslice into SliceReader
  2. Remove Deref and to_vec to avoid accidental conversion to a slice of whatever-state the reader is in.
  3. Add .slice(|r| { ... }) method that remembers the offset before/after the closure and returns both the result of the closure and the &[u8] that was parsed in it. Forwards the failure. We'll use it to compute UTXO after parsing the contract, but reusing the bytes that we parsed without wasting CPU on re-serializing.
  4. Add Reader::parse(&[u8]){|r| ...->T } -> Result<T, VMError> that checks that the whole input buffer is "consumed" (the reader ends with len()==0). This replaces Subslice::new(&[u8]).
  5. To support use in Verifier where the program is not fully parsed in a static scope, add
    reader.skip_trailing_bytes() -> usize // returns the offset of the trailing bytes; and advances the reader to the end
    
    this allows removing range() api and be compatible with checking logic inside ::parse which is now the only way to construct a reader. We can also make this method consume the Reader - we only need it in places where we own the Reader because we own the backing bytearray.
  6. Make sure the parsing APIs take &mut reader instead of moving it in (to make them compatible with the checking ::parse)
  7. Remove Copy/Clone traits and see if we can live w/o them - it's kinda dangerous for the same reason as converting the reader to a slice.

The above snippets then will look like this:

Parsing a point:

let point = SliceReader::parse(&data){|r| r.read_point() }?;

Parsing an input (note the lack of ? - closure and all methods are failable, and errors are propagated cleanly):

impl Input {
    pub fn from_bytes(data: Vec<u8>) -> Result<Self, VMError> {
        SliceReader::parse(&data, |r| Self::decode(r))
    }
}

Computing UTXO:

impl Input {
    fn decode<'a>(reader: &mut SliceReader<'a>) -> Result<Self, VMError> {
        // Input  =  PreviousTxID || PreviousOutput
        // PreviousTxID  =  <32 bytes>
        let txid = TxID(reader.read_u8x32()?);
        let (contract, contract_bytes) = reader.slice(|r| {
            FrozenContract::decode(r)
        })?;
        let utxo = UTXO::from_output(contract_bytes, &txid);
        Ok(Input {
            contract,
            utxo,
            txid,
        })
    }
}

musig: multi-message API for mitigating Russell's attack

Background

See appendices A.2/A.3 in the MuSig paper.

TL;DR

If you have two UTXOs using the same key, and you did not check ALL the utxos being using in the signed message, then your partial signature for one UTXO can be reused to also sign off inclusion of another UTXO.

Solution for aggregation-by-verifier

The MuSig paper's appendix A.3 describes a solution:

  1. The total signed message is formed with ordered pairs (pubkey, item) where item is a specific message signed by the given pubkey (e.g. a unique utxo id or contract id - see also #172).
  2. Each party generates their own Fiat-Shamir challenge corresponding to their index.
  3. The per-party F-S challenges effectively cover the "delinearization" factor applied to each pubkey, so there is no need to apply it to pubkeys in prior steps.
  4. The verifier needs to see each individual pubkey and individual message for this to work.

Key aggregation by provers

In the cases when the verifier does not see individual keys, that is, when they were compressed under n-of-n rule offline into a single predicate, each signer is supposed to be fully aware of a message they are signing.

This works fine in case of delegate (provided the contract's anchor is unique and covered by signature: #172).

For signtx, the signer must be "aware of" not just txid, but the specific pair of (pubkey, item) which is normally a (predicate, utxo_id). They usually would ignore txid and only verify proofs of certain outputs to belong to the txid that they sign, since the txid itself and other signers' outputs are not relevant to the given signer.

API

The multi-message API is inherently different from single-message API, since each key's delinearization factor is effectively an individual fiat-shamir challenge bound to the specific (key-submessage) pair.

This should be composable over the regular musig protocol for nested Multikeys via API for cumulative multiplicative adjustment factor (#236).

zkvm: encrypted values and data instead of reblinding protocol

Problem

Blinding/Reblinding protocol in ZkVM solves the problem of (roughly speaking) "communicating the blinding factor to a counter-party". The problem is still relevant, but the solution is sub-optimal:

  1. Values become "stateful" — their variables for qty/flavor have to keep commitments inside the VM to make them "replaceable" (re-blindeable).
  2. This makes the runtime state more complicated and requires tracking whether the variable is "attached" or "detached".
  3. We cannot use PortableItem and Value types in the Output: instead we have another set of types FrozenItem and FrozenValue.
  4. When the commitment is blinded with blind instruction, the user is supposed to copy-paste a part of the proof into the reblind instruction to "remind the VM" that a certain commitment has a required shape. This is an unnecessary overhead that may be eliminated if the VM encodes the valid transformation in the type system.

Suggestion

Instead of focusing on commitments, introduce EncryptedValue and EncryptedData that represent valid ElGamal encryptions of a Value type and Data type respectively.

Then, we can remove FrozenItem/FrozenValue types, use simple commitments within Value type, removing variable_commitment vector and concepts of "attached" and "detached" variable state from the VM.

Maybe the decryption proof can be made simpler since we'll be using a more suitable structure (elgamal encryption) within the new "encrypted" types.

TBD: should we allow individual encryption of qty/flavor, e.g. what if flavor is statically predetermined. Or maybe we can share the encryption nonce among both of those?

Alternative

If there's efficiency gain from encrypting a lot of commitments at once to a single key, we can encrypt the entire contract!

  1. Make Variable(Commitment) a portable type.
  2. Encrypt a list of items, applying it to Value and Variable and skipping Data items.
  3. Keep additional elgamal encryption data inside a separate type EncryptedContract, or per-item within EncryptedValue or EncryptedVariable.

zkvm: try collapsing Data and DataWitness in one enum

Maybe things will be easier to navigate if we simply had:

#[derive(Clone, Debug)]
pub enum Data {
    Opaque(Vec<u8>),
    Program(Vec<Instruction>),
    Predicate(Box<Predicate>),
    Commitment(Box<Commitment>),
    Scalar(Box<ScalarWitness>),
    Input(Box<Input>),
}

instead of Data::Witness(DataWitness::____).

The runtime overhead is exactly the same.

zkvm: review use of `_` in the `match` statements

It is sometimes dangerous to use catch-all case (_) in match statements: example. When we make a bulk decision for "all other" variants, but then add an n+1 variant that does not conform, we can overlook this and forget to update the match statement.

On the other hand, sometimes we need to match on a single specific variant and fail on all the others, and if there is a large number of alternative variants, it may be safe to just say _ => Err(...), since it's very likely going to remain correct when more variants are added.

Here is a draft of guidelines for using match:

  1. Never use _ for short enumerations (2-3 variants). E.g. it's clearer to do None => None when matching on Option than _ => None.
  2. Never use _ for medium enumerations (e.g. data/witness types). It's safer to just enumerate A(_), B(_), C(_) => Err(...) — we'll be forced to update the list when we add or remove a variant, and think of this decision.
  3. Only use _ when the number of options is large and we want to fail on all but one variant. E.g. _ => Err(...) is allowed.
  4. Add a macro to expand arbitrary boilerplate for instructions without arguments in ops.rs and replace the rule (3) with rule (3b): never use _ in the match statement.

zkvm: implement `blind` and `reblind` instructions

blind and reblind instructions allow two parties to perform an El Gamal encryption/decryption for their secret values within the contracts. The protocol is a bit more complicated than classic El Gamal encryption because the commitments that we use are not El Gamal commitments, but ordinary Pedersen commitments. This means the blinding protocol also needs to make sure that the committed value is preserved and not leaked.

UPD: Do not implement these! See #177

zkvm: de-emphasize witness APIs for more types

In #97 all of Predicate API is concentrated in the main Predicate type, while the PredicateWitness is left as a helper type. It must be public, but it does not have any public API and cannot be constructed other than via Predicate. This makes the code more straightforward as the user is exposed only to the type they actually care about while all the witness organization is left as an implementation detail.

We can do the same for other types, such as Commitment/CommitmentWitness. And maybe make Frozen* types pub(crate) and shrink down their API w.r.t. to usage within VM.

zkvm: make contracts unique

PR: #193

Currently delegate instruction signs only a program and a predicate, but not the contract's contents. So if there are two contracts with the same predicate, delegated program signed for one can be reused for another.

Half-way solution to this is to serialize the contract too. Due to (normally) unique blinding factors making the value commitments unique, this prevents reuse for most cases. However, there are still possible corner cases when values are repeated and/or non-blinded.

Full-way solution is to preserve some unique "anchor" (see also TxVM anchors) to a contract. However, unlike TxVM, we need this anchor attached to a contract, not the values.

How do we do that?

  1. Each contract that is instantiated from a UTXO already has an anchor - the UTXO ID.
  2. Each contract created during tx execution needs to sample a unique value. For that we can have an extra running Transcript instance that's instantiated on the first input/nonce use and seeded with the corresponding utxo id or nonce. Subsequent inputs/nonces similarly seed the transcript. And transiently created contracts squeeze the unique anchors from it.

Why don't we squeeze anchors for claimed utxos too? Because then the signing is more dependent on all the preceding steps, making it impossible to sign an individual contract for use with delegate prior to forming the transaction — that's an important feature for payment channels, for instance.

We can replace .unique flag with this Option<Transcript> instance.

zkvm: predicate tree traversal API

The VM's left/right instructions expect a proof of correct branching. In other words, these must be preceded with push(L) and push(R) instructions. Since the prover's Predicate type has all the structure to compute these values, it may be convenient to have Predicate type generate instructions itself:

impl Predicate {
    pub fn choose_left(&self, program: &mut Program) -> Result<(),VMError> {
        let (L,R) = self.to_disjunction()?;
        program.push(L);
        program.push(R);
        program.left();
    }
}

Note: the prover would need to have a reference to the predicate. It is usually available since the prover would construct an Input prior to that and can borrow the corresponding predicate from there.

zkvm: factor out binary merkle tree from txid computation

Currently merkle tree logic is tied to txid/txlog computation. We'll need it for blockchain tree-of-transactions commitments (#93) too.

We can replace slices &[Entry] with generic slices &[I] where I: MerkleItem, with trait MerkleItem having method to write contents into the &mut Transcript:

trait MerkleItem {
    fn commit(&self, t: &mut Transcript);
}

We can then implement MerkleItem for txlog::Entry and also implement it for u64 inside the tests to make them decoupled from txid.

token: abstraction for issuing and retiring tokens

This is related issue to Accounts API #94:

Features:

  1. Create a token
  2. Issue a token
  3. Compute amount of outstanding tokens in circulation
  4. Receive and retire tokens
  5. Embed and verify metadata into the token's flavor.

See #94 for more considerations w.r.t. statefulness and FFI.

ZkVM: inconsistent terminology around Expressions/Variables

@decentralisedkev pointed this out in a private conversation:

Neg instruction states that:

2.If the expression is a detached variable, attaches it to the constraint system.

Does this conflict with the part in Attached and Detached Variables, which states:

When an expression is formed using detached variables, all of them transition to an attached state?

If it does then, I may need to edit the part on Expressions which states that:

Expression is a linear combination of attached variables with cleartext scalar weights.?

Sources: neg, Attached and detached variables, Expression.

The short answer is: "Expression" is accidentally used both as an object representing a linear combination of low-level and high-level variables, as well as a "supertype" of a Variable (since any variable can be turned into a linear combination of itself at no cost), which allows using variables where expressions are expected w/o explicit conversion.

How do we make this more clear?

Option 1: explicit name for the supertype

Rewrite all the parts of the spec where Expression | Variable is expected in terms of this supertype, and reflect this in implementation with explicit conversion to expression with attaching the vars as necessary.

Option 2: explicit conversion instruction for Variable->Expression

Instruction attach (or expr) would turn the Variable into Expression and attach it into R1CS. The spec can be made simpler, and the user is more explicitly aware of the variable being attached. But we expose this extra instruction which seems like a bit of redundancy. But maybe things would be overall more clear and less footgunny.

PR: #84

Else

Other ideas?

zkvm: factor out all assignments/witness conversion out of the shared path

Problem

VM has two parts: verification and proving. They have common data types (Data, Value etc) and a shared stack machine implementation (vm::VM).

The verification part is much smaller and more critical: it cannot arbitrarily change w/o breaking compatibility with already deployed software that verifies transactions.

The proving part is much bigger and less critical: its API and capabilities can change without breaking compatibility with the entire network, as long as the produced transaction is compatible.

Goal

To keep the verification part easier to audit, we need to minimize amount of proving code that is entangled with it.

Suggestion

  1. Factor out all witness types from types.rs into witness_types.rs, so that types.rs has minimal amount of mention of the witness types (at most, as witness: Option<Box<SomeWitness>> references).
  2. All the methods for witnesses/assignments conversion should be moved into witness_types.rs.
  3. All the glue code that handles witnesses/assignments in vm::VM should be factored out in methods to minimize amount of code related to glueing witnesses in the VM stack machine code.
  4. Move all witness-/assignment-/proving-specific errors from errors.rs into prover_errors.rs. And nest them into VMError under a single variant VMError::ProverError(ProverError::WitnessIsNotWhatItShouldBe).

slidechain: possible flakiness in post-pegout tx

The post-pegout TxVM transaction retires exported funds after a successful peg-out on the Stellar side. (Alternatively, it repays the TxVM funds to the exporter if pegging out encounters a permanent error.)

This works by invoking the second phase of a smart contract in which the TxVM funds were locked during export. Invoking the second phase means constructing the contract in the exact form it had at the end of its first phase, which ended with output. Reconstructing the contract happens here.

A possible problem here is that the refdata value on the contract's stack is computed via a call to json.Marshal (here).

This is probably fine. On the other hand, the encoding/json package does not make a guarantee about always producing the bytewise identical output for the same input. If a future version of encoding/json produced an equivalent JSON object encoded slightly differently, it could lead to a loss-of-funds scenario.

A safer approach would be to store the JSON string from the contract's phase 1 in the database, then recover and double-check it during phase 2, instead of recomputing it.

zkvm: deny missing docs

  1. Add #![deny(missing_docs)] to lib.rs.
  2. Add documentation lines to all public types and methods until compiler is happy.

Example: #115

zkvm end-to-end unit tests

As we are approaching complete Prover/Verifier set of APIs (cf. #72), here are the unit tests that we want to make for the low-level API (#61):

  1. Issue $10 (with nonce) into a pubkey.
  2. Spend two utxos with $6+$4 into $9 and $1 outputs.
  3. Spend 2 utxos into 1 output.
  4. Spend 1 utxo into 2 outputs.
  5. Spend 1 utxo into 1 output.
  6. Issue (w/o nonce) $4 while spending $6 from a utxo into $9 and $1.
  7. Have variants of these tests for inconsistent amounts - these should fail at verification.

zkvm: boolean `not` instruction

There's a cool trick to implement logical negation described in Setty, Vu, Panpalia, Braun, Ali, Blumberg, Walfish (2012) (appendix D.1), later implemented in libsnark. It lets us use just two multipliers:

x - the input secret scalar mod |G|.
y - the output of NOT(x): if x == 0 then 1 else 0.
w - additional free variable chosen by the prover.

Statements in R1CS:

x·y = 0
x·w = 1-y
  • The first statement implies that y == 0 when x != 0.
  • The second statement implies that y == 1 when x == 0.
  • To satisfy both statements, the prover must choose w to be x^-1 mod |G| if x != 0.
  • If x == 0 the w could be set to an arbitrary value.

Spec draft

Stack diagram for the not instruction:

constr1 notconstr2

  1. Allocate two multipliers.
  2. Constrain the first mult's left wire to the input constraint (x).
  3. Constrain the second mult's left wire to first mult's left wire (i.e. x == x on both multipliers).
  4. Assign the second mult's right wire (w) to the inverse of input constraint x or zero if it's zero. No constraints needed for w.
  5. Constrain the first mult's output wire to 0.
  6. Constrain the second mult's output wire to 1-y, where y is the first mult's right wire.
  7. Wrap the first mult's right wire (y) in a Constraint constr2 and push it to the stack.

image

(4 constraints)

Implementation notes

  1. Add Constraint::Not(Box<Constraint>) variant to existing ::Eq/Or/And.
  2. Note that truth is represented by equations stuff == 0, therefore, the truth of NOT is represented by y == 0 (which means that the corresponding input to the NOT must satisfy stuff ≠ 0).

Alternative

@cathieyun made a good point that if we simply need to check that something is not zero, we could use a single multiplier using statement x·w == 1, where w must be chosen to be x^-1 mod |G|.

This could be used with a hypothetical instruction notverify that inverses the composition of boolean functions (And/Or). Unfortunately, this does not permit nice compositions like not(x) and y, not(x) or y. Bringing not outside in these would bring back not to the other term.

2-multiplier not instruction seems to be of a minor overhead and is easily composable with the rest of boolean operations, so probably the tradeoff is worth it.

ZkVM: disallow dropping vars/expressions/constraints

Problem

Some constraint instructions have side effects in the R1CS state, even if the primary use is to construct a transient object before adding it with verify:

  1. mul internally allocates a multiplier and adds input expressions as constraints on the input wires into r1cs, returning a variable for the output wire.
  2. neg/add attach the potentially detached variable, allocating a slot for it in the r1cs.

This means, that dropping an expression may seem like having no effect on the proof, while in some cases it actually will, and it would be not immediately easy to see w/o tracing the execution path.

Suggestion

The simpler approach is to prohibit dropping variables, expressions and constraints. We should see if there are some legitimate cases for that.

Gotchas

Variables are probably fine being dropped: when they are attached to the constraint system, they are converted to expressions, and expressions are not droppable per this proposal.

Someone may think that if constraint is not droppable, it can be used as a "deferred predicate" — the contract may drop it on the stack for the user to satisfy it. But that's not safe at all, since the user can combine such constraint with OR(_, true) and then add the resulting constraint to R1CS.

Compatibility with extensions

One non-legitimate case is with ext that is typically used as "verify stuff, then drop arguments" (cf. CHECKLOCKTIMEVERIFY in Bitcoin). This is not a reason to allow dropping variables and constraints because meaningful operations on them change the r1cs, and that's not a no-op w.r.t forward compatibility. So ext cannot be meaningfully used with constraints, only with plain data, like in something like <preimage> <image> verifysha256 drop.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.