Add the sp wallet + daemon logic, some refactoring
This commit is contained in:
parent
2d044ec2c2
commit
5f4efa5aa3
384
Cargo.lock
generated
384
Cargo.lock
generated
@ -26,6 +26,21 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.80"
|
||||
@ -70,6 +85,12 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.9.1"
|
||||
@ -173,6 +194,12 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
@ -211,6 +238,25 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
@ -286,12 +332,57 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@ -412,6 +503,12 @@ version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
@ -480,6 +577,35 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
@ -490,6 +616,17 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.3"
|
||||
@ -497,7 +634,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -515,13 +653,22 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@ -574,6 +721,21 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
@ -593,6 +755,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@ -617,6 +785,12 @@ version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@ -754,6 +928,7 @@ dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"silentpayments",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@ -821,6 +996,36 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.2.3",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@ -871,6 +1076,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.49"
|
||||
@ -930,6 +1141,37 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@ -1023,7 +1265,7 @@ version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.2.3",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@ -1121,6 +1363,60 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@ -1152,13 +1448,22 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1167,13 +1472,28 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.4",
|
||||
"windows_aarch64_msvc 0.52.4",
|
||||
"windows_i686_gnu 0.52.4",
|
||||
"windows_i686_msvc 0.52.4",
|
||||
"windows_x86_64_gnu 0.52.4",
|
||||
"windows_x86_64_gnullvm 0.52.4",
|
||||
"windows_x86_64_msvc 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1182,42 +1502,84 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.1"
|
||||
|
@ -13,6 +13,7 @@ futures-util = { version = "0.3.28", default-features = false, features = ["sink
|
||||
log = "0.4.20"
|
||||
serde = { version = "1.0.193", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
serde_with = "3.6.0"
|
||||
silentpayments = { git = "https://github.com/cygnet3/rust-silentpayments", branch = "master", features = ['utils'] }
|
||||
tokio = { version = "1.0.0", features = ["io-util", "rt-multi-thread", "macros", "sync"] }
|
||||
tokio-stream = "0.1"
|
||||
|
24
src/constants.rs
Normal file
24
src/constants.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type SecretKeyString = String;
|
||||
type PublicKeyString = String;
|
||||
|
||||
pub const PSBT_SP_PREFIX: &str = "sp";
|
||||
pub const PSBT_SP_SUBTYPE: u8 = 0;
|
||||
pub const PSBT_SP_TWEAK_KEY: &str = "tweak";
|
||||
pub const PSBT_SP_ADDRESS_KEY: &str = "address";
|
||||
|
||||
pub const NUMS: &str = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
|
||||
|
||||
pub struct LogEntry {
|
||||
// pub time_millis: i64,
|
||||
// pub level: i32,
|
||||
// pub tag: String,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
pub struct SyncStatus {
|
||||
pub peer_count: u32,
|
||||
pub blockheight: u64,
|
||||
pub bestblockhash: String,
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result, Error};
|
||||
|
||||
use bitcoin::{consensus::deserialize, hashes::hex::FromHex};
|
||||
use bitcoin::{Amount, BlockHash, Transaction, Txid};
|
||||
use bitcoin::{block, Address, Amount, BlockHash, Network, OutPoint, Psbt, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid};
|
||||
use bitcoincore_rpc::json::{CreateRawTransactionInput, ListUnspentQueryOptions, ListUnspentResultEntry};
|
||||
use bitcoincore_rpc::{json, jsonrpc, Auth, Client, RpcApi};
|
||||
// use crossbeam_channel::Receiver;
|
||||
// use parking_lot::Mutex;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -113,7 +115,7 @@ impl Daemon {
|
||||
let mut rpc = rpc_connect()?;
|
||||
|
||||
loop {
|
||||
match rpc_poll(&mut rpc, true) {
|
||||
match rpc_poll(&mut rpc, false) {
|
||||
PollResult::Done(result) => {
|
||||
result.context("bitcoind RPC polling failed")?;
|
||||
break; // on success, finish polling
|
||||
@ -161,6 +163,78 @@ impl Daemon {
|
||||
.relay_fee)
|
||||
}
|
||||
|
||||
pub(crate) fn get_current_height(&self) -> Result<u64> {
|
||||
Ok(self
|
||||
.rpc
|
||||
.get_block_count()
|
||||
.context("failed to get block count")?
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn list_unspent_from_to(&self, minconf: Option<usize>, maxconf: Option<usize>) -> Result<Vec<json::ListUnspentResultEntry>> {
|
||||
Ok(self.rpc
|
||||
.list_unspent(
|
||||
minconf,
|
||||
maxconf,
|
||||
None,
|
||||
Some(false),
|
||||
Some(ListUnspentQueryOptions {
|
||||
maximum_count: Some(5),
|
||||
..Default::default()
|
||||
})
|
||||
)?)
|
||||
}
|
||||
|
||||
pub(crate) fn create_psbt(&self, utxo: ListUnspentResultEntry, spk: ScriptBuf, network: Network) -> Result<String> {
|
||||
let input = CreateRawTransactionInput {
|
||||
txid: utxo.txid,
|
||||
vout: utxo.vout,
|
||||
sequence: None
|
||||
};
|
||||
let address = Address::from_script(&spk, network)?;
|
||||
let mut outputs = HashMap::new();
|
||||
outputs.insert(address.to_string(), utxo.amount);
|
||||
let psbt = self.rpc
|
||||
.create_psbt(
|
||||
&vec![input],
|
||||
&outputs,
|
||||
None,
|
||||
None
|
||||
)?;
|
||||
Ok(psbt.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn process_psbt(&self, psbt: String) -> Result<String> {
|
||||
let processed_psbt = self.rpc
|
||||
.wallet_process_psbt(
|
||||
&psbt,
|
||||
None,
|
||||
None,
|
||||
None
|
||||
)?;
|
||||
match processed_psbt.complete {
|
||||
true => Ok(processed_psbt.psbt),
|
||||
false => Err(Error::msg("Failed to complete the psbt"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finalize_psbt(&self, psbt: String) -> Result<Vec<u8>> {
|
||||
let final_tx = self.rpc
|
||||
.finalize_psbt(&psbt, Some(true))?;
|
||||
|
||||
match final_tx.complete {
|
||||
true => Ok(final_tx.hex.expect("We shouldn't have an empty tx for a complete return")),
|
||||
false => Err(Error::msg("Failed to finalize psbt"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_network(&self) -> Result<Network> {
|
||||
let blockchain_info = self.rpc
|
||||
.get_blockchain_info()?;
|
||||
|
||||
Ok(blockchain_info.chain)
|
||||
}
|
||||
|
||||
pub(crate) fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
||||
self.rpc
|
||||
.send_raw_transaction(tx)
|
||||
|
55
src/db.rs
Normal file
55
src/db.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::{
|
||||
fs::{create_dir_all, remove_file, File},
|
||||
io::{Read, Write},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
env
|
||||
};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct FileWriter {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl FileWriter {
|
||||
pub fn new(file: &str) -> Result<Self> {
|
||||
match env::var("HOME") {
|
||||
Ok(home_dir) => {
|
||||
let mut config_path = PathBuf::from(home_dir);
|
||||
config_path.push(".4nk");
|
||||
config_path.push(file);
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
match create_dir_all(&config_path) {
|
||||
Ok(_) => Ok(Self { path: config_path }),
|
||||
Err(e) => Err(Error::new(e)),
|
||||
}
|
||||
},
|
||||
Err(e) => Err(Error::new(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_to_file<T: Serialize>(&self, data: &T) -> Result<()> {
|
||||
let json = serde_json::to_string(data)?;
|
||||
let mut file = File::create(self.path.clone())?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_from_file<T: for<'de> Deserialize<'de>>(&self) -> Result<T> {
|
||||
let mut file = File::open(self.path.clone())?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
let data: T = serde_json::from_str(&contents)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn delete(self) -> Result<()> {
|
||||
remove_file(self.path).map_err(Error::new)
|
||||
}
|
||||
}
|
184
src/main.rs
184
src/main.rs
@ -3,23 +3,33 @@ use std::{
|
||||
env,
|
||||
io::Error as IoError,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex},
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use bitcoin::{consensus::deserialize, secp256k1::PublicKey};
|
||||
use bitcoin::{consensus::deserialize, key::TapTweak, secp256k1::PublicKey, OutPoint, ScriptBuf, XOnlyPublicKey};
|
||||
use bitcoincore_rpc::json as bitcoin_json;
|
||||
use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt};
|
||||
|
||||
use log::{debug, error};
|
||||
use silentpayments::sending::SilentPaymentAddress;
|
||||
use silentpayments::secp256k1::rand::{thread_rng, Rng};
|
||||
use spclient::Recipient;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
|
||||
use anyhow::{Result, Error};
|
||||
|
||||
mod sp;
|
||||
mod daemon;
|
||||
mod spclient;
|
||||
mod constants;
|
||||
mod db;
|
||||
|
||||
use crate::daemon::Daemon;
|
||||
use crate::sp::VinData;
|
||||
use crate::spclient::{SpClient, SpendKey, OutputSpendStatus};
|
||||
|
||||
type Tx = UnboundedSender<Message>;
|
||||
|
||||
@ -80,81 +90,80 @@ fn flatten_msg(parts: &[Vec<u8>]) -> Vec<u8> {
|
||||
}
|
||||
|
||||
final_vec
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_zmq(peer_map: PeerMap, daemon: Daemon) {
|
||||
fn process_raw_tx_message(core_msg: &bitcoincore_zmq::Message, daemon: Arc<Mutex<Daemon>>) -> Result<Vec<u8>> {
|
||||
let tx: bitcoin::Transaction = deserialize(&core_msg.serialize_data_to_vec())?;
|
||||
|
||||
if tx.is_coinbase() {
|
||||
return Err(Error::msg("Can't process coinbase transaction"));
|
||||
}
|
||||
|
||||
let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len());
|
||||
let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len());
|
||||
for input in tx.input {
|
||||
outpoints.push((input.previous_output.txid.to_string(), input.previous_output.vout));
|
||||
let prev_tx = daemon.lock()
|
||||
.map_err(|e| Error::msg(format!("Failed to lock the daemon: {}", e)))?
|
||||
.get_transaction(&input.previous_output.txid, None)
|
||||
.map_err(|_| Error::msg("Failed to find previous transaction"))?;
|
||||
|
||||
if let Some(output) = prev_tx.output.get(input.previous_output.vout as usize) {
|
||||
let vin_data = VinData {
|
||||
script_sig: input.script_sig.to_bytes().to_vec(),
|
||||
txinwitness: input.witness.to_vec(),
|
||||
script_pub_key: output.script_pubkey.to_bytes()
|
||||
};
|
||||
match sp::get_pubkey_from_input(&vin_data) {
|
||||
Ok(Some(pubkey)) => pubkeys.push(pubkey),
|
||||
Ok(None) => continue,
|
||||
Err(e) => return Err(Error::msg(format!("Can't extract pubkey from input: {}", e))),
|
||||
}
|
||||
} else {
|
||||
return Err(Error::msg("Transaction with a non-existing input"));
|
||||
}
|
||||
}
|
||||
|
||||
let input_pub_keys: Vec<&PublicKey> = pubkeys.iter().collect();
|
||||
match silentpayments::utils::receiving::recipient_calculate_tweak_data(&input_pub_keys, &outpoints) {
|
||||
Ok(partial_tweak) => {
|
||||
let mut vecs = core_msg.serialize_to_vecs().to_vec();
|
||||
vecs.push(partial_tweak.serialize().to_vec());
|
||||
Ok(flatten_msg(&vecs))
|
||||
},
|
||||
Err(e) => Err(Error::msg(format!("Failed to compute tweak data: {}", e.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_zmq(peer_map: PeerMap, daemon: Arc<Mutex<Daemon>>) {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
debug!("Starting listening on Core");
|
||||
for msg in bitcoincore_zmq::subscribe_receiver(&["tcp://127.0.0.1:29000"]).unwrap() {
|
||||
match msg {
|
||||
Ok(core_msg) => {
|
||||
debug!("Received a {} message", core_msg.topic_str());
|
||||
let peers = peer_map.lock().unwrap();
|
||||
|
||||
let payload: Vec<u8>;
|
||||
match core_msg.topic_str() {
|
||||
"rawtx" => {
|
||||
let tx: bitcoin::Transaction = deserialize(&core_msg.serialize_data_to_vec()).unwrap();
|
||||
|
||||
if tx.is_coinbase() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut outpoints: Vec<(String, u32)> = Vec::with_capacity(tx.input.len());
|
||||
let mut pubkeys: Vec<PublicKey> = Vec::with_capacity(tx.input.len());
|
||||
for input in tx.input {
|
||||
outpoints.push((input.previous_output.txid.to_string(), input.previous_output.vout));
|
||||
let prev_tx = daemon.get_transaction(&input.previous_output.txid, None).unwrap();
|
||||
if let Some(output) = prev_tx.output.get(input.previous_output.vout as usize) {
|
||||
let vin_data = VinData {
|
||||
script_sig: input.script_sig.to_bytes().to_vec(),
|
||||
txinwitness: input.witness.to_vec(),
|
||||
script_pub_key: output.script_pubkey.to_bytes()
|
||||
};
|
||||
match sp::get_pubkey_from_input(&vin_data) {
|
||||
Ok(res) => {
|
||||
if let Some(pubkey) = res {
|
||||
pubkeys.push(pubkey);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Can't extract pubkey from input: {}", e.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Transaction with a non existing input");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let input_pub_keys: Vec<&PublicKey> = pubkeys.iter().collect();
|
||||
match silentpayments::utils::receiving::recipient_calculate_tweak_data(&input_pub_keys, &outpoints) {
|
||||
Ok(partial_tweak) => {
|
||||
let mut vecs = core_msg.serialize_to_vecs().to_vec();
|
||||
vecs.push(partial_tweak.serialize().to_vec());
|
||||
payload = flatten_msg(&vecs);
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to compute tweak data: {}", e.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
payload = flatten_msg(&core_msg.serialize_to_vecs());
|
||||
}
|
||||
}
|
||||
|
||||
for tx in peers.values() {
|
||||
let _ = tx.send(Message::Binary(payload.clone()));
|
||||
}
|
||||
}
|
||||
let core_msg = match msg {
|
||||
Ok(core_msg) => core_msg,
|
||||
Err(e) => {
|
||||
error!("Error receiving ZMQ message: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
debug!("Received a {} message", core_msg.topic_str());
|
||||
let peers = peer_map.lock().unwrap();
|
||||
|
||||
let payload: Vec<u8> = match core_msg.topic_str() {
|
||||
"rawtx" => {
|
||||
let processed = process_raw_tx_message(&core_msg, daemon.clone());
|
||||
match processed {
|
||||
Ok(p) => p,
|
||||
Err(_) => continue
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
flatten_msg(&core_msg.serialize_to_vecs())
|
||||
}
|
||||
};
|
||||
|
||||
for tx in peers.values() {
|
||||
let _ = tx.send(Message::Binary(payload.clone()));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -167,14 +176,47 @@ async fn main() -> Result<(), IoError> {
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let wallet_name = env::args()
|
||||
.nth(2)
|
||||
.unwrap_or_else(|| "default".to_owned());
|
||||
let is_testnet: bool = env::args()
|
||||
.nth(3)
|
||||
.unwrap_or_else(|| "true".to_owned())
|
||||
.parse()
|
||||
.expect("Please provide either \"true\" or \"false\"");
|
||||
|
||||
let state = PeerMap::new(Mutex::new(HashMap::new()));
|
||||
|
||||
// Connect the rpc daemon
|
||||
let daemon = Daemon::connect().unwrap();
|
||||
|
||||
let current_tip: u32 = daemon.get_current_height().expect("Failed to make rpc call").try_into().expect("block count is higher than u32::MAX");
|
||||
|
||||
// load an existing sp_wallet, or create a new one
|
||||
let sp_client = match spclient::SpClient::try_init_from_disk(wallet_name.clone()) {
|
||||
Ok(existing) => existing,
|
||||
Err(_) => {
|
||||
let mut seed = [0u8;64];
|
||||
thread_rng().fill(&mut seed);
|
||||
let (scan_sk, spend_sk) = spclient::derive_keys_from_seed(&seed, is_testnet).expect("Couldn't generate a new sp_wallet");
|
||||
SpClient::new(
|
||||
wallet_name,
|
||||
scan_sk,
|
||||
SpendKey::Secret(spend_sk),
|
||||
None,
|
||||
current_tip,
|
||||
is_testnet
|
||||
).expect("Failed to create a new SpClient")
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("Using wallet {} with address {}", sp_client.label, sp_client.get_receiving_address());
|
||||
|
||||
let shared_sp_client = Arc::new(Mutex::new(sp_client));
|
||||
let shared_daemon = Arc::new(Mutex::new(daemon));
|
||||
|
||||
// Subscribe to Bitcoin Core
|
||||
tokio::spawn(handle_zmq(state.clone(), daemon));
|
||||
tokio::spawn(handle_zmq(state.clone(), shared_daemon.clone()));
|
||||
|
||||
// Create the event loop and TCP listener we'll accept connections on.
|
||||
let try_socket = TcpListener::bind(&addr).await;
|
||||
@ -183,7 +225,7 @@ async fn main() -> Result<(), IoError> {
|
||||
|
||||
// Let's spawn the handling of each connection in a separate task.
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
tokio::spawn(handle_connection(state.clone(), stream, addr));
|
||||
tokio::spawn(handle_connection(state.clone(), stream, addr, shared_sp_client.clone(), shared_daemon.clone()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
787
src/spclient.rs
Normal file
787
src/spclient.rs
Normal file
@ -0,0 +1,787 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use bitcoin::psbt::{raw, Input, Output};
|
||||
use bitcoin::{
|
||||
bip32::{DerivationPath, Xpriv},
|
||||
consensus::{deserialize, serialize},
|
||||
hashes::hex::FromHex,
|
||||
key::TapTweak,
|
||||
psbt::PsbtSighashType,
|
||||
secp256k1::{
|
||||
constants::SECRET_KEY_SIZE, Keypair, Message, PublicKey, Scalar, Secp256k1, SecretKey,
|
||||
ThirtyTwoByteHash,
|
||||
},
|
||||
sighash::{Prevouts, SighashCache},
|
||||
taproot::Signature,
|
||||
Address, Amount, BlockHash, Network, OutPoint, ScriptBuf, TapLeafHash, Transaction, Txid, TxIn, TxOut, Witness,
|
||||
};
|
||||
use log::info;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use serde_with::DisplayFromStr;
|
||||
|
||||
use silentpayments::sending::SilentPaymentAddress;
|
||||
use silentpayments::utils as sp_utils;
|
||||
use silentpayments::{receiving::Receiver, utils::LabelHash};
|
||||
use silentpayments::secp256k1::rand::{thread_rng, prelude::SliceRandom};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
|
||||
use crate::db::FileWriter;
|
||||
use crate::constants::{NUMS, PSBT_SP_ADDRESS_KEY, PSBT_SP_PREFIX, PSBT_SP_SUBTYPE, PSBT_SP_TWEAK_KEY};
|
||||
|
||||
pub use bitcoin::psbt::Psbt;
|
||||
|
||||
pub struct ScanProgress {
|
||||
pub start: u32,
|
||||
pub current: u32,
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
type SpendingTxId = String;
|
||||
type MinedInBlock = String;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub enum OutputSpendStatus {
|
||||
Unspent,
|
||||
Spent(SpendingTxId),
|
||||
Mined(MinedInBlock),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct OwnedOutput {
|
||||
pub txoutpoint: String,
|
||||
pub blockheight: u32,
|
||||
pub tweak: String,
|
||||
pub amount: u64,
|
||||
pub script: String,
|
||||
pub label: Option<String>,
|
||||
pub spend_status: OutputSpendStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Recipient {
|
||||
pub address: String, // either old school or silent payment
|
||||
pub amount: u64,
|
||||
pub nb_outputs: u32, // if address is not SP, only 1 is valid
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub enum SpendKey {
|
||||
Secret(SecretKey),
|
||||
Public(PublicKey),
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct SpClient {
|
||||
pub label: String,
|
||||
scan_sk: SecretKey,
|
||||
spend_key: SpendKey,
|
||||
pub mnemonic: Option<String>,
|
||||
pub sp_receiver: Receiver,
|
||||
pub birthday: u32,
|
||||
pub last_scan: u32,
|
||||
#[serde_as(as = "HashMap<DisplayFromStr, _>")]
|
||||
owned: HashMap<OutPoint, OwnedOutput>,
|
||||
writer: FileWriter,
|
||||
}
|
||||
|
||||
impl SpClient {
|
||||
pub fn new(
|
||||
label: String,
|
||||
scan_sk: SecretKey,
|
||||
spend_key: SpendKey,
|
||||
mnemonic: Option<String>,
|
||||
birthday: u32,
|
||||
is_testnet: bool,
|
||||
) -> Result<Self> {
|
||||
let secp = Secp256k1::signing_only();
|
||||
let scan_pubkey = scan_sk.public_key(&secp);
|
||||
let sp_receiver: Receiver;
|
||||
let change_label = LabelHash::from_b_scan_and_m(scan_sk, 0).to_scalar();
|
||||
match spend_key {
|
||||
SpendKey::Public(key) => {
|
||||
sp_receiver = Receiver::new(0, scan_pubkey, key, change_label.into(), is_testnet)?;
|
||||
}
|
||||
SpendKey::Secret(key) => {
|
||||
let spend_pubkey = key.public_key(&secp);
|
||||
sp_receiver = Receiver::new(
|
||||
0,
|
||||
scan_pubkey,
|
||||
spend_pubkey,
|
||||
change_label.into(),
|
||||
is_testnet,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
let writer = FileWriter::new(&label)?;
|
||||
|
||||
Ok(Self {
|
||||
label,
|
||||
scan_sk,
|
||||
spend_key,
|
||||
mnemonic,
|
||||
sp_receiver,
|
||||
birthday,
|
||||
last_scan: if birthday == 0 { 0 } else { birthday - 1 },
|
||||
owned: HashMap::new(),
|
||||
writer,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_init_from_disk(label: String) -> Result<SpClient> {
|
||||
let empty = SpClient::new(
|
||||
label,
|
||||
SecretKey::from_slice(&[1u8; SECRET_KEY_SIZE]).unwrap(),
|
||||
SpendKey::Secret(SecretKey::from_slice(&[1u8; SECRET_KEY_SIZE]).unwrap()),
|
||||
None,
|
||||
0,
|
||||
false,
|
||||
)?;
|
||||
|
||||
empty.retrieve_from_disk()
|
||||
}
|
||||
|
||||
pub fn update_last_scan(&mut self, scan_height: u32) {
|
||||
self.last_scan = scan_height;
|
||||
}
|
||||
|
||||
pub fn get_spendable_amt(&self) -> u64 {
|
||||
self.owned
|
||||
.values()
|
||||
.filter(|x| x.spend_status == OutputSpendStatus::Unspent)
|
||||
.fold(0, |acc, x| acc + x.amount)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_unconfirmed_amt(&self) -> u64 {
|
||||
self.owned
|
||||
.values()
|
||||
.filter(|x| match x.spend_status {
|
||||
OutputSpendStatus::Spent(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
.fold(0, |acc, x| acc + x.amount)
|
||||
}
|
||||
|
||||
pub fn extend_owned(&mut self, owned: Vec<(OutPoint, OwnedOutput)>) {
|
||||
self.owned.extend(owned);
|
||||
}
|
||||
|
||||
pub fn check_outpoint_owned(&self, outpoint: OutPoint) -> bool {
|
||||
self.owned.contains_key(&outpoint)
|
||||
}
|
||||
|
||||
// pub fn mark_transaction_inputs_as_spent(
|
||||
// &mut self,
|
||||
// tx: nakamoto::chain::Transaction,
|
||||
// ) -> Result<()> {
|
||||
// let txid = tx.txid();
|
||||
|
||||
// // note: this currently fails for collaborative transactions
|
||||
// for input in tx.input {
|
||||
// self.mark_outpoint_spent(input.previous_output, txid)?;
|
||||
// }
|
||||
|
||||
// send_amount_update(self.get_spendable_amt());
|
||||
|
||||
// self.save_to_disk()
|
||||
// }
|
||||
|
||||
pub fn mark_outpoint_spent(&mut self, outpoint: OutPoint, txid: Txid) -> Result<()> {
|
||||
if let Some(owned) = self.owned.get_mut(&outpoint) {
|
||||
match owned.spend_status {
|
||||
OutputSpendStatus::Unspent => {
|
||||
info!("marking {} as spent by tx {}", owned.txoutpoint, txid);
|
||||
owned.spend_status = OutputSpendStatus::Spent(txid.to_string());
|
||||
}
|
||||
_ => return Err(Error::msg("owned outpoint is already spent")),
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("owned outpoint not found"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_outpoint_mined(&mut self, outpoint: OutPoint, blkhash: BlockHash) -> Result<()> {
|
||||
if let Some(owned) = self.owned.get_mut(&outpoint) {
|
||||
match owned.spend_status {
|
||||
OutputSpendStatus::Mined(_) => {
|
||||
return Err(Error::msg("owned outpoint is already mined"))
|
||||
}
|
||||
_ => {
|
||||
info!("marking {} as mined in block {}", owned.txoutpoint, blkhash);
|
||||
owned.spend_status = OutputSpendStatus::Mined(blkhash.to_string());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("owned outpoint not found"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_outpoints(&self) -> Vec<OwnedOutput> {
|
||||
self.owned.values().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn reset_from_blockheight(self, blockheight: u32) -> Self {
|
||||
let mut new = self.clone();
|
||||
new.owned = HashMap::new();
|
||||
new.owned = self
|
||||
.owned
|
||||
.into_iter()
|
||||
.filter(|o| o.1.blockheight <= blockheight)
|
||||
.collect();
|
||||
new.last_scan = blockheight;
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub fn save_to_disk(&self) -> Result<()> {
|
||||
self.writer.write_to_file(self)
|
||||
}
|
||||
|
||||
pub fn retrieve_from_disk(self) -> Result<Self> {
|
||||
self.writer.read_from_file()
|
||||
}
|
||||
|
||||
pub fn delete_from_disk(self) -> Result<()> {
|
||||
self.writer.delete()
|
||||
}
|
||||
|
||||
pub fn get_receiving_address(&self) -> String {
|
||||
self.sp_receiver.get_receiving_address()
|
||||
}
|
||||
|
||||
pub fn get_scan_key(&self) -> SecretKey {
|
||||
self.scan_sk
|
||||
}
|
||||
|
||||
pub fn fill_sp_outputs(&self, psbt: &mut Psbt) -> Result<()> {
|
||||
let b_spend = match self.spend_key {
|
||||
SpendKey::Secret(key) => key,
|
||||
SpendKey::Public(_) => return Err(Error::msg("Watch-only wallet, can't spend")),
|
||||
};
|
||||
|
||||
let mut input_privkeys: Vec<SecretKey> = vec![];
|
||||
for (i, input) in psbt.inputs.iter().enumerate() {
|
||||
if let Some(tweak) = input.proprietary.get(&raw::ProprietaryKey {
|
||||
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||
subtype: PSBT_SP_SUBTYPE,
|
||||
key: PSBT_SP_TWEAK_KEY.as_bytes().to_vec(),
|
||||
}) {
|
||||
let mut buffer = [0u8; 32];
|
||||
if tweak.len() != 32 {
|
||||
return Err(Error::msg(format!("Invalid tweak at input {}", i)));
|
||||
}
|
||||
buffer.copy_from_slice(tweak.as_slice());
|
||||
let scalar = Scalar::from_be_bytes(buffer)?;
|
||||
input_privkeys.push(b_spend.add_tweak(&scalar)?);
|
||||
} else {
|
||||
// For now all inputs belong to us
|
||||
return Err(Error::msg(format!("Missing tweak at input {}", i)));
|
||||
}
|
||||
}
|
||||
|
||||
let a_sum = Self::get_a_sum_secret_keys(&input_privkeys);
|
||||
let outpoints: Vec<(String, u32)> = psbt
|
||||
.unsigned_tx
|
||||
.input
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let prev_out = i.previous_output;
|
||||
(prev_out.txid.to_string(), prev_out.vout)
|
||||
})
|
||||
.collect();
|
||||
let outpoints_hash: Scalar =
|
||||
sp_utils::hash_outpoints(&outpoints, a_sum.public_key(&Secp256k1::signing_only()))?;
|
||||
let partial_secret =
|
||||
sp_utils::sending::sender_calculate_partial_secret(a_sum, outpoints_hash)?;
|
||||
|
||||
// get all the silent addresses
|
||||
let mut sp_addresses: Vec<String> = Vec::with_capacity(psbt.outputs.len());
|
||||
for output in psbt.outputs.iter() {
|
||||
// get the sp address from psbt
|
||||
if let Some(value) = output.proprietary.get(&raw::ProprietaryKey {
|
||||
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||
subtype: PSBT_SP_SUBTYPE,
|
||||
key: PSBT_SP_ADDRESS_KEY.as_bytes().to_vec(),
|
||||
}) {
|
||||
let sp_address = SilentPaymentAddress::try_from(deserialize::<String>(value)?)?;
|
||||
sp_addresses.push(sp_address.into());
|
||||
} else {
|
||||
// Not a sp output
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut sp_address2xonlypubkeys =
|
||||
silentpayments::sending::generate_multiple_recipient_pubkeys(
|
||||
sp_addresses,
|
||||
partial_secret,
|
||||
)?;
|
||||
for (i, output) in psbt.unsigned_tx.output.iter_mut().enumerate() {
|
||||
// get the sp address from psbt
|
||||
let output_data = &psbt.outputs[i];
|
||||
if let Some(value) = output_data.proprietary.get(&raw::ProprietaryKey {
|
||||
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||
subtype: PSBT_SP_SUBTYPE,
|
||||
key: PSBT_SP_ADDRESS_KEY.as_bytes().to_vec(),
|
||||
}) {
|
||||
let sp_address = SilentPaymentAddress::try_from(deserialize::<String>(value)?)?;
|
||||
if let Some(xonlypubkeys) = sp_address2xonlypubkeys.get_mut(&sp_address.to_string())
|
||||
{
|
||||
if !xonlypubkeys.is_empty() {
|
||||
let output_key = xonlypubkeys.remove(0); // actually we could randomize it
|
||||
// update the script pubkey
|
||||
output.script_pubkey =
|
||||
ScriptBuf::new_p2tr_tweaked(output_key.dangerous_assume_tweaked());
|
||||
} else {
|
||||
return Err(Error::msg(format!(
|
||||
"We're missing a key for address {}",
|
||||
sp_address
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::msg(format!("Can't find address {}", sp_address)));
|
||||
}
|
||||
} else {
|
||||
// Not a sp output
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_fees(psbt: &mut Psbt, fee_rate: u32, payer: String) -> Result<()> {
|
||||
let payer_vouts: Vec<u32> = match SilentPaymentAddress::try_from(payer.clone()) {
|
||||
Ok(sp_address) => psbt
|
||||
.outputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, o)| {
|
||||
if let Some(value) = o.proprietary.get(&raw::ProprietaryKey {
|
||||
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||
subtype: PSBT_SP_SUBTYPE,
|
||||
key: PSBT_SP_ADDRESS_KEY.as_bytes().to_vec(),
|
||||
}) {
|
||||
let candidate =
|
||||
SilentPaymentAddress::try_from(deserialize::<String>(value).unwrap())
|
||||
.unwrap();
|
||||
if sp_address == candidate {
|
||||
Some(i as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
Err(_) => {
|
||||
let address = Address::from_str(&payer)?;
|
||||
let spk = address.assume_checked().script_pubkey();
|
||||
psbt.unsigned_tx
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, o)| {
|
||||
if o.script_pubkey == spk {
|
||||
Some(i as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect() // Actually we should have only one output for normal address
|
||||
}
|
||||
};
|
||||
|
||||
if payer_vouts.is_empty() {
|
||||
return Err(Error::msg("Payer is not part of this transaction"));
|
||||
}
|
||||
|
||||
// check against the total amt in inputs
|
||||
let total_input_amt: u64 = psbt
|
||||
.iter_funding_utxos()
|
||||
.try_fold(0u64, |sum, utxo_result| {
|
||||
utxo_result.map(|utxo| sum + utxo.value.to_sat())
|
||||
})?;
|
||||
|
||||
// total amt in outputs should be equal
|
||||
let total_output_amt: u64 = psbt
|
||||
.unsigned_tx
|
||||
.output
|
||||
.iter()
|
||||
.fold(0, |sum, add| sum + add.value.to_sat());
|
||||
|
||||
let dust = total_input_amt - total_output_amt;
|
||||
|
||||
// now compute the size of the tx
|
||||
let fake = Self::sign_psbt_fake(psbt);
|
||||
let vsize = fake.vsize();
|
||||
|
||||
// absolut amount of fees
|
||||
let fee_amt: u64 = (fee_rate * vsize as u32).into();
|
||||
|
||||
// now deduce the fees from one of the payer outputs
|
||||
// TODO deduce fee from the change address
|
||||
if fee_amt > dust {
|
||||
let mut rng = thread_rng();
|
||||
if let Some(deduce_from) = payer_vouts.choose(&mut rng) {
|
||||
let output = &mut psbt.unsigned_tx.output[*deduce_from as usize];
|
||||
let old_value = output.value;
|
||||
output.value = old_value - Amount::from_sat(fee_amt - dust); // account for eventual dust
|
||||
} else {
|
||||
return Err(Error::msg("no payer vout"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_new_psbt(
|
||||
&self,
|
||||
inputs: Vec<OwnedOutput>,
|
||||
mut recipients: Vec<Recipient>,
|
||||
payload: Option<&[u8]>
|
||||
) -> Result<Psbt> {
|
||||
let mut tx_in: Vec<bitcoin::TxIn> = vec![];
|
||||
let mut inputs_data: Vec<(ScriptBuf, u64, Scalar)> = vec![];
|
||||
let mut total_input_amount = 0u64;
|
||||
let mut total_output_amount = 0u64;
|
||||
|
||||
for i in inputs {
|
||||
tx_in.push(TxIn {
|
||||
previous_output: bitcoin::OutPoint::from_str(&i.txoutpoint)?,
|
||||
script_sig: ScriptBuf::new(),
|
||||
sequence: bitcoin::Sequence::MAX,
|
||||
witness: bitcoin::Witness::new(),
|
||||
});
|
||||
|
||||
let scalar = Scalar::from_be_bytes(FromHex::from_hex(&i.tweak)?)?;
|
||||
|
||||
total_input_amount += i.amount;
|
||||
|
||||
inputs_data.push((ScriptBuf::from_hex(&i.script)?, i.amount, scalar));
|
||||
}
|
||||
|
||||
// We could compute the outputs key right away,
|
||||
// but keeping things separated may be interesting,
|
||||
// for example creating transactions in a watch-only wallet
|
||||
// and using another signer
|
||||
let placeholder_spk = ScriptBuf::new_p2tr_tweaked(
|
||||
bitcoin::XOnlyPublicKey::from_str(NUMS)?.dangerous_assume_tweaked(),
|
||||
);
|
||||
|
||||
let _outputs: Result<Vec<bitcoin::TxOut>> = recipients
|
||||
.iter()
|
||||
.map(|o| {
|
||||
let script_pubkey: ScriptBuf;
|
||||
|
||||
match SilentPaymentAddress::try_from(o.address.as_str()) {
|
||||
Ok(sp_address) => {
|
||||
if self.sp_receiver.is_testnet != sp_address.is_testnet() {
|
||||
return Err(Error::msg(format!(
|
||||
"Wrong network for address {}",
|
||||
sp_address
|
||||
)));
|
||||
}
|
||||
|
||||
script_pubkey = placeholder_spk.clone();
|
||||
}
|
||||
Err(_) => {
|
||||
let unchecked_address = Address::from_str(&o.address)?; // TODO: handle better garbage string
|
||||
|
||||
let address_is_testnet = match *unchecked_address.network() {
|
||||
Network::Bitcoin => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if self.sp_receiver.is_testnet != address_is_testnet {
|
||||
return Err(Error::msg(format!(
|
||||
"Wrong network for address {}",
|
||||
unchecked_address.assume_checked()
|
||||
)));
|
||||
}
|
||||
|
||||
script_pubkey = ScriptBuf::from_bytes(
|
||||
unchecked_address
|
||||
.assume_checked()
|
||||
.script_pubkey()
|
||||
.to_bytes(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
total_output_amount += o.amount;
|
||||
|
||||
Ok(TxOut {
|
||||
value: Amount::from_sat(o.amount),
|
||||
script_pubkey,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut outputs = _outputs?;
|
||||
|
||||
let change_amt = total_input_amount - total_output_amount;
|
||||
|
||||
// Add change output
|
||||
let change_address = self.sp_receiver.get_change_address();
|
||||
|
||||
outputs.push(TxOut {
|
||||
value: Amount::from_sat(change_amt),
|
||||
script_pubkey: placeholder_spk,
|
||||
});
|
||||
|
||||
recipients.push(Recipient {
|
||||
address: change_address,
|
||||
amount: change_amt,
|
||||
nb_outputs: 1,
|
||||
});
|
||||
|
||||
if let Some(data) = payload {
|
||||
if data.len() > 40 {
|
||||
return Err(Error::msg("Payload must be max 40B"));
|
||||
}
|
||||
let mut op_return = bitcoin::script::PushBytesBuf::new();
|
||||
op_return.extend_from_slice(data);
|
||||
outputs.push(TxOut {
|
||||
value: Amount::from_sat(0),
|
||||
script_pubkey: ScriptBuf::new_op_return(op_return),
|
||||
});
|
||||
}
|
||||
|
||||
let tx = bitcoin::Transaction {
|
||||
version: bitcoin::transaction::Version(2),
|
||||
lock_time: bitcoin::absolute::LockTime::ZERO,
|
||||
input: tx_in,
|
||||
output: outputs,
|
||||
};
|
||||
|
||||
let mut psbt = Psbt::from_unsigned_tx(tx)?;
|
||||
|
||||
// Add the witness utxo to the input in psbt
|
||||
for (i, input_data) in inputs_data.iter().enumerate() {
|
||||
let (script_pubkey, value, tweak) = input_data;
|
||||
let witness_txout = TxOut {
|
||||
value: Amount::from_sat(*value),
|
||||
script_pubkey: script_pubkey.clone(),
|
||||
};
|
||||
let mut psbt_input = Input {
|
||||
witness_utxo: Some(witness_txout),
|
||||
..Default::default()
|
||||
};
|
||||
psbt_input.proprietary.insert(
|
||||
raw::ProprietaryKey {
|
||||
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||
subtype: PSBT_SP_SUBTYPE,
|
||||
key: PSBT_SP_TWEAK_KEY.as_bytes().to_vec(),
|
||||
},
|
||||
tweak.to_be_bytes().to_vec(),
|
||||
);
|
||||
psbt.inputs[i] = psbt_input;
|
||||
}
|
||||
|
||||
for (i, recipient) in recipients.iter().enumerate() {
|
||||
if let Ok(sp_address) = SilentPaymentAddress::try_from(recipient.address.as_str()) {
|
||||
// Add silentpayment address to the output
|
||||
let mut psbt_output = Output {
|
||||
..Default::default()
|
||||
};
|
||||
psbt_output.proprietary.insert(
|
||||
raw::ProprietaryKey {
|
||||
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||
subtype: PSBT_SP_SUBTYPE,
|
||||
key: PSBT_SP_ADDRESS_KEY.as_bytes().to_vec(),
|
||||
},
|
||||
serialize(&sp_address.to_string()),
|
||||
);
|
||||
psbt.outputs[i] = psbt_output;
|
||||
} else {
|
||||
// Regular address, we don't need to add more data
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(psbt)
|
||||
}
|
||||
|
||||
pub fn get_a_sum_secret_keys(input: &Vec<SecretKey>) -> SecretKey {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let mut negated_keys: Vec<SecretKey> = vec![];
|
||||
|
||||
for key in input {
|
||||
let (_, parity) = key.x_only_public_key(&secp);
|
||||
|
||||
if parity == bitcoin::secp256k1::Parity::Odd {
|
||||
negated_keys.push(key.negate());
|
||||
} else {
|
||||
negated_keys.push(*key);
|
||||
}
|
||||
}
|
||||
|
||||
let (head, tail) = negated_keys.split_first().unwrap();
|
||||
|
||||
let result: SecretKey = tail
|
||||
.iter()
|
||||
.fold(*head, |acc, &item| acc.add_tweak(&item.into()).unwrap());
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn taproot_sighash<
|
||||
T: std::ops::Deref<Target = Transaction> + std::borrow::Borrow<Transaction>,
|
||||
>(
|
||||
input: &Input,
|
||||
prevouts: &Vec<&TxOut>,
|
||||
input_index: usize,
|
||||
cache: &mut SighashCache<T>,
|
||||
tapleaf_hash: Option<TapLeafHash>,
|
||||
) -> Result<(Message, PsbtSighashType), Error> {
|
||||
let prevouts = Prevouts::All(prevouts);
|
||||
|
||||
let hash_ty = input
|
||||
.sighash_type
|
||||
.map(|ty| ty.taproot_hash_ty())
|
||||
.unwrap_or(Ok(bitcoin::TapSighashType::Default))?;
|
||||
|
||||
let sighash = match tapleaf_hash {
|
||||
Some(leaf_hash) => cache.taproot_script_spend_signature_hash(
|
||||
input_index,
|
||||
&prevouts,
|
||||
leaf_hash,
|
||||
hash_ty,
|
||||
)?,
|
||||
None => cache.taproot_key_spend_signature_hash(input_index, &prevouts, hash_ty)?,
|
||||
};
|
||||
let msg = Message::from_digest(sighash.into_32());
|
||||
Ok((msg, hash_ty.into()))
|
||||
}
|
||||
|
||||
// Sign a transaction with garbage, used for easier fee estimation
|
||||
fn sign_psbt_fake(psbt: &Psbt) -> Transaction {
|
||||
let mut fake_psbt = psbt.clone();
|
||||
|
||||
let fake_sig = [1u8; 64];
|
||||
|
||||
for i in fake_psbt.inputs.iter_mut() {
|
||||
i.tap_key_sig = Some(Signature::from_slice(&fake_sig).unwrap());
|
||||
}
|
||||
|
||||
Self::finalize_psbt(&mut fake_psbt).unwrap();
|
||||
|
||||
fake_psbt.extract_tx().expect("Invalid fake tx")
|
||||
}
|
||||
|
||||
pub fn sign_psbt(&self, psbt: Psbt) -> Result<Psbt> {
|
||||
let b_spend = match self.spend_key {
|
||||
SpendKey::Secret(key) => key,
|
||||
SpendKey::Public(_) => return Err(Error::msg("Watch-only wallet, can't spend")),
|
||||
};
|
||||
|
||||
let mut cache = SighashCache::new(&psbt.unsigned_tx);
|
||||
|
||||
let mut prevouts: Vec<&TxOut> = vec![];
|
||||
|
||||
for input in &psbt.inputs {
|
||||
if let Some(witness_utxo) = &input.witness_utxo {
|
||||
prevouts.push(witness_utxo);
|
||||
}
|
||||
}
|
||||
|
||||
let mut signed_psbt = psbt.clone();
|
||||
|
||||
let secp = Secp256k1::signing_only();
|
||||
|
||||
for (i, input) in psbt.inputs.iter().enumerate() {
|
||||
let tap_leaf_hash: Option<TapLeafHash> = None;
|
||||
|
||||
let (msg, sighash_ty) =
|
||||
Self::taproot_sighash(input, &prevouts, i, &mut cache, tap_leaf_hash)?;
|
||||
|
||||
// Construct the signing key
|
||||
let tweak = input.proprietary.get(&raw::ProprietaryKey {
|
||||
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||
subtype: PSBT_SP_SUBTYPE,
|
||||
key: PSBT_SP_TWEAK_KEY.as_bytes().to_vec(),
|
||||
});
|
||||
|
||||
if tweak.is_none() {
|
||||
panic!("Missing tweak")
|
||||
};
|
||||
|
||||
let tweak = SecretKey::from_slice(tweak.unwrap().as_slice()).unwrap();
|
||||
|
||||
let sk = b_spend.add_tweak(&tweak.into())?;
|
||||
|
||||
let keypair = Keypair::from_secret_key(&secp, &sk);
|
||||
|
||||
let sig = secp.sign_schnorr_with_rng(&msg, &keypair, &mut thread_rng());
|
||||
|
||||
signed_psbt.inputs[i].tap_key_sig = Some(Signature {
|
||||
sig,
|
||||
hash_ty: sighash_ty.taproot_hash_ty()?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(signed_psbt)
|
||||
}
|
||||
|
||||
pub(crate) fn finalize_psbt(psbt: &mut Psbt) -> Result<()> {
|
||||
psbt.inputs.iter_mut().for_each(|i| {
|
||||
let mut script_witness = Witness::new();
|
||||
if let Some(sig) = i.tap_key_sig {
|
||||
script_witness.push(sig.to_vec());
|
||||
} else {
|
||||
panic!("Missing signature");
|
||||
}
|
||||
|
||||
i.final_script_witness = Some(script_witness);
|
||||
|
||||
// Clear all the data fields as per the spec.
|
||||
i.tap_key_sig = None;
|
||||
i.partial_sigs = BTreeMap::new();
|
||||
i.sighash_type = None;
|
||||
i.redeem_script = None;
|
||||
i.witness_script = None;
|
||||
i.bip32_derivation = BTreeMap::new();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_keys_from_seed(seed: &[u8], is_testnet: bool) -> Result<(SecretKey, SecretKey)> {
|
||||
let network = if is_testnet {
|
||||
Network::Testnet
|
||||
} else {
|
||||
Network::Bitcoin
|
||||
};
|
||||
|
||||
let xprv = Xpriv::new_master(network, seed)?;
|
||||
|
||||
let (scan_privkey, spend_privkey) = derive_keys_from_xprv(xprv)?;
|
||||
|
||||
Ok((scan_privkey, spend_privkey))
|
||||
}
|
||||
|
||||
fn derive_keys_from_xprv(xprv: Xpriv) -> Result<(SecretKey, SecretKey)> {
|
||||
let (scan_path, spend_path) = match xprv.network {
|
||||
bitcoin::Network::Bitcoin => ("m/352h/0h/0h/1h/0", "m/352h/0h/0h/0h/0"),
|
||||
_ => ("m/352h/1h/0h/1h/0", "m/352h/1h/0h/0h/0"),
|
||||
};
|
||||
|
||||
let secp = Secp256k1::signing_only();
|
||||
let scan_path = DerivationPath::from_str(scan_path)?;
|
||||
let spend_path = DerivationPath::from_str(spend_path)?;
|
||||
let scan_privkey = xprv.derive_priv(&secp, &scan_path)?.private_key;
|
||||
let spend_privkey = xprv.derive_priv(&secp, &spend_path)?.private_key;
|
||||
|
||||
Ok((scan_privkey, spend_privkey))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user