Skip to content

Commit 3a9c5a2

Browse files
committed
Add p2ms demo with associated Quickstart markdown doc
1 parent 274fe89 commit 3a9c5a2

File tree

2 files changed

+293
-0
lines changed

2 files changed

+293
-0
lines changed

docs/assets/p2ms-demo.sh

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
#!/bin/bash
2+
3+
# Fund and spend an example "pay to multisig" SimplicityHL contract
4+
# on Liquid Testnet.
5+
6+
# Dependencies: simc hal-simplicity jq curl
7+
8+
pause() { echo -n "Press Enter to continue. " ; read -r; echo; echo; }
9+
# pause() { echo; echo; }
10+
11+
# This demo has been updated to not require the use of elements-cli.
12+
# Some tasks could be simpler or more scalable in some sense if we used
13+
# a local elements-cli, but here we use the Liquid Testnet web API
14+
# instead to remove local dependencies. The main reason for this is that
15+
# elementsd will require multiple gigabytes of blockchain data. Having
16+
# a working local copy of elementsd can be useful for other development
17+
# tasks, but represents a larger time and disk space commitment.
18+
19+
PROGRAM_SOURCE=~/src/SimplicityHL/examples/p2ms.simf
20+
WITNESS_FILE=~/src/SimplicityHL/examples/p2ms.wit
21+
22+
# This is an unspendable public key address derived from BIP 0341. It is
23+
# semi-hardcoded in some Simplicity tools. You can change it in order
24+
# to make existing contract source code have a different address on the
25+
# blockchain but you must use a NUMS method to make sure that the key
26+
# is unspendable. BIP 0341 gives a method to modify the number below to
27+
# retain that property.
28+
#
29+
# If you are sending assets to a contract created by someone else, you
30+
# must make sure that the internal key used for that instance of the
31+
# contract is an unspendable value (if it is changed from this default).
32+
# Otherwise, the contract creator may be able to unilaterally steal
33+
# value from the contract.
34+
INTERNAL_KEY="50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
35+
TMPDIR=$(mktemp -d)
36+
37+
38+
# Private keys of parties whose signatures can approve this transaction.
39+
PRIVKEY_1="0000000000000000000000000000000000000000000000000000000000000001"
40+
PRIVKEY_2="0000000000000000000000000000000000000000000000000000000000000002"
41+
PRIVKEY_3="0000000000000000000000000000000000000000000000000000000000000003"
42+
43+
44+
# Hardcoded address of the Liquid testnet for returning tLBTC
45+
# (so that they aren't wasted!)
46+
# We could also send these to our own wallet, but here we are choosing
47+
# to send them back.
48+
# Confidential address
49+
FAUCET_ADDRESS=tlq1qq2g07nju42l0nlx0erqa3wsel2l8prnq96rlnhml262mcj7pe8w6ndvvyg237japt83z24m8gu4v3yfhaqvrqxydadc9scsmw
50+
# FAUCET_ADDRESS=$("$ELEMENTS_CLI" validateaddress "$FAUCET_ADDRESS" | jq -r .unconfidential)
51+
# Unconfidential address
52+
FAUCET_ADDRESS=tex1qkkxzy9glfws4nc392an5w2kgjym7sxpshuwkjy
53+
# The ability to derive the unconfidential address without depending on elements-cli
54+
# will soon be added to "hal-simplicity address inspect".
55+
56+
for variable in PROGRAM_SOURCE WITNESS_FILE INTERNAL_KEY PRIVKEY_1 PRIVKEY_2 PRIVKEY_3 FAUCET_ADDRESS
57+
do
58+
echo -n "$variable="
59+
eval echo \$$variable
60+
done
61+
62+
# This check is used for two different purposes. First, we need to be
63+
# able to actually download the transaction data in order to extract
64+
# some details. Second, we need the node that we will ultimately submit
65+
# our transaction to to have a copy of the input transaction (at least
66+
# in its mempool; not necessarily confirmed!). There are propagation
67+
# delays in the Liquid Network, so we cannot assume all nodes have heard
68+
# about all newly-submitted transactions within a matter of only 1-2
69+
# seconds.
70+
#
71+
# An alternative using elements-cli would use the subcommand gettxout,
72+
# again polling to ensure that the local node has received a copy of the
73+
# relevant transaction!
74+
75+
propagation_check(){
76+
# TODO: Give a useful error if this times out.
77+
echo -n "Checking for transaction $FAUCET_TRANSACTION via Liquid API..."
78+
for _ in {1..60}; do
79+
if curl -sSL "$1""$FAUCET_TRANSACTION" | jq ".vout[0]" 2>/dev/null | tee "$TMPDIR"/faucet-tx-data.json | jq -e >/dev/null 2>&1
80+
then
81+
echo " found."
82+
break
83+
else
84+
echo -n "."
85+
fi
86+
sleep 1
87+
done
88+
}
89+
90+
pause
91+
92+
# Compile program
93+
echo simc "$PROGRAM_SOURCE"
94+
simc "$PROGRAM_SOURCE"
95+
96+
pause
97+
98+
# Extract the compiled program from the output of that command
99+
COMPILED_PROGRAM=$(simc "$PROGRAM_SOURCE" | tail -1)
100+
101+
echo hal-simplicity simplicity info "$COMPILED_PROGRAM"
102+
hal-simplicity simplicity info "$COMPILED_PROGRAM" | jq
103+
# You can get the human-readable display of the low-level combinators with
104+
# hal-simplicity simplicity info "$COMPILED_PROGRAM" | jq -r .commit_decode
105+
# but this isn't used anywhere in the transaction.
106+
CMR=$(hal-simplicity simplicity info "$COMPILED_PROGRAM" | jq -r .cmr)
107+
CONTRACT_ADDRESS=$(hal-simplicity simplicity info "$COMPILED_PROGRAM" | jq -r .liquid_testnet_address_unconf)
108+
echo
109+
110+
for variable in CMR CONTRACT_ADDRESS
111+
do
112+
echo -n "$variable="
113+
eval echo \$$variable
114+
done
115+
116+
pause
117+
118+
# Here we use a curl command to contact the Liquid Testnet faucet to
119+
# ask it to fund our contract. The HTML result is unstructured text
120+
# data, so we use a sed substitution to pull out the txid.
121+
echo Running curl to connect to Liquid Testnet faucet...
122+
FAUCET_TRANSACTION=$(curl "https://liquidtestnet.com/faucet?address=$CONTRACT_ADDRESS&action=lbtc" 2>/dev/null | sed -n "s/.*with transaction \([0-9a-f]*\)\..*$/\1/p")
123+
124+
echo "FAUCET_TRANSACTION=$FAUCET_TRANSACTION"
125+
126+
pause
127+
128+
# Ask hal-simplicity to create a minimal PSET which asks to spend the
129+
# value that the faucet sent to our contract, by sending it back to
130+
# FAUCET_ADDRESS (the address the contract is asked to send this value
131+
# to) -- less a fee.
132+
#
133+
# Note that this hard-coded fee is higher than required. The concept of
134+
# weight can be used to calculate the minimum appropriate fee, but we'll
135+
# need other tools to determine it.
136+
echo hal-simplicity simplicity pset create '[ { "txid": "'"$FAUCET_TRANSACTION"'", "vout": 0 } ]' '[ { "'"$FAUCET_ADDRESS"'": 0.00099900 }, { "fee": 0.00000100 } ]'
137+
PSET=$(hal-simplicity simplicity pset create '[ { "txid": "'"$FAUCET_TRANSACTION"'", "vout": 0 } ]' '[ { "'"$FAUCET_ADDRESS"'": 0.00099900 }, { "fee": 0.00000100 } ]' | jq -r .pset)
138+
139+
echo "Minimal PSET is $PSET"
140+
141+
pause
142+
143+
# Now we will attach a lot of stuff to this PSET.
144+
145+
# First, we want to know more details related to the incoming transaction
146+
# that funded the contract and that we are now attempting to spend.
147+
echo "Looking up faucet transaction details."
148+
149+
# Note that the elements-cli version would also require polling in a loop
150+
# until the local elementsd has a copy of the transaction in its mempool
151+
# (this isn't guaranteed to be true instantly). The JSON details are also
152+
# slightly different from the API in this case, including measuring the
153+
# value in a different base unit.
154+
# echo $ELEMENTS_CLI gettxout $FAUCET_TRANSACTION 0
155+
# $ELEMENTS_CLI gettxout $FAUCET_TRANSACTION 0 | tee $TMPDIR/faucet-tx-data.orig.json | jq
156+
# HEX=$(jq -r .scriptPubKey.hex < $TMPDIR/faucet-tx-data.json)
157+
# ASSET=$(jq -r .asset < $TMPDIR/faucet-tx-data.json)
158+
# VALUE=$(jq -r .value < $TMPDIR/faucet-tx-data.json)
159+
160+
propagation_check https://liquid.network/liquidtestnet/api/tx/
161+
cat "$TMPDIR"/faucet-tx-data.json | jq
162+
163+
HEX=$(jq -r .scriptpubkey < "$TMPDIR"/faucet-tx-data.json)
164+
ASSET=$(jq -r .asset < "$TMPDIR"/faucet-tx-data.json)
165+
VALUE=0.00$(jq -r .value < "$TMPDIR"/faucet-tx-data.json)
166+
167+
echo "Extracted hex:asset:value parameters $HEX:$ASSET:$VALUE"
168+
169+
pause
170+
171+
echo hal-simplicity simplicity pset update-input "$PSET" 0 -i "$HEX:$ASSET:$VALUE" -c "$CMR" -p "$INTERNAL_KEY"
172+
hal-simplicity simplicity pset update-input "$PSET" 0 -i "$HEX:$ASSET:$VALUE" -c "$CMR" -p "$INTERNAL_KEY" | tee "$TMPDIR"/updated.json | jq
173+
174+
PSET=$(cat "$TMPDIR"/updated.json | jq -r .pset)
175+
176+
pause
177+
178+
# Now we have to generate the sighash and then, for this case, make
179+
# signatures on it corresponding to the signatures of 2 out of 3
180+
# authorized keys. This allows us to build the witness that will be
181+
# input to the Simplicity program, convincing it to approve this
182+
# transaction.
183+
#
184+
# Note that these signatures are specific to this individual
185+
# transaction (so, calculating them requires a copy of the PSET).
186+
# In the real-world case where these signatures are actually made
187+
# by different people or devices, the PSET should be sent to them
188+
# and they should make the signatures and send their respective
189+
# numerical signature values back.
190+
191+
# Signature 1
192+
echo "Signing on behalf of Alice using private key $PRIVKEY_1"
193+
echo hal-simplicity simplicity sighash "$PSET" 0 "$CMR" -x "$PRIVKEY_1"
194+
hal-simplicity simplicity sighash "$PSET" 0 "$CMR" -x "$PRIVKEY_1" | jq
195+
SIGNATURE_1=$(hal-simplicity simplicity sighash "$PSET" 0 "$CMR" -x "$PRIVKEY_1" | jq -r .signature)
196+
echo "Alice's signature is $SIGNATURE_1 (different from JSON due to signing nonce)"
197+
198+
pause
199+
200+
# No signature 2
201+
echo "Bob's signature using private key $PRIVKEY_2 is"
202+
echo "intentionally absent in this demo."
203+
204+
pause
205+
206+
# Signature 3
207+
echo "Signing on behalf of Charlie using private key $PRIVKEY_3"
208+
echo hal-simplicity simplicity sighash "$PSET" 0 "$CMR" -x "$PRIVKEY_3"
209+
hal-simplicity simplicity sighash "$PSET" 0 "$CMR" -x "$PRIVKEY_3" | jq
210+
SIGNATURE_3=$(hal-simplicity simplicity sighash "$PSET" 0 "$CMR" -x "$PRIVKEY_3" | jq -r .signature)
211+
echo "Charlie's signature is $SIGNATURE_3 (different from JSON due to signing nonce)"
212+
213+
# Put the signatures into the appropriate place in the .wit file
214+
# (these substitutions are extremely hard-coded for this specific
215+
# .wit file)
216+
echo "Copying signatures into copy of witness file $WITNESS_FILE..."
217+
cp $WITNESS_FILE "$TMPDIR"/witness.wit
218+
sed -i "s/\[Some([^)]*)/[Some(0x$SIGNATURE_1)/" "$TMPDIR"/witness.wit
219+
sed -i "s/Some([^)]*)]/Some(0x$SIGNATURE_3)]/" "$TMPDIR"/witness.wit
220+
221+
echo "Contents of witness:"
222+
cat "$TMPDIR"/witness.wit
223+
224+
pause
225+
226+
# The compiled program is not itself different when compiled with the
227+
# witness, but it's usual that it would be compiled once without a
228+
# witness when sending assets to the contract, as we do above, and once
229+
# with a witness when claiming assets from the contract, as we do here.
230+
# Those would usually be done by different people on different occasions.
231+
echo "Recompiling Simplicity program with attached populated witness file..."
232+
echo simc "$PROGRAM_SOURCE" "$TMPDIR"/witness.wit
233+
simc "$PROGRAM_SOURCE" "$TMPDIR"/witness.wit | tee "$TMPDIR"/compiled-with-witness
234+
235+
# Maybe simc should also output structured data like JSON!
236+
PROGRAM=$(cat "$TMPDIR"/compiled-with-witness | sed '1d; 3,$d')
237+
WITNESS=$(cat "$TMPDIR"/compiled-with-witness | sed '1,3d; 5,$d')
238+
239+
pause
240+
241+
echo hal-simplicity simplicity pset finalize "$PSET" 0 "$PROGRAM" "$WITNESS"
242+
hal-simplicity simplicity pset finalize "$PSET" 0 "$PROGRAM" "$WITNESS" | jq
243+
PSET=$(hal-simplicity simplicity pset finalize "$PSET" 0 "$PROGRAM" "$WITNESS" | jq -r .pset)
244+
245+
pause
246+
247+
echo hal-simplicity simplicity pset extract "$PSET"
248+
hal-simplicity simplicity pset extract "$PSET" | jq
249+
RAW_TX=$(hal-simplicity simplicity pset extract "$PSET" | jq -r)
250+
251+
echo "Raw transaction is $RAW_TX"
252+
253+
pause
254+
255+
propagation_check https://blockstream.info/liquidtestnet/api/tx/
256+
257+
# With a sufficiently up-to-date local copy of elementsd, we could also use
258+
# TXID=$("$ELEMENTS_CLI" sendrawtransaction "$RAW_TX")
259+
# instead of using the web API.
260+
echo "Submitting raw transaction via Liquid Testnet web API..."
261+
echo -n "Resulting transaction ID is "
262+
TXID=$(curl -X POST "https://blockstream.info/liquidtestnet/api/tx" -d "$RAW_TX" 2>/dev/null)
263+
echo "$TXID"
264+
echo
265+
echo "You can view it online at https://blockstream.info/liquidtestnet/tx/$TXID?expand"
266+
267+
echo

docs/getting-started/quickstart.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SimplicityHL Quickstart
2+
We'll use only two tools initially: `simc` (the SimplicityHL compiler) and `hal-simplicity` (an all-purpose Simplicity utility that can do various tasks related to programs and transactions).
3+
4+
Both of these can be installed with `cargo`; make sure you <a href="https://doc.rust-lang.org/stable/cargo/getting-started/installation.html">have `cargo` installed</a> first. The demo script also requires `jq` and `curl` (for parsing JSON data and connecting to Liquid Testnet API endpoints).
5+
6+
You will also need to download the <a href="/assets/p2ms-demo.sh">p2ms-demo.sh</a> bash script from this site. Then mark it executable with `chmod +x p2ms-demo`.
7+
8+
The steps below should then be sufficient to perform your first Simplicity transaction with an on-chain contract written in SimplicityHL!
9+
10+
```
11+
cargo install simplicityhl
12+
cargo install --git https://github.com/BlockstreamResearch/hal-simplicity
13+
14+
# We'll check out a local copy of the SimplicityHL repository to use its
15+
# contract code examples. If you choose to check this out under a different
16+
# path, change the path to the source files in the p2ms-demo script below, as
17+
# it defaults to assuming that the example SimplicityHL source code is found
18+
# under ~/src/SimplicityHL/examples.
19+
pushd ~/src
20+
git clone https://github.com/BlockstreamResearch/SimplicityHL
21+
popd
22+
23+
./p2ms-demo.sh
24+
```
25+
26+
This demo script exercises a Pay to Multisig contract written in SimplicityHL by performing a real transaction to and from this contract on the Liquid Testnet. You can look at the contents of `p2ms-demo.sh` to understand more about the steps it performs, or use it as a basis for performing Liquid Testnet transactions with other Simplicity contracts.

0 commit comments

Comments
 (0)