depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit 21f0a4befc6336aeea39149149a0e1d5cfc66654
parent 487fc93c99317f0fbedf2a91550e2dcf1ecdffc9
Author: Antoine A <>
Date:   Thu, 11 Nov 2021 23:45:56 +0100

Setup GPG sign

Diffstat:
A.gitignore | 1+
ACargo.lock | 663+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACargo.toml | 18++++++++++++++++++
Aresearch.md | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main.rs | 338+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest.js | 4++++
6 files changed, 1160 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock @@ -0,0 +1,663 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "argh" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f023c76cd7975f9969f8e29f0e461decbdc7f51048ce43427107a3d192f1c9bf" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ad219abc0c06ca788aface2e3a1970587e3413ab70acd20e54b6ec524c1f8f" +dependencies = [ + "argh_shared", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + +[[package]] +name = "bech32" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" + +[[package]] +name = "bitcoin" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a41df6ad9642c5c15ae312dd3d074de38fd3eb7cc87ad4ce10f90292a83fe4d" +dependencies = [ + "bech32", + "bitcoin_hashes", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-bech32" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754eb4c7f35c031f33c95cc257b4c4192a5c9d3de637d3ee78ab052a3f35da57" +dependencies = [ + "bech32", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoincore-rpc" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b8d99d58466295cb2bf72c6959b784d59f8f0d6977458d2ba3eb75c834f36c3" +dependencies = [ + "bitcoincore-rpc-json", + "jsonrpc", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoincore-rpc-json" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce91de73c61f5776cf938bfa88378c5b404a70e3369b761dacbe6024fea79dd" +dependencies = [ + "bitcoin", + "serde", + "serde_json", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clipboard-win" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "depolymerization" +version = "0.1.0" +dependencies = [ + "argh", + "bitcoin-bech32", + "bitcoincore-rpc", + "bs58", + "digest", + "fastrand", + "rustyline", + "sha2", + "static_init", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "error-code" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fastrand" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" +dependencies = [ + "instant", +] + +[[package]] +name = "fd-lock" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8806dd91a06a7a403a8e596f9bfbfb34e469efbc363fc9c9713e79e26472e36" +dependencies = [ + "cfg-if", + "libc", + "winapi", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "jsonrpc" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad24d69a8a0698db8ffb9048e937e8ae3ee3bc45772a5d7b6979b1d2d5b6a9f7" +dependencies = [ + "base64-compat", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3bb9a13fa32bc5aeb64150cd3f32d6cf4c748f8f8a417cce5d2eb976a8370ba" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "rustyline" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790487c3881a63489ae77126f57048b42d62d3b2bafbf37453ea19eedb6340d6" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "dirs-next", + "fd-lock", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "scopeguard", + "smallvec", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827cb7cce42533829c792fc51b82fbf18b125b45a702ef2c8be77fce65463a7b" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "static_init" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12a91dc56a0b47b48a3150157803b02cf82aa4c15ed499cb9b7ee0ff39941b1" +dependencies = [ + "bitflags", + "cfg_aliases", + "libc", + "parking_lot", + "parking_lot_core", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3fb6e118f42ea83548bb2194a79f5cbc8866fc38d87f6548139a7fac10e732" +dependencies = [ + "cfg_aliases", + "memchr", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "str-buf" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "depolymerization" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitcoincore-rpc = "0.14.0" +sha2 = "0.9.8" +bs58 = "0.4.0" +fastrand = "1.5.0" +digest = "0.9.0" +argh = "0.1.6" +rustyline = "9.0.0" +bitcoin-bech32 = "0.12.1" +static_init = "1.0.1" + diff --git a/research.md b/research.md @@ -0,0 +1,136 @@ +# Depolymerization + +## The problem + +We want to store metadata in the blockchain to identify taler wire transaction. + +## Bitcoin + +### Methods + +#### Fake legacy address + +The good: + +- Work with common wallets +- Multiple payload of 20B + +The bad: + +- Two fees: higher weight fee and a a dust to burn (546 satoshis for a legacy + address) +- Pollute the blockchain by bloating UTXO (Unspent Transaction Outputs) +- Bitcoin URI does not support multiple recipients + +Assuming people using cryptocurrencies are techies, asking them to enter +multiple recipients manually doe no sound that bad. + +#### Fake SegWit address + +Same as fake legacy address but: + +- dust burn reduced from 546sa/20B to 294sa/20B (0.3247€ to 0.1748€) +- for huge address 330sa/32B (0.1784€) but against standard + +The good: + +- Work with common wallets +- Multiple payload of 20B + +The bad: + +- Two fees: higher weight fee and a a dust to burn (546 satoshis for a legacy + address) +- Pollute the blockchain by bloating UTXO (Unspent Transaction Outputs) +- Bitcoin URI does not support multiple recipients + +Assuming people using cryptocurrencies are techies, asking them to enter +multiple recipients manually doe no sound that bad. + +#### OP_RETURN + +The good: + +- Recommended way to store metadata +- No blockchain abuse +- Only cost is a higher weight fee + +The bad: + +- Require custom raw transaction +- Require custom client (need more research) +- Max 40B or 80B payload (one OP_RETURN by transaction) + +Solution: + +- Create a custom online wallet middleware (https://coinb.in/#txinputs) +- Good user interface +- Lot of complexity + +#### Unique address + +The good: + +- The normal way to do transaction + +The bad: + +- We need to store state in a database +- We need to add a new API routine +- Complicated to do correctly + +`sendmany` shuffle outputs for privacy reasons in RPC since v0.17, and the order +cannot be relied upon for other wallets. + +Solutions: + +- Encode order in payload + +### Blockchain trust + +We can virtually never be sure that a transaction have been made in durable +manner. By waiting for 6 blocks (~1h) we ca, have a significant assurance. + +- Make the number of confirmation blocks configurable + +TODO: + +- Find a way to detect extended forks using bitcoin alert (-alertnotify or + getnetworkinfo RPC) +- Or detect it ourselves by reading row block using RPC +- How to handle removed transaction ? Do the wire history have to be consistent + ? + +### Refunds + +To refund we could send bitcoins back to the sender address (minus some fee of +course). However some people use wallet solutions using a shared address for +multiple users, in that case the refund will go to the solution but not to the +designated user. + +Do we care ? + +### Sources + +Pompianu, Livio & Bartoletti, Massimo & Bellomy, Bryn. (2019). A Journey into +Bitcoin Metadata. Journal of Grid Computing. 10.1007/s10723-019-09473-3. + +## Out + +OP_RETURN test + +## Ethereum + +- A transaction is from one address to another +- Have a arbitrary data payload for smart contract + +Can we use smart contract to read metadata ? + +Rust client library OpenEthereum - JSON RCP API + +## TODO + +- Magic bit detection +- Optimize magic bit +- lower level WitnessProgram alternative ? +- OP_RETURN logic diff --git a/src/main.rs b/src/main.rs @@ -0,0 +1,338 @@ +use std::{ + collections::{BTreeMap, HashSet}, + iter::repeat_with, + path::PathBuf, +}; + +use bitcoin_bech32::{constants::Network, u5, WitnessProgram}; +use bitcoincore_rpc::{ + bitcoin::{Address, Amount, Txid}, + jsonrpc::serde_json::Value, + Auth, Client, RpcApi, +}; +use static_init::dynamic; + +// https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp +#[dynamic] +static LEGACY_MIN_AMOUNT: Amount = Amount::from_sat(546); +#[dynamic] +static SEGWIT_MIN_AMOUNT: Amount = Amount::from_sat(294); + +const CLIENT: &str = "client"; +const WIRE: &str = "wire"; +const DATA_PATH: &str = "C:/Users/antoi/AppData/Roaming/Bitcoin/regtest"; +const RPC_URL: &str = "http://localhost:18443"; + +#[derive(argh::FromArgs)] +/// Bitcoin metadata tester +struct Args { + /// start as a sender + #[argh(switch, short = 'c')] + client: bool, + + /// start as a receiver + #[argh(switch, short = 'w')] + wire: bool, + + /// a message to send + #[argh(option)] + msg: Option<String>, +} + +fn encode_segwit_address( + network: Network, + is_first: bool, + magic_id: &[u8; 4], + key_half: &[u8; 16], +) -> String { + // Combine magic_it and the key half + let mut buf = vec![0u8; 20]; + buf[..4].copy_from_slice(magic_id); + buf[4..].copy_from_slice(key_half); + // Toggle first bit for ordering + if is_first { + buf[0] &= 0b0111_1111 // Unset first bit + } else { + buf[0] |= 0b1000_0000 // Set first bit + } + // Encode into an fake segwit address + WitnessProgram::new(u5::try_from_u8(0).unwrap(), buf, network) + .unwrap() + .to_address() +} + +fn encode_segwit_key(network: Network, key: &[u8; 32]) -> [String; 2] { + // Generate a random magic identifier + let mut magic_id = [0; 4]; + // TODO use secure os based random + magic_id.fill_with(|| fastrand::u8(..)); + // Split key in half; + let mut split = ([0; 16], [0; 16]); + split.0.copy_from_slice(&key[..16]); + split.1.copy_from_slice(&key[16..]); + [ + encode_segwit_address(network, true, &magic_id, &split.0), + encode_segwit_address(network, false, &magic_id, &split.1), + ] +} + +#[derive(Debug, Clone)] +pub enum DecodeError { + MissingSeqWitAddress, + MagicIdCollision, +} + +fn decode_segwit_msg(segwit_addrs: &[&str]) -> Result<[u8; 32], DecodeError> { + if segwit_addrs.len() < 2 { + return Err(DecodeError::MissingSeqWitAddress); + } + + let decoded: Vec<[u8; 20]> = segwit_addrs + .into_iter() + .filter_map(|addr| { + WitnessProgram::from_address(addr).ok().and_then(|wp| { + let pg = wp.program(); + if pg.len() == 20 { + let mut buf = [0; 20]; + buf.copy_from_slice(pg); + Some(buf) + } else { + None + } + }) + }) + .collect(); + + if decoded.len() < 2 { + return Err(DecodeError::MissingSeqWitAddress); + } + + let mut parts: Vec<(bool, [u8; 4], &[u8; 16])> = decoded + .iter() + .map(|c| { + let mut magic_id: [u8; 4] = c[..4].try_into().unwrap(); + let key_half: &[u8; 16] = c[4..].try_into().unwrap(); + let is_first = !c[0] & 0b1000_0000 == 0; + // Clear first bit + magic_id[0] &= 0b0111_1111; + (is_first, magic_id, key_half) + }) + .collect(); + let mut map = BTreeMap::new(); + + for (_, magic, _) in &parts { + match map.get_mut(magic) { + Some(prev) => *prev = true, + None => { + map.insert(*magic, false); + } + } + } + map.retain(|_, many| *many); + assert_eq!(map.len(), 1, "Two possible magic id"); + let magic_id = map.into_keys().next().unwrap(); + parts.retain(|(_, magic, _)| *magic == magic_id); + assert_eq!(parts.len(), 2, "Magic ID collision"); + + let mut key = [0; 32]; + for (is_first, _, half) in parts { + key[is_first as usize * 16..][..16].copy_from_slice(half); + } + Ok(key) +} + +#[test] +fn test_shuffle() { + for _ in 0..1000 { + let mut key = [0; 32]; + key.fill_with(|| fastrand::u8(..)); + let mut addresses = encode_segwit_key(Network::Bitcoin, &key); + fastrand::shuffle(&mut addresses); + let decoded = + decode_segwit_msg(&addresses.iter().map(|s| s.as_str()).collect::<Vec<&str>>()) + .unwrap(); + assert_eq!(key, decoded); + } +} + +#[test] +fn test_shuffle_many() { + for _ in 0..1000 { + let mut rng_address: Vec<String> = std::iter::repeat_with(|| { + let key: Vec<u8> = repeat_with(|| fastrand::u8(..)).take(20).collect(); + WitnessProgram::new(u5::try_from_u8(0).unwrap(), key, Network::Bitcoin) + .unwrap() + .to_address() + }) + .take(2) + .collect(); + + let mut key = [0; 32]; + key.fill_with(|| fastrand::u8(..)); + let mut addresses = encode_segwit_key(Network::Bitcoin, &key).to_vec(); + addresses.append(&mut rng_address); + fastrand::shuffle(&mut addresses); + let decoded = + decode_segwit_msg(&addresses.iter().map(|s| s.as_str()).collect::<Vec<&str>>()) + .unwrap(); + assert_eq!(key, decoded); + } +} + +fn common_rpc() -> Client { + Client::new( + RPC_URL, + Auth::CookieFile(PathBuf::from(DATA_PATH).join(".cookie")), + ) + .expect("Failed to open common client") +} +fn wallet_rpc(wallet: &str) -> Client { + Client::new( + &format!("{}/wallet/{}", RPC_URL, wallet), + Auth::CookieFile(PathBuf::from(DATA_PATH).join(".cookie")), + ) + .expect(&format!("Failed to open wallet '{}' client", wallet)) +} + +fn send_many(client: &Client, recipients: Vec<(String, Amount)>) -> bitcoincore_rpc::Result<Txid> { + let amounts = Value::Object( + recipients + .into_iter() + .map(|(addr, amount)| (addr, amount.as_btc().into())) + .collect(), + ); + client.call( + "sendmany", + &[ + "".into(), // dummy + amounts, // amounts + 0.into(), // minconf + "".into(), // comment + Value::Null, // substractfeefrom + false.into(), // replaceable + Value::Null, // conf_target + Value::Null, // estimate mode + 1.into(), // fee rate + false.into(), // verbose + ], + ) +} + +fn send_with_metadata( + rpc: &Client, + to: &Address, + amount: Amount, + metadata: &[u8], +) -> bitcoincore_rpc::Result<Txid> { + let addresses = encode_segwit_key(Network::Regtest, &metadata.try_into().unwrap()); + let mut recipients = vec![(to.to_string(), amount)]; + recipients.extend(addresses.into_iter().map(|addr| (addr, *SEGWIT_MIN_AMOUNT))); + send_many(rpc, recipients) +} + +fn last_metadata(rpc: &Client) -> bitcoincore_rpc::Result<Vec<u8>> { + let txs = rpc.list_transactions(None, None, None, None)?; + let last = txs.last().unwrap(); + + let info: Value = rpc.call( + "gettransaction", + &[last.info.txid.to_string().into(), Value::Null, true.into()], + )?; + let addresses: Vec<&str> = info["decoded"]["vout"] + .as_array() + .unwrap() + .into_iter() + .filter_map(|it| { + if it["value"].as_f64().unwrap() == SEGWIT_MIN_AMOUNT.as_btc() { + Some(it["scriptPubKey"]["address"].as_str().unwrap()) + } else { + None + } + }) + .collect(); + Ok(decode_segwit_msg(&addresses).unwrap().to_vec()) +} + +fn main() { + { + let existing_wallets: HashSet<String> = + std::fs::read_dir(PathBuf::from(DATA_PATH).join("wallets")) + .unwrap() + .filter_map(|it| it.ok()) + .map(|it| it.file_name().to_string_lossy().to_string()) + .collect(); + + let rpc = common_rpc(); + if !existing_wallets.contains(CLIENT) || !existing_wallets.contains(WIRE) { + println!("Generate new wallets"); + // Create wallets + rpc.create_wallet(&WIRE, None, None, None, None).unwrap(); + rpc.create_wallet(&CLIENT, None, None, None, None).unwrap(); + // Add 50 BTC to client wallet + let rpc = wallet_rpc(CLIENT); + let addr = rpc.get_new_address(None, None).unwrap(); + rpc.generate_to_address(101 /* Need 100 blocks to validate */, &addr) + .unwrap(); + } else { + rpc.load_wallet(&WIRE).ok(); + rpc.load_wallet(&CLIENT).ok(); + } + } + + println!("Initial state:"); + let client_rpc = wallet_rpc(CLIENT); + let wire_rpc = wallet_rpc(WIRE); + let client_addr = client_rpc.get_new_address(None, None).unwrap(); + let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); + println!( + "{} {} {}", + WIRE, + wire_addr, + wire_rpc.get_balance(None, None).unwrap() + ); + println!( + "{} {} {}", + CLIENT, + client_addr, + client_rpc.get_balance(None, None).unwrap() + ); + + let args: Args = argh::from_env(); + + if args.client { + println!("Start client"); + let mut rl = rustyline::Editor::<()>::new(); + loop { + let rl = rl.readline(">> "); + match rl { + Ok(line) => { + send_with_metadata( + &client_rpc, + &wire_addr, + *LEGACY_MIN_AMOUNT, + line.as_bytes(), + ) + .unwrap(); + client_rpc.generate_to_address(1, &client_addr).unwrap(); + } + Err(_) => break, + } + } + } else if args.wire { + println!("Start wire"); + loop { + wire_rpc.wait_for_new_block(60 * 60 * 1000).ok(); + let decoded = last_metadata(&wire_rpc).unwrap(); + println!(">> {}", String::from_utf8_lossy(&decoded)); + } + } else { + // Send metadata + let metadata: Vec<u8> = repeat_with(|| fastrand::u8(..)).take(32).collect(); + send_with_metadata(&client_rpc, &wire_addr, *LEGACY_MIN_AMOUNT, &metadata).unwrap(); + // Mine one block + client_rpc.generate_to_address(1, &client_addr).unwrap(); + // Read metadata + let decoded = last_metadata(&wire_rpc).unwrap(); + assert_eq!(metadata, decoded); + } +} diff --git a/test.js b/test.js @@ -0,0 +1,4 @@ +import { createRemote } from 'https://deno.land/x/gentle_rpc@v2.9/mod.ts'; +const remote = createRemote('http://127.0.0.1:18443'); +const info = await remote.call('getbestblockhash'); +console.log(info);