Skip to content
Merged
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
31 changes: 27 additions & 4 deletions src/bin/hal-simplicity/cmd/simplicity/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::{Error, ErrorExt as _};

use hal_simplicity::hal_simplicity::{elements_address, Program};
use hal_simplicity::simplicity::{jet, Amr, Cmr, Ihr};
use simplicity::hex::parse::FromHex as _;

use serde::Serialize;

Expand Down Expand Up @@ -42,20 +43,32 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> {
cmd::arg("witness", "a hex encoding of all the witness data for the program")
.takes_value(true)
.required(false),
cmd::opt(
"state",
"32-byte state commitment to put alongside the program when generating addresess (hex)",
)
.takes_value(true)
.short("s")
.required(false),
])
}

pub fn exec<'a>(matches: &clap::ArgMatches<'a>) {
let program = matches.value_of("program").expect("program is mandatory");
let witness = matches.value_of("witness");
let state = matches.value_of("state");

match exec_inner(program, witness) {
match exec_inner(program, witness, state) {
Ok(info) => cmd::print_output(matches, &info),
Err(e) => cmd::print_output(matches, &e),
}
}

fn exec_inner(program: &str, witness: Option<&str>) -> Result<ProgramInfo, Error> {
fn exec_inner(
program: &str,
witness: Option<&str>,
state: Option<&str>,
) -> Result<ProgramInfo, Error> {
// In the future we should attempt to parse as a Bitcoin program if parsing as
// Elements fails. May be tricky/annoying in Rust since Program<Elements> is a
// different type from Program<Bitcoin>.
Expand All @@ -73,17 +86,27 @@ fn exec_inner(program: &str, witness: Option<&str>) -> Result<ProgramInfo, Error
x // binding needed for truly stupid borrowck reasons
});

let state = state
.map(<[u8; 32]>::from_hex)
.transpose()
.result_context("parsing 32-byte state commitment as hex")?;

Ok(ProgramInfo {
jets: "core",
commit_base64: program.commit_prog().to_string(),
// FIXME this is, in general, exponential in size. Need to limit it somehow; probably need upstream support
commit_decode: program.commit_prog().display_expr().to_string(),
type_arrow: program.commit_prog().arrow().to_string(),
cmr: program.cmr(),
liquid_address_unconf: elements_address(program.cmr(), &elements::AddressParams::LIQUID)
.to_string(),
liquid_address_unconf: elements_address(
program.cmr(),
state,
&elements::AddressParams::LIQUID,
)
.to_string(),
liquid_testnet_address_unconf: elements_address(
program.cmr(),
state,
&elements::AddressParams::LIQUID_TESTNET,
)
.to_string(),
Expand Down
22 changes: 20 additions & 2 deletions src/bin/hal-simplicity/cmd/simplicity/pset/update_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use super::UpdatedPset;

use elements::schnorr::XOnlyPublicKey;
use hal_simplicity::hal_simplicity::taproot_spend_info;
use simplicity::hex::parse::FromHex as _;

pub fn cmd<'a>() -> clap::App<'a, 'a> {
cmd::subcommand("update-input", "Attach UTXO data to a PSET input")
Expand All @@ -32,6 +33,13 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> {
.short("c")
.takes_value(true)
.required(false),
cmd::opt(
"state",
"32-byte state commitment to put alongside the program when generating addresess (hex)",
)
.takes_value(true)
.short("s")
.required(false),
// FIXME add merkle path, needed to compute nontrivial control blocks
])
}
Expand All @@ -43,8 +51,9 @@ pub fn exec<'a>(matches: &clap::ArgMatches<'a>) {

let internal_key = matches.value_of("internal-key");
let cmr = matches.value_of("cmr");
let state = matches.value_of("state");

match exec_inner(pset_b64, input_idx, input_utxo, internal_key, cmr) {
match exec_inner(pset_b64, input_idx, input_utxo, internal_key, cmr, state) {
Ok(info) => cmd::print_output(matches, &info),
Err(e) => cmd::print_output(matches, &e),
}
Expand All @@ -57,6 +66,7 @@ fn exec_inner(
input_utxo: &str,
internal_key: Option<&str>,
cmr: Option<&str>,
state: Option<&str>,
) -> Result<UpdatedPset, Error> {
let mut pset: elements::pset::PartiallySignedTransaction =
pset_b64.parse().result_context("decoding PSET")?;
Expand Down Expand Up @@ -87,6 +97,14 @@ fn exec_inner(
.result_context("input UTXO");
}

// FIXME state is meaningless without CMR; should we warn here
// FIXME also should we warn if you don't provide a CMR? seems like if you're calling `simplicity pset update-input`
// you probably have a simplicity program right? maybe we should even provide a --no-cmr flag
let state = state
.map(<[u8; 32]>::from_hex)
.transpose()
.result_context("parsing 32-byte state commitment as hex")?;

let mut updated_values = vec![];
if let Some(internal_key) = internal_key {
updated_values.push("tap_internal_key");
Expand All @@ -97,7 +115,7 @@ fn exec_inner(
// Guess that the given program is the only Tapleaf. This is the case for addresses
// generated from the web IDE, and from `hal-simplicity simplicity info`, and for
// most "test" scenarios. We need to design an API to handle more general cases.
let spend_info = taproot_spend_info(internal_key, cmr);
let spend_info = taproot_spend_info(internal_key, state, cmr);
if spend_info.output_key().as_inner().serialize() != input_utxo.script_pubkey[2..] {
// If our guess was wrong, at least error out..
return Err(format!("CMR and internal key imply output key {}, which does not match input scriptPubKey {}", spend_info.output_key().as_inner(), input_utxo.script_pubkey))
Expand Down
23 changes: 21 additions & 2 deletions src/hal_simplicity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,29 @@ fn script_ver(cmr: simplicity::Cmr) -> (elements::Script, elements::taproot::Lea
/// for a Taptree with this CMR as its single leaf.
pub fn taproot_spend_info(
internal_key: secp256k1::XOnlyPublicKey,
state: Option<[u8; 32]>,
cmr: simplicity::Cmr,
) -> TaprootSpendInfo {
let builder = TaprootBuilder::new();
let (script, version) = script_ver(cmr);
let builder = builder.add_leaf_with_ver(0, script, version).expect("tap tree should be valid");
let builder = if let Some(state) = state {
use elements::hashes::{sha256, Hash as _, HashEngine as _};
let tag = sha256::Hash::hash(b"TapData");
let mut eng = sha256::Hash::engine();
eng.input(tag.as_byte_array());
eng.input(tag.as_byte_array());
eng.input(&state);
let state_hash = sha256::Hash::from_engine(eng);

builder
.add_leaf_with_ver(1, script, version)
.expect("tap tree should be valid")
.add_hidden(1, state_hash)
.expect("tap tree should be valid")
} else {
builder.add_leaf_with_ver(0, script, version).expect("tap tree should be valid")
};

builder.finalize(secp256k1::SECP256K1, internal_key).expect("tap tree should be valid")
}

Expand All @@ -125,9 +143,10 @@ pub fn taproot_spend_info(
/// internal key and this CMR as its single leaf.
pub fn elements_address(
cmr: simplicity::Cmr,
state: Option<[u8; 32]>,
params: &'static elements::AddressParams,
) -> elements::Address {
let info = taproot_spend_info(unspendable_internal_key(), cmr);
let info = taproot_spend_info(unspendable_internal_key(), state, cmr);
let blinder = None;
elements::Address::p2tr(
secp256k1::SECP256K1,
Expand Down
7 changes: 5 additions & 2 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,7 @@ hal-simplicity-simplicity-info 0.1.0
Parse a base64-encoded Simplicity program and decode it

USAGE:
hal-simplicity simplicity info [FLAGS] <program> [witness]
hal-simplicity simplicity info [FLAGS] [OPTIONS] <program> [witness]

FLAGS:
-r, --elementsregtest run in elementsregtest mode
Expand All @@ -1086,6 +1086,9 @@ FLAGS:
-v, --verbose print verbose logging output to stderr
-y, --yaml print output in YAML instead of JSON

OPTIONS:
-s, --state <state> 32-byte state commitment to put alongside the program when generating addresess (hex)

ARGS:
<program> a Simplicity program in base64
<witness> a hex encoding of all the witness data for the program
Expand All @@ -1103,7 +1106,7 @@ error: The following required arguments were not provided:
<program>

USAGE:
hal-simplicity simplicity info [FLAGS] <program> [witness]
hal-simplicity simplicity info [FLAGS] [OPTIONS] <program> [witness]

For more information try --help
",
Expand Down