diff --git a/.gitignore b/.gitignore index 9802fe48..84e91a92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ publish/ rust/target -.vscode/ \ No newline at end of file +.vscode/ +.idea +rust/.idea diff --git a/LICENSE b/LICENSE index 728ca327..0540392a 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Includes other software related under the MIT license: +- chain-libs, Copyright 2018-2019 IOHK. For licensing see /LICENSE-IOHK diff --git a/LICENSE-IOHK b/LICENSE-IOHK new file mode 100644 index 00000000..c03cf180 --- /dev/null +++ b/LICENSE-IOHK @@ -0,0 +1,25 @@ +Copyright (c) 2018-2019 Input Output HK + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index a5efcf60..17cf6d2f 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,4 @@ This is a library for serialization & deserialization of data structures used in ## Documentation -You can find documentation [here](https://docs.cardano.org/projects/cardano-serialization-lib/en/latest/) \ No newline at end of file +You can find documentation [here](https://docs.cardano.org/projects/cardano-serialization-lib/en/latest/) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9fffd811..7c27b54a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,10 +1,19 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "bech32" -version = "0.6.0" +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58946044516aa9dc922182e0d6e9d124a31aafe6b421614654eb27cf90cec09c" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "bech32" @@ -27,7 +36,16 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", ] [[package]] @@ -57,30 +75,26 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -[[package]] -name = "cardano-legacy-address" -version = "0.1.1" -dependencies = [ - "cbor_event", - "cryptoxide 0.1.3", - "ed25519-bip32", -] - [[package]] name = "cardano-serialization-lib" version = "2.1.0" dependencies = [ - "bech32 0.7.2", - "cardano-legacy-address", + "bech32", "cbor_event", - "chain-crypto", - "chain-impl-mockchain", + "cfg-if", "clear_on_drop", - "cryptoxide 0.2.1", + "cryptoxide", + "curve25519-dalek 1.2.4", + "digest 0.8.1", "ed25519-bip32", - "hex 0.4.2", + "ed25519-dalek", + "hex", "js-sys", + "quickcheck", + "quickcheck_macros", + "rand_chacha 0.1.1", "rand_os", + "sha2 0.8.2", "wasm-bindgen", ] @@ -102,77 +116,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -[[package]] -name = "chain-addr" -version = "0.1.0" -dependencies = [ - "bech32 0.6.0", - "cfg-if", - "chain-core", - "chain-crypto", - "cryptoxide 0.1.3", -] - -[[package]] -name = "chain-core" -version = "0.1.0" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "chain-crypto" -version = "0.1.0" -dependencies = [ - "bech32 0.6.0", - "cfg-if", - "cryptoxide 0.1.3", - "curve25519-dalek 1.2.4", - "digest", - "ed25519-bip32", - "ed25519-dalek", - "generic-array", - "hex 0.3.2", - "rand_core 0.3.1", - "sha2", - "typed-bytes", -] - -[[package]] -name = "chain-impl-mockchain" -version = "0.1.0" -dependencies = [ - "cardano-legacy-address", - "cfg-if", - "chain-addr", - "chain-core", - "chain-crypto", - "chain-storage", - "chain-time", - "custom_error", - "imhamt", - "rand_core 0.3.1", - "rand_os", - "sparse-array", - "strum", - "strum_macros", - "typed-bytes", -] - -[[package]] -name = "chain-storage" -version = "0.1.0" -dependencies = [ - "chain-core", -] - -[[package]] -name = "chain-time" -version = "0.1.0" -dependencies = [ - "cfg-if", -] - [[package]] name = "clear_on_drop" version = "0.2.4" @@ -192,10 +135,10 @@ dependencies = [ ] [[package]] -name = "cryptoxide" -version = "0.1.3" +name = "cpuid-bool" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35f15e1a0699dd988fed910dd78fdc6407f44654cd12589c91fa44ea67d9159" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" [[package]] name = "cryptoxide" @@ -211,58 +154,82 @@ checksum = "405681bfe2b7b25ad8660dfd90b6e8be9e470e224ff49e36b587d43f29a22601" dependencies = [ "byteorder", "clear_on_drop", - "digest", + "digest 0.8.1", "rand_core 0.3.1", "subtle", ] [[package]] name = "curve25519-dalek" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" +checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307" dependencies = [ "byteorder", - "digest", + "digest 0.9.0", "rand_core 0.5.1", "subtle", "zeroize", ] [[package]] -name = "custom_error" -version = "1.7.1" +name = "digest" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a0fc65739ae998afc8d68e64bdac2efd1bc4ffa1a0703d171ef2defae3792f" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.3", +] [[package]] name = "digest" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.4", +] + +[[package]] +name = "ed25519" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf038a7b6fd7ef78ad3348b63f3a17550877b0e28f8d68bcc94894d1412158bc" +dependencies = [ + "signature", ] [[package]] name = "ed25519-bip32" -version = "0.1.5" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dc90e26f8ad41b7d3d3ce540bf465df20d54fb3b809427d9a046babf16f5a3d" +checksum = "e4727ce16fc016d8047462988ced12496aad2bb29b2b55ecf60d29eb5706fc6f" dependencies = [ - "cryptoxide 0.1.3", + "cryptoxide", ] [[package]] name = "ed25519-dalek" -version = "1.0.0-pre.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" +checksum = "53d2e93f837d749c16d118e7ddf7a4dfd0ac8f452cf51e46e9348824e5ef6851" dependencies = [ - "clear_on_drop", - "curve25519-dalek 2.1.0", + "curve25519-dalek 3.0.0", + "ed25519", "rand", - "sha2", + "serde", + "sha2 0.9.1", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "log", + "regex", ] [[package]] @@ -286,6 +253,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -297,31 +274,12 @@ dependencies = [ "wasi", ] -[[package]] -name = "heck" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hex" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" - [[package]] name = "hex" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" -[[package]] -name = "imhamt" -version = "0.1.0" - [[package]] name = "itoa" version = "0.4.6" @@ -358,43 +316,60 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + [[package]] name = "opaque-debug" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "ppv-lite86" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" [[package]] name = "proc-macro2" -version = "0.4.30" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] -name = "proc-macro2" -version = "1.0.18" +name = "quickcheck" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ - "unicode-xid 0.2.1", + "env_logger", + "log", + "rand", + "rand_core 0.5.1", ] [[package]] -name = "quote" -version = "0.6.13" +name = "quickcheck_macros" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f" dependencies = [ - "proc-macro2 0.4.30", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -403,7 +378,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2 1.0.18", + "proc-macro2", ] [[package]] @@ -414,11 +389,21 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg", + "rand_core 0.3.1", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -486,6 +471,24 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + [[package]] name = "ryu" version = "1.0.5" @@ -515,33 +518,30 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.7.3", + "digest 0.8.1", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] -name = "sparse-array" -version = "0.1.0" - -[[package]] -name = "strum" -version = "0.15.0" +name = "sha2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f" +checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", +] [[package]] -name = "strum_macros" -version = "0.15.0" +name = "signature" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e" -dependencies = [ - "heck", - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", -] +checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210" [[package]] name = "subtle" @@ -551,29 +551,35 @@ checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" [[package]] name = "syn" -version = "0.15.44" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] -name = "syn" -version = "1.0.34" +name = "synstructure" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.7", - "unicode-xid 0.2.1", + "proc-macro2", + "quote", + "syn", + "unicode-xid", ] [[package]] -name = "typed-bytes" -version = "0.1.0" +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] [[package]] name = "typenum" @@ -581,23 +587,17 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -[[package]] -name = "unicode-segmentation" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" - [[package]] name = "unicode-xid" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] -name = "unicode-xid" -version = "0.2.1" +name = "version_check" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "wasi" @@ -626,9 +626,9 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.18", - "quote 1.0.7", - "syn 1.0.34", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-shared", ] @@ -638,7 +638,7 @@ version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8" dependencies = [ - "quote 1.0.7", + "quote", "wasm-bindgen-macro-support", ] @@ -648,9 +648,9 @@ version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75" dependencies = [ - "proc-macro2 1.0.18", - "quote 1.0.7", - "syn 1.0.34", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -688,3 +688,18 @@ name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index efd7d2e4..40f42d63 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,14 +11,16 @@ crate-type = ["cdylib", "rlib"] cryptoxide = "0.2.0" cbor_event = "2.1.3" wasm-bindgen = { version = "0.2", features=["serde-serialize"] } -chain-crypto = { path = "./chain-libs/chain-crypto" } -cardano-legacy-address = { path = "./chain-libs/cardano-legacy-address" } -chain-impl-mockchain = { path = "./chain-libs/chain-impl-mockchain" } -ed25519-bip32 = "^0.1.1" +curve25519-dalek = "1" +ed25519-dalek = "1.0.0-pre.1" +ed25519-bip32 = "0.3.1" +sha2 = "^0.8" +digest = "^0.8" bech32 = "0.7.2" hex = "0.4.0" js-sys = "0.3.24" rand_os = { version = "0.1", features = ["wasm-bindgen"] } +cfg-if = "0.1" # The default can't be compiled to wasm, so it's necessary to use either the 'nightly' # feature or this one @@ -27,3 +29,9 @@ clear_on_drop = { version = "0.2", features = ["no_cc"] } [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s" + +[dev-dependencies] +quickcheck = "0.9.2" +quickcheck_macros = "0.9.1" +rand_os = "0.1" +rand_chacha = "0.1" diff --git a/rust/chain-libs b/rust/chain-libs deleted file mode 160000 index 65c94944..00000000 --- a/rust/chain-libs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 65c949441ebfda7f343123df13d2a0220237f25a diff --git a/rust/src/address.rs b/rust/src/address.rs index 209a0e6a..7c4eab7b 100644 --- a/rust/src/address.rs +++ b/rust/src/address.rs @@ -1,6 +1,6 @@ use super::*; use bech32::ToBase32; -use cardano_legacy_address::ExtendedAddr; +use crate::legacy_address::ExtendedAddr; use ed25519_bip32::XPub; // returns (Number represented, bytes read) if valid encoding @@ -170,7 +170,7 @@ impl ByronAddress { // recall: in Byron mainnet, the network_id is omitted from the address to save a few bytes let mainnet_network_id = 764824073; // so here we return the mainnet id if none is found in the address - + match self.0.attributes.network_magic { // although mainnet should never be explicitly added, we check for it just in case Some(x) => if x == mainnet_network_id { 0b0001 } else { 0b000 }, @@ -191,7 +191,7 @@ impl ByronAddress { out.clone_from_slice(&key.as_bytes()); // need to ensure we use None for mainnet since Byron-era addresses omitted the network id - let mapped_network_id = if network == 0b0001 { None } else { Some(0b000 as u32) }; + let mapped_network_id = if network == 0b0001 { None } else { Some(0b000 as u32) }; ByronAddress(ExtendedAddr::new_simple(& XPub::from_bytes(out), mapped_network_id)) } @@ -208,7 +208,7 @@ impl ByronAddress { } pub fn from_address(addr: &Address) -> Option { - match &addr.0 { + match &addr.0 { AddrType::Byron(byron) => Some(byron.clone()), _ => None, } @@ -453,7 +453,7 @@ impl BaseAddress { } pub fn from_address(addr: &Address) -> Option { - match &addr.0 { + match &addr.0 { AddrType::Base(base) => Some(base.clone()), _ => None, } @@ -486,7 +486,7 @@ impl EnterpriseAddress { } pub fn from_address(addr: &Address) -> Option { - match &addr.0 { + match &addr.0 { AddrType::Enterprise(enterprise) => Some(enterprise.clone()), _ => None, } @@ -518,7 +518,7 @@ impl RewardAddress { } pub fn from_address(addr: &Address) -> Option { - match &addr.0 { + match &addr.0 { AddrType::Reward(reward) => Some(reward.clone()), _ => None, } @@ -606,7 +606,7 @@ impl PointerAddress { } pub fn from_address(addr: &Address) -> Option { - match &addr.0 { + match &addr.0 { AddrType::Ptr(ptr) => Some(ptr.clone()), _ => None, } diff --git a/rust/src/chain_core/abor.rs b/rust/src/chain_core/abor.rs new file mode 100644 index 00000000..69b6fedc --- /dev/null +++ b/rust/src/chain_core/abor.rs @@ -0,0 +1,344 @@ +/// ABOR Encoder +pub struct Encoder { + data: Vec, + hole: Vec<(usize, usize)>, + current_element: usize, +} + +/// ABOR Types +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Tag { + U8 = 1, + U16 = 2, + U32 = 3, + U64 = 4, + U128 = 5, + Bytes = 6, + Array = 7, +} + +impl Tag { + pub fn from_u8(v: u8) -> Option { + match v { + 1 => Some(Tag::U8), + 2 => Some(Tag::U16), + 3 => Some(Tag::U32), + 4 => Some(Tag::U64), + 5 => Some(Tag::U128), + 6 => Some(Tag::Bytes), + 7 => Some(Tag::Array), + _ => None, + } + } +} + +impl Encoder { + pub fn new() -> Self { + Encoder { + data: Vec::new(), + hole: Vec::new(), + current_element: 0, + } + } + + fn push_byte(self, u: u8) -> Self { + let mut v = self.data; + v.push(u); + Self { + data: v, + hole: self.hole, + current_element: self.current_element, + } + } + + fn push_tag(self, tag: Tag) -> Self { + self.push_byte(tag as u8) + } + + fn push_slice(self, s: &[u8]) -> Self { + let mut v = self.data; + v.extend_from_slice(s); + Self { + data: v, + hole: self.hole, + current_element: self.current_element, + } + } + + fn incr(self) -> Self { + Self { + data: self.data, + hole: self.hole, + current_element: self.current_element + 1, + } + } + + /// Add an unsigned byte + pub fn u8(self, u: u8) -> Self { + self.push_tag(Tag::U8).push_byte(u).incr() + } + + /// Add a 16-bit unsigned value in little endian format + pub fn u16(self, u: u16) -> Self { + self.push_tag(Tag::U16).push_slice(&u.to_le_bytes()).incr() + } + + /// Add a 32-bit unsigned value in little endian format + pub fn u32(self, u: u32) -> Self { + self.push_tag(Tag::U32).push_slice(&u.to_le_bytes()).incr() + } + + /// Add a 64-bit unsigned value in little endian format + pub fn u64(self, u: u64) -> Self { + self.push_tag(Tag::U64).push_slice(&u.to_le_bytes()).incr() + } + + /// Add a 128-bit unsigned value in little endian format + pub fn u128(self, u: u128) -> Self { + self.push_tag(Tag::U128).push_slice(&u.to_le_bytes()).incr() + } + + /// cannot serialize more than 256 bytes of contiguous data + pub fn bytes(self, bs: &[u8]) -> Self { + assert!(bs.len() < 256); + self.push_tag(Tag::Bytes) + .push_byte(bs.len() as u8) + .push_slice(&bs) + .incr() + } + + /// Array cannot contain more than 256 elements + pub fn struct_start(self) -> Self { + let mut d = self.push_tag(Tag::Array); + let mut h = d.hole; + let c = d.current_element + 1; + h.push((c, d.data.len())); + d.data.push(0xfe); // placeholder poison until struct end fill the actual size + Self { + data: d.data, + hole: h, + current_element: c, + } + } + + /// Terminate an array + pub fn struct_end(self) -> Self { + let mut h = self.hole; + match h.pop() { + None => panic!("unmatched end"), + Some((start_element, ofs)) => { + let mut v = self.data; + let nb_elements = self.current_element - start_element; + assert!(nb_elements < 256); + v[ofs] = nb_elements as u8; + Self { + data: v, + hole: h, + current_element: self.current_element, + } + } + } + } + + /// Finalize the encoder into an immutable array of data + pub fn finalize(self) -> Box<[u8]> { + assert_eq!(self.hole.len(), 0); + self.data.into() + } +} + +/// Create a decoder on some data +pub struct Decoder<'a> { + slice: &'a [u8], + //ctx: Vec, + //element: usize, +} + +#[derive(Debug, Clone)] +pub enum DecodeError { + EndOfStream, + StreamTooSmall { want: usize, has: usize }, + StreamPending { left: usize }, + TypeUnknown(u8), + TypeMismatch { got: Tag, expected: Tag }, +} + +impl<'a> Decoder<'a> { + #[must_use] + pub fn new(data: &'a [u8]) -> Self { + Decoder { slice: data } + } + + #[must_use] + fn pop(&mut self) -> Result { + if self.slice.len() > 0 { + let v = self.slice[0]; + self.slice = &self.slice[1..]; + Ok(v) + } else { + Err(DecodeError::EndOfStream) + } + } + + #[must_use] + fn expect_tag(&mut self, tag: Tag) -> Result<(), DecodeError> { + let t = self.pop()?; + match Tag::from_u8(t) { + None => Err(DecodeError::TypeUnknown(self.slice[0])), + Some(got) if got == tag => Ok(()), + Some(got) => Err(DecodeError::TypeMismatch { got, expected: tag }), + } + } + + #[must_use] + fn expect_size(&self, nb_bytes: usize) -> Result<(), DecodeError> { + if nb_bytes <= self.slice.len() { + Ok(()) + } else { + Err(DecodeError::StreamTooSmall { + want: nb_bytes, + has: self.slice.len(), + }) + } + } + + #[must_use] + fn expect_tag_size(&mut self, tag: Tag, nb_bytes: usize) -> Result<(), DecodeError> { + self.expect_tag(tag)?; + self.expect_size(nb_bytes) + } + + #[must_use] + pub fn array(&mut self) -> Result { + self.expect_tag_size(Tag::Array, 1)?; + let len = self.pop()?; + Ok(len as usize) + } + + #[must_use] + pub fn u8(&mut self) -> Result { + self.expect_tag_size(Tag::U8, 1)?; + let len = self.pop()?; + Ok(len) + } + + #[must_use] + pub fn u16(&mut self) -> Result { + self.expect_tag_size(Tag::U16, 2)?; + let v = { + let mut b = [0; 2]; + b.copy_from_slice(&self.slice[0..2]); + u16::from_le_bytes(b) + }; + self.slice = &self.slice[2..]; + Ok(v) + } + + #[must_use] + pub fn u32(&mut self) -> Result { + self.expect_tag_size(Tag::U32, 2)?; + let v = { + let mut b = [0; 4]; + b.copy_from_slice(&self.slice[0..4]); + u32::from_le_bytes(b) + }; + self.slice = &self.slice[4..]; + Ok(v) + } + + #[must_use] + pub fn u64(&mut self) -> Result { + self.expect_tag_size(Tag::U64, 8)?; + let v = { + let mut b = [0; 8]; + b.copy_from_slice(&self.slice[0..8]); + u64::from_le_bytes(b) + }; + self.slice = &self.slice[8..]; + Ok(v) + } + + #[must_use] + pub fn u128(&mut self) -> Result { + self.expect_tag_size(Tag::U128, 16)?; + let v = { + let mut b = [0; 16]; + b.copy_from_slice(&self.slice[0..16]); + u128::from_le_bytes(b) + }; + self.slice = &self.slice[16..]; + Ok(v) + } + + #[must_use] + pub fn bytes(&mut self) -> Result, DecodeError> { + self.expect_tag_size(Tag::Bytes, 1)?; + let len = self.pop()? as usize; + self.expect_size(len)?; + let mut v = Vec::with_capacity(len); + v.extend_from_slice(&self.slice[0..len]); + self.slice = &self.slice[len..]; + Ok(v.into()) + } + + #[must_use] + pub fn end(self) -> Result<(), DecodeError> { + if self.slice.len() == 0 { + Ok(()) + } else { + Err(DecodeError::StreamPending { + left: self.slice.len(), + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn serialize_unit1() { + let v = 0xf1235_fc; + let e = Encoder::new().u32(v).finalize(); + let mut d = Decoder::new(&e); + let ev = d.u32().unwrap(); + assert_eq!(d.end().is_ok(), true); + assert_eq!(v, ev) + } + + #[test] + pub fn serialize_unit2() { + let v1 = 10; + let v2 = 0x12345; + let v3 = 0xffeeddcc00112233; + let v4 = 0xffeeddcc0011223321490219480912; + let bs1 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + let e = Encoder::new() + .u16(v1) + .u32(v2) + .u64(v3) + .u128(v4) + .bytes(&bs1[..]) + .finalize(); + let mut d = Decoder::new(&e); + let ev1 = d.u16().unwrap(); + let ev2 = d.u32().unwrap(); + let ev3 = d.u64().unwrap(); + let ev4 = d.u128().unwrap(); + let ebs1 = d.bytes().unwrap(); + let is_end = d.end(); + assert_eq!(v1, ev1); + assert_eq!(v2, ev2); + assert_eq!(v3, ev3); + assert_eq!(v4, ev4); + assert_eq!(&bs1[..], &ebs1[..]); + assert_eq!( + is_end.is_ok(), + true, + "not reached end {:?}", + is_end.unwrap_err() + ); + } +} diff --git a/rust/src/chain_core/mempack.rs b/rust/src/chain_core/mempack.rs new file mode 100644 index 00000000..784c0ac9 --- /dev/null +++ b/rust/src/chain_core/mempack.rs @@ -0,0 +1,322 @@ +use std::error::Error; +use std::fmt; +use std::num::{NonZeroU32, NonZeroU64}; + +/// A local memory buffer to serialize data to +pub struct WriteBuf(Vec); + +impl WriteBuf { + pub fn new() -> Self { + WriteBuf(Vec::new()) + } + + pub fn put_u8(&mut self, v: u8) { + self.0.push(v) + } + pub fn put_u16(&mut self, v: u16) { + self.0.extend_from_slice(&v.to_be_bytes()) + } + pub fn put_u32(&mut self, v: u32) { + self.0.extend_from_slice(&v.to_be_bytes()) + } + pub fn put_u64(&mut self, v: u64) { + self.0.extend_from_slice(&v.to_be_bytes()) + } + pub fn put_u128(&mut self, v: u128) { + self.0.extend_from_slice(&v.to_be_bytes()) + } + pub fn put_bytes(&mut self, v: &[u8]) { + self.0.extend_from_slice(v) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ReadError { + /// Return the number of bytes left and the number of bytes demanded + NotEnoughBytes(usize, usize), + /// Data is left in the buffer + UnconsumedData(usize), + /// Expecting a size that is above the limit + SizeTooBig(usize, usize), + /// Structure of data is not what it should be + StructureInvalid(String), + /// Unknown enumeration tag + UnknownTag(u32), +} + +impl fmt::Display for ReadError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ReadError::NotEnoughBytes(left, demanded) => write!( + f, + "NotEnoughBytes: demanded {} bytes but got {}", + demanded, left + ), + ReadError::UnconsumedData(len) => write!(f, "Unconsumed data: {} bytes left", len), + ReadError::SizeTooBig(e, limit) => write!( + f, + "Ask for number of elements {} above expected limit value: {}", + e, limit + ), + ReadError::StructureInvalid(s) => write!(f, "Structure invalid: {}", s), + ReadError::UnknownTag(t) => write!(f, "Unknown tag: {}", t), + } + } +} + +impl Error for ReadError {} + +/// A local memory slice to read from memory +pub struct ReadBuf<'a> { + offset: usize, + data: &'a [u8], + //trace: Vec<(usize, String)>, +} + +impl<'a> ReadBuf<'a> { + /// Create a readbuf from a slice + pub fn from(slice: &'a [u8]) -> Self { + ReadBuf { + offset: 0, + data: slice, + //trace: Vec::new(), + } + } + + pub fn position(&self) -> usize { + self.offset + } + + fn left(&self) -> usize { + self.data.len() - self.offset + } + + fn assure_size(&self, expected: usize) -> Result<(), ReadError> { + let left = self.left(); + if left >= expected { + Ok(()) + } else { + dbg!(self.debug()); + Err(ReadError::NotEnoughBytes(left, expected)) + } + } + + /// Check if everything has been properly consumed + pub fn expect_end(&mut self) -> Result<(), ReadError> { + let l = self.left(); + if l == 0 { + Ok(()) + } else { + Err(ReadError::UnconsumedData(l)) + } + } + + /// Check if we reach the end of the buffer + pub fn is_end(&self) -> bool { + self.left() == 0 + } + + /// Skip a number of bytes from the buffer. + pub fn skip_bytes(&mut self, sz: usize) -> Result<(), ReadError> { + self.assure_size(sz)?; + self.offset += sz; + Ok(()) + } + + /// Return a slice of the next bytes from the buffer + pub fn get_slice(&mut self, sz: usize) -> Result<&'a [u8], ReadError> { + self.assure_size(sz)?; + let s = &self.data[self.offset..self.offset + sz]; + self.offset += sz; + Ok(s) + } + + pub fn get_slice_end(&mut self) -> &'a [u8] { + let s = &self.data[self.offset..]; + self.offset = self.data.len(); + s + } + + pub fn into_slice_mut(&mut self, slice: &mut [u8]) -> Result<(), ReadError> { + let s = self.get_slice(slice.len())?; + slice.copy_from_slice(s); + Ok(()) + } + + /// Return a sub-buffer ending at the given byte offset + pub fn split_to(&mut self, sz: usize) -> Result, ReadError> { + let slice = self.get_slice(sz)?; + Ok(ReadBuf::from(slice)) + } + + /// Peek at the next u8 from the buffer. the cursor is **not** advanced to the next byte. + pub fn peek_u8(&mut self) -> Result { + self.assure_size(1)?; + let v = self.data[self.offset]; + Ok(v) + } + + /// Return the next u8 from the buffer + pub fn get_u8(&mut self) -> Result { + self.assure_size(1)?; + let v = self.data[self.offset]; + self.offset += 1; + Ok(v) + } + + /// Return the next u16 from the buffer + pub fn get_u16(&mut self) -> Result { + const SIZE: usize = 2; + let mut buf = [0u8; SIZE]; + buf.copy_from_slice(self.get_slice(SIZE)?); + Ok(u16::from_be_bytes(buf)) + } + + /// Return the next u32 from the buffer + pub fn get_u32(&mut self) -> Result { + const SIZE: usize = 4; + let mut buf = [0u8; SIZE]; + buf.copy_from_slice(self.get_slice(SIZE)?); + Ok(u32::from_be_bytes(buf)) + } + + pub fn get_nz_u32(&mut self) -> Result { + let v = self.get_u32()?; + NonZeroU32::new(v).ok_or(ReadError::StructureInvalid("received zero u32".to_string())) + } + + /// Return the next u64 from the buffer + pub fn get_u64(&mut self) -> Result { + const SIZE: usize = 8; + let mut buf = [0u8; SIZE]; + buf.copy_from_slice(self.get_slice(SIZE)?); + Ok(u64::from_be_bytes(buf)) + } + + pub fn get_nz_u64(&mut self) -> Result { + let v = self.get_u64()?; + NonZeroU64::new(v).ok_or(ReadError::StructureInvalid("received zero u64".to_string())) + } + + /// Return the next u128 from the buffer + pub fn get_u128(&mut self) -> Result { + const SIZE: usize = 16; + let mut buf = [0u8; SIZE]; + buf.copy_from_slice(self.get_slice(SIZE)?); + Ok(u128::from_be_bytes(buf)) + } + + /* + pub fn trace(&mut self, s: &str) { + self.trace.push((self.offset, s.to_string())) + } + */ + + pub fn debug(&self) -> String { + let mut s = String::new(); + for (i, x) in self.data.iter().enumerate() { + //self.trace.iter().find(|(ofs,_)| ofs == &i).map(|(_,name)| { s.push_str(&name); s.push(' ') }); + if i == self.offset { + s.push_str(&".. "); + } + let bytes = format!("{:02x} ", x); + s.push_str(&bytes); + } + s + } +} + +pub trait Readable: Sized { + fn read<'a>(buf: &mut ReadBuf<'a>) -> Result; + + fn read_validate<'a>(buf: &mut ReadBuf<'a>) -> Result<(), ReadError> { + Self::read(buf).map(|_| ()) + } +} + +impl Readable for () { + fn read<'a>(_: &mut ReadBuf<'a>) -> Result<(), ReadError> { + Ok(()) + } + fn read_validate<'a>(buf: &mut ReadBuf<'a>) -> Result<(), ReadError> { + Self::read(buf) + } +} + +macro_rules! read_prim_impl { + ($Ty: ty, $meth: ident) => { + impl Readable for $Ty { + fn read<'a>(buf: &mut ReadBuf<'a>) -> Result { + buf.$meth() + } + } + }; +} + +read_prim_impl! { u8, get_u8 } +read_prim_impl! { u16, get_u16 } +read_prim_impl! { u32, get_u32 } +read_prim_impl! { u64, get_u64 } +read_prim_impl! { u128, get_u128 } + +macro_rules! read_array_impls { + ($($N: expr)+) => { + $( + impl Readable for [u8; $N] { + fn read<'a>(readbuf: &mut ReadBuf<'a>) -> Result { + let mut buf = [0u8; $N]; + buf.copy_from_slice(readbuf.get_slice($N)?); + Ok(buf) + } + } + )+ + }; +} + +read_array_impls! { + 4 8 12 16 20 24 28 32 64 96 128 +} + +/// read N times for a T elements in sequences +pub fn read_vec<'a, T: Readable>(readbuf: &mut ReadBuf<'a>, n: usize) -> Result, ReadError> { + let mut v = Vec::with_capacity(n); + for _ in 0..n { + let t = T::read(readbuf)?; + v.push(t) + } + Ok(v) +} + +/// Fill a mutable slice with as many T as filling requires +pub fn read_mut_slice<'a, T: Readable>( + readbuf: &mut ReadBuf<'a>, + v: &mut [T], +) -> Result<(), ReadError> { + for i in 0..v.len() { + let t = T::read(readbuf)?; + v[i] = t + } + Ok(()) +} + +/// Transform a raw buffer into a Header +pub fn read_from_raw(raw: &[u8]) -> Result { + let mut rbuf = ReadBuf::from(raw); + match T::read(&mut rbuf) { + Err(e) => { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("invalid data {:?} {:?}", e, raw).to_owned(), + )); + } + Ok(h) => match rbuf.expect_end() { + Err(e) => { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("end of data {:?}", e).to_owned(), + )); + } + Ok(()) => Ok(h), + }, + } +} diff --git a/rust/src/chain_core/mod.rs b/rust/src/chain_core/mod.rs new file mode 100644 index 00000000..ea08dc4b --- /dev/null +++ b/rust/src/chain_core/mod.rs @@ -0,0 +1,12 @@ +cfg_if! { + if #[cfg(test)] { + extern crate quickcheck; + } else if #[cfg(feature = "property-test-api")] { + extern crate quickcheck; + } +} + +pub mod abor; +pub mod mempack; +pub mod packer; +pub mod property; diff --git a/rust/src/chain_core/packer.rs b/rust/src/chain_core/packer.rs new file mode 100644 index 00000000..5b166440 --- /dev/null +++ b/rust/src/chain_core/packer.rs @@ -0,0 +1,190 @@ +//! Tooling for packing and unpacking from streams +//! +//! This will allow us to expose some standard way of serializing +//! data. + +const INITIAL_BUFFERED_CAPACITY: usize = 2048; + +pub struct Codec(I); +impl Codec { + pub fn new(inner: I) -> Self { + Codec(inner) + } + + pub fn into_inner(self) -> I { + self.0 + } +} + +pub struct Buffered(I, Codec>); + +pub struct Hole { + _marker: std::marker::PhantomData, + start: usize, + end: usize, +} + +impl Codec { + #[inline] + pub fn get_u8(&mut self) -> std::io::Result { + let mut buf = [0u8; 1]; + self.0.read_exact(&mut buf)?; + Ok(buf[0]) + } + #[inline] + pub fn get_u16(&mut self) -> std::io::Result { + let mut buf = [0u8; 2]; + self.0.read_exact(&mut buf)?; + Ok(u16::from_be_bytes(buf)) + } + #[inline] + pub fn get_u32(&mut self) -> std::io::Result { + let mut buf = [0u8; 4]; + self.0.read_exact(&mut buf)?; + Ok(u32::from_be_bytes(buf)) + } + #[inline] + pub fn get_u64(&mut self) -> std::io::Result { + let mut buf = [0u8; 8]; + self.0.read_exact(&mut buf)?; + Ok(u64::from_be_bytes(buf)) + } + #[inline] + pub fn get_u128(&mut self) -> std::io::Result { + let mut buf = [0u8; 16]; + self.0.read_exact(&mut buf)?; + Ok(u128::from_be_bytes(buf)) + } + #[inline] + pub fn get_bytes(&mut self, n: usize) -> std::io::Result> { + let mut buf = vec![0u8; n]; + self.0.read_exact(&mut buf)?; + Ok(buf) + } +} +impl Codec { + #[inline] + pub fn buffered(self) -> Buffered { + Buffered(self.0, Codec(Vec::with_capacity(INITIAL_BUFFERED_CAPACITY))) + } + + #[inline] + pub fn put_u8(&mut self, v: u8) -> std::io::Result<()> { + self.0.write_all(&[v]) + } + #[inline] + pub fn put_u16(&mut self, v: u16) -> std::io::Result<()> { + self.0.write_all(&v.to_be_bytes()) + } + #[inline] + pub fn put_u32(&mut self, v: u32) -> std::io::Result<()> { + self.0.write_all(&v.to_be_bytes()) + } + #[inline] + pub fn put_u64(&mut self, v: u64) -> std::io::Result<()> { + self.0.write_all(&v.to_be_bytes()) + } + #[inline] + pub fn put_u128(&mut self, v: u128) -> std::io::Result<()> { + self.0.write_all(&v.to_be_bytes()) + } +} +impl Buffered { + #[inline] + pub fn hole(&mut self, len: usize) -> std::io::Result> { + use std::io::Write; + let start = (self.1).0.len(); + let end = start + len; + let buf = vec![0; len]; + self.write_all(&buf)?; + Ok(Hole { + _marker: std::marker::PhantomData, + start: start, + end: end, + }) + } + + #[inline] + pub fn into_inner(self) -> std::io::Result> { + let mut codec = Codec(self.0); + let buffer = (self.1).0; + codec.0.write_all(&buffer)?; + Ok(codec) + } + + #[inline] + pub fn fill_hole_u8(&mut self, hole: Hole, value: u8) { + (self.1).0[hole.start..hole.end].copy_from_slice(&[value]) + } + #[inline] + pub fn fill_hole_u16(&mut self, hole: Hole, value: u16) { + (self.1).0[hole.start..hole.end].copy_from_slice(&value.to_be_bytes()) + } + #[inline] + pub fn fill_hole_u32(&mut self, hole: Hole, value: u32) { + (self.1).0[hole.start..hole.end].copy_from_slice(&value.to_be_bytes()) + } + #[inline] + pub fn fill_hole_u64(&mut self, hole: Hole, value: u64) { + (self.1).0[hole.start..hole.end].copy_from_slice(&value.to_be_bytes()) + } + #[inline] + pub fn fill_hole_u128(&mut self, hole: Hole, value: u128) { + (self.1).0[hole.start..hole.end].copy_from_slice(&value.to_be_bytes()) + } + + #[inline] + pub fn buffered_len(&self) -> usize { + (self.1).0.len() + } +} + +impl std::io::Read for Codec { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.read(buf) + } +} +impl std::io::BufRead for Codec
{ + #[inline] + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + self.0.fill_buf() + } + #[inline] + fn consume(&mut self, amt: usize) { + self.0.consume(amt) + } +} +impl std::io::Write for Codec { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf) + } + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } +} +impl std::io::Write for Buffered { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.1.write(buf) + } + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + self.1.flush() + } +} +impl std::ops::Deref for Buffered { + type Target = Codec>; + #[inline] + fn deref(&self) -> &Self::Target { + &self.1 + } +} +impl std::ops::DerefMut for Buffered { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.1 + } +} diff --git a/rust/src/chain_core/property.rs b/rust/src/chain_core/property.rs new file mode 100644 index 00000000..33870653 --- /dev/null +++ b/rust/src/chain_core/property.rs @@ -0,0 +1,384 @@ +//! chain core properties +//! +//! define the different properties a _supported_ chain needs to +//! implement to work in our models. +//! +//! # Block +//! +//! The Block is the atomic element that compose a chain. Or in other +//! words the chain is composed of a succession of `Block`. +//! +//! the `Block` trait implements the necessary feature we expect of +//! a `Block` in the chain. Having a function that requires the object +//! to implement the Block traits means that we are expecting to have +//! only access to: +//! +//! * the block and its parent's identifier (the block hash); +//! * the block number, its position in the blockchain relative +//! to the beginning of the chain. We often call this number +//! the block Date. +//! +//! # Ledger +//! +//! this trait is to make sure we are following the Transactions of the chain +//! appropriately. +//! +//! # LeaderSelection +//! +//! This trait is following the protocol of the blockchain is followed +//! properly and determined a given instance of the LeaderSelection object +//! is selected to write a block in the chain. +//! + +use std::{fmt::Debug, hash::Hash}; + +/// Trait identifying the block identifier type. +pub trait BlockId: Eq + Ord + Clone + Debug + Hash + Serialize + Deserialize { + /// A special ID used to denote a non-existent block (e.g. the + /// parent of the first block). + fn zero() -> Self; +} + +/// A trait representing block dates. +pub trait BlockDate: Eq + Ord + Clone { + fn from_epoch_slot_id(epoch: u32, slot_id: u32) -> Self; +} + +pub trait ChainLength: Eq + Ord + Clone + Debug { + fn next(&self) -> Self; +} + +/// Trait identifying the transaction identifier type. +pub trait TransactionId: Eq + Hash + Debug {} + +/// Trait identifying the block header type. +pub trait Header: Serialize { + /// The block header id. + type Id: BlockId; + + /// The block date. + type Date: BlockDate; + + /// the length of the blockchain (number of blocks) + type ChainLength: ChainLength; + + /// the type associated to the version of a block + type Version; + + /// Retrieves the block's header id. + fn id(&self) -> Self::Id; + + /// get the parent block identifier (the previous block in the + /// blockchain). + fn parent_id(&self) -> Self::Id; + + /// Retrieves the block's date. + fn date(&self) -> Self::Date; + + /// access the version of a given block + fn version(&self) -> Self::Version; + + /// get the block's chain length. The number of block + /// created following this thread of blocks on the blockchain + /// (including Self). + fn chain_length(&self) -> Self::ChainLength; +} + +/// Block property +/// +/// a block is part of a chain of block called Blockchain. +/// the chaining is done via one block pointing to another block, +/// the parent block (the previous block). +/// +/// This means that a blockchain is a link-list, ordered from the most +/// recent block to the furthest/oldest block. +/// +/// The Oldest block is called the Genesis Block. +pub trait Block: Serialize + Deserialize { + /// the Block identifier. It must be unique. This mean that + /// 2 different blocks have 2 different identifiers. + /// + /// In bitcoin this block is a SHA2 256bits. For Cardano's + /// blockchain it is Blake2b 256bits. + type Id: BlockId; + + /// the block date (also known as a block number) represents the + /// absolute position of the block in the chain. This can be used + /// for random access (if the storage algorithm allows it) or for + /// identifying the position of a block in a given epoch or era. + type Date: BlockDate; + + /// the type associated to the version of a block + type Version; + + /// the length of the blockchain (number of blocks) + type ChainLength: ChainLength; + + /// return the Block's identifier. + fn id(&self) -> Self::Id; + + /// get the parent block identifier (the previous block in the + /// blockchain). + fn parent_id(&self) -> Self::Id; + + /// get the block date of the block + fn date(&self) -> Self::Date; + + /// access the version of a given block + fn version(&self) -> Self::Version; + + /// get the block's chain length. The number of block + /// created following this thread of blocks on the blockchain + /// (including Self). + fn chain_length(&self) -> Self::ChainLength; +} + +/// Access to the block header. +/// +/// If featured by the blockchain, the header can be used to transmit +/// block's metadata via a network protocol or in other uses where the +/// full content of the block is too bulky and not necessary. +pub trait HasHeader { + /// The block header type. + type Header: Header; + + /// Retrieves the block's header. + fn header(&self) -> Self::Header; +} + +/// Trait identifying the fragment identifier type. +pub trait FragmentId: Eq + Hash + Clone + Debug + Serialize + Deserialize {} + +/// A fragment is some item contained in a block, such as a +/// transaction, a delegation-related certificate, an update proposal, +/// and so on. Fragments can be serialized (so that they can be +/// concatenated to form a binary block( and have a unique ID +/// (typically the hash of their serialization). +pub trait Fragment: Serialize + Deserialize { + type Id: FragmentId; + + /// Return the message's identifier. + fn id(&self) -> Self::Id; +} + +/// Accessor to fragments within a block. +/// +/// This trait has a lifetime parameter and is normally implemented by +/// reference types. +pub trait HasFragments<'a> { + /// The type representing fragments in this block. + type Fragment: 'a + Fragment; + + /// A by-reference iterator over block's fragments. + type Fragments: 'a + Iterator; + + /// Returns a by-reference iterator over the fragments in the block. + fn fragments(self) -> Self::Fragments; +} + +/// define a transaction within the blockchain. This transaction can be used +/// for the UTxO model. However it can also be used for any other elements that +/// the blockchain has (a transaction type to add Stacking Pools and so on...). +/// +pub trait Transaction: Serialize + Deserialize { + /// The input type of the transaction (if none use `()`). + type Input; + /// The output type of the transaction (if none use `()`). + type Output; + /// The iterable type of transaction inputs (if none use `Option<()>` and return `None`). + type Inputs: ?Sized; + /// The iterable type of transaction outputs (if none use `Option<()>` and return `None`). + type Outputs: ?Sized; + + /// Returns a reference that can be used to iterate over transaction's inputs. + fn inputs(&self) -> &Self::Inputs; + + /// Returns a reference that can be used to iterate over transaction's outputs. + fn outputs(&self) -> &Self::Outputs; +} + +pub trait State: Sized + Clone { + type Error: std::error::Error; + type Header: Header; + type Content: Fragment; + + /// yield a new block in the state + /// + /// This will change the state in the sense that it acknowledge the creation + /// of a new block in its internal state. + fn apply_block<'a, I>(&self, header: &Self::Header, contents: I) -> Result + where + I: IntoIterator, + Self::Content: 'a; + + /// apply new block contents. This modify the state in small steps + /// however it does not acknowledge the creation of a new block + fn apply_contents<'a, I>(&self, contents: I) -> Result + where + I: IntoIterator, + Self::Content: 'a; +} + +/// Define the Ledger side of the blockchain. This is not really on the blockchain +/// but should be able to maintain a valid state of the overall blockchain at a given +/// `Block`. +pub trait Ledger: Sized { + /// Ledger's errors + type Error: std::error::Error; + + fn input<'a, I>( + &'a self, + input: ::Input, + ) -> Result<&'a ::Output, Self::Error>; +} + +/// Trait identifying the leader identifier type. +pub trait LeaderId: Eq + Clone + Hash + Debug {} + +/// interface for the leader selection algorithm +/// +/// this is the interface that is responsible to verify the Block are +/// created by the right Leaders (i.e. that everyone follows the +/// consensus algorithm). +/// +/// This is also the same interface that is used to detect if we are the +/// leader for the block at the given date. +pub trait LeaderSelection { + /// the block that we will get the information from + type Block: Block; + + /// Leader Selection error type + type Error: std::error::Error; + + /// Identifier of the leader (e.g. a public key). + type LeaderId: LeaderId; + + type State: State; + + fn retrieve(state: &Self::State) -> Self; + + /// return the ID of the leader of the blockchain at the given + /// date. + fn get_leader_at( + &self, + date: ::Date, + ) -> Result; +} + +/// the settings of the blockchain this is something that can be used to maintain +/// the blockchain protocol update details: +/// +pub trait Settings { + type Block: Block; + + /// return the tip of the current branch + /// + fn tip(&self) -> ::Id; + + /// the current chain_length + fn chain_length(&self) -> ::ChainLength; + + /// the number of transactions in a block + fn max_number_of_transactions_per_block(&self) -> u32; + + /// the block version format + fn block_version(&self) -> ::Version; +} + +/// Define that an object can be written to a `Write` object. +pub trait Serialize { + type Error: std::error::Error + From; + + fn serialize(&self, writer: W) -> Result<(), Self::Error>; + + /// Convenience method to serialize into a byte vector. + fn serialize_as_vec(&self) -> Result, Self::Error> { + let mut data = vec![]; + self.serialize(&mut data)?; + Ok(data) + } +} + +/// Define that an object can be read from a `Read` object. +pub trait Deserialize: Sized { + type Error: std::error::Error + From + Send + Sync + 'static; + + fn deserialize(reader: R) -> Result; +} + +/// Defines the way to parse the object from a UTF-8 string. +/// +/// This is like the standard `FromStr` trait, except that it imposes +/// additional bounds on the error type to make it more usable for +/// aggregation to higher level errors and passing between threads. +pub trait FromStr: Sized { + type Error: std::error::Error + Send + Sync + 'static; + + fn from_str(s: &str) -> Result; +} + +impl FromStr for T +where + T: std::str::FromStr, + ::Err: std::error::Error + Send + Sync + 'static, +{ + type Error = ::Err; + + fn from_str(s: &str) -> Result { + std::str::FromStr::from_str(s) + } +} + +impl Serialize for &T { + type Error = T::Error; + + fn serialize(&self, writer: W) -> Result<(), T::Error> { + (**self).serialize(writer) + } +} + +#[cfg(feature = "property-test-api")] +pub mod testing { + use super::super::mempack::{ReadBuf, Readable}; + use super::*; + use quickcheck::{Arbitrary, TestResult}; + + /// test that any arbitrary given object can serialize and deserialize + /// back into itself (i.e. it is a bijection, or a one to one match + /// between the serialized bytes and the object) + pub fn serialization_bijection(t: T) -> TestResult + where + T: Arbitrary + Serialize + Deserialize + Eq, + { + let vec = match t.serialize_as_vec() { + Err(error) => return TestResult::error(format!("serialization: {}", error)), + Ok(v) => v, + }; + let decoded_t = match T::deserialize(&vec[..]) { + Err(error) => return TestResult::error(format!("deserialization: {}", error)), + Ok(v) => v, + }; + TestResult::from_bool(decoded_t == t) + } + + /// test that any arbitrary given object can serialize and deserialize + /// back into itself (i.e. it is a bijection, or a one to one match + /// between the serialized bytes and the object) + pub fn serialization_bijection_r(t: T) -> TestResult + where + T: Arbitrary + Serialize + Readable + Eq, + { + let vec = match t.serialize_as_vec() { + Err(error) => return TestResult::error(format!("serialization: {}", error)), + Ok(v) => v, + }; + let mut buf = ReadBuf::from(&vec); + let decoded_t = match T::read(&mut buf) { + Err(error) => { + return TestResult::error(format!("deserialization: {:?}\n{}", error, buf.debug())) + } + Ok(v) => v, + }; + TestResult::from_bool(buf.expect_end().is_ok() && decoded_t == t) + } +} diff --git a/rust/src/chain_crypto/algorithms/ed25519.rs b/rust/src/chain_crypto/algorithms/ed25519.rs new file mode 100644 index 00000000..6f0a2061 --- /dev/null +++ b/rust/src/chain_crypto/algorithms/ed25519.rs @@ -0,0 +1,145 @@ +use crate::chain_crypto::key::{ + AsymmetricKey, AsymmetricPublicKey, PublicKeyError, SecretKeyError, SecretKeySizeStatic, +}; +use crate::chain_crypto::sign::{SignatureError, SigningAlgorithm, Verification, VerificationAlgorithm}; +use cryptoxide::ed25519; +use rand_os::rand_core::{CryptoRng, RngCore}; + +use ed25519_bip32::XPub; + +/// ED25519 Signing Algorithm +pub struct Ed25519; + +#[derive(Clone)] +pub struct Priv([u8; ed25519::SEED_LENGTH]); + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Pub(pub(crate) [u8; ed25519::PUBLIC_KEY_LENGTH]); + +#[derive(Clone)] +pub struct Sig(pub(crate) [u8; ed25519::SIGNATURE_LENGTH]); + +impl Pub { + pub fn from_xpub(xpub: &XPub) -> Self { + let mut buf = [0; ed25519::PUBLIC_KEY_LENGTH]; + xpub.get_without_chaincode(&mut buf); + Pub(buf) + } +} + +impl AsRef<[u8]> for Priv { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsRef<[u8]> for Pub { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsRef<[u8]> for Sig { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsymmetricPublicKey for Ed25519 { + type Public = Pub; + + const PUBLIC_BECH32_HRP: &'static str = "ed25519_pk"; + const PUBLIC_KEY_SIZE: usize = ed25519::PUBLIC_KEY_LENGTH; + + fn public_from_binary(data: &[u8]) -> Result { + if data.len() != ed25519::PUBLIC_KEY_LENGTH { + return Err(PublicKeyError::SizeInvalid); + } + let mut buf = [0; ed25519::PUBLIC_KEY_LENGTH]; + buf[0..ed25519::PUBLIC_KEY_LENGTH].clone_from_slice(data); + Ok(Pub(buf)) + } +} + +impl AsymmetricKey for Ed25519 { + type Secret = Priv; + type PubAlg = Ed25519; + + const SECRET_BECH32_HRP: &'static str = "ed25519_sk"; + + fn generate(mut rng: T) -> Self::Secret { + let mut priv_bytes = [0u8; ed25519::SEED_LENGTH]; + rng.fill_bytes(&mut priv_bytes); + Priv(priv_bytes) + } + + fn compute_public(key: &Self::Secret) -> ::Public { + let (_, pk) = ed25519::keypair(&key.0); + Pub(pk) + } + + fn secret_from_binary(data: &[u8]) -> Result { + if data.len() != ed25519::SEED_LENGTH { + return Err(SecretKeyError::SizeInvalid); + } + let mut buf = [0; ed25519::SEED_LENGTH]; + buf[0..ed25519::SEED_LENGTH].clone_from_slice(data); + Ok(Priv(buf)) + } +} + +impl SecretKeySizeStatic for Ed25519 { + const SECRET_KEY_SIZE: usize = ed25519::SEED_LENGTH; +} + +impl VerificationAlgorithm for Ed25519 { + type Signature = Sig; + + const SIGNATURE_SIZE: usize = ed25519::SIGNATURE_LENGTH; + const SIGNATURE_BECH32_HRP: &'static str = "ed25519_sig"; + + fn signature_from_bytes(data: &[u8]) -> Result { + if data.len() != ed25519::SIGNATURE_LENGTH { + return Err(SignatureError::SizeInvalid { + expected: ed25519::SIGNATURE_LENGTH, + got: data.len(), + }); + } + let mut buf = [0; ed25519::SIGNATURE_LENGTH]; + buf[0..ed25519::SIGNATURE_LENGTH].clone_from_slice(data); + Ok(Sig(buf)) + } + + fn verify_bytes( + pubkey: &Self::Public, + signature: &Self::Signature, + msg: &[u8], + ) -> Verification { + ed25519::verify(msg, &pubkey.0, signature.as_ref()).into() + } +} + +impl SigningAlgorithm for Ed25519 { + fn sign(key: &Self::Secret, msg: &[u8]) -> Sig { + let (sk, _) = ed25519::keypair(&key.0); + Sig(ed25519::signature(msg, &sk)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::chain_crypto::key::KeyPair; + use crate::chain_crypto::sign::test::{keypair_signing_ko, keypair_signing_ok}; + + #[quickcheck] + fn sign_ok(input: (KeyPair, Vec)) -> bool { + keypair_signing_ok(input) + } + + #[quickcheck] + fn sign_ko(input: (KeyPair, KeyPair, Vec)) -> bool { + keypair_signing_ko(input) + } +} diff --git a/rust/src/chain_crypto/algorithms/ed25519_derive.rs b/rust/src/chain_crypto/algorithms/ed25519_derive.rs new file mode 100644 index 00000000..ed946108 --- /dev/null +++ b/rust/src/chain_crypto/algorithms/ed25519_derive.rs @@ -0,0 +1,121 @@ +use crate::chain_crypto::key::{ + AsymmetricKey, AsymmetricPublicKey, PublicKeyError, SecretKeyError, SecretKeySizeStatic, +}; +use crate::chain_crypto::sign::{SignatureError, SigningAlgorithm, Verification, VerificationAlgorithm}; + +use ed25519_bip32 as i; +use ed25519_bip32::{XPrv, XPub, XPRV_SIZE, XPUB_SIZE}; +use rand_os::rand_core::{CryptoRng, RngCore}; + +/// Ed25519 BIP32 Signature algorithm +pub struct Ed25519Bip32; + +impl From for SecretKeyError { + fn from(v: i::PrivateKeyError) -> Self { + match v { + i::PrivateKeyError::HighestBitsInvalid => SecretKeyError::StructureInvalid, + i::PrivateKeyError::LowestBitsInvalid => SecretKeyError::StructureInvalid, + i::PrivateKeyError::LengthInvalid(_) => SecretKeyError::SizeInvalid, + } + } +} + +impl From for PublicKeyError { + fn from(v: i::PublicKeyError) -> Self { + match v { + i::PublicKeyError::LengthInvalid(_) => PublicKeyError::SizeInvalid, + } + } +} + +impl AsymmetricPublicKey for Ed25519Bip32 { + type Public = XPub; + const PUBLIC_BECH32_HRP: &'static str = "xpub"; + const PUBLIC_KEY_SIZE: usize = XPUB_SIZE; + fn public_from_binary(data: &[u8]) -> Result { + let xpub = XPub::from_slice(data)?; + Ok(xpub) + } +} + +impl AsymmetricKey for Ed25519Bip32 { + type Secret = XPrv; + type PubAlg = Ed25519Bip32; + + const SECRET_BECH32_HRP: &'static str = "xprv"; + + fn generate(mut rng: T) -> Self::Secret { + let mut priv_bytes = [0u8; XPRV_SIZE]; + rng.fill_bytes(&mut priv_bytes); + XPrv::normalize_bytes_force3rd(priv_bytes) + } + + fn compute_public(key: &Self::Secret) -> ::Public { + key.public() + } + + fn secret_from_binary(data: &[u8]) -> Result { + let xprv = XPrv::from_slice_verified(data)?; + Ok(xprv) + } +} + +impl SecretKeySizeStatic for Ed25519Bip32 { + const SECRET_KEY_SIZE: usize = XPRV_SIZE; +} + +impl From for SignatureError { + fn from(v: i::SignatureError) -> Self { + match v { + i::SignatureError::InvalidLength(got) => SignatureError::SizeInvalid { + expected: ed25519_bip32::SIGNATURE_SIZE, + got: got, + }, + } + } +} + +type XSig = ed25519_bip32::Signature; + +impl VerificationAlgorithm for Ed25519Bip32 { + type Signature = XSig; + + const SIGNATURE_SIZE: usize = ed25519_bip32::SIGNATURE_SIZE; + const SIGNATURE_BECH32_HRP: &'static str = "xsig"; + + fn signature_from_bytes(data: &[u8]) -> Result { + let xsig = XSig::from_slice(data)?; + Ok(xsig) + } + + fn verify_bytes( + pubkey: &Self::Public, + signature: &Self::Signature, + msg: &[u8], + ) -> Verification { + pubkey.verify(msg, signature).into() + } +} + +impl SigningAlgorithm for Ed25519Bip32 { + fn sign(key: &Self::Secret, msg: &[u8]) -> XSig { + key.sign(msg) + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::chain_crypto::key::KeyPair; + use crate::chain_crypto::sign::test::{keypair_signing_ko, keypair_signing_ok}; + + #[quickcheck] + fn sign_ok(input: (KeyPair, Vec)) -> bool { + keypair_signing_ok(input) + } + #[quickcheck] + fn sign_ko(input: (KeyPair, KeyPair, Vec)) -> bool { + keypair_signing_ko(input) + } +} diff --git a/rust/src/chain_crypto/algorithms/ed25519_extended.rs b/rust/src/chain_crypto/algorithms/ed25519_extended.rs new file mode 100644 index 00000000..b9abdb56 --- /dev/null +++ b/rust/src/chain_crypto/algorithms/ed25519_extended.rs @@ -0,0 +1,89 @@ +use crate::chain_crypto::key::{AsymmetricKey, AsymmetricPublicKey, SecretKeyError, SecretKeySizeStatic}; +use crate::chain_crypto::sign::SigningAlgorithm; + +use super::ed25519 as ei; + +use cryptoxide::ed25519; +use rand_os::rand_core::{CryptoRng, RngCore}; + +use ed25519_bip32::{XPrv, XPRV_SIZE}; + +/// ED25519 Signing Algorithm with extended secret key +pub struct Ed25519Extended; + +#[derive(Clone)] +pub struct ExtendedPriv([u8; ed25519::PRIVATE_KEY_LENGTH]); + +impl AsRef<[u8]> for ExtendedPriv { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl ExtendedPriv { + pub fn from_xprv(xprv: &XPrv) -> Self { + let mut buf = [0; ed25519::PRIVATE_KEY_LENGTH]; + xprv.get_extended_mut(&mut buf); + ExtendedPriv(buf) + } +} + +impl AsymmetricKey for Ed25519Extended { + type Secret = ExtendedPriv; + type PubAlg = ei::Ed25519; + + const SECRET_BECH32_HRP: &'static str = "ed25519e_sk"; + + fn generate(mut rng: T) -> Self::Secret { + let mut priv_bytes = [0u8; XPRV_SIZE]; + rng.fill_bytes(&mut priv_bytes); + let xprv = XPrv::normalize_bytes_force3rd(priv_bytes); + + let mut out = [0u8; ed25519::PRIVATE_KEY_LENGTH]; + xprv.get_extended_mut(&mut out); + ExtendedPriv(out) + } + + fn compute_public(key: &Self::Secret) -> ::Public { + let pk = ed25519::to_public(&key.0); + ei::Pub(pk) + } + + fn secret_from_binary(data: &[u8]) -> Result { + if data.len() != ed25519::PRIVATE_KEY_LENGTH { + return Err(SecretKeyError::SizeInvalid); + } + let mut buf = [0; ed25519::PRIVATE_KEY_LENGTH]; + buf[0..ed25519::PRIVATE_KEY_LENGTH].clone_from_slice(data); + // TODO structure check + Ok(ExtendedPriv(buf)) + } +} + +impl SecretKeySizeStatic for Ed25519Extended { + const SECRET_KEY_SIZE: usize = ed25519::PRIVATE_KEY_LENGTH; +} + +impl SigningAlgorithm for Ed25519Extended { + fn sign(key: &Self::Secret, msg: &[u8]) -> ei::Sig { + ei::Sig(ed25519::signature_extended(msg, &key.0)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::chain_crypto::key::KeyPair; + use crate::chain_crypto::sign::test::{keypair_signing_ko, keypair_signing_ok}; + + #[quickcheck] + fn sign_ok(input: (KeyPair, Vec)) -> bool { + keypair_signing_ok(input) + } + + #[quickcheck] + fn sign_ko(input: (KeyPair, KeyPair, Vec)) -> bool { + keypair_signing_ko(input) + } +} diff --git a/rust/src/chain_crypto/algorithms/legacy_daedalus.rs b/rust/src/chain_crypto/algorithms/legacy_daedalus.rs new file mode 100644 index 00000000..df2b8c8a --- /dev/null +++ b/rust/src/chain_crypto/algorithms/legacy_daedalus.rs @@ -0,0 +1,179 @@ +use crate::chain_crypto::key::{ + AsymmetricKey, AsymmetricPublicKey, PublicKeyError, SecretKeyError, SecretKeySizeStatic, +}; +use crate::chain_crypto::sign::{SignatureError, SigningAlgorithm, Verification, VerificationAlgorithm}; + +use cryptoxide::digest::Digest; +use cryptoxide::hmac::Hmac; +use cryptoxide::mac::Mac; +use cryptoxide::sha2::Sha512; + +use super::ed25519 as ei; +use cryptoxide::ed25519; +use ed25519_bip32::{XPrv, XPub, XPRV_SIZE, XPUB_SIZE}; +use rand_os::rand_core::{CryptoRng, RngCore}; + +const CHAIN_CODE_SIZE: usize = 32; +const SEED_SIZE: usize = 64; + +/// Legacy Daedalus algorithm +pub struct LegacyDaedalus; + +#[derive(Clone)] +pub struct LegacyPriv([u8; XPRV_SIZE]); + +impl AsRef<[u8]> for LegacyPriv { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl LegacyPriv { + pub fn from_xprv(xprv: &XPrv) -> Self { + let mut buf = [0; XPRV_SIZE]; + buf[0..XPRV_SIZE].clone_from_slice(xprv.as_ref()); + LegacyPriv(buf) + } + + pub fn inner_key(&self) -> [u8; ed25519::PRIVATE_KEY_LENGTH] { + let mut buf = [0; ed25519::PRIVATE_KEY_LENGTH]; + buf[0..ed25519::PRIVATE_KEY_LENGTH] + .clone_from_slice(&self.0.as_ref()[0..ed25519::PRIVATE_KEY_LENGTH]); + buf + } + + pub fn chaincode(&self) -> [u8; CHAIN_CODE_SIZE] { + let mut buf = [0; CHAIN_CODE_SIZE]; + buf[0..CHAIN_CODE_SIZE] + .clone_from_slice(&self.0.as_ref()[ed25519::PRIVATE_KEY_LENGTH..XPRV_SIZE]); + buf + } +} + +impl AsymmetricPublicKey for LegacyDaedalus { + type Public = XPub; + const PUBLIC_BECH32_HRP: &'static str = "legacy_xpub"; + const PUBLIC_KEY_SIZE: usize = XPUB_SIZE; + fn public_from_binary(data: &[u8]) -> Result { + let xpub = XPub::from_slice(data)?; + Ok(xpub) + } +} + +impl AsymmetricKey for LegacyDaedalus { + type Secret = LegacyPriv; + type PubAlg = LegacyDaedalus; + + const SECRET_BECH32_HRP: &'static str = "legacy_xprv"; + + fn generate(mut rng: T) -> Self::Secret { + let mut seed = [0u8; SEED_SIZE]; + rng.fill_bytes(&mut seed); + let mut mac = Hmac::new(Sha512::new(), &seed); + + let mut iter = 1; + let mut out = [0u8; XPRV_SIZE]; + + loop { + let s = format!("Root Seed Chain {}", iter); + mac.reset(); + mac.input(s.as_bytes()); + let mut block = [0u8; 64]; + mac.raw_result(&mut block); + mk_ed25519_extended(&mut out[0..64], &block[0..32]); + + if (out[31] & 0x20) == 0 { + out[64..96].clone_from_slice(&block[32..64]); + break; + } + iter = iter + 1; + } + + LegacyPriv(out) + } + + fn compute_public(key: &Self::Secret) -> ::Public { + let ed25519e = key.inner_key(); + let pubkey = ed25519::to_public(&ed25519e); + let chaincode = key.chaincode(); + + let mut buf = [0; XPUB_SIZE]; + buf[0..ed25519::PUBLIC_KEY_LENGTH].clone_from_slice(&pubkey); + buf[ed25519::PUBLIC_KEY_LENGTH..XPUB_SIZE].clone_from_slice(&chaincode); + + XPub::from_bytes(buf) + } + + fn secret_from_binary(data: &[u8]) -> Result { + // Note: we do NOT verify that the bytes match proper bip32 format + // this is because legacy Daedalus wallets do not match the format + if data.len() != XPRV_SIZE { + return Err(SecretKeyError::SizeInvalid); + } + let mut buf = [0; XPRV_SIZE]; + buf[0..XPRV_SIZE].clone_from_slice(data); + Ok(LegacyPriv(buf)) + } +} + +impl SecretKeySizeStatic for LegacyDaedalus { + const SECRET_KEY_SIZE: usize = XPRV_SIZE; +} + +type XSig = ed25519_bip32::Signature; + +impl VerificationAlgorithm for LegacyDaedalus { + type Signature = XSig; + + const SIGNATURE_SIZE: usize = ed25519_bip32::SIGNATURE_SIZE; + const SIGNATURE_BECH32_HRP: &'static str = "legacy_xsig"; + + fn signature_from_bytes(data: &[u8]) -> Result { + let xsig = XSig::from_slice(data)?; + Ok(xsig) + } + + fn verify_bytes( + pubkey: &Self::Public, + signature: &Self::Signature, + msg: &[u8], + ) -> Verification { + pubkey.verify(msg, signature).into() + } +} + +impl SigningAlgorithm for LegacyDaedalus { + fn sign(key: &Self::Secret, msg: &[u8]) -> XSig { + let buf = key.inner_key(); + let sig = ei::Sig(ed25519::signature_extended(msg, &buf)); + ed25519_bip32::Signature::from_bytes(sig.0) + } +} + +fn mk_ed25519_extended(extended_out: &mut [u8], secret: &[u8]) { + assert!(extended_out.len() == 64); + assert!(secret.len() == 32); + let mut hasher = Sha512::new(); + hasher.input(secret); + hasher.result(extended_out); + extended_out[0] &= 248; + extended_out[31] &= 63; + extended_out[31] |= 64; +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::chain_crypto::key::KeyPair; + use crate::chain_crypto::sign::test::{keypair_signing_ko, keypair_signing_ok}; + + #[quickcheck] + fn sign_ok(input: (KeyPair, Vec)) -> bool { + keypair_signing_ok(input) + } + #[quickcheck] + fn sign_ko(input: (KeyPair, KeyPair, Vec)) -> bool { + keypair_signing_ko(input) + } +} diff --git a/rust/src/chain_crypto/algorithms/mod.rs b/rust/src/chain_crypto/algorithms/mod.rs new file mode 100644 index 00000000..1e0ecaad --- /dev/null +++ b/rust/src/chain_crypto/algorithms/mod.rs @@ -0,0 +1,9 @@ +pub mod ed25519; +pub mod ed25519_derive; +pub mod ed25519_extended; +pub mod legacy_daedalus; + +pub use ed25519::Ed25519; +pub use ed25519_derive::Ed25519Bip32; +pub use ed25519_extended::Ed25519Extended; +pub use legacy_daedalus::LegacyDaedalus; diff --git a/rust/src/chain_crypto/bech32.rs b/rust/src/chain_crypto/bech32.rs new file mode 100644 index 00000000..f9fd37a8 --- /dev/null +++ b/rust/src/chain_crypto/bech32.rs @@ -0,0 +1,79 @@ +use bech32::{Error as Bech32Error, FromBase32, ToBase32}; +use std::error::Error as StdError; +use std::fmt; +use std::result::Result as StdResult; + +pub type Result = StdResult; + +pub trait Bech32 { + const BECH32_HRP: &'static str; + + fn try_from_bech32_str(bech32_str: &str) -> Result + where + Self: Sized; + + fn to_bech32_str(&self) -> String; +} + +pub fn to_bech32_from_bytes(bytes: &[u8]) -> String { + bech32::encode(B::BECH32_HRP, bytes.to_base32()) + .unwrap_or_else(|e| panic!("Failed to build bech32: {}", e)) + .to_string() +} + +pub fn try_from_bech32_to_bytes(bech32_str: &str) -> Result> { + let (hrp, bech32_data) = bech32::decode(bech32_str)?; + if hrp != B::BECH32_HRP { + return Err(Error::HrpInvalid { + expected: B::BECH32_HRP, + actual: hrp + }); + } + Vec::::from_base32(&bech32_data).map_err(Into::into) +} + +#[derive(Debug)] +pub enum Error { + Bech32Malformed(Bech32Error), + HrpInvalid { + expected: &'static str, + actual: String, + }, + DataInvalid(Box), +} + +impl Error { + pub fn data_invalid(cause: impl StdError + Send + Sync + 'static) -> Self { + Error::DataInvalid(Box::new(cause)) + } +} + +impl From for Error { + fn from(error: Bech32Error) -> Self { + Error::Bech32Malformed(error) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> StdResult<(), fmt::Error> { + match self { + Error::Bech32Malformed(_) => write!(f, "Failed to parse bech32, invalid data format"), + Error::HrpInvalid { expected, actual } => write!( + f, + "Parsed bech32 has invalid HRP prefix '{}', expected '{}'", + actual, expected + ), + Error::DataInvalid(_) => write!(f, "Failed to parse data decoded from bech32"), + } + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self { + Error::Bech32Malformed(cause) => Some(cause), + Error::DataInvalid(cause) => Some(&**cause), + _ => None, + } + } +} diff --git a/rust/src/chain_crypto/derive.rs b/rust/src/chain_crypto/derive.rs new file mode 100644 index 00000000..34c81427 --- /dev/null +++ b/rust/src/chain_crypto/derive.rs @@ -0,0 +1,38 @@ +use crate::chain_crypto::key::{PublicKey, SecretKey}; +use crate::chain_crypto::algorithms::{Ed25519, ed25519_derive::Ed25519Bip32, ed25519_extended::ExtendedPriv, ed25519::Pub}; +use cryptoxide::hmac::Hmac; +use cryptoxide::pbkdf2::pbkdf2; +use cryptoxide::sha2::Sha512; +use ed25519_bip32::{DerivationError, DerivationScheme}; +use ed25519_bip32::{XPrv, XPRV_SIZE}; +use crate::chain_crypto::Ed25519Extended; + +pub fn derive_sk_ed25519(key: &SecretKey, index: u32) -> SecretKey { + let new_key = key.0.derive(DerivationScheme::V2, index); + SecretKey(new_key) +} + +pub fn derive_pk_ed25519( + key: &PublicKey, + index: u32, +) -> Result, DerivationError> { + key.0.derive(DerivationScheme::V2, index).map(PublicKey) +} + +pub fn to_raw_sk(key: &SecretKey) -> SecretKey { + SecretKey(ExtendedPriv::from_xprv(&key.0)) +} + +pub fn to_raw_pk(key: &PublicKey) -> PublicKey { + PublicKey(Pub::from_xpub(&key.0)) +} + +pub fn from_bip39_entropy(entropy: &[u8], password: &[u8]) -> SecretKey { + let mut pbkdf2_result = [0; XPRV_SIZE]; + + const ITER: u32 = 4096; + let mut mac = Hmac::new(Sha512::new(), password); + pbkdf2(&mut mac, entropy.as_ref(), ITER, &mut pbkdf2_result); + + SecretKey(XPrv::normalize_bytes_force3rd(pbkdf2_result)) +} diff --git a/rust/src/chain_crypto/digest.rs b/rust/src/chain_crypto/digest.rs new file mode 100644 index 00000000..92a21e7a --- /dev/null +++ b/rust/src/chain_crypto/digest.rs @@ -0,0 +1,469 @@ +//! module to provide some handy interfaces atop the hashes so we have +//! the common interfaces for the project to work with. + +use std::convert::TryFrom; +use std::hash::{Hash, Hasher}; +use std::str::FromStr; +use std::{error, fmt, result}; + +use cryptoxide::blake2b::Blake2b; +use cryptoxide::digest::Digest as _; +use cryptoxide::sha3; +use hex::FromHexError; + +use crate::typed_bytes::ByteSlice; + +use crate::chain_crypto::bech32::{self, Bech32}; +use crate::chain_crypto::hash::{Blake2b256, Sha3_256}; + +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + InvalidDigestSize { got: usize, expected: usize }, + InvalidHexEncoding(FromHexError), +} +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::InvalidDigestSize { got: sz, expected } => write!( + f, + "invalid digest size, expected {} but received {} bytes.", + expected, sz + ), + Error::InvalidHexEncoding(_) => write!(f, "invalid hex encoding for digest value"), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::InvalidDigestSize { + got: _, + expected: _, + } => None, + Error::InvalidHexEncoding(err) => Some(err), + } + } +} + +impl From for Error { + fn from(err: FromHexError) -> Self { + Error::InvalidHexEncoding(err) + } +} + +#[derive(Debug, Copy, Clone)] +pub struct TryFromSliceError(()); + +impl fmt::Display for TryFromSliceError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt("could not convert slice to digest", f) + } +} + +pub trait DigestAlg { + const HASH_SIZE: usize; + type DigestData: Clone + PartialEq + Hash + Send + AsRef<[u8]>; + type DigestContext: Clone; + + fn try_from_slice(slice: &[u8]) -> Result; + fn new() -> Self::DigestContext; + fn append_data(ctx: &mut Self::DigestContext, data: &[u8]); + fn finalize(ctx: Self::DigestContext) -> Self::DigestData; +} + +impl DigestAlg for Blake2b256 { + const HASH_SIZE: usize = 32; + type DigestData = [u8; Self::HASH_SIZE]; + type DigestContext = Blake2b; + + fn try_from_slice(slice: &[u8]) -> Result { + if slice.len() == Self::HASH_SIZE { + let mut out = [0u8; Self::HASH_SIZE]; + out.copy_from_slice(slice); + Ok(out) + } else { + Err(Error::InvalidDigestSize { + expected: Self::HASH_SIZE, + got: slice.len(), + }) + } + } + + fn new() -> Self::DigestContext { + Blake2b::new(Self::HASH_SIZE) + } + + fn append_data(ctx: &mut Self::DigestContext, data: &[u8]) { + ctx.input(data) + } + + fn finalize(mut ctx: Self::DigestContext) -> Self::DigestData { + let mut out: Self::DigestData = [0; Self::HASH_SIZE]; + ctx.result(&mut out); + out + } +} + +impl DigestAlg for Sha3_256 { + const HASH_SIZE: usize = 32; + type DigestData = [u8; Self::HASH_SIZE]; + type DigestContext = sha3::Sha3_256; + + fn try_from_slice(slice: &[u8]) -> Result { + if slice.len() == Self::HASH_SIZE { + let mut out = [0u8; Self::HASH_SIZE]; + out.copy_from_slice(slice); + Ok(out) + } else { + Err(Error::InvalidDigestSize { + expected: Self::HASH_SIZE, + got: slice.len(), + }) + } + } + + fn new() -> Self::DigestContext { + sha3::Sha3_256::new() + } + + fn append_data(ctx: &mut Self::DigestContext, data: &[u8]) { + ctx.input(data) + } + + fn finalize(mut ctx: Self::DigestContext) -> Self::DigestData { + let mut out: Self::DigestData = [0; Self::HASH_SIZE]; + ctx.result(&mut out); + out + } +} + +/// A Digest Context for the H digest algorithm +pub struct Context(H::DigestContext); + +impl Clone for Context { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Context { + /// Create a new digest context + pub fn new() -> Self { + Self(H::new()) + } + + /// Append data in the context + pub fn append_data(&mut self, data: &[u8]) { + H::append_data(&mut self.0, data) + } + + /// Finalize a context and create a digest + pub fn finalize(self) -> Digest { + Digest(H::finalize(self.0)) + } +} + +pub struct Digest(H::DigestData); + +impl Clone for Digest { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +macro_rules! define_from_instances { + ($hash_ty:ty, $hash_size:expr, $bech32_hrp:expr) => { + impl From> for [u8; $hash_size] { + fn from(digest: Digest<$hash_ty>) -> Self { + digest.0 + } + } + + impl<'a> From<&'a Digest<$hash_ty>> for &'a [u8; $hash_size] { + fn from(digest: &'a Digest<$hash_ty>) -> Self { + &digest.0 + } + } + + impl From<[u8; $hash_size]> for Digest<$hash_ty> { + fn from(bytes: [u8; $hash_size]) -> Self { + Digest(bytes) + } + } + impl From<$hash_ty> for Digest<$hash_ty> { + fn from(bytes: $hash_ty) -> Self { + let out: [u8; $hash_size] = bytes.into(); + out.into() + } + } + impl Bech32 for Digest<$hash_ty> { + const BECH32_HRP: &'static str = $bech32_hrp; + + fn try_from_bech32_str(bech32_str: &str) -> bech32::Result { + let bytes = bech32::try_from_bech32_to_bytes::(bech32_str)?; + Digest::try_from(&bytes[..]).map_err(bech32::Error::data_invalid) + } + + fn to_bech32_str(&self) -> String { + bech32::to_bech32_from_bytes::(self.as_ref()) + } + } + }; +} + +define_from_instances!(Sha3_256, 32, "sha3"); +define_from_instances!(Blake2b256, 32, "blake2b"); + +unsafe impl Send for Digest {} + +impl PartialEq for Digest { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for Digest {} + +impl PartialOrd for Digest { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Digest { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_ref().cmp(other.as_ref()) + } +} + +impl AsRef<[u8]> for Digest { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Hash for Digest { + fn hash(&self, state: &mut HA) { + self.0.hash(state) + } +} + +impl TryFrom<&[u8]> for Digest { + type Error = Error; + fn try_from(slice: &[u8]) -> Result, Self::Error> { + ::try_from_slice(slice).map(Digest) + } +} + +impl FromStr for Digest { + type Err = Error; + fn from_str(s: &str) -> result::Result, Self::Err> { + let bytes = hex::decode(s)?; + Digest::try_from(&bytes[..]) + } +} + +impl fmt::Display for Digest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.as_ref())) + } +} +impl fmt::Debug for Digest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(concat!(stringify!($hash_ty), "(0x"))?; + write!(f, "{}", hex::encode(self.as_ref()))?; + f.write_str(")") + } +} + +impl Digest { + /// Get the digest of a slice of data + pub fn digest(slice: &[u8]) -> Self { + let mut ctx = Context::new(); + ctx.append_data(slice); + ctx.finalize() + } +} + +use std::marker::PhantomData; + +/// A typed version of Digest +pub struct DigestOf { + inner: Digest, + marker: PhantomData, +} + +unsafe impl Send for DigestOf {} + +impl Clone for DigestOf { + fn clone(&self) -> Self { + DigestOf { + inner: self.inner.clone(), + marker: self.marker.clone(), + } + } +} + +impl From> for Digest { + fn from(d: DigestOf) -> Self { + d.inner + } +} + +impl From> for DigestOf { + fn from(d: Digest) -> Self { + DigestOf { + inner: d, + marker: PhantomData, + } + } +} + +impl PartialEq for DigestOf { + fn eq(&self, other: &Self) -> bool { + &self.inner == &other.inner + } +} + +impl Eq for DigestOf {} + +impl PartialOrd for DigestOf { + fn partial_cmp(&self, other: &Self) -> Option { + self.inner.partial_cmp(&other.inner) + } +} + +impl Ord for DigestOf { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.inner.cmp(&other.inner) + } +} + +impl AsRef<[u8]> for DigestOf { + fn as_ref(&self) -> &[u8] { + self.inner.as_ref() + } +} + +impl Hash for DigestOf { + fn hash(&self, state: &mut HA) { + self.inner.hash(state) + } +} + +impl TryFrom<&[u8]> for DigestOf { + type Error = Error; + fn try_from(slice: &[u8]) -> Result { + Digest::::try_from(slice).map(|d| d.into()) + } +} + +impl FromStr for DigestOf { + type Err = Error; + fn from_str(s: &str) -> result::Result { + Digest::::from_str(s).map(|d| d.into()) + } +} + +impl fmt::Display for DigestOf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} +impl fmt::Debug for DigestOf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl DigestOf { + /// Coerce a digest of T, to a digest of U + pub fn coerce(&self) -> DigestOf { + DigestOf { + inner: self.inner.clone(), + marker: PhantomData, + } + } + + pub fn digest_byteslice<'a>(byteslice: &ByteSlice<'a, T>) -> Self { + let mut ctx = Context::new(); + ctx.append_data(byteslice.as_slice()); + DigestOf { + inner: ctx.finalize(), + marker: PhantomData, + } + } + + /// Get the digest of object T, given its AsRef<[u8]> implementation + pub fn digest(obj: &T) -> Self + where + T: AsRef<[u8]>, + { + let mut ctx = Context::new(); + ctx.append_data(obj.as_ref()); + DigestOf { + inner: ctx.finalize(), + marker: PhantomData, + } + } + + /// Get the digest of object T, given its serialization function in closure + pub fn digest_with(obj: &T, f: F) -> Self + where + F: FnOnce(&T) -> &[u8], + { + let mut ctx = Context::new(); + ctx.append_data(f(obj)); + DigestOf { + inner: ctx.finalize(), + marker: PhantomData, + } + } +} + +macro_rules! typed_define_from_instances { + ($hash_ty:ty, $hash_size:expr, $bech32_hrp:expr) => { + impl From> for [u8; $hash_size] { + fn from(digest: DigestOf<$hash_ty, T>) -> Self { + digest.inner.into() + } + } + impl<'a, T> From<&'a DigestOf<$hash_ty, T>> for &'a [u8; $hash_size] { + fn from(digest: &'a DigestOf<$hash_ty, T>) -> Self { + (&digest.inner).into() + } + } + + impl From<[u8; $hash_size]> for DigestOf<$hash_ty, T> { + fn from(bytes: [u8; $hash_size]) -> Self { + Digest::from(bytes).into() + } + } + + impl From<$hash_ty> for DigestOf<$hash_ty, T> { + fn from(bytes: $hash_ty) -> Self { + let out: [u8; $hash_size] = bytes.into(); + out.into() + } + } + impl Bech32 for DigestOf<$hash_ty, T> { + const BECH32_HRP: &'static str = $bech32_hrp; + + fn try_from_bech32_str(bech32_str: &str) -> bech32::Result { + let bytes = bech32::try_from_bech32_to_bytes::(bech32_str)?; + Digest::try_from(&bytes[..]) + .map_err(bech32::Error::data_invalid) + .map(|d| d.into()) + } + + fn to_bech32_str(&self) -> String { + bech32::to_bech32_from_bytes::(self.inner.as_ref()) + } + } + }; +} + +typed_define_from_instances!(Sha3_256, 32, "sha3"); +typed_define_from_instances!(Blake2b256, 32, "blake2b"); diff --git a/rust/src/chain_crypto/hash.rs b/rust/src/chain_crypto/hash.rs new file mode 100644 index 00000000..3fa05cc4 --- /dev/null +++ b/rust/src/chain_crypto/hash.rs @@ -0,0 +1,160 @@ +//! module to provide some handy interfaces atop the hashes so we have +//! the common interfaces for the project to work with. + +use std::hash::{Hash, Hasher}; +use std::str::FromStr; +use std::{error, fmt, result}; + +use cryptoxide::blake2b::Blake2b; +use cryptoxide::digest::Digest as _; +use cryptoxide::sha3; +use hex::FromHexError; + +use crate::chain_crypto::bech32::{self, Bech32}; + +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + InvalidHashSize(usize, usize), + InvalidHexEncoding(FromHexError), +} +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::InvalidHashSize(sz, expected) => write!( + f, + "invalid hash size, expected {} but received {} bytes.", + expected, sz + ), + Error::InvalidHexEncoding(_) => write!(f, "invalid hex encoding for hash value"), + } + } +} +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::InvalidHashSize(..) => None, + Error::InvalidHexEncoding(err) => Some(err), + } + } +} + +impl From for Error { + fn from(err: FromHexError) -> Self { + Error::InvalidHexEncoding(err) + } +} + +pub type Result = result::Result; + +/// defines a blake2b object +macro_rules! define_blake2b_new { + ($hash_ty:ty) => { + impl $hash_ty { + pub fn new(buf: &[u8]) -> Self { + let mut b2b = Blake2b::new(Self::HASH_SIZE); + let mut out = [0; Self::HASH_SIZE]; + b2b.input(buf); + b2b.result(&mut out); + Self::from(out) + } + } + }; +} +macro_rules! define_hash_object { + ($hash_ty:ty, $constructor:expr, $hash_size:ident, $bech32_hrp:expr) => { + impl $hash_ty { + pub const HASH_SIZE: usize = $hash_size; + + pub fn as_hash_bytes(&self) -> &[u8; Self::HASH_SIZE] { + &self.0 + } + + pub fn try_from_slice(slice: &[u8]) -> Result { + if slice.len() != Self::HASH_SIZE { + return Err(Error::InvalidHashSize(slice.len(), Self::HASH_SIZE)); + } + let mut buf = [0; Self::HASH_SIZE]; + + buf[0..Self::HASH_SIZE].clone_from_slice(slice); + Ok(Self::from(buf)) + } + } + impl AsRef<[u8]> for $hash_ty { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } + } + impl From<$hash_ty> for [u8; $hash_size] { + fn from(bytes: $hash_ty) -> Self { + bytes.0 + } + } + impl<'a> From<&'a $hash_ty> for &'a [u8; $hash_size] { + fn from(bytes: &'a $hash_ty) -> Self { + &bytes.0 + } + } + impl From<[u8; Self::HASH_SIZE]> for $hash_ty { + fn from(bytes: [u8; Self::HASH_SIZE]) -> Self { + $constructor(bytes) + } + } + impl Hash for $hash_ty { + fn hash(&self, state: &mut H) { + self.0.hash(state) + } + } + impl FromStr for $hash_ty { + type Err = Error; + fn from_str(s: &str) -> result::Result { + let bytes = hex::decode(s)?; + Self::try_from_slice(&bytes) + } + } + impl fmt::Display for $hash_ty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.as_ref())) + } + } + impl fmt::Debug for $hash_ty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(concat!(stringify!($hash_ty), "(0x"))?; + write!(f, "{}", hex::encode(self.as_ref()))?; + f.write_str(")") + } + } + impl Bech32 for $hash_ty { + const BECH32_HRP: &'static str = $bech32_hrp; + + fn try_from_bech32_str(bech32_str: &str) -> bech32::Result { + let bytes = bech32::try_from_bech32_to_bytes::(bech32_str)?; + Self::try_from_slice(&bytes).map_err(bech32::Error::data_invalid) + } + + fn to_bech32_str(&self) -> String { + bech32::to_bech32_from_bytes::(self.as_ref()) + } + } + }; +} + +pub const HASH_SIZE_256: usize = 32; + +/// Blake2b 256 bits +#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +pub struct Blake2b256([u8; HASH_SIZE_256]); +define_hash_object!(Blake2b256, Blake2b256, HASH_SIZE_256, "blake2b256"); +define_blake2b_new!(Blake2b256); + +#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +pub struct Sha3_256([u8; HASH_SIZE_256]); +define_hash_object!(Sha3_256, Sha3_256, HASH_SIZE_256, "sha3256"); +impl Sha3_256 { + pub fn new(buf: &[u8]) -> Self { + let mut sh3 = sha3::Sha3_256::new(); + let mut out = [0; Self::HASH_SIZE]; + sh3.input(buf.as_ref()); + sh3.result(&mut out); + Self::from(out) + } +} diff --git a/rust/src/chain_crypto/key.rs b/rust/src/chain_crypto/key.rs new file mode 100644 index 00000000..63d7cf01 --- /dev/null +++ b/rust/src/chain_crypto/key.rs @@ -0,0 +1,270 @@ +use crate::chain_crypto::bech32::{self, Bech32}; +use hex::FromHexError; +use rand_os::rand_core::{CryptoRng, RngCore}; +use std::fmt; +use std::hash::Hash; +use std::str::FromStr; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum SecretKeyError { + SizeInvalid, + StructureInvalid, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum PublicKeyError { + SizeInvalid, + StructureInvalid, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PublicKeyFromStrError { + HexMalformed(FromHexError), + KeyInvalid(PublicKeyError), +} + +pub trait AsymmetricPublicKey { + type Public: AsRef<[u8]> + Clone + PartialEq + Eq + Hash; + const PUBLIC_BECH32_HRP: &'static str; + const PUBLIC_KEY_SIZE: usize; + + fn public_from_binary(data: &[u8]) -> Result; +} + +pub trait AsymmetricKey { + // The name of the public key Algorithm to represent the public key + // where PubAlg::Public is the public key type. + type PubAlg: AsymmetricPublicKey; + + // the secret key type + type Secret: AsRef<[u8]> + Clone; + + const SECRET_BECH32_HRP: &'static str; + + fn generate(rng: T) -> Self::Secret; + fn compute_public(secret: &Self::Secret) -> ::Public; + fn secret_from_binary(data: &[u8]) -> Result; +} + +pub trait SecretKeySizeStatic: AsymmetricKey { + const SECRET_KEY_SIZE: usize; +} + +pub struct SecretKey(pub(crate) A::Secret); + +pub struct PublicKey(pub(crate) A::Public); + +pub struct KeyPair(SecretKey, PublicKey); + +impl KeyPair { + pub fn private_key(&self) -> &SecretKey { + &self.0 + } + pub fn public_key(&self) -> &PublicKey { + &self.1 + } + pub fn into_keys(self) -> (SecretKey, PublicKey) { + (self.0, self.1) + } + pub fn generate(rng: &mut R) -> Self { + let sk = A::generate(rng); + let pk = A::compute_public(&sk); + KeyPair(SecretKey(sk), PublicKey(pk)) + } +} +impl std::fmt::Debug for KeyPair { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "KeyPair(, {:?})", self.public_key()) + } +} +impl std::fmt::Display for KeyPair { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "KeyPair(, {})", self.public_key()) + } +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.0.as_ref())) + } +} +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.0.as_ref())) + } +} + +impl FromStr for PublicKey { + type Err = PublicKeyFromStrError; + + fn from_str(hex: &str) -> Result { + let bytes = hex::decode(hex).map_err(PublicKeyFromStrError::HexMalformed)?; + Self::from_binary(&bytes).map_err(PublicKeyFromStrError::KeyInvalid) + } +} + +impl fmt::Display for SecretKeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SecretKeyError::SizeInvalid => write!(f, "Invalid Secret Key size"), + SecretKeyError::StructureInvalid => write!(f, "Invalid Secret Key structure"), + } + } +} + +impl fmt::Display for PublicKeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PublicKeyError::SizeInvalid => write!(f, "Invalid Public Key size"), + PublicKeyError::StructureInvalid => write!(f, "Invalid Public Key structure"), + } + } +} + +impl fmt::Display for PublicKeyFromStrError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PublicKeyFromStrError::HexMalformed(_) => "hex encoding malformed", + PublicKeyFromStrError::KeyInvalid(_) => "invalid public key data", + } + .fmt(f) + } +} + +impl std::error::Error for SecretKeyError {} + +impl std::error::Error for PublicKeyError {} + +impl std::error::Error for PublicKeyFromStrError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + PublicKeyFromStrError::HexMalformed(e) => Some(e), + PublicKeyFromStrError::KeyInvalid(e) => Some(e), + } + } +} + +impl AsRef<[u8]> for PublicKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsRef<[u8]> for SecretKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl From> for KeyPair { + fn from(secret_key: SecretKey) -> Self { + let public_key = secret_key.to_public(); + KeyPair(secret_key, public_key) + } +} + +impl SecretKey { + pub fn generate(rng: T) -> Self { + SecretKey(A::generate(rng)) + } + pub fn to_public(&self) -> PublicKey { + PublicKey(::compute_public(&self.0)) + } + pub fn from_binary(data: &[u8]) -> Result { + Ok(SecretKey(::secret_from_binary(data)?)) + } +} + +impl PublicKey { + pub fn from_binary(data: &[u8]) -> Result { + Ok(PublicKey(::public_from_binary( + data, + )?)) + } +} + +impl Clone for SecretKey { + fn clone(&self) -> Self { + SecretKey(self.0.clone()) + } +} +impl Clone for PublicKey { + fn clone(&self) -> Self { + PublicKey(self.0.clone()) + } +} +impl Clone for KeyPair { + fn clone(&self) -> Self { + KeyPair(self.0.clone(), self.1.clone()) + } +} + +impl std::cmp::PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.0.as_ref().eq(other.0.as_ref()) + } +} + +impl std::cmp::Eq for PublicKey {} + +impl std::cmp::PartialOrd for PublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.as_ref().partial_cmp(other.0.as_ref()) + } +} + +impl std::cmp::Ord for PublicKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.as_ref().cmp(other.0.as_ref()) + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + self.0.as_ref().hash(state) + } +} + +impl Bech32 for PublicKey { + const BECH32_HRP: &'static str = A::PUBLIC_BECH32_HRP; + + fn try_from_bech32_str(bech32_str: &str) -> Result { + let bytes = bech32::try_from_bech32_to_bytes::(bech32_str)?; + Self::from_binary(&bytes).map_err(bech32::Error::data_invalid) + } + + fn to_bech32_str(&self) -> String { + bech32::to_bech32_from_bytes::(self.as_ref()) + } +} + +impl Bech32 for SecretKey { + const BECH32_HRP: &'static str = A::SECRET_BECH32_HRP; + + fn try_from_bech32_str(bech32_str: &str) -> Result { + let bytes = bech32::try_from_bech32_to_bytes::(bech32_str)?; + Self::from_binary(&bytes).map_err(bech32::Error::data_invalid) + } + + fn to_bech32_str(&self) -> String { + bech32::to_bech32_from_bytes::(self.0.as_ref()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + // ONLY ALLOWED WHEN TESTING + impl std::fmt::Debug for SecretKey + where + A: AsymmetricKey, + { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "SecretKey ({:?})", self.0.as_ref()) + } + } +} diff --git a/rust/src/chain_crypto/mod.rs b/rust/src/chain_crypto/mod.rs new file mode 100644 index 00000000..a5f979a3 --- /dev/null +++ b/rust/src/chain_crypto/mod.rs @@ -0,0 +1,26 @@ +cfg_if! { + if #[cfg(test)] { + mod testing; + } else if #[cfg(feature = "property-test-api")] { + pub mod testing; + } +} + +pub mod algorithms; +pub mod bech32; +pub mod derive; +pub mod digest; +pub mod hash; +mod sign; +mod key; + +pub use algorithms::*; +pub use hash::{Blake2b256, Sha3_256}; +pub use key::{ + AsymmetricKey, AsymmetricPublicKey, KeyPair, PublicKey, PublicKeyError, PublicKeyFromStrError, + SecretKey, SecretKeyError, SecretKeySizeStatic, +}; +pub use sign::{ + Signature, SignatureError, SignatureFromStrError, SigningAlgorithm, Verification, + VerificationAlgorithm, +}; diff --git a/rust/src/chain_crypto/sign.rs b/rust/src/chain_crypto/sign.rs new file mode 100644 index 00000000..f6bfb960 --- /dev/null +++ b/rust/src/chain_crypto/sign.rs @@ -0,0 +1,246 @@ +use crate::chain_crypto::{ + bech32::{self, Bech32}, + key, +}; +use hex::FromHexError; +use std::{fmt, marker::PhantomData, str::FromStr}; +use crate::typed_bytes::{ByteArray, ByteSlice}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Verification { + Failed, + Success, +} + +impl From for Verification { + fn from(b: bool) -> Self { + if b { + Verification::Success + } else { + Verification::Failed + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum SignatureError { + SizeInvalid { expected: usize, got: usize }, // expected, got in bytes + StructureInvalid, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SignatureFromStrError { + HexMalformed(FromHexError), + Invalid(SignatureError), +} + +pub trait VerificationAlgorithm: key::AsymmetricPublicKey { + type Signature: AsRef<[u8]> + Clone; + + const SIGNATURE_SIZE: usize; + const SIGNATURE_BECH32_HRP: &'static str; + + fn verify_bytes(pubkey: &Self::Public, signature: &Self::Signature, msg: &[u8]) + -> Verification; + + fn signature_from_bytes(data: &[u8]) -> Result; +} + +pub trait SigningAlgorithm: key::AsymmetricKey +where + Self::PubAlg: VerificationAlgorithm, +{ + fn sign(key: &Self::Secret, msg: &[u8]) -> ::Signature; +} + +pub struct Signature { + signdata: A::Signature, + phantom: PhantomData, +} + +impl fmt::Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.signdata.as_ref())) + } +} +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.signdata.as_ref())) + } +} +impl FromStr for Signature { + type Err = SignatureFromStrError; + + fn from_str(hex: &str) -> Result { + let bytes = hex::decode(hex).map_err(SignatureFromStrError::HexMalformed)?; + Self::from_binary(&bytes).map_err(SignatureFromStrError::Invalid) + } +} + +impl fmt::Display for SignatureError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SignatureError::SizeInvalid { expected, got } => write!( + f, + "Invalid Signature size expecting {} got {}", + expected, got + ), + SignatureError::StructureInvalid => write!(f, "Invalid Signature structure"), + } + } +} +impl fmt::Display for SignatureFromStrError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SignatureFromStrError::HexMalformed(_) => "hex encoding malformed", + SignatureFromStrError::Invalid(_) => "invalid signature data", + } + .fmt(f) + } +} + +impl std::error::Error for SignatureError {} +impl std::error::Error for SignatureFromStrError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + SignatureFromStrError::HexMalformed(e) => Some(e), + SignatureFromStrError::Invalid(e) => Some(e), + } + } +} + +impl Signature { + pub fn from_binary(sig: &[u8]) -> Result { + Ok(Signature { + signdata: A::signature_from_bytes(sig)?, + phantom: PhantomData, + }) + } + pub fn coerce(self) -> Signature { + Signature { + signdata: self.signdata, + phantom: PhantomData, + } + } + + pub fn safe_coerce>(self) -> Signature { + Signature { + signdata: self.signdata, + phantom: PhantomData, + } + } +} + +pub trait SafeSignatureCoerce {} + +impl<'a, T> SafeSignatureCoerce> for ByteSlice<'a, T> {} + +impl> Signature { + #[must_use] + pub fn verify(&self, publickey: &key::PublicKey, object: &T) -> Verification { + ::verify_bytes(&publickey.0, &self.signdata, object.as_ref()) + } +} + +impl Signature { + #[must_use] + pub fn verify_slice(&self, publickey: &key::PublicKey, slice: &[u8]) -> Verification { + ::verify_bytes(&publickey.0, &self.signdata, slice) + } +} + +/* +impl> Signature + where ::Public: VerificationAlgorithm, +{ + pub fn generate(secretkey: &key::SecretKey, object: &T) -> Signature { + Signature { + signdata: ::sign(&secretkey.0, object.as_ref()), + phantom: PhantomData, + } + } +} +*/ +impl key::SecretKey +where + ::PubAlg: VerificationAlgorithm, +{ + pub fn sign>(&self, object: &T) -> Signature { + Signature { + signdata: ::sign(&self.0, object.as_ref()), + phantom: PhantomData, + } + } + + pub fn sign_slice(&self, slice: &[u8]) -> Signature { + Signature { + signdata: ::sign(&self.0, slice), + phantom: PhantomData, + } + } +} + +impl Clone for Signature { + fn clone(&self) -> Self { + Signature { + signdata: self.signdata.clone(), + phantom: std::marker::PhantomData, + } + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.signdata.as_ref() + } +} + +impl Bech32 for Signature { + const BECH32_HRP: &'static str = A::SIGNATURE_BECH32_HRP; + + fn try_from_bech32_str(bech32_str: &str) -> Result { + let bytes = bech32::try_from_bech32_to_bytes::(bech32_str)?; + Self::from_binary(&bytes).map_err(bech32::Error::data_invalid) + } + + fn to_bech32_str(&self) -> String { + bech32::to_bech32_from_bytes::(self.as_ref()) + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use crate::chain_crypto::key::{AsymmetricKey, KeyPair}; + + pub(crate) fn keypair_signing_ok( + input: (KeyPair, Vec), + ) -> bool + where + ::PubAlg: VerificationAlgorithm, + { + let (sk, pk) = input.0.into_keys(); + let data = input.1; + + let signature = sk.sign(&data); + signature.verify(&pk, &data) == Verification::Success + } + + pub(crate) fn keypair_signing_ko( + input: (KeyPair, KeyPair, Vec), + ) -> bool + where + ::PubAlg: VerificationAlgorithm, + { + let (sk, pk) = input.0.into_keys(); + let pk_random = input.1.public_key(); + let data = input.2; + + if &pk == pk_random { + return true; + } + + let signature = sk.sign(&data); + signature.verify(&pk_random, &data) == Verification::Failed + } +} diff --git a/rust/src/chain_crypto/testing.rs b/rust/src/chain_crypto/testing.rs new file mode 100644 index 00000000..c84c79e3 --- /dev/null +++ b/rust/src/chain_crypto/testing.rs @@ -0,0 +1,126 @@ +use super::*; +use crate::chain_crypto::digest; + +use quickcheck::{Arbitrary, Gen}; +use rand_chacha::ChaChaRng; +use rand_os::rand_core::SeedableRng; + +/// an Arbitrary friendly cryptographic generator +/// +/// Given the same generation, all the cryptographic +/// material that is created through it, is +/// deterministic, and thus can be replay +/// +/// For obvious reasons, do *not* use anywhere except for testing +#[derive(Clone, Debug)] +pub struct TestCryptoGen(pub u64); + +impl Arbitrary for TestCryptoGen { + fn arbitrary(g: &mut G) -> Self { + TestCryptoGen(Arbitrary::arbitrary(g)) + } +} + +impl TestCryptoGen { + /// get the nth deterministic RNG + pub fn get_rng(&self, idx: u32) -> ChaChaRng { + ChaChaRng::seed_from_u64(idx as u64 * 2 ^ 12 + self.0) + } + + /// Get the nth deterministic secret key + pub fn secret_key(&self, idx: u32) -> SecretKey { + SecretKey::generate(self.get_rng(idx)) + } + + /// Get the nth deterministic keypair + pub fn keypair(&self, idx: u32) -> KeyPair { + KeyPair::from(self.secret_key(idx)) + } +} + +#[allow(dead_code)] +pub fn arbitrary_public_key(g: &mut G) -> PublicKey { + TestCryptoGen::arbitrary(g) + .keypair::(0) + .public_key() + .clone() +} + +pub fn arbitrary_secret_key(g: &mut G) -> SecretKey +where + A: AsymmetricKey, + G: Gen, +{ + TestCryptoGen::arbitrary(g).secret_key(0) +} + +impl Arbitrary for SecretKey +where + A: AsymmetricKey + 'static, + A::Secret: Send, +{ + fn arbitrary(g: &mut G) -> Self { + arbitrary_secret_key(g) + } +} +impl Arbitrary for KeyPair +where + A: AsymmetricKey + 'static, + A::Secret: Send, + ::Public: Send, +{ + fn arbitrary(g: &mut G) -> Self { + let secret_key = SecretKey::arbitrary(g); + KeyPair::from(secret_key) + } +} + +impl Arbitrary for Signature +where + A: VerificationAlgorithm + 'static, + A::Signature: Send, + T: Send + 'static, +{ + fn arbitrary(g: &mut G) -> Self { + let bytes: Vec<_> = std::iter::repeat_with(|| u8::arbitrary(g)) + .take(A::SIGNATURE_SIZE) + .collect(); + Signature::from_binary(&bytes).unwrap() + } +} + +impl Arbitrary for Blake2b256 { + fn arbitrary(g: &mut G) -> Self { + let bytes: Vec<_> = std::iter::repeat_with(|| u8::arbitrary(g)) + .take(Self::HASH_SIZE) + .collect(); + Self::try_from_slice(&bytes).unwrap() + } +} + +impl Arbitrary for Sha3_256 { + fn arbitrary(g: &mut G) -> Self { + let bytes: Vec<_> = std::iter::repeat_with(|| u8::arbitrary(g)) + .take(Self::HASH_SIZE) + .collect(); + Self::try_from_slice(&bytes).unwrap() + } +} + +impl Arbitrary for digest::Digest { + fn arbitrary(g: &mut G) -> Self { + let bytes: Vec<_> = std::iter::repeat_with(|| u8::arbitrary(g)) + .take(26) // actual number doesn't really matter + .collect(); + digest::Digest::::digest(&bytes[..]) + } +} + +impl Arbitrary for digest::DigestOf { + fn arbitrary(g: &mut G) -> Self { + let bytes: Vec<_> = std::iter::repeat_with(|| u8::arbitrary(g)) + .take(26) // actual number doesn't really matter + .collect(); + digest::DigestOf::>::digest(&bytes).coerce() + } +} diff --git a/rust/src/crypto.rs b/rust/src/crypto.rs index 5856dc82..68db2d48 100644 --- a/rust/src/crypto.rs +++ b/rust/src/crypto.rs @@ -1,6 +1,6 @@ use cbor_event::{de::Deserializer, se::Serializer}; -use chain_impl_mockchain as chain; -use chain_crypto as crypto; +use crate::impl_mockchain as chain; +use crate::chain_crypto as crypto; use chain::{key}; use crypto::bech32::Bech32 as _; use rand_os::OsRng; @@ -631,7 +631,7 @@ macro_rules! impl_signature { serializer.write_bytes(self.0.as_ref()) } } - + impl Deserialize for $name { fn deserialize(raw: &mut Deserializer) -> Result { Ok(Self(crypto::Signature::from_binary(raw.bytes()?.as_ref())?)) @@ -682,7 +682,7 @@ macro_rules! impl_hash_type { serializer.write_bytes(self.0) } } - + impl Deserialize for $name { fn deserialize(raw: &mut Deserializer) -> Result { use std::convert::TryInto; @@ -916,4 +916,4 @@ mod tests { let deser = Nonce::deserialize(&mut Deserializer::from(std::io::Cursor::new(orig.to_bytes()))).unwrap(); assert_eq!(orig.to_bytes(), deser.to_bytes()); } -} \ No newline at end of file +} diff --git a/rust/src/error.rs b/rust/src/error.rs index 24dac4a2..eb397311 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -1,6 +1,7 @@ use cbor_event::{self, de::Deserializer, se::{Serialize, Serializer}}; use std::io::{BufRead, Seek, Write}; use wasm_bindgen::prelude::*; +use crate::chain_crypto; // This file was code-generated using an experimental CDDL to rust tool: // https://github.com/Emurgo/cddl-codegen @@ -134,4 +135,4 @@ impl From for DeserializeError { failure: DeserializeFailure::PublicKeyError(err), } } -} \ No newline at end of file +} diff --git a/rust/src/impl_mockchain/key.rs b/rust/src/impl_mockchain/key.rs new file mode 100644 index 00000000..7b4054e4 --- /dev/null +++ b/rust/src/impl_mockchain/key.rs @@ -0,0 +1,200 @@ +//! Module provides cryptographic utilities and types related to +//! the user keys. +//! +use crate::chain_core::mempack::{read_mut_slice, ReadBuf, ReadError, Readable}; +use crate::chain_core::property; +use crate::chain_crypto as crypto; +use crate::chain_crypto::{ + AsymmetricKey, AsymmetricPublicKey, SecretKey, SigningAlgorithm, VerificationAlgorithm, +}; +use rand_os::rand_core::{CryptoRng, RngCore}; + +#[derive(Clone)] +pub enum EitherEd25519SecretKey { + Extended(crypto::SecretKey), + Normal(crypto::SecretKey), +} + +impl EitherEd25519SecretKey { + pub fn generate(rng: R) -> Self { + EitherEd25519SecretKey::Extended(SecretKey::generate(rng)) + } + + pub fn to_public(&self) -> crypto::PublicKey { + match self { + EitherEd25519SecretKey::Extended(sk) => sk.to_public(), + EitherEd25519SecretKey::Normal(sk) => sk.to_public(), + } + } + + pub fn sign>(&self, dat: &T) -> crypto::Signature { + match self { + EitherEd25519SecretKey::Extended(sk) => sk.sign(dat), + EitherEd25519SecretKey::Normal(sk) => sk.sign(dat), + } + } + + pub fn sign_slice(&self, dat: &[u8]) -> crypto::Signature { + match self { + EitherEd25519SecretKey::Extended(sk) => sk.sign_slice(dat), + EitherEd25519SecretKey::Normal(sk) => sk.sign_slice(dat), + } + } +} + +pub type SpendingPublicKey = crypto::PublicKey; +pub type SpendingSignature = crypto::Signature; + +pub type Ed25519Signature = crypto::Signature; + + +fn chain_crypto_pub_err(e: crypto::PublicKeyError) -> ReadError { + match e { + crypto::PublicKeyError::SizeInvalid => { + ReadError::StructureInvalid("publickey size invalid".to_string()) + } + crypto::PublicKeyError::StructureInvalid => { + ReadError::StructureInvalid("publickey structure invalid".to_string()) + } + } +} +fn chain_crypto_sig_err(e: crypto::SignatureError) -> ReadError { + match e { + crypto::SignatureError::SizeInvalid { expected, got } => ReadError::StructureInvalid( + format!("signature size invalid, expected {} got {}", expected, got), + ), + crypto::SignatureError::StructureInvalid => { + ReadError::StructureInvalid("signature structure invalid".to_string()) + } + } +} + +#[inline] +pub fn serialize_public_key( + key: &crypto::PublicKey, + mut writer: W, +) -> Result<(), std::io::Error> { + writer.write_all(key.as_ref()) +} +#[inline] +pub fn serialize_signature( + signature: &crypto::Signature, + mut writer: W, +) -> Result<(), std::io::Error> { + writer.write_all(signature.as_ref()) +} +#[inline] +pub fn deserialize_public_key<'a, A>( + buf: &mut ReadBuf<'a>, +) -> Result, ReadError> +where + A: AsymmetricPublicKey, +{ + let mut bytes = vec![0u8; A::PUBLIC_KEY_SIZE]; + read_mut_slice(buf, &mut bytes[..])?; + crypto::PublicKey::from_binary(&bytes).map_err(chain_crypto_pub_err) +} +#[inline] +pub fn deserialize_signature<'a, A, T>( + buf: &mut ReadBuf<'a>, +) -> Result, ReadError> +where + A: VerificationAlgorithm, +{ + let mut bytes = vec![0u8; A::SIGNATURE_SIZE]; + read_mut_slice(buf, &mut bytes[..])?; + crypto::Signature::from_binary(&bytes).map_err(chain_crypto_sig_err) +} + +pub fn make_signature( + spending_key: &crypto::SecretKey, + data: &T, +) -> crypto::Signature +where + A: SigningAlgorithm, + ::PubAlg: VerificationAlgorithm, + T: property::Serialize, +{ + let bytes = data.serialize_as_vec().unwrap(); + spending_key.sign(&bytes).coerce() +} + +pub fn verify_signature( + signature: &crypto::Signature, + public_key: &crypto::PublicKey, + data: &T, +) -> crypto::Verification +where + A: VerificationAlgorithm, + T: property::Serialize, +{ + let bytes = data.serialize_as_vec().unwrap(); + signature.clone().coerce().verify(public_key, &bytes) +} + +/// A serializable type T with a signature. +pub struct Signed { + pub data: T, + pub sig: crypto::Signature, +} + +pub fn signed_new( + secret_key: &crypto::SecretKey, + data: T, +) -> Signed +where + A::PubAlg: VerificationAlgorithm, +{ + let bytes = data.serialize_as_vec().unwrap(); + let signature = secret_key.sign(&bytes).coerce(); + Signed { + data: data, + sig: signature, + } +} + +impl property::Serialize for Signed +where + std::io::Error: From, +{ + type Error = std::io::Error; + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + self.data.serialize(&mut writer)?; + serialize_signature(&self.sig, &mut writer)?; + Ok(()) + } +} + +impl Readable for Signed { + fn read<'a>(buf: &mut ReadBuf<'a>) -> Result { + Ok(Signed { + data: T::read(buf)?, + sig: deserialize_signature(buf)?, + }) + } +} + +impl PartialEq for Signed { + fn eq(&self, other: &Self) -> bool { + self.data.eq(&other.data) && self.sig.as_ref() == other.sig.as_ref() + } +} +impl Eq for Signed {} +impl std::fmt::Debug for Signed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Signed ( data: {:?}, signature: {:?} )", + self.data, + self.sig.as_ref() + ) + } +} +impl Clone for Signed { + fn clone(&self) -> Self { + Signed { + data: self.data.clone(), + sig: self.sig.clone(), + } + } +} diff --git a/rust/src/impl_mockchain/mod.rs b/rust/src/impl_mockchain/mod.rs new file mode 100644 index 00000000..59cdad79 --- /dev/null +++ b/rust/src/impl_mockchain/mod.rs @@ -0,0 +1 @@ +pub mod key; diff --git a/rust/src/legacy_address/address.rs b/rust/src/legacy_address/address.rs new file mode 100644 index 00000000..0d7ccb9d --- /dev/null +++ b/rust/src/legacy_address/address.rs @@ -0,0 +1,511 @@ +//! Address creation and parsing +//! +//! Address components are: +//! * `HashedSpendingData` computed from `SpendingData` +//! * `Attributes` +//! * `AddrType` +//! +//! All this components form an `ExtendedAddr`, which serialized +//! to binary makes an `Addr` +//! + +use crate::legacy_address::base58; +use crate::legacy_address::cbor; +use cbor_event::{self, de::Deserializer, se::Serializer, cbor}; +use cryptoxide::blake2b::Blake2b; +use cryptoxide::digest::Digest; +use cryptoxide::sha3; +use ed25519_bip32::XPub; + +use std::{ + convert::{TryFrom, TryInto}, + fmt, + io::{BufRead, Write}, +}; + +// public key tag only for encoding/decoding purpose +struct PubKeyTag(); + +// [TkListLen 1, TkInt (fromEnum t)] +impl cbor_event::se::Serialize for PubKeyTag { + fn serialize<'se, W: Write>( + &self, + serializer: &'se mut Serializer, + ) -> cbor_event::Result<&'se mut Serializer> { + serializer.write_unsigned_integer(0) + } +} +impl cbor_event::de::Deserialize for PubKeyTag { + fn deserialize(reader: &mut Deserializer) -> cbor_event::Result { + match reader.unsigned_integer()? { + 0 => Ok(PubKeyTag()), + _ => Err(cbor_event::Error::CustomError(format!("Invalid AddrType"))), + } + } +} + +type HDAddressPayload = Vec; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct Attributes { + pub derivation_path: Option, + pub network_magic: Option, +} +impl Attributes { + pub fn new_bootstrap_era(hdap: Option, network_magic: Option) -> Self { + Attributes { + derivation_path: hdap, + network_magic, + } + } +} + +const ATTRIBUTE_NAME_TAG_DERIVATION: u64 = 1; +const ATTRIBUTE_NAME_TAG_NETWORK_MAGIC: u64 = 2; + +impl cbor_event::se::Serialize for Attributes { + fn serialize<'se, W: Write>( + &self, + serializer: &'se mut Serializer, + ) -> cbor_event::Result<&'se mut Serializer> { + let mut len = 0; + if let Some(_) = &self.derivation_path { + len += 1 + }; + if let Some(_) = &self.network_magic { + len += 1 + }; + let serializer = serializer.write_map(cbor_event::Len::Len(len))?; + let serializer = match &self.derivation_path { + &None => serializer, + &Some(ref dp) => serializer + .write_unsigned_integer(ATTRIBUTE_NAME_TAG_DERIVATION)? + .write_bytes(&dp)?, + }; + let serializer = match &self.network_magic { + &None => serializer, + &Some(network_magic) => serializer + .write_unsigned_integer(ATTRIBUTE_NAME_TAG_NETWORK_MAGIC)? + .write_bytes(cbor!(&network_magic)?)?, + }; + Ok(serializer) + } +} +impl cbor_event::de::Deserialize for Attributes { + fn deserialize(reader: &mut Deserializer) -> cbor_event::Result { + let len = reader.map()?; + let mut len = match len { + cbor_event::Len::Indefinite => { + return Err(cbor_event::Error::CustomError(format!( + "Invalid Attributes: received map of {:?} elements", + len + ))); + } + cbor_event::Len::Len(len) => len, + }; + let mut derivation_path = None; + let mut network_magic = None; + while len > 0 { + let key = reader.unsigned_integer()?; + match key { + ATTRIBUTE_NAME_TAG_DERIVATION => derivation_path = Some(reader.bytes()?), + ATTRIBUTE_NAME_TAG_NETWORK_MAGIC => { + // Yes, this is an integer encoded as CBOR encoded as Bytes in CBOR. + let bytes = reader.bytes()?; + let n = Deserializer::from(std::io::Cursor::new(bytes)).deserialize::()?; + network_magic = Some(n); + } + _ => { + return Err(cbor_event::Error::CustomError(format!( + "invalid Attribute key {}", + key + ))); + } + } + len -= 1; + } + Ok(Attributes { + derivation_path, + network_magic, + }) + } +} + +// calculate the hash of the data using SHA3 digest then using Blake2b224 +fn sha3_then_blake2b224(data: &[u8]) -> [u8; 28] { + let mut sh3 = sha3::Sha3_256::new(); + let mut sh3_out = [0; 32]; + sh3.input(data.as_ref()); + sh3.result(&mut sh3_out); + + let mut b2b = Blake2b::new(28); + let mut out = [0; 28]; + b2b.input(&sh3_out[..]); + b2b.result(&mut out); + out +} + +fn hash_spending_data(xpub: &XPub, attrs: &Attributes) -> [u8; 28] { + let buf = cbor!(&(&PubKeyTag(), &SpendingData(xpub), attrs)) + .expect("serialize the HashedSpendingData's digest data"); + sha3_then_blake2b224(&buf) +} + +/// A valid cardano Address that is displayed in base58 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct Addr(Vec); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AddressMatchXPub { + Yes, + No, +} + +impl Addr { + pub fn deconstruct(&self) -> ExtendedAddr { + let mut raw = Deserializer::from(std::io::Cursor::new(&self.0)); + cbor_event::de::Deserialize::deserialize(&mut raw).unwrap() // unwrap should never fail from addr to extended addr + } + + /// Check if the Addr can be reconstructed with a specific xpub + pub fn identical_with_pubkey(&self, xpub: &XPub) -> AddressMatchXPub { + let ea = self.deconstruct(); + let newea = ExtendedAddr::new(xpub, ea.attributes); + if self == &newea.to_address() { + AddressMatchXPub::Yes + } else { + AddressMatchXPub::No + } + } + + /// mostly helper of the previous function, so not to have to expose the xpub construction + pub fn identical_with_pubkey_raw(&self, xpub: &[u8]) -> AddressMatchXPub { + match XPub::from_slice(xpub) { + Ok(xpub) => self.identical_with_pubkey(&xpub), + _ => AddressMatchXPub::No, + } + } +} + +impl AsRef<[u8]> for Addr { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl TryFrom<&[u8]> for Addr { + type Error = cbor_event::Error; + + fn try_from(slice: &[u8]) -> Result { + let mut v = Vec::new(); + // TODO we only want validation of slice here, but we don't have api to do that yet. + { + let mut raw = Deserializer::from(std::io::Cursor::new(&slice)); + let _: ExtendedAddr = cbor_event::de::Deserialize::deserialize(&mut raw)?; + } + v.extend_from_slice(slice); + Ok(Addr(v)) + } +} + +impl ::std::str::FromStr for Addr { + type Err = ParseExtendedAddrError; + fn from_str(s: &str) -> Result { + let bytes = base58::decode(s).map_err(ParseExtendedAddrError::Base58Error)?; + Self::try_from(&bytes[..]).map_err(ParseExtendedAddrError::EncodingError) + } +} + +impl From for Addr { + fn from(ea: ExtendedAddr) -> Self { + ea.to_address() + } +} + +impl fmt::Display for Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", base58::encode(&self.0)) + } +} + +impl cbor_event::se::Serialize for Addr { + fn serialize<'se, W: Write>( + &self, + serializer: &'se mut Serializer, + ) -> cbor_event::Result<&'se mut Serializer> { + // Addr is already serialized + serializer.write_raw_bytes(&self.0) + } +} +impl cbor_event::de::Deserialize for Addr { + fn deserialize(reader: &mut Deserializer) -> cbor_event::Result { + let ea: ExtendedAddr = cbor_event::de::Deserialize::deserialize(reader)?; + Ok(ea.to_address()) + } +} + +const EXTENDED_ADDR_LEN: usize = 28; + +/// A valid cardano address deconstructed +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ExtendedAddr { + pub addr: [u8; EXTENDED_ADDR_LEN], + pub attributes: Attributes, +} +impl ExtendedAddr { + pub fn new(xpub: &XPub, attrs: Attributes) -> Self { + ExtendedAddr { + addr: hash_spending_data(xpub, &attrs), + attributes: attrs, + } + } + + // bootstrap era + no hdpayload address + pub fn new_simple(xpub: &XPub, network_magic: Option) -> Self { + ExtendedAddr::new(xpub, Attributes::new_bootstrap_era(None, network_magic)) + } + + pub fn to_address(&self) -> Addr { + Addr(cbor!(self).unwrap()) // unwrap should never fail from strongly typed extended addr to addr + } +} +#[derive(Debug)] +pub enum ParseExtendedAddrError { + EncodingError(cbor_event::Error), + Base58Error(base58::Error), +} + +impl fmt::Display for ParseExtendedAddrError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ParseExtendedAddrError::*; + match self { + EncodingError(_error) => f.write_str("encoding error"), + Base58Error(_error) => f.write_str("base58 error"), + } + } +} + +impl std::error::Error for ParseExtendedAddrError { + fn source<'a>(&'a self) -> Option<&'a (dyn std::error::Error + 'static)> { + use ParseExtendedAddrError::*; + match self { + EncodingError(ref error) => Some(error), + Base58Error(ref error) => Some(error), + } + } +} + +impl ::std::str::FromStr for ExtendedAddr { + type Err = ParseExtendedAddrError; + fn from_str(s: &str) -> Result { + let bytes = base58::decode(s).map_err(ParseExtendedAddrError::Base58Error)?; + + Self::try_from(&bytes[..]).map_err(ParseExtendedAddrError::EncodingError) + } +} +impl TryFrom<&[u8]> for ExtendedAddr { + type Error = cbor_event::Error; + + fn try_from(slice: &[u8]) -> Result { + let mut raw = Deserializer::from(std::io::Cursor::new(slice)); + cbor_event::de::Deserialize::deserialize(&mut raw) + } +} +impl cbor_event::se::Serialize for ExtendedAddr { + fn serialize<'se, W: Write>( + &self, + serializer: &'se mut Serializer, + ) -> cbor_event::Result<&'se mut Serializer> { + let addr_bytes = cbor_event::Value::Bytes(self.addr.to_vec()); + cbor::util::encode_with_crc32_(&(&addr_bytes, &self.attributes, &PubKeyTag()), serializer)?; + Ok(serializer) + } +} +impl cbor_event::de::Deserialize for ExtendedAddr { + fn deserialize(reader: &mut Deserializer) -> cbor_event::Result { + let bytes = cbor::util::raw_with_crc32(reader)?; + let mut raw = Deserializer::from(std::io::Cursor::new(bytes)); + raw.tuple(3, "ExtendedAddr")?; + let addr_bytes = raw.bytes()?; + let addr = addr_bytes.as_slice().try_into().map_err(|_| { + cbor_event::Error::WrongLen( + addr_bytes.len() as u64, + cbor_event::Len::Len(EXTENDED_ADDR_LEN as u64), + "invalid extended address length", + ) + })?; + let attributes = cbor_event::de::Deserialize::deserialize(&mut raw)?; + let _addr_type: PubKeyTag = cbor_event::de::Deserialize::deserialize(&mut raw)?; + + Ok(ExtendedAddr { addr, attributes }) + } +} +impl fmt::Display for ExtendedAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_address()) + } +} + +const SPENDING_DATA_TAG_PUBKEY: u64 = 0; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct SpendingData<'a>(&'a XPub); + +impl<'a> cbor_event::se::Serialize for SpendingData<'a> { + fn serialize<'se, W: Write>( + &self, + serializer: &'se mut Serializer, + ) -> cbor_event::Result<&'se mut Serializer> { + let ar: [u8; 64] = self.0.clone().into(); + serializer + .write_array(cbor_event::Len::Len(2))? + .write_unsigned_integer(SPENDING_DATA_TAG_PUBKEY)? + .write_bytes(&ar[..]) + } +} + +#[cfg(test)] +mod tests { + use super::{Addr, AddressMatchXPub}; + use ed25519_bip32::XPub; + + fn assert_same_address(address: Addr, xpub: XPub) { + assert_eq!( + address.identical_with_pubkey(&xpub), + AddressMatchXPub::Yes, + "expected public key {} to match address {}", + xpub, + address, + ) + } + + #[test] + fn test_vector_1() { + let address = "DdzFFzCqrhsrcTVhLygT24QwTnNqQqQ8mZrq5jykUzMveU26sxaH529kMpo7VhPrt5pwW3dXeB2k3EEvKcNBRmzCfcQ7dTkyGzTs658C".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x6a, 0x50, 0x96, 0x89, 0xc6, 0x53, 0x17, 0x58, 0x65, 0x98, 0x5a, 0xd1, 0xe0, 0xeb, + 0x5f, 0xf9, 0xad, 0xa6, 0x99, 0x7a, 0xa4, 0x03, 0xe6, 0x48, 0x61, 0x4b, 0x3b, 0x78, + 0xfc, 0xba, 0x9c, 0x27, 0x30, 0x82, 0x28, 0xd9, 0x87, 0x2a, 0xf8, 0xb6, 0x5b, 0x98, + 0x7f, 0xf2, 0x3e, 0x1a, 0x20, 0xcd, 0x90, 0xd8, 0x34, 0x6c, 0x31, 0xf0, 0xed, 0xb8, + 0x99, 0x89, 0x52, 0xdc, 0x67, 0x66, 0x55, 0x80, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_2() { + let address = "DdzFFzCqrht4it4GYgBp4J39FNnKBsPFejSppARXHCf2gGiTJcwXzpRvgDmxPvKQ8aZZmVqcLUz5L66a8Ja46pfKVtFRaKyn9eKdvpaC".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0xff, 0x7b, 0xf1, 0x29, 0x9d, 0xf3, 0xd7, 0x17, 0x98, 0xae, 0xfd, 0xc4, 0xae, 0xa7, + 0xdb, 0x2f, 0x8d, 0xb7, 0x60, 0x46, 0x56, 0x94, 0x41, 0xea, 0xe5, 0x8b, 0x72, 0x23, + 0xb6, 0x8b, 0x44, 0x04, 0x82, 0x15, 0xcb, 0xac, 0x94, 0xbc, 0xb7, 0xf2, 0xcf, 0x33, + 0x6c, 0x6c, 0x18, 0xbc, 0x3e, 0x71, 0x3f, 0xfd, 0x82, 0x67, 0x59, 0x4f, 0xf6, 0x34, + 0x93, 0x32, 0xce, 0x4f, 0x98, 0x04, 0xa7, 0xff, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_3() { + let address = "DdzFFzCqrhsvNQtyViTvEdGxfdc5T1E5RorzFWjYodqjhFDy8fQxfDPccmTc4ePbvkiwvRkR8dtqQ1SHpH53fDSoxD17fo9f6WkRjjAA".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x5c, 0x36, 0x51, 0xe0, 0xeb, 0x9d, 0x6d, 0xc9, 0x64, 0x07, 0x13, 0x7c, 0xcc, 0x1f, + 0x37, 0x7a, 0x87, 0x94, 0x61, 0x77, 0xa5, 0x2c, 0xa3, 0x77, 0x2c, 0x6b, 0x4b, 0xeb, + 0x72, 0x39, 0x50, 0xdc, 0x50, 0x22, 0x46, 0x68, 0x21, 0x8b, 0x8b, 0x36, 0x62, 0x02, + 0xfe, 0x5b, 0x7d, 0x55, 0x6f, 0x50, 0x1c, 0x5c, 0x4e, 0x2d, 0x58, 0xe0, 0x54, 0x67, + 0xe1, 0xab, 0xc0, 0x44, 0xc6, 0xc1, 0xbf, 0x8e, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_4() { + let address = "DdzFFzCqrhsn7ZAhKy8mxkzW6G3wryM7K6bH38VAjE2FesJMxia3UviivMvGz146TP1FpDharxTE6nUgCCnZx2fmtKpmxAosg9Tf5b8y".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0xcd, 0x84, 0x2e, 0x01, 0x0d, 0x81, 0xa6, 0xbe, 0x1e, 0x16, 0x9f, 0xd6, 0x35, 0x21, + 0xdb, 0xb9, 0x5f, 0x42, 0x41, 0xfc, 0x82, 0x3f, 0x45, 0xb1, 0xcf, 0x1a, 0x1c, 0xb4, + 0xc5, 0x89, 0x57, 0x27, 0x1d, 0x4d, 0x14, 0x2a, 0x22, 0x94, 0xea, 0x5f, 0xa3, 0x16, + 0xa4, 0xad, 0xbf, 0xcd, 0x59, 0x7a, 0x7c, 0x89, 0x6a, 0x52, 0xa9, 0xa3, 0xa9, 0xce, + 0x49, 0x64, 0x4a, 0x10, 0x2d, 0x00, 0x71, 0x99, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_5() { + let address = "DdzFFzCqrhssTCJf4sv664bdQURovAwzx1hNKkMkNLwMNyaxZFuPSDdZTTRMcoDyXHuCiZhbD4umvMJcWGkvFMMzBoBUW5UBdBbDqXGX".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x5a, 0xac, 0x2d, 0xd0, 0xa8, 0xdc, 0x5d, 0x61, 0x0a, 0x4b, 0x6f, 0xdf, 0x3f, 0x5e, + 0xf1, 0xb6, 0x4a, 0xcb, 0x76, 0xb1, 0xe8, 0x1f, 0x6a, 0x35, 0x70, 0x31, 0xfa, 0x19, + 0xd5, 0xe6, 0x56, 0x9d, 0xcc, 0x37, 0xb7, 0xae, 0x6f, 0x39, 0x15, 0x82, 0xfb, 0x05, + 0x4b, 0x72, 0xba, 0xda, 0x90, 0xab, 0x14, 0x6c, 0xdd, 0x01, 0x42, 0x0e, 0x4b, 0x40, + 0x18, 0xf1, 0xa0, 0x55, 0x29, 0x82, 0xd2, 0x31, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_6() { + let address = "DdzFFzCqrhsfi5fFjJUHYPSnfTYrnMohzh3PrrtrVQgwua33HWPKUdTJXo3o77pSGCmDNrjYaAiZmJddaPW9iHyUDatvU2WhX7MgnNMy".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x2a, 0x6a, 0xd1, 0x51, 0x09, 0x96, 0xff, 0x2d, 0x10, 0x89, 0xcb, 0x8e, 0xd5, 0xf5, + 0xc0, 0x61, 0xf6, 0xad, 0x0a, 0xfb, 0xb5, 0x3d, 0x95, 0x40, 0xa0, 0xfc, 0x89, 0xef, + 0xc0, 0xa2, 0x63, 0xb9, 0x6d, 0xac, 0x00, 0xbd, 0x0d, 0x7b, 0xda, 0x7d, 0x16, 0x3a, + 0x08, 0xdb, 0x20, 0xba, 0x64, 0xb6, 0x33, 0x4d, 0xca, 0x34, 0xea, 0xc8, 0x2c, 0xf7, + 0xb4, 0x91, 0xc3, 0x5f, 0x5c, 0xae, 0xc7, 0xb0, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_7() { + let address = "DdzFFzCqrhsy2zYMDQRCF4Nw34C3P7aT5B7JwHFQ6gLAeoHgVXurCLPCm3AeV1nTa1Nd46uDoNt16cnsPFkb4fpLi1J17AmvphCtGFz2".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x0c, 0xd2, 0x15, 0x54, 0xa0, 0xf9, 0xb8, 0x25, 0x9c, 0x46, 0x88, 0xdd, 0x00, 0xfc, + 0x01, 0x88, 0x43, 0x50, 0x79, 0x76, 0x4f, 0xa5, 0x50, 0xfb, 0x57, 0x38, 0x2b, 0xff, + 0x43, 0xe2, 0xd8, 0xd8, 0x27, 0x27, 0x4e, 0x2a, 0x12, 0x9f, 0x86, 0xc3, 0x80, 0x88, + 0x34, 0x37, 0x4d, 0xfe, 0x3f, 0xda, 0xa6, 0x28, 0x48, 0x30, 0xb8, 0xf6, 0xe4, 0x0d, + 0x29, 0x93, 0xde, 0xa2, 0xfb, 0x0a, 0xbe, 0x82, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_8() { + let address = "DdzFFzCqrht8ygB5pLM4uVbS2x4ek2NTDx6R3DJqP7fUaWEkx8RA9UFR8CHitp2R74XLDP876Pe3KLUByHnrWrKWnffpqPpm14rPCxeP".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x1f, 0x0a, 0xb8, 0x33, 0xfd, 0xb1, 0xfa, 0x49, 0x58, 0xce, 0x74, 0x04, 0x81, 0x84, + 0x5b, 0x3a, 0x26, 0x6e, 0xfa, 0xab, 0x2d, 0x65, 0xd1, 0x6b, 0xdd, 0x3d, 0xfe, 0x7f, + 0xcb, 0xe4, 0x46, 0x30, 0x25, 0x9e, 0xd1, 0x91, 0x98, 0x93, 0x03, 0x9d, 0xfd, 0x40, + 0x02, 0x4a, 0x72, 0x03, 0x45, 0x5b, 0x03, 0xd6, 0xd0, 0x0d, 0x0a, 0x5c, 0xd6, 0xee, + 0x82, 0xde, 0x2e, 0xce, 0x73, 0x8a, 0xa1, 0xbf, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_9() { + let address = "DdzFFzCqrhssTywqjv3dw3EakpEydWQcc3phQzR3YF9NPgQN9Ftkx68FfLLnpJ4vhWo9mAjx5EcpM1wNvorSySrpARZGfk5QugHkVs58".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x16, 0xf7, 0xd2, 0x55, 0x32, 0x6d, 0x77, 0x6e, 0xc1, 0xb5, 0xed, 0xd2, 0x5f, 0x75, + 0xd3, 0xe3, 0xeb, 0xe0, 0xb9, 0xd4, 0x9c, 0xdd, 0xb2, 0x46, 0xd8, 0x0c, 0xf4, 0x1b, + 0x25, 0x24, 0x64, 0xb6, 0x24, 0x50, 0xa2, 0x4e, 0xf5, 0x98, 0x7b, 0x4b, 0xd6, 0x5e, + 0x0d, 0x25, 0x23, 0x43, 0xab, 0xa8, 0xef, 0x77, 0x93, 0x34, 0x79, 0xde, 0xa8, 0xdd, + 0xe2, 0x9e, 0xec, 0x56, 0xcc, 0x6a, 0xc0, 0x69, + ]); + assert_same_address(address, public_key) + } + + #[test] + fn test_vector_10() { + let address = "DdzFFzCqrhsqTG4t3uq5UBqFrxhxGVM6bvF4q1QcZXqUpizFddEEip7dx5rbife2s9o2fRU3hVKhRp4higog7As8z42s4AMw6Pcu8vL4".parse().unwrap(); + let public_key = XPub::from_bytes([ + 0x97, 0xb8, 0x6c, 0x69, 0xd1, 0x2a, 0xf1, 0x64, 0xdc, 0x87, 0xf2, 0x71, 0x26, 0x8f, + 0x33, 0xbc, 0x4d, 0xee, 0xb0, 0xdf, 0xd3, 0x73, 0xc3, 0xfd, 0x3b, 0xac, 0xd4, 0x47, + 0x53, 0xa3, 0x1d, 0xe7, 0x8f, 0x10, 0xe5, 0x55, 0x03, 0x7c, 0xd4, 0x00, 0x43, 0x6c, + 0xcf, 0xd5, 0x38, 0x0d, 0xbb, 0xcd, 0x4d, 0x7c, 0x28, 0x0a, 0xef, 0x9e, 0xc7, 0x57, + 0x4a, 0xe0, 0xac, 0xac, 0x0c, 0xf7, 0x9e, 0x89, + ]); + assert_same_address(address, public_key) + } +} diff --git a/rust/src/legacy_address/base58.rs b/rust/src/legacy_address/base58.rs new file mode 100644 index 00000000..e736f1dc --- /dev/null +++ b/rust/src/legacy_address/base58.rs @@ -0,0 +1,176 @@ +//! bitcoin's base58 encoding format + +pub const ALPHABET: &'static str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +pub enum Error { + /// error when a given character is not part of the supported + /// base58 `ALPHABET`. Contains the index of the faulty byte. + UnknownSymbol(usize), +} +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + &Error::UnknownSymbol(idx) => write!(f, "Unknown symbol at byte index {}", idx), + } + } +} +impl ::std::error::Error for Error {} + +pub type Result = ::std::result::Result; + +/// encode in base58 the given input +pub fn encode(input: &[u8]) -> String { + // unsafe to unwrap the result of `String::from_utf8` as the given + // returned bytes are valid within the base58 alphabet (they are all ASCII7) + String::from_utf8(base_encode(ALPHABET, input)).unwrap() +} + +/// decode from base58 the given input +pub fn decode(input: &str) -> Result> { + base_decode(ALPHABET, input.as_bytes()) +} + +/// decode from base58 the given input +//pub fn decode_bytes(input: &[u8]) -> Result> { +// base_decode(ALPHABET, input) +//} + +#[cfg(test)] +mod tests { + fn encode(input: &[u8], expected: &str) { + let encoded = super::encode(input); + assert_eq!(encoded, expected); + } + fn decode(expected: &[u8], input: &str) { + let decoded = super::decode(input).unwrap(); + assert_eq!(decoded.as_slice(), expected); + } + + #[test] + fn test_vector_1() { + encode(b"\0\0\0\0", "11111"); + decode(b"\0\0\0\0", "11111"); + } + + #[test] + fn test_vector_2() { + encode(b"This is awesome!", "BRY7dK2V98Sgi7CFWiZbap"); + decode(b"This is awesome!", "BRY7dK2V98Sgi7CFWiZbap"); + } + + #[test] + fn test_vector_3() { + encode(b"Hello World...", "TcgsE5dzphUWfjcb9i5"); + decode(b"Hello World...", "TcgsE5dzphUWfjcb9i5"); + } + + #[test] + fn test_vector_4() { + encode(b"\0abc", "1ZiCa"); + decode(b"\0abc", "1ZiCa"); + } + + #[test] + fn test_vector_5() { + encode(b"\0\0abc", "11ZiCa"); + decode(b"\0\0abc", "11ZiCa"); + } + + #[test] + fn test_vector_6() { + encode(b"\0\0\0abc", "111ZiCa"); + decode(b"\0\0\0abc", "111ZiCa"); + } + + #[test] + fn test_vector_7() { + encode(b"\0\0\0\0abc", "1111ZiCa"); + decode(b"\0\0\0\0abc", "1111ZiCa"); + } + + #[test] + fn test_vector_8() { + encode( + b"abcdefghijklmnopqrstuvwxyz", + "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f", + ); + decode( + b"abcdefghijklmnopqrstuvwxyz", + "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f", + ); + } +} + +fn base_encode(alphabet_s: &str, input: &[u8]) -> Vec { + let alphabet = alphabet_s.as_bytes(); + let base = alphabet.len() as u32; + + let mut digits = vec![0 as u8]; + for input in input.iter() { + let mut carry = input.clone() as u32; + for j in 0..digits.len() { + carry = carry + ((digits[j] as u32) << 8); + digits[j] = (carry % base) as u8; + carry = carry / base; + } + + while carry > 0 { + digits.push((carry % base) as u8); + carry = carry / base; + } + } + + let mut string = vec![]; + + let mut k = 0; + while (k < input.len()) && (input[k] == 0) { + string.push(alphabet[0]); + k += 1; + } + for digit in digits.iter().rev() { + string.push(alphabet[digit.clone() as usize]); + } + + string +} + +fn base_decode(alphabet_s: &str, input: &[u8]) -> Result> { + let alphabet = alphabet_s.as_bytes(); + let base = alphabet.len() as u32; + + let mut bytes: Vec = vec![0]; + let zcount = input.iter().take_while(|x| **x == alphabet[0]).count(); + + for i in zcount..input.len() { + let value = match alphabet.iter().position(|&x| x == input[i]) { + Some(idx) => idx, + None => return Err(Error::UnknownSymbol(i)), + }; + let mut carry = value as u32; + for j in 0..bytes.len() { + carry = carry + (bytes[j] as u32 * base); + bytes[j] = carry as u8; + carry = carry >> 8; + } + + while carry > 0 { + bytes.push(carry as u8); + carry = carry >> 8; + } + } + let leading_zeros = bytes.iter().rev().take_while(|x| **x == 0).count(); + if zcount > leading_zeros { + if leading_zeros > 0 { + for _ in 0..(zcount - leading_zeros - 1) { + bytes.push(0); + } + } else { + for _ in 0..zcount { + bytes.push(0); + } + } + } + bytes.reverse(); + Ok(bytes) +} diff --git a/rust/src/legacy_address/cbor.rs b/rust/src/legacy_address/cbor.rs new file mode 100644 index 00000000..0abd9deb --- /dev/null +++ b/rust/src/legacy_address/cbor.rs @@ -0,0 +1,99 @@ +//! the CBOR util and compatible with the haskell usage... + +pub mod util { + //! CBor util and other stuff + + use crate::legacy_address::crc32::crc32; + use cbor_event::{self, de::Deserializer, se::Serializer, Len, cbor}; + + pub fn encode_with_crc32_(t: &T, s: &mut Serializer) -> cbor_event::Result<()> + where + T: cbor_event::Serialize, + W: ::std::io::Write + Sized, + { + let bytes = cbor!(t)?; + let crc32 = crc32(&bytes); + s.write_array(Len::Len(2))? + .write_tag(24)? + .write_bytes(&bytes)? + .write_unsigned_integer(crc32 as u64)?; + Ok(()) + } + pub fn raw_with_crc32( + raw: &mut Deserializer, + ) -> cbor_event::Result> { + let len = raw.array()?; + assert!(len == Len::Len(2)); + + let tag = raw.tag()?; + if tag != 24 { + return Err(cbor_event::Error::CustomError(format!( + "Invalid Tag: {} but expected 24", + tag + ))); + } + let bytes = raw.bytes()?; + + let crc = raw.unsigned_integer()?; + + let found_crc = crc32(&bytes); + + if crc != found_crc as u64 { + return Err(cbor_event::Error::CustomError(format!( + "Invalid CRC32: 0x{:x} but expected 0x{:x}", + crc, found_crc + ))); + } + + Ok(bytes) + } + + #[cfg(test)] + #[cfg(feature = "with-bench")] + mod bench { + use super::*; + use cbor_event::{ + self, + de::RawCbor, + se::{Serialize, Serializer}, + }; + + #[cfg(feature = "with-bench")] + use test; + + const CBOR: &'static [u8] = &[ + 0x82, 0xd8, 0x18, 0x53, 0x52, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x72, 0x61, 0x6e, 0x64, + 0x6f, 0x6d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x71, 0xad, 0x58, 0x36, + ]; + + const BYTES: &'static [u8] = b"some bytes"; + + #[bench] + fn encode_crc32_with_cbor_event(b: &mut test::Bencher) { + b.iter(|| { + let _ = encode_with_crc32_(&Test(BYTES), Serializer::new_vec()).unwrap(); + }) + } + + #[bench] + fn decode_crc32_with_cbor_event(b: &mut test::Bencher) { + b.iter(|| { + let mut raw = RawCbor::from(CBOR); + let bytes = raw_with_crc32(&mut raw).unwrap(); + }) + } + + struct Test(&'static [u8]); + impl Serialize for Test { + fn serialize<'se, W>( + &self, + serializer: &'se mut Serializer, + ) -> cbor_event::Result<&'se mut Serializer> + where + W: ::std::io::Write, + { + serializer.write_bytes(self.0) + } + } + } +} diff --git a/rust/src/legacy_address/crc32.rs b/rust/src/legacy_address/crc32.rs new file mode 100644 index 00000000..bce1e914 --- /dev/null +++ b/rust/src/legacy_address/crc32.rs @@ -0,0 +1,323 @@ +const CRC_TABLE: [u32; 256] = [ + 0x00000000u32, + 0x77073096u32, + 0xee0e612cu32, + 0x990951bau32, + 0x076dc419u32, + 0x706af48fu32, + 0xe963a535u32, + 0x9e6495a3u32, + 0x0edb8832u32, + 0x79dcb8a4u32, + 0xe0d5e91eu32, + 0x97d2d988u32, + 0x09b64c2bu32, + 0x7eb17cbdu32, + 0xe7b82d07u32, + 0x90bf1d91u32, + 0x1db71064u32, + 0x6ab020f2u32, + 0xf3b97148u32, + 0x84be41deu32, + 0x1adad47du32, + 0x6ddde4ebu32, + 0xf4d4b551u32, + 0x83d385c7u32, + 0x136c9856u32, + 0x646ba8c0u32, + 0xfd62f97au32, + 0x8a65c9ecu32, + 0x14015c4fu32, + 0x63066cd9u32, + 0xfa0f3d63u32, + 0x8d080df5u32, + 0x3b6e20c8u32, + 0x4c69105eu32, + 0xd56041e4u32, + 0xa2677172u32, + 0x3c03e4d1u32, + 0x4b04d447u32, + 0xd20d85fdu32, + 0xa50ab56bu32, + 0x35b5a8fau32, + 0x42b2986cu32, + 0xdbbbc9d6u32, + 0xacbcf940u32, + 0x32d86ce3u32, + 0x45df5c75u32, + 0xdcd60dcfu32, + 0xabd13d59u32, + 0x26d930acu32, + 0x51de003au32, + 0xc8d75180u32, + 0xbfd06116u32, + 0x21b4f4b5u32, + 0x56b3c423u32, + 0xcfba9599u32, + 0xb8bda50fu32, + 0x2802b89eu32, + 0x5f058808u32, + 0xc60cd9b2u32, + 0xb10be924u32, + 0x2f6f7c87u32, + 0x58684c11u32, + 0xc1611dabu32, + 0xb6662d3du32, + 0x76dc4190u32, + 0x01db7106u32, + 0x98d220bcu32, + 0xefd5102au32, + 0x71b18589u32, + 0x06b6b51fu32, + 0x9fbfe4a5u32, + 0xe8b8d433u32, + 0x7807c9a2u32, + 0x0f00f934u32, + 0x9609a88eu32, + 0xe10e9818u32, + 0x7f6a0dbbu32, + 0x086d3d2du32, + 0x91646c97u32, + 0xe6635c01u32, + 0x6b6b51f4u32, + 0x1c6c6162u32, + 0x856530d8u32, + 0xf262004eu32, + 0x6c0695edu32, + 0x1b01a57bu32, + 0x8208f4c1u32, + 0xf50fc457u32, + 0x65b0d9c6u32, + 0x12b7e950u32, + 0x8bbeb8eau32, + 0xfcb9887cu32, + 0x62dd1ddfu32, + 0x15da2d49u32, + 0x8cd37cf3u32, + 0xfbd44c65u32, + 0x4db26158u32, + 0x3ab551ceu32, + 0xa3bc0074u32, + 0xd4bb30e2u32, + 0x4adfa541u32, + 0x3dd895d7u32, + 0xa4d1c46du32, + 0xd3d6f4fbu32, + 0x4369e96au32, + 0x346ed9fcu32, + 0xad678846u32, + 0xda60b8d0u32, + 0x44042d73u32, + 0x33031de5u32, + 0xaa0a4c5fu32, + 0xdd0d7cc9u32, + 0x5005713cu32, + 0x270241aau32, + 0xbe0b1010u32, + 0xc90c2086u32, + 0x5768b525u32, + 0x206f85b3u32, + 0xb966d409u32, + 0xce61e49fu32, + 0x5edef90eu32, + 0x29d9c998u32, + 0xb0d09822u32, + 0xc7d7a8b4u32, + 0x59b33d17u32, + 0x2eb40d81u32, + 0xb7bd5c3bu32, + 0xc0ba6cadu32, + 0xedb88320u32, + 0x9abfb3b6u32, + 0x03b6e20cu32, + 0x74b1d29au32, + 0xead54739u32, + 0x9dd277afu32, + 0x04db2615u32, + 0x73dc1683u32, + 0xe3630b12u32, + 0x94643b84u32, + 0x0d6d6a3eu32, + 0x7a6a5aa8u32, + 0xe40ecf0bu32, + 0x9309ff9du32, + 0x0a00ae27u32, + 0x7d079eb1u32, + 0xf00f9344u32, + 0x8708a3d2u32, + 0x1e01f268u32, + 0x6906c2feu32, + 0xf762575du32, + 0x806567cbu32, + 0x196c3671u32, + 0x6e6b06e7u32, + 0xfed41b76u32, + 0x89d32be0u32, + 0x10da7a5au32, + 0x67dd4accu32, + 0xf9b9df6fu32, + 0x8ebeeff9u32, + 0x17b7be43u32, + 0x60b08ed5u32, + 0xd6d6a3e8u32, + 0xa1d1937eu32, + 0x38d8c2c4u32, + 0x4fdff252u32, + 0xd1bb67f1u32, + 0xa6bc5767u32, + 0x3fb506ddu32, + 0x48b2364bu32, + 0xd80d2bdau32, + 0xaf0a1b4cu32, + 0x36034af6u32, + 0x41047a60u32, + 0xdf60efc3u32, + 0xa867df55u32, + 0x316e8eefu32, + 0x4669be79u32, + 0xcb61b38cu32, + 0xbc66831au32, + 0x256fd2a0u32, + 0x5268e236u32, + 0xcc0c7795u32, + 0xbb0b4703u32, + 0x220216b9u32, + 0x5505262fu32, + 0xc5ba3bbeu32, + 0xb2bd0b28u32, + 0x2bb45a92u32, + 0x5cb36a04u32, + 0xc2d7ffa7u32, + 0xb5d0cf31u32, + 0x2cd99e8bu32, + 0x5bdeae1du32, + 0x9b64c2b0u32, + 0xec63f226u32, + 0x756aa39cu32, + 0x026d930au32, + 0x9c0906a9u32, + 0xeb0e363fu32, + 0x72076785u32, + 0x05005713u32, + 0x95bf4a82u32, + 0xe2b87a14u32, + 0x7bb12baeu32, + 0x0cb61b38u32, + 0x92d28e9bu32, + 0xe5d5be0du32, + 0x7cdcefb7u32, + 0x0bdbdf21u32, + 0x86d3d2d4u32, + 0xf1d4e242u32, + 0x68ddb3f8u32, + 0x1fda836eu32, + 0x81be16cdu32, + 0xf6b9265bu32, + 0x6fb077e1u32, + 0x18b74777u32, + 0x88085ae6u32, + 0xff0f6a70u32, + 0x66063bcau32, + 0x11010b5cu32, + 0x8f659effu32, + 0xf862ae69u32, + 0x616bffd3u32, + 0x166ccf45u32, + 0xa00ae278u32, + 0xd70dd2eeu32, + 0x4e048354u32, + 0x3903b3c2u32, + 0xa7672661u32, + 0xd06016f7u32, + 0x4969474du32, + 0x3e6e77dbu32, + 0xaed16a4au32, + 0xd9d65adcu32, + 0x40df0b66u32, + 0x37d83bf0u32, + 0xa9bcae53u32, + 0xdebb9ec5u32, + 0x47b2cf7fu32, + 0x30b5ffe9u32, + 0xbdbdf21cu32, + 0xcabac28au32, + 0x53b39330u32, + 0x24b4a3a6u32, + 0xbad03605u32, + 0xcdd70693u32, + 0x54de5729u32, + 0x23d967bfu32, + 0xb3667a2eu32, + 0xc4614ab8u32, + 0x5d681b02u32, + 0x2a6f2b94u32, + 0xb40bbe37u32, + 0xc30c8ea1u32, + 0x5a05df1bu32, + 0x2d02ef8du32, +]; + +/// structure to compute the CRC32 of chunks of bytes. +/// +/// This structure allows implements the `Write` trait making it easier +/// to compute the crc32 of a stream. +/// +pub struct Crc32(u32); +impl Crc32 { + /// initialise a new CRC32 state + #[inline] + pub fn new() -> Self { + Crc32(0xFFFF_FFFF) + } + + /// update the CRC32 with the given bytes. + /// + /// beware that the order in which you update the Crc32 + /// matter. + #[inline] + pub fn update<'a, I>(&'a mut self, bytes: I) -> &mut Self + where + I: IntoIterator, + { + for byte in bytes { + let index = (self.0 ^ (*byte as u32)) & 0xFF; + self.0 = (self.0 >> 8) ^ CRC_TABLE[index as usize]; + } + self + } + + /// finalize the CRC32, recovering the computed value + #[inline] + pub fn finalize(self) -> u32 { + self.0 ^ 0xFFFF_FFFF + } +} +impl ::std::io::Write for Crc32 { + #[inline] + fn write(&mut self, bytes: &[u8]) -> Result { + self.update(bytes.iter()); + Ok(bytes.len()) + } + #[inline] + fn flush(&mut self) -> Result<(), std::io::Error> { + Ok(()) + } +} + +/// function is kept for compatibility. however prefer the +/// `Crc32` structure. +/// +pub fn crc32(input: &[u8]) -> u32 { + let mut crc32 = Crc32::new(); + crc32.update(input.iter()); + crc32.finalize() +} + +#[cfg(test)] +mod tests { + #[test] + fn crc32() { + let s = b"The quick brown fox jumps over the lazy dog"; + assert_eq!(0x414fa339, super::crc32(s)); + } +} diff --git a/rust/src/legacy_address/mod.rs b/rust/src/legacy_address/mod.rs new file mode 100644 index 00000000..62c6fbff --- /dev/null +++ b/rust/src/legacy_address/mod.rs @@ -0,0 +1,6 @@ +mod address; +mod base58; +mod cbor; +mod crc32; + +pub use address::{Addr, AddressMatchXPub, ExtendedAddr, ParseExtendedAddrError}; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 9a5c4622..ce18a945 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,19 +1,45 @@ +#![cfg_attr(feature = "with-bench", feature(test))] + +#[macro_use] +extern crate cfg_if; + +#[cfg(test)] +#[cfg(feature = "with-bench")] +extern crate test; + +#[cfg(test)] +extern crate quickcheck; +#[cfg(test)] +#[macro_use(quickcheck)] +extern crate quickcheck_macros; +extern crate hex; + + use std::io::{BufRead, Seek, Write}; use wasm_bindgen::prelude::*; // This file was code-generated using an experimental CDDL to rust tool: // https://github.com/Emurgo/cddl-codegen -use cbor_event::{self, de::Deserializer, se::{Serialize, Serializer}}; -use cbor_event::Type as CBORType; use cbor_event::Special as CBORSpecial; +use cbor_event::Type as CBORType; +use cbor_event::{ + self, + de::Deserializer, + se::{Serialize, Serializer}, +}; pub mod address; +pub mod chain_core; +pub mod chain_crypto; pub mod crypto; pub mod error; pub mod fees; +pub mod impl_mockchain; +pub mod legacy_address; pub mod serialization; pub mod tx_builder; +pub mod typed_bytes; #[macro_use] pub mod utils; @@ -77,7 +103,11 @@ impl Transaction { self.metadata.clone() } - pub fn new(body: &TransactionBody, witness_set: &TransactionWitnessSet, metadata: Option) -> Self { + pub fn new( + body: &TransactionBody, + witness_set: &TransactionWitnessSet, + metadata: Option, + ) -> Self { Self { body: body.clone(), witness_set: witness_set.clone(), @@ -230,8 +260,12 @@ impl TransactionBody { self.metadata_hash.clone() } - - pub fn new(inputs: &TransactionInputs, outputs: &TransactionOutputs, fee: &Coin, ttl: u32) -> Self { + pub fn new( + inputs: &TransactionInputs, + outputs: &TransactionOutputs, + fee: &Coin, + ttl: u32, + ) -> Self { Self { inputs: inputs.clone(), outputs: outputs.clone(), @@ -472,7 +506,17 @@ impl PoolParams { self.pool_metadata.clone() } - pub fn new(operator: &Ed25519KeyHash, vrf_keyhash: &VRFKeyHash, pledge: &Coin, cost: &Coin, margin: &UnitInterval, reward_account: &RewardAddress, pool_owners: &Ed25519KeyHashes, relays: &Relays, pool_metadata: Option) -> Self { + pub fn new( + operator: &Ed25519KeyHash, + vrf_keyhash: &VRFKeyHash, + pledge: &Coin, + cost: &Coin, + margin: &UnitInterval, + reward_account: &RewardAddress, + pool_owners: &Ed25519KeyHashes, + relays: &Relays, + pool_metadata: Option, + ) -> Self { Self { operator: operator.clone(), vrf_keyhash: vrf_keyhash.clone(), @@ -559,7 +603,11 @@ impl GenesisKeyDelegation { self.vrf_keyhash.clone() } - pub fn new(genesishash: &GenesisHash, genesis_delegate_hash: &GenesisDelegateHash, vrf_keyhash: &VRFKeyHash) -> Self { + pub fn new( + genesishash: &GenesisHash, + genesis_delegate_hash: &GenesisDelegateHash, + vrf_keyhash: &VRFKeyHash, + ) -> Self { Self { genesishash: genesishash.clone(), genesis_delegate_hash: genesis_delegate_hash.clone(), @@ -621,11 +669,15 @@ to_from_bytes!(Certificate); #[wasm_bindgen] impl Certificate { pub fn new_stake_registration(stake_registration: &StakeRegistration) -> Self { - Self(CertificateEnum::StakeRegistration(stake_registration.clone())) + Self(CertificateEnum::StakeRegistration( + stake_registration.clone(), + )) } pub fn new_stake_deregistration(stake_deregistration: &StakeDeregistration) -> Self { - Self(CertificateEnum::StakeDeregistration(stake_deregistration.clone())) + Self(CertificateEnum::StakeDeregistration( + stake_deregistration.clone(), + )) } pub fn new_stake_delegation(stake_delegation: &StakeDelegation) -> Self { @@ -641,11 +693,17 @@ impl Certificate { } pub fn new_genesis_key_delegation(genesis_key_delegation: &GenesisKeyDelegation) -> Self { - Self(CertificateEnum::GenesisKeyDelegation(genesis_key_delegation.clone())) + Self(CertificateEnum::GenesisKeyDelegation( + genesis_key_delegation.clone(), + )) } - pub fn new_move_instantaneous_rewards_cert(move_instantaneous_rewards_cert: &MoveInstantaneousRewardsCert) -> Self { - Self(CertificateEnum::MoveInstantaneousRewardsCert(move_instantaneous_rewards_cert.clone())) + pub fn new_move_instantaneous_rewards_cert( + move_instantaneous_rewards_cert: &MoveInstantaneousRewardsCert, + ) -> Self { + Self(CertificateEnum::MoveInstantaneousRewardsCert( + move_instantaneous_rewards_cert.clone(), + )) } pub fn kind(&self) -> CertificateKind { @@ -656,7 +714,9 @@ impl Certificate { CertificateEnum::PoolRegistration(_) => CertificateKind::PoolRegistration, CertificateEnum::PoolRetirement(_) => CertificateKind::PoolRetirement, CertificateEnum::GenesisKeyDelegation(_) => CertificateKind::GenesisKeyDelegation, - CertificateEnum::MoveInstantaneousRewardsCert(_) => CertificateKind::MoveInstantaneousRewardsCert, + CertificateEnum::MoveInstantaneousRewardsCert(_) => { + CertificateKind::MoveInstantaneousRewardsCert + } } } @@ -731,7 +791,7 @@ impl MoveInstantaneousReward { pub fn new(pot: MIRPot) -> Self { Self { pot, - rewards: std::collections::BTreeMap::new() + rewards: std::collections::BTreeMap::new(), } } @@ -748,7 +808,12 @@ impl MoveInstantaneousReward { } pub fn keys(&self) -> StakeCredentials { - StakeCredentials(self.rewards.iter().map(|(k, _v)| k.clone()).collect::>()) + StakeCredentials( + self.rewards + .iter() + .map(|(k, _v)| k.clone()) + .collect::>(), + ) } } @@ -857,9 +922,7 @@ impl MultiHostName { } pub fn new(dns_name: DnsName) -> Self { - Self { - dns_name: dns_name, - } + Self { dns_name: dns_name } } } @@ -982,7 +1045,6 @@ impl StakeCredentials { } } - #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct RewardAddresses(Vec); @@ -1033,7 +1095,12 @@ impl Withdrawals { } pub fn keys(&self) -> RewardAddresses { - RewardAddresses(self.0.iter().map(|(k, _v)| k.clone()).collect::>()) + RewardAddresses( + self.0 + .iter() + .map(|(k, _v)| k.clone()) + .collect::>(), + ) } } @@ -1109,7 +1176,9 @@ impl TransactionWitnessSet { #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct MapTransactionMetadatumToTransactionMetadatum(std::collections::BTreeMap); +pub struct MapTransactionMetadatumToTransactionMetadatum( + std::collections::BTreeMap, +); to_from_bytes!(MapTransactionMetadatumToTransactionMetadatum); @@ -1123,7 +1192,11 @@ impl MapTransactionMetadatumToTransactionMetadatum { self.0.len() } - pub fn insert(&mut self, key: &TransactionMetadatum, value: &TransactionMetadatum) -> Option { + pub fn insert( + &mut self, + key: &TransactionMetadatum, + value: &TransactionMetadatum, + ) -> Option { self.0.insert(key.clone(), value.clone()) } @@ -1132,7 +1205,12 @@ impl MapTransactionMetadatumToTransactionMetadatum { } pub fn keys(&self) -> TransactionMetadatums { - TransactionMetadatums(self.0.iter().map(|(k, _v)| k.clone()).collect::>()) + TransactionMetadatums( + self.0 + .iter() + .map(|(k, _v)| k.clone()) + .collect::>(), + ) } } @@ -1188,12 +1266,22 @@ to_from_bytes!(TransactionMetadatum); #[wasm_bindgen] impl TransactionMetadatum { - pub fn new_map_transaction_metadatum_to_transaction_metadatum(map_transaction_metadatum_to_transaction_metadatum: &MapTransactionMetadatumToTransactionMetadatum) -> Self { - Self(TransactionMetadatumEnum::MapTransactionMetadatumToTransactionMetadatum(map_transaction_metadatum_to_transaction_metadatum.clone())) + pub fn new_map_transaction_metadatum_to_transaction_metadatum( + map_transaction_metadatum_to_transaction_metadatum: &MapTransactionMetadatumToTransactionMetadatum, + ) -> Self { + Self( + TransactionMetadatumEnum::MapTransactionMetadatumToTransactionMetadatum( + map_transaction_metadatum_to_transaction_metadatum.clone(), + ), + ) } - pub fn new_arr_transaction_metadatum(arr_transaction_metadatum: &TransactionMetadatums) -> Self { - Self(TransactionMetadatumEnum::ArrTransactionMetadatum(arr_transaction_metadatum.clone())) + pub fn new_arr_transaction_metadatum( + arr_transaction_metadatum: &TransactionMetadatums, + ) -> Self { + Self(TransactionMetadatumEnum::ArrTransactionMetadatum( + arr_transaction_metadatum.clone(), + )) } pub fn new_int(int: &Int) -> Self { @@ -1210,17 +1298,25 @@ impl TransactionMetadatum { pub fn kind(&self) -> TransactionMetadatumKind { match &self.0 { - TransactionMetadatumEnum::MapTransactionMetadatumToTransactionMetadatum(_) => TransactionMetadatumKind::MapTransactionMetadatumToTransactionMetadatum, - TransactionMetadatumEnum::ArrTransactionMetadatum(_) => TransactionMetadatumKind::ArrTransactionMetadatum, + TransactionMetadatumEnum::MapTransactionMetadatumToTransactionMetadatum(_) => { + TransactionMetadatumKind::MapTransactionMetadatumToTransactionMetadatum + } + TransactionMetadatumEnum::ArrTransactionMetadatum(_) => { + TransactionMetadatumKind::ArrTransactionMetadatum + } TransactionMetadatumEnum::Int(_) => TransactionMetadatumKind::Int, TransactionMetadatumEnum::Bytes(_) => TransactionMetadatumKind::Bytes, TransactionMetadatumEnum::Text(_) => TransactionMetadatumKind::Text, } } - pub fn as_map_transaction_metadatum_to_transaction_metadatum(&self) -> Option { + pub fn as_map_transaction_metadatum_to_transaction_metadatum( + &self, + ) -> Option { match &self.0 { - TransactionMetadatumEnum::MapTransactionMetadatumToTransactionMetadatum(x) => Some(x.clone()), + TransactionMetadatumEnum::MapTransactionMetadatumToTransactionMetadatum(x) => { + Some(x.clone()) + } _ => None, } } @@ -1283,7 +1379,9 @@ impl TransactionMetadatumLabels { #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct TransactionMetadata(std::collections::BTreeMap); +pub struct TransactionMetadata( + std::collections::BTreeMap, +); to_from_bytes!(TransactionMetadata); @@ -1297,7 +1395,11 @@ impl TransactionMetadata { self.0.len() } - pub fn insert(&mut self, key: &TransactionMetadatumLabel, value: &TransactionMetadatum) -> Option { + pub fn insert( + &mut self, + key: &TransactionMetadatumLabel, + value: &TransactionMetadatum, + ) -> Option { self.0.insert(key.clone(), value.clone()) } @@ -1306,7 +1408,12 @@ impl TransactionMetadata { } pub fn keys(&self) -> TransactionMetadatumLabels { - TransactionMetadatumLabels(self.0.iter().map(|(k, _v)| k.clone()).collect::>()) + TransactionMetadatumLabels( + self.0 + .iter() + .map(|(k, _v)| k.clone()) + .collect::>(), + ) } } @@ -1421,7 +1528,9 @@ to_from_bytes!(MultisigScript); #[wasm_bindgen] impl MultisigScript { pub fn new_msig_pubkey(addr_keyhash: &Ed25519KeyHash) -> Self { - Self(MultisigScriptEnum::MsigPubkey(MsigPubkey::new(addr_keyhash))) + Self(MultisigScriptEnum::MsigPubkey(MsigPubkey::new( + addr_keyhash, + ))) } pub fn new_msig_all(multisig_scripts: &MultisigScripts) -> Self { @@ -1433,7 +1542,10 @@ impl MultisigScript { } pub fn new_msig_n_of_k(n: u32, multisig_scripts: &MultisigScripts) -> Self { - Self(MultisigScriptEnum::MsigNOfK(MsigNOfK::new(n, multisig_scripts))) + Self(MultisigScriptEnum::MsigNOfK(MsigNOfK::new( + n, + multisig_scripts, + ))) } pub fn kind(&self) -> MultisigScriptKind { @@ -1493,7 +1605,10 @@ impl Update { self.epoch.clone() } - pub fn new(proposed_protocol_parameter_updates: &ProposedProtocolParameterUpdates, epoch: Epoch) -> Self { + pub fn new( + proposed_protocol_parameter_updates: &ProposedProtocolParameterUpdates, + epoch: Epoch, + ) -> Self { Self { proposed_protocol_parameter_updates: proposed_protocol_parameter_updates.clone(), epoch: epoch.clone(), @@ -1528,7 +1643,9 @@ impl GenesisHashes { #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct ProposedProtocolParameterUpdates(std::collections::BTreeMap); +pub struct ProposedProtocolParameterUpdates( + std::collections::BTreeMap, +); to_from_bytes!(ProposedProtocolParameterUpdates); @@ -1542,7 +1659,11 @@ impl ProposedProtocolParameterUpdates { self.0.len() } - pub fn insert(&mut self, key: &GenesisHash, value: &ProtocolParamUpdate) -> Option { + pub fn insert( + &mut self, + key: &GenesisHash, + value: &ProtocolParamUpdate, + ) -> Option { self.0.insert(key.clone(), value.clone()) } @@ -1551,7 +1672,12 @@ impl ProposedProtocolParameterUpdates { } pub fn keys(&self) -> GenesisHashes { - GenesisHashes(self.0.iter().map(|(k, _v)| k.clone()).collect::>()) + GenesisHashes( + self.0 + .iter() + .map(|(k, _v)| k.clone()) + .collect::>(), + ) } } @@ -1575,10 +1701,7 @@ impl ProtocolVersion { } pub fn new(major: u32, minor: u32) -> Self { - Self { - major, - minor, - } + Self { major, minor } } } @@ -2046,4 +2169,4 @@ impl HeaderBody { protocol_version: protocol_version.clone(), } } -} \ No newline at end of file +} diff --git a/rust/src/typed_bytes/builder.rs b/rust/src/typed_bytes/builder.rs new file mode 100644 index 00000000..594fa9ed --- /dev/null +++ b/rust/src/typed_bytes/builder.rs @@ -0,0 +1,154 @@ +use crate::typed_bytes::ByteArray; +use std::marker::PhantomData; +use std::num::NonZeroUsize; + +/// A dynamically created buffer for T +#[derive(Clone)] +pub struct ByteBuilder { + buffer: Vec, + phantom: PhantomData, + expected: Option, +} + +impl From> for Vec { + fn from(bb: ByteBuilder) -> Vec { + bb.buffer + } +} + +impl ByteBuilder { + /// Create an unconstrained Builder + pub fn new() -> Self { + ByteBuilder { + buffer: Vec::new(), + phantom: PhantomData, + expected: None, + } + } + + /// Create a builder of fixed size + pub fn new_fixed(size: NonZeroUsize) -> Self { + ByteBuilder { + buffer: Vec::with_capacity(size.get()), + phantom: PhantomData, + expected: Some(size), + } + } + + /// Append an u8 in the builder + pub fn u8(self, v: u8) -> Self { + let mut buf = self.buffer; + buf.push(v); + ByteBuilder { + buffer: buf, + phantom: self.phantom, + expected: self.expected, + } + } + /// Append bytes in the builder + pub fn bytes(self, v: &[u8]) -> Self { + let mut buf = self.buffer; + buf.extend_from_slice(v); + ByteBuilder { + buffer: buf, + phantom: self.phantom, + expected: self.expected, + } + } + + /// fold over an iterator + pub fn fold(self, l: I, f: F) -> Self + where + I: Iterator, + F: FnMut(Self, I::Item) -> Self, + { + l.fold(self, f) + } + + /// write an iterator of maximum 256 items using the closure F + /// + /// note that the buffer contains a byte to represent the size + /// of the list + pub fn iter8(self, l: I, f: F) -> Self + where + I: Iterator + ExactSizeIterator, + F: FnMut(Self, I::Item) -> Self, + { + assert!(l.len() < 256); + let bb = self.u8(l.len() as u8); + l.fold(bb, f) + } + + /// write an iterator of maximum 2^16 items using the closure F + /// + /// note that the buffer contains 2 bytes to represent the size + /// of the list + pub fn iter16(self, l: I, f: F) -> Self + where + I: Iterator + ExactSizeIterator, + F: FnMut(Self, I::Item) -> Self, + { + assert!(l.len() < 65536); + let bb = self.u16(l.len() as u16); + l.fold(bb, f) + } + + pub fn sub(self, f: F) -> Self + where + F: Fn(ByteBuilder) -> ByteBuilder, + { + let res = f(ByteBuilder { + buffer: self.buffer, + phantom: PhantomData, + expected: None, + }); + ByteBuilder { + buffer: res.buffer, + phantom: self.phantom, + expected: self.expected, + } + } + + /// Append an u16 in the builder + pub fn u16(self, v: u16) -> Self { + self.bytes(&v.to_be_bytes()) + } + + /// Append an u32 in the builder + pub fn u32(self, v: u32) -> Self { + self.bytes(&v.to_be_bytes()) + } + + /// Append an u64 in the builder + pub fn u64(self, v: u64) -> Self { + self.bytes(&v.to_be_bytes()) + } + + /// Append an u128 in the builder + pub fn u128(self, v: u128) -> Self { + self.bytes(&v.to_be_bytes()) + } + + /// Finalize the buffer and return a fixed ByteArray of T + pub fn finalize(self) -> ByteArray { + match self.expected { + None => ByteArray::from_vec(self.buffer), + Some(expected_sz) => { + if expected_sz.get() == self.buffer.len() { + ByteArray::from_vec(self.buffer) + } else { + panic!( + "internal-error: bytebuilder: expected size {} but got {}", + expected_sz.get(), + self.buffer.len() + ) + } + } + } + } + + /// Finalize the buffer and return a fixed ByteArray of T + pub fn finalize_as_vec(self) -> Vec { + self.buffer + } +} diff --git a/rust/src/typed_bytes/mod.rs b/rust/src/typed_bytes/mod.rs new file mode 100644 index 00000000..2032d9ae --- /dev/null +++ b/rust/src/typed_bytes/mod.rs @@ -0,0 +1,114 @@ +mod builder; + +pub use builder::ByteBuilder; +use std::marker::PhantomData; + +/// A typed slice of bytes +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ByteSlice<'a, T> { + slice: &'a [u8], + phantom: PhantomData, +} + +/// A typed Array of bytes +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ByteArray { + array: Box<[u8]>, + phantom: PhantomData, +} + +impl AsRef<[u8]> for ByteArray { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +impl<'a, T> AsRef<[u8]> for ByteSlice<'a, T> { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +impl ByteArray { + pub fn as_byteslice(&self) -> ByteSlice<'_, T> { + ByteSlice { + slice: &self.array[..], + phantom: self.phantom, + } + } + pub fn as_slice(&self) -> &[u8] { + &self.array[..] + } + + fn from_vec(v: Vec) -> Self { + ByteArray { + array: v.into(), + phantom: PhantomData, + } + } +} + +impl From> for ByteArray { + fn from(v: Vec) -> Self { + ByteArray::from_vec(v) + } +} + +impl<'a, T> ByteSlice<'a, T> { + pub fn as_slice(&self) -> &[u8] { + self.slice + } + + fn sub_byteslice(&'a self, start: usize, size: usize) -> ByteSlice<'a, U> { + ByteSlice { + slice: &self.slice[start..start + size], + phantom: PhantomData, + } + } +} + +pub trait ByteAccessor { + const START_SIZE: (usize, usize); +} + +impl ByteArray { + pub fn sub(&self) -> ByteSlice<'_, U> + where + T: ByteAccessor, + { + let (start, size) = >::START_SIZE; + ByteSlice { + slice: &self.array[start..start + size], + phantom: PhantomData, + } + } +} + +impl<'a, T> ByteSlice<'a, T> { + pub fn sub(&'a self) -> ByteSlice<'a, U> + where + T: ByteAccessor, + { + let (start, sz) = >::START_SIZE; + self.sub_byteslice(start, sz) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub struct Big; + pub struct Little; + + impl ByteAccessor for Big { + const START_SIZE: (usize, usize) = (2, 3); + } + + #[test] + pub fn typed_accessor() { + let v: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let b: ByteArray = v.into(); + assert_eq!(b.sub::().as_slice(), [2, 3, 4]) + } +}