 # Vault Language Proposal

Vault Language Proposal

Author : Joonmo Yang<jmyang@codechain.io>

Created: 2019-10-02

# 1. Abstract

Writing a complex script for CodeChain Virtual Machine(CCVM) is difficult. Although it is powerful enough to express most of the common use cases for UTXOs, it’s too low-level for programmers to manage. Bitcoin had a similar problem for their script system, and Miniscript was proposed recently to address this situation. We propose Vault, a simple and human-readable script language inspired by Miniscript to provide a better experience for writing scripts for CodeChain VM.

# 2. Examples

#### 2.1. p2pk

``````pk(A)
``````

#### 2.2. p2pkh

``````pkh(H_A)
``````

#### 2.3. A or B

``````pk(A) || pk(B)
``````

#### 2.4. (A and B) can unlock, or H_C can burn before 100s

``````success = pk(A) && pk(B);
burn = pkh(C) && before(100s);
``````

#### 2.5. 2-of-3 multisig

``````pk(2, A, B, C)
``````

#### 2.6. 2 of H_A, B and 100s timelock

``````thresh(2, pkh(H_A), pk(B), after(100s))
``````

#### 2.7. Hash Time Locked Contract

``````pk(A) && after(1000blk) || pkh(B) && hash(X)
``````

# 3. Overview

## 3.1. Writing a Vault script

Top level structure:

``````success = expr ;
burn = expr ;
``````

If `success = expr ;` is omitted, `success = false;` is inserted as a default.

If `burn = expr ;` is omitted, `burn = false;` is inserted as a default.

If `burn = expr ;` is omitted, `success =` and the semicolon can be omitted from the success condition as well.

Base expressions:

• `pk(A)`: true if the unlock script provides a signature of public key `A`
• `pkh(H_A)`: true if the unlock script provides a signature and a public key of the public key hash `H_A`
• `after(time)`: true if the asset is unlocked after `time`. `time` can be specified in seconds (s) or a block number (blk)
• `before(time)` : true if the asset is unlocked after `time`. `time` can be specified in seconds (s) or a block number (blk)
• `hash(X)` : true if the unlock script provides a preimage of hash `X`

Complex expressions:

• `A && B` = `thresh(2, A, B)` : true if both A and B are true
• `A || B` = `thresh(1, A, B)` : true if either A or B is true
• `thresh(n, A, B, C, …)` : true if exactly `n`(>0) expressions in `A`, `B`, `C`, … are true

## 3.2. Locking an asset with Vault

A Vault script is compiled into two values: the lock script and the parameters. When creating an asset in CodeChain, the creator should attach the parameters and the hash of the compiled lock script to the transaction.

## 3.3. Unlocking an asset with Vault

When unlocking an asset locked with a Vault script, the required unlock script can be generated from the compiler. The compiler receives a Vault script, list of signatures and preimages, expected execution time, and a flag for whether to burn or unlock this asset. The unlock script will be generated only if the conditions specified by the Vault script can be satisfied with the given values and the given time. If there are multiple combinations of values that can satisfy the lock condition, the generated unlock script could be different depending on the implementation. With the generated unlock script, the asset owner can unlock and spend the asset by attaching it to a transaction.

# 3.4. Example Usage

The following is a example Javascript-like pseudocode that locks and unlocks an asset with the Vault script `pk(A) || after(100s)`. The details might be changed in the actual implementation.

``````// Creating an asset
const pubkey = "SOME_PUBLIC_KEY";
const timelock = 100;
const script = `pk(\${pubkey}) || after(\${timelock}s)`;

const [lockScript, parameters] = compileVault(script);
const lockScriptHash = blake160(lockScript);
const createTransaction = new TransferAssetTransaction({
...someOtherArguments1,
outputs: [{ lockScriptHash, parameters }]
);
``````

<Example pseudocode for creating an asset with Vault>

``````// Unlocking an asset
const pubkey = "SOME_PUBLIC_KEY";
const timelock = 100;
const script = `pk(\${pubkey}) || after(\${timelock}s)`;

const tag = createTag(ALL_OUTPUTS, ALL_INPUTS);
const signatures = { [pubkey]: [tag, "VALID_SIGNATURE"] };
const confirmTime = 120;
const [lockScript, unlockScript] = unlockVault(script, {
signatures,
time: confirmTime
});
const unlockTransaction = new TransferAssetTransaction({
...someOtherArguments2,
inputs: [{
utxo: createTransaction.output,
lockScript,
unlockScript
}]
});
``````

<Example pseudocode for unlocking an asset created with Vault>

# 4. Language Specification

## 4.1. Top level structure

A Vault script consists of two parts: the success condition and the burn condition. The order of the conditions does not matter. Examples of top-level structures are as follows:

Condition type Syntax Default
success `success = expr ;` `success = false;`
burn `burn = expr ;` `burn = false;`

If a condition is omitted, the default condition is automatically inserted. Furthermore, if the burn condition is omitted, the `success =` part and the semicolon can be omitted as well.

The following are examples of how the omitted conditions are treated:

Script Fully expanded
`success = pk(A);burn = after(100s);` `success = pk(A);burn = after(100s);`
`success = pk(A);` `success = pk(A);burn = false;`
`burn = after(100s);` `success = false;burn = after(100s);`
`pk(A)` `success = pk(A);burn = false;`

## 4.2. Types

Expressions accept one or more parameters, and each of the parameters must have the correct type that the expression expects. The type check is performed at compile-time and does not affect the run-time behavior of an expression. The following types are used in the expressions:

Name Description Syntax Example
`U64` 64bit unsigned integer `0 [1-9][0-9]*` `12345`
`H160` 160bit hexadecimal value `0x[0-9a-fA-F]{20}` `0x0123456789abcdef0123`
`H256` 256bit hexadecimal value `0x[0-9a-fA-F]{32}` `0x`(`0123` repeated 8 times)
`H512` 512bit hexadecimal value `0x[0-9a-fA-F]{64}` `0x`(`0123` repeated 16 times)
`Time` Length of time (`U64` value)`s`
(`U64` value)`blk`
`300s`
`128blk`

## 4.3. Expressions

### 4.3.1. Base expressions

Expression Parameter Type Description
`true` Always true
`false` Always false
`pk(pub)` `pub: H512` True if a signature of public key `pub` is provided.
`pk(m, pub1, …, pubn)` `m: U64`
`pub: H512`
True if `m` signatures of `pub1`, …, `pubn` are provided.
`pkh(hash)` `hash: H160` True if a public key that hashes to `hash` and the signature of that public key is provided. Blake160 is used as the hash function.
`after(time)` `time: Time` True if it’s executed after `time`.
`before(time)` `time: Time` True if it’s executed before `time`.
`blake256(hash)` `hash: H256` True if the blake256 preimage of `hash` is provided.
`sha256(hash)` `hash: H256` True if the sha256 preimage of `hash` is provided.
`ripemd160(hash)` `hash: H160` True if the ripemd160 preimage for `hash` is provided.
`keccak256(hash)` `hash: H256` True if the keccak256 preimage for `hash` is provided.
`blake160(hash)` `hash: H160` True if the blake160 preimage for `hash` is provided.

### 4.3.2. `thresh` expression

The `thresh` expression is the only way to combine different expressions. The syntax is as follows:

``````thresh(m, e1, …, en) (m: U64 > 0, e1,…,en: Expression)
``````

The `thresh` expression above is true when exactly `m` expressions in subexpressions `e1`, …, `en` are true. The evaluation order of the subexpressions is from left to right, and if the number of true expressions becomes `m`, the remaining expressions are not executed. In other words, if a user can satisfy `m` expressions in `e1`, …, `ek`, then `ek+1`, …, `en` will not be executed.

To provide a better user experience, the following syntactic sugars are provided:

• `expr1 || expr2` = `thresh(1, expr1, expr2)`
• `expr1 && expr2` = `thresh(2, expr1, expr2)`

These syntactic sugars are expanded to the `thresh` expression by the compiler before compiling them to the CCVM instructions. Note that if `&&` and `||` are used together, `&&` has a higher precedence over `||`.

# 5. Generating a lock script

## 5.1. Compiling base expressions

Base expressions are compiled into the CodeChain script according to the following translation rules:

Expression Parameters Script
`true` `PUSH 1`
`false` `PUSH 0`
`pk(pub)` `pub` `CHKSIG`
`pk(m, pub1, …, pubn)` `n pub1 … pubn m` `CHKMULTISIG`
`pkh(hash)` `hash` `COPY 1 BLAKE160 EQ JZ 2 PUSH 0 JMP 1 CHKSIG`
`after(time)` `time` `RELTIME GT`
`before(time)` `time` `RELTIME LT`
`blake256(hash)` `hash` `SWAP BLAKE256 EQ`
`sha256(hash)` `hash` `SWAP SHA256 EQ`
`ripemd160(hash)` `hash` `SWAP RIPEMD160 EQ`
`keccak256(hash)` `hash` `SWAP KECCAK256 EQ`
`blake160(hash)` `hash` `SWAP BLAKE160 EQ`

## 5.2. Compiling thresh expressions

A `thresh` expression executes its subexpressions from left to right, and stops the execution when the number of true subexpressions becomes `m`(>0). This is implemented by pushing a special value counter to the stack and incrementing it if the execution result of a subexpression is true. When the counter becomes `m`, the remaining subexpressions are not executed and the parameters of those subexpressions are removed from the stack. This ensures that the stack only contains the parameters of the remaining expressions.

### 5.2.1. Parameters

The parameters of the `thresh(m, e1, …, en)` are defined as follows:

``````…param(e1), …, …param(en), m
``````

### 5.2.2. Preparing counter

At the start of the execution, a special value counter is created for tracking the number of true subexpressions in the current `thresh` expression. It is initialized by the following script:

``````PUSH 0
``````

### 5.2.3. Executing subexpressions

Before executing the subexpressions, the parameters for that expression is lifted to the top of the stack. If the subexpression is a base expression, the required number of values from the unlock script are lifted too. Note that these lifted values will be consumed and removed from the stack after executing the subexpression.

After executing a subexpression, the stack will look like the following table:

Stack Description
`0/1` Execution result of the subexpression
`0` counter
Parameters of the remaining subexpressions
`m` Number of required true expressions

The result is added to the counter and is compared with the threshold value(`m`) in the stack. If the counter is equal to `m`, all the remaining subexpressions are skipped. It can be expressed as the following script:

``````ADD DUP COPY (index of m in the stack) EQ JNZ (distance to the POP instructions)
``````
Instruction Stack (left is the stack top)
`0/1`(result) `counter` `params` `m`
`ADD` `new_counter` `params` `m`
`DUP` `new_counter` `new_counter` `params` `m`
`COPY` `m` `new_counter` `new_counter` `params` `m`
`EQ` `new_counter == m` `new_counter` `params` `m`
`JNZ` `new_counter` `params` `m`

<Execution example>

When the subexpressions are skipped, all the remaining parameters for those subexpressions are dropped and 1 is pushed to the stack. It can be expressed as the following script:

``````POP POP … POP PUSH 1
``````

For optimization purposes, the counter handling instructions for the last subexpression is different from the other instructions. When the last subexpression is executed, we don’t have to maintain the counter because we don’t have any more subexpression to execute. Thus, we can push `counter + result == m` to the stack as a result. It can be expressed as the following script:

``````ADD EQ JMP (distance to the end of the script)
``````
Instruction Stack
`0/1`(result) `counter` `m`
`ADD` `m`
`EQ` `new_counter == m`
`JMP` `new_counter == m`

<Execution example for thresh>

So the compilation result of `thresh(m, e1, e2, …, en)` looks like this:

``````PUSH 0
(…LIFT values for e1)
(…instructions for e1)
ADD DUP COPY (index of m) EQ JNZ (distance to POPs)
(…LIFT values for e2)
(…instructions for e2)
ADD DUP COPY (index of m) EQ JNZ (distance to POPs)
…
(…LIFT values for en)
(…instructions for en)
ADD EQ JMP (distance to the end of the script)
POP … POP PUSH 1
``````

## 5.3. Merging success and burn conditions

The final compilation result depends on the success expression and the burn expression. There are three cases we should consider:

### 5.3.1. burn = false

The compilation result of the expression for the success condition is used as the final result.

### 5.3.2. burn != false && success = false

In this case, the script must burn the asset if the burn expression is satisfied. Thus, the compilation result of the burn expression is used and the following instructions are added at the end:

``````JZ 1 BURN
``````

### 5.3.3. burn != false && success != false

In this case, the compilation result requires an additional value to be provided by the unlock script. If the provided value is zero, the success condition is executed. Otherwise, the burn condition is executed.

## 5.4. Compilation examples

### 5.4.1. p2pk

Vault script: `success = pk(A);`
Parameters: `A`
Lock script: `CHKSIG`

### 5.4.2. p2pkh

Vault script: `success = pkh(H_A);`
Parameters: `H_A`
Lock script: `COPY 1 BLAKE160 EQ JZ 2 PUSH 0 JMP 1 CHKSIG`

### 5.4.3. A or B

Vault script: `success = pk(A) || pk(B);`
Parameters: `A B 1`
Lock script:

``````PUSH 0
LIFT 5 LIFT 5 LIFT 3
CHKSIG
ADD DUP COPY 3 EQ JNZ 7
LIFT 4 LIFT 4 LIFT 3
CHKSIG
POP POP POP PUSH 1
``````

### 5.4.4. (A and B) can unlock, H_C can burn before 100s

Vault script:

``````success = pk(A) && pk(B);
burn = pkh(H_C) && before(100s);
``````

Parameters: `H_C 100 2 A B 2`
Lock script:

``````JZ 30
DROP 3 DROP 3 DROP 3
PUSH 0
LIFT 5 LIFT 5 LIFT 3
COPY 1 BLAKE160 EQ JZ 2 PUSH 0 JMP 1 CHKSIG
ADD DUP COPY 3 EQ JNZ 6
LIFT 1
RELTIME LT
POP POP PUSH 1
JZ 25 BURN
POP POP POP
PUSH 0
LIFT 5 LIFT 5 LIFT 3
CHKSIG
ADD DUP COPY 3 EQ JNZ 7
LIFT 4 LIFT 4 LIFT 3
CHKSIG
POP POP POP PUSH 1
``````

### 5.4.5. 2-of-3 multisig

Vault script: `success = pk(2, A, B, C);`
Parameters: `3 A B C 2`
Lock script: `CHKMULTISIG`

### 5.4.6. 2 of H_A, B and 100s timelock

Vault script: `thresh(2, pkh(H_A), pk(B), after(100s))`
Parameters: `A B 100 2`
Lock script:

``````PUSH 0
LIFT 7 LIFT 7 LIFT 7 LIFT 4
COPY 1 BLAKE160 EQ JZ 2 PUSH 0 JMP 1 CHKSIG
ADD DUP COPY 4 EQ JNZ 15
LIFT 5 LIFT 5 LIFT 3
CHKSIG
ADD DUP COPY 3 EQ JNZ 7
LIFT 1
RELTIME GT
POP POP POP POP PUSH 1
``````

### 5.4.7. Hash Time Locked Contract

Vault script: `pk(A) && after(1000blk) || pkh(B) && hash(X)`
Parameters: `A 1000 2 B X 2 1`
Lock script:

``````PUSH 0
LIFT 3 LIFT 3 LIFT 3
PUSH 0
LIFT 10 LIFT 10 LIFT 3
CHKSIG
ADD DUP COPY 3 EQ JNZ 6
LIFT 1
RELTIME GT
POP POP POP PUSH 1
ADD DUP COPY 6 EQ JNZ 38
LIFT 4 LIFT 4 LIFT 4 LIFT 4
PUSH 0
LIFT 8 LIFT 8 LIFT 8 LIFT 4
COPY 1 BLAKE160 EQ JZ 2 PUSH 0 JMP 1 CHKSIG
ADD DUP COPY 3 EQ JNZ 8
LIFT 5 LIFT 2
SWAP BLAKE160 EQ
POP POP POP PUSH 1
POP POP POP POP POP PUSH 1
``````

# 6. Generating an unlock script

The compiler is also responsible for generating an unlock script corresponding to a Vault script when the correct set of values are given.

## 6.1. Deciding which expressions to satisfy

For the assets locked with a Vault script, the structure of the unlock script differs a lot depending on the conditions that can be satisfied. For example, for the expression `thresh(2, e0, e1, e2)`, if we are only aware of the values that can satisfy `e0` and `e2`, we have to provide some values that can make the `e1`’s result false. However, if we know the values that can satisfy `e0` and `e1`, we don’t have to provide any more values.

Thus, the list of expressions to be satisfied should be decided before generating an unlock script for a Vault script. The compiler receives values that can be used for this decision, and automatically derives the list of expressions from them. The list of values that the compiler receives is as follows:

• Map of (public key, (tag, signature)) (default: empty map)
• Map of (hash, preimage) (default: empty map)
• Expected age of an asset at the time of execution in terms of seconds
• Expected age of an asset at the time of execution in terms of blocks
• Flag for deciding whether to unlock or burn. `burn` and `success` are allowed. (default: `success`)

All the values are optional, but the execution time must be provided if the Vault script contains `after(…s)` or `before(…s)` and the execution block number must be provided if the Vault script contains `after(…blk)` or `before(…blk)`.

The resulting list of expressions must satisfy the following rules:

• If the flag is `success`, the top level expression of the success condition must be selected.
• If the flag is `burn`, the top level expression of the burn condition must be selected.
• If `thresh(m, e0, …, en)` is selected, exactly `m` expressions in `e0`, …, `en` must be selected.
• If a base expression is selected, the condition in the table below must be satisfied.
Expression Satisfaction condition
`true` Always
`false` Never
`pk(pub)` The signature table contains an entry for public key `pub`
`pk(m, pub1, …, pubn)` The signature table contains `m` entries in `n` public keys `pub1`, …, `pubn`
The signature table’s entries for `pub1`, …, `pubn` must have the same value for tag
`pkh(hash)` The preimage table contains an entry for `hash`.
The preimage of the `hash` has a length of 64.
The signature table contains an entry for the preimage of `hash`
`after(times)` The expected age of an asset in second is larger than `time`.
`after(timeblk)` The expected age of an asset in block is larger than `time`.
`before(times)` The expected age of an asset in second is less than `time`.
`before(timeblk)` The expected age of an asset in block is less than `time`.
`blake256(hash)` The preimage table contains an entry for `hash`.
`sha256(hash)` The preimage table contains an entry for `hash`.
`ripemd160(hash)` The preimage table contains an entry for `hash`.
`keccak256(hash)` The preimage table contains an entry for `hash`.
`blake160(hash)` The preimage table contains an entry for `hash`.

<Base expression satisfaction table>

If the top level expression couldn’t be selected, the compiler throws an error. If there were multiple possible ways to select a list of expressions, the actual selected list of expressions depends on the implementation.

## 6.2. Base expressions

There are some cases (e.g. in `thresh` expression) where we have to intentionally make the script fail. To generate such unlock scripts, dummy values that are expected to never satisfy the condition is used.

Unlock scripts that {do, do not} satisfy the condition for the base expressions are as follows:

Expression Used values Unlock script that satisfies the condition Unlock script that fails to satisfy the condition
`true` Always possible Impossible
`false` Impossible Always possible
`pk(pub)` (`tag`, `sig`) = SignatureMap[`pub`] `PUSHB 65 sig PUSH tag` `PUSHB 65 0x0…0 PUSH 0`
`pk(m, pub1, …, pubn)` `sig1`, `sig2`, … = signatures of m public keys in `pub1`, …, `pubn` `PUSH tag PUSHB 65 sig1 PUSHB 65 sig2, …` `PUSH 0 PUSHB 65 sig1 PUSHB 65 sig2, …`
`pkh(hash)` `pub` = PreimageMap[`hash`]
(`tag`, `sig`) = SignatureMap[`pub`]
`PUSHB 65 sig PUSH tag PUSHB 64 pub` `PUSHB 65 0x0…0 PUSH 0 PUSHB 64 0x0…0`
`after(time)` Always possible if selected
Impossible if not selected
Always possible if not selected
Impossible if selected
`before(time)` Always possible if selected
Impossible if not selected
Always possible if not selected
Impossible if selected
`blake256(hash)` `preimage` = PreimageMap[`hash`] `PUSHB len(preimage) preimage` `PUSH 0 if blake256(0) != hash`
`PUSH 1 if blake256(0) == hash`
`sha256(hash)` `preimage` = PreimageMap[`hash`] `PUSHB len(preimage) preimage` `PUSH 0 if sha256(0) != hash`
`PUSH 1 if sha256(0) == hash`
`ripemd160(hash)` `preimage` = PreimageMap[`hash`] `PUSHB len(preimage) preimage` `PUSH 0 if ripemd160(0) != hash`
`PUSH 1 if ripemd160(0) == hash`
`keccak256(hash)` `preimage` = PreimageMap[`hash`] `PUSHB len(preimage) preimage` `PUSH 0 if keccak256(0) != hash`
`PUSH 1 if keccak256(0) == hash`
`blake160(hash)` `preimage` = PreimageMap[`hash`] `PUSHB len(preimage) preimage` `PUSH 0 if blake160(0) != hash`
`PUSH 1 if blake160(0) == hash`

## 6.3. thresh expressions

As described in 4.3.2, the `thresh(m, e1, …, en)` expression executes the subexpressions from left to right, and skips the remaining subexpressions if the number of accumulated true expressions is equal to `m`. If `ek` is the rightmost subexpression in the selected subexpressions, `ek` is the last executed subexpression because the number of selected subexpressions is equal to `m`. Thus, the generated unlock script should provide inputs for all the subexpressions until `ek`, and should not provide inputs for the subexpressions after `ek`. If there are any subexpressions in `e1`, …, `ek` that are not selected, the generated unlock script should provide the inputs that can make that subexpression’s result false.

The generated unlock script for `thresh(m, e1, …, en)` is as follows:

``````…unlock(ek, is_selected(ek)), …, …unlock(e1, is_selected(e1))
``````

where

• `ek` = rightmost selected expression in `e1`, …, `en`
• `is_selected(e)` = true if `e` is selected, false otherwise
• `unlock(e, true)` = Unlock script that makes `e` succeed
• `unlock(e, false)` = Unlock script that makes `e` fail

To make a `thresh` expression fail, all the unlock scripts for the subexpressions must fail as follows:

``````…unlock(en, false), …, …unlock(e1, false)
``````

where

• `unlock(e, false)` = Unlock script that makes `e` fail

## 6.4. Generating the final unlock script

After generating the unlock script for the selected expressions, we make final changes to the result for the burn/success selection.

### 6.4.1. burn = false

The generated unlock script of the success expression is used as the final result.

### 6.4.2. burn != false && success = false

The generated unlock script of the burn expression is used as the final result.

### 6.4.3. burn != false && success != false

In this case, the lock script requires an additional value as specified in 5.3.3. If the flag provided to the compiler was `burn`, `PUSH 1` is appended at the end of the unlock script. If the flag was `success`, `PUSH 0` is appended.

## 6.5. Example

### 6.5.1. p2pk

Vault script: `success = pk(A);`
Unlock script: `PUSHB 65 <sig> PUSH 3`

### 6.5.2. p2pkh

Vault script: `success = pkh(H_A);`
Unlock script: `PUSHB 65 <sig> PUSH 3 PUSHB 64 <pub>`

### 6.5.3. A or B

Vault script: `success = pk(A) || pk(B);`
Unlock script: `PUSHB 65 <sigA> PUSH 3`

### 6.5.4. (A and B) can unlock, H_C can burn before 100s

Vault script:

``````success = pk(A) && pk(B);
burn = pkh(H_C) && before(100s);
``````

Unlock script: `PUSHB 65 <sigC> PUSH 3 PUSHB 64 <pubC> PUSH 1`

### 6.5.5. 2-of-3 multisig

Vault script: `success = pk(2, A, B, C);`
Unlock script: `PUSH 3 PUSHB 65 <sigA> PUSHB 65 <sigB>`

### 6.5.6. 2 of H_A, B and 100s timelock

Vault script: `thresh(2, pkh(H_A), pk(B), after(100s))`
Unlock script:

``````PUSHB 65 <0x0…0> PUSH 0
PUSHB 65 <sigA> PUSH 3 PUSHB 65 <pubA>
``````

### 6.5.7. Hash Time Locked Contract

Vault script: `pk(A) && after(1000blk) || pkh(B) && hash(X)`
Unlock script:

``````PUSHB 32 <preX>
PUSHB 65 <sigB> PUSH 3 PUSHB 64 <pubB>
PUSHB 65 <0x0…0> PUSH 0
``````

# 7. Required features in CodeChain

## 7.1. `LIFT` opcode

The `LIFT` opcode used in this document doesn’t exist in the CCVM now. Its behavior is "lifting” the value in the stack to the top. In other words, `LIFT n` removes the stack item at the `n`th index and pushes the removed value to the top of the stack. Its behavior is the same as `COPY n DROP n+1`, but since it’s repeated too much, it’d be better to make a new opcode for this.

## 7.2. Script-based timelock

The current implementation of the timelock uses the “timelock” field on the AssetTransferInput data structure. This allows only one timelock condition in an asset, so composable timelocks such as `after(100s) && before(200s) && pk(A)` cannot exist. One way of solving this is by introducing opcodes for fetching the current (absolute and relative) timestamp of the transaction. We can compare the desired time with the value retrieved from the opcode to check if the timelock is satisfied. The `RELTIME` opcode in this document is used for this purpose, representing the opcode for pushing the relative time value (in seconds) since the asset was created.

## 7.3. Arithmetic and Integer comparison opcodes

The `ADD` opcode is used for accumulating the number of satisfied expressions in `thresh` expressions. Also, `GT` and `LT` opcodes are used for checking the timelock. Since we do not have such opcodes in our current CCVM spec, we need to add these instructions.

1 Like