Skip to content

Commit 01236a0

Browse files
committed
Merge #33: Add state commitments to simplicity info and simplictiy pset update-input
7e16364 add state to 'simplicity pset update-input' (Andrew Poelstra) a2ae36d add state commitment ability to `simplicity info` (Andrew Poelstra) Pull request description: This does not include documentation, tutorials or extended unit tests. I believe that this is not discoverable. But it is better that it exist than not, so I'm PR'ing it as-is. In short: * adds a `-s <state as 32-byte hex>` flag to `simplicity info` which tweaks the generated addresses/scriptpubkeys accordingly * adds a `-s <state as 32-byte hex>` flag to `simplicity pset update-input` which creates a two-leaf taptree rather than a one-leaf one when computing control blocks If you fail to provide these, in both cases the command will just silently produce output you didn't intend. ACKs for top commit: delta1: ACK 7e16364 Tree-SHA512: 02a6617c85cc19d5030c733544d2e62614410b81051bb615bc29e8c6cfdd3a72985b393a86c07267254d2263f0968f7e64f355e28c9cf8718531123fd6b688ce
2 parents 9d60d1b + 7e16364 commit 01236a0

File tree

4 files changed

+73
-10
lines changed

4 files changed

+73
-10
lines changed

src/bin/hal-simplicity/cmd/simplicity/info.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use super::{Error, ErrorExt as _};
77

88
use hal_simplicity::hal_simplicity::{elements_address, Program};
99
use hal_simplicity::simplicity::{jet, Amr, Cmr, Ihr};
10+
use simplicity::hex::parse::FromHex as _;
1011

1112
use serde::Serialize;
1213

@@ -42,20 +43,32 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> {
4243
cmd::arg("witness", "a hex encoding of all the witness data for the program")
4344
.takes_value(true)
4445
.required(false),
46+
cmd::opt(
47+
"state",
48+
"32-byte state commitment to put alongside the program when generating addresess (hex)",
49+
)
50+
.takes_value(true)
51+
.short("s")
52+
.required(false),
4553
])
4654
}
4755

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

52-
match exec_inner(program, witness) {
61+
match exec_inner(program, witness, state) {
5362
Ok(info) => cmd::print_output(matches, &info),
5463
Err(e) => cmd::print_output(matches, &e),
5564
}
5665
}
5766

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

89+
let state = state
90+
.map(<[u8; 32]>::from_hex)
91+
.transpose()
92+
.result_context("parsing 32-byte state commitment as hex")?;
93+
7694
Ok(ProgramInfo {
7795
jets: "core",
7896
commit_base64: program.commit_prog().to_string(),
7997
// FIXME this is, in general, exponential in size. Need to limit it somehow; probably need upstream support
8098
commit_decode: program.commit_prog().display_expr().to_string(),
8199
type_arrow: program.commit_prog().arrow().to_string(),
82100
cmr: program.cmr(),
83-
liquid_address_unconf: elements_address(program.cmr(), &elements::AddressParams::LIQUID)
84-
.to_string(),
101+
liquid_address_unconf: elements_address(
102+
program.cmr(),
103+
state,
104+
&elements::AddressParams::LIQUID,
105+
)
106+
.to_string(),
85107
liquid_testnet_address_unconf: elements_address(
86108
program.cmr(),
109+
state,
87110
&elements::AddressParams::LIQUID_TESTNET,
88111
)
89112
.to_string(),

src/bin/hal-simplicity/cmd/simplicity/pset/update_input.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use super::UpdatedPset;
1111

1212
use elements::schnorr::XOnlyPublicKey;
1313
use hal_simplicity::hal_simplicity::taproot_spend_info;
14+
use simplicity::hex::parse::FromHex as _;
1415

1516
pub fn cmd<'a>() -> clap::App<'a, 'a> {
1617
cmd::subcommand("update-input", "Attach UTXO data to a PSET input")
@@ -32,6 +33,13 @@ pub fn cmd<'a>() -> clap::App<'a, 'a> {
3233
.short("c")
3334
.takes_value(true)
3435
.required(false),
36+
cmd::opt(
37+
"state",
38+
"32-byte state commitment to put alongside the program when generating addresess (hex)",
39+
)
40+
.takes_value(true)
41+
.short("s")
42+
.required(false),
3543
// FIXME add merkle path, needed to compute nontrivial control blocks
3644
])
3745
}
@@ -43,8 +51,9 @@ pub fn exec<'a>(matches: &clap::ArgMatches<'a>) {
4351

4452
let internal_key = matches.value_of("internal-key");
4553
let cmr = matches.value_of("cmr");
54+
let state = matches.value_of("state");
4655

47-
match exec_inner(pset_b64, input_idx, input_utxo, internal_key, cmr) {
56+
match exec_inner(pset_b64, input_idx, input_utxo, internal_key, cmr, state) {
4857
Ok(info) => cmd::print_output(matches, &info),
4958
Err(e) => cmd::print_output(matches, &e),
5059
}
@@ -57,6 +66,7 @@ fn exec_inner(
5766
input_utxo: &str,
5867
internal_key: Option<&str>,
5968
cmr: Option<&str>,
69+
state: Option<&str>,
6070
) -> Result<UpdatedPset, Error> {
6171
let mut pset: elements::pset::PartiallySignedTransaction =
6272
pset_b64.parse().result_context("decoding PSET")?;
@@ -87,6 +97,14 @@ fn exec_inner(
8797
.result_context("input UTXO");
8898
}
8999

100+
// FIXME state is meaningless without CMR; should we warn here
101+
// FIXME also should we warn if you don't provide a CMR? seems like if you're calling `simplicity pset update-input`
102+
// you probably have a simplicity program right? maybe we should even provide a --no-cmr flag
103+
let state = state
104+
.map(<[u8; 32]>::from_hex)
105+
.transpose()
106+
.result_context("parsing 32-byte state commitment as hex")?;
107+
90108
let mut updated_values = vec![];
91109
if let Some(internal_key) = internal_key {
92110
updated_values.push("tap_internal_key");
@@ -97,7 +115,7 @@ fn exec_inner(
97115
// Guess that the given program is the only Tapleaf. This is the case for addresses
98116
// generated from the web IDE, and from `hal-simplicity simplicity info`, and for
99117
// most "test" scenarios. We need to design an API to handle more general cases.
100-
let spend_info = taproot_spend_info(internal_key, cmr);
118+
let spend_info = taproot_spend_info(internal_key, state, cmr);
101119
if spend_info.output_key().as_inner().serialize() != input_utxo.script_pubkey[2..] {
102120
// If our guess was wrong, at least error out..
103121
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))

src/hal_simplicity.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,29 @@ fn script_ver(cmr: simplicity::Cmr) -> (elements::Script, elements::taproot::Lea
112112
/// for a Taptree with this CMR as its single leaf.
113113
pub fn taproot_spend_info(
114114
internal_key: secp256k1::XOnlyPublicKey,
115+
state: Option<[u8; 32]>,
115116
cmr: simplicity::Cmr,
116117
) -> TaprootSpendInfo {
117118
let builder = TaprootBuilder::new();
118119
let (script, version) = script_ver(cmr);
119-
let builder = builder.add_leaf_with_ver(0, script, version).expect("tap tree should be valid");
120+
let builder = if let Some(state) = state {
121+
use elements::hashes::{sha256, Hash as _, HashEngine as _};
122+
let tag = sha256::Hash::hash(b"TapData");
123+
let mut eng = sha256::Hash::engine();
124+
eng.input(tag.as_byte_array());
125+
eng.input(tag.as_byte_array());
126+
eng.input(&state);
127+
let state_hash = sha256::Hash::from_engine(eng);
128+
129+
builder
130+
.add_leaf_with_ver(1, script, version)
131+
.expect("tap tree should be valid")
132+
.add_hidden(1, state_hash)
133+
.expect("tap tree should be valid")
134+
} else {
135+
builder.add_leaf_with_ver(0, script, version).expect("tap tree should be valid")
136+
};
137+
120138
builder.finalize(secp256k1::SECP256K1, internal_key).expect("tap tree should be valid")
121139
}
122140

@@ -125,9 +143,10 @@ pub fn taproot_spend_info(
125143
/// internal key and this CMR as its single leaf.
126144
pub fn elements_address(
127145
cmr: simplicity::Cmr,
146+
state: Option<[u8; 32]>,
128147
params: &'static elements::AddressParams,
129148
) -> elements::Address {
130-
let info = taproot_spend_info(unspendable_internal_key(), cmr);
149+
let info = taproot_spend_info(unspendable_internal_key(), state, cmr);
131150
let blinder = None;
132151
elements::Address::p2tr(
133152
secp256k1::SECP256K1,

tests/cli.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ hal-simplicity-simplicity-info 0.1.0
10771077
Parse a base64-encoded Simplicity program and decode it
10781078
10791079
USAGE:
1080-
hal-simplicity simplicity info [FLAGS] <program> [witness]
1080+
hal-simplicity simplicity info [FLAGS] [OPTIONS] <program> [witness]
10811081
10821082
FLAGS:
10831083
-r, --elementsregtest run in elementsregtest mode
@@ -1086,6 +1086,9 @@ FLAGS:
10861086
-v, --verbose print verbose logging output to stderr
10871087
-y, --yaml print output in YAML instead of JSON
10881088
1089+
OPTIONS:
1090+
-s, --state <state> 32-byte state commitment to put alongside the program when generating addresess (hex)
1091+
10891092
ARGS:
10901093
<program> a Simplicity program in base64
10911094
<witness> a hex encoding of all the witness data for the program
@@ -1103,7 +1106,7 @@ error: The following required arguments were not provided:
11031106
<program>
11041107
11051108
USAGE:
1106-
hal-simplicity simplicity info [FLAGS] <program> [witness]
1109+
hal-simplicity simplicity info [FLAGS] [OPTIONS] <program> [witness]
11071110
11081111
For more information try --help
11091112
",

0 commit comments

Comments
 (0)