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",
|
"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]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.80"
|
version = "1.0.80"
|
||||||
@ -70,6 +85,12 @@ version = "0.13.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bech32"
|
name = "bech32"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@ -173,6 +194,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.15.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -211,6 +238,25 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
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]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@ -286,12 +332,57 @@ dependencies = [
|
|||||||
"typenum",
|
"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]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
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]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@ -412,6 +503,12 @@ version = "0.28.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.3"
|
version = "0.14.3"
|
||||||
@ -480,6 +577,35 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
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]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -490,6 +616,17 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.3"
|
version = "2.2.3"
|
||||||
@ -497,7 +634,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
|
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.14.3",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -515,13 +653,22 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "jsonrpc"
|
name = "jsonrpc"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2"
|
checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.13.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
@ -574,6 +721,21 @@ dependencies = [
|
|||||||
"windows-sys",
|
"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]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@ -593,6 +755,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@ -617,6 +785,12 @@ version = "0.3.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@ -754,6 +928,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
"silentpayments",
|
"silentpayments",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
@ -821,6 +996,36 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@ -871,6 +1076,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.49"
|
version = "2.0.49"
|
||||||
@ -930,6 +1141,37 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -1023,7 +1265,7 @@ version = "0.22.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
|
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap 2.2.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
@ -1121,6 +1363,60 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
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]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -1152,13 +1448,22 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1167,13 +1472,28 @@ version = "0.48.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm 0.48.5",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc 0.48.5",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu 0.48.5",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc 0.48.5",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu 0.48.5",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm 0.48.5",
|
||||||
"windows_x86_64_msvc",
|
"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]]
|
[[package]]
|
||||||
@ -1182,42 +1502,84 @@ version = "0.48.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
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]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -13,6 +13,7 @@ futures-util = { version = "0.3.28", default-features = false, features = ["sink
|
|||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
serde = { version = "1.0.193", features = ["derive"]}
|
serde = { version = "1.0.193", features = ["derive"]}
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
serde_with = "3.6.0"
|
||||||
silentpayments = { git = "https://github.com/cygnet3/rust-silentpayments", branch = "master", features = ['utils'] }
|
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 = { version = "1.0.0", features = ["io-util", "rt-multi-thread", "macros", "sync"] }
|
||||||
tokio-stream = "0.1"
|
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::{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 bitcoincore_rpc::{json, jsonrpc, Auth, Client, RpcApi};
|
||||||
// use crossbeam_channel::Receiver;
|
// use crossbeam_channel::Receiver;
|
||||||
// use parking_lot::Mutex;
|
// use parking_lot::Mutex;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@ -113,7 +115,7 @@ impl Daemon {
|
|||||||
let mut rpc = rpc_connect()?;
|
let mut rpc = rpc_connect()?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match rpc_poll(&mut rpc, true) {
|
match rpc_poll(&mut rpc, false) {
|
||||||
PollResult::Done(result) => {
|
PollResult::Done(result) => {
|
||||||
result.context("bitcoind RPC polling failed")?;
|
result.context("bitcoind RPC polling failed")?;
|
||||||
break; // on success, finish polling
|
break; // on success, finish polling
|
||||||
@ -161,6 +163,78 @@ impl Daemon {
|
|||||||
.relay_fee)
|
.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> {
|
pub(crate) fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
||||||
self.rpc
|
self.rpc
|
||||||
.send_raw_transaction(tx)
|
.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)
|
||||||
|
}
|
||||||
|
}
|
182
src/main.rs
182
src/main.rs
@ -3,23 +3,33 @@ use std::{
|
|||||||
env,
|
env,
|
||||||
io::Error as IoError,
|
io::Error as IoError,
|
||||||
net::SocketAddr,
|
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 futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt};
|
||||||
|
|
||||||
use log::{debug, error};
|
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::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
|
||||||
|
use anyhow::{Result, Error};
|
||||||
|
|
||||||
mod sp;
|
mod sp;
|
||||||
mod daemon;
|
mod daemon;
|
||||||
|
mod spclient;
|
||||||
|
mod constants;
|
||||||
|
mod db;
|
||||||
|
|
||||||
use crate::daemon::Daemon;
|
use crate::daemon::Daemon;
|
||||||
use crate::sp::VinData;
|
use crate::sp::VinData;
|
||||||
|
use crate::spclient::{SpClient, SpendKey, OutputSpendStatus};
|
||||||
|
|
||||||
type Tx = UnboundedSender<Message>;
|
type Tx = UnboundedSender<Message>;
|
||||||
|
|
||||||
@ -82,79 +92,78 @@ fn flatten_msg(parts: &[Vec<u8>]) -> Vec<u8> {
|
|||||||
final_vec
|
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 || {
|
tokio::task::spawn_blocking(move || {
|
||||||
debug!("Starting listening on Core");
|
debug!("Starting listening on Core");
|
||||||
for msg in bitcoincore_zmq::subscribe_receiver(&["tcp://127.0.0.1:29000"]).unwrap() {
|
for msg in bitcoincore_zmq::subscribe_receiver(&["tcp://127.0.0.1:29000"]).unwrap() {
|
||||||
match msg {
|
let core_msg = match msg {
|
||||||
Ok(core_msg) => {
|
Ok(core_msg) => 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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error receiving ZMQ message: {}", e);
|
error!("Error receiving ZMQ message: {}", e);
|
||||||
continue;
|
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()
|
let addr = env::args()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
.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()));
|
let state = PeerMap::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
// Connect the rpc daemon
|
// Connect the rpc daemon
|
||||||
let daemon = Daemon::connect().unwrap();
|
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
|
// 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.
|
// Create the event loop and TCP listener we'll accept connections on.
|
||||||
let try_socket = TcpListener::bind(&addr).await;
|
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.
|
// Let's spawn the handling of each connection in a separate task.
|
||||||
while let Ok((stream, addr)) = listener.accept().await {
|
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(())
|
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