Giter Club home page Giter Club logo

fuel-specs's Introduction

Fuel Specifications

Fuel: A Secure Decentralized Generalized Massively Scalable Transaction Ledger

This repository specifies the Fuel protocol, including the Fuel Virtual Machine (short: FuelVM), a blazingly fast verifiable blockchain virtual machine.

mdbook

The Fuel Specifications book is built with mdbook.

Install mdbook and then open a new terminal session in order to run the subsequent commands

cargo install mdbook

To build book:

mdbook build

To serve locally:

mdbook serve

Contributing

Markdown files must conform to GitHub Flavored Markdown. Markdown must be formatted with:

fuel-specs's People

Contributors

adlerjohn avatar alicanc avatar anton-trunov avatar bitzoic avatar braqzen avatar bvrooman avatar dentosal avatar digorithm avatar dmihal avatar endophysics avatar iqdecay avatar mitchmindtree avatar mitchturner avatar mohammadfawaz avatar nfurfaro avatar paulrberg avatar pixelcircuits avatar quinnlee avatar rakita avatar sarahschwartz avatar sezna avatar shahankhatch avatar silentcicero avatar simonr0204 avatar vlopes11 avatar voxelot avatar wolflo avatar xgreenx avatar xunilrj 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

fuel-specs's Issues

Specify call frames

A call frame is pushed on the stack every time a new contract is called (not for intra-contract jumps). They defined a segmentation of memory access rights: only the current call frame and owned heap memory can be written to.

For the purpose of keeping things simple for a first version, heap memory writes can be restricted to only the current call frame with no way of passing this right to child call frames. Due to the immutable nature of stake elements, it should be possible to assign write permissions for child call frames to ranges on the stack.

Allow contracts to increase their own balance of their own token

Currently, TRANSFEROUT does a balance check. This means a contract that mints new tokens on Fuel can't ever turns its internal representation of tokens into a coin resource. An opcode should be added that can increase a contract's balance of its own token arbitrarily, e.g. MINT <amount>.

Opcode to clear memory range

While the MALLOC opcode can be used to reserve a memory range, it doesn't clear memory so as to support constant-cost reserving. Currently, clearing this would be done by clearing one word at a time in a loop, which is unnecessarily expensive for what will be a widely-used usecase.

A new opcode should be added that simply zeroes out a range of memory.

Support large code for a single contract

Currently we only support contracts with up to CONTRACT_MAX_SIZE bytes. We would like to support much larger contracts, potentially up to CONTRACT_MAX_SIZE * MAX_INPUTS bytes. This would require an analogue of the EVM's DELEGATECALL opcode (https://eips.ethereum.org/EIPS/eip-7).

Depending on the scheme chosen, this could be relatively simple, or relatively complex. Proposal:

Add a new opcode, LOADCODE, which loads the bytecode from a contract which is one of the inputs and places it in memory. This opcode is only valid in an internal context and if $ssp == $sp. In other words, nothing is in the stack area of the call frame. The last bytes of the call frame before the writable stack area is actually the program code, so LOADCODE would simply append another contract's code. To ensure potential future restrictions on ACE jumps will be smooth, the code length in the call frame should also be updated. In addition, $ssp and $sp should be updated.

Upsides:

  • Super simple since we don't need another context for delegatecall-style calls.

Downsides:

  • This requires loading code contiguously. So if you only need a tiny bit of code at the end of the large bytecode, you have to load the whole thing. This can be optimized by the compiler to not be a huge issue.
  • This doesn't support independent libraries, only large code for a single monolithic contract. I don't think this is too big a deal since libraries that are actually libraries (e.g. SafeMath in Solidity) are almost always pure, and our CALL opcode doesn't require memcopies to send or return data.
  • This doesn't support proxies. Proxies would be complicated to replicate in this scheme since loaded code would only work offset against the primary code's size. Alternative designs can be used however that replicate the functionality.

Optimize calldata requirements for static contracts

Currently, loading code from a contract requires declaring that contract as an input (and as an output). Explorer if we can reduce the data that needs to be declared in order to support this functionality.

Consideration: we want to guarantee that transactions across blocks can also be validated in parallel (modulo UTXO existence, which requires a sequential pass).

One possibility is for contracts to declare once, on contract creation, a static list of contract IDs that can be loaded. Then, LOADCODE would panic if the contract to load code from is not in both the input list and static list.

Pros: easy.
Cons: not very flexible.

Add error flag semantics

Certain operations should set an error flag:

  • divide by zero at runtime
  • failure to allocate memory on the heap
  • failure to allocate memory on the stack (either a call or a stack extend)
  • subtraction resulting in a negative number

Consider native re-entrancy guard

Re-entrancy has historically been a significant issue for smart contracts on Ethereum. We should consider a native re-entrancy guard that panics when a contract is called but already has a call frame on the stack. There are some issues with supporting this however: since we define call frames recursively, without an addition table of "which contract is currently on the stack," proving that a contract is on the stack would require going through every single call frame.

Two alternatives:

  1. Add additional metadata to each contract output, a one-bit flag of whether it is on the stack or not. Then, every time a contract is called, it can check this flag. Problems:
    • This is at the granularity of contracts, not functions. This coarse granularity may not be good enough, but introduce extra complexity and overhead to the VM.
  2. Make the high-level language compiler check for the check-effects-interaction pattern and enforce it with an (optional, but on-by-default) compiler flag. This solves the issue entirely with no additional complexity or cost in the VM or the generated bytecode.

My recommendation is simply to do (2).

Receipts, return values, and events

This issue is for tracking how to handle three related concepts: receipts (did this transaction succeed or fail, and why), return values (returning data from the VM up all the way to an external process), and events (logs that are fired by contracts on state changes, e.g. whenever a token is transferred).

Receipts

This can be accomplished entirely outside of the VM, with the block proposer appending some metadata to each transaction indicating whether its script succeeded or failed, and the failure reason based on some standard enumeration or reasons (e.g. out of gas, illegal math op, forbidden write, etc.). If the receipt doesn't match up, it can be proven fraudulent by simply running the last instruction of the trace.

Return Values

Return values from the VM up to an external process is of dubious value. Volatile in-memory data should probably never be returned in this way, and storage slots can be returned by simply querying the underlying KV store. The additional complexity of supporting variable-length return data might not be worth it.

Events

Events improve application developer ergonomics by having a commitment to all fired events in a block in the block header, filterable by topics. However, events are hugely burdensome to nodes and abused to no end for cheap indexed permanent storage. An alternative to native events would be to allow local simulation of individual transaction execution (which the VM design is intended to support) and "watching" particular storage slot changes.

Another alternative would be having a LOG opcode that does nothing in the VM itself other than accept parameters, and whole execution can be logged client-side. Whether this is then committed to in the block is outside the scope of the VM.

Transaction to create contracts should commit to bytecode

Currently, transaction that create contracts include the contract bytecode as non-witness data. However, this means computing the transaction ID of those contracts involves revealing the entire bytecode. Instead, these transactions should only commit to the bytecode's root, and leave the bytecode as witness data.

Add change output type

Any unused gas can be refunded to the sender. A new output type for this "change" amount should be added, which defines a recipient but not an amount, with the amount filled in by the block proposer at inclusion time.

Rename repo to fuel-specs

I think it's time we make this repo the home of the Fuel specifications more broadly.

Any reasons why not to change it at this stage?

Support token deposits and withdrawals

Related: #101.

Currently, only deposits and withdrawals of a single coin type are supported (along with transfers). We would like to support multiple tokens, but this introduces a number of complexities. Specifically, now there are non-native tokens (i.e. tokens implemented as a smart contract on Fuel) that can't be withdrawn and Fuel-native tokens (i.e. tokens implemented as a smart contract on Ethereum and deposited to Fuel) that can be withdrawn but are individual UTXOs.

Proposal 1

I think the withdrawal side can be resolved with a new opcode to "pop out" tokens from inside a contract into a native colored UTXO, e.g. TRANSFERTOKEN <recipient> <output index> <amount>, which sets an output of type OutputType.Variable to the color of the currently-executing contract.

The depositing side is more complex, since "popping in" tokens into a contract from a native colored UTXO is non-trivial. Rather than handle the generic case, the easier approach here might simply be to have a new transaction type that only pops in a colored UTXO to a contract. This could be a 3-input, 3-output transaction (one for each of: fee payment, colored UTXO, contract to deposit to on the input side, and one for each of: change, colored UTXO in case of reversion, contract on the output side).

The last piece of the puzzle is making sure no inflation happens for regular transactions. To that end, for each color, the sum of OutputType.Coin outputs must be at most the sum of the InputType.Coin inputs (i.e. no variability possible). Variable outputs can be set by the VM with the aforementioned opcode.

One issue with this approach is that contract IDs on Fuel don't match contract addresses on Ethereum. This can be resolved by identifying deposits by both an Ethereum contract address and Fuel contract ID, and likewise separating token balances.

Proposal 2

This is @SilentCicero's suggestion.

Instead of having native colored coins, we can debit tokens that are deposited directly to a token contract on Fuel. For withdrawing, we can simply record a burn event from the contract. This would require event logs to be Merkleized however, so as to be able to prove messages.

Since Fuel contract IDs and Ethereum contract addresses won't match (they won't have the same bytecode for starters), we need a way of provably mapping between the two. This can be done by taking the hash of the contract address as the Fuel contract ID. Then, when the deposit of a new token type is seen, a contract is deployed on Fuel with the appropriate ID. This would require a new transaction type to make things clean and easy.

To do this right, we would need one reference implementation of a fungible token and one reference implementation of a non-fungible token. If users want the tokens to have different functionality once on Fuel, they can implement wrapper contracts.

Forbid conditional contract creation

Unlike Ethereum, which only allows programmability via deployed contracts, the FuelVM supports programmability via both predicates and scripts. As such, there is less value---but much more complexity---in allowing contracts to deploy new contracts. Conditional contract outputs that may or may not exist greatly increase complexity. Instead, we can forbid generic programmatic contract creation.

Proposal

We introduce a new transaction type, which can create contracts but not interact with them. We remove the CREATE opcode entirely. To create a contract, the transaction provides the bytecode of the contract.

Note that since there is no dependence on state, and inputs are known at compile time (but parametrized), there is no need for the concept of constructor code (also known as initcode in Ethereum). The transaction simply provides the bytecode and nonce and a contract ID is computed from that.

Consider reduced-width arithmetic opcodes

Currently, the arithmetic opcodes (ADD SUB, etc.) only operate on 64 bits. If the user is working with two 64-bit operands, then this is fine, and the $of and $err registers (along with overflow and error flags) work as intended. However, if users are working with reduced bits (e.g. with u8s), then the $of register is useless, and the compiler will have to generate code to check for overflow. This can be remediated with additional opcodes that work with reduced bit widths. As an alternative to completely new opcodes, the existing opcodes can accept an additional 6-bit immediate parameter for bit width, as a power of 2 starting at 8 and up to 64).

Feedback

My big broad item of feedback is that there doesn't seem to be a clear boundary for the VM interface. Things like calling conventions, how to structure data on the stack, etc, should be up to the bytecode, but the VM effectively enforces one by doing things like reading and writing memory in a specific format directly. That also adds a lot of complexity around having to write-protect different areas of memory for different callers etc.

I think it'd be much simpler to follow the EVM's approach here and instantiate a new VM for each contract that's called (possibly excepting a delegatecall-type operation); that way a program can always read and write anything inside its VM, and you don't have to enforce access checks on every memory access, which will get expensive.

Detailed feedback on the current VM model:

  • The instruction encoding is straightforward and should be fairly easy to decode. Naive implementations are likely to be slower than a stack-based VM where most opcodes have no operands, though.
  • There should probably be a register containing 1, since that value gets used about as much as 0.
  • Stack, frame, and heap pointers can be part of the calling-convention. Since those registers aren't privileged in the VM, there's no reason to specify that they have to be at specific addresses.
  • Likewise, there doesn't seem to be a compelling reason for opcodes to grow or shrink the stack or heap, when those operations can be accomplished with ADD/SUB.
  • You may want to reconsider offering non-immediate JUMP opcodes; static analysis will be much easier without them, and there's relatively few use-cases for them in most programs.
  • Why not make memory page-based and with a much larger address space? That would introduce the need to bill for memory, but would also make it much easier to manage. You could also memory-map call and return data efficiently, for instance.
  • Likewise, why not make state page-based? Having state values only 256 bits long is very inefficient, and results in multiple-hundreds-of-percent storage overhead for data. Pages on the order of 1k-256k are probably closer to optimal. With a large memory address space, you could charge to map each page into memory and to write it out if modified at the end.

Finally, one really big item of feedback: Why make this a low-level VM at all? Why not take a page from the JVM and the CLR, and take advantage of the fact that this will be run in software, not implemented in hardware? You don't need registers or stack slots that are machine words; you can make them take objects, with a set of native data types, and have opcodes like 'create array', 'set map element', etc. In this model you don't need memory because you can allocate arbitrary buffers/objects/maps etc as needed and store them as primitives that opcodes operate on. By enabling higher-level primitives, this kind of VM has the potential to be more efficient, without being significantly more complex - because implementers can use their language's features like polymorphism, typecasts, and garbage collection to do the work for them.

Consider: Sighash modes

In Bitcoin, a signature specifies whether it commits to a complete transaction or some lesser portion of the transaction. This allows inputs to be signed by multiple parties without knowing full transaction details.

In essence, a sighash mode is a way of signing a partially-specified transaction and placing specific constraints on the final version. Witnesses are invalid if these constraints are not met

Bitcoin offers 4 useful modes:

  • SIGHASH_ALL - Sign all inputs and all outputs
  • SIGHASH_SINGLE - Sign all inputs and the output at that input's index
  • SIGHASH_ALL_ANYONECANPAY - Sign 1 input and all outputs
  • SIGHASH_SINGLE_ANYONECANPAY - Sign 1 input and 1 output.output Require that they be at the same index in the completed transation

Fuel's signature (right now) is sighash all. It commits to the exact transaction details and allows no modification or partial specification

stupid modes nobody should use:

  • SIGHASH_NONE - sign all inputs and no output
  • SIGHASH_NONE_ANYONECANPAY - sign 1 input and no output

Sighash None is "blank check mode" where you give your money away to whatever miner wants to take it.

Examples

  • Alice and Bob want to co-fund a transaction. They know the outputs, but don't know eachothers' UTXOs. They can each construct a partial tx with their input and all outputs. They each sign their input(s) sighash_all_anyonecanpay. The complete transaction is made by joining the input and witness vectors
  • Summa auctions used sighash single anyonecanpay to create an insufficently funded tx to self. This allows any buyer to fund the transaction with confidence that the seller cannot steal their funds
  • A multisig construction can use SIGHASH_SINGLE to authorize specific UTXOs to be spent, and guarantee that a specific payment is made, without asserting what the fee will be (until the last signer finalizes the transaction). CAre muse be taken not to over-fund to the point where deconstructing the tx becomes profitable

Some weaknesses

  • All/One is not very granular. We'd love more
  • Single's reliance on input/output vector positioning is annoying and causes issues.
  • Would be cool to have sighash_noinput to allow complex channel protocols (signatures that delegate ALL UTXO rights)

notes

Handshake added more modes. Ask @tynes about it

Opcode to transfer

An opcode like send <output_index> <to> <amount>. This would revert if the output is already set, or if the amount is 0. Unlike the accounts data model, with UTXOs transfers involve actually creating a new object instead of updating an existing object.

Specify access lists

Specify:

  1. What are the access lists for each tx
  2. Checks for existence of stuff in the access lists

Add additional arithemtic opcodes that are easy to verify

Inspired by this, several opcodes can be added that are easy to verify but potentially expensive to compute in the EVM.

Some examples:

  1. Integer square root. Can be verified by taking the square of the result and the square of the result plus 1. This can actually be extended to integers beyond 2 as well (e.g. cube root and so on).
  2. Integer logarithm of any base. Can be verified by computing two exponents.

Memory Model

We should have a clear diagram around our memory model and how our heap / stack system works.

Add safe math flag

Add a toggle-able flag to turn safe math operations on or off. If on, math operations that result in an error should immediately halt/revert.

Bound the cost of all operations

All operations must have a bounded cost, or more specifically access a bounded number of memory locations. E.g. memory copy should only be able to copy a small range of memory so as to be verifiable.

Make block proposer malleate transaction data instead of witness data

Currently, outputs point to witness data for things that aren't known until a transaction is confirmed. However, this is a Bad Idea since the entirety of the transaction is placed in memory on VM initialization, including witness data. Therefore how the transaction executes before and after it's confirmed can vary, introducing a DoS vector against block proposers.

To fix this, those fields should instead be actual values (not pointers to witnesses) and filled in by the block proposer. During transaction execution and during the signing process/computing the transaction hash, they can be set to zero (0) so that everything is deterministic.

Add coin forwarding/transferring to called contracts

The CALL opcode should be able to forward an amount of coins, and the RETURN opcode should be able to return an amount of coins at most equal to the amount received. Unreturned balance should persist (unless a global revert).

Handle distribution of fees

Currently transaction fees simply disappear. We would like to support distributing transaction fees to the block proposer. There are a few alternatives:

  1. Add an implicit UTXO for each transaction that is the transaction fee for that transaction. Cumbersome to collect transaction fees and an additional case for UTXO existence is needed.
  2. Add an implicit UTXO for each block that is the transaction fees for that block. Not impossible, but it add an extra case when checking if a UTXO exists.
  3. Withdraw transaction fees directly to Ethereum when finalizing a block.

I'm more inclined towards (3) or (2), since (1) makes collecting fees unnecessarily expensive. Between those two options, I prefer (2) since it's more self-contained.

Add global gas counter

Currently there's no way to verify how much gas a transaction spent, since $gas is local. There should be a global gas counter. This would also ease the addition of local reverts if they are deemed necessary.

Compressed Address Registries

Motivation

For security, we are enforcing a 32 byte address system. While this increases security, this sized address and even the Ethereum 20 byte address format is quite large for normal day-to-day usage. With the cost of bytes increasing in price on Ethereum, every bit of savings can increase overall transaction efficiency.

Proposal

A 6 byte address ID system, whereby all addresses used in Fuel must be registered either in the block they are used, or a previous block.

Schema

The ID specifier is made up of two parts concatenated together, a (1) 4 byte block number and a (2) two byte address registration index.

Each block header will commit to a (1) Merkle root of all addresses registered, (2) an array of addresses being registered (i.e. from 0 - N), and (3) a commitment hash of the addresses.

Breakdown:

32 bits [Block Number]
16 bits [Index/Bitmap ID] 

Block Header Example

struct BlockHeader {
     ... block header data ...
     bytes32[] addresses;
     bytes32 address_commitment;
     bytes32 address_root;
}

Example

// This would reduce to address number 5 registered in block number 3.
0x000000030005

Proofing

When doing any kind of address based fraud or transaction proofing, the addresses used in the proof (e.g. for signature or witness verification), will require the addition of a Merkle proof and block block header.

This means, for each address in question that is required to provide proofs, an SSLOAD and Merkle proof reduction is required. However, this is not a horrible expense as it is only required for fraud proofing, and should only be required for witness checking and witness verification.

Invalid Merkle Root Submission

If an invalid Merkle root is submitted for the BlockHeader, this can be easily checked by building a merkle tree out of the address for that block and checking the root against the address root.

Implementation Notes

  • Each block could register their Merkle root in a separate SSTORE mapping on Ethereum instead of just the block commitments, which means that when doing the address proofing, you wont need to provide the full block header per address, just the Merkle proof (which would reduce to) the registered Merkle root.

Example:

// block number => address merkle root
mapping(uint32 => bytes32) public address_roots;
  • Alternatively, block headers can be provided for each address when in use in proofing.

Example:

struct AddressPoof {
    bytes32 address;
    bytes32[] merkleProof;
    BlockHeader header;
}

Changes to Transaction Format

This would mean that instead of using 32 byte addresses in the transaction format, we would only need to include the 6 byte identifier. This would alter the transaction specification.

To keep things simple, we would want to always use the 6 byte constant size.

Unconsidered Consequences

Not all transaction format related edge cases have been discussed or research yet. Opening the floor to further discussion.

Add opcode to extend call frame

Call frames need to be extendable to account for intra-contract jumps, that push to the stack. An opcode that can extend the current call frame is required.

Add withdrawal output type

Motivation:

I didn't see any deposit and withdraw types in our tx format as of today.

With v1.x we have clearly specified withdraw and deposit types. I recommend adding these for our tx format, keeping them the same as before unless further changes are required.

Posterity:

I would keep the same properties for deposit, namely the block number, amount, token and receiver.

Note:

Funnels can come later if choose to continue that pattern.

Support non-fungible tokens

While non-fungible tokens (NFTs) can be supported by creating a new fungible contract with a single unit, this would be an extremely wasteful workaround. Should support NFTs as native resources.

Rather than having an amount like fungible resources, non-fungible resources should have some sort of identifier nonce, which when combined with the NFT contract ID indexes a unique NFT. In other words: nft id = hash(nft contract id ++ nonce). This prevents resource ID collisions in the balance tree.

Add gas costs

Opcodes currently don't have gas costs. These need to be specified.

Support withdrawal of resources minted on Fuel

Motivation:

We would ideally like contracts to be able to create new UTXO colors (token IDs) within a Fuel contract without having to deploy the contract on Ethereum L1, while also having some pathway for Ethereum contract creation, in the event someone wants to withdraw these tokens.

Proposal

Ideally, even if a contract has not been created yet on Ethereum for the color, a contract can specify/mint a new UTXO type (akin to Move resources) such that contracts can now create UTXOs in addition to just intaking and outputting them.

In addition to this, a 32 byte contract bytecode hash should be specified and registered in state, in the event someone wants to withdraw these this new token color, the user would be required to create the exact contract specified in the preimage of the bytecode hash before withdrawing (simple logic that can be added to the withdraw function).

This way, in the event of a withdraw, a contract can be created on Ethereum to exact specification, and then the withdraw can occur.

Status

This idea is at the early stages, still considering all the UTXO edge cases.

Allow predicates to load code

Inputs define an access list, and if any contract in the access list is not in the state, the transaction is invalid. This level of indirection can actually be used to allow predicates to load code from either an input contract, or a static access list (see #115), which could decrease costs for often-used bytecode templates (e.g. a multisig).

This does however introduce an additional complexity: predicates are currently metered by bytes, since without loops this is a reasonable mechanism for DoS resistance. If we allow predicates to load code, we need to have gas metering which is non-trivial.

Opcode for unconditional transfer to contract

Currently, coins can be transferred via TRANSFER, which creates a new UTXO, or CALL, which calls a contract and forwards some coins. We would like to have an opcode that unconditionally transfers coins to a contract without calling it.

Transaction signature aggregation

Motivation:

Presently, Fuel uses secp256k1 elliptic signatures for witness verification in Fuel. This requires including 65 byte signatures for each transaction. Ethereum is currently very expensive, and every byte ends up costing users greatly.

Fuel would like to use BLS12-381 when ready with our smart-contract system, this would allow for the removal of a vast majority of signature data in Fuel blocks.

Problem Point:

One issue with using aggregate signatures in the current Fuel architecture is that Fuel uses a system of both block producer included metadata (block number, transaction index, output index, 5-7 bytes) and UTXO hash data (32 bytes) for each input spent. To save data, we remove the UTXO hash data as it is implied by verification reduction from the metadata.

This makes verifying and determining has the witness signed over the provided data straight forward.

  1. Bring each full transaction proof for each input
  2. Reduce the hash for that input provided
  3. Reduce signature over provided transaction data with hashes derived

When we introduce aggregate signatures, this becomes more problematic, because if you remove the transaction data and the signature data, you must provide all data and all public keys for signed over by the aggregate signature. This means bringing the transaction proofs for all inputs spent in all transactions signed over by that aggregate signature proof (potentially 32 * 8 transaction proofs to validate one signature in a single round fraud proof). This would be extremely costly to reduce in one round.

Put simply, you cannot use a single round fraud proof style game to validate signatures for BLS in Fuel.

Proposal:

We would like to use aggregate signatures like BLS12-381 to remove the signature data in Fuel and enforce signature correctness using a very simple interactive verification game, which both allows the removal of UTXO hashes from Fuel transactions and signature data on a per-transaction bases.

One aggregate signature will be provided for groupings of 32 or less transactions (less in the case a block has less than 32 transactions).

One Merkle root will be posted, with the leafs of that tree being the transactionId's for each of the transactions (note, this will be used for the IVG) and a hash of the list of public keys used for that transaction.

Interactive Verification Game:

If an aggregate signature is incorrect in a group:

If any of the committed UTXO data does not derive (invalid internal Merkle root):

  1. Player 1 (fraud prover) will begin the game, and ask Player 2 (block producer) to reveal any of the requested transaction index leafs within that group.
  2. Once that leaf is revealed, Player 1 can show that either the transactionId/public keys committed to in the Merkle root is either valid by single round fraud proving or fraudulent (i.e. not what the output specifies)
  3. This can be used to show a single transaction has not been appropriately witnessed, without having to provide all public keys and UTXO hashes per group on-chain.

If the committed data is now derivable or known and incorrect:

  1. All public keys, transactionId's and UTXO hashes used for that commitment/group are provided (verified by the Merkle root derivation)
  2. The aggregate signature is can be deemed valid or invalid to any of the public keys provided

Notes:

Currently, Ethereum only supports a form of BLS via the altbn_128 precompiles with less than 100 bits of security. Which we have deemed not secure enough for Fuel.

BLS12-381 is on the way with more than 100 bits of security, but will likely not be ready for use until end of year 2021 at best.

Allow contracts to reclaim UTXOs they own

Currently, TRANSFEROUT can be used to transfer coins to an output. However, if the recipient is a contract ID (which could happen due to user error, as wallets are not guaranteed to perform all the checks they should), the coins will be permanently inaccessible. We would like to allow contracts to reclaim the coins in a UTXO without a digital signature.

Unfortunately, this would require some major changes and special cases to handle spending coins without a digital signature. As a result, in the short term it is probably preferable to push wallets to actually do the proper checks.

Add static call for resource-only manipulation

Currently, doing logic on a resource requires transferring the resource to a contract with CALL (which locks the contract's state). However, we could also support resource-only manipulation via a STATICCALL opcode, which only loads bytecode but not state. Any state access (including CALLs) within a static call context causes a panic. This would allow some logic to accept and emit resources without touching any contract state.

Since static calls only read contract bytecode but not contract state, they don't need an input and output for the contract; instead, they can simply use an access list of contract IDs, which must exist for the transaction to be valid.

Fix color compression

#135 added a compressed transaction format, but left color IDs with a placeholder. Specifically, we need to align both an on-Fuel color (32 bytes, pointing to the TXO that generated the token contract), and an on-Ethereum address (20 bytes, from a token contract address).

Specify Deposit ID

Following up from #101, we need a specify a deposit ID format so that Fuel nodes can add deposit to their UTXO set.

The UTXO ID in Fuel v1 of deposits is the hash of the key, with deposits keyed with { owner, token, blockNumber } (ref). Multiple deposits to a single key are restricted to one per block (i.e a user can only deposit the same token in the same block once). This restriction is mostly for developer ergonomics, since an event will be emitted on every deposit, and if deposits could be updated, this logic would have to be handled by the node.

An alternative would be to add a nonce to the key, so as to lift this restriction. In addition, some magic bytes should be added as a simple domain separator.

Opcodes to access parts of the tx

The inputs, outputs, and witnesses of the tx should be accessible via opcodes. On top of this, opcodes should be able to parse any transaction in memory to do the same (to allow smart contracts to deal with metatransactions, etc. without having to write a transaction parser).

Add opcodes for memory convenience

There should be some convenience opcodes for memory operations, which while they could be implemented using registers, that would be messy and wasteful:

  • memory copy (copies from memory to memory)
  • memory equals (compares two equally-sized memory ranges)

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.