Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

# Unreleased

- Add `load` and `store` functions to assert state commitments for
covenants via Taproot leaves. This allows an instance of a smart
contract to remember state information across multiple transactions.

## [0.0.1]

The initial release with checked arithmetic operations for `u8`, `u16`, `u32`, `u64`, and `u128`;
Expand Down
2 changes: 1 addition & 1 deletion WISHLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Q. Isn't the first one directly implemented by a jet? Do we just need to `unwrap

# Storage (state management)

* Store and load single uninterpreted `u256` value
Store and load single uninterpreted `u256` value
* Merkle tree tools (maybe also codegen for Merkle tree manipulation based on a separate schema?)

# Covenants (high-level)
Expand Down
53 changes: 53 additions & 0 deletions simf/lib/storage.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
fn own_script_hash_with_state(state_data: u256) -> u256 {
// This is the bulk of our "compute state commitment" logic.
let tap_leaf: u256 = jet::tapleaf_hash();
let state_ctx1: Ctx8 = jet::tapdata_init();
let state_ctx2: Ctx8 = jet::sha_256_ctx_8_add_32(state_ctx1, state_data);
let state_leaf: u256 = jet::sha_256_ctx_8_finalize(state_ctx2);
let tap_node: u256 = jet::build_tapbranch(tap_leaf, state_leaf);

// Compute a taptweak using this.
let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0;
let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node);

// Turn the taptweak into a script hash.
let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init();
let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32
let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key);
jet::sha_256_ctx_8_finalize(hash_ctx3)
}

pub fn load(state_data: u256) {
// Assert that the input state is correct, i.e. "load".
//
// Enforce that the state commitment hash in the Taptree alongside
// the current input is equal to state_data. (This must be a result
// of the transaction builder's having constructed the prior
// transaction so that this is true.)
// Panics otherwise.
assert!(jet::eq_256(
own_script_hash_with_state(state_data),
unwrap(jet::input_script_hash(jet::current_index()))
));
}

pub fn store(new_state: u256, index: u32) {
// Assert that the output state is correct, i.e. "store".
//
// The index parameter specifies the output index where the
// new copy of this covenant is located. Depending on the
// covenant convention, that could be jet::current_index()
// (same index as input), some other hard-coded index
// demanded by convention, or could even be flexible and
// determined by a witness parameter.
//
// Enforce that the state commitment hash in the Taptree alongside
// the specified output is equal to new_state. (This must be a
// result of the transaction builder constructing the transaction
// so that this is true.)
// Panics otherwise.
assert!(jet::eq_256(
own_script_hash_with_state(new_state),
unwrap(jet::output_script_hash(index))
));
}
14 changes: 14 additions & 0 deletions simf/storage_test.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::lib::storage::{load, store};
use crate::helper::if_test_this_function;

fn main() {
let fn_idx: u8 = witness::FUNCTION_INDEX;

let state_data: u256 = witness::STATE_DATA;
let new_state: u256 = witness::NEW_STATE;
let index: u32 = witness::INDEX;

match if_test_this_function(0, fn_idx) { true => { load(state_data); }, false => (), };
match if_test_this_function(1, fn_idx) { true => { store(new_state, index); }, false => (), };
match if_test_this_function(2, fn_idx) { true => { load(state_data); store(new_state, index); }, false => (), };
}
68 changes: 56 additions & 12 deletions tests/common/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![allow(dead_code)]

use simplex::program::{Program, WitnessTrait};
use simplex::simplicityhl::elements::Script;
use simplex::simplicityhl::elements::{Script, Sequence};
use simplex::transaction::{
FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature,
};
Expand Down Expand Up @@ -43,13 +43,17 @@ pub fn fund(
Ok(script)
}

/// Construct the funded UTXO with `witness`.

/// Construct the funded UTXO with `witness`, under a caller-chosen `sequence` (nSequence)
/// and with any additional `outputs` appended (in order, before any auto-generated
/// change/fee outputs -- so the Nth entry lands at output index N).
pub fn construct_final_tx<W>(
context: &simplex::TestContext,
program: &impl AsRef<Program>,
script: &Script,
witness: W,
data: Option<&[u8]>,
sequence: Sequence,
outputs: Vec<PartialOutput>,
) -> anyhow::Result<FinalTransaction>
where
W: WitnessTrait + 'static,
Expand All @@ -60,30 +64,32 @@ where

let mut ft = FinalTransaction::new();
ft.add_program_input(
PartialInput::new(utxos[0].clone()),
PartialInput::new(utxos[0].clone()).with_sequence(sequence),
ProgramInput::new(Box::new(program.as_ref().clone()), Box::new(witness)),
RequiredSignature::None,
);

if let Some(data) = data {
ft.add_output(PartialOutput::new_metadata(data))
};
for output in outputs {
ft.add_output(output);
}

Ok(ft)
}

/// Spend the funded UTXO with `witness`. Return the broadcast result.
/// Spend the funded UTXO with `witness`, `sequence`, and `outputs`. Return the broadcast
/// result.
pub fn spend<W>(
context: &simplex::TestContext,
program: &impl AsRef<Program>,
script: &Script,
witness: W,
data: Option<&[u8]>,
sequence: Sequence,
outputs: Vec<PartialOutput>,
) -> anyhow::Result<String>
where
W: WitnessTrait + 'static,
{
let ft = construct_final_tx(context, program, script, witness, data)?;
let ft = construct_final_tx(context, program, script, witness, sequence, outputs)?;

Ok(context.get_default_signer().broadcast(&ft)?.to_string())
}
Expand Down Expand Up @@ -119,7 +125,7 @@ where
W: WitnessTrait + 'static,
{
let script = fund(context, &program)?;
let result = spend(context, &program, &script, witness, None);
let result = spend(context, &program, &script, witness, Sequence::default(), vec![]);

assert_error_msg(result, expect)
}
Expand All @@ -137,7 +143,45 @@ where
W: WitnessTrait + 'static,
{
let script = fund(context, &program)?;
let result = spend(context, &program, &script, witness, Some(data));
let result = spend(
context,
&program,
&script,
witness,
Sequence::default(),
vec![PartialOutput::new_metadata(data)],
);

assert_error_msg(result, expect)
}

/// Fund + spend + assert the outcome, with a list of extra `(script, amount)` outputs
/// added to the transaction.
pub fn run_with_outputs<W>(
context: &simplex::TestContext,
program: impl AsRef<Program>,
witness: W,
outputs: Vec<(Script, u64)>,
expect: Expect,
) -> anyhow::Result<()>
where
W: WitnessTrait + 'static,
{
let script = fund(context, &program)?;
let outputs = outputs
.into_iter()
.map(|(output_script, amount)| {
PartialOutput::new(output_script, amount, context.get_network().policy_asset())
})
.collect();
let result = spend(
context,
&program,
&script,
witness,
Sequence::default(),
outputs,
);

assert_error_msg(result, expect)
}
Loading