Skip to content

Commit

Permalink
WIP scripting
Browse files Browse the repository at this point in the history
  • Loading branch information
tmpolaczyk committed Aug 10, 2022
1 parent 5e7a8df commit 6e95068
Showing 1 changed file with 194 additions and 0 deletions.
194 changes: 194 additions & 0 deletions wip-scripting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<pre>
WIP: WIP-scripting
Layer: Consensus (hard fork)
Title: Add scripting to Witnet
Authors: Tomasz Polaczyk <[email protected]>
Discussions-To: `#dev-general` channel on Witnet Community's Discord server
Status: Draft
Type: Standards Track
Created: 2022-08-09
License: BSD-2-Clause
</pre>


## Abstract

Add scripts to value transfer transactions to allow spending UTXOs with complex conditions.

## Motivation and rationale

Support the following use cases:

* Multisig
* Atomic swap

#### Multisig


#### Atomic swap



## Specification

Scripting language, forth stack machine.

Stack, dont mention alt stack because it is not used.

When validating a `ValueTransferTransaction`, for each input, if that input has a `redeem_script`, do a script validation. Otherwise, if the input does not have a `redeem_script`, do a normal signature validation. Transactions can combine both script inputs and normal inputs.

Script validation is described in the "Script execution" section. If the script validation is successful, the input can be spent. In case of error, the transaction is marked as invalid and cannot be included in the block.

#### Types

Unlike in Bitcoin, in Witnet scripts are type-safe. This means it is not possible to apply an operator to a value of a type that is not supported by that operator, any attempt to do so will result in an error and the script execution will halt.

There are 3 types: `Boolean`, `Integer`, and `Bytes`. The `Bytes` type is used to encode addresses, signatures, and hashes.

```rust
pub enum MyValue {
/// A binary value: either `true` or `false`.
Boolean(bool),
/// A signed integer value.
Integer(i128),
/// Bytes.
Bytes(Vec<u8>),
}
```

TODO: signatures are converted to bytes using protobuf to encode a KeyedSignature. We should be able to remove the protobuf step and encode it as bytes somehow.

#### Operators

```rust
pub enum MyOperator {
/// Pop two elements from the stack, push boolean indicating whether they are equal.
Equal,
/// Pop bytes from the stack and apply SHA-256 truncated to 160 bits. This is the hash used in Witnet to calculate a PublicKeyHash from a PublicKey.
Hash160,
/// Pop bytes from the stack and apply SHA-256.
Sha256,
/// Pop PublicKeyHash and Signature from the stack, push boolean indicating whether the signature is valid.
CheckSig,
/// Pop integer "n", n PublicKeyHashes, integer "m" and m Signatures. Push boolean indicating whether the signatures are valid.
CheckMultiSig,
/// Pop integer "timelock" from the stack, push boolean indicating whether the block timestamp is greater than the timelock.
CheckTimeLock,
/// Pop element from the stack and stop script execution if that element is not "true".
Verify,
/// Pop boolean from the stack and conditionally execute the next If block.
If,
/// Flip execution condition inside an If block.
Else,
/// Mark end of If block.
EndIf,
}
```

#### Script execution

##### Locking script

The first verification when running a script is to check if the script address (also known as locking script, `redeem_script_hash`) matches with the provided `redeem_script`. That check is performed as `hash160(redeem_script_bytes) == redeem_script_hash`:

```rust
// Check locking script
let locking_script = &[
// Push redeem script as first argument
Item::Value(MyValue::Bytes(redeem_bytes.to_vec())),
// Compare hash of redeem script with value of "locking_bytes"
Item::Operator(MyOperator::Hash160),
Item::Value(MyValue::Bytes(locking_bytes.to_vec())),
Item::Operator(MyOperator::Equal),
];
```

If the result of executing the locking script is an error, the script execution stops with that error. If the result of the locking script is anything other than a boolean `true`, the script execution stops with `false`. If after executing the last instruction the number of items left in the stack is greater than one, the script execution stops with `false`.

##### Redeem script

To execute the redeem script, it needs to be prepended with the witness script. The witness script must not contain any operators, it must only contain values.

Script execution is considered successful if the stack ends up containing exactly one item, a boolean "true".

If script execution stops because of an error, such as an operator receving an invalid argument, then the script execution is unsuccessful, regardless of the contents of the stack.


#### Networking changes

Some network messages need to be modified to support the new script transactions.

Scripts are encoded as bytes. The redeem script is a new optional field in the `Input` type:

```rust
pub struct Input {
pub output_pointer: OutputPointer,
pub redeem_script: Vec<u8>,
}
```

The redeem script is the part of the script that is used to create a script address.

Additional inputs such as the signature are encoded separately in the `witness` field of the `VTTransaction`.


```rust
message VTTransaction {
VTTransactionBody body = 1;
repeated bytes witness = 2;
}
```

The previous version of the protocol had a `signatures` field instead of a witness field. Depending on the context, the value of `witness[0]` is deserialized as a script, or as a `KeyedSignature` encoded with protobuf.

TODO: yes, we use protobuf inside protobuf, think of a better solution

If a transaction has both a `signatures` and a `witness` field, the `witness` field must be ignored. This is for backwards compatibility because old nodes will only see the `signatures` field.

#### Serialization

Scripts need to be encoded as bytes, because they need to be sent over the network, and also because of the locking script check: we need a way to calculate the script hash.

TODO: Currently we use JSON but that needs to change.

## Backwards compatibility

- Implementer: a client that implements or adopts the protocol improvements proposed in this document.
- Non-implementer: a client that fails to or refuses to implement the protocol improvements proposed in this document.


#### Consensus cheat sheet

Upon entry into force of the proposed improvements:

- Blocks and transactions that were considered valid by former versions of the protocol MUST continue to be considered valid.
- Blocks and transactions produced by non-implementers MUST be validated by implementers using the same rules as with those coming from implementers, that is, the new rules.
- Implementers MUST apply the proposed logic when evaluating transactions or computing their own data request eligibility.

As a result:

- Non-implementers MAY NOT get their blocks and transactions accepted by implementers.
- Implementers MAY NOT get their valid blocks accepted by non-implementers.
- Non-implementers MAY consolidate different blocks and superblocks than implementers.

Due to this last point, this MUST be considered a consensus-critical protocol improvement. An adoption plan MUST be proposed, setting an activation date that gives miners enough time in advance to upgrade their existing clients.


### Libraries, clients, and interfaces


## Reference Implementation

A reference implementation for the proposed protocol improvement can be found in the [witnet-rust] repository.


## Adoption Plan

An adoption plan will be proposed upon moving this document from the _Draft_ stage to the _Proposed_ stage.


## Acknowledgements

This proposal has been cooperatively discussed and devised by many individuals from the Witnet development community.

[witnet-rust]: https://github.com/witnet/witnet-rust/

0 comments on commit 6e95068

Please sign in to comment.