[][src]Crate private_currency

Privacy-focused Exonum service. The service hides the amounts being transferred among registered accounts (but not the identities of transacting accounts).

Implementation details

The service uses pure Rust implementation for Bulletproofs, a technique allowing to prove a range of a value hidden with the help of the Pedersen commitment scheme. Commitments are used instead of values both in account details and in transfer transactions.

Accounts

The service uses account-based scheme similar to one used for ERC-20 tokens in Ethereum; it is also used in simpler demo cryptocurrency services for Exonum. Unlike other demos, each wallet contains a commitment to the current balance Comm(bal; r) instead of its plaintext value bal. Only the owner of the account knows the opening to this commitment.

Transfers

Each transfer transaction contains a commitment to the transferred amount C_a = Comm(a; r). It is supplied with two range proofs:

The first proof is stateless, i.e., can be verified without consulting the blockchain state. In order to verify the second proof, it’s necessary to know the commitment C_bal to the sender’s current balance (which is stored in her wallet info). The proof is equivalent to proving C_bal - C_a opens to a value in the allowed range.

Transfer acceptance

A natural question is how the receiver of the payment finds out about its amount a; by design, it is impossible using only blockchain information. We solve this by asymmetrically encrypting the opening to C_a – i.e., pair (a, r) – with the help of box routine from libsodium, so it can only be decrypted by the receiver and sender of the transfer. For simplicity, we convert Ed25519 keys used to sign transactions to Curve25519 keys required for box; i.e., accounts are identified by a single Ed25519 public key.

A sender may maliciously encrypt garbage. Thus, we give the receiver a certain amount of time after the transfer transaction is committed, to verify that she can decrypt it. To signal successful verification, the receiver creates and sends a separate acceptance transaction referencing the transfer.

The sender’s balance decreases immediately after the transfer transaction is committed (recall that it is stored as a commitment, so we perform arithmetic on commitments rather than plaintext values). The receiver’s balance is not changed immediately; it is only increased (again, using commitment arithmetic) only after her appropriate acceptance transaction is committed.

To prevent deadlocks, each transfer transaction specifies the timelock parameter (in relative blockchain height, a la Bitcoin’s CSV opcode). If this timelock expires and the receiver of the transfer still hasn’t accepted it, the transfer is automatically refunded to the sender.

Referencing past wallet states

The scheme described above is almost practical, except for one thing: the sender might not now her balance precisely at the moment of transfer! Indeed, it might happen that the sender’s stray accept transaction or a refund are processed just before the sender’s transfer (but after the transfer has been created, signed and sent to the network). Hence, if we simply retrieve the sender’s balance from the blockchain state during transaction execution, there is a good chance it will differ from the one the sender had in mind when creating the sufficient balance proof.

In order to alleviate this problem, we allow the sender to specify what she thinks is the length of her wallet history history_len. The proof of sufficient balance is then checked against the balance commitment at this point in history. For this scheme to be safe, we demand that history_len - 1 >= last_send_index, where last_send_index is the index of the latest outgoing transfer in the sender’s history (we track last_send_index directly in the sender’s account details). If this inequality holds, it’s safe to process the transfer; we know for sure that since last_send_index the sender’s balance can only increase (via incoming transfers and/or refunds). Thus, if we subtract the transfer amount from the sender’s current balance, we still end up with non-negative balance.

Limitations

Even with heuristics described above, the scheme is limiting: before making a transfer, the sender needs to know that there are no other unconfirmed outgoing transfers. This problem could be solved with auto-increment counters a la Ethereum, or other means to order transactions originating from the same user. This is outside the scope of this PoC.

Re-exports

pub use api::Api;
pub use storage::Schema;
pub use storage::Wallet;
pub use transactions::CryptoTransactions as Transactions;

Modules

api

HTTP API for the service.

crypto

Cryptographic primitives used in the service.

storage

Storage logic for the service.

transactions

Transaction logic of the service.

Structs

Config

Service configuration.

Debugger

Debugger provides ability to connect to the service and retrieve information useful for debugging.

DebuggerOptions

Debugger options.

EncryptedData

Encrypted information embedded into transfers.

SecretState

Secret state of an account owner.

Service

Privacy-preserving cryptocurrency service.

VerifiedTransfer

Information about an incoming transfer successfully verified w.r.t. the SecretState of the receiver’s wallet.

Enums

DebugEvent

Event sent to the debugger.

Constants

CONFIG

Service configuration.

SERVICE_ID

Service identifier.

SERVICE_NAME

Human-readable service name.