diff --git a/Cargo.lock b/Cargo.lock index c169462b..df546635 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -28,9 +28,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "arrayvec" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "autocfg" @@ -47,48 +47,122 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base58ck" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6a87a8367e7a4248c8dfd783c37ef492bca1307cd4b21b4cfad9cfd15bf060" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ "bitcoin-internals", "bitcoin_hashes", ] [[package]] -name = "bitcoin-consensus-encoding" -version = "0.1.0" +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bech32" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d7ca3dc8ff835693ad73bf1596240c06f974a31eeb3f611aaedf855f1f2725" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bitcoin" +version = "0.33.0-beta" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ + "base58ck", + "base64", + "bech32", + "bitcoin-consensus-encoding", + "bitcoin-crypto", "bitcoin-internals", + "bitcoin-io", + "bitcoin-network-kind", + "bitcoin-primitives", + "bitcoin-taproot-primitives", + "bitcoin-units", + "bitcoin_hashes", + "bitcoinconsensus", + "hex-conservative 0.3.2", + "hex-conservative 1.2.0", + "secp256k1", ] [[package]] name = "bitcoin-consensus-encoding" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "192615d454a4398c0e4654692610fcc7c9f67647e51ebec2786ae3b1f3f0fc35" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ "bitcoin-internals", ] +[[package]] +name = "bitcoin-crypto" +version = "0.2.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" +dependencies = [ + "base58ck", + "bitcoin-internals", + "bitcoin-network-kind", + "bitcoin_hashes", + "hex-conservative 0.3.2", + "hex-conservative 1.2.0", + "secp256k1", + "serde", +] + [[package]] name = "bitcoin-internals" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" +dependencies = [ + "hex-conservative 0.3.2", + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.5.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ + "bitcoin-consensus-encoding", + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "bitcoin-network-kind" +version = "0.1.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin-p2p-messages" +version = "0.1.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" +dependencies = [ + "bitcoin", + "bitcoin-consensus-encoding", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-network-kind", + "bitcoin-primitives", + "bitcoin-units", + "bitcoin_hashes", "hex-conservative 0.3.2", + "hex-conservative 1.2.0", "serde", ] [[package]] name = "bitcoin-primitives" version = "0.102.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48379e4d9a4456c038d78551ccbb8a3d7f9514c799f51ae815a6e947d64c9efe" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ - "bitcoin-consensus-encoding 0.1.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "bitcoin-units", "bitcoin_hashes", @@ -97,13 +171,24 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-taproot-primitives" +version = "0.1.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" +dependencies = [ + "bitcoin-crypto", + "bitcoin-internals", + "bitcoin_hashes", + "secp256k1", + "serde", +] + [[package]] name = "bitcoin-units" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe8fafd73be14659c450deb64f90d2fd5a354fce366a01223a8319cf4d98b40" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ - "bitcoin-consensus-encoding 0.1.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "serde", ] @@ -111,20 +196,29 @@ dependencies = [ [[package]] name = "bitcoin_hashes" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a45c2b41c457a9a9e4670422fcbdf109afb3b22bc920b4045e8bdfd788a3d" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ - "bitcoin-consensus-encoding 0.1.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "hex-conservative 0.3.2", + "hex-conservative 1.2.0", "serde", ] +[[package]] +name = "bitcoinconsensus" +version = "0.106.0+26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12cba9cce5043cdda968e07b9df6d05ec6b0b38aa27a9a40bb575cf3e521ae9" +dependencies = [ + "cc", +] + [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block-buffer" @@ -137,9 +231,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" dependencies = [ "cc", "glob", @@ -178,9 +272,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.65" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -234,9 +328,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" dependencies = [ "clap_builder", "clap_derive", @@ -244,9 +338,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" dependencies = [ "anstyle", "clap_lex", @@ -255,14 +349,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -312,9 +406,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -322,18 +416,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -348,7 +442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -367,7 +461,7 @@ dependencies = [ name = "dash-dev" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "built", "cfg-if", "chrono", @@ -385,7 +479,7 @@ dependencies = [ "rayon", "serde", "serde_json", - "syn", + "syn 2.0.119", "sysinfo", "xxhash-rust", ] @@ -394,7 +488,7 @@ dependencies = [ name = "dash-num" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "dash-dev", "dash-types", "divan", @@ -407,9 +501,12 @@ dependencies = [ name = "dash-p2p-core" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin-internals", + "bitcoin-p2p-messages", + "bitcoin-primitives", "bitcoin-units", + "cfg-if", "dash-dev", "dash-num", "dash-params", @@ -426,7 +523,7 @@ dependencies = [ name = "dash-params" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin-primitives", "bitcoin-units", "dash-num", @@ -443,7 +540,7 @@ name = "dash-pkc" version = "0.0.0" dependencies = [ "base58ck", - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin_hashes", "blst", "cfg-if", @@ -454,7 +551,7 @@ dependencies = [ "hex-conservative 0.3.2", "hex-literal", "k256", - "rand_core", + "rand_core 0.6.4", "rayon", "rstest", "serde", @@ -480,7 +577,7 @@ dependencies = [ name = "dash-primitives" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "bitcoin-primitives", "bitcoin-units", @@ -504,11 +601,11 @@ name = "dash-script" version = "0.0.0" dependencies = [ "base58ck", - "bitcoin-consensus-encoding 0.2.0", - "bitcoin_hashes", + "bitcoin-consensus-encoding", + "dash-num", "dash-pkc", "dash-types", - "hex-literal", + "hex-conservative 0.3.2", "rstest", "serde", ] @@ -518,7 +615,8 @@ name = "dash-types" version = "0.0.0" dependencies = [ "base58ck", - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", + "bitcoin-p2p-messages", "bitcoin-primitives", "bitcoin_hashes", "cfg-if", @@ -535,7 +633,7 @@ name = "dash-types-marker" version = "0.0.0" dependencies = [ "quote", - "syn", + "syn 2.0.119", "xxhash-rust", ] @@ -563,13 +661,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -594,7 +692,7 @@ checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -612,9 +710,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elliptic-curve" @@ -628,7 +726,7 @@ dependencies = [ "ff", "generic-array", "group", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -662,7 +760,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -689,26 +787,26 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-timer" @@ -718,9 +816,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-macro", @@ -759,10 +857,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + [[package]] name = "git2" version = "0.20.4" @@ -778,9 +887,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "group" @@ -789,7 +898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1012,11 +1121,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] @@ -1056,15 +1165,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libgit2-sys" -version = "0.18.5+1.9.4" +version = "0.18.7+1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" +checksum = "23c7391e4b9f4ffab1a624223cc1d7385ff9a678f490768add717de7ea2f4d89" dependencies = [ "cc", "libc", @@ -1110,9 +1219,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "ntapi" @@ -1162,9 +1271,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.6" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -1172,9 +1281,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.6" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" dependencies = [ "pest", "pest_generator", @@ -1182,25 +1291,24 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.6" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "pest_meta" -version = "2.8.6" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" dependencies = [ "pest", - "sha2", ] [[package]] @@ -1227,9 +1335,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "potential_utf" @@ -1240,29 +1348,38 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.12+spec-1.1.0", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1273,6 +1390,32 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -1282,6 +1425,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rayon" version = "1.12.0" @@ -1304,9 +1456,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -1316,9 +1468,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1379,7 +1531,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.119", "unicode-ident", ] @@ -1407,9 +1559,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "sec1" @@ -1424,6 +1576,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.32.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5fdc7d6e800869d3fd60ff857c479bf0a83ea7bf44b389e64461e844204994" +dependencies = [ + "rand", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3be00697c88c00fe102af8dc316038cc2062eab8da646e7463f4c0e70ca9fd" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "1.0.28" @@ -1436,9 +1607,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1446,29 +1617,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -1510,7 +1681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1539,9 +1710,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1556,7 +1738,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1647,23 +1829,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.12+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] @@ -1767,7 +1949,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -1855,7 +2037,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1866,7 +2048,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1877,7 +2059,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1888,7 +2070,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2017,9 +2199,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] @@ -2038,9 +2220,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xxhash-rust" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" [[package]] name = "yoke" @@ -2061,28 +2243,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.52" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.52" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2102,7 +2284,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -2123,7 +2305,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2156,11 +2338,11 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 6e9fe865..10799d71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,15 @@ members = [ ] resolver = "2" +[workspace.dependencies] +base58ck = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin-consensus-encoding = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin-internals = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin-p2p-messages = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin-primitives = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin-units = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin_hashes = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } + [workspace.lints.clippy] expect_used = "deny" panic = "deny" diff --git a/contrib/codeql/lib/policy.qll b/contrib/codeql/lib/policy.qll index 31996d39..ac7aebb8 100644 --- a/contrib/codeql/lib/policy.qll +++ b/contrib/codeql/lib/policy.qll @@ -323,7 +323,7 @@ class DeclSlot extends TDeclSlot { this = TNumCodecImpl() and result = "NumCodec impl" or this = TBaseCodecImpl() and - result = "BaseCodec/Encodable/Decodable impl" + result = "BaseCodec/Encode/Decode impl" or this = TCheckableImpl() and result = "Checkable impl" or @@ -341,9 +341,9 @@ DeclSlot traitSlot(string traitName) { or traitName = "BaseCodec" and result = TBaseCodecImpl() or - traitName = "Encodable" and result = TBaseCodecImpl() + traitName = "Encode" and result = TBaseCodecImpl() or - traitName = "Decodable" and result = TBaseCodecImpl() + traitName = "Decode" and result = TBaseCodecImpl() or traitName = "Checkable" and result = TCheckableImpl() or diff --git a/contrib/samples/Cargo.lock b/contrib/samples/Cargo.lock index 1a3665c1..fa450cb5 100644 --- a/contrib/samples/Cargo.lock +++ b/contrib/samples/Cargo.lock @@ -4,34 +4,29 @@ version = 4 [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] -name = "base58ck" -version = "0.4.0" +name = "base16ct" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6a87a8367e7a4248c8dfd783c37ef492bca1307cd4b21b4cfad9cfd15bf060" -dependencies = [ - "bitcoin-internals", - "bitcoin_hashes", -] +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] -name = "bitcoin-consensus-encoding" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d7ca3dc8ff835693ad73bf1596240c06f974a31eeb3f611aaedf855f1f2725" +name = "base58ck" +version = "0.4.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ "bitcoin-internals", + "bitcoin_hashes", ] [[package]] name = "bitcoin-consensus-encoding" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "192615d454a4398c0e4654692610fcc7c9f67647e51ebec2786ae3b1f3f0fc35" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ "bitcoin-internals", ] @@ -39,8 +34,7 @@ dependencies = [ [[package]] name = "bitcoin-internals" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ "hex-conservative 0.3.2", "serde", @@ -49,10 +43,9 @@ dependencies = [ [[package]] name = "bitcoin-primitives" version = "0.102.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48379e4d9a4456c038d78551ccbb8a3d7f9514c799f51ae815a6e947d64c9efe" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ - "bitcoin-consensus-encoding 0.1.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "bitcoin-units", "bitcoin_hashes", @@ -64,10 +57,9 @@ dependencies = [ [[package]] name = "bitcoin-units" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe8fafd73be14659c450deb64f90d2fd5a354fce366a01223a8319cf4d98b40" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ - "bitcoin-consensus-encoding 0.1.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "serde", ] @@ -75,15 +67,24 @@ dependencies = [ [[package]] name = "bitcoin_hashes" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a45c2b41c457a9a9e4670422fcbdf109afb3b22bc920b4045e8bdfd788a3d" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?tag=bitcoin-crypto-0.2.0#11650c6c480088cc30a04e583e187cd7a84a2d8f" dependencies = [ - "bitcoin-consensus-encoding 0.1.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "hex-conservative 0.3.2", + "hex-conservative 1.2.0", "serde", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -96,11 +97,48 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "dash-num" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "dash-types", "serde", ] @@ -110,13 +148,14 @@ name = "dash-pkc" version = "0.0.0" dependencies = [ "base58ck", - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin_hashes", "cfg-if", "dash-num", "dash-types", "hex-conservative 0.3.2", "hex-literal", + "k256", "rand_core", "serde", "subtle", @@ -135,7 +174,7 @@ dependencies = [ name = "dash-primitives" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin-internals", "bitcoin-primitives", "bitcoin-units", @@ -166,7 +205,7 @@ dependencies = [ name = "dash-sample-solver" version = "0.0.0" dependencies = [ - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin-primitives", "bitcoin-units", "dash-num", @@ -185,8 +224,8 @@ name = "dash-script" version = "0.0.0" dependencies = [ "base58ck", - "bitcoin-consensus-encoding 0.2.0", - "bitcoin_hashes", + "bitcoin-consensus-encoding", + "dash-num", "dash-pkc", "dash-types", "serde", @@ -197,7 +236,7 @@ name = "dash-types" version = "0.0.0" dependencies = [ "base58ck", - "bitcoin-consensus-encoding 0.2.0", + "bitcoin-consensus-encoding", "bitcoin-primitives", "bitcoin_hashes", "cfg-if", @@ -212,10 +251,95 @@ name = "dash-types-marker" version = "0.0.0" dependencies = [ "quote", - "syn", + "syn 2.0.119", "xxhash-rust", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "hex-conservative" version = "0.3.2" @@ -240,12 +364,39 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + [[package]] name = "libm" version = "0.2.16" @@ -254,9 +405,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "once_cell" @@ -266,18 +417,18 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -288,17 +439,40 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "sec1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -306,29 +480,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -337,6 +511,27 @@ dependencies = [ "zmij", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "subtle" version = "2.6.1" @@ -345,26 +540,49 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasm-bindgen" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -375,9 +593,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -385,22 +603,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] @@ -428,11 +646,11 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/contrib/samples/Cargo.toml b/contrib/samples/Cargo.toml index 0075c000..9ca94f07 100644 --- a/contrib/samples/Cargo.toml +++ b/contrib/samples/Cargo.toml @@ -2,6 +2,11 @@ members = ["parser", "solver"] resolver = "2" +[workspace.dependencies] +bitcoin-consensus-encoding = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin-primitives = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } +bitcoin-units = { git = "https://github.com/rust-bitcoin/rust-bitcoin", tag = "bitcoin-crypto-0.2.0", default-features = false } + [workspace.lints.clippy] expect_used = "deny" panic = "deny" diff --git a/contrib/samples/solver/Cargo.toml b/contrib/samples/solver/Cargo.toml index 10053e69..46bd839f 100644 --- a/contrib/samples/solver/Cargo.toml +++ b/contrib/samples/solver/Cargo.toml @@ -19,15 +19,9 @@ std = [] full = ["std"] [dependencies] -bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ - "alloc", -] } -bitcoin-primitives = { version = "0.102", default-features = false, features = [ - "alloc", -] } -bitcoin-units = { version = "0.3", default-features = false, features = [ - "alloc", -] } +bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } +bitcoin-primitives = { workspace = true, features = ["alloc"] } +bitcoin-units = { workspace = true, features = ["alloc"] } dash-num = { version = "0.0.0", path = "../../../pkgs/num", default-features = false } dash-pow = { version = "0.0.0", path = "../../../pkgs/pow", default-features = false, features = ["simd"] } dash-primitives = { version = "0.0.0", path = "../../../pkgs/primitives", default-features = false } diff --git a/pkgs/dev/Cargo.toml b/pkgs/dev/Cargo.toml index 8bb9a89b..2b3f4cef 100644 --- a/pkgs/dev/Cargo.toml +++ b/pkgs/dev/Cargo.toml @@ -43,9 +43,7 @@ syn = { version = "2", features = ["full", "visit"] } xxhash-rust = { version = "0.8", features = ["xxh32"] } [dependencies] -bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ - "alloc", -] } +bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } cfg-if = "1" dash-num = { version = "0.0.0", path = "../num", optional = true } dash-params = { version = "0.0.0", path = "../params", optional = true } diff --git a/pkgs/dev/src/lambda.rs b/pkgs/dev/src/lambda.rs index d4b16a79..cae024e7 100644 --- a/pkgs/dev/src/lambda.rs +++ b/pkgs/dev/src/lambda.rs @@ -8,7 +8,7 @@ use crate::prelude::*; -use bitcoin_consensus_encoding::{decode_from_slice, encode_to_vec, Decodable, Decoder, Encodable}; +use bitcoin_consensus_encoding::{decode_from_slice, encode_to_vec, Decode, Decoder, Encode}; use dash_primitives::{Transaction, TxHash}; use dash_types::codec::{BaseCodec, Checkable, Hashable}; @@ -26,7 +26,7 @@ use core::fmt::{Debug, Display}; /// validation failure. pub fn check_tx(raw: &[u8], details: &T, label: &str) where - T: Encodable + Decodable + Checkable + PartialEq + Debug, + T: Encode + Decode + Checkable + PartialEq + Debug, ::Error: Debug + Display, T::Error: Display, { @@ -37,7 +37,7 @@ where } } -/// Check function for wire-encoded types (`Encodable`/`Decodable`). +/// Check function for wire-encoded types (`Encode`/`Decode`). /// /// Decodes `raw`, asserts equality with `details`, and verifies that /// re-encoding produces the original bytes. @@ -47,7 +47,7 @@ where /// Panics on decode failure or mismatch. pub fn check_wire(raw: &[u8], details: &T, label: &str) where - T: Encodable + Decodable + PartialEq + Debug, + T: Encode + Decode + PartialEq + Debug, ::Error: Debug + Display, { let decoded: T = decode_from_slice(raw).unwrap_or_else(|e| panic!("{label}: decode: {e}")); diff --git a/pkgs/num/Cargo.toml b/pkgs/num/Cargo.toml index a474646e..0a0f0420 100644 --- a/pkgs/num/Cargo.toml +++ b/pkgs/num/Cargo.toml @@ -11,7 +11,7 @@ full = ["std", "serde"] serde = ["dep:serde", "dash-types/serde"] [dependencies] -bitcoin-consensus-encoding = { version = "0.2", default-features = false } +bitcoin-consensus-encoding = { workspace = true } dash-types = { version = "0.0.0", path = "../types", default-features = false } serde = { version = "1", default-features = false, features = [ "derive", diff --git a/pkgs/num/src/util.rs b/pkgs/num/src/util.rs index 1fbbe065..eb2addf9 100644 --- a/pkgs/num/src/util.rs +++ b/pkgs/num/src/util.rs @@ -22,7 +22,7 @@ macro_rules! cfg_serde { ($($item:tt)*) => {}; } -/// Generates `BaseCodec` + `Encodable` + `Decodable` for hash newtypes. +/// Generates `BaseCodec` + `Encode` + `Decode` for hash newtypes. #[macro_export] macro_rules! impl_hash { ($base:ty, $($name:ident),* $(,)?) => { $( diff --git a/pkgs/p2p_core/Cargo.toml b/pkgs/p2p_core/Cargo.toml index 0071abf7..b919ef22 100644 --- a/pkgs/p2p_core/Cargo.toml +++ b/pkgs/p2p_core/Cargo.toml @@ -6,9 +6,17 @@ license = "MIT" [features] default = [] -std = ["dash-pkc/std", "dash-primitives/std", "dash-script/std", "dash-types/std"] +std = [ + "bitcoin-p2p-messages/std", + "bitcoin-primitives/std", + "dash-pkc/std", + "dash-primitives/std", + "dash-script/std", + "dash-types/std", +] serde = [ "dep:serde", + "bitcoin-p2p-messages/serde", "dash-num/serde", "dash-pkc/serde", "dash-primitives/serde", @@ -18,19 +26,20 @@ serde = [ full = ["std", "serde"] [dependencies] -bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ - "alloc", -] } -bitcoin-internals = { version = "0.5", default-features = false } -bitcoin-units = { version = "0.3", default-features = false, features = [ - "alloc", -] } +bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } +bitcoin-internals = { workspace = true } +bitcoin-p2p-messages = { workspace = true } +bitcoin-primitives = { workspace = true } +bitcoin-units = { workspace = true, features = ["alloc"] } +cfg-if = "1" dash-num = { version = "0.0.0", path = "../num" } dash-pkc = { version = "0.0.0", path = "../pkc" } dash-script = { version = "0.0.0", path = "../script" } dash-params = { version = "0.0.0", path = "../params" } dash-primitives = { version = "0.0.0", path = "../primitives" } -dash-types = { version = "0.0.0", path = "../types", default-features = false } +dash-types = { version = "0.0.0", path = "../types", default-features = false, features = [ + "bitcoin-p2p-messages", +] } serde = { version = "1", default-features = false, features = [ "alloc", "derive", diff --git a/pkgs/p2p_core/src/v2/mod.rs b/pkgs/p2p_core/src/bip324.rs similarity index 61% rename from pkgs/p2p_core/src/v2/mod.rs rename to pkgs/p2p_core/src/bip324.rs index acf6eb97..da0ca071 100644 --- a/pkgs/p2p_core/src/v2/mod.rs +++ b/pkgs/p2p_core/src/bip324.rs @@ -5,22 +5,15 @@ // //! BIP324 V2 message framing. -//! -//! Handles the 1-byte short-ID dispatch layer that sits between -//! the raw message payload and the BIP324 encrypted transport. -use crate::msg::DashNetworkMessage; +use crate::command::CommandString; +use crate::msg::P2pMsg; use crate::prelude::*; -use crate::primitives::CommandString; -use crate::primitives::ShortId; +use crate::short_id::ShortId; use crate::P2pDecodeError; -/// Encodes a `DashNetworkMessage` into V2 framed bytes. -/// -/// The result is ready to pass to `bip324::Payload::genuine()`. -/// -/// Format: `[1-byte short_id] [optional 12-byte command] [payload]` -pub fn encode_v2(msg: &DashNetworkMessage, buf: &mut Vec) { +/// Encodes a `P2pMsg` into V2 framed bytes. +pub fn encode_v2(msg: &P2pMsg, buf: &mut Vec) { match msg.short_id() { Some(id) => { buf.push(id.0); @@ -35,10 +28,8 @@ pub fn encode_v2(msg: &DashNetworkMessage, buf: &mut Vec) { msg.encode_payload(buf); } -/// Decodes V2 framed bytes into a `DashNetworkMessage`. -/// -/// `payload` is the decrypted content from `bip324::Payload::contents()`. -pub fn decode_v2(payload: &[u8]) -> Result { +/// Decodes V2 framed bytes into a `P2pMsg`. +pub fn decode_v2(payload: &[u8]) -> Result { if payload.is_empty() { return Err(P2pDecodeError::Consensus(String::from( "unexpected eof: needed 1 byte, 0 remaining", @@ -59,13 +50,13 @@ pub fn decode_v2(payload: &[u8]) -> Result { let mut cmd_bytes = [0u8; 12]; cmd_bytes.copy_from_slice(&rest[..12]); let cmd = CommandString::from_bytes(cmd_bytes); - DashNetworkMessage::decode_payload(&cmd, &rest[12..]) + P2pMsg::decode_payload(&cmd, &rest[12..]) } else { // Short ID: resolve to command, then decode payload. let sid = ShortId(short_id); let cmd = sid .to_command() .ok_or(P2pDecodeError::UnknownShortId { id: short_id })?; - DashNetworkMessage::decode_payload(&cmd, rest) + P2pMsg::decode_payload(&cmd, rest) } } diff --git a/pkgs/p2p_core/src/codec.rs b/pkgs/p2p_core/src/codec.rs index 7731c5cf..b9624cc9 100644 --- a/pkgs/p2p_core/src/codec.rs +++ b/pkgs/p2p_core/src/codec.rs @@ -9,7 +9,7 @@ /// Maximum buffered P2P message payload. pub(crate) const MAX_P2P_PAYLOAD_SIZE: usize = 3_145_728; -/// Generates `Encodable` + `Decodable` with P2P payload size limit. +/// Generates `Encode` + `Decode` with P2P payload size limit. macro_rules! impl_p2p { ($ty:ty) => { $crate::__private::dash_types::impl_type!($ty, crate::codec::MAX_P2P_PAYLOAD_SIZE); @@ -17,7 +17,7 @@ macro_rules! impl_p2p { } pub(crate) use impl_p2p; -/// Generates `BaseCodec` + `Encodable` + `Decodable` for flat structs +/// Generates `BaseCodec` + `Encode` + `Decode` for flat structs /// with P2P payload size limit. macro_rules! codec_p2p { ($ty:ty { $($field:ident),+ $(,)? }) => { diff --git a/pkgs/p2p_core/src/command.rs b/pkgs/p2p_core/src/command.rs new file mode 100644 index 00000000..f0ff0cd4 --- /dev/null +++ b/pkgs/p2p_core/src/command.rs @@ -0,0 +1,71 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Twelve-byte null-padded command string for P2P message dispatch. + +use dash_primitives::hash_impl; +use dash_types::{impl_bytes, TypeId}; + +use core::fmt; + +/// A 12-byte, null-padded ASCII command identifying a P2P message type. +#[derive(Clone, Copy, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct CommandString([u8; 12]); + +impl_bytes!(CommandString, 12); + +hash_impl!(CommandString); + +impl CommandString { + /// Builds a command string from a static `&str` at compile time. + /// + /// # Panics + /// + /// Compile-time panic if `s` is longer than 12 bytes. + pub const fn from_static(s: &str) -> Self { + let b = s.as_bytes(); + let len = b.len(); + assert!(len <= 12, "command string exceeds 12 bytes"); + let mut buf = [0u8; 12]; + let mut i = 0; + while i < len { + buf[i] = b[i]; + i += 1; + } + Self(buf) + } + + /// Wraps raw bytes into a command string. + pub const fn from_bytes(bytes: [u8; 12]) -> Self { + Self(bytes) + } + + /// Returns the raw 12-byte command buffer. + pub const fn as_bytes(&self) -> &[u8; 12] { + &self.0 + } + + /// Returns the command as a `&str` (trimmed of null padding). + pub fn as_str(&self) -> &str { + let end = self.0.iter().position(|&b| b == 0).unwrap_or(12); + // The bytes are always valid ASCII written by from_static or + // validated on decode, so this conversion is sound. + core::str::from_utf8(&self.0[..end]).unwrap_or("") + } +} + +impl fmt::Debug for CommandString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CommandString(\"{}\")", self.as_str()) + } +} + +impl fmt::Display for CommandString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} diff --git a/pkgs/p2p_core/src/error.rs b/pkgs/p2p_core/src/error.rs index fc6d3afd..13ea849a 100644 --- a/pkgs/p2p_core/src/error.rs +++ b/pkgs/p2p_core/src/error.rs @@ -32,11 +32,18 @@ pub enum P2pDecodeError { PayloadTooLarge { /// Wire command name. command: &'static str, - /// Actual decoded size. + /// Size of the raw payload, in bytes. size: usize, - /// Maximum allowed size. + /// Maximum allowed size, in bytes. max: usize, }, + /// Message carries a payload where none is permitted. + PayloadNotEmpty { + /// Wire command name. + command: &'static str, + /// Size of the raw payload, in bytes. + size: usize, + }, /// A field value is outside the valid range. InvalidValue { /// Brief description of what was invalid. @@ -74,6 +81,9 @@ impl fmt::Display for P2pDecodeError { Self::PayloadTooLarge { command, size, max } => { write!(f, "{command} payload too large: {size} bytes, max {max}") } + Self::PayloadNotEmpty { command, size } => { + write!(f, "{command} must have an empty payload, got {size} bytes") + } Self::InvalidValue { field } => { write!(f, "invalid value for {field}") } diff --git a/pkgs/p2p_core/src/lib.rs b/pkgs/p2p_core/src/lib.rs index 5b93507e..945cb27c 100644 --- a/pkgs/p2p_core/src/lib.rs +++ b/pkgs/p2p_core/src/lib.rs @@ -12,29 +12,31 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; +mod bip324; mod codec; +mod command; mod error; +#[macro_use] +mod macros; +mod magic; mod msg; #[allow(unused_imports, reason = "ergonomic shim, exports may be unused")] mod prelude; -mod primitives; -mod v2; +mod short_id; +mod version; #[doc(hidden)] pub mod __private { pub use dash_primitives; pub use dash_types; } +#[cfg(feature = "serde")] +pub(crate) mod serialize; +pub use bip324::{decode_v2, encode_v2}; +pub use command::CommandString; pub use error::P2pDecodeError; -pub use msg::{ - Addr, AddrV2Entry, AddrV2Msg, CFCheckpt, CFHeaders, CFilter, DashNetworkMessage, FilterType, GetCFCheckpt, - GetCFHeaders, GetCFilters, GetData, GetHeaders, GetHeaders2, GovSync, Headers, Headers2, Inv, NotFound, Ping, Pong, - TimestampedAddr, Version, VersionAddr, -}; -pub use primitives::{ - CommandString, CompressionState, DeletedQuorum, GetMnListDiff, InvType, Inventory, Magic, MnListDiff, - MnListDiffPayload, ProtocolVersion, QuorumClSig, ServiceFlags, ShortId, SimplifiedMnListEntry, UserAgent, - UserAgentTooLong, -}; -pub use v2::{decode_v2, encode_v2}; +pub use magic::Magic; +pub use msg::*; +pub use short_id::ShortId; +pub use version::ProtocolVersion; diff --git a/pkgs/p2p_core/src/macros.rs b/pkgs/p2p_core/src/macros.rs new file mode 100644 index 00000000..7bf78747 --- /dev/null +++ b/pkgs/p2p_core/src/macros.rs @@ -0,0 +1,211 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Shared macro definitions. + +use crate::codec::MAX_P2P_PAYLOAD_SIZE; +use crate::prelude::*; +use crate::P2pDecodeError; + +use bitcoin_consensus_encoding::{decode_from_slice, Decode, Decoder}; + +/// Decode from slice, mapping the error. +pub(crate) fn decode_msg(payload: &[u8]) -> Result +where + ::Error: core::fmt::Display, +{ + decode_from_slice(payload).map_err(|e| P2pDecodeError::Consensus(format!("{e}"))) +} + +/// Reject payloads that exceed the protocol limit. +pub(crate) fn check_payload(command: &'static str, payload: &[u8]) -> Result<(), P2pDecodeError> { + if payload.len() > MAX_P2P_PAYLOAD_SIZE { + return Err(P2pDecodeError::PayloadTooLarge { + command, + size: payload.len(), + max: MAX_P2P_PAYLOAD_SIZE, + }); + } + Ok(()) +} + +/// Reject payloads that should be empty. +pub(crate) fn check_empty(command: &'static str, payload: &[u8]) -> Result<(), P2pDecodeError> { + if !payload.is_empty() { + return Err(P2pDecodeError::PayloadNotEmpty { + command, + size: payload.len(), + }); + } + Ok(()) +} + +/// Generates `P2pMsg` definitions for every given valid message +macro_rules! define_p2p { + ( + // Fully-parsed messages with a typed payload. + parsed { + $( + $(#[$p_attr:meta])* + $p_variant:ident ( $p_type:ty ) => $p_cmd:ident $p_wire:literal $(@ $p_sid:literal)? + ),* $(,)? + } + // Fully-parsed messages with an empty payload. + parsed_empty { + $( + $(#[$pe_attr:meta])* + $pe_variant:ident => $pe_cmd:ident $pe_wire:literal $(@ $pe_sid:literal)? + ),* $(,)? + } + // Recognised but not-yet-implemented (raw `Vec` payload). + stub { + $( + $(#[$s_attr:meta])* + $s_variant:ident => $s_cmd:ident $s_wire:literal $(@ $s_sid:literal)? + ),* $(,)? + } + // Recognised but not-yet-implemented (empty payload). + stub_empty { + $( + $(#[$se_attr:meta])* + $se_variant:ident => $se_cmd:ident $se_wire:literal $(@ $se_sid:literal)? + ),* $(,)? + } + ) => { + impl CommandString { + $( + #[doc = concat!("Command string for `", $p_wire, "` messages.")] + pub const $p_cmd: Self = Self::from_static($p_wire); + )* + $( + #[doc = concat!("Command string for `", $pe_wire, "` messages.")] + pub const $pe_cmd: Self = Self::from_static($pe_wire); + )* + $( + #[doc = concat!("Command string for `", $s_wire, "` messages.")] + pub const $s_cmd: Self = Self::from_static($s_wire); + )* + $( + #[doc = concat!("Command string for `", $se_wire, "` messages.")] + pub const $se_cmd: Self = Self::from_static($se_wire); + )* + } + + impl ShortId { + /// Resolves the short ID to its command name, if one is assigned. + pub const fn to_command_str(self) -> Option<&'static str> { + match self.0 { + $( $( $p_sid => Some($p_wire), )? )* + $( $( $pe_sid => Some($pe_wire), )? )* + $( $( $s_sid => Some($s_wire), )? )* + $( $( $se_sid => Some($se_wire), )? )* + _ => None, + } + } + + /// Looks up the short ID for a command, if one is assigned. + /// + /// `None` means the message has no short ID and must be sent in + /// the long format. + pub fn from_command(cmd: &CommandString) -> Option { + match *cmd { + $( $( CommandString::$p_cmd => Some(Self($p_sid)), )? )* + $( $( CommandString::$pe_cmd => Some(Self($pe_sid)), )? )* + $( $( CommandString::$s_cmd => Some(Self($s_sid)), )? )* + $( $( CommandString::$se_cmd => Some(Self($se_sid)), )? )* + _ => None, + } + } + } + + /// A P2P network message. + #[derive(Clone, Debug, Eq, PartialEq, Unencodable)] + #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] + pub enum P2pMsg { + $( $(#[$p_attr])* $p_variant($p_type), )* + $( $(#[$pe_attr])* $pe_variant, )* + $( $(#[$s_attr])* $s_variant(Vec), )* + $( $(#[$se_attr])* $se_variant, )* + } + + impl P2pMsg { + /// Returns the 12-byte command string for this message. + pub fn command(&self) -> CommandString { + match self { + $( Self::$p_variant(_) => CommandString::$p_cmd, )* + $( Self::$pe_variant => CommandString::$pe_cmd, )* + $( Self::$s_variant(_) => CommandString::$s_cmd, )* + $( Self::$se_variant => CommandString::$se_cmd, )* + } + } + + /// Returns the V2 short ID for this message, if one exists. + pub fn short_id(&self) -> Option { + ShortId::from_command(&self.command()) + } + + /// Returns `true` when the message is recognised but not decoded. + pub fn is_stub(&self) -> bool { + match self { + $( Self::$p_variant(_) => false, )* + $( Self::$pe_variant => false, )* + $( Self::$s_variant(_) => true, )* + $( Self::$se_variant => true, )* + } + } + + /// Decodes a message from its command string and raw payload. + pub fn decode_payload( + cmd: &CommandString, + payload: &[u8], + ) -> Result { + let raw = || Vec::from(payload); + let msg = match *cmd { + $( + CommandString::$p_cmd => { + crate::macros::check_payload($p_wire, payload)?; + Self::$p_variant(crate::macros::decode_msg(payload)?) + } + )* + $( + CommandString::$pe_cmd => { + crate::macros::check_empty($pe_wire, payload)?; + Self::$pe_variant + } + )* + $( + CommandString::$s_cmd => { + crate::macros::check_payload($s_wire, payload)?; + Self::$s_variant(raw()) + } + )* + $( + CommandString::$se_cmd => { + crate::macros::check_empty($se_wire, payload)?; + Self::$se_variant + } + )* + _ => return Err(crate::P2pDecodeError::UnknownCommand { bytes: *cmd.as_bytes() }), + }; + Ok(msg) + } + + /// Encodes this message's payload (without command/short-ID framing). + pub fn encode_payload(&self, buf: &mut Vec) { + match self { + $( + Self::$p_variant(m) => { + buf.extend_from_slice(&encoding::encode_to_vec(m)); + } + )* + $( Self::$pe_variant => {} )* + $( Self::$s_variant(raw) => buf.extend_from_slice(raw), )* // nosemgrep: codec-no-raw-extend + $( Self::$se_variant => {} )* + } + } + } + }; +} diff --git a/pkgs/p2p_core/src/primitives/magic.rs b/pkgs/p2p_core/src/magic.rs similarity index 100% rename from pkgs/p2p_core/src/primitives/magic.rs rename to pkgs/p2p_core/src/magic.rs diff --git a/pkgs/p2p_core/src/msg/addr.rs b/pkgs/p2p_core/src/msg/addr.rs index fcbb1ec7..aa736385 100644 --- a/pkgs/p2p_core/src/msg/addr.rs +++ b/pkgs/p2p_core/src/msg/addr.rs @@ -4,11 +4,11 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Address messages: addr, addrv2 (getaddr and sendaddrv2 are empty). +//! Address messages. +use super::version::ServiceFlags; use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use crate::primitives::ServiceFlags; use dash_primitives::{hash_impl, AddrV2, ServiceV1}; use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; @@ -16,19 +16,15 @@ use dash_types::{CompactSize, TypeId}; use core::fmt; -/// Timestamped v1 address entry used in `addr` messages. +/// V1 address announcement carrying timestamped addresses. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct TimestampedAddr { - /// Seconds since Unix epoch. - pub time: u32, - /// Advertised services. - pub services: ServiceFlags, - /// IPv4-mapped IPv6 address + port. - pub addr: ServiceV1, +pub struct Addr { + /// Timestamped v1 address entries. + pub addrs: Vec, } -codec_p2p!(TimestampedAddr { time, services, addr }); +codec_p2p!(Addr { addrs }); /// BIP155 timestamped v2 address entry used in `addrv2` messages. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] @@ -36,11 +32,11 @@ codec_p2p!(TimestampedAddr { time, services, addr }); pub struct AddrV2Entry { /// Seconds since Unix epoch. pub time: u32, - /// Advertised services (CompactSize-encoded on wire). + /// Advertised services. pub services: ServiceFlags, /// Network address. pub addr: AddrV2, - /// Port number (big-endian on wire). + /// Port number. pub port: u16, } @@ -76,16 +72,6 @@ impl fmt::Display for AddrV2Entry { } } -/// V1 address announcement carrying timestamped addresses. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct Addr { - /// Timestamped v1 address entries. - pub addrs: Vec, -} - -codec_p2p!(Addr { addrs }); - /// BIP155 v2 address announcement. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] @@ -95,3 +81,17 @@ pub struct AddrV2Msg { } codec_p2p!(AddrV2Msg { addrs }); + +/// Timestamped v1 address entry used in `addr` messages. +#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct TimestampedAddr { + /// Seconds since Unix epoch. + pub time: u32, + /// Advertised services. + pub services: ServiceFlags, + /// Network address. + pub addr: ServiceV1, +} + +codec_p2p!(TimestampedAddr { time, services, addr }); diff --git a/pkgs/p2p_core/src/msg/compact_filters.rs b/pkgs/p2p_core/src/msg/compact_filters.rs deleted file mode 100644 index c065eaac..00000000 --- a/pkgs/p2p_core/src/msg/compact_filters.rs +++ /dev/null @@ -1,160 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! BIP157 compact block filter types and messages. - -use crate::codec::{codec_p2p, impl_p2p}; -use crate::prelude::*; - -use bitcoin_units::BlockHeight; -use dash_primitives::{hash_impl, BlockHash}; -use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; -use dash_types::{make_num, TypeId}; - -make_num! { - /// BIP157 filter type, encoded as a single byte on the wire. - FilterType, u8, 1 -} - -hash_impl!(FilterType); - -impl FilterType { - /// Basic filter (the only type defined by BIP158). - pub const BASIC: Self = Self(0); -} - -/// Requests compact filters for a range of blocks. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetCFilters { - /// Filter type (0 = basic). - pub filter_type: FilterType, - /// Start height (inclusive). - pub start_height: BlockHeight, - /// Stop block hash (inclusive). - pub stop_hash: BlockHash, -} - -impl_p2p!(GetCFilters); - -impl BaseCodec for GetCFilters { - fn decode(data: &mut &[u8]) -> Result { - Ok(Self { - filter_type: FilterType(u8::decode(data)?), - start_height: BlockHeight::from_u32(u32::decode(data)?), - stop_hash: BlockHash::decode(data)?, - }) - } - - fn encode(&self, buf: &mut impl EncodeBuf) { - self.filter_type.0.encode(buf); - self.start_height.to_u32().encode(buf); - self.stop_hash.encode(buf); - } -} - -hash_impl!(GetCFilters); - -/// A single compact block filter. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CFilter { - /// Filter type. - pub filter_type: FilterType, - /// Block hash this filter covers. - pub block_hash: BlockHash, - /// Raw GCS filter data. - pub filter_data: Vec, -} - -codec_p2p!(CFilter { - filter_type, - block_hash, - filter_data -}); - -/// Requests compact filter headers for a range of blocks. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetCFHeaders { - /// Filter type. - pub filter_type: FilterType, - /// Start height (inclusive). - pub start_height: BlockHeight, - /// Stop block hash (inclusive). - pub stop_hash: BlockHash, -} - -impl_p2p!(GetCFHeaders); - -impl BaseCodec for GetCFHeaders { - fn decode(data: &mut &[u8]) -> Result { - Ok(Self { - filter_type: FilterType(u8::decode(data)?), - start_height: BlockHeight::from_u32(u32::decode(data)?), - stop_hash: BlockHash::decode(data)?, - }) - } - - fn encode(&self, buf: &mut impl EncodeBuf) { - self.filter_type.0.encode(buf); - self.start_height.to_u32().encode(buf); - self.stop_hash.encode(buf); - } -} - -hash_impl!(GetCFHeaders); - -/// Response carrying filter headers and their hashes. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CFHeaders { - /// Filter type. - pub filter_type: FilterType, - /// Hash of the stop block. - pub stop_hash: BlockHash, - /// Previous filter header (for chaining). - pub previous_filter_header: BlockHash, - /// Filter hashes in block-height order. - pub filter_hashes: Vec, -} - -codec_p2p!(CFHeaders { - filter_type, - stop_hash, - previous_filter_header, - filter_hashes, -}); - -/// Requests evenly-spaced compact filter checkpoints. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetCFCheckpt { - /// Filter type. - pub filter_type: FilterType, - /// Stop block hash. - pub stop_hash: BlockHash, -} - -codec_p2p!(GetCFCheckpt { filter_type, stop_hash }); - -/// Response carrying filter header checkpoints at 1000-block intervals. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CFCheckpt { - /// Filter type. - pub filter_type: FilterType, - /// Stop block hash. - pub stop_hash: BlockHash, - /// Filter headers at every 1000th block. - pub filter_headers: Vec, -} - -codec_p2p!(CFCheckpt { - filter_type, - stop_hash, - filter_headers -}); diff --git a/pkgs/p2p_core/src/msg/headers.rs b/pkgs/p2p_core/src/msg/headers.rs index 9e619ebf..8b9d20e8 100644 --- a/pkgs/p2p_core/src/msg/headers.rs +++ b/pkgs/p2p_core/src/msg/headers.rs @@ -4,11 +4,11 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Block header messages: getheaders, headers, sendheaders. +//! Standard header messages. use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use crate::primitives::ProtocolVersion; +use crate::version::ProtocolVersion; use dash_primitives::{hash_impl, BlockHash, BlockHeader, MerkleRoot}; use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; diff --git a/pkgs/p2p_core/src/msg/headers2.rs b/pkgs/p2p_core/src/msg/headers2.rs index 8d805ee1..685426f0 100644 --- a/pkgs/p2p_core/src/msg/headers2.rs +++ b/pkgs/p2p_core/src/msg/headers2.rs @@ -4,19 +4,226 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Compressed header messages: getheaders2, headers2, sendheaders2. +//! Compressed header messages. use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use crate::primitives::{CompressionState, ProtocolVersion}; +use crate::version::ProtocolVersion; -use dash_primitives::{hash_impl, BlockHash}; -use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; -use dash_types::{CompactSize, TypeId}; +use dash_primitives::{hash_impl, BlockHash, BlockHeader, MerkleRoot}; +use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf, Hashable}; +use dash_types::{CompactSize, TypeId, Unencodable}; /// Maximum headers per message. const MAX_HEADERS: usize = 2_000; +/// Maximum entries in the MRU version cache. +const MAX_VERSION_CACHE: usize = 7; + +// Bits 0-2: version offset (0 = full version present, 1-7 = MRU cache index). +const VERSION_OFFSET_MASK: u8 = 0b0000_0111; + +// Bit 3: prev_blockhash present. +const FLAG_PREV_HASH: u8 = 0b0000_1000; + +// Bit 4: timestamp mode (0 = i16 delta, 1 = full u32). +const FLAG_TIMESTAMP_FULL: u8 = 0b0001_0000; + +// Bit 5: nbits present. +const FLAG_NBITS: u8 = 0b0010_0000; + +/// Stateful compressor/decompressor for a stream of block headers. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Unencodable)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct CompressionState { + /// MRU version cache (front = most recently used). + pub version_cache: Vec, + /// Previous fully-resolved header. + pub prev_header: Option, + /// Cached block hash of `prev_header`. + #[cfg_attr(feature = "serde", serde(skip))] + prev_block_hash: Option, +} + +impl CompressionState { + /// Creates fresh state with an empty cache and no previous header. + pub fn new() -> Self { + Self { + version_cache: Vec::with_capacity(MAX_VERSION_CACHE), + prev_header: None, + prev_block_hash: None, + } + } + + /// Moves the version at `position` to the front of the cache. + fn mark_version_mru(&mut self, position: usize) { + let v = self.version_cache.remove(position); + self.version_cache.insert(0, v); + } + + /// Inserts `version` at the front, evicting the oldest if full. + fn save_version_mru(&mut self, version: i32) { + if self.version_cache.len() >= MAX_VERSION_CACHE { + self.version_cache.pop(); + } + self.version_cache.insert(0, version); + } + + /// Finds the cache position (0-based) for a version, if cached. + fn find_version(&self, version: i32) -> Option { + self.version_cache.iter().position(|&v| v == version) + } + + /// Returns cached hash, recomputing from `prev_header` if the cache is cold. + fn prev_hash(&mut self) -> Option { + if self.prev_block_hash.is_none() { + self.prev_block_hash = self.prev_header.as_ref().map(|h| h.hash()); + } + self.prev_block_hash + } + + /// Decodes one compressed header, advancing the slice and state. + pub(crate) fn decode_header(&mut self, sl: &mut &[u8]) -> Result { + let flags = u8::decode(sl)?; + let version_offset = flags & VERSION_OFFSET_MASK; + + let version = if version_offset == 0 { + let v = i32::decode(sl)?; + self.save_version_mru(v); + v + } else { + let pos = (version_offset - 1) as usize; + if pos >= self.version_cache.len() { + return Err(DecodeError::InvalidValue { + expected: (0..self.version_cache.len() as u64).collect(), + actual: pos as u64, + }); + } + let v = self.version_cache[pos]; + self.mark_version_mru(pos); + v + }; + + let prev_hash = if flags & FLAG_PREV_HASH != 0 { + BlockHash::decode(sl)? + } else { + self.prev_hash().unwrap_or_default() + }; + + let merkle_root = MerkleRoot::decode(sl)?; + + let time = if flags & FLAG_TIMESTAMP_FULL != 0 { + u32::decode(sl)? + } else { + let delta = i16::decode(sl)?; + match &self.prev_header { + Some(prev) => (prev.time as i64 + delta as i64) as u32, + None => delta as u32, + } + }; + + let bits = if flags & FLAG_NBITS != 0 { + u32::decode(sl)? + } else { + match &self.prev_header { + Some(prev) => prev.bits, + None => 0, + } + }; + + let nonce = u32::decode(sl)?; + + let header = BlockHeader { + version, + prev_hash, + merkle_root, + time, + bits, + nonce, + }; + self.prev_block_hash = Some(header.hash()); + self.prev_header = Some(header); + Ok(header) + } + + /// Encodes one header in compressed form, advancing the state. + pub(crate) fn encode_header(&mut self, header: &BlockHeader, buf: &mut impl EncodeBuf) { + let mut flags: u8 = 0; + + let version_offset = match self.find_version(header.version) { + Some(pos) => (pos + 1) as u8, + None => 0, + }; + flags |= version_offset & VERSION_OFFSET_MASK; + + let need_prev_hash = match self.prev_hash() { + Some(hash) => header.prev_hash != hash, + None => true, + }; + if need_prev_hash { + flags |= FLAG_PREV_HASH; + } + + let time_delta = match &self.prev_header { + Some(prev) => { + let d = header.time as i64 - prev.time as i64; + if (i16::MIN as i64..=i16::MAX as i64).contains(&d) { + Some(d as i16) + } else { + None + } + } + None => None, + }; + if time_delta.is_none() { + flags |= FLAG_TIMESTAMP_FULL; + } + + let need_nbits = match &self.prev_header { + Some(prev) => header.bits != prev.bits, + None => true, + }; + if need_nbits { + flags |= FLAG_NBITS; + } + + buf.push(flags); + + if version_offset == 0 { + header.version.encode(buf); + self.save_version_mru(header.version); + } else { + let pos = (version_offset - 1) as usize; + self.mark_version_mru(pos); + } + + if need_prev_hash { + header.prev_hash.encode(buf); + } + + header.merkle_root.encode(buf); + + match time_delta { + Some(d) => d.encode(buf), + None => header.time.encode(buf), + } + + if need_nbits { + header.bits.encode(buf); + } + + header.nonce.encode(buf); + self.prev_block_hash = Some(header.hash()); + self.prev_header = Some(*header); + } +} + +impl Default for CompressionState { + fn default() -> Self { + Self::new() + } +} + /// Requests compressed block headers starting from a locator. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] @@ -35,12 +242,12 @@ codec_p2p!(GetHeaders2 { hash_stop }); -/// Response carrying DIP-0025 delta-compressed block headers. +/// Response carrying delta-compressed block headers. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct Headers2 { - /// Fully resolved block headers (decompressed). - pub headers: Vec, + /// Fully resolved block headers. + pub headers: Vec, } impl_p2p!(Headers2); diff --git a/pkgs/p2p_core/src/msg/inv.rs b/pkgs/p2p_core/src/msg/inv.rs index 4623f8a7..a9fc655f 100644 --- a/pkgs/p2p_core/src/msg/inv.rs +++ b/pkgs/p2p_core/src/msg/inv.rs @@ -4,33 +4,80 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Inventory messages: inv, getdata, notfound. +//! Inventory messages and types. use crate::codec::codec_p2p; use crate::prelude::*; -use crate::primitives::Inventory; -use dash_types::TypeId; +use dash_num::Hash256; +use dash_primitives::hash_impl; +use dash_types::{enum_map, impl_num, TypeId}; -/// Announces available inventory to a peer. +use core::fmt; + +/// Requests specific inventory items from a peer. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct Inv { - /// Inventory items being announced. +pub struct GetData { + /// Inventory items being requested. pub inventory: Vec, } -codec_p2p!(Inv { inventory }); +codec_p2p!(GetData { inventory }); -/// Requests specific inventory items from a peer. +enum_map! { + /// Inventory object type. + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] + pub enum InvType, u32, Unknown { + /// Error / not used. + Error = 0 => "error", + /// Transaction. + Tx = 1 => "tx", + /// Block. + Block = 2 => "block", + /// Filtered block (BIP37). + FilteredBlock = 3 => "filtered_block", + /// Governance object. + GovernanceObject = 17 => "governance_object", + /// Governance object vote. + GovernanceObjectVote = 18 => "governance_object_vote", + /// Compact block (BIP152). + CompactBlock = 20 => "compact_block", + } +} + +impl_num!(InvType, u32); + +hash_impl!(InvType); + +/// An inventory vector: a typed 32-byte hash. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct Inventory { + /// Object type. + #[cfg_attr(feature = "serde", serde(rename = "type"))] + pub inv_type: InvType, + /// Object hash. + pub hash: Hash256, +} + +codec_p2p!(Inventory { inv_type, hash }); + +impl fmt::Display for Inventory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.inv_type, self.hash) + } +} + +/// Announces available inventory to a peer. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetData { - /// Inventory items being requested. +pub struct Inv { + /// Inventory items being announced. pub inventory: Vec, } -codec_p2p!(GetData { inventory }); +codec_p2p!(Inv { inventory }); /// Indicates requested inventory items were not found. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] diff --git a/pkgs/p2p_core/src/primitives/mn_list.rs b/pkgs/p2p_core/src/msg/mn_list.rs similarity index 99% rename from pkgs/p2p_core/src/primitives/mn_list.rs rename to pkgs/p2p_core/src/msg/mn_list.rs index 8481b892..a6a7b9a3 100644 --- a/pkgs/p2p_core/src/primitives/mn_list.rs +++ b/pkgs/p2p_core/src/msg/mn_list.rs @@ -4,7 +4,7 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Simplified masternode list types for `getmnlistd`/`mnlistdiff`. +//! Simplified masternode list. use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; @@ -19,6 +19,121 @@ use dash_types::TypeId; use core::fmt; +/// Deleted quorum identifier (type + hash). +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct DeletedQuorum { + /// LLMQ type. + pub llmq_type: LlmqType, + /// Quorum hash. + pub hash: BlockHash, +} + +codec_p2p!(DeletedQuorum { llmq_type, hash }); + +/// Requests a masternode list diff between two blocks. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct GetMnListDiff { + /// Base block hash (beginning of range). + pub base_block_hash: BlockHash, + /// Target block hash (end of range). + pub block_hash: BlockHash, +} + +codec_p2p!(GetMnListDiff { + base_block_hash, + block_hash +}); + +/// Response carrying the masternode list diff. +#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct MnListDiff { + /// The full diff payload. + pub payload: MnListDiffPayload, +} + +codec_p2p!(MnListDiff { payload }); + +/// Full masternode list diff payload. +#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct MnListDiffPayload { + /// Serialisation version. + pub version: u16, + /// Base block hash (start of the diff range). + pub base_block_hash: BlockHash, + /// Target block hash (end of the diff range). + pub block_hash: BlockHash, + /// Number of transactions in the coinbase merkle proof. + pub total_transactions: u32, + /// Merkle branch hashes. + pub merkle_hashes: Vec, + /// Merkle branch flag bytes. + #[cfg_attr(feature = "serde", serde(with = "dash_types::serialize::hex"))] + pub merkle_flags: Vec, + /// Coinbase transaction (carries the MN list commitment). + pub cb_tx: Transaction, + /// ProTxHashes of removed masternodes. + pub deleted_mns: Vec, + /// New or updated masternode entries. + pub mn_list: Vec, + /// Removed quorums. + pub deleted_quorums: Vec, + /// New quorum final commitments. + pub new_quorums: Vec, + /// Chainlock signature mappings. + pub quorum_cl_sigs: Vec, +} + +codec_p2p!(MnListDiffPayload { + version, + base_block_hash, + block_hash, + total_transactions, + merkle_hashes, + merkle_flags, + cb_tx, + deleted_mns, + mn_list, + deleted_quorums, + new_quorums, + quorum_cl_sigs, +}); + +impl fmt::Display for MnListDiffPayload { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "mnlistdiff v{}: {}..{} ({} MNs, {} deleted)", + self.version, + self.base_block_hash, + self.block_hash, + self.mn_list.len(), + self.deleted_mns.len(), + ) + } +} + +/// Chainlock signature entry in the MN list diff. +/// +/// Each entry maps a BLS signature to the indices (within +/// `new_quorums`) of the quorums it covers. +#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct QuorumClSig { + /// BLS signature. + pub sig: BlsSigBytes, + /// Indices into the `new_quorums` vector. + pub index_set: Vec, +} + +codec_p2p!(QuorumClSig { sig, index_set }); + /// A single entry in the simplified masternode list. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] @@ -113,121 +228,6 @@ impl fmt::Display for SimplifiedMnListEntry { } } -/// Deleted quorum identifier (type + hash). -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct DeletedQuorum { - /// LLMQ type. - pub llmq_type: LlmqType, - /// Quorum hash. - pub hash: BlockHash, -} - -codec_p2p!(DeletedQuorum { llmq_type, hash }); - -/// Chainlock signature entry in the MN list diff. -/// -/// Each entry maps a BLS signature to the indices (within -/// `new_quorums`) of the quorums it covers. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct QuorumClSig { - /// BLS signature. - pub sig: BlsSigBytes, - /// Indices into the `new_quorums` vector. - pub index_set: Vec, -} - -codec_p2p!(QuorumClSig { sig, index_set }); - -/// Full masternode list diff payload. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct MnListDiffPayload { - /// Serialisation version. - pub version: u16, - /// Base block hash (start of the diff range). - pub base_block_hash: BlockHash, - /// Target block hash (end of the diff range). - pub block_hash: BlockHash, - /// Number of transactions in the coinbase merkle proof. - pub total_transactions: u32, - /// Merkle branch hashes. - pub merkle_hashes: Vec, - /// Merkle branch flag bytes. - #[cfg_attr(feature = "serde", serde(with = "dash_types::serialize::hex"))] - pub merkle_flags: Vec, - /// Coinbase transaction (carries the MN list commitment). - pub cb_tx: Transaction, - /// ProTxHashes of removed masternodes. - pub deleted_mns: Vec, - /// New or updated masternode entries. - pub mn_list: Vec, - /// Removed quorums. - pub deleted_quorums: Vec, - /// New quorum final commitments. - pub new_quorums: Vec, - /// Chainlock signature mappings. - pub quorum_cl_sigs: Vec, -} - -codec_p2p!(MnListDiffPayload { - version, - base_block_hash, - block_hash, - total_transactions, - merkle_hashes, - merkle_flags, - cb_tx, - deleted_mns, - mn_list, - deleted_quorums, - new_quorums, - quorum_cl_sigs, -}); - -impl fmt::Display for MnListDiffPayload { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "mnlistdiff v{}: {}..{} ({} MNs, {} deleted)", - self.version, - self.base_block_hash, - self.block_hash, - self.mn_list.len(), - self.deleted_mns.len(), - ) - } -} - -/// Requests a masternode list diff between two blocks. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetMnListDiff { - /// Base block hash (beginning of range). - pub base_block_hash: BlockHash, - /// Target block hash (end of range). - pub block_hash: BlockHash, -} - -codec_p2p!(GetMnListDiff { - base_block_hash, - block_hash -}); - -/// Response carrying the masternode list diff. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct MnListDiff { - /// The full diff payload. - pub payload: MnListDiffPayload, -} - -codec_p2p!(MnListDiff { payload }); - #[cfg(all(test, feature = "serde"))] mod tests { use super::*; diff --git a/pkgs/p2p_core/src/msg/mod.rs b/pkgs/p2p_core/src/msg/mod.rs index c1455c4d..5806664a 100644 --- a/pkgs/p2p_core/src/msg/mod.rs +++ b/pkgs/p2p_core/src/msg/mod.rs @@ -6,319 +6,202 @@ //! P2P message types and dispatch. +mod addr; +mod gov; +mod headers; +mod headers2; +mod inv; +mod mn_list; +mod ping; +mod version; + +use crate::command::CommandString; use crate::prelude::*; -use crate::primitives::CommandString; -use crate::primitives::ShortId; -use crate::primitives::{GetMnListDiff, MnListDiff}; +use crate::short_id::ShortId; use bitcoin_consensus_encoding as encoding; +use bitcoin_p2p_messages::message_bloom::{FilterAdd, FilterLoad}; +use bitcoin_p2p_messages::message_compact_blocks::SendCmpct; +use bitcoin_p2p_messages::message_filter::{CFCheckpt, CFHeaders, CFilter, GetCFCheckpt, GetCFHeaders, GetCFilters}; +use dash_primitives::{GovObject, GovVote}; use dash_types::Unencodable; -pub mod addr; -pub mod compact_filters; -pub mod gov; -pub mod headers; -pub mod headers2; -pub mod inv; -pub mod ping; -pub mod version; - pub use addr::{Addr, AddrV2Entry, AddrV2Msg, TimestampedAddr}; -pub use compact_filters::{CFCheckpt, CFHeaders, CFilter, FilterType, GetCFCheckpt, GetCFHeaders, GetCFilters}; pub use gov::GovSync; pub use headers::{GetHeaders, Headers}; -pub use headers2::{GetHeaders2, Headers2}; -pub use inv::{GetData, Inv, NotFound}; +pub use headers2::{CompressionState, GetHeaders2, Headers2}; +pub use inv::{GetData, Inv, InvType, Inventory, NotFound}; +pub use mn_list::{DeletedQuorum, GetMnListDiff, MnListDiff, MnListDiffPayload, QuorumClSig, SimplifiedMnListEntry}; pub use ping::{Ping, Pong}; -pub use version::{Version, VersionAddr}; - -/// Decode helper: decode from slice, mapping the error. -fn decode_msg(payload: &[u8]) -> Result -where - ::Error: core::fmt::Display, -{ - encoding::decode_from_slice(payload).map_err(|e| crate::P2pDecodeError::Consensus(format!("{e}"))) -} - -/// Generates `DashNetworkMessage`, its `command()`, `short_id()`, -/// `is_stub()`, `decode_payload()`, and `encode_payload()` methods -/// from a single definition table. Each entry is written once; the -/// macro fans it out to every match arm and enum variant. -macro_rules! define_network_messages { - ( - // Fully-parsed messages with a typed payload. - parsed { - $( - $(#[$p_doc:meta])* - $p_variant:ident ( $p_type:ty ) => $p_cmd:ident - ),* $(,)? - } - // Fully-parsed messages with an empty payload. - parsed_empty { - $( - $(#[$pe_doc:meta])* - $pe_variant:ident => $pe_cmd:ident - ),* $(,)? - } - // Recognised but not-yet-implemented (raw `Vec` payload). - stub { - $( - $(#[$s_doc:meta])* - $s_variant:ident => $s_cmd:ident - ),* $(,)? - } - // Recognised but not-yet-implemented (empty payload). - stub_empty { - $( - $(#[$se_doc:meta])* - $se_variant:ident => $se_cmd:ident - ),* $(,)? - } - ) => { - /// A Dash P2P network message. - /// - /// Fully-parsed variants carry typed payloads. Recognised but - /// not-yet-implemented messages use `Vec` to hold the raw - /// payload so callers can identify *what* was received (for - /// logging) without needing a full decoder. - #[derive(Clone, Debug, Eq, Hash, PartialEq, Unencodable)] - #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] - pub enum DashNetworkMessage { - $( $(#[$p_doc])* $p_variant($p_type), )* - $( $(#[$pe_doc])* $pe_variant, )* - $( $(#[$s_doc])* $s_variant(Vec), )* - $( $(#[$se_doc])* $se_variant, )* - } - - impl DashNetworkMessage { - /// Returns the 12-byte command string for this message. - pub fn command(&self) -> CommandString { - match self { - $( Self::$p_variant(_) => CommandString::$p_cmd, )* - $( Self::$pe_variant => CommandString::$pe_cmd, )* - $( Self::$s_variant(_) => CommandString::$s_cmd, )* - $( Self::$se_variant => CommandString::$se_cmd, )* - } - } - - /// Returns the V2 short ID for this message, if one exists. - pub fn short_id(&self) -> Option { - ShortId::from_command(&self.command()) - } - - /// Returns `true` when the message type is recognised but - /// its payload is not fully decoded (a stub). - pub fn is_stub(&self) -> bool { - match self { - $( Self::$p_variant(_) => false, )* - $( Self::$pe_variant => false, )* - $( Self::$s_variant(_) => true, )* - $( Self::$se_variant => true, )* - } - } - - /// Decodes a message from its command string and raw payload. - /// - /// Fully-implemented messages are decoded into typed variants. - /// Recognised stubs retain the raw payload as `Vec`. - pub fn decode_payload( - cmd: &CommandString, - payload: &[u8], - ) -> Result { - let raw = || Vec::from(payload); - let msg = match *cmd { - $( CommandString::$p_cmd => Self::$p_variant(decode_msg(payload)?), )* - $( CommandString::$pe_cmd => Self::$pe_variant, )* - $( CommandString::$s_cmd => Self::$s_variant(raw()), )* - $( CommandString::$se_cmd => Self::$se_variant, )* - _ => return Err(crate::P2pDecodeError::UnknownCommand { bytes: *cmd.as_bytes() }), - }; - Ok(msg) - } +pub use version::{ServiceFlags, UserAgent, UserAgentTooLong, Version, VersionAddr}; - /// Encodes this message's payload (without command/short-ID - /// framing). - pub fn encode_payload(&self, buf: &mut Vec) { - match self { - $( - Self::$p_variant(m) => { - buf.extend_from_slice(&encoding::encode_to_vec(m)); - } - )* - $( Self::$pe_variant => {} )* - $( Self::$s_variant(raw) => buf.extend_from_slice(raw), )* // nosemgrep: codec-no-raw-extend - $( Self::$se_variant => {} )* - } - } - } - }; -} - -define_network_messages! { +define_p2p! { parsed { /// Protocol version exchange. - Version(Version) => VERSION, + Version(Version) => VERSION "version", /// Keepalive request. - Ping(Ping) => PING, + Ping(Ping) => PING "ping" @ 18, /// Keepalive response. - Pong(Pong) => PONG, + Pong(Pong) => PONG "pong" @ 19, /// V1 address list. - Addr(Addr) => ADDR, + Addr(Addr) => ADDR "addr" @ 1, /// BIP155 V2 address list. - AddrV2(AddrV2Msg) => ADDRV2, + AddrV2(AddrV2Msg) => ADDRV2 "addrv2" @ 28, /// Inventory announcement. - Inv(Inv) => INV, + Inv(Inv) => INV "inv" @ 14, /// Request specific inventory. - GetData(GetData) => GETDATA, + GetData(GetData) => GETDATA "getdata" @ 11, /// Inventory not found. - NotFound(NotFound) => NOTFOUND, + NotFound(NotFound) => NOTFOUND "notfound" @ 17, /// Request block headers. - GetHeaders(GetHeaders) => GETHEADERS, + GetHeaders(GetHeaders) => GETHEADERS "getheaders" @ 12, /// Block headers. - Headers(Headers) => HEADERS, + Headers(Headers) => HEADERS "headers" @ 13, /// Request compressed block headers. - GetHeaders2(GetHeaders2) => GETHEADERS2, + GetHeaders2(GetHeaders2) => GETHEADERS2 "getheaders2" @ 163, /// Compressed block headers. - Headers2(Headers2) => HEADERS2, + Headers2(Headers2) => HEADERS2 "headers2" @ 165, /// Request compact filters. - GetCFilters(GetCFilters) => GETCFILTERS, + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::get_cfilters"))] + GetCFilters(GetCFilters) => GETCFILTERS "getcfilters" @ 22, /// Compact block filter. - CFilter(CFilter) => CFILTER, + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::cfilter"))] + CFilter(CFilter) => CFILTER "cfilter" @ 23, /// Request compact filter headers. - GetCFHeaders(GetCFHeaders) => GETCFHEADERS, + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::get_cfheaders"))] + GetCFHeaders(GetCFHeaders) => GETCFHEADERS "getcfheaders" @ 24, /// Compact filter headers. - CFHeaders(CFHeaders) => CFHEADERS, + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::cfheaders"))] + CFHeaders(CFHeaders) => CFHEADERS "cfheaders" @ 25, /// Request compact filter checkpoints. - GetCFCheckpt(GetCFCheckpt) => GETCFCHECKPT, + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::get_cfcheckpt"))] + GetCFCheckpt(GetCFCheckpt) => GETCFCHECKPT "getcfcheckpt" @ 26, /// Compact filter checkpoints. - CFCheckpt(CFCheckpt) => CFCHECKPT, + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::cfcheckpt"))] + CFCheckpt(CFCheckpt) => CFCHECKPT "cfcheckpt" @ 27, /// Governance sync request. - GovSync(GovSync) => GOVSYNC, + GovSync(GovSync) => GOVSYNC "govsync" @ 140, /// Governance object. - GovObj(dash_primitives::GovObject) => GOVOBJ, + GovObj(GovObject) => GOVOBJ "govobj" @ 141, /// Governance vote. - GovObjVote(dash_primitives::GovVote) => GOVOBJVOTE, + GovObjVote(GovVote) => GOVOBJVOTE "govobjvote" @ 142, /// Request MN list diff. - GetMnListDiff(GetMnListDiff) => GETMNLISTD, + GetMnListDiff(GetMnListDiff) => GETMNLISTD "getmnlistd" @ 143, /// MN list diff. - MnListDiff(MnListDiff) => MNLISTDIFF, + MnListDiff(MnListDiff) => MNLISTDIFF "mnlistdiff" @ 144, + /// BIP152: signal compact block support. + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::send_cmpct"))] + SendCmpct(SendCmpct) => SENDCMPCT "sendcmpct" @ 20, + /// BIP37: load bloom filter. + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::filter_load"))] + FilterLoad(FilterLoad) => FILTERLOAD "filterload" @ 8, + /// BIP37: add data to bloom filter. + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::filter_add"))] + FilterAdd(FilterAdd) => FILTERADD "filteradd" @ 6, } parsed_empty { /// Version acknowledgement. - Verack => VERACK, + Verack => VERACK "verack", /// Request peer addresses. - GetAddr => GETADDR, + GetAddr => GETADDR "getaddr", /// Signal addrv2 support. - SendAddrV2 => SENDADDRV2, + SendAddrV2 => SENDADDRV2 "sendaddrv2", /// Prefer unsolicited header announcements. - SendHeaders => SENDHEADERS, + SendHeaders => SENDHEADERS "sendheaders", /// Prefer compressed header announcements. - SendHeaders2 => SENDHEADERS2, + SendHeaders2 => SENDHEADERS2 "sendheaders2" @ 164, } stub { - // Bitcoin base protocol /// Block data. - Block => BLOCK, + Block => BLOCK "block" @ 2, /// BIP152: compact block transactions. - BlockTxn => BLOCKTXN, + BlockTxn => BLOCKTXN "blocktxn" @ 3, /// BIP152: compact block. - CmpctBlock => CMPCTBLOCK, - /// BIP37: add data to bloom filter. - FilterAdd => FILTERADD, - /// BIP37: load bloom filter. - FilterLoad => FILTERLOAD, + CmpctBlock => CMPCTBLOCK "cmpctblock" @ 4, /// Request block hashes. - GetBlocks => GETBLOCKS, + GetBlocks => GETBLOCKS "getblocks" @ 9, /// BIP152: request compact block transactions. - GetBlockTxn => GETBLOCKTXN, + GetBlockTxn => GETBLOCKTXN "getblocktxn" @ 10, /// BIP37: filtered block. - MerkleBlock => MERKLEBLOCK, - /// BIP152: signal compact block support. - SendCmpct => SENDCMPCT, + MerkleBlock => MERKLEBLOCK "merkleblock" @ 16, /// Transaction. - Tx => TX, + Tx => TX "tx" @ 21, /// BIP330: transaction reconciliation. - SendTxRcncl => SENDTXRCNCL, - // Sporks + SendTxRcncl => SENDTXRCNCL "sendtxrcncl", /// Spork broadcast/request. - Spork => SPORK, - // CoinJoin + Spork => SPORK "spork" @ 128, /// CoinJoin: accept denomination. - Dsa => DSA, + Dsa => DSA "dsa" @ 131, /// CoinJoin: submit inputs. - Dsi => DSI, + Dsi => DSI "dsi" @ 132, /// CoinJoin: final transaction. - Dsf => DSF, + Dsf => DSF "dsf" @ 133, /// CoinJoin: sign final transaction. - Dss => DSS, + Dss => DSS "dss" @ 134, /// CoinJoin: complete. - Dsc => DSC, + Dsc => DSC "dsc" @ 135, /// CoinJoin: status update. - Dssu => DSSU, + Dssu => DSSU "dssu" @ 136, /// CoinJoin: broadcast transaction. - Dstx => DSTX, + Dstx => DSTX "dstx" @ 137, /// CoinJoin: queue entry. - Dsq => DSQ, + Dsq => DSQ "dsq" @ 138, /// Sync status count. - Ssc => SSC, - // LLMQ / Quorum + Ssc => SSC "ssc" @ 139, /// LLMQ: final commitment. - QfCommit => QFCOMMIT, + QfCommit => QFCOMMIT "qfcommit" @ 146, /// LLMQ: contribution. - QContrib => QCONTRIB, + QContrib => QCONTRIB "qcontrib" @ 147, /// LLMQ: complaint. - QComplaint => QCOMPLAINT, + QComplaint => QCOMPLAINT "qcomplaint" @ 148, /// LLMQ: justification. - QJustify => QJUSTIFY, + QJustify => QJUSTIFY "qjustify" @ 149, /// LLMQ: premature commitment. - QpCommit => QPCOMMIT, + QpCommit => QPCOMMIT "qpcommit" @ 150, /// LLMQ: signing session announcement. - QSigSesAnn => QSIGSESANN, + QSigSesAnn => QSIGSESANN "qsigsesann" @ 152, /// LLMQ: signature shares inventory. - QSigsInv => QSIGSINV, + QSigsInv => QSIGSINV "qsigsinv" @ 153, /// LLMQ: request signature shares. - QGetSigs => QGETSIGS, + QGetSigs => QGETSIGS "qgetsigs" @ 154, /// LLMQ: batched signature shares. - QbSigs => QBSIGS, + QbSigs => QBSIGS "qbsigs" @ 155, /// LLMQ: recovered signature. - QSigRec => QSIGREC, + QSigRec => QSIGREC "qsigrec" @ 156, /// LLMQ: single signature share. - QSigShare => QSIGSHARE, + QSigShare => QSIGSHARE "qsigshare" @ 157, /// LLMQ: request quorum data. - QGetData => QGETDATA, + QGetData => QGETDATA "qgetdata" @ 158, /// LLMQ: quorum data. - QData => QDATA, - // InstantSend / ChainLock + QData => QDATA "qdata" @ 159, /// ChainLock signature. - ClSig => CLSIG, + ClSig => CLSIG "clsig" @ 160, /// InstantSend deterministic lock. - IsdLock => ISDLOCK, - // Masternode auth / rotation + IsdLock => ISDLOCK "isdlock" @ 161, /// Masternode authentication. - MnAuth => MNAUTH, + MnAuth => MNAUTH "mnauth" @ 162, /// Request quorum rotation info. - GetQrInfo => GETQRINFO, + GetQrInfo => GETQRINFO "getqrinfo" @ 166, /// Quorum rotation info. - QrInfo => QRINFO, - // Platform + QrInfo => QRINFO "qrinfo" @ 167, /// DIP-0031: platform ban. - PlatformBan => PLATFORMBAN, + PlatformBan => PLATFORMBAN "platformban" @ 168, } stub_empty { /// BIP37: clear bloom filter. - FilterClear => FILTERCLEAR, + FilterClear => FILTERCLEAR "filterclear" @ 7, /// Request mempool contents. - Mempool => MEMPOOL, + Mempool => MEMPOOL "mempool" @ 15, /// Request active sporks. - GetSporks => GETSPORKS, + GetSporks => GETSPORKS "getsporks" @ 129, /// Signal CoinJoin queue relay. - SendDsq => SENDDSQ, + SendDsq => SENDDSQ "senddsq" @ 130, /// LLMQ: send recovered signatures. - QSendRecSigs => QSENDRECSIGS, + QSendRecSigs => QSENDRECSIGS "qsendrecsigs" @ 145, /// LLMQ: watch quorums. - QWatch => QWATCH, + QWatch => QWATCH "qwatch" @ 151, } } diff --git a/pkgs/p2p_core/src/msg/ping.rs b/pkgs/p2p_core/src/msg/ping.rs index be9ef7ae..92a4fca3 100644 --- a/pkgs/p2p_core/src/msg/ping.rs +++ b/pkgs/p2p_core/src/msg/ping.rs @@ -4,7 +4,7 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Ping and Pong keepalive messages. +//! Keepalive messages. use crate::codec::codec_p2p; diff --git a/pkgs/p2p_core/src/msg/version.rs b/pkgs/p2p_core/src/msg/version.rs index 4bb52032..d21b4e98 100644 --- a/pkgs/p2p_core/src/msg/version.rs +++ b/pkgs/p2p_core/src/msg/version.rs @@ -4,16 +4,156 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Version handshake message (Dash-extended). +//! Version and capability handshake. -use crate::codec::codec_p2p; -use crate::primitives::{ProtocolVersion, ServiceFlags, UserAgent}; +use crate::codec::{codec_p2p, impl_p2p}; +use crate::prelude::*; +use crate::version::ProtocolVersion; use dash_num::Hash256; -use dash_primitives::ServiceV1; -use dash_types::TypeId; +use dash_primitives::{hash_impl, ServiceV1}; +use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; +use dash_types::{make_num, CompactSize, TypeId, Unencodable}; use core::fmt; +use core::ops::{BitAnd, BitOr, BitOrAssign}; + +/// Maximum user agent (subversion) length in bytes. +const MAX_USER_AGENT: usize = 256; + +make_num! { + /// Bitfield advertised in `version` messages describing node capabilities. + ServiceFlags, u64, 8 +} + +hash_impl!(ServiceFlags); + +impl ServiceFlags { + /// No services. + pub const NONE: Self = Self(0); + /// Full blockchain data. + pub const NODE_NETWORK: Self = Self(1 << 0); + /// BIP37 bloom filters. + pub const NODE_BLOOM: Self = Self(1 << 2); + /// BIP157 compact block filters. + pub const NODE_COMPACT_FILTERS: Self = Self(1 << 6); + /// Last 288 blocks only. + pub const NODE_NETWORK_LIMITED: Self = Self(1 << 10); + /// Dash compressed headers (headers2). + pub const NODE_HEADERS_COMPRESSED: Self = Self(1 << 11); + /// BIP324 v2 transport. + pub const NODE_P2P_V2: Self = Self(1 << 12); + + /// Returns `true` if all bits in `flag` are set. + pub const fn has(self, flag: Self) -> bool { + self.0 & flag.0 == flag.0 + } +} + +impl BitAnd for ServiceFlags { + type Output = Self; + fn bitand(self, rhs: Self) -> Self { + Self(self.0 & rhs.0) + } +} + +impl BitOr for ServiceFlags { + type Output = Self; + fn bitor(self, rhs: Self) -> Self { + Self(self.0 | rhs.0) + } +} + +impl BitOrAssign for ServiceFlags { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +/// The user agent exceeds the 256-byte limit. +#[derive(Debug, Clone, PartialEq, Eq, Unencodable)] +pub struct UserAgentTooLong { + /// Actual length in bytes. + pub len: usize, +} + +impl fmt::Display for UserAgentTooLong { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "user agent too long: {} bytes, max {MAX_USER_AGENT}", self.len) + } +} + +/// CompactSize-prefixed user agent bytestring. +#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct UserAgent(#[cfg_attr(feature = "serde", serde(with = "dash_types::serialize::utf8_lossy"))] Vec); + +impl_p2p!(UserAgent); + +impl BaseCodec for UserAgent { + fn decode(data: &mut &[u8]) -> Result { + let len = CompactSize::decode(data)?.into_len(MAX_USER_AGENT)?; + let raw = codec::read_bytes(data, len)?; + Ok(Self(raw.to_vec())) + } + + fn encode(&self, buf: &mut impl EncodeBuf) { + self.0.encode(buf); + } +} + +hash_impl!(UserAgent); + +impl UserAgent { + /// Creates a new user agent from raw bytes. + /// + /// # Errors + /// + /// Returns `UserAgentTooLong` if `bytes` exceeds 256 bytes. + pub fn new(bytes: Vec) -> Result { + if bytes.len() > MAX_USER_AGENT { + return Err(UserAgentTooLong { len: bytes.len() }); + } + Ok(Self(bytes)) + } + + /// Returns the user agent bytes as a str, if valid UTF-8. + pub fn as_str(&self) -> Option<&str> { + core::str::from_utf8(&self.0).ok() + } + + /// Returns the raw bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Returns the length in bytes. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the user agent is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl fmt::Display for UserAgent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_str() { + Some(s) => f.write_str(s), + None => write!(f, "<{} bytes>", self.0.len()), + } + } +} + +impl TryFrom> for UserAgent { + type Error = UserAgentTooLong; + + fn try_from(bytes: Vec) -> Result { + Self::new(bytes) + } +} /// Network address with service flags (used inside the version message). /// @@ -85,9 +225,18 @@ codec_p2p!(Version { mod tests { use super::*; - use dash_dev::{assert_serde_rt, check_wire, Corpus}; + use dash_dev::{assert_json_rt, assert_serde_rt, check_wire, Corpus}; use rstest::rstest; + #[rstest] + #[case::utf8(alloc::vec![b'/', b'>', b'.', b'.', b'<', b'/'])] + #[case::non_utf8(alloc::vec![0xff, 0xfe, 0x80])] + #[case::empty(Vec::new())] + #[case::max(alloc::vec![0x80; MAX_USER_AGENT])] + fn user_agent_json_round_trips(#[case] bytes: Vec) { + assert_json_rt(&UserAgent(bytes)); + } + #[rstest] fn corpus_version() { let corpus = Corpus::open(env!("CARGO_MANIFEST_DIR"), "version"); diff --git a/pkgs/p2p_core/src/prelude.rs b/pkgs/p2p_core/src/prelude.rs index b0509665..95453022 100644 --- a/pkgs/p2p_core/src/prelude.rs +++ b/pkgs/p2p_core/src/prelude.rs @@ -6,6 +6,7 @@ //! Re-exports for no_std compatibility. +pub(crate) use alloc::borrow::Cow; pub(crate) use alloc::format; pub(crate) use alloc::string::String; pub(crate) use alloc::vec; diff --git a/pkgs/p2p_core/src/primitives/command.rs b/pkgs/p2p_core/src/primitives/command.rs deleted file mode 100644 index f5b2194a..00000000 --- a/pkgs/p2p_core/src/primitives/command.rs +++ /dev/null @@ -1,222 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Twelve-byte null-padded command string for P2P message dispatch. - -use dash_primitives::hash_impl; -use dash_types::{impl_bytes, TypeId}; - -use core::fmt; - -/// A 12-byte, null-padded ASCII command identifying a P2P message type. -#[derive(Clone, Copy, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CommandString([u8; 12]); - -impl_bytes!(CommandString, 12); - -hash_impl!(CommandString); - -impl CommandString { - /// Builds a command string from a static `&str` at compile time. - /// - /// # Panics - /// - /// Compile-time panic if `s` is longer than 12 bytes. - pub const fn from_static(s: &str) -> Self { - let b = s.as_bytes(); - let len = b.len(); - assert!(len <= 12, "command string exceeds 12 bytes"); - let mut buf = [0u8; 12]; - let mut i = 0; - while i < len { - buf[i] = b[i]; - i += 1; - } - Self(buf) - } - - /// Wraps raw bytes into a command string. - pub const fn from_bytes(bytes: [u8; 12]) -> Self { - Self(bytes) - } - - /// Returns the raw 12-byte command buffer. - pub const fn as_bytes(&self) -> &[u8; 12] { - &self.0 - } - - /// Returns the command as a `&str` (trimmed of null padding). - pub fn as_str(&self) -> &str { - let end = self.0.iter().position(|&b| b == 0).unwrap_or(12); - // The bytes are always valid ASCII written by from_static or - // validated on decode, so this conversion is sound. - core::str::from_utf8(&self.0[..end]).unwrap_or("") - } - - /// Peer address list (v1). - pub const ADDR: Self = Self::from_static("addr"); - /// Peer address list (v2, BIP155). - pub const ADDRV2: Self = Self::from_static("addrv2"); - /// Request peer addresses. - pub const GETADDR: Self = Self::from_static("getaddr"); - /// Signal addrv2 support. - pub const SENDADDRV2: Self = Self::from_static("sendaddrv2"); - /// Inventory announcement. - pub const INV: Self = Self::from_static("inv"); - /// Request specific inventory. - pub const GETDATA: Self = Self::from_static("getdata"); - /// Requested inventory not found. - pub const NOTFOUND: Self = Self::from_static("notfound"); - /// Block headers request. - pub const GETHEADERS: Self = Self::from_static("getheaders"); - /// Block headers. - pub const HEADERS: Self = Self::from_static("headers"); - /// Keepalive request. - pub const PING: Self = Self::from_static("ping"); - /// Keepalive response. - pub const PONG: Self = Self::from_static("pong"); - /// BIP157: request compact filters. - pub const GETCFILTERS: Self = Self::from_static("getcfilters"); - /// BIP157: compact filter. - pub const CFILTER: Self = Self::from_static("cfilter"); - /// BIP157: request compact filter headers. - pub const GETCFHEADERS: Self = Self::from_static("getcfheaders"); - /// BIP157: compact filter headers. - pub const CFHEADERS: Self = Self::from_static("cfheaders"); - /// BIP157: request compact filter checkpoints. - pub const GETCFCHECKPT: Self = Self::from_static("getcfcheckpt"); - /// BIP157: compact filter checkpoints. - pub const CFCHECKPT: Self = Self::from_static("cfcheckpt"); - /// Block data. - pub const BLOCK: Self = Self::from_static("block"); - /// BIP152: compact block transactions. - pub const BLOCKTXN: Self = Self::from_static("blocktxn"); - /// BIP152: compact block. - pub const CMPCTBLOCK: Self = Self::from_static("cmpctblock"); - /// BIP37: add data to bloom filter. - pub const FILTERADD: Self = Self::from_static("filteradd"); - /// BIP37: clear bloom filter. - pub const FILTERCLEAR: Self = Self::from_static("filterclear"); - /// BIP37: load bloom filter. - pub const FILTERLOAD: Self = Self::from_static("filterload"); - /// Request block hashes. - pub const GETBLOCKS: Self = Self::from_static("getblocks"); - /// BIP152: request compact block transactions. - pub const GETBLOCKTXN: Self = Self::from_static("getblocktxn"); - /// Request mempool contents. - pub const MEMPOOL: Self = Self::from_static("mempool"); - /// BIP37: filtered block. - pub const MERKLEBLOCK: Self = Self::from_static("merkleblock"); - /// BIP152: signal compact block support. - pub const SENDCMPCT: Self = Self::from_static("sendcmpct"); - /// Transaction. - pub const TX: Self = Self::from_static("tx"); - - /// Protocol handshake. - pub const VERSION: Self = Self::from_static("version"); - /// Handshake acknowledgement. - pub const VERACK: Self = Self::from_static("verack"); - /// Prefer unsolicited headers announcements. - pub const SENDHEADERS: Self = Self::from_static("sendheaders"); - /// BIP330: transaction reconciliation. - pub const SENDTXRCNCL: Self = Self::from_static("sendtxrcncl"); - - /// Spork broadcast/request. - pub const SPORK: Self = Self::from_static("spork"); - /// Request active sporks. - pub const GETSPORKS: Self = Self::from_static("getsporks"); - /// Signal CoinJoin queue relay. - pub const SENDDSQ: Self = Self::from_static("senddsq"); - /// CoinJoin: accept denomination. - pub const DSA: Self = Self::from_static("dsa"); - /// CoinJoin: submit inputs. - pub const DSI: Self = Self::from_static("dsi"); - /// CoinJoin: final transaction. - pub const DSF: Self = Self::from_static("dsf"); - /// CoinJoin: sign final transaction. - pub const DSS: Self = Self::from_static("dss"); - /// CoinJoin: complete. - pub const DSC: Self = Self::from_static("dsc"); - /// CoinJoin: status update. - pub const DSSU: Self = Self::from_static("dssu"); - /// CoinJoin: broadcast transaction. - pub const DSTX: Self = Self::from_static("dstx"); - /// CoinJoin: queue entry. - pub const DSQ: Self = Self::from_static("dsq"); - /// Sync status count. - pub const SSC: Self = Self::from_static("ssc"); - /// Governance sync request. - pub const GOVSYNC: Self = Self::from_static("govsync"); - /// Governance object. - pub const GOVOBJ: Self = Self::from_static("govobj"); - /// Governance object vote. - pub const GOVOBJVOTE: Self = Self::from_static("govobjvote"); - /// Request masternode list diff. - pub const GETMNLISTD: Self = Self::from_static("getmnlistd"); - /// Masternode list diff. - pub const MNLISTDIFF: Self = Self::from_static("mnlistdiff"); - /// LLMQ: send recovered signatures. - pub const QSENDRECSIGS: Self = Self::from_static("qsendrecsigs"); - /// LLMQ: final commitment. - pub const QFCOMMIT: Self = Self::from_static("qfcommit"); - /// LLMQ: contribution. - pub const QCONTRIB: Self = Self::from_static("qcontrib"); - /// LLMQ: complaint. - pub const QCOMPLAINT: Self = Self::from_static("qcomplaint"); - /// LLMQ: justification. - pub const QJUSTIFY: Self = Self::from_static("qjustify"); - /// LLMQ: premature commitment. - pub const QPCOMMIT: Self = Self::from_static("qpcommit"); - /// LLMQ: watch quorums. - pub const QWATCH: Self = Self::from_static("qwatch"); - /// LLMQ: signing session announcement. - pub const QSIGSESANN: Self = Self::from_static("qsigsesann"); - /// LLMQ: signature shares inventory. - pub const QSIGSINV: Self = Self::from_static("qsigsinv"); - /// LLMQ: request signature shares. - pub const QGETSIGS: Self = Self::from_static("qgetsigs"); - /// LLMQ: batched signature shares. - pub const QBSIGS: Self = Self::from_static("qbsigs"); - /// LLMQ: recovered signature. - pub const QSIGREC: Self = Self::from_static("qsigrec"); - /// LLMQ: single signature share. - pub const QSIGSHARE: Self = Self::from_static("qsigshare"); - /// LLMQ: request quorum data. - pub const QGETDATA: Self = Self::from_static("qgetdata"); - /// LLMQ: quorum data. - pub const QDATA: Self = Self::from_static("qdata"); - /// ChainLock signature. - pub const CLSIG: Self = Self::from_static("clsig"); - /// InstantSend deterministic lock. - pub const ISDLOCK: Self = Self::from_static("isdlock"); - /// Masternode authentication. - pub const MNAUTH: Self = Self::from_static("mnauth"); - /// Compressed headers request. - pub const GETHEADERS2: Self = Self::from_static("getheaders2"); - /// Prefer compressed header announcements. - pub const SENDHEADERS2: Self = Self::from_static("sendheaders2"); - /// Compressed headers. - pub const HEADERS2: Self = Self::from_static("headers2"); - /// Request quorum rotation info. - pub const GETQRINFO: Self = Self::from_static("getqrinfo"); - /// Quorum rotation info. - pub const QRINFO: Self = Self::from_static("qrinfo"); - /// DIP-0031: platform ban. - pub const PLATFORMBAN: Self = Self::from_static("platformban"); -} - -impl fmt::Debug for CommandString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "CommandString(\"{}\")", self.as_str()) - } -} - -impl fmt::Display for CommandString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} diff --git a/pkgs/p2p_core/src/primitives/compressed_header.rs b/pkgs/p2p_core/src/primitives/compressed_header.rs deleted file mode 100644 index d5842e6a..00000000 --- a/pkgs/p2p_core/src/primitives/compressed_header.rs +++ /dev/null @@ -1,223 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! DIP-0025 compressed block header (headers2 format). - -use crate::prelude::*; - -use dash_primitives::{BlockHash, BlockHeader, MerkleRoot}; -use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf, Hashable}; -use dash_types::Unencodable; - -// Bitfield layout (1 byte): -// bits 0-2: version offset (0 = full version present, 1-7 = MRU cache index) -// bit 3: prev_blockhash present -// bit 4: timestamp mode (0 = i16 delta, 1 = full u32) -// bit 5: nbits present -const VERSION_OFFSET_MASK: u8 = 0b0000_0111; -const FLAG_PREV_HASH: u8 = 0b0000_1000; -const FLAG_TIMESTAMP_FULL: u8 = 0b0001_0000; -const FLAG_NBITS: u8 = 0b0010_0000; - -/// Maximum entries in the MRU version cache. -const MAX_VERSION_CACHE: usize = 7; - -/// Stateful compressor/decompressor for a stream of block headers. -/// -/// The DIP-0025 encoding is inherently sequential: each header is -/// delta-encoded against its predecessor and a shared MRU version -/// cache. Create one `CompressionState` per `headers2` message and -/// feed headers through it in order. -#[derive(Clone, Debug, Eq, Hash, PartialEq, Unencodable)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CompressionState { - /// MRU version cache (front = most recently used). - pub version_cache: Vec, - /// Previous fully-resolved header. - pub prev_header: Option, - /// Cached block hash of `prev_header`. - #[cfg_attr(feature = "serde", serde(skip))] - prev_block_hash: Option, -} - -impl CompressionState { - /// Creates fresh state with an empty cache and no previous header. - pub fn new() -> Self { - Self { - version_cache: Vec::with_capacity(MAX_VERSION_CACHE), - prev_header: None, - prev_block_hash: None, - } - } - - /// Moves the version at `position` to the front of the cache. - fn mark_version_mru(&mut self, position: usize) { - let v = self.version_cache.remove(position); - self.version_cache.insert(0, v); - } - - /// Inserts `version` at the front, evicting the oldest if full. - fn save_version_mru(&mut self, version: i32) { - if self.version_cache.len() >= MAX_VERSION_CACHE { - self.version_cache.pop(); - } - self.version_cache.insert(0, version); - } - - /// Finds the cache position (0-based) for a version, if cached. - fn find_version(&self, version: i32) -> Option { - self.version_cache.iter().position(|&v| v == version) - } - - /// Returns cached hash, recomputing from `prev_header` if the cache is cold. - fn prev_hash(&mut self) -> Option { - if self.prev_block_hash.is_none() { - self.prev_block_hash = self.prev_header.as_ref().map(|h| h.hash()); - } - self.prev_block_hash - } - - /// Decodes one compressed header, advancing the slice and state. - pub(crate) fn decode_header(&mut self, sl: &mut &[u8]) -> Result { - let flags = u8::decode(sl)?; - let version_offset = flags & VERSION_OFFSET_MASK; - - let version = if version_offset == 0 { - let v = i32::decode(sl)?; - self.save_version_mru(v); - v - } else { - let pos = (version_offset - 1) as usize; - if pos >= self.version_cache.len() { - return Err(DecodeError::InvalidValue { - expected: (0..self.version_cache.len() as u64).collect(), - actual: pos as u64, - }); - } - let v = self.version_cache[pos]; - self.mark_version_mru(pos); - v - }; - - let prev_hash = if flags & FLAG_PREV_HASH != 0 { - BlockHash::decode(sl)? - } else { - self.prev_hash().unwrap_or_default() - }; - - let merkle_root = MerkleRoot::decode(sl)?; - - let time = if flags & FLAG_TIMESTAMP_FULL != 0 { - u32::decode(sl)? - } else { - let delta = i16::decode(sl)?; - match &self.prev_header { - Some(prev) => (prev.time as i64 + delta as i64) as u32, - None => delta as u32, - } - }; - - let bits = if flags & FLAG_NBITS != 0 { - u32::decode(sl)? - } else { - match &self.prev_header { - Some(prev) => prev.bits, - None => 0, - } - }; - - let nonce = u32::decode(sl)?; - - let header = BlockHeader { - version, - prev_hash, - merkle_root, - time, - bits, - nonce, - }; - self.prev_block_hash = Some(header.hash()); - self.prev_header = Some(header); - Ok(header) - } - - /// Encodes one header in compressed form, advancing the state. - pub fn encode_header(&mut self, header: &BlockHeader, buf: &mut impl EncodeBuf) { - let mut flags: u8 = 0; - - let version_offset = match self.find_version(header.version) { - Some(pos) => (pos + 1) as u8, - None => 0, - }; - flags |= version_offset & VERSION_OFFSET_MASK; - - let need_prev_hash = match self.prev_hash() { - Some(hash) => header.prev_hash != hash, - None => true, - }; - if need_prev_hash { - flags |= FLAG_PREV_HASH; - } - - let time_delta = match &self.prev_header { - Some(prev) => { - let d = header.time as i64 - prev.time as i64; - if (i16::MIN as i64..=i16::MAX as i64).contains(&d) { - Some(d as i16) - } else { - None - } - } - None => None, - }; - if time_delta.is_none() { - flags |= FLAG_TIMESTAMP_FULL; - } - - let need_nbits = match &self.prev_header { - Some(prev) => header.bits != prev.bits, - None => true, - }; - if need_nbits { - flags |= FLAG_NBITS; - } - - buf.push(flags); - - if version_offset == 0 { - header.version.encode(buf); - self.save_version_mru(header.version); - } else { - let pos = (version_offset - 1) as usize; - self.mark_version_mru(pos); - } - - if need_prev_hash { - header.prev_hash.encode(buf); - } - - header.merkle_root.encode(buf); - - match time_delta { - Some(d) => d.encode(buf), - None => header.time.encode(buf), - } - - if need_nbits { - header.bits.encode(buf); - } - - header.nonce.encode(buf); - self.prev_block_hash = Some(header.hash()); - self.prev_header = Some(*header); - } -} - -impl Default for CompressionState { - fn default() -> Self { - Self::new() - } -} diff --git a/pkgs/p2p_core/src/primitives/inventory.rs b/pkgs/p2p_core/src/primitives/inventory.rs deleted file mode 100644 index 92300308..00000000 --- a/pkgs/p2p_core/src/primitives/inventory.rs +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Inventory vector used by inv, getdata, and notfound messages. - -use crate::codec::codec_p2p; - -use dash_num::Hash256; -use dash_primitives::hash_impl; -use dash_types::{enum_map, impl_num, TypeId}; - -use core::fmt; - -enum_map! { - /// Inventory object type. - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] - pub enum InvType, u32, Unknown { - /// Error / not used. - Error = 0 => "error", - /// Transaction. - Tx = 1 => "tx", - /// Block. - Block = 2 => "block", - /// Filtered block (BIP37). - FilteredBlock = 3 => "filtered_block", - /// Compact block (BIP152). - CompactBlock = 4 => "compact_block", - /// Governance object. - GovernanceObject = 17 => "governance_object", - /// Governance object vote. - GovernanceObjectVote = 18 => "governance_object_vote", - } -} - -impl_num!(InvType, u32); - -hash_impl!(InvType); - -/// An inventory vector: a typed 32-byte hash. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, TypeId)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct Inventory { - /// Object type. - #[cfg_attr(feature = "serde", serde(rename = "type"))] - pub inv_type: InvType, - /// Object hash. - pub hash: Hash256, -} - -codec_p2p!(Inventory { inv_type, hash }); - -impl fmt::Display for Inventory { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.inv_type, self.hash) - } -} diff --git a/pkgs/p2p_core/src/primitives/mod.rs b/pkgs/p2p_core/src/primitives/mod.rs deleted file mode 100644 index 332c3f70..00000000 --- a/pkgs/p2p_core/src/primitives/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! P2P-specific primitive types. - -mod command; -mod compressed_header; -mod inventory; -mod magic; -mod mn_list; -mod protocol_version; -mod service_flags; -mod short_id; -mod user_agent; - -pub use command::CommandString; -pub use compressed_header::CompressionState; -pub use inventory::{InvType, Inventory}; -pub use magic::Magic; -pub use mn_list::{DeletedQuorum, GetMnListDiff, MnListDiff, MnListDiffPayload, QuorumClSig, SimplifiedMnListEntry}; -pub use protocol_version::ProtocolVersion; -pub use service_flags::ServiceFlags; -pub use short_id::ShortId; -pub use user_agent::{UserAgent, UserAgentTooLong}; diff --git a/pkgs/p2p_core/src/primitives/service_flags.rs b/pkgs/p2p_core/src/primitives/service_flags.rs deleted file mode 100644 index 6635ae6e..00000000 --- a/pkgs/p2p_core/src/primitives/service_flags.rs +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Dash service flag bitfield. - -use dash_primitives::hash_impl; -use dash_types::make_num; - -use core::ops; - -make_num! { - /// Bitfield advertised in `version` messages describing node capabilities. - ServiceFlags, u64, 8 -} - -hash_impl!(ServiceFlags); - -impl ServiceFlags { - /// No services. - pub const NONE: Self = Self(0); - /// Full blockchain data. - pub const NODE_NETWORK: Self = Self(1 << 0); - /// BIP37 bloom filters. - pub const NODE_BLOOM: Self = Self(1 << 2); - /// BIP157 compact block filters. - pub const NODE_COMPACT_FILTERS: Self = Self(1 << 6); - /// Last 288 blocks only. - pub const NODE_NETWORK_LIMITED: Self = Self(1 << 10); - /// Dash compressed headers (headers2). - pub const NODE_HEADERS_COMPRESSED: Self = Self(1 << 11); - /// BIP324 v2 transport. - pub const NODE_P2P_V2: Self = Self(1 << 12); - - /// Returns `true` if all bits in `flag` are set. - pub const fn has(self, flag: Self) -> bool { - self.0 & flag.0 == flag.0 - } -} - -impl ops::BitOr for ServiceFlags { - type Output = Self; - fn bitor(self, rhs: Self) -> Self { - Self(self.0 | rhs.0) - } -} - -impl ops::BitAnd for ServiceFlags { - type Output = Self; - fn bitand(self, rhs: Self) -> Self { - Self(self.0 & rhs.0) - } -} - -impl ops::BitOrAssign for ServiceFlags { - fn bitor_assign(&mut self, rhs: Self) { - self.0 |= rhs.0; - } -} diff --git a/pkgs/p2p_core/src/primitives/short_id.rs b/pkgs/p2p_core/src/primitives/short_id.rs deleted file mode 100644 index 5afedabf..00000000 --- a/pkgs/p2p_core/src/primitives/short_id.rs +++ /dev/null @@ -1,183 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! V2 short ID mapping for BIP324 message framing. - -use crate::primitives::CommandString; - -use dash_types::Unencodable; - -// Maps between V2 1-byte short IDs and command strings. -// -// Bitcoin occupies IDs 0-32 (lower half), Dash occupies 128-168 -// (upper half). ID 0 means "long format" -- a 12-byte command -// string follows instead. - -/// Total number of Bitcoin short IDs (including slot 0 = long format). -const BITCOIN_COUNT: usize = 33; -/// Offset where Dash short IDs begin. -const DASH_OFFSET: u8 = 128; -/// Number of Dash short IDs. -const DASH_COUNT: usize = 41; - -/// Bitcoin short ID table (index = short ID byte). -/// -/// Slot 0 is the long-format sentinel (empty string). -/// Empty strings in other slots are intentionally unassigned. -static BITCOIN_IDS: [&str; BITCOIN_COUNT] = [ - "", // 0: long format - "addr", // 1 - "block", // 2 - "blocktxn", // 3 - "cmpctblock", // 4 - "", // 5: feefilter (unused in Dash) - "filteradd", // 6 - "filterclear", // 7 - "filterload", // 8 - "getblocks", // 9 - "getblocktxn", // 10 - "getdata", // 11 - "getheaders", // 12 - "headers", // 13 - "inv", // 14 - "mempool", // 15 - "merkleblock", // 16 - "notfound", // 17 - "ping", // 18 - "pong", // 19 - "sendcmpct", // 20 - "tx", // 21 - "getcfilters", // 22 - "cfilter", // 23 - "getcfheaders", // 24 - "cfheaders", // 25 - "getcfcheckpt", // 26 - "cfcheckpt", // 27 - "addrv2", // 28 - "", // 29 - "", // 30 - "", // 31 - "", // 32 -]; - -/// Dash short ID table (index 0 = short ID 128). -static DASH_IDS: [&str; DASH_COUNT] = [ - "spork", // 128 - "getsporks", // 129 - "senddsq", // 130 - "dsa", // 131 - "dsi", // 132 - "dsf", // 133 - "dss", // 134 - "dsc", // 135 - "dssu", // 136 - "dstx", // 137 - "dsq", // 138 - "ssc", // 139 - "govsync", // 140 - "govobj", // 141 - "govobjvote", // 142 - "getmnlistd", // 143 - "mnlistdiff", // 144 - "qsendrecsigs", // 145 - "qfcommit", // 146 - "qcontrib", // 147 - "qcomplaint", // 148 - "qjustify", // 149 - "qpcommit", // 150 - "qwatch", // 151 - "qsigsesann", // 152 - "qsigsinv", // 153 - "qgetsigs", // 154 - "qbsigs", // 155 - "qsigrec", // 156 - "qsigshare", // 157 - "qgetdata", // 158 - "qdata", // 159 - "clsig", // 160 - "isdlock", // 161 - "mnauth", // 162 - "getheaders2", // 163 - "sendheaders2", // 164 - "headers2", // 165 - "getqrinfo", // 166 - "qrinfo", // 167 - "platformban", // 168 -]; - -/// A resolved V2 short ID. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Unencodable)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct ShortId(pub u8); - -impl ShortId { - /// Long-format sentinel (a 12-byte command follows). - pub const LONG_FORMAT: Self = Self(0); - - /// Returns `true` if this ID is valid (maps to a non-empty command). - pub fn is_valid(self) -> bool { - self.to_command_str().is_some() - } - - /// Resolves the short ID to its command name, if known. - pub fn to_command_str(self) -> Option<&'static str> { - let id = self.0; - if (id as usize) < BITCOIN_COUNT { - let s = BITCOIN_IDS[id as usize]; - if s.is_empty() { - None - } else { - Some(s) - } - } else if id >= DASH_OFFSET { - let idx = (id - DASH_OFFSET) as usize; - if idx < DASH_COUNT { - let s = DASH_IDS[idx]; - if s.is_empty() { - None - } else { - Some(s) - } - } else { - None - } - } else { - None - } - } - - /// Resolves the short ID to a `CommandString`, if known. - pub fn to_command(self) -> Option { - self.to_command_str().map(CommandString::from_static) - } - - /// Looks up the short ID for a given command string. - /// - /// Returns `None` if the command has no short ID (must use - /// long format with ID 0). - pub fn from_command(cmd: &CommandString) -> Option { - let s = cmd.as_str(); - // Search Bitcoin table (skip slot 0 which is the sentinel). - for (i, &entry) in BITCOIN_IDS.iter().enumerate().skip(1) { - if entry == s { - return Some(Self(i as u8)); - } - } - // Search Dash table. - for (i, &entry) in DASH_IDS.iter().enumerate() { - if entry == s { - return Some(Self(DASH_OFFSET + i as u8)); - } - } - None - } - - /// Returns `true` if the byte is in a range that could hold a - /// valid short ID (Bitcoin 1-32 or Dash 128-168). - pub fn is_valid_range(id: u8) -> bool { - (1..BITCOIN_COUNT as u8).contains(&id) || (id >= DASH_OFFSET && (id - DASH_OFFSET) < DASH_COUNT as u8) - } -} diff --git a/pkgs/p2p_core/src/primitives/user_agent.rs b/pkgs/p2p_core/src/primitives/user_agent.rs deleted file mode 100644 index dedc0cc3..00000000 --- a/pkgs/p2p_core/src/primitives/user_agent.rs +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! User agent string exchanged in version messages. - -use crate::codec::impl_p2p; -use crate::prelude::*; - -use dash_primitives::hash_impl; -use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; -use dash_types::{CompactSize, TypeId, Unencodable}; - -use core::fmt; - -/// Maximum user agent (subversion) length in bytes. -const MAX_USER_AGENT: usize = 256; - -/// CompactSize-prefixed user agent bytestring. -#[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] -pub struct UserAgent(Vec); - -impl_p2p!(UserAgent); - -/// The user agent exceeds the 256-byte limit. -#[derive(Debug, Clone, PartialEq, Eq, Unencodable)] -pub struct UserAgentTooLong { - /// Actual length in bytes. - pub len: usize, -} - -impl fmt::Display for UserAgentTooLong { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "user agent too long: {} bytes, max {MAX_USER_AGENT}", self.len) - } -} - -impl BaseCodec for UserAgent { - fn decode(data: &mut &[u8]) -> Result { - let len = CompactSize::decode(data)?.into_len(MAX_USER_AGENT)?; - let raw = codec::read_bytes(data, len)?; - Ok(Self(raw.to_vec())) - } - - fn encode(&self, buf: &mut impl EncodeBuf) { - self.0.encode(buf); - } -} - -hash_impl!(UserAgent); - -impl UserAgent { - /// Creates a new user agent from raw bytes. - /// - /// # Errors - /// - /// Returns `UserAgentTooLong` if `bytes` exceeds 256 bytes. - pub fn new(bytes: Vec) -> Result { - if bytes.len() > MAX_USER_AGENT { - return Err(UserAgentTooLong { len: bytes.len() }); - } - Ok(Self(bytes)) - } - - /// Returns the user agent bytes as a str, if valid UTF-8. - pub fn as_str(&self) -> Option<&str> { - core::str::from_utf8(&self.0).ok() - } - - /// Returns the raw bytes. - pub fn as_bytes(&self) -> &[u8] { - &self.0 - } - - /// Returns the length in bytes. - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns `true` if the user agent is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl fmt::Display for UserAgent { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.as_str() { - Some(s) => f.write_str(s), - None => write!(f, "<{} bytes>", self.0.len()), - } - } -} - -#[cfg(feature = "serde")] -impl ::serde::Serialize for UserAgent { - fn serialize(&self, s: S) -> Result { - match self.as_str() { - Some(text) => s.serialize_str(text), - None => Err(::serde::ser::Error::custom("user agent contains non-utf8 data")), - } - } -} - -#[cfg(feature = "serde")] -impl<'de> ::serde::Deserialize<'de> for UserAgent { - fn deserialize>(d: D) -> Result { - let text = ::deserialize(d)?; - Self::new(text.into_bytes()).map_err(::serde::de::Error::custom) - } -} diff --git a/pkgs/p2p_core/src/serialize.rs b/pkgs/p2p_core/src/serialize.rs new file mode 100644 index 00000000..b0cdb5b5 --- /dev/null +++ b/pkgs/p2p_core/src/serialize.rs @@ -0,0 +1,404 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Reusable serde helpers for `#[serde(with = "...")]`. + +use crate::prelude::*; + +use bitcoin_primitives::BlockHash; +use bitcoin_units::BlockHeight; +use dash_types::Unencodable; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// For [`BlockHash`](bitcoin_primitives::BlockHash) as a hex string. +mod block_hash { + use super::*; + + pub fn serialize(val: &BlockHash, s: S) -> Result { + s.serialize_str(&alloc::format!("{val}")) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + let s = >::deserialize(d)?; + s.parse().map_err(::serde::de::Error::custom) + } +} + +/// For [`BlockHeight`](bitcoin_units::BlockHeight) as `u32`. +mod block_height { + use super::*; + + pub fn serialize(val: &BlockHeight, s: S) -> Result { + s.serialize_u32(val.to_u32()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + Ok(BlockHeight::from_u32(u32::deserialize(d)?)) + } +} + +/// For [`GetCFilters`](bitcoin_p2p_messages::message_filter::GetCFilters). +pub mod get_cfilters { + use super::*; + + use bitcoin_p2p_messages::message_filter::GetCFilters; + + pub fn serialize(val: &GetCFilters, s: S) -> Result { + #[derive(Serialize)] + struct Ser { + filter_type: u8, + #[serde(with = "super::block_height")] + start_height: BlockHeight, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + } + Ser { + filter_type: val.filter_type, + start_height: val.start_height, + stop_hash: val.stop_hash, + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + filter_type: u8, + #[serde(with = "super::block_height")] + start_height: BlockHeight, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + } + let r = De::deserialize(d)?; + Ok(GetCFilters { + filter_type: r.filter_type, + start_height: r.start_height, + stop_hash: r.stop_hash, + }) + } +} + +/// For [`CFilter`](bitcoin_p2p_messages::message_filter::CFilter). +pub mod cfilter { + use super::*; + + use bitcoin_p2p_messages::message_filter::CFilter; + + pub fn serialize(val: &CFilter, s: S) -> Result { + #[derive(Serialize)] + struct Ser<'a> { + filter_type: u8, + #[serde(with = "super::block_hash")] + block_hash: BlockHash, + filter: &'a [u8], + } + Ser { + filter_type: val.filter_type, + block_hash: val.block_hash, + filter: &val.filter, + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + filter_type: u8, + #[serde(with = "super::block_hash")] + block_hash: BlockHash, + filter: Vec, + } + let r = De::deserialize(d)?; + Ok(CFilter { + filter_type: r.filter_type, + block_hash: r.block_hash, + filter: r.filter, + }) + } +} + +/// For [`GetCFHeaders`](bitcoin_p2p_messages::message_filter::GetCFHeaders). +pub mod get_cfheaders { + use super::*; + + use bitcoin_p2p_messages::message_filter::GetCFHeaders; + + pub fn serialize(val: &GetCFHeaders, s: S) -> Result { + #[derive(Serialize)] + struct Ser { + filter_type: u8, + #[serde(with = "super::block_height")] + start_height: BlockHeight, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + } + Ser { + filter_type: val.filter_type, + start_height: val.start_height, + stop_hash: val.stop_hash, + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + filter_type: u8, + #[serde(with = "super::block_height")] + start_height: BlockHeight, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + } + let r = De::deserialize(d)?; + Ok(GetCFHeaders { + filter_type: r.filter_type, + start_height: r.start_height, + stop_hash: r.stop_hash, + }) + } +} + +/// For [`CFHeaders`](bitcoin_p2p_messages::message_filter::CFHeaders). +pub mod cfheaders { + use super::*; + + use bitcoin_p2p_messages::message_filter::{CFHeaders, FilterHash, FilterHeader}; + + pub fn serialize(val: &CFHeaders, s: S) -> Result { + #[derive(Serialize)] + struct Ser<'a> { + filter_type: u8, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + previous_filter_header: FilterHeader, + filter_hashes: &'a [FilterHash], + } + Ser { + filter_type: val.filter_type, + stop_hash: val.stop_hash, + previous_filter_header: val.previous_filter_header, + filter_hashes: &val.filter_hashes, + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + filter_type: u8, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + previous_filter_header: FilterHeader, + filter_hashes: Vec, + } + let r = De::deserialize(d)?; + Ok(CFHeaders { + filter_type: r.filter_type, + stop_hash: r.stop_hash, + previous_filter_header: r.previous_filter_header, + filter_hashes: r.filter_hashes, + }) + } +} + +/// For [`GetCFCheckpt`](bitcoin_p2p_messages::message_filter::GetCFCheckpt). +pub mod get_cfcheckpt { + use super::*; + + use bitcoin_p2p_messages::message_filter::GetCFCheckpt; + + pub fn serialize(val: &GetCFCheckpt, s: S) -> Result { + #[derive(Serialize)] + struct Ser { + filter_type: u8, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + } + Ser { + filter_type: val.filter_type, + stop_hash: val.stop_hash, + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + filter_type: u8, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + } + let r = De::deserialize(d)?; + Ok(GetCFCheckpt { + filter_type: r.filter_type, + stop_hash: r.stop_hash, + }) + } +} + +/// For [`FilterLoad`](bitcoin_p2p_messages::message_bloom::FilterLoad). +pub mod filter_load { + use super::*; + + use bitcoin_p2p_messages::message_bloom::{BloomFlags, FilterLoad}; + + #[derive(Clone, Debug, Eq, Hash, PartialEq, Unencodable, Serialize, Deserialize)] + enum FlagsRepr { + None, + All, + PubkeyOnly, + } + + impl From<&BloomFlags> for FlagsRepr { + fn from(f: &BloomFlags) -> Self { + match f { + BloomFlags::None => Self::None, + BloomFlags::All => Self::All, + BloomFlags::PubkeyOnly => Self::PubkeyOnly, + } + } + } + + impl From for BloomFlags { + fn from(f: FlagsRepr) -> Self { + match f { + FlagsRepr::None => Self::None, + FlagsRepr::All => Self::All, + FlagsRepr::PubkeyOnly => Self::PubkeyOnly, + } + } + } + + pub fn serialize(val: &FilterLoad, s: S) -> Result { + #[derive(Serialize)] + struct Ser<'a> { + filter: &'a [u8], + hash_funcs: u32, + tweak: u32, + flags: FlagsRepr, + } + Ser { + filter: &val.filter, + hash_funcs: val.hash_funcs, + tweak: val.tweak, + flags: FlagsRepr::from(&val.flags), + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + filter: Vec, + hash_funcs: u32, + tweak: u32, + flags: FlagsRepr, + } + let r = De::deserialize(d)?; + Ok(FilterLoad { + filter: r.filter, + hash_funcs: r.hash_funcs, + tweak: r.tweak, + flags: r.flags.into(), + }) + } +} + +/// For [`FilterAdd`](bitcoin_p2p_messages::message_bloom::FilterAdd). +pub mod filter_add { + use super::*; + + use bitcoin_p2p_messages::message_bloom::FilterAdd; + + pub fn serialize(val: &FilterAdd, s: S) -> Result { + #[derive(Serialize)] + struct Ser<'a> { + data: &'a [u8], + } + Ser { data: &val.data }.serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + data: Vec, + } + let r = De::deserialize(d)?; + Ok(FilterAdd { data: r.data }) + } +} + +/// For [`SendCmpct`](bitcoin_p2p_messages::message_compact_blocks::SendCmpct). +pub mod send_cmpct { + use super::*; + + use bitcoin_p2p_messages::message_compact_blocks::SendCmpct; + + pub fn serialize(val: &SendCmpct, s: S) -> Result { + #[derive(Serialize)] + struct Ser { + send_compact: bool, + version: u64, + } + Ser { + send_compact: val.send_compact, + version: val.version, + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + send_compact: bool, + version: u64, + } + let r = De::deserialize(d)?; + Ok(SendCmpct { + send_compact: r.send_compact, + version: r.version, + }) + } +} + +/// For [`CFCheckpt`](bitcoin_p2p_messages::message_filter::CFCheckpt). +pub mod cfcheckpt { + use super::*; + + use bitcoin_p2p_messages::message_filter::{CFCheckpt, FilterHeader}; + + pub fn serialize(val: &CFCheckpt, s: S) -> Result { + #[derive(Serialize)] + struct Ser<'a> { + filter_type: u8, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + filter_headers: &'a [FilterHeader], + } + Ser { + filter_type: val.filter_type, + stop_hash: val.stop_hash, + filter_headers: &val.filter_headers, + } + .serialize(s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + #[derive(Deserialize)] + struct De { + filter_type: u8, + #[serde(with = "super::block_hash")] + stop_hash: BlockHash, + filter_headers: Vec, + } + let r = De::deserialize(d)?; + Ok(CFCheckpt { + filter_type: r.filter_type, + stop_hash: r.stop_hash, + filter_headers: r.filter_headers, + }) + } +} diff --git a/pkgs/p2p_core/src/short_id.rs b/pkgs/p2p_core/src/short_id.rs new file mode 100644 index 00000000..20a4fc78 --- /dev/null +++ b/pkgs/p2p_core/src/short_id.rs @@ -0,0 +1,31 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! V2 short ID mapping for BIP324 message framing. + +use crate::command::CommandString; + +use dash_types::Unencodable; + +/// A resolved V2 short ID. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Unencodable)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct ShortId(pub u8); + +impl ShortId { + /// Long-format sentinel (a 12-byte command follows). + pub const LONG_FORMAT: Self = Self(0); + + /// Returns `true` if this ID maps to a known command. + pub const fn is_valid(self) -> bool { + self.to_command_str().is_some() + } + + /// Resolves the short ID to a `CommandString`, if known. + pub fn to_command(self) -> Option { + self.to_command_str().map(CommandString::from_static) + } +} diff --git a/pkgs/p2p_core/src/primitives/protocol_version.rs b/pkgs/p2p_core/src/version.rs similarity index 96% rename from pkgs/p2p_core/src/primitives/protocol_version.rs rename to pkgs/p2p_core/src/version.rs index 54f82d4e..3d07d65e 100644 --- a/pkgs/p2p_core/src/primitives/protocol_version.rs +++ b/pkgs/p2p_core/src/version.rs @@ -4,7 +4,7 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Dash protocol version constants. +//! Protocol version. use dash_primitives::hash_impl; use dash_types::make_num; diff --git a/pkgs/params/Cargo.toml b/pkgs/params/Cargo.toml index 664ba4db..45828fb2 100644 --- a/pkgs/params/Cargo.toml +++ b/pkgs/params/Cargo.toml @@ -10,17 +10,15 @@ std = ["bitcoin-primitives/std", "dash-primitives/std", "dash-script/std"] full = ["std"] [dependencies] -bitcoin-primitives = { version = "0.102", default-features = false, features = ["alloc"] } -bitcoin-units = { version = "0.3", default-features = false, features = ["alloc"] } +bitcoin-primitives = { workspace = true, features = ["alloc"] } +bitcoin-units = { workspace = true, features = ["alloc"] } dash-num = { version = "0.0.0", path = "../num" } dash-primitives = { version = "0.0.0", path = "../primitives" } dash-script = { version = "0.0.0", path = "../script" } hex-literal = "0.4" [dev-dependencies] -bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ - "alloc", -] } +bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } dash-pow = { version = "0.0.0", path = "../pow" } dash-types = { version = "0.0.0", path = "../types", default-features = false } hex-literal = "0.4" diff --git a/pkgs/pkc/Cargo.toml b/pkgs/pkc/Cargo.toml index e1b51cb3..18c4068b 100644 --- a/pkgs/pkc/Cargo.toml +++ b/pkgs/pkc/Cargo.toml @@ -5,13 +5,9 @@ edition = "2021" license = "MIT" [dependencies] -base58ck = { version = "0.4", default-features = false, features = ["alloc"] } -bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ - "alloc", -] } -bitcoin_hashes = { version = "0.20", default-features = false, features = [ - "alloc", -] } +base58ck = { workspace = true, features = ["alloc"] } +bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } +bitcoin_hashes = { workspace = true, features = ["alloc"] } blst = { version = "0.3", default-features = false, optional = true } cfg-if = "1" dash-num = { version = "0.0.0", path = "../num" } diff --git a/pkgs/pkc/src/ecdsa/secret_ops.rs b/pkgs/pkc/src/ecdsa/secret_ops.rs index 6309d289..99f778fd 100644 --- a/pkgs/pkc/src/ecdsa/secret_ops.rs +++ b/pkgs/pkc/src/ecdsa/secret_ops.rs @@ -327,7 +327,7 @@ mod tests { #[case::compressed(Compression::Compressed)] #[case::uncompressed(Compression::Uncompressed)] fn consensus_bridge_roundtrip(#[case] compressed: Compression) { - use bitcoin_consensus_encoding::{encode_to_vec, Decodable, Decoder}; + use bitcoin_consensus_encoding::{encode_to_vec, Decode, Decoder}; let sk = EcdsaSecretKey::from_bytes(&ALICE_SK, compressed).unwrap(); let wire = encode_to_vec(&sk); @@ -337,7 +337,7 @@ mod tests { assert_eq!(wire, direct, "bridge must match the BaseCodec image"); assert_eq!(wire.len(), if compressed.is_compressed() { 214 } else { 279 }); - let mut dec = ::decoder(); + let mut dec = ::decoder(); let mut cursor = wire.as_slice(); while dec.push_bytes(&mut cursor).unwrap() && !cursor.is_empty() {} let back = dec.end().unwrap(); diff --git a/pkgs/primitives/Cargo.toml b/pkgs/primitives/Cargo.toml index bf3ff333..47d377ee 100644 --- a/pkgs/primitives/Cargo.toml +++ b/pkgs/primitives/Cargo.toml @@ -31,19 +31,11 @@ serde = [ full = ["std", "serde"] [dependencies] -bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ - "alloc", -] } -bitcoin-internals = { version = "0.5", default-features = false } -bitcoin-primitives = { version = "0.102", default-features = false, features = [ - "alloc", -] } -bitcoin_hashes = { version = "0.20", default-features = false, features = [ - "alloc", -] } -bitcoin-units = { version = "0.3", default-features = false, features = [ - "alloc", -] } +bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } +bitcoin-internals = { workspace = true } +bitcoin-primitives = { workspace = true, features = ["alloc"] } +bitcoin_hashes = { workspace = true, features = ["alloc"] } +bitcoin-units = { workspace = true, features = ["alloc"] } dash-num = { version = "0.0.0", path = "../num" } dash-pkc = { version = "0.0.0", path = "../pkc", default-features = false } dash-pow = { version = "0.0.0", path = "../pow" } diff --git a/pkgs/primitives/src/codec.rs b/pkgs/primitives/src/codec.rs index 0adc4449..c4b1ee41 100644 --- a/pkgs/primitives/src/codec.rs +++ b/pkgs/primitives/src/codec.rs @@ -36,7 +36,7 @@ macro_rules! hash_impl { )* }; } -/// Generates `Encodable` + `Decodable` with payload size limit. +/// Generates `Encode` + `Decode` with payload size limit. macro_rules! impl_payload { ($ty:ty) => { $crate::__private::dash_types::impl_type!($ty, crate::codec::MAX_SPTX_PAYLOAD_SIZE); @@ -44,7 +44,7 @@ macro_rules! impl_payload { } pub(crate) use impl_payload; -/// Generates `BaseCodec` + `Encodable` + `Decodable` for flat structs +/// Generates `BaseCodec` + `Encode` + `Decode` for flat structs /// without `Hashable`. #[macro_export] macro_rules! codec_base { @@ -68,7 +68,7 @@ macro_rules! codec_base { }; } -/// Generates `BaseCodec` + `Encodable` + `Decodable` + `Hashable` for +/// Generates `BaseCodec` + `Encode` + `Decode` + `Hashable` for /// flat structs. #[macro_export] macro_rules! codec_type { @@ -82,7 +82,7 @@ macro_rules! codec_type { }; } -/// Generates `BaseCodec` + `Encodable` + `Decodable` + `Hashable` for +/// Generates `BaseCodec` + `Encode` + `Decode` + `Hashable` for /// flat structs with payload size limit. macro_rules! codec_payload { ($ty:ty { $($field:ident),+ $(,)? }) => { diff --git a/pkgs/primitives/src/payload/assetlock.rs b/pkgs/primitives/src/payload/assetlock.rs index f928ed0c..9f5c9312 100644 --- a/pkgs/primitives/src/payload/assetlock.rs +++ b/pkgs/primitives/src/payload/assetlock.rs @@ -10,6 +10,7 @@ use crate::codec::codec_payload; use crate::prelude::*; use crate::transaction::TxOut; +use dash_script::Recipient; use dash_types::codec::Checkable; use dash_types::{TypeId, Unencodable}; @@ -78,7 +79,10 @@ impl Checkable for AssetLock { if total > max_money { return Some(AssetLockInvalid::CreditOutOfRange { index: i }); } - if !dash_script::is_p2pkh(out.script_pubkey.as_bytes()) { + if !matches!( + Recipient::from_script(out.script_pubkey.as_bytes()), + Some(Recipient::PubKeyHash(_)) + ) { return Some(AssetLockInvalid::CreditNotP2pkh { index: i }); } } diff --git a/pkgs/primitives/src/payload/proregtx.rs b/pkgs/primitives/src/payload/proregtx.rs index dba4bc59..2194e7c3 100644 --- a/pkgs/primitives/src/payload/proregtx.rs +++ b/pkgs/primitives/src/payload/proregtx.rs @@ -17,7 +17,7 @@ use crate::{hash_impl, TxHash}; use bitcoin_primitives::script::ScriptPubKeyBuf; use dash_pkc::bls::{BlsPkBytes, BlsScIetf}; -use dash_script::PubKeyHash; +use dash_script::{PubKeyHash, Recipient}; use dash_types::codec::{BaseCodec, Checkable, DecodeError, EncodeBuf, NumCodec}; use dash_types::{make_bytes, TypeId}; @@ -203,8 +203,10 @@ impl Checkable for ProRegTx { } let payout = self.script_payout.as_bytes(); - if !dash_script::is_p2pkh(payout) && !dash_script::is_p2sh(payout) { - return Some(ProTxInvalid::BadPayoutScript); + let recipient = Recipient::from_script(payout); + match &recipient { + Some(Recipient::PubKeyHash(_) | Recipient::ScriptHash(_)) => {} + _ => return Some(ProTxInvalid::BadPayoutScript), } let is_extended = matches!(self.net_info, NetInfo::Extended(_)); @@ -237,8 +239,8 @@ impl Checkable for ProRegTx { return Some(e); } - if let Some(hash) = dash_script::p2pkh_hash160(payout) { - if hash == self.key_id_owner.as_bytes() || hash == self.key_id_voting.as_bytes() { + if let Some(Recipient::PubKeyHash(hash)) = &recipient { + if *hash == self.key_id_owner || *hash == self.key_id_voting { return Some(ProTxInvalid::PayoutKeyReuse); } } diff --git a/pkgs/primitives/src/payload/proupregtx.rs b/pkgs/primitives/src/payload/proupregtx.rs index 50c41d24..6f3453ed 100644 --- a/pkgs/primitives/src/payload/proupregtx.rs +++ b/pkgs/primitives/src/payload/proupregtx.rs @@ -13,7 +13,7 @@ use crate::TxHash; use bitcoin_primitives::script::ScriptPubKeyBuf; use dash_pkc::bls::{BlsPkBytes, BlsScIetf}; -use dash_script::PubKeyHash; +use dash_script::{PubKeyHash, Recipient}; use dash_types::codec::Checkable; use dash_types::TypeId; @@ -77,7 +77,10 @@ impl Checkable for ProUpRegTx { } let payout = self.script_payout.as_bytes(); - if !dash_script::is_p2pkh(payout) && !dash_script::is_p2sh(payout) { + if !matches!( + Recipient::from_script(payout), + Some(Recipient::PubKeyHash(_) | Recipient::ScriptHash(_)) + ) { return Some(ProTxInvalid::BadPayoutScript); } diff --git a/pkgs/script/Cargo.toml b/pkgs/script/Cargo.toml index 1c54e60f..0615e0c0 100644 --- a/pkgs/script/Cargo.toml +++ b/pkgs/script/Cargo.toml @@ -7,7 +7,6 @@ license = "MIT" [features] default = [] std = [ - "bitcoin_hashes/std", "base58ck/std", "bitcoin-consensus-encoding/std", "dash-pkc/std", @@ -17,14 +16,12 @@ full = ["std", "serde"] serde = ["dep:serde", "dash-pkc/serde", "dash-types/serde"] [dependencies] -base58ck = { version = "0.4", default-features = false, features = ["alloc"] } -bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ - "alloc", -] } -bitcoin_hashes = { version = "0.20", default-features = false, features = [ - "alloc", +base58ck = { workspace = true, features = ["alloc"] } +bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } +dash-num = { version = "0.0.0", path = "../num", default-features = false } +dash-pkc = { version = "0.0.0", path = "../pkc", default-features = false, features = [ + "ecdsa", ] } -dash-pkc = { version = "0.0.0", path = "../pkc", default-features = false } dash-types = { version = "0.0.0", path = "../types", default-features = false, features = [ "bitcoin-primitives", ] } @@ -34,7 +31,7 @@ serde = { version = "1", default-features = false, features = [ ], optional = true } [dev-dependencies] -hex-literal = "0.4" +hex-conservative = "0.3" rstest = "0.25" [lints] diff --git a/pkgs/script/src/addrs.rs b/pkgs/script/src/addrs.rs index b13e31b9..15355a39 100644 --- a/pkgs/script/src/addrs.rs +++ b/pkgs/script/src/addrs.rs @@ -6,7 +6,14 @@ //! Address definitions and network parameters. -use dash_types::Unencodable; +use crate::prelude::*; +use crate::{opcode::Opcode, PubKeyHash, ScriptHash}; + +use base58ck::decode_check; +use dash_num::Hash160; +use dash_pkc::ecdsa::EcdsaPkBytes; +use dash_types::codec::{BaseCodec, EncodeBuf, Hashable, NumCodec}; +use dash_types::{type_cvrt, Unencodable}; /// Network address encoding parameters. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Unencodable)] @@ -24,3 +31,299 @@ pub struct AddrParams { /// BIP44 coin type index. pub bip44_idx: u32, } + +/// Recipient parsed from address. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Unencodable)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "type", content = "data"))] +pub enum Recipient { + /// Pay-to-public-key. + #[cfg_attr(feature = "serde", serde(rename = "p2pk"))] + PubKey(EcdsaPkBytes), + /// Pay-to-public-key-hash. + #[cfg_attr(feature = "serde", serde(rename = "p2pkh"))] + PubKeyHash(PubKeyHash), + /// Pay-to-script-hash. + #[cfg_attr(feature = "serde", serde(rename = "p2sh"))] + ScriptHash(ScriptHash), + /// Provably unspendable `OP_RETURN`. + #[cfg_attr(feature = "serde", serde(rename = "unspendable"))] + Unspendable, +} + +impl Recipient { + /// Parse a scriptPubKey. Returns `None` for unrecognized patterns. + pub fn from_script(script: &[u8]) -> Option { + let len = script.len(); + + // P2PKH: OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG + if len == 25 + && script[0] == Opcode::Dup.to_base() + && script[1] == Opcode::Hash160.to_base() + && script[2] == Hash160::LEN as u8 + && script[23] == Opcode::EqualVerify.to_base() + && script[24] == Opcode::CheckSig.to_base() + { + let bytes: &[u8; Hash160::LEN] = script[3..23].try_into().ok()?; + return Some(Self::PubKeyHash(PubKeyHash::from(*bytes))); + } + + // P2SH: OP_HASH160 <20 bytes> OP_EQUAL + if len == 23 + && script[0] == Opcode::Hash160.to_base() + && script[1] == Hash160::LEN as u8 + && script[22] == Opcode::Equal.to_base() + { + let bytes: &[u8; Hash160::LEN] = script[2..22].try_into().ok()?; + return Some(Self::ScriptHash(ScriptHash::from(*bytes))); + } + + // P2PK: <33 or 65 byte pubkey> OP_CHECKSIG + if (len == 35 || len == 67) && script[0] as usize == len - 2 && script[len - 1] == Opcode::CheckSig.to_base() { + let pk = EcdsaPkBytes::from_bytes(&script[1..len - 1])?; + return Some(Self::PubKey(pk)); + } + + // OP_RETURN + if script.first() == Some(&Opcode::Return.to_base()) { + return Some(Self::Unspendable); + } + + None + } + + /// Encode as a Base58Check address. + /// + /// Returns `None` for `Unspendable`, which has no address form. + pub fn to_base58c(&self, params: &AddrParams) -> Option { + match self { + Self::PubKeyHash(h) => Some(h.to_base58c(params.pubkey_addr)), + Self::ScriptHash(h) => Some(h.to_base58c(params.script_addr)), + Self::PubKey(pk) => Some(pk.hash().to_base58c(params.pubkey_addr)), + Self::Unspendable => None, + } + } + + /// Decode a Base58Check address string. + /// + /// Returns `None` when the string is not a valid P2PKH or P2SH address for + /// the given network parameters. + pub fn from_base58c(s: &str, params: &AddrParams) -> Option { + let data = decode_check(s).ok()?; + if data.len() != 21 { + return None; + } + let hash: [u8; Hash160::LEN] = data[1..].try_into().ok()?; + if data[0] == params.pubkey_addr { + Some(Self::PubKeyHash(PubKeyHash::from(hash))) + } else if data[0] == params.script_addr { + Some(Self::ScriptHash(ScriptHash::from(hash))) + } else { + None + } + } + + /// Encode as a scriptPubKey. + pub fn to_script(&self, buf: &mut impl EncodeBuf) { + match self { + Self::PubKeyHash(h) => { + buf.push(Opcode::Dup.to_base()); + buf.push(Opcode::Hash160.to_base()); + buf.push(Hash160::LEN as u8); + h.encode(buf); + buf.push(Opcode::EqualVerify.to_base()); + buf.push(Opcode::CheckSig.to_base()); + } + Self::ScriptHash(h) => { + buf.push(Opcode::Hash160.to_base()); + buf.push(Hash160::LEN as u8); + h.encode(buf); + buf.push(Opcode::Equal.to_base()); + } + Self::PubKey(pk) => { + let bytes = pk.as_bytes(); + buf.push(bytes.len() as u8); + // nosemgrep: codec-no-raw-extend + buf.extend_from_slice(bytes); + buf.push(Opcode::CheckSig.to_base()); + } + Self::Unspendable => { + buf.push(Opcode::Return.to_base()); + } + } + } +} + +type_cvrt!( + enum Recipient { + PubKey(EcdsaPkBytes), + PubKeyHash(PubKeyHash), + ScriptHash(ScriptHash), + } +); + +#[cfg(test)] +#[expect(clippy::unwrap_used, reason = "test code")] +mod tests { + use super::*; + + use hex_conservative::hex; + use rstest::rstest; + + const MAINNET: AddrParams = AddrParams { + pubkey_addr: 76, + script_addr: 16, + secret_key: 204, + ext_pubkey: [0x04, 0x88, 0xB2, 0x1E], + ext_secret: [0x04, 0x88, 0xAD, 0xE4], + bip44_idx: 5, + }; + + // The generator point's coordinates shared across P2PK vectors + macro_rules! gen_x { + () => { + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }; + } + macro_rules! gen_y { + () => { + "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" + }; + } + + // OP_DUP OP_HASH160, a zero hash160, then OP_EQUALVERIFY OP_CHECKSIG. + const P2PKH: [u8; 25] = hex!(concat!("76a914", "0000000000000000000000000000000000000000", "88ac")); + // OP_HASH160, the same zero hash160, then OP_EQUAL. + const P2SH: [u8; 23] = hex!(concat!("a914", "0000000000000000000000000000000000000000", "87")); + // The generator is used so the vectors are real keys. + const P2PK_COMP_EVEN: [u8; 35] = hex!(concat!("21", "02", gen_x!(), "ac")); + // Same X, 0x03 header: the other root, i.e. the negated generator, Y odd. + const P2PK_COMP_ODD: [u8; 35] = hex!(concat!("21", "03", gen_x!(), "ac")); + // The generator again, both coordinates spelled out. + const P2PK_UNCOMP: [u8; 67] = hex!(concat!("41", "04", gen_x!(), gen_y!(), "ac")); + // X = 0 has no point on the curve, but the SEC1 framing is well-formed, so + // a structural classifier still reads it as P2PK. + const P2PK_X_ZERO: [u8; 35] = hex!(concat!( + "21", + "02", + "0000000000000000000000000000000000000000000000000000000000000000", + "ac", + )); + // The generator's X with a Y that is not its square root: likewise well-formed, + // just as unspendable. + const P2PK_BAD_Y: [u8; 67] = hex!(concat!( + "41", + "04", + gen_x!(), + "0000000000000000000000000000000000000000000000000000000000000001", + "ac", + )); + + #[rstest] + #[case::p2pkh(&P2PKH, Recipient::PubKeyHash(PubKeyHash::from([0; Hash160::LEN])))] + #[case::p2sh(&P2SH, Recipient::ScriptHash(ScriptHash::from([0; Hash160::LEN])))] + #[case::p2pkh_nonzero( + &hex!("76a914010203040506070809101112131415161718192088ac"), + Recipient::PubKeyHash(PubKeyHash::from(hex!("0102030405060708091011121314151617181920"))), + )] + #[case::p2sh_nonzero( + &hex!("a914aabbccddee00112233445566778899aabbccddee87"), + Recipient::ScriptHash(ScriptHash::from(hex!("aabbccddee00112233445566778899aabbccddee"))), + )] + #[case::op_return_data(&hex!("6a04deadbeef"), Recipient::Unspendable)] + #[case::op_return_bare(&[Opcode::Return.to_base()], Recipient::Unspendable)] + fn from_script_matches(#[case] script: &[u8], #[case] expected: Recipient) { + assert_eq!(Recipient::from_script(script), Some(expected)); + } + + #[rstest] + #[case::p2pk_even(&P2PK_COMP_EVEN, &P2PK_COMP_EVEN[1..34])] + #[case::p2pk_odd(&P2PK_COMP_ODD, &P2PK_COMP_ODD[1..34])] + #[case::p2pk_uncomp(&P2PK_UNCOMP, &P2PK_UNCOMP[1..66])] + #[case::p2pk_x_not_on_curve(&P2PK_X_ZERO, &P2PK_X_ZERO[1..34])] + #[case::p2pk_y_not_a_root(&P2PK_BAD_Y, &P2PK_BAD_Y[1..66])] + fn from_script_p2pk(#[case] script: &[u8], #[case] key_bytes: &[u8]) { + assert!(matches!( + Recipient::from_script(script), + Some(Recipient::PubKey(pk)) if pk.as_bytes() == key_bytes + )); + } + + #[rstest] + #[case::empty(&[])] + #[case::p2pkh_extra_byte(&hex!("76a914000000000000000000000000000000000000000088acac"))] + #[case::p2pkh_truncated(&hex!("76a9140000000088ac"))] + #[case::p2pkh_wrong_trailing(&hex!("76a91400000000000000000000000000000000000000001414"))] + #[case::p2pkh_wrong_leading(&hex!("a91400000000000000000000000000000000000000008888ac6a"))] + #[case::p2sh_pushdata1(&hex!("a94c14000000000000000000000000000000000000000087"))] + #[case::p2sh_wrong_leading(&hex!("611400000000000000000000000000000000000000000087"))] + #[case::p2sh_wrong_trailing(&hex!("a9140000000000000000000000000000000000000000ac"))] + #[case::p2pk_missing_checksig(&hex!( + "21020000000000000000000000000000000000000000000000000000000000000000" + ))] + #[case::p2pk_wrong_trailing(&hex!( + "2102000000000000000000000000000000000000000000000000000000000000000088" + ))] + #[case::p2pk_too_short(&hex!("210200ac"))] + #[case::arithmetic(&hex!("59935b87"))] + #[case::p2sh_pushdata2(&hex!("4d1400000000000000000000000000000000000000000087"))] + #[case::p2sh_pushdata4(&hex!("4e14000000000000000000000000000000000000000000000087"))] + fn from_script_rejects(#[case] script: &[u8]) { + assert!(Recipient::from_script(script).is_none()); + } + + #[rstest] + #[case::p2pk(&P2PK_COMP_EVEN, Some("XmN7PQYWKn5MJFna5fRYgP6mxT2F7xpekE"))] + #[case::p2pkh(&P2PKH, Some("XagqqFetxiDb9wbartKDrXgnqLah6SqX2S"))] + #[case::p2sh( + &hex!("a914242424242424242424242424242424242424242487"), + Some("7VhkNn2LJ9YE35ZGbWkfPjKisrCFT7ovqy"), + )] + #[case::op_return(&hex!("6a00"), None)] + fn to_base58c_address(#[case] script: &[u8], #[case] expected: Option<&str>) { + let addr = Recipient::from_script(script).and_then(|r| r.to_base58c(&MAINNET)); + assert_eq!(addr.as_deref(), expected); + } + + // `Unspendable` is deliberately excluded: it discards the OP_RETURN operand, + // so it is the one variant that cannot round-trip. + #[rstest] + #[case::p2pkh(&P2PKH)] + #[case::p2sh(&P2SH)] + #[case::p2pk_even(&P2PK_COMP_EVEN)] + #[case::p2pk_odd(&P2PK_COMP_ODD)] + #[case::p2pk_uncomp(&P2PK_UNCOMP)] + fn to_script_round_trips(#[case] script: &[u8]) { + let recipient = Recipient::from_script(script).unwrap(); + let mut buf = Vec::new(); + recipient.to_script(&mut buf); + assert_eq!(buf.as_slice(), script); + } + + #[rstest] + fn unspendable_to_script_drops_operand() { + let recipient = Recipient::from_script(&hex!("6a04deadbeef")).unwrap(); + let mut buf = Vec::new(); + recipient.to_script(&mut buf); + assert_eq!(buf.as_slice(), &[Opcode::Return.to_base()]); + } + + #[rstest] + #[case::p2pkh("XdywTHuctyLo75vU4gjnVFeve7erBRcpzP", Recipient::PubKeyHash(PubKeyHash::from([0x24; Hash160::LEN])))] + #[case::p2sh("7VhkNn2LJ9YE35ZGbWkfPjKisrCFT7ovqy", Recipient::ScriptHash(ScriptHash::from([0x24; Hash160::LEN])))] + fn from_base58c_round_trips(#[case] addr: &str, #[case] expected: Recipient) { + let recipient = Recipient::from_base58c(addr, &MAINNET).unwrap(); + assert_eq!(recipient, expected); + assert_eq!(recipient.to_base58c(&MAINNET).as_deref(), Some(addr)); + } + + #[rstest] + // Valid Base58Check, but the testnet version byte matches no MAINNET kind. + #[case::wrong_version("yPcYUEz4LWzsSpr1dY4BXH5GvQ9DdCV6cv")] + #[case::bad_checksum("7VhkNn2LJ9YE35ZGbWkfPjKisrCFT7ovqz")] + #[case::not_base58("not an address")] + #[case::empty("")] + fn from_base58c_rejects(#[case] addr: &str) { + assert!(Recipient::from_base58c(addr, &MAINNET).is_none()); + } +} diff --git a/pkgs/script/src/lib.rs b/pkgs/script/src/lib.rs index 48a2e4cf..9ba867b5 100644 --- a/pkgs/script/src/lib.rs +++ b/pkgs/script/src/lib.rs @@ -4,7 +4,7 @@ // See the accompanying file LICENSE or https://opensource.org/license/MIT // -//! Script opcodes, classification, and address derivation for Dash. +//! Scripting and addressing. #![no_std] @@ -13,486 +13,13 @@ extern crate alloc; extern crate std; mod addrs; +mod opcode; #[allow(unused_imports, reason = "ergonomic shim, exports may be unused")] mod prelude; +mod sigops; -use crate::opcode::Opcode as Op; -use crate::prelude::*; - -use bitcoin_hashes::{hash160, sha256}; -use dash_types::codec::NumCodec; -use dash_types::Unencodable; - -pub mod opcode; - -pub use addrs::AddrParams; +pub use addrs::{AddrParams, Recipient}; pub use dash_pkc::__private::__PubKeyHash as PubKeyHash; pub use dash_types::__private::__ScriptHash as ScriptHash; pub use opcode::Opcode; - -/// RIPEMD-160(SHA-256) output length in bytes. -const HASH160_LEN: usize = 20; - -/// P2PKH scriptPubKey: OP_DUP OP_HASH160 <20> OP_EQUALVERIFY OP_CHECKSIG. -const P2PKH_SCRIPT_LEN: usize = 25; - -/// P2SH scriptPubKey: OP_HASH160 <20> OP_EQUAL. -const P2SH_SCRIPT_LEN: usize = 23; - -/// P2PK scriptPubKey with a 33-byte compressed public key. -const P2PK_COMPRESSED_SCRIPT_LEN: usize = 35; - -/// P2PK scriptPubKey with a 65-byte uncompressed public key. -const P2PK_UNCOMPRESSED_SCRIPT_LEN: usize = 67; - -/// SEC1 compressed public key length in bytes. -const P2PK_COMPRESSED_KEY_LEN: usize = 33; - -/// SEC1 uncompressed public key length in bytes. -const P2PK_UNCOMPRESSED_KEY_LEN: usize = 65; - -/// Known output script patterns. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Unencodable)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub enum ScriptKind { - /// Pay-to-public-key-hash. - P2pkh, - /// Pay-to-script-hash. - P2sh, - /// Pay-to-public-key. - P2pk, - /// Provably unspendable `OP_RETURN`. - OpReturn, - /// Unrecognized or unsupported script pattern (leading opcode). - Unknown(u8), -} - -/// Classify a scriptPubKey by its leading pattern. -pub fn classify(script: &[u8]) -> ScriptKind { - if is_p2pkh(script) { - return ScriptKind::P2pkh; - } - if is_p2sh(script) { - return ScriptKind::P2sh; - } - if is_p2pk(script) { - return ScriptKind::P2pk; - } - if is_op_return(script) { - return ScriptKind::OpReturn; - } - ScriptKind::Unknown(script.first().copied().unwrap_or(0)) -} - -/// Returns `true` for P2PKH scripts -/// (`OP_DUP OP_HASH160 <20> OP_EQUALVERIFY OP_CHECKSIG`). -pub fn is_p2pkh(script: &[u8]) -> bool { - script.len() == P2PKH_SCRIPT_LEN - && script[0] == Op::Dup.to_base() - && script[1] == Op::Hash160.to_base() - && script[2] == HASH160_LEN as u8 - && script[23] == Op::EqualVerify.to_base() - && script[24] == Op::CheckSig.to_base() -} - -/// Returns `true` for P2SH scripts -/// (`OP_HASH160 <20> OP_EQUAL`). -pub fn is_p2sh(script: &[u8]) -> bool { - script.len() == P2SH_SCRIPT_LEN - && script[0] == Op::Hash160.to_base() - && script[1] == HASH160_LEN as u8 - && script[22] == Op::Equal.to_base() -} - -/// Returns `true` for P2PK scripts (compressed or uncompressed). -pub fn is_p2pk(script: &[u8]) -> bool { - let len = script.len(); - (len == P2PK_COMPRESSED_SCRIPT_LEN - && script[0] == P2PK_COMPRESSED_KEY_LEN as u8 - && script[P2PK_COMPRESSED_SCRIPT_LEN - 1] == Op::CheckSig.to_base()) - || (len == P2PK_UNCOMPRESSED_SCRIPT_LEN - && script[0] == P2PK_UNCOMPRESSED_KEY_LEN as u8 - && script[P2PK_UNCOMPRESSED_SCRIPT_LEN - 1] == Op::CheckSig.to_base()) -} - -/// Returns `true` when the script starts with `OP_RETURN`. -pub fn is_op_return(script: &[u8]) -> bool { - script.first() == Some(&Op::Return.to_base()) -} - -/// Extracts the 20-byte key hash from a P2PKH script. -pub fn p2pkh_hash160(script: &[u8]) -> Option<&[u8; 20]> { - if is_p2pkh(script) { - script[3..23].try_into().ok() - } else { - None - } -} - -/// Extracts the 20-byte script hash from a P2SH script. -pub fn p2sh_hash160(script: &[u8]) -> Option<&[u8; 20]> { - if is_p2sh(script) { - script[2..22].try_into().ok() - } else { - None - } -} - -fn encode_base58_check(prefix: u8, hash: &[u8]) -> Option { - if hash.len() != HASH160_LEN { - return None; - } - let mut payload = Vec::with_capacity(HASH160_LEN + 1); - payload.push(prefix); - // nosemgrep: codec-no-raw-extend - payload.extend_from_slice(hash); - Some(base58ck::encode_check(&payload)) -} - -/// Encode a 20-byte public-key hash as a Base58Check P2PKH -/// address. -pub fn encode_p2pkh(hash160: &[u8], prefix: u8) -> Option { - encode_base58_check(prefix, hash160) -} - -/// Encode a 20-byte script hash as a Base58Check P2SH address. -pub fn encode_p2sh(hash160: &[u8], prefix: u8) -> Option { - encode_base58_check(prefix, hash160) -} - -/// Derive a Base58Check address from a scriptPubKey. -/// -/// Returns `None` for `OP_RETURN` and unrecognized scripts. -pub fn derive_address(script: &[u8], p2pkh_version: u8, p2sh_version: u8) -> Option { - match classify(script) { - ScriptKind::P2pkh => encode_p2pkh(&script[3..23], p2pkh_version), - ScriptKind::P2sh => encode_p2sh(&script[2..22], p2sh_version), - ScriptKind::P2pk => { - let pubkey = if script.len() == P2PK_COMPRESSED_SCRIPT_LEN { - &script[1..(1 + P2PK_COMPRESSED_KEY_LEN)] - } else { - &script[1..(1 + P2PK_UNCOMPRESSED_KEY_LEN)] - }; - let sha = sha256::Hash::hash(pubkey); - let h160 = hash160::Hash::from_byte_array(*bitcoin_hashes::ripemd160::Hash::hash(sha.as_ref()).as_byte_array()); - encode_p2pkh(h160.as_ref(), p2pkh_version) - } - ScriptKind::OpReturn | ScriptKind::Unknown(_) => None, - } -} - -/// Count legacy signature operations in a script. -/// -/// Counts `OP_CHECKSIG`, `OP_CHECKSIGVERIFY`, `OP_CHECKMULTISIG` (weighted by -/// `MAX_PUBKEYS_PER_MULTISIG`), and `OP_CHECKMULTISIGVERIFY`. -pub fn legacy_sigop_count(script: &[u8]) -> usize { - const MAX_PUBKEYS: usize = 20; - - let mut count: usize = 0; - let mut i = 0; - while i < script.len() { - let byte = script[i]; - if Opcode::is_direct_push(byte) { - // skip over pushed data - i += 1 + byte as usize; - continue; - } - match Op::from_base(byte) { - Op::CheckSig | Op::CheckSigVerify => count += 1, - Op::CheckMultiSig | Op::CheckMultiSigVerify => { - count += MAX_PUBKEYS; - } - Op::PushData1 if i + 1 < script.len() => { - i += 2 + script[i + 1] as usize; - continue; - } - Op::PushData2 if i + 2 < script.len() => { - let n = u16::from_le_bytes([script[i + 1], script[i + 2]]); - i += 3 + n as usize; - continue; - } - Op::PushData4 if i + 4 < script.len() => { - let n = u32::from_le_bytes([script[i + 1], script[i + 2], script[i + 3], script[i + 4]]); - i += 5 + n as usize; - continue; - } - _ => {} - } - i += 1; - } - count -} - -#[cfg(test)] -mod tests { - use super::*; - - use hex_literal::hex; - - #[test] - fn p2pkh_valid() { - let script = hex!("76a914000000000000000000000000000000000000000088ac"); - assert!(is_p2pkh(&script)); - assert_eq!(classify(&script), ScriptKind::P2pkh); - } - - #[test] - fn p2pkh_extra_byte_is_not_p2pkh() { - let script = hex!("76a914000000000000000000000000000000000000000088acac"); - assert!(!is_p2pkh(&script)); - } - - #[test] - fn p2sh_is_not_p2pkh() { - let script = hex!("a914000000000000000000000000000000000000000087"); - assert!(!is_p2pkh(&script)); - } - - #[test] - fn p2pkh_missing_leading_opcodes() { - let script = hex!("a91400000000000000000000000000000000000000008888ac6a"); - assert!(!is_p2pkh(&script)); - } - - #[test] - fn p2pkh_truncated_hash() { - let script = hex!("76a9140000000088ac"); - assert!(!is_p2pkh(&script)); - } - - #[test] - fn p2pkh_missing_trailing_opcodes() { - let script = hex!("76a91400000000000000000000000000000000000000001414"); - assert!(!is_p2pkh(&script)); - } - - #[test] - fn p2sh_valid() { - let script = hex!("a914000000000000000000000000000000000000000087"); - assert!(is_p2sh(&script)); - assert_eq!(classify(&script), ScriptKind::P2sh); - } - - #[test] - fn p2sh_pushdata1_is_not_p2sh() { - // direct push byte required; PUSHDATA1 must not match - let script = hex!("a94c14000000000000000000000000000000000000000087"); - assert!(!is_p2sh(&script)); - } - - #[test] - fn p2sh_empty_is_not_p2sh() { - assert!(!is_p2sh(&[])); - } - - #[test] - fn p2sh_wrong_leading_opcode() { - let script = hex!("611400000000000000000000000000000000000000000087"); - assert!(!is_p2sh(&script)); - } - - #[test] - fn p2sh_wrong_trailing_opcode() { - let script = hex!("a9140000000000000000000000000000000000000000ac"); - assert!(!is_p2sh(&script)); - } - - #[test] - fn p2pk_compressed_even() { - let script = hex!("2102 00000000000000000000000000000000 00000000000000000000000000000000 ac"); - assert_eq!(script.len(), 35); - assert!(is_p2pk(&script)); - assert_eq!(classify(&script), ScriptKind::P2pk); - } - - #[test] - fn p2pk_compressed_odd() { - let script = hex!("2103 00000000000000000000000000000000 00000000000000000000000000000000 ac"); - assert!(is_p2pk(&script)); - } - - #[test] - fn p2pk_uncompressed() { - let script = hex!( - "4104 00000000000000000000000000000000 00000000000000000000000000000000" - " 00000000000000000000000000000000 00000000000000000000000000000000 ac" - ); - assert_eq!(script.len(), 67); - assert!(is_p2pk(&script)); - } - - #[test] - fn p2pk_missing_checksig() { - // truncated: no trailing OP_CHECKSIG - let script = hex!("2102 00000000000000000000000000000000 00000000000000000000000000000000"); - assert!(!is_p2pk(&script)); - } - - #[test] - fn p2pk_wrong_trailing_opcode() { - // OP_EQUALVERIFY (0x88) in place of OP_CHECKSIG - let script = hex!("2102 00000000000000000000000000000000 00000000000000000000000000000000 88"); - assert!(!is_p2pk(&script)); - } - - #[test] - fn p2pk_too_short() { - let script = hex!("210200ac"); - assert!(!is_p2pk(&script)); - } - - #[test] - fn op_return_with_data() { - let script = hex!("6a04deadbeef"); - assert!(is_op_return(&script)); - assert_eq!(classify(&script), ScriptKind::OpReturn); - } - - #[test] - fn op_return_bare() { - assert!(is_op_return(&[Op::Return.to_base()])); - } - - #[test] - fn op_return_empty_is_not_op_return() { - assert!(!is_op_return(&[])); - } - - #[test] - fn empty_is_unknown() { - assert_eq!(classify(&[]), ScriptKind::Unknown(0)); - } - - #[test] - fn arithmetic_script_is_unknown() { - let script = hex!("59935b87"); - assert_eq!(classify(&script), ScriptKind::Unknown(0x59)); - } - - #[test] - fn p2pkh_hash160_extraction() { - let script = hex!("76a914 0102030405060708091011121314151617181920 88ac"); - assert_eq!(script.len(), 25); - assert!(is_p2pkh(&script)); - let hash = p2pkh_hash160(&script); - assert_eq!(hash, Some(&hex!("0102030405060708091011121314151617181920"))); - } - - #[test] - fn p2sh_hash160_extraction() { - let script = hex!("a914aabbccddee00112233445566778899aabbccddee87"); - assert!(is_p2sh(&script)); - let hash = p2sh_hash160(&script); - assert_eq!(hash, Some(&hex!("aabbccddee00112233445566778899aabbccddee"))); - } - - #[test] - fn hash_extraction_returns_none_for_wrong_type() { - let p2sh = hex!("a914000000000000000000000000000000000000000087"); - assert_eq!(p2pkh_hash160(&p2sh), None); - - let p2pkh = hex!("76a914000000000000000000000000000000000000000088ac"); - assert_eq!(p2sh_hash160(&p2pkh), None); - } - - #[test] - fn derive_p2sh_address() { - let script = hex!("a914242424242424242424242424242424242424242487"); - assert_eq!( - derive_address(&script, 76, 16), - Some("7VhkNn2LJ9YE35ZGbWkfPjKisrCFT7ovqy".to_owned()) - ); - } - - #[test] - fn encode_p2pkh_rejects_wrong_length() { - assert_eq!(encode_p2pkh(&[0x42; 19], 76), None); - } - - #[test] - fn op_return_address_is_none() { - let script = hex!("6a00"); - assert_eq!(derive_address(&script, 76, 16), None); - } - - #[test] - fn unknown_script_address_is_none() { - assert_eq!(derive_address(&[], 76, 16), None); - } - - #[test] - fn sigop_empty_script() { - assert_eq!(legacy_sigop_count(&[]), 0); - } - - #[test] - fn sigop_single_checksig() { - let script = [Op::CheckSig.to_base()]; - assert_eq!(legacy_sigop_count(&script), 1); - } - - #[test] - fn sigop_single_checksigverify() { - let script = [Op::CheckSigVerify.to_base()]; - assert_eq!(legacy_sigop_count(&script), 1); - } - - #[test] - fn sigop_checkmultisig_counts_as_20() { - let script = [Op::CheckMultiSig.to_base()]; - assert_eq!(legacy_sigop_count(&script), 20); - } - - #[test] - fn sigop_checkmultisigverify_counts_as_20() { - let script = [Op::CheckMultiSigVerify.to_base()]; - assert_eq!(legacy_sigop_count(&script), 20); - } - - #[test] - fn sigop_p2pkh_script() { - // P2PKH has one OP_CHECKSIG - let script = hex!("76a914000000000000000000000000000000000000000088ac"); - assert_eq!(legacy_sigop_count(&script), 1); - } - - #[test] - fn sigop_multisig_then_checksig() { - // legacy count: CHECKMULTISIG(20) + CHECKSIG(1) = 21 - let script = hex!( - "51" - "1400000000000000000000000000000000000000001400000000000000000000000000000000000000" - "52ae" - "63ac68" - ); - assert_eq!(legacy_sigop_count(&script), 21); - } - - #[test] - fn sigop_skips_pushed_data() { - // 0xac inside pushed data must not count as OP_CHECKSIG - let script = hex!("02acac"); - assert_eq!(legacy_sigop_count(&script), 0); - } - - #[test] - fn sigop_skips_pushdata1() { - // 0xac inside PUSHDATA1 payload must not count - let script = hex!("4c01ac"); - assert_eq!(legacy_sigop_count(&script), 0); - } - - #[test] - fn sigop_skips_pushdata2() { - // 0xac inside PUSHDATA2 payload must not count - let script = hex!("4d0100ac"); - assert_eq!(legacy_sigop_count(&script), 0); - } - - #[test] - fn sigop_skips_pushdata4() { - // 0xac inside PUSHDATA4 payload must not count - let script = hex!("4e01000000ac"); - assert_eq!(legacy_sigop_count(&script), 0); - } -} +pub use sigops::legacy_sigop_count; diff --git a/pkgs/script/src/prelude.rs b/pkgs/script/src/prelude.rs index 12c3adf9..16ed857d 100644 --- a/pkgs/script/src/prelude.rs +++ b/pkgs/script/src/prelude.rs @@ -9,4 +9,5 @@ pub(crate) use alloc::borrow::ToOwned; pub(crate) use alloc::string::String; pub(crate) use alloc::string::ToString; +pub(crate) use alloc::vec; pub(crate) use alloc::vec::Vec; diff --git a/pkgs/script/src/sigops.rs b/pkgs/script/src/sigops.rs new file mode 100644 index 00000000..28c01453 --- /dev/null +++ b/pkgs/script/src/sigops.rs @@ -0,0 +1,107 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Signature operations count. + +use crate::opcode::Opcode; + +use dash_types::codec::NumCodec; + +const MAX_PUBKEYS: usize = 20; + +/// Count legacy signature operations in a script. +/// +/// Counts `OP_CHECKMULTISIG` (weighted by `MAX_PUBKEYS_PER_MULTISIG`), +/// `OP_CHECKSIG`, `OP_CHECKSIGVERIFY` and `OP_CHECKMULTISIGVERIFY`. +pub fn legacy_sigop_count(script: &[u8]) -> usize { + let mut count: usize = 0; + let mut i = 0; + while i < script.len() { + let byte = script[i]; + if Opcode::is_direct_push(byte) { + // skip over pushed data + i = i.saturating_add(1).saturating_add(byte as usize); + continue; + } + match Opcode::from_base(byte) { + Opcode::CheckSig | Opcode::CheckSigVerify => count = count.saturating_add(1), + Opcode::CheckMultiSig | Opcode::CheckMultiSigVerify => { + count = count.saturating_add(MAX_PUBKEYS); + } + // A push whose length header is cut short ends the scan: the bytes that + // follow are a truncated operand rather than opcodes, so nothing past + // this point counts as a sigop. + Opcode::PushData1 => match script.get(i + 1) { + Some(&n) => { + i = i.saturating_add(2).saturating_add(n as usize); + continue; + } + None => break, + }, + Opcode::PushData2 => match script.get(i + 1..i + 3) { + Some(&[lo, hi]) => { + let n = u16::from_le_bytes([lo, hi]); + i = i.saturating_add(3).saturating_add(n as usize); + continue; + } + _ => break, + }, + Opcode::PushData4 => match script.get(i + 1..i + 5) { + Some(&[a, b, c, d]) => { + let n = u32::from_le_bytes([a, b, c, d]); + i = i.saturating_add(5).saturating_add(n as usize); + continue; + } + _ => break, + }, + _ => {} + } + i += 1; + } + count +} + +#[cfg(test)] +mod tests { + use super::legacy_sigop_count; + + use hex_conservative::hex; + use rstest::rstest; + + #[rstest] + #[case::empty(&[], 0)] + #[case::checksig(&[0xac], 1)] + #[case::checksigverify(&[0xad], 1)] + #[case::checkmultisig(&[0xae], 20)] + #[case::checkmultisigverify(&[0xaf], 20)] + #[case::multisig_always_20(&hex!("5152ae"), 20)] + #[case::multisig_then_checksig( + &hex!(concat!( + "51", + "1400000000000000000000000000000000000000001400000000000000000000000000000000000000", + "52ae", + "63ac68", + )), + 21, + )] + #[case::p2pkh(&hex!("76a914000000000000000000000000000000000000000088ac"), 1)] + #[case::if_checksig_endif(&hex!("63ac68"), 1)] + #[case::skip_direct_push(&hex!("02acac"), 0)] + #[case::skip_pushdata1(&hex!("4c01ac"), 0)] + #[case::skip_pushdata2(&hex!("4d0100ac"), 0)] + #[case::skip_pushdata4(&hex!("4e01000000ac"), 0)] + // A truncated push header ends the scan, so trailing bytes that would read + // as OP_CHECKSIG are operand and go uncounted. + #[case::truncated_pushdata1(&hex!("4c"), 0)] + #[case::truncated_pushdata2(&hex!("4dac"), 0)] + #[case::truncated_pushdata4(&hex!("4eacacac"), 0)] + // Sigops before the truncation still count; those after it do not. + #[case::checksig_then_truncated_pushdata2(&hex!("ac4dac"), 1)] + #[case::checksig_then_truncated_pushdata4(&hex!("ac4eacacac"), 1)] + fn sigop_count(#[case] script: &[u8], #[case] expected: usize) { + assert_eq!(legacy_sigop_count(script), expected); + } +} diff --git a/pkgs/types/Cargo.toml b/pkgs/types/Cargo.toml index 6b19b883..19431f00 100644 --- a/pkgs/types/Cargo.toml +++ b/pkgs/types/Cargo.toml @@ -8,10 +8,12 @@ license = "MIT" default = [] std = [ "bitcoin-consensus-encoding/std", + "bitcoin-p2p-messages?/std", "bitcoin-primitives?/std", "hex-conservative?/std", ] -full = ["std", "serde", "bitcoin-primitives"] +full = ["std", "serde", "bitcoin-p2p-messages", "bitcoin-primitives"] +bitcoin-p2p-messages = ["dep:bitcoin-p2p-messages"] bitcoin-primitives = [ "dep:base58ck", "dep:bitcoin-primitives", @@ -20,16 +22,11 @@ bitcoin-primitives = [ serde = ["dep:serde", "dep:hex-conservative"] [dependencies] -base58ck = { version = "0.4", default-features = false, optional = true, features = [ - "alloc", -] } -bitcoin-consensus-encoding = { version = "0.2", default-features = false } -bitcoin_hashes = { version = "0.20", default-features = false, optional = true, features = [ - "alloc", -] } -bitcoin-primitives = { version = "0.102", default-features = false, optional = true, features = [ - "alloc", -] } +base58ck = { workspace = true, optional = true, features = ["alloc"] } +bitcoin-consensus-encoding = { workspace = true } +bitcoin-p2p-messages = { workspace = true, optional = true } +bitcoin_hashes = { workspace = true, optional = true, features = ["alloc"] } +bitcoin-primitives = { workspace = true, optional = true, features = ["alloc"] } cfg-if = "1" dash-types-marker = { version = "0.0.0", path = "marker" } hex-conservative = { version = "0.3", default-features = false, features = [ diff --git a/pkgs/types/src/adapters.rs b/pkgs/types/src/adapters.rs index d9489ff3..b47bd3ea 100644 --- a/pkgs/types/src/adapters.rs +++ b/pkgs/types/src/adapters.rs @@ -39,6 +39,15 @@ macro_rules! adapt_codec { }; } +#[cfg(feature = "bitcoin-p2p-messages")] +mod bitcoin_p2p_messages { + // nosemgrep: macro-no-bare-foreign-crate + use ::bitcoin_p2p_messages::message_filter::{FilterHash, FilterHeader}; + + adapt_codec!(FilterHash, 32); + adapt_codec!(FilterHeader, 32); +} + #[cfg(feature = "bitcoin-primitives")] pub mod bitcoin_primitives { use crate::codec::{BaseCodec, EncodeBuf, Hashable}; @@ -46,9 +55,10 @@ pub mod bitcoin_primitives { use crate::prelude::*; use crate::secret::ArrayBuf; + // nosemgrep: macro-no-bare-foreign-crate + use ::bitcoin_primitives::script::{ScriptBuf, ScriptHashableTag}; use base58ck::encode_check; use bitcoin_hashes::{ripemd160, sha256}; - use bitcoin_primitives::script::{ScriptBuf, ScriptHashableTag}; adapt_codec!(, ScriptBuf); diff --git a/pkgs/types/src/entity.rs b/pkgs/types/src/entity.rs index e51a3ff0..40359a86 100644 --- a/pkgs/types/src/entity.rs +++ b/pkgs/types/src/entity.rs @@ -132,7 +132,7 @@ impl Decoder for VecDecoder { } } -/// Generates `Encodable` + `Decodable` for a `BaseCodec` implementor. +/// Generates `Encode` + `Decode` for a `BaseCodec` implementor. /// /// Stages through the growable [`VecEncoder`]/[`VecDecoder`] pair. For /// secret material use [`impl_stype!`](crate::impl_stype) instead, which is @@ -140,7 +140,7 @@ impl Decoder for VecDecoder { #[macro_export] macro_rules! impl_type { (@parse [$($impl_generics:tt)*] $ty:ty, $max:expr, $err:ty) => { - impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Encodable for $ty { + impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Encode for $ty { type Encoder<'e> = $crate::VecEncoder; fn encoder(&self) -> Self::Encoder<'_> { let mut buf = ::alloc::vec::Vec::new(); @@ -149,7 +149,7 @@ macro_rules! impl_type { } } - impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Decodable for $ty { + impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Decode for $ty { type Decoder = $crate::VecDecoder<$ty, $err>; fn decoder() -> Self::Decoder { $crate::VecDecoder::new(<$ty as $crate::codec::BaseCodec<$err>>::decode, $max) @@ -174,7 +174,7 @@ macro_rules! impl_type { }; } -/// Generates `BaseCodec` + `Encodable` + `Decodable` + `From<[u8; N]>` for a +/// Generates `BaseCodec` + `Encode` + `Decode` + `From<[u8; N]>` for a /// fixed-size byte newtype, expressed only through `from_bytes` / `as_bytes`. /// /// Staged through the growable [`VecEncoder`]. For a newtype whose contents diff --git a/pkgs/types/src/macros.rs b/pkgs/types/src/macros.rs index d497a9a8..583c81b1 100644 --- a/pkgs/types/src/macros.rs +++ b/pkgs/types/src/macros.rs @@ -310,6 +310,22 @@ macro_rules! type_cvrt { (for[$($generic:tt)*] $($args:tt)*) => { $crate::type_cvrt!(@parse [$($generic)*] $($args)*); }; + (enum $enum:ident { $($variant:ident($inner:ty)),* $(,)? }) => { $( + impl ::core::convert::From<$inner> for $enum { + fn from(v: $inner) -> Self { + Self::$variant(v) + } + } + impl ::core::convert::TryFrom<$enum> for $inner { + type Error = $enum; + fn try_from(v: $enum) -> Result { + match v { + $enum::$variant(inner) => Ok(inner), + other => Err(other), + } + } + } + )* }; ($($args:tt)*) => { $crate::type_cvrt!(@parse [] $($args)*); }; diff --git a/pkgs/types/src/secret.rs b/pkgs/types/src/secret.rs index 3e2ef1c5..0d38b9dc 100644 --- a/pkgs/types/src/secret.rs +++ b/pkgs/types/src/secret.rs @@ -253,7 +253,7 @@ impl Decoder for ArrDecoder { } } -/// Generates `Encodable` + `Decodable` for a `BaseCodec` implementor whose +/// Generates `Encode` + `Decode` for a `BaseCodec` implementor whose /// wire image is secret. /// /// Stages through the wiping [`ArrEncoder`]/[`ArrDecoder`] pair, both sized by @@ -263,7 +263,7 @@ impl Decoder for ArrDecoder { #[macro_export] macro_rules! impl_stype { (@parse [$($impl_generics:tt)*] $ty:ty, $n:expr, $err:ty) => { - impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Encodable for $ty { + impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Encode for $ty { type Encoder<'e> = $crate::ArrEncoder<{ $n }>; fn encoder(&self) -> Self::Encoder<'_> { let mut buf = $crate::ArrayBuf::<{ $n }>::new(); @@ -272,7 +272,7 @@ macro_rules! impl_stype { } } - impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Decodable for $ty { + impl<$($impl_generics)*> $crate::__private::bitcoin_consensus_encoding::Decode for $ty { type Decoder = $crate::ArrDecoder<$ty, { $n }, $err>; fn decoder() -> Self::Decoder { $crate::ArrDecoder::new(<$ty as $crate::codec::BaseCodec<$err>>::decode) diff --git a/pkgs/types/src/serialize.rs b/pkgs/types/src/serialize.rs index f89ffa73..96a31659 100644 --- a/pkgs/types/src/serialize.rs +++ b/pkgs/types/src/serialize.rs @@ -90,3 +90,70 @@ pub mod utf8 { Ok(s.into_bytes()) } } + +/// Best-effort UTF-8 serde for `Vec` fields. +/// +/// Unlike [`utf8`], which rejects anything that is not valid UTF-8, +/// [`utf8_lossy`] will preserve arbitrary bytes in round-trips. +pub mod utf8_lossy { + use crate::prelude::*; + + use ::serde::de::{Error as DeError, SeqAccess, Visitor}; + + use core::fmt; + use core::str::from_utf8; + + /// Serializes bytes as a string when valid UTF-8, otherwise as raw bytes. + /// + /// # Errors + /// + /// Returns a serialization error when the serializer rejects the value. + pub fn serialize(data: &[u8], serializer: S) -> Result { + match from_utf8(data) { + Ok(text) => serializer.serialize_str(text), + Err(_) => serializer.serialize_bytes(data), + } + } + + /// Deserializes bytes from a string, a byte buffer, or a sequence of bytes. + /// + /// Length limits are a codec concern, so nothing is bounded here; a newtype + /// with a maximum enforces it in its own constructor. + /// + /// # Errors + /// + /// Returns a deserialization error when the input is none of those forms. + pub fn deserialize<'de, D: ::serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + struct BytesVisitor; + + impl<'de> Visitor<'de> for BytesVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a string or byte sequence") + } + + fn visit_str(self, v: &str) -> Result { + Ok(v.as_bytes().to_vec()) + } + + fn visit_bytes(self, v: &[u8]) -> Result { + Ok(v.to_vec()) + } + + fn visit_seq>(self, mut seq: A) -> Result { + let mut bytes = Vec::new(); + while let Some(byte) = seq.next_element::()? { + bytes.push(byte); + } + Ok(bytes) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_any(BytesVisitor) + } else { + deserializer.deserialize_byte_buf(BytesVisitor) + } + } +} diff --git a/pkgs/types/src/uint.rs b/pkgs/types/src/uint.rs index c9007db4..edf8a1bd 100644 --- a/pkgs/types/src/uint.rs +++ b/pkgs/types/src/uint.rs @@ -6,7 +6,7 @@ //! Fixed-size integer newtype macros. -/// Generates `BaseCodec` + `Encodable` + `Decodable` + serde for a type +/// Generates `BaseCodec` + `Encode` + `Decode` + serde for a type /// that already implements `NumCodec<$uint>`. #[macro_export] macro_rules! impl_num {