diff --git a/share/rpcuser/assets_tutorial.sh b/share/rpcuser/assets_tutorial.sh new file mode 100644 index 0000000000..954a367b81 --- /dev/null +++ b/share/rpcuser/assets_tutorial.sh @@ -0,0 +1,428 @@ + +shopt -s expand_aliases + +rm -r ~/elementsdir1 +rm -r ~/elementsdir2 +rm -r ~/bitcoindir +mkdir ~/elementsdir1 +mkdir ~/elementsdir2 +mkdir ~/bitcoindir + +# First we need to set up our config files to walk through this demo + +cat < ~/elementsdir1/elements.conf +# Standard bitcoind stuff +rpcuser=user1 +rpcpassword=password1 +rpcport=18884 +port=18886 + +# Over p2p we will only connect to local other elementsd +connect=localhost:18887 + +regtest=1 +daemon=1 +# Make sure you set listen after -connect, otherwise neither +# will accept incoming connections! +listen=1 +# Just for looking at random txs +txindex=1 + +# This is the script that controls pegged in funds in Bitcoin network +# Users will be pegging into a P2SH of this, and the "watchmen" +# can then recover these funds and send them to users who desire to peg out. +# This template is 1-of-1 checkmultisig +#fedpegscript=512151ae + +# This is the script that controls how blocks are made +# We have to supply a signature that satisfies this to create +# a valid block. +#signblockscript=512151ae + +# We want to validate pegins by checking with bitcoind if header exists +# in best known chain, and how deep. We combine this with pegin +# proof included in the pegin to get full security. +validatepegin=1 + +# If in the same datadir and using standard ports, these are unneeded +# thanks to cookie auth. If not, like in our situation, elementsd needs +# more info to connect to bitcoind: +mainchainrpcport=18888 +mainchainrpcuser=user3 +mainchainrpcpassword=password3 +EOF + +cat < ~/elementsdir2/elements.conf +rpcuser=user2 +rpcpassword=password2 +rpcport=18885 +port=18887 +connect=localhost:18886 + +regtest=1 +daemon=1 +listen=1 +txindex=1 + +#fedpegscript=5151ae +#signblockscript=5151ae + +mainchainrpcport=18888 +mainchainrpcuser=user3 +mainchainrpcpassword=password3 +validatepegin=1 +EOF + +cat < ~/bitcoindir/bitcoin.conf +rpcuser=user3 +rpcpassword=password3 +rpcport=18888 +port=18889 + +regtest=1 +testnet=0 +daemon=1 +txindex=1 +EOF + +ELEMENTSPATH="." +BITCOINPATH="." + +alias e1-cli="$ELEMENTSPATH/elements-cli -datadir=$HOME/elementsdir1" +alias e1-dae="$ELEMENTSPATH/elementsd -datadir=$HOME/elementsdir1" +alias e2-cli="$ELEMENTSPATH/elements-cli -datadir=$HOME/elementsdir2" +alias e2-dae="$ELEMENTSPATH/elementsd -datadir=$HOME/elementsdir2" + +alias b-cli="$BITCOINPATH/bitcoin-cli -datadir=$HOME/bitcoindir" +alias b-dae="$BITCOINPATH/bitcoind -datadir=$HOME/bitcoindir" + +# Should throw an error, can't connect to bitcoin daemon to validate pegins +e1-dae + +# Need to start bitcoind first, elementsd will wait until bitcoind gives warmup finished status +b-dae +e1-dae +e2-dae + +# Prime the chain, see "immature balance" holds all funds until genesis is mature +e1-cli getwalletinfo + +# Mining for now is OP_TRUE +e1-cli generate 101 + +# Now we have 21M OP_TRUE value +e1-cli getwalletinfo +e2-cli getwalletinfo + +# Primed and ready + +######## WALLET ########### + + +#Sample raw transaction RPC API + +# ~Core API + +#* `getrawtransaction 1` +#* `gettransaction 1` +#* `listunspent` +#* `decoderawtransaction ` +#* `sendrawtransaction ` +#* `validateaddress
+#* `listreceivedbyaddress ` + +# Elements Only API + +#* `blindrawtransaction ` +#* `dumpblindingkey
` +#* `importblindingkey ` + +# But let's start with a managed wallet example + +# First, drain OP_TRUE +e1-cli sendtoaddress $(e1-cli getnewaddress) 10500000 "" "" true +e1-cli generate 101 +e2-cli sendtoaddress $(e2-cli getnewaddress) 10500000 "" "" true +e2-cli generate 101 + +# Funds should be evenly split +e1-cli getwalletinfo +e2-cli getwalletinfo + +# Have Bob send coins to himself using a blinded Elements address! +# Blinded addresses start with `CTE`, unblinded `2` +ADDR=$(e2-cli getnewaddress) + +# How do we know it's blinded? Check for blinding key, unblinded address. +e2-cli validateaddress $ADDR + +TXID=$(e2-cli sendtoaddress $ADDR 1) + +e2-cli generate 1 + +# Now let's examine the transaction, both in wallet and without + +# In-wallet, take a look at blinding information +e2-cli gettransaction $TXID +# e1 doesn't have in wallet +e1-cli gettransaction $TXID + +# public info, see blinded ranges, etc +e1-cli getrawtransaction $TXID 1 + +# Now let's import the key to spend +e1-cli importprivkey $(e2-cli dumpprivkey $ADDR) + +# We can't see output value info though +e1-cli gettransaction $TXID +# And it won't show in balance or known outputs +e1-cli getwalletinfo +# Amount for transaction is unknown, so it is not shown. +e1-cli listunspent 1 1 + +# Solution: Import blinding key +e1-cli importblindingkey $ADDR $(e2-cli dumpblindingkey $ADDR) + +# Check again, funds should show +e1-cli getwalletinfo +e1-cli listunspent 1 1 +e1-cli gettransaction $TXID + +#Exercises +#=== +# Resources: https://bitcoin.org/en/developer-documentation +#1. Find the change output in one of your transactions. +#2. Use both methods to get the total input value of the transaction. +#3. Find your UTXO with the most confirmations. +#4. Create a raw transaction that pays 0.1 coins in fees and has two change addresses. +#5. Build blinded multisig p2sh + +###### ASSETS ####### + +# Many of the RPC calls have added asset type or label +# arguments and reveal alternative asset information. With no argument all are listed: +e1-cli getwalletinfo + +# Notice we now see "bitcoin" as an asset. This is the asset label for the hex for "bitcoin" which can be discovered: +e1-cli dumpassetlabels + +# You can also filter calls using specific asset hex or labels: +e1-cli getwalletinfo bitcoin +# bitcoin's hex asset type +e1-cli getwalletinfo 09f663de96be771f50cab5ded00256ffe63773e2eaa9a604092951cc3d7c6621 + +# We can also issue our own assets, 1 asset and 1 reissuance token in this case +ISSUE=$(e1-cli issueasset 1 1) +echo $ISSUE +ASSET=$(echo $ISSUE | jq '.asset' | tr -d '"') +echo $ASSET + +# From there you can look at the issuances you have in your wallet +e1-cli listissuances + +# If you gave `issueasset` a 2nd argument greater than 0, you can also reissue the base asset +e1-cli reissueasset $ASSET 1 + +# or make another unblinded asset issuance, with only reissuance tokens initially +e1-cli issueasset 0 1 false + +# Then two issuances for that particular asset will show +e1-cli listissuances $ASSET + +# To label the asset add this to your elements.conf file then restart your daemon: +# assetdir=$ASSET:yourlabelhere +# It really doesn't matter what you call it, labels are local things only. + +# To send issued assets, add an additional argument to sendtoaddress using the hex or label +e1-cli sendtoaddress $(e2-cli getnewaddress) 1 "" "" false $ASSET +e2-cli getwalletinfo $ASSET +e2-cli generate 1 + +# e2 doesn't know about the issuance for the transaction sending him the new asset +e2-cli listissuances + +# let's import an associated address and rescan +TXID=$(echo $ISSUE | jq '.txid' | tr -d '"') +ADDR=$(e1-cli gettransaction $TXID | jq '.details[0].address' | tr -d '"') + +e2-cli importaddress $ADDR + +# e2 now sees issuance, but doesn't know amounts as they are blinded +e2-cli listissuances + +# We need to import the issuance blinding key. We refer to issuances by their txid/vin pair +# as there is only one per input +VIN=$(echo $ISSUE | jq '.vin' | tr -d '"') +ISSUEKEY=$(e1-cli dumpissuanceblindingkey $TXID $VIN) +echo $ISSUEKEY + +e2-cli importissuanceblindingkey $TXID $VIN $ISSUEKEY + +# Now e2 can see issuance amounts and blinds +e2-cli listissuances + +###### BLOCKSIGNING ####### + +# Recall blocksigning is OP_TRUE +e1-cli generate 1 + +# Let's set it to something more interesting... 2-of-2 multisig + +# First lets get some keys from both clients to make our block "challenge" +ADDR1=$(e1-cli getnewaddress) +ADDR2=$(e2-cli getnewaddress) +VALID1=$(e1-cli validateaddress $ADDR1) +PUBKEY1=$(echo $VALID1 | python3 -c "import sys, json; print(json.load(sys.stdin)['pubkey'])") +VALID2=$(e2-cli validateaddress $ADDR2) +PUBKEY2=$(echo $VALID2 | python3 -c "import sys, json; print(json.load(sys.stdin)['pubkey'])") + +KEY1=$(e1-cli dumpprivkey $ADDR1) +KEY2=$(e2-cli dumpprivkey $ADDR2) + +e1-cli stop +e2-cli stop + +# Now filled with the pubkeys as 2-of-2 checkmultisig +SIGNBLOCKARG="-signblockscript=5221$(echo $PUBKEY1)21$(echo $PUBKEY2)52ae" + +# Wipe out the chain and wallet to get funds with new genesis block +# You can not swap out blocksigner sets as of now for security reasons, +# so we start fresh on a new chain. +rm -r ~/elementsdir1/elementsregtest/blocks +rm -r ~/elementsdir1/elementsregtest/chainstate +rm ~/elementsdir1/elementsregtest/wallet.dat +rm -r ~/elementsdir2/elementsregtest/blocks +rm -r ~/elementsdir2/elementsregtest/chainstate +rm ~/elementsdir2/elementsregtest/wallet.dat + +e1-dae $SIGNBLOCKARG +e2-dae $SIGNBLOCKARG + +# Now import signing keys +e1-cli importprivkey $KEY1 +e2-cli importprivkey $KEY2 + +# Generate no longer works, even if keys are in wallet +e1-cli generate 1 +e2-cli generate 1 + +# Let's propose and accept some blocks, e1 is master! +HEX=$(e1-cli getnewblockhex) + +# Unsigned is no good +# 0 before, 0 after +e1-cli getblockcount + +e1-cli submitblock $HEX + +# Still 0 +e1-cli getblockcount + +#### +# Signblock tests validity except block signatures +# This signing step can be outsourced to a HSM signing to enforce business logic of any sort +# See Strong Federations paper +SIGN1=$(e1-cli signblock $HEX) +SIGN2=$(e2-cli signblock $HEX) +#### + +# We now can gather signatures any way you want, combine them into a fully signed block +BLOCKRESULT=$(e1-cli combineblocksigs $HEX '''["'''$SIGN1'''", "'''$SIGN2'''"]''') + +COMPLETE=$(echo $BLOCKRESULT | python3 -c "import sys, json; print(json.load(sys.stdin)['complete'])") +SIGNBLOCK=$(echo $BLOCKRESULT | python3 -c "import sys, json; print(json.load(sys.stdin)['hex'])") + +# Should get True here as we have signatures from each key +echo $COMPLETE + +# Now submit the block, doesn't matter who +e2-cli submitblock $SIGNBLOCK + +# We now have moved forward one block! +e1-cli getblockcount +e2-cli getblockcount + +e1-cli stop +e2-cli stop + +# Further Exercises: +# 1.Make funny/different block block challenge? modify generate to allow arbitrary proof, instead of from wallet only +# 2.Arbitrary consensus change? +# 3.Make a python script that does round-robin consensus + +######## Pegging ####### + +# Everything pegging related can be done inside the Elements daemon directly, except for +# pegging out. This is due to the multisig pool aka Watchmen that controls the bitcoin +# on the Bitcoin blockchain. That is the easiest part to get wrong, and by far the most +# important as there is no going back if you lose the funds. + +# Wipe out the chain and wallet to get funds with new genesis block +rm -r ~/elementsdir1/elementsregtest/blocks +rm -r ~/elementsdir1/elementsregtest/chainstate +rm ~/elementsdir1/elementsregtest/wallet.dat +rm -r ~/elementsdir2/elementsregtest/blocks +rm -r ~/elementsdir2/elementsregtest/chainstate +rm ~/elementsdir2/elementsregtest/wallet.dat + +FEDPEGARG="-fedpegscript=5221$(echo $PUBKEY1)21$(echo $PUBKEY2)52ae" + +# Back to OP_TRUE blocks, re-using pubkeys for pegin pool instead +# Keys can be the same or different, doesn't matter +e1-dae $FEDPEGARG +e2-dae $FEDPEGARG + +# Mature some outputs on each side +e1-cli generate 101 +b-cli generate 101 + +# We have to lock up some of the funds first. Regtest(what we're running) has all funds as OP_TRUE +# but this is not the case in testnet/production. Doesn't matter where we send it +# inside Bitcoin, this is just a hack to lock some funds up. +e1-cli sendtomainchain $(b-cli getnewaddress) 50 + +# Mature the pegout +e1-cli generate 101 + +# Now we can actually start pegging in. Examine the pegin address fields +e1-cli getpeginaddress +# Changes each time as it's a new sidechain address as well as new "tweak" for the watchmen keys +# mainchain_address : where you send your bitcoin from Bitcoin network +# sidechain_address : where the bitcoin will end up on the sidechain after pegging in + +# Each call of this takes the pubkeys defined in the config file, adds a random number to them +# that is essetially the hash of the sidechain_address and other information, +# then creates a new P2SH Bitcoin address from that. We reveal that "tweak" to the functionaries +# during `claimpegin`, then they are able to calculate the necessary private key and control +# funds. +e1-cli getpeginaddress + +ADDRS=$(e1-cli getpeginaddress) + +MAINCHAIN=$(echo $ADDRS | python3 -c "import sys, json; print(json.load(sys.stdin)['mainchain_address'])") +SIDECHAIN=$(echo $ADDRS | python3 -c "import sys, json; print(json.load(sys.stdin)['sidechain_address'])") + +#Send funds to unique watchmen P2SH address +TXID=$(b-cli sendtoaddress $MAINCHAIN 1) + +# Mature pegin funds to avoid reorg -> fractional reserve +b-cli generate 101 +PROOF=$(b-cli gettxoutproof '''["'''$TXID'''"]''') +RAW=$(b-cli getrawtransaction $TXID) + +# Attempt claim! +CLAIMTXID=$(e1-cli claimpegin $SIDECHAIN $RAW $PROOF) + +# Other node should accept to mempool and mine +e2-cli generate 1 + +# Should see confirmations +e1-cli getrawtransaction $CLAIMTXID 1 + +#### Pegging Out #### + +#sendtomainaddress + +#Exercises +#1. Implement really dumb/unsafe watchmen to allow pegouts for learning purposes +# Recover tweak from pegin, add to privkey, combined tweaked pubkeys into a redeemscript, add to Core wallet