diff --git a/.gitignore b/.gitignore index bb46a99..f9cbc7a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *~ librustzcash-wrapper/target/ stack.yaml.lock +dist-newstyle/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b07525..cf7b6d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,184 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.1.1] + +### Added + +- Type for transaction creation errors +- Types for Sapling circuit parameters +- Function to create transaction +- Function to derive distinct transparent spending keys + +### Changed + +- Add `Read` instance for `Rseed` + +## [0.6.1.0] + +### Added + +- Function to create a raw transaction +- New types for transaction creation: + - `Rseed` + - `TransparentTxSpend` + - `SaplingTxSpend` + - `OrchardTxSpend` + - `OutgoingNote` +- Rust crates: + - `secp256k1` + - `jubjub` + - `rand_core` + + +### Changed + +- `DecodedNote` type now includes a field for `rho` and one for `rseed` + +## [0.6.0.0] + +### Added + +- Rust crates: + - `sapling-crypto` 0.1.3 + +### Changed + +- Modified handling of `ShieldedOutput`s based on new Rust crates +- Upgraded Rust crates: + - `orchard` to 0.7.1 + - `zcash_primitives` to 0.14.0 + - `zcash_client_backend` to 0.11.1 + + +## [0.5.5.4] + +### Added + +- Functions to update Sapling witnesses. +- Functions to update Orchard witnesses. + +## [0.5.5.3] + +### Added + +- Added function to generate an `ExchangeAddress` in Human Readable Format Using a `TransparentAddress` in HRF +- `encodeExchangeAddress` a function to create a `ExchangeAddress` in HRF +- `decodeExchangeAddress` a function to obtain a `TransparentAddress` object from an `ExchangeAddress` in HRF +- Added new type `ExchangeAddress` + +### Fixed + +- Orchard note nullifier calculation +- Sapling spend field parsing + +## [0.5.5.2] + +### Added + +- Added function to encode a Sappling Address in Human Readable Format Using a SaplingReceiver + `encodeSaplingAddress` a zcash sapling address is returned or Nothing if the function fails +- Added decoding and encoding test + +## [0.5.5.1] + +### Added + +- Added unction to decode a Sappling Address in Human Readable Format + `decodeSaplingAddress` returns 43 byte array containing +- Added a new Datatype `SaplingAddress` +- Added a new FFI function `rust_wrapper_decode_sapling_address` to haskell-rust interface + +### Changed + +- `TransparentAddress` type refactored +- `TransparentReceiver` added to replace old `TransparentAddress` +- `sha256` Function moved outside of `encodeTransparentReceiver` + +## [0.5.5.0] + +### Added + +- Added unction to decode Transparent Address in Human Readable Format + +### Changed + +- `TransparentAddress` type refactored +- `TransparentReceiver` added to replace old `TransparentAddress` +- `sha256` Function moved outside of `encodeTransparentReceiver` +## [0.5.4.1] + +### Added + +- Functions to handle Sapling commitment trees, incremental witnesses and note positions + +## [0.5.4.0] + +### Added +- Function to decode Orchard actions with a spending key +- Functions for Bech32 encoding +- Function to encode a Sapling address + +## [0.5.3.0] + +### Added + +- Function to decode Sapling outputs with a spending key + +### Fixed + +- Parsing of `TxIn` for FFI + +## [0.5.2.0] + +### Added + +- Functionality to parse transparent bundles from Zebra +- Types for transparent `TxIn`, `TxOut`, `OutPoint` + +## [0.5.1.0] + +### Added + +- Functionality to capture Sapling Spends + +### Changed + +- Modified the `makeZebraCall` function to handle errors explicitly +- Modified the RPC response to handle missing `result` field + +## [0.5.0.1] + +### Added + +- Function to encode a human-readable transparent address +- Function to generate a seed phrase +- Implementations of `Read` for types +- Function to make RPC calls to `zebrad` +- Function to encode unified addresses from receivers +- Function to generate an Orchard spending key +- Constants for Zcash protocol +- Types for Spending Keys and Receivers for Sapling and Orchard +- Function to generate an Orchard receiver +- Function to generate a Sapling receiver +- Function to generate a Transparent receiver + +### Changed + +- Update installation to `cabal` +- Updated Rust crates: + - `bech32` to 0.11 + - `orchard` to 0.7.0 + - `zcash_note_encryption` to 0.4.0 + - `zcash_primitives` to 0.13.0 + - `zcash_client_backend` to 0.10.0 + - `zip32` to 0.1.0 +- Changed the `UnifiedAddress` to allow for optional shielded receivers + +### Removed + +- `Makefile` + ## [0.3.0] ### Added diff --git a/COPYING.LESSER.md b/COPYING.LESSER.md deleted file mode 100644 index 6fb6a01..0000000 --- a/COPYING.LESSER.md +++ /dev/null @@ -1,157 +0,0 @@ -# GNU LESSER GENERAL PUBLIC LICENSE - -Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. - - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -This version of the GNU Lesser General Public License incorporates the -terms and conditions of version 3 of the GNU General Public License, -supplemented by the additional permissions listed below. - -## 0. Additional Definitions. - -As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the -GNU General Public License. - -"The Library" refers to a covered work governed by this License, other -than an Application or a Combined Work as defined below. - -An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - -A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - -The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - -The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - -## 1. Exception to Section 3 of the GNU GPL. - -You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - -## 2. Conveying Modified Versions. - -If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - -- a) under this License, provided that you make a good faith effort - to ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or -- b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - -## 3. Object Code Incorporating Material from Library Header Files. - -The object code form of an Application may incorporate material from a -header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - -- a) Give prominent notice with each copy of the object code that - the Library is used in it and that the Library and its use are - covered by this License. -- b) Accompany the object code with a copy of the GNU GPL and this - license document. - -## 4. Combined Works. - -You may convey a Combined Work under terms of your choice that, taken -together, effectively do not restrict modification of the portions of -the Library contained in the Combined Work and reverse engineering for -debugging such modifications, if you also do each of the following: - -- a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. -- b) Accompany the Combined Work with a copy of the GNU GPL and this - license document. -- c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. -- d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of - this License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with - the Library. A suitable mechanism is one that (a) uses at run - time a copy of the Library already present on the user's - computer system, and (b) will operate properly with a modified - version of the Library that is interface-compatible with the - Linked Version. -- e) Provide Installation Information, but only if you would - otherwise be required to provide such information under section 6 - of the GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the Application - with a modified version of the Linked Version. (If you use option - 4d0, the Installation Information must accompany the Minimal - Corresponding Source and Corresponding Application Code. If you - use option 4d1, you must provide the Installation Information in - the manner specified by section 6 of the GNU GPL for conveying - Corresponding Source.) - -## 5. Combined Libraries. - -You may place library facilities that are a work based on the Library -side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - -- a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities, conveyed under the terms of this License. -- b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - -## 6. Revised Versions of the GNU Lesser General Public License. - -The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -as you received it specifies that a certain numbered version of the -GNU Lesser General Public License "or any later version" applies to -it, you have the option of following the terms and conditions either -of that published version or of any later version published by the -Free Software Foundation. If the Library as you received it does not -specify a version number of the GNU Lesser General Public License, you -may choose any version of the GNU Lesser General Public License ever -published by the Free Software Foundation. - -If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/COPYING.md b/COPYING.md deleted file mode 100644 index 496acdb..0000000 --- a/COPYING.md +++ /dev/null @@ -1,675 +0,0 @@ -# GNU GENERAL PUBLIC LICENSE - -Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. - - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -## Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom -to share and change all versions of a program--to make sure it remains -free software for all its users. We, the Free Software Foundation, use -the GNU General Public License for most of our software; it applies -also to any other work released this way by its authors. You can apply -it to your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you -have certain responsibilities if you distribute copies of the -software, or if you modify it: responsibilities to respect the freedom -of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the -manufacturer can do so. This is fundamentally incompatible with the -aim of protecting users' freedom to change the software. The -systematic pattern of such abuse occurs in the area of products for -individuals to use, which is precisely where it is most unacceptable. -Therefore, we have designed this version of the GPL to prohibit the -practice for those products. If such problems arise substantially in -other domains, we stand ready to extend this provision to those -domains in future versions of the GPL, as needed to protect the -freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish -to avoid the special danger that patents applied to a free program -could make it effectively proprietary. To prevent this, the GPL -assures that patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - -## TERMS AND CONDITIONS - -### 0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds -of works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of -an exact copy. The resulting work is called a "modified version" of -the earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user -through a computer network, with no transfer of a copy, is not -conveying. - -An interactive user interface displays "Appropriate Legal Notices" to -the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -### 1. Source Code. - -The "source code" for a work means the preferred form of the work for -making modifications to it. "Object code" means any non-source form of -a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users can -regenerate automatically from other parts of the Corresponding Source. - -The Corresponding Source for a work in source code form is that same -work. - -### 2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not convey, -without conditions so long as your license otherwise remains in force. -You may convey covered works to others for the sole purpose of having -them make modifications exclusively for you, or provide you with -facilities for running those works, provided that you comply with the -terms of this License in conveying all material for which you do not -control copyright. Those thus making or running the covered works for -you must do so exclusively on your behalf, under your direction and -control, on terms that prohibit them from making any copies of your -copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under the -conditions stated below. Sublicensing is not allowed; section 10 makes -it unnecessary. - -### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such -circumvention is effected by exercising rights under this License with -respect to the covered work, and you disclaim any intention to limit -operation or modification of the work as a means of enforcing, against -the work's users, your or third parties' legal rights to forbid -circumvention of technological measures. - -### 4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -### 5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these -conditions: - -- a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. -- b) The work must carry prominent notices stating that it is - released under this License and any conditions added under - section 7. This requirement modifies the requirement in section 4 - to "keep intact all notices". -- c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. -- d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -### 6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms of -sections 4 and 5, provided that you also convey the machine-readable -Corresponding Source under the terms of this License, in one of these -ways: - -- a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. -- b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the Corresponding - Source from a network server at no charge. -- c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. -- d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. -- e) Convey the object code using peer-to-peer transmission, - provided you inform other peers where the object code and - Corresponding Source of the work are being offered to the general - public at no charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, -family, or household purposes, or (2) anything designed or sold for -incorporation into a dwelling. In determining whether a product is a -consumer product, doubtful cases shall be resolved in favor of -coverage. For a particular product received by a particular user, -"normally used" refers to a typical or common use of that class of -product, regardless of the status of the particular user or of the way -in which the particular user actually uses, or expects or is expected -to use, the product. A product is a consumer product regardless of -whether the product has substantial commercial, industrial or -non-consumer uses, unless such uses represent the only significant -mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to -install and execute modified versions of a covered work in that User -Product from a modified version of its Corresponding Source. The -information must suffice to ensure that the continued functioning of -the modified object code is in no case prevented or interfered with -solely because modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or -updates for a work that has been modified or installed by the -recipient, or for the User Product in which it has been modified or -installed. Access to a network may be denied when the modification -itself materially and adversely affects the operation of the network -or violates the rules and protocols for communication across the -network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -### 7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders -of that material) supplement the terms of this License with terms: - -- a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or -- b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or -- c) Prohibiting misrepresentation of the origin of that material, - or requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or -- d) Limiting the use for publicity purposes of names of licensors - or authors of the material; or -- e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or -- f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions - of it) with contractual assumptions of liability to the recipient, - for any liability that these contractual assumptions directly - impose on those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; the -above requirements apply either way. - -### 8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally -terminates your license, and (b) permanently, if the copyright holder -fails to notify you of the violation by some reasonable means prior to -60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -### 9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or run -a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -### 10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -### 11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims owned -or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within the -scope of its coverage, prohibits the exercise of, or is conditioned on -the non-exercise of one or more of the rights that are specifically -granted under this License. You may not convey a covered work if you -are a party to an arrangement with a third party that is in the -business of distributing software, under which you make payment to the -third party based on the extent of your activity of conveying the -work, and under which the third party grants, to any of the parties -who would receive the covered work from you, a discriminatory patent -license (a) in connection with copies of the covered work conveyed by -you (or copies made from those copies), or (b) primarily for and in -connection with specific products or compilations that contain the -covered work, unless you entered into that arrangement, or that patent -license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -### 12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under -this License and any other pertinent obligations, then as a -consequence you may not convey it at all. For example, if you agree to -terms that obligate you to collect a royalty for further conveying -from those to whom you convey the Program, the only way you could -satisfy both those terms and this License would be to refrain entirely -from conveying the Program. - -### 13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -### 14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions -of the GNU General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in -detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies that a certain numbered version of the GNU General Public -License "or any later version" applies to it, you have the option of -following the terms and conditions either of that numbered version or -of any later version published by the Free Software Foundation. If the -Program does not specify a version number of the GNU General Public -License, you may choose any version ever published by the Free -Software Foundation. - -If the Program specifies that a proxy can decide which future versions -of the GNU General Public License can be used, that proxy's public -statement of acceptance of a version permanently authorizes you to -choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -### 15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT -WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND -PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE -DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR -CORRECTION. - -### 16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR -CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT -NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR -LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM -TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER -PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -### 17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - -END OF TERMS AND CONDITIONS - -## How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these -terms. - -To do so, attach the following notices to the program. It is safest to -attach them to the start of each source file to most effectively state -the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper -mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands \`show w' and \`show c' should show the -appropriate parts of the General Public License. Of course, your -program's commands might be different; for a GUI interface, you would -use an "about box". - -You should also get your employer (if you work as a programmer) or -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. For more information on this, and how to apply and follow -the GNU GPL, see . - -The GNU General Public License does not permit incorporating your -program into proprietary programs. If your program is a subroutine -library, you may consider it more useful to permit linking proprietary -applications with the library. If this is what you want to do, use the -GNU Lesser General Public License instead of this License. But first, -please read . diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..03b6d4f --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022-2024 Vergara Technologies LLC + +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. + diff --git a/Makefile b/Makefile deleted file mode 100644 index 5943f32..0000000 --- a/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2022-2024 Vergara Technologies LLC -# -# This file is part of Zcash-Haskell. -# -# Zcash-Haskell is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. - -# You should have received a copy of the GNU Lesser General Public License along with -# Zcash-Haskell. If not, see . - -rustlib := librustzcash-wrapper/target/x86_64-unknown-linux-gnu/debug - -.PHONY: all - -all: haskell - -test: test/Spec.hs haskell - stack test - -haskell: src/ZcashHaskell/Orchard.hs src/ZcashHaskell/Sapling.hs src/ZcashHaskell/Types.hs src/ZcashHaskell/Utils.hs src/C/Zcash.chs package.yaml stack.yaml $(rustlib)/rustzcash_wrapper.h $(rustlib)/librustzcash_wrapper.a $(rustlib)/librustzcash_wrapper.so $(rustlib)/rustzcash_wrapper-uninstalled.pc - stack build - -$(rustlib)/rustzcash_wrapper.h: librustzcash-wrapper/src/lib.rs librustzcash-wrapper/Cargo.toml - cd librustzcash-wrapper && cargo +nightly cbuild diff --git a/README.md b/README.md index 8ffa015..a6f7cc1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,10 @@ A Haskell library to interact with the Zcash blockchain. ``` ./configure ``` -- Compile the Rust and Haskell portions: `make` +- Compile the Rust and Haskell portions: + ``` + cabal build + ``` ## Use diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..968fd0c --- /dev/null +++ b/Setup.hs @@ -0,0 +1,137 @@ +import Control.Exception (throw) +import Control.Monad (forM_, when) +import Data.Maybe (fromMaybe) +import Distribution.PackageDescription +import Distribution.Simple +import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..), localPkgDescr) +import Distribution.Simple.PreProcess +import Distribution.Simple.Program.Find + ( defaultProgramSearchPath + , findProgramOnSearchPath + ) +import Distribution.Simple.Setup +import Distribution.Simple.Utils + ( IODataMode(IODataModeBinary) + , maybeExit + , rawSystemStdInOut + ) +import Distribution.Verbosity (Verbosity) +import qualified Distribution.Verbosity as Verbosity +import GHC.Generics +import System.Directory + ( XdgDirectory(..) + , copyFile + , createDirectory + , createDirectoryIfMissing + , doesDirectoryExist + , doesFileExist + , getCurrentDirectory + , getDirectoryContents + , getHomeDirectory + , getXdgDirectory + ) +import System.Environment +import System.FilePath (()) +import Text.Regex +import Text.Regex.Base + +main :: IO () +main = defaultMainWithHooks hooks + where + hooks = + simpleUserHooks + { preConf = + \_ flags -> do + rsMake (fromFlag $ configVerbosity flags) + pure emptyHookedBuildInfo + , hookedPreProcessors = knownSuffixHandlers + , confHook = \a flags -> confHook simpleUserHooks a flags >>= rsAddDirs + , postClean = \_ flags _ _ -> rsClean (fromFlag $ cleanVerbosity flags) + } + +rsFolder :: FilePath +rsFolder = "librustzcash-wrapper" + +execCargo :: Verbosity -> String -> [String] -> IO () +execCargo verbosity command args = do + cargoPath <- + findProgramOnSearchPath Verbosity.normal defaultProgramSearchPath "cargo" + dir <- getCurrentDirectory + let cargoExec = + case cargoPath of + Just (p, _) -> p + Nothing -> "cargo" + cargoArgs = command : args + workingDir = Just (dir rsFolder) + thirdComponent (_, _, c) = c + maybeExit . fmap thirdComponent $ + rawSystemStdInOut + verbosity + cargoExec + cargoArgs + workingDir + Nothing + Nothing + IODataModeBinary + +rsMake :: Verbosity -> IO () +rsMake verbosity = do + execCargo verbosity "cbuild" [] + +rsAddDirs :: LocalBuildInfo -> IO LocalBuildInfo +rsAddDirs lbi' = do + localData <- getXdgDirectory XdgData "zcash-haskell" + createDirectoryIfMissing True localData + dir <- getCurrentDirectory + let rustIncludeDir = + dir rsFolder "target/x86_64-unknown-linux-gnu/debug" + rustLibDir = dir rsFolder "target/x86_64-unknown-linux-gnu/debug" + updateLbi lbi = lbi {localPkgDescr = updatePkgDescr (localPkgDescr lbi)} + updatePkgDescr pkgDescr = + pkgDescr {library = updateLib <$> library pkgDescr} + updateLib lib = lib {libBuildInfo = updateLibBi (libBuildInfo lib)} + updateLibBi libBuild = + libBuild + { includeDirs = rustIncludeDir : includeDirs libBuild + , extraLibDirs = rustLibDir : extraLibDirs libBuild + } + copyDir rustLibDir localData + pure $ updateLbi lbi' + +rsClean :: Verbosity -> IO () +rsClean verbosity = execCargo verbosity "clean" [] + +cabalFlag :: FlagName -> ConfigFlags -> Bool +cabalFlag name = + fromMaybe False . lookupFlagAssignment name . configConfigurationsFlags + +unlessFlagM :: FlagName -> ConfigFlags -> IO () -> IO () +unlessFlagM name flags action + | cabalFlag name flags = pure () + | otherwise = action + +applyUnlessM :: FlagName -> ConfigFlags -> (a -> IO a) -> a -> IO a +applyUnlessM name flags apply a + | cabalFlag name flags = pure a + | otherwise = apply a + +copyDir :: FilePath -> FilePath -> IO () +copyDir src dst = do + whenM (not <$> doesDirectoryExist src) $ + throw (userError "source does not exist") + --whenM (doesFileOrDirectoryExist dst) $ + --throw (userError "destination already exists") + createDirectoryIfMissing True dst + content <- getDirectoryContents src + let xs = filter (`notElem` [".", ".."]) content + forM_ xs $ \name -> do + let srcPath = src name + let dstPath = dst name + isDirectory <- doesDirectoryExist srcPath + if isDirectory + then copyDir srcPath dstPath + else copyFile srcPath dstPath + where + doesFileOrDirectoryExist x = orM [doesDirectoryExist x, doesFileExist x] + orM xs = or <$> sequence xs + whenM s r = s >>= flip when r diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..989ae5a --- /dev/null +++ b/cabal.project @@ -0,0 +1,13 @@ +packages: ./*.cabal + +with-compiler: ghc-9.4.8 + +source-repository-package + type: git + location: https://git.vergara.tech/Vergara_Tech/haskell-foreign-rust.git + tag: 335e804454cd30da2c526457be37e477f71e4665 + +source-repository-package + type: git + location: https://git.vergara.tech/Vergara_Tech/haskell-hexstring.git + tag: 39d8da7b11a80269454c2f134a5c834e0f3cb9a7 diff --git a/cabal.project.freeze b/cabal.project.freeze new file mode 100644 index 0000000..5cf3ed7 --- /dev/null +++ b/cabal.project.freeze @@ -0,0 +1,207 @@ +active-repositories: hackage.haskell.org:merge +constraints: any.Cabal ==3.8.1.0, + any.Cabal-syntax ==3.8.1.0, + any.HUnit ==1.6.2.0, + any.OneTuple ==0.4.1.1, + any.QuickCheck ==2.14.3, + QuickCheck -old-random +templatehaskell, + any.StateVar ==1.2.2, + any.aeson ==2.2.1.0, + aeson +ordered-keymap, + any.alex ==3.5.1.0, + any.ansi-terminal ==1.1, + ansi-terminal -example, + any.ansi-terminal-types ==1.1, + any.appar ==0.1.8, + any.array ==0.5.4.0, + any.asn1-encoding ==0.9.6, + any.asn1-parse ==0.9.5, + any.asn1-types ==0.3.4, + any.assoc ==1.1, + assoc +tagged, + any.async ==2.2.5, + async -bench, + any.attoparsec ==0.14.4, + attoparsec -developer, + any.attoparsec-aeson ==2.2.0.1, + any.base ==4.17.2.1, + any.base-orphans ==0.9.1, + any.base16 ==1.0, + any.base16-bytestring ==1.0.2.0, + any.base58-bytestring ==0.1.0, + any.base64-bytestring ==1.2.1.0, + any.basement ==0.0.16, + any.bifunctors ==5.6.1, + bifunctors +tagged, + any.binary ==0.8.9.1, + any.binary-orphans ==1.0.4.1, + any.bitvec ==1.1.5.0, + bitvec +simd, + any.blaze-builder ==0.4.2.3, + any.borsh ==0.3.0, + any.byteorder ==1.0.4, + any.bytes ==0.17.3, + any.bytestring ==0.11.5.3, + any.c2hs ==0.28.8, + c2hs +base3 -regression, + any.call-stack ==0.4.0, + any.case-insensitive ==1.2.1.0, + any.cborg ==0.2.10.0, + cborg +optimize-gmp, + any.cereal ==0.5.8.3, + cereal -bytestring-builder, + any.colour ==2.3.6, + any.comonad ==5.0.8, + comonad +containers +distributive +indexed-traversable, + any.conduit ==1.3.5, + any.conduit-extra ==1.3.6, + any.containers ==0.6.7, + any.contravariant ==1.5.5, + contravariant +semigroups +statevar +tagged, + any.cookie ==0.5.0, + any.crypton ==0.34, + crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes, + any.crypton-connection ==0.3.2, + any.crypton-x509 ==1.7.6, + any.crypton-x509-store ==1.6.9, + any.crypton-x509-system ==1.6.7, + any.crypton-x509-validation ==1.6.12, + any.cryptonite ==0.30, + cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes, + any.data-default ==0.7.1.1, + any.data-default-class ==0.1.2.0, + any.data-default-instances-containers ==0.0.1, + any.data-default-instances-dlist ==0.0.1, + any.data-default-instances-old-locale ==0.0.1, + any.data-fix ==0.3.2, + any.deepseq ==1.4.8.0, + any.directory ==1.3.7.1, + any.distributive ==0.6.2.1, + distributive +semigroups +tagged, + any.dlist ==1.0, + dlist -werror, + any.entropy ==0.4.1.10, + entropy -donotgetentropy, + any.envy ==2.1.2.0, + any.exceptions ==0.10.5, + any.filepath ==1.4.2.2, + any.foldable1-classes-compat ==0.1, + foldable1-classes-compat +tagged, + any.foreign-rust ==0.1.0, + any.generically ==0.1.1, + any.generics-sop ==0.5.1.4, + any.ghc-bignum ==1.3, + any.ghc-boot-th ==9.4.8, + any.ghc-prim ==0.9.1, + any.half ==0.3.1, + any.happy ==1.20.1.1, + any.hashable ==1.4.3.0, + hashable +integer-gmp -random-initial-seed, + any.haskell-lexer ==1.1.1, + any.haskoin-core ==1.0.4, + any.hexstring ==0.12.1.0, + any.hourglass ==0.2.12, + any.hsc2hs ==0.68.10, + hsc2hs -in-ghc-tree, + any.hspec ==2.11.7, + any.hspec-core ==2.11.7, + any.hspec-discover ==2.11.7, + any.hspec-expectations ==0.8.4, + any.http-client ==0.7.16, + http-client +network-uri, + any.http-client-tls ==0.3.6.3, + any.http-conduit ==2.3.8.3, + http-conduit +aeson, + any.http-types ==0.12.4, + any.indexed-traversable ==0.1.3, + any.indexed-traversable-instances ==0.1.1.2, + any.integer-conversion ==0.1.0.1, + any.integer-gmp ==1.1, + any.integer-logarithms ==1.0.3.1, + integer-logarithms -check-bounds +integer-gmp, + any.iproute ==1.7.12, + any.language-c ==0.9.3, + language-c -allwarnings +iecfpextension +usebytestrings, + any.memory ==0.18.0, + memory +support_bytestring +support_deepseq, + any.mime-types ==0.1.2.0, + any.mono-traversable ==1.0.17.0, + any.mtl ==2.2.2, + any.murmur3 ==1.0.5, + any.network ==3.1.4.0, + network -devel, + any.network-uri ==2.6.4.2, + any.old-locale ==1.0.0.7, + any.old-time ==1.1.0.4, + any.parsec ==3.1.16.1, + any.pem ==0.2.4, + any.pretty ==1.1.3.6, + any.primitive ==0.9.0.0, + any.process ==1.6.18.0, + any.quickcheck-io ==0.2.0, + any.random ==1.2.1.2, + any.regex-base ==0.94.0.2, + any.regex-compat ==0.95.2.1, + any.regex-posix ==0.96.0.1, + regex-posix -_regex-posix-clib, + any.resourcet ==1.3.0, + any.rts ==1.0.2, + any.safe ==0.3.21, + any.scientific ==0.3.7.0, + scientific -bytestring-builder -integer-simple, + any.secp256k1-haskell ==1.1.0, + any.semialign ==1.3, + semialign +semigroupoids, + any.semigroupoids ==6.0.0.1, + semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers, + any.serialise ==0.2.6.1, + serialise +newtime15, + any.socks ==0.6.1, + any.sop-core ==0.5.0.2, + any.split ==0.2.5, + any.splitmix ==0.1.0.5, + splitmix -optimised-mixer, + any.stm ==2.5.1.0, + any.streaming-commons ==0.2.2.6, + streaming-commons -use-bytestring-builder, + any.strict ==0.5, + any.string-conversions ==0.4.0.1, + any.tagged ==0.8.8, + tagged +deepseq +transformers, + any.template-haskell ==2.19.0.0, + any.text ==2.0.2, + any.text-iso8601 ==0.1, + any.text-short ==0.1.5, + text-short -asserts, + any.tf-random ==0.5, + any.th-abstraction ==0.6.0.0, + any.th-compat ==0.1.4, + any.these ==1.2, + any.time ==1.12.2, + any.time-compat ==1.9.6.1, + time-compat -old-locale, + any.tls ==2.0.1, + tls -devel, + any.transformers ==0.5.6.2, + any.transformers-compat ==0.7.2, + transformers-compat -five +five-three -four +generic-deriving +mtl -three -two, + any.typed-process ==0.2.11.1, + any.unix ==2.7.3, + any.unix-time ==0.4.12, + any.unliftio-core ==0.2.1.0, + any.unordered-containers ==0.2.20, + unordered-containers -debug, + any.utf8-string ==1.0.2, + any.uuid-types ==1.0.5.1, + any.vector ==0.13.1.0, + vector +boundschecks -internalchecks -unsafechecks -wall, + any.vector-algorithms ==0.9.0.1, + vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks, + any.vector-stream ==0.1.0.1, + any.void ==0.7.3, + void -safe, + any.wide-word ==0.1.6.0, + any.witherable ==0.4.2, + any.zlib ==0.7.0.0, + zlib -bundled-c-zlib +non-blocking-ffi +pkg-config +index-state: hackage.haskell.org 2024-03-06T20:26:39Z diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index a77c068..53583a4 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -58,12 +58,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - [[package]] name = "base64" version = "0.21.2" @@ -88,6 +82,33 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bellman" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afceed28bac7f9f5a508bca8aeeff51cdfa4770c0b967ac55c621e2ddfd6171" +dependencies = [ + "bitvec", + "blake2s_simd", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core", + "rayon", + "subtle", +] + [[package]] name = "bincode" version = "1.3.3" @@ -117,6 +138,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "bitvec" version = "1.0.1" @@ -282,10 +309,20 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.13.0" +name = "bs58" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "sha2 0.10.6", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byteorder" @@ -310,9 +347,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -355,12 +392,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - [[package]] name = "constant_time_eq" version = "0.2.5" @@ -435,6 +466,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.9.0" @@ -456,10 +496,13 @@ dependencies = [ ] [[package]] -name = "discard" -version = "1.0.4" +name = "document-features" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] [[package]] name = "either" @@ -666,6 +709,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "hdwallet" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a03ba7d4c9ea41552cd4351965ff96883e629693ae85005c501bb4b9e1c48a7" +dependencies = [ + "lazy_static", + "rand_core", + "ring", + "secp256k1", + "thiserror", +] + [[package]] name = "heck" version = "0.4.1" @@ -704,11 +760,11 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.3.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b" +checksum = "eb1872810fb725b06b8c153dde9e86f3ec26747b9b60096da7a869883b549cbe" dependencies = [ - "serde", + "either", ] [[package]] @@ -760,10 +816,13 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.6" +name = "js-sys" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] [[package]] name = "jubjub" @@ -790,9 +849,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.146" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -806,6 +865,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "log" version = "0.4.19" @@ -885,6 +950,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -928,9 +999,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" -version = "0.4.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6f418f2c25573923f81a091f38b4b19bc20f6c92b5070fb8f0711e64a2b998" +checksum = "1fb255c3ffdccd3c84fe9ebed72aef64fdc72e6a3e4180dd411002d47abaad42" dependencies = [ "aes", "bitvec", @@ -952,6 +1023,8 @@ dependencies = [ "subtle", "tracing", "zcash_note_encryption", + "zcash_spec", + "zip32", ] [[package]] @@ -1017,9 +1090,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "poly1305" @@ -1032,6 +1105,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1040,12 +1119,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.1.25" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 1.0.109", + "syn 2.0.32", ] [[package]] @@ -1057,12 +1136,6 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.66" @@ -1074,9 +1147,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", "prost-derive", @@ -1084,53 +1157,53 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", "heck", "itertools", - "lazy_static", "log", "multimap", + "once_cell", "petgraph", "prettyplease", "prost", "prost-types", "regex", - "syn 1.0.109", + "syn 2.0.32", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.32", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ "prost", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1211,13 +1284,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "redjubjub" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a60db2c3bc9c6fd1e8631fee75abc008841d27144be744951d6b9b75f9b569c" +dependencies = [ + "rand_core", + "reddsa", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1237,7 +1323,7 @@ checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.32", ] [[package]] @@ -1256,12 +1342,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] -name = "rustc_version" -version = "0.2.3" +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ - "semver", + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", ] [[package]] @@ -1270,7 +1371,7 @@ version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -1282,23 +1383,56 @@ dependencies = [ name = "rustzcash-wrapper" version = "0.1.0" dependencies = [ - "bech32 0.9.1", + "bech32 0.11.0", "borsh 0.10.3", "f4jumble", "haskell-ffi", + "incrementalmerkletree", + "jubjub", + "nonempty", "orchard", "proc-macro2", - "zcash_address", + "rand_core", + "sapling-crypto", + "secp256k1", + "zcash_address 0.2.0", "zcash_client_backend", "zcash_note_encryption", "zcash_primitives", + "zip32", ] [[package]] -name = "ryu" -version = "1.0.13" +name = "sapling-crypto" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "02f4270033afcb0c74c5c7d59c73cfd1040367f67f224fe7ed9a919ae618f1b7" +dependencies = [ + "aes", + "bellman", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381", + "byteorder", + "document-features", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "rand", + "rand_core", + "redjubjub", + "subtle", + "tracing", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] [[package]] name = "scopeguard" @@ -1306,6 +1440,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "secp256k1" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -1315,67 +1467,26 @@ dependencies = [ "zeroize", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" -version = "1.0.159" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.32", ] -[[package]] -name = "serde_json" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" version = "0.9.9" @@ -1400,76 +1511,30 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "shardtree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf20c7a2747d9083092e3a3eeb9a7ed75577ae364896bebbc5e0bdcd4e97735" +dependencies = [ + "bitflags 2.4.2", + "either", + "incrementalmerkletree", + "tracing", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "subtle" version = "2.4.1" @@ -1489,9 +1554,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -1535,46 +1600,27 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.32", ] [[package]] name = "time" -version = "0.2.27" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi", + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", ] [[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros-impl" +name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn 1.0.109", -] +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tinyvec" @@ -1602,15 +1648,15 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "quote", - "syn 1.0.109", + "syn 2.0.32", ] [[package]] @@ -1633,7 +1679,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.32", ] [[package]] @@ -1688,6 +1734,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "version_check" version = "0.9.4" @@ -1702,9 +1754,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1712,24 +1764,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.32", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1737,22 +1789,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.32", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "which" @@ -1869,39 +1931,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "804268e702b664fc09d3e2ce82786d0addf4ae57ba6976469be63e09066bf9f7" dependencies = [ "bech32 0.8.1", - "bs58", + "bs58 0.4.0", + "f4jumble", + "zcash_encoding", +] + +[[package]] +name = "zcash_address" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce173f1d9ed4f806e310bc3a873301531e7a6dc209928584d6404e3f8228ef4" +dependencies = [ + "bech32 0.9.1", + "bs58 0.5.0", "f4jumble", "zcash_encoding", ] [[package]] name = "zcash_client_backend" -version = "0.9.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55db8d2cb4ca82a71fa66ccd9fa5b211f5ab90c866721311ddd85f8f90d0701" +checksum = "001ec65dc2828ee648dc6d29f0944d7a877fe68ad06e001a203c11770ab1b3d4" dependencies = [ "base64", "bech32 0.9.1", "bls12_381", - "bs58", + "bs58 0.5.0", "crossbeam-channel", + "document-features", "group", + "hex", + "incrementalmerkletree", "memuse", "nom", - "orchard", + "nonempty", "percent-encoding", "prost", + "rand_core", "rayon", + "sapling-crypto", "secrecy", + "shardtree", "subtle", "time", "tonic-build", "tracing", "which", - "zcash_address", + "zcash_address 0.3.1", "zcash_encoding", + "zcash_keys", "zcash_note_encryption", "zcash_primitives", + "zip32", ] [[package]] @@ -1915,10 +1997,33 @@ dependencies = [ ] [[package]] -name = "zcash_note_encryption" -version = "0.3.0" +name = "zcash_keys" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb2149e6cd5fbee36c5b87c601715a8c35554602f7fe84af38b636afa2db318" +checksum = "4f22d3407fdd6992b49f037f23862ab376be6013be6f2d0bc85948a635edc1f5" +dependencies = [ + "bech32 0.9.1", + "bls12_381", + "bs58 0.5.0", + "document-features", + "group", + "memuse", + "nonempty", + "rand_core", + "sapling-crypto", + "subtle", + "tracing", + "zcash_address 0.3.1", + "zcash_encoding", + "zcash_primitives", + "zip32", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b4580cd6cee12e44421dac43169be8d23791650816bdb34e6ddfa70ac89c1c5" dependencies = [ "chacha20", "chacha20poly1305", @@ -1929,35 +2034,49 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.11.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914d2195a478d5b63191584dff126f552751115181857b290211ec88e68acc3e" +checksum = "9070e084570bb78aed4f8d71fd6254492e62c87a5d01e084183980e98117092d" dependencies = [ "aes", "bip0039", - "bitvec", "blake2b_simd", - "blake2s_simd", - "bls12_381", "byteorder", + "document-features", "equihash", "ff", "fpe", "group", + "hdwallet", "hex", "incrementalmerkletree", "jubjub", - "lazy_static", "memuse", "nonempty", "orchard", "rand", "rand_core", + "redjubjub", + "ripemd", + "sapling-crypto", + "secp256k1", "sha2 0.10.6", "subtle", - "zcash_address", + "tracing", + "zcash_address 0.3.1", "zcash_encoding", "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "zcash_spec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3bf58b673cb3dacd8ae09ba345998923a197ab0da70d6239d8e8838949e9b" +dependencies = [ + "blake2b_simd", ] [[package]] @@ -1977,5 +2096,16 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.32", +] + +[[package]] +name = "zip32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e" +dependencies = [ + "blake2b_simd", + "memuse", + "subtle", ] diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index 7496e7c..b8b2b37 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -4,19 +4,26 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] haskell-ffi.git = "https://github.com/BeFunctional/haskell-rust-ffi.git" haskell-ffi.rev = "2bf292e2e56eac8e9fb0fb2e1450cf4a4bd01274" f4jumble = "0.1" zcash_address = "0.2.0" borsh = "0.10" -bech32 = "0.9.1" -orchard = "0.4.0" -zcash_note_encryption = "0.3.0" -zcash_primitives = "0.11.0" -zcash_client_backend = "0.9.0" +bech32 = "0.11" +orchard = "0.7.1" +zcash_note_encryption = "0.4.0" +zcash_primitives = { version = "0.14.0", features = ["transparent-inputs"]} +zcash_client_backend = "0.11.1" +sapling-crypto = "0.1.3" +zip32 = "0.1.0" proc-macro2 = "1.0.66" +nonempty = "0.7.0" +incrementalmerkletree = "0.5.0" +secp256k1 = "0.26.0" +jubjub = "0.10.0" +rand_core = { version = "0.6.4", features = ["getrandom"]} + [features] capi = [] diff --git a/librustzcash-wrapper/rust-toolchain.toml b/librustzcash-wrapper/rust-toolchain.toml new file mode 100644 index 0000000..af255e0 --- /dev/null +++ b/librustzcash-wrapper/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-02-04" +components = [ "rustfmt", "rustc-dev"] +profile = "minimal" diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 8973af8..7397401 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -2,28 +2,18 @@ // // This file is part of Zcash-Haskell. // -// Zcash-Haskell is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -// details. -// -// You should have received a copy of the GNU Lesser General Public License along with -// Zcash-Haskell. If not, see . - use std::{ marker::PhantomData, io::{ Write, Cursor }, - fmt::{Debug, Display, Formatter} }; +use nonempty::NonEmpty; + +use rand_core::OsRng; + use f4jumble; use borsh::{BorshDeserialize, BorshSerialize}; @@ -35,28 +25,99 @@ use haskell_ffi::{ FromHaskell, HaskellSize, ToHaskell }; -use zcash_primitives::{ - zip32::Scope as SaplingScope, - transaction::components::sapling::{ - read_zkproof, +use secp256k1::SecretKey; + +use jubjub::Fr; + +use incrementalmerkletree::{ + frontier::CommitmentTree, + witness::IncrementalWitness +}; + +use zip32::{ + Scope as SaplingScope, + ChildIndex +}; + + +use sapling_crypto::{ + NOTE_COMMITMENT_TREE_DEPTH as SAPLING_DEPTH, + Node, + MerklePath, + PaymentAddress, + Anchor as SaplingAnchor, + value::ValueCommitment as SaplingValueCommitment, + note::{ + ExtractedNoteCommitment as SaplingNoteCommitment, + Note as SaplingNote, + Rseed + }, + keys::{ + PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey, + ExpandedSpendingKey, + FullViewingKey as SaplingFullViewingKey + }, + note_encryption::{ + SaplingDomain, + Zip212Enforcement + }, + bundle::{ GrothProofBytes, OutputDescription, - CompactOutputDescription + SpendDescription, + Authorized as SaplingAuthorized, + Bundle as SaplingBundle }, - sapling::{ - value::ValueCommitment as SaplingValueCommitment, - keys::{ - FullViewingKey as SaplingViewingKey, - PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey + value::NoteValue as SaplingNoteValue, + circuit::{ + SpendParameters, + OutputParameters + }, + zip32::{ + sapling_find_address, + DiversifierKey + } +}; + +use zcash_primitives::{ + merkle_tree::{ + read_commitment_tree, + write_commitment_tree, + read_incremental_witness, + write_incremental_witness + }, + legacy::{ + Script, + TransparentAddress + }, + zip339::{Count, Mnemonic}, + transaction::{ + Transaction, + fees::zip317::FeeRule, + builder::{ + Builder, + Error, + BuildConfig }, - note_encryption::SaplingDomain, - PaymentAddress, - note::ExtractedNoteCommitment as SaplingExtractedNoteCommitment + components::{ + amount::{ + Amount, + NonNegativeAmount + }, + transparent::{ + Bundle as TransparentBundle, + TxIn, + TxOut, + OutPoint, + Authorized + } + } }, - transaction::Transaction, + memo::MemoBytes, consensus::{ BranchId::Nu5, MainNetwork, + TestNetwork, BlockHeight } }; @@ -67,31 +128,49 @@ use zcash_address::{ ZcashAddress }; -use zcash_client_backend::keys::sapling::ExtendedFullViewingKey; - -use orchard::{ - Action, - keys::{FullViewingKey, PreparedIncomingViewingKey, Scope}, - note::{Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, - note_encryption::OrchardDomain, - primitives::redpallas::{VerificationKey, SpendAuth, Signature}, - value::ValueCommitment +use zcash_client_backend::encoding::decode_payment_address; +use zcash_client_backend::keys::sapling::{ + spending_key, + ExtendedFullViewingKey, + ExtendedSpendingKey, + DiversifiableFullViewingKey }; -use zcash_note_encryption::EphemeralKeyBytes; +use zcash_primitives::zip32::DiversifierIndex; + +use orchard::{ + Address as OrchardAddress, + Bundle as OrchardBundle, + bundle::{ + Authorized as OrchardAuthorized, + Flags + }, + Action, + keys::{SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope}, + note::{RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, + note_encryption::OrchardDomain, + primitives::redpallas::{VerificationKey, SpendAuth, Signature}, + tree::{ + MerklePath as OrchardMerklePath, + MerkleHashOrchard, + Anchor as OrchardAnchor + }, + value::{ + ValueCommitment, + NoteValue + } +}; use bech32::{ - decode, - u5, - FromBase32, - ToBase32, - Variant + Hrp, + Bech32, + Bech32m }; pub enum RW {} pub const RW: PhantomData = PhantomData; -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct RawData { hrp: Vec, bytes: Vec @@ -111,7 +190,7 @@ impl ToHaskell for RawData { //} //} -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct HrawTx { bytes: Vec, s: bool, @@ -125,14 +204,14 @@ impl ToHaskell for HrawTx { } } -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct HshieldedOutput { - cv: Vec, - cmu: Vec, - eph_key: Vec, - enc_txt: Vec, - out_txt: Vec, - proof: Vec + pub cv: Hhex, + cmu: Hhex, + eph_key: Hhex, + enc_txt: Hhex, + out_txt: Hhex, + proof: Hhex } impl FromHaskell for HshieldedOutput { @@ -150,22 +229,48 @@ impl ToHaskell for HshieldedOutput { } impl HshieldedOutput { - fn from_object(s: OutputDescription) -> Result { - let o = HshieldedOutput { cv: s.cv().to_bytes().to_vec(), cmu: s.cmu().to_bytes().to_vec(), eph_key: s.ephemeral_key().0.to_vec(), enc_txt: s.enc_ciphertext().to_vec(), out_txt: s.out_ciphertext().to_vec(), proof: s.zkproof().to_vec() }; - Ok(o) + fn from_object(s: &OutputDescription) -> HshieldedOutput { + HshieldedOutput { cv: Hhex{ bytes: s.cv().to_bytes().to_vec()}, cmu: Hhex{ bytes: s.cmu().to_bytes().to_vec()}, eph_key: Hhex{ bytes: s.ephemeral_key().0.to_vec()}, enc_txt: Hhex{ bytes: s.enc_ciphertext().to_vec()}, out_txt: Hhex{ bytes: s.out_ciphertext().to_vec()}, proof: Hhex{ bytes: s.zkproof().to_vec()} } + } + pub fn pack(sp: &[OutputDescription]) -> Vec { + let mut r = Vec::new(); + for s in sp { + r.push(HshieldedOutput::from_object(s)); + } + return r + } + pub fn to_output_description(&mut self) -> Result> { + let cv = SaplingValueCommitment::from_bytes_not_small_order(&to_array(self.cv.bytes.clone())).unwrap(); + let cmu = SaplingNoteCommitment::from_bytes(&to_array(self.cmu.bytes.clone())).unwrap(); + let eph_key = zcash_note_encryption::EphemeralKeyBytes::from(to_array(self.eph_key.bytes.clone())); + let x = OutputDescription::from_parts(cv, cmu, eph_key, to_array(self.enc_txt.bytes.clone()), to_array(self.out_txt.bytes.clone()), to_array(self.proof.bytes.clone())); + return Ok(x) } } -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct Hhex { + bytes: Vec +} + +impl ToHaskell for Hhex { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + + +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Haction { - nf: Vec, - rk: Vec, - cmx: Vec, - eph_key: Vec, - enc_txt: Vec, - out_txt: Vec, - cv: Vec, - auth: Vec + nf: Hhex, + rk: Hhex, + cmx: Hhex, + eph_key: Hhex, + enc_txt: Hhex, + out_txt: Hhex, + cv: Hhex, + auth: Hhex } impl FromHaskell for Haction { @@ -175,11 +280,24 @@ impl FromHaskell for Haction { } } -#[derive(BorshSerialize, BorshDeserialize)] +impl Haction { + pub fn pack(sp: &NonEmpty>>) -> Vec { + let mut r = Vec::new(); + for s in sp { + r.push(Haction {nf: Hhex { bytes: s.nullifier().to_bytes().to_vec()}, rk: Hhex { bytes: <[u8; 32]>::from(s.rk()).to_vec()}, cmx: Hhex{bytes: s.cmx().to_bytes().to_vec()}, eph_key: Hhex{ bytes: s.encrypted_note().epk_bytes.to_vec()}, enc_txt: Hhex {bytes: s.encrypted_note().enc_ciphertext.to_vec()}, out_txt: Hhex {bytes: s.encrypted_note().out_ciphertext.to_vec()}, cv: Hhex {bytes: s.cv_net().to_bytes().to_vec()}, auth: Hhex { bytes: <[u8; 64]>::from(s.authorization()).to_vec()}}); + } + return r + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Hnote { note: u64, recipient: Vec, - memo: Vec + memo: Vec, + nullifier: Vec, + rho: Vec, + rseed: Hrseed } impl ToHaskell for Hnote { @@ -189,7 +307,28 @@ impl ToHaskell for Hnote { } } -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct Hrseed { + kind: u8, + bytes: Vec +} + + +impl FromHaskell for Hrseed { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Hrseed::deserialize(buf)?; + Ok(x) + } +} + +impl ToHaskell for Hrseed { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Hua { net: u8, o_rec: Vec, @@ -222,7 +361,288 @@ impl Hua { } } -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct Htx { + txid: Vec, + locktime: u32, + expiry: u32, + t_bundle: HTBundle, + s_bundle: HSBundle, + o_bundle: HOBundle +} + +impl ToHaskell for Htx { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HTBundle { + empty: bool, + vin: Vec, + vout: Vec, + coinbase: bool +} + +impl ToHaskell for HTBundle { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTBundle { + pub fn from_bundle(b: &TransparentBundle) -> HTBundle { + HTBundle {empty: false, vin: b.vin.iter().map(HTxIn::pack).collect() , vout: b.vout.iter().map(HTxOut::pack).collect(), coinbase: b.is_coinbase()} + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HTxIn { + outpoint: Houtpoint, + script: Vec, + sequence: u32 +} + +impl ToHaskell for HTxIn { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTxIn { + pub fn pack(t: &TxIn) -> HTxIn { + return HTxIn { outpoint: Houtpoint::pack(&t.prevout), script: t.script_sig.0.clone(), sequence: t.sequence} + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HTxOut { + amt: i64, + script: Vec +} + +impl FromHaskell for HTxOut { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HTxOut::deserialize(buf)?; + Ok(x) + } +} + +impl ToHaskell for HTxOut { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HTxOut { + pub fn pack(t: &TxOut) -> HTxOut { + return HTxOut { amt: i64::from_le_bytes(t.value.to_i64_le_bytes()) , script: t.script_pubkey.0.clone() } + } + pub fn unpack(&self) -> TxOut { + TxOut { value: NonNegativeAmount::from_nonnegative_i64(self.amt).unwrap(), script_pubkey: Script(self.script.clone())} + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct Houtpoint { + hash: Vec, + index: u32 +} + +impl FromHaskell for Houtpoint { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Houtpoint::deserialize(buf)?; + Ok(x) + } +} + +impl ToHaskell for Houtpoint { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl Houtpoint { + pub fn pack(o: &OutPoint) -> Houtpoint { + return Houtpoint {hash: o.hash().to_vec() , index: o.n() } + } + + pub fn unpack(&self) -> OutPoint { + OutPoint::new(to_array(self.hash.clone()), self.index) + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HtransparentInput { + sk: Vec, + utxo: Houtpoint, + coin: HTxOut +} + +impl FromHaskell for HtransparentInput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HtransparentInput::deserialize(buf)?; + Ok(x) + } +} + +impl ToHaskell for HtransparentInput { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HSBundle { + empty: bool, + spends: Vec, + outputs: Vec , + value: i64, + sig: Vec +} + +impl ToHaskell for HSBundle { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HSBundle { + pub fn from_bundle(sb: &SaplingBundle) -> HSBundle { + let sig = <[u8; 64]>::from(sb.authorization().binding_sig); + return HSBundle {empty: false, spends: Hspend::pack(sb.shielded_spends()) , outputs: HshieldedOutput::pack(sb.shielded_outputs()) , value: i64::from(sb.value_balance()) , sig: sig.to_vec()} + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct Hspend { + cv: Hhex, + anchor: Hhex, + nullifier: Hhex, + rk: Hhex, + proof: Hhex, + authsig: Hhex +} + +impl ToHaskell for Hspend { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl Hspend { + pub fn pack(sp: &[SpendDescription]) -> Vec { + let mut r = Vec::new(); + for s in sp { + let rk = s.rk().clone(); + let sig = s.spend_auth_sig().clone(); + r.push(Hspend {cv: Hhex{bytes:s.cv().to_bytes().to_vec()}, anchor: Hhex{bytes:s.anchor().to_bytes().to_vec()}, nullifier: Hhex{bytes:s.nullifier().to_vec()}, rk: Hhex{bytes: <[u8; 32]>::from(rk).to_vec()}, proof: Hhex{bytes:s.zkproof().to_vec()}, authsig: Hhex{bytes: <[u8; 64]>::from(sig).to_vec()}}); + } + return r + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HsaplingInput { + sk: Vec, + note: Hnote, + iw: Vec +} + +impl FromHaskell for HsaplingInput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HsaplingInput::deserialize(buf)?; + Ok(x) + } +} + + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HorchardInput { + sk: Vec, + note: Hnote, + iw: Vec +} + +impl FromHaskell for HorchardInput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HorchardInput::deserialize(buf)?; + Ok(x) + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct Houtput { + kind: u8, + ovk: Vec, + to: Vec, + amt: u64, + memo: Vec, + chg: bool +} + +impl FromHaskell for Houtput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Houtput::deserialize(buf)?; + Ok(x) + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct HOBundle { + empty: bool, + actions: Vec, + flags: Hflags, + value: i64, + anchor: Hhex, + proof: Hhex, + bindingsig: Hhex +} + +impl ToHaskell for HOBundle { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl HOBundle { + pub fn from_bundle(b: &OrchardBundle) -> HOBundle { + return HOBundle {empty: false, actions: Haction::pack(b.actions()), flags: Hflags::pack(b.flags()), value: i64::from(b.value_balance()), anchor: Hhex{ bytes: b.anchor().to_bytes().to_vec()}, proof: Hhex { bytes: b.authorization().proof().as_ref().to_vec()}, bindingsig: Hhex {bytes: <[u8; 64]>::from(b.authorization().binding_signature()).to_vec()}} + } +} + + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct Hflags { + spends: bool, + outputs: bool +} + +impl ToHaskell for Hflags { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl Hflags { + pub fn pack(f: &Flags) -> Hflags { + return Hflags {spends: f.spends_enabled(), outputs: f.outputs_enabled()} + } +} + +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Hufvk { net: u8, orchard: Vec, @@ -251,7 +671,7 @@ impl Hufvk { } } -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Hsvk { vk: Vec, ovk: Vec @@ -334,10 +754,10 @@ pub extern "C" fn rust_wrapper_bech32decode( out_len: &mut usize ) { let input: String = marshall_from_haskell_var(input, input_len, RW); - let decodedBytes = bech32::decode(&input); - match decodedBytes { - Ok((hrp, bytes, variant)) => { - let rd = RawData {hrp: hrp.into(), bytes: Vec::::from_base32(&bytes).unwrap()}; + let decoded_bytes = bech32::decode(&input); + match decoded_bytes { + Ok((hrp, bytes)) => { + let rd = RawData {hrp: hrp.as_bytes().to_vec(), bytes}; marshall_to_haskell_var(&rd, out, out_len, RW); } Err(_e) => { @@ -347,6 +767,22 @@ pub extern "C" fn rust_wrapper_bech32decode( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_bech32m_encode( + hr: *const u8, + hr_len: usize, + b: *const u8, + b_len: usize, + out: *mut u8, + out_len: &mut usize + ) { + let hr: String = marshall_from_haskell_var(hr, hr_len, RW); + let hrp = Hrp::parse(&hr).unwrap(); + let b: Vec = marshall_from_haskell_var(b, b_len, RW); + let string = bech32::encode::(hrp, &b).unwrap(); + marshall_to_haskell_var(&string, out, out_len, RW); +} + #[no_mangle] pub extern "C" fn rust_wrapper_svk_decode( input: *const u8, @@ -355,11 +791,11 @@ pub extern "C" fn rust_wrapper_svk_decode( let input: Vec = marshall_from_haskell_var(input, input_len, RW); let svk = ExtendedFullViewingKey::read(&*input); match svk { - Ok(k) => { + Ok(_k) => { true } Err(e) => { - print!("{}", e); + print!("{:?}", e); false } } @@ -378,10 +814,10 @@ pub extern "C" fn rust_wrapper_svk_check_address( let sa = PaymentAddress::from_bytes(&to_array(address_input)).unwrap(); match svk { Ok(k) => { - let (div_index, def_address) = k.default_address(); + let (_div_index, def_address) = k.default_address(); sa == def_address } - Err(e) => { + Err(_e) => { false } } @@ -451,6 +887,71 @@ pub extern "C" fn rust_wrapper_ufvk_decode( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_esk_decrypt( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + external: bool, + net: bool, + pos: u64, + out: *mut u8, + out_len: &mut usize + ){ + let sk: Vec = marshall_from_haskell_var(key, key_len, RW); + let mut note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW); + let esk = ExtendedSpendingKey::from_bytes(&sk); + let main_domain = SaplingDomain::new(Zip212Enforcement::On); + //let test_domain = SaplingDomain::for_height(TestNetwork, BlockHeight::from_u32(2000000)); + let scope = if external { + SaplingScope::External + } else { + SaplingScope::Internal + }; + match esk { + Ok(k) => { + let action = note_input.to_output_description(); + match action { + Ok(action2) => { + let dfvk = k.to_diversifiable_full_viewing_key(); + let ivk = dfvk.to_ivk(scope); + let nk = dfvk.to_nk(scope); + let pivk = SaplingPreparedIncomingViewingKey::new(&ivk); + let result = zcash_note_encryption::try_note_decryption(&main_domain, &pivk, &action2); + match result { + Some((n, r, m)) => { + let nullifier = n.nf(&nk, pos); + let rseed = match n.rseed() { + Rseed::BeforeZip212(x) => { + Hrseed {kind: 1, bytes: x.to_bytes().to_vec()} + }, + Rseed::AfterZip212(y) => { + Hrseed {kind: 2, bytes: y.to_vec()} + } + }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: nullifier.to_vec(), rho: vec![0], rseed }; + marshall_to_haskell_var(&hn, out, out_len, RW); + }, + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed{ kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + }, + Err(_e1) => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed{ kind: 0, bytes: vec![0]} }; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + }, + Err(_e) => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed{ kind: 0, bytes: vec![0]} }; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( key: *const u8, @@ -461,13 +962,12 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( out_len: &mut usize ){ let evk: Vec = marshall_from_haskell_var(key, key_len, RW); - let note_input: Vec = marshall_from_haskell_var(note,note_len,RW); - let mut note_reader = Cursor::new(note_input); + let mut note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW); let svk = ExtendedFullViewingKey::read(&*evk); match svk { Ok(k) => { - let domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(2000000)); - let action2 = OutputDescription::read(&mut note_reader); + let domain = SaplingDomain::new(Zip212Enforcement::On); + let action2 = note_input.to_output_description(); match action2 { Ok(action3) => { let fvk = k.to_diversifiable_full_viewing_key().to_ivk(SaplingScope::External); @@ -475,23 +975,31 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action3); match result { Some((n, r, m)) => { - let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec() }; + let rseed = match n.rseed() { + Rseed::BeforeZip212(x) => { + Hrseed { kind: 1, bytes: x.to_bytes().to_vec()} + }, + Rseed::AfterZip212(y) => { + Hrseed { kind: 2, bytes: y.to_vec()} + } + }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: vec![0], rho: vec![0], rseed}; marshall_to_haskell_var(&hn, out, out_len, RW); } None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } }, Err(_e1) => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] , nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } } Err(_e) => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } @@ -509,12 +1017,12 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( let fvk_input: Vec = marshall_from_haskell_var(key, key_len, RW); let note_input: Haction = marshall_from_haskell_var(note, note_len, RW); let action: Action> = Action::from_parts( - Nullifier::from_bytes(&to_array(note_input.nf)).unwrap(), - VerificationKey::try_from(to_array(note_input.rk)).unwrap(), - ExtractedNoteCommitment::from_bytes(&to_array(note_input.cmx)).unwrap(), - TransmittedNoteCiphertext {epk_bytes: to_array(note_input.eph_key), enc_ciphertext: to_array(note_input.enc_txt), out_ciphertext: to_array(note_input.out_txt)}, - ValueCommitment::from_bytes(&to_array(note_input.cv)).unwrap(), - Signature::from(to_array(note_input.auth))); + Nullifier::from_bytes(&to_array(note_input.nf.bytes)).unwrap(), + VerificationKey::try_from(to_array(note_input.rk.bytes)).unwrap(), + ExtractedNoteCommitment::from_bytes(&to_array(note_input.cmx.bytes)).unwrap(), + TransmittedNoteCiphertext {epk_bytes: to_array(note_input.eph_key.bytes), enc_ciphertext: to_array(note_input.enc_txt.bytes), out_ciphertext: to_array(note_input.out_txt.bytes)}, + ValueCommitment::from_bytes(&to_array(note_input.cv.bytes)).unwrap(), + Signature::from(to_array(note_input.auth.bytes))); let fvk_array = to_array(fvk_input); let domain = OrchardDomain::for_nullifier(*action.nullifier()); let dec_fvk = FullViewingKey::from_bytes(&fvk_array); @@ -525,22 +1033,118 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); match result { Some((n, r, m)) => { - let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec() }; + let rho = n.rho().to_bytes().to_vec(); + let rseed = Hrseed {kind: 3, bytes: n.rseed().as_bytes().to_vec()}; + let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: vec![0], rho, rseed}; marshall_to_haskell_var(&hn, out, out_len, RW); } None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } }, None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } } +#[no_mangle] +pub extern "C" fn rust_wrapper_orchard_note_decrypt_sk( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + external: bool, + out: *mut u8, + out_len: &mut usize + ){ + let sk_input: Vec = marshall_from_haskell_var(key, key_len, RW); + let note_input: Haction = marshall_from_haskell_var(note, note_len, RW); + let action: Action> = Action::from_parts( + Nullifier::from_bytes(&to_array(note_input.nf.bytes)).unwrap(), + VerificationKey::try_from(to_array(note_input.rk.bytes)).unwrap(), + ExtractedNoteCommitment::from_bytes(&to_array(note_input.cmx.bytes)).unwrap(), + TransmittedNoteCiphertext {epk_bytes: to_array(note_input.eph_key.bytes), enc_ciphertext: to_array(note_input.enc_txt.bytes), out_ciphertext: to_array(note_input.out_txt.bytes)}, + ValueCommitment::from_bytes(&to_array(note_input.cv.bytes)).unwrap(), + Signature::from(to_array(note_input.auth.bytes))); + let sk_array = to_array(sk_input); + let domain = OrchardDomain::for_nullifier(*action.nullifier()); + let dec_sk = SpendingKey::from_bytes(sk_array).unwrap(); + let fvk = FullViewingKey::from(&dec_sk); + let ivk = if external { + fvk.to_ivk(Scope::External) + } else { + fvk.to_ivk(Scope::Internal) + }; + let pivk = PreparedIncomingViewingKey::new(&ivk); + let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); + match result { + Some((n, r, m)) => { + let rho = n.rho().to_bytes().to_vec(); + let rseed = Hrseed {kind: 3, bytes: n.rseed().as_bytes().to_vec()}; + let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: n.nullifier(&fvk).to_bytes().to_vec(), rho, rseed}; + marshall_to_haskell_var(&hn, out, out_len, RW); + } + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_tx_read( + tx: *const u8, + tx_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tx_input: Vec = marshall_from_haskell_var(tx, tx_len, RW); + let mut tx_reader = Cursor::new(tx_input); + let parsed_tx = Transaction::read(&mut tx_reader, Nu5); + match parsed_tx { + Ok(t) => { + let tb = t.transparent_bundle(); + let sb = t.sapling_bundle(); + let ob = t.orchard_bundle(); + let h1 = Htx + { txid: t.txid().as_ref().to_vec() + , locktime: t.lock_time() + , expiry: u32::from(t.expiry_height()) + , t_bundle: match tb { + Some(tb1) => {HTBundle::from_bundle(tb1)}, + None => {HTBundle {empty: true, vin: vec![HTxIn {outpoint: Houtpoint {hash: vec![0], index: 0}, script: vec![0], sequence: 0}], vout: vec![HTxOut {amt: 0, script: vec![0]}], coinbase: false}}} + , s_bundle: match sb { + Some(sb1) => {HSBundle::from_bundle(sb1)}, + None => {HSBundle{empty: true, spends: vec![Hspend{cv:Hhex { bytes: vec![0]} , anchor:Hhex { bytes: vec![0]} , nullifier:Hhex { bytes: vec![0]} , rk:Hhex { bytes: vec![0]} , proof:Hhex { bytes: vec![0]} , authsig:Hhex { bytes: vec![0]} }], outputs: vec![HshieldedOutput {cv: Hhex { bytes: vec![0]}, cmu: Hhex { bytes: vec![0]}, eph_key: Hhex { bytes: vec![0]}, enc_txt: Hhex { bytes: vec![0]}, out_txt: Hhex { bytes: vec![0]}, proof: Hhex { bytes: vec![0]}}], value: 0, sig: vec![0]}} } + , o_bundle: match ob { + Some(ob1) => {HOBundle::from_bundle(ob1)}, + None => {HOBundle{empty: true, actions: vec![Haction {nf:Hhex { bytes: vec![0]} , rk:Hhex { bytes: vec![0]} , cmx:Hhex { bytes: vec![0]} , eph_key:Hhex { bytes: vec![0]} , enc_txt:Hhex { bytes: vec![0]} , out_txt:Hhex { bytes: vec![0]} , cv:Hhex { bytes: vec![0]} , auth:Hhex { bytes: vec![0]} }], flags: Hflags{ spends:false, outputs:false}, value: 0, anchor: Hhex { bytes: vec![0]}, proof: Hhex { bytes: vec![0]} , bindingsig: Hhex { bytes: vec![0]}}} + } + }; + marshall_to_haskell_var(&h1, out, out_len, RW); + }, + Err(_e) => { + let h0 = Htx + {txid: vec![0], + locktime: 0, + expiry: 0, + t_bundle: HTBundle + {empty: true, + vin: vec![HTxIn {outpoint: Houtpoint {hash: vec![0], index: 0}, script: vec![0], sequence: 0}], + vout: vec![HTxOut {amt: 0, script: vec![0]}], + coinbase: true}, + s_bundle: HSBundle{empty: true, spends: vec![Hspend{cv:Hhex { bytes: vec![0]} , anchor:Hhex { bytes: vec![0]} , nullifier:Hhex { bytes: vec![0]} , rk:Hhex { bytes: vec![0]} , proof:Hhex { bytes: vec![0]} , authsig:Hhex { bytes: vec![0]} }], outputs: vec![HshieldedOutput {cv: Hhex { bytes: vec![0]}, cmu: Hhex { bytes: vec![0]}, eph_key: Hhex { bytes: vec![0]}, enc_txt: Hhex { bytes: vec![0]}, out_txt: Hhex { bytes: vec![0]}, proof: Hhex { bytes: vec![0]}}], value: 0, sig: vec![0]}, + o_bundle: HOBundle{empty: true, actions: vec![Haction {nf:Hhex { bytes: vec![0]} , rk:Hhex { bytes: vec![0]} , cmx:Hhex { bytes: vec![0]} , eph_key:Hhex { bytes: vec![0]} , enc_txt:Hhex { bytes: vec![0]} , out_txt:Hhex { bytes: vec![0]} , cv:Hhex { bytes: vec![0]} , auth:Hhex { bytes: vec![0]} }], flags: Hflags{ spends:false, outputs:false}, value: 0, anchor: Hhex { bytes: vec![0]}, proof: Hhex { bytes: vec![0]} , bindingsig: Hhex { bytes: vec![0]}} + }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_tx_parse( tx: *const u8, @@ -549,22 +1153,14 @@ pub extern "C" fn rust_wrapper_tx_parse( out_len: &mut usize ){ let tx_input: Vec = marshall_from_haskell_var(tx, tx_len, RW); - let tx_bytes: Vec = tx_input.clone(); let mut tx_reader = Cursor::new(tx_input); - let s_o = false; - let o_a = false; let parsed_tx = Transaction::read(&mut tx_reader, Nu5); match parsed_tx { Ok(t) => { let s_bundle = t.sapling_bundle(); match s_bundle { Some(b) => { - let mut s_output = Vec::new(); - for s_each_out in b.shielded_outputs().iter() { - let mut out_bytes = Vec::new(); - let _ = s_each_out.write_v4(&mut out_bytes); - s_output.push(out_bytes); - } + let s_output = HshieldedOutput::pack(b.shielded_outputs()); marshall_to_haskell_var(&s_output, out, out_len, RW); }, None => { @@ -582,3 +1178,706 @@ pub extern "C" fn rust_wrapper_tx_parse( } } } + +#[no_mangle] +pub extern "C" fn rust_wrapper_gen_seed_phrase( + out: *mut u8, + out_len: &mut usize + ){ + let mnemonic = Mnemonic::generate(Count::Words24); + let seed = mnemonic.phrase().as_bytes().to_vec(); + marshall_to_haskell_var(&seed, out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_recover_seed( + input: *const u8, + input_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let phrase: String = marshall_from_haskell_var(input, input_len, RW); + let mnemonic = Mnemonic::from_phrase(phrase); + match mnemonic { + Ok(m) => { + let s = m.to_seed("").to_vec(); + marshall_to_haskell_var(&s, out, out_len, RW); + }, + Err(_e) => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_spendingkey( + seed: *const u8, + seed_len: usize, + coin_type: u32, + acc_id: u32, + out: *mut u8, + out_len: &mut usize + ){ + let s: Vec = marshall_from_haskell_var(seed, seed_len, RW); + let sk = spending_key(&s, coin_type, zcash_primitives::zip32::AccountId::try_from(acc_id).unwrap()); + marshall_to_haskell_var(&sk.to_bytes().to_vec(), out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_paymentaddress( + extspk: *const u8, + extspk_len: usize, + div_ix: u32, + out: *mut u8, + out_len: &mut usize + ){ + let extspk: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); + if div_ix == 0 { + let sp_key = ExtendedSpendingKey::from_bytes(&extspk); + match sp_key { + Ok(sp_key_x) => { + let (_def_div, def_address) = sp_key_x.default_address(); + marshall_to_haskell_var(&def_address.to_bytes().to_vec(), out, out_len, RW); + }, + Err(_e) => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } + } else { + let expsk = ExpandedSpendingKey::from_spending_key(&extspk); + let fvk = SaplingFullViewingKey::from_expanded_spending_key(&expsk); + let dk = DiversifierKey::master(&extspk); + let result = sapling_find_address(&fvk, &dk, DiversifierIndex::from(div_ix)); + match result { + Some((_d, p_address)) => { + marshall_to_haskell_var(&p_address.to_bytes().to_vec(), out, out_len, RW); + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_chgpaymentaddress( + extspk: *const u8, + extspk_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let vexspk: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); + let vexspkp = &vexspk; + let extspku8 : &[u8] = &vexspkp; + let extspk = match ExtendedSpendingKey::from_bytes(&extspku8) { + Ok( k ) => k, + Err( _e ) => { + // error recovering ExtendedSpendingKey + marshall_to_haskell_var(&vec![0], out, out_len, RW); + return + } + }; + let dfvk = extspk.to_diversifiable_full_viewing_key(); + let ( _divIx, cPmtAddress ) = dfvk.change_address(); + marshall_to_haskell_var(&cPmtAddress.to_bytes().to_vec(), out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_derive_orchard_spending_key( + seed: *const u8, + seed_len: usize, + coin_type: u32, + acc_id: u32, + out: *mut u8, + out_len: &mut usize + ){ + let s: Vec = marshall_from_haskell_var(seed, seed_len, RW); + let sk = SpendingKey::from_zip32_seed(&s, coin_type, zip32::AccountId::try_from(acc_id).unwrap()); + match sk { + Ok(key) => { + marshall_to_haskell_var(&key.to_bytes().to_vec(), out, out_len, RW); + }, + Err(_e) => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_derive_orchard_receiver( + spend_key: *const u8, + spend_key_len: usize, + add_id: u32, + scope: bool, + out: *mut u8, + out_len: &mut usize + ){ + let sk_in: Vec = marshall_from_haskell_var(spend_key, spend_key_len, RW); + let sk = SpendingKey::from_bytes(sk_in[0..32].try_into().unwrap()).unwrap(); + let fvk = FullViewingKey::from(&sk); + let sc = if scope { + Scope::External + } else {Scope::Internal}; + let o_rec = fvk.address_at(add_id, sc); + marshall_to_haskell_var(&o_rec.to_raw_address_bytes().to_vec(), out, out_len, RW); + +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_bech32_encode( + hr: *const u8, + hr_len: usize, + b: *const u8, + b_len: usize, + out: *mut u8, + out_len: &mut usize + ) { + let hr: String = marshall_from_haskell_var(hr, hr_len, RW); + let hrp = Hrp::parse(&hr).unwrap(); + let b: Vec = marshall_from_haskell_var(b, b_len, RW); + let string = bech32::encode::(hrp, &b).unwrap(); + marshall_to_haskell_var(&string, out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_commitment_tree( + tree: *const u8, + tree_len: usize, + node: *const u8, + node_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let mut ct = read_commitment_tree::>, SAPLING_DEPTH>(tree_reader); + match ct { + Ok(mut comm_tree) => { + let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); + let sap_note_comm = SaplingNoteCommitment::from_bytes(&to_array(node_in)); + if sap_note_comm.is_some().into() { + let n = Node::from_cmu(&sap_note_comm.unwrap()); + comm_tree.append(n); + let mut out_bytes: Vec = Vec::new(); + let result = write_commitment_tree(&comm_tree, &mut out_bytes ); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } + } else { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } + +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_witness( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let ct: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + let inc_wit = IncrementalWitness::from_tree(ct); + let mut out_bytes: Vec = Vec::new(); + let result = write_incremental_witness(&inc_wit, &mut out_bytes); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_sapling_position( + wit: *const u8, + wit_len: usize, + ) -> u64 { + let wit_in: Vec = marshall_from_haskell_var(wit, wit_len, RW); + let wit_reader = Cursor::new(wit_in); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let pos = iw.witnessed_position(); + return u64::from(pos); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_update_sapling_witness( + wit: *const u8, + wit_len: usize, + cmus: *const u8, + cmus_len: usize, + out: *mut u8, + out_len: &mut usize + ) { + let wit_in: Vec = marshall_from_haskell_var(wit, wit_len, RW); + let wit_reader = Cursor::new(wit_in); + let mut iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let cmu: Vec> = marshall_from_haskell_var(cmus, cmus_len, RW); + for c in cmu { + let sap_note_comm = SaplingNoteCommitment::from_bytes(&to_array(c)); + if sap_note_comm.is_some().into() { + let n = Node::from_cmu(&sap_note_comm.unwrap()); + iw.append(n); + } + } + let mut out_bytes: Vec = Vec::new(); + let result = write_incremental_witness(&iw, &mut out_bytes); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_commitment_tree( + tree: *const u8, + tree_len: usize, + node: *const u8, + node_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let ct = read_commitment_tree::>, 32>(tree_reader); + match ct { + Ok(mut comm_tree) => { + let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); + let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(node_in)); + if orchard_note_comm.is_some().into() { + let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); + comm_tree.append(n); + let mut out_bytes: Vec = Vec::new(); + let result = write_commitment_tree(&comm_tree, &mut out_bytes ); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } + } else { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_witness( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let ct: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + let inc_wit = IncrementalWitness::from_tree(ct); + let mut out_bytes: Vec = Vec::new(); + let result = write_incremental_witness(&inc_wit, &mut out_bytes); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_position( + wit: *const u8, + wit_len: usize, + ) -> u64 { + let wit_in: Vec = marshall_from_haskell_var(wit, wit_len, RW); + let wit_reader = Cursor::new(wit_in); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let path = iw.path(); + match path { + Some(p) => { + let pos = p.position(); + return u64::from(pos); + }, + None => { + return 0; + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_update_orchard_witness( + wit: *const u8, + wit_len: usize, + cm: *const u8, + cm_len: usize, + out: *mut u8, + out_len: &mut usize + ) { + let wit_in: Vec = marshall_from_haskell_var(wit, wit_len, RW); + let wit_reader = Cursor::new(wit_in); + let mut iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let cmu: Vec> = marshall_from_haskell_var(cm, cm_len, RW); + for c in cmu { + let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(c)); + if orchard_note_comm.is_some().into() { + let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); + iw.append(n); + } + } + let mut out_bytes: Vec = Vec::new(); + let result = write_incremental_witness(&iw, &mut out_bytes); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let h0 = Hhex { bytes: vec![0]}; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_decode_sapling_address( + sapling: *const u8, + sapling_len: usize, + out: *mut u8, + out_len: &mut usize){ + let sapling_address_from_haskell : Vec = marshall_from_haskell_var(sapling, sapling_len, RW); + let sapling_address = std::str::from_utf8(&sapling_address_from_haskell).unwrap(); + + let mut netid = 0; + match sapling_address.find("1") { + Some(ix) => { + let netstr = &sapling_address[0..ix]; + if netstr == "zs" { + netid = 1 + } else { + if netstr == "ztestsapling" { + netid = 2 + } + } + match decode_payment_address(netstr, sapling_address) { + Ok( t )=> { + let address_to_bytes = t.to_bytes(); + let mut out_bytes_temp : [u8;44] = [0;44]; + out_bytes_temp[0] = netid; + let mut iy = 1; + for ix in 0..43 { + out_bytes_temp[iy] = address_to_bytes[ix]; + iy += 1; + } + let out_bytes: Vec = out_bytes_temp.to_vec(); + marshall_to_haskell_var(&out_bytes, out, out_len, RW); + } + Err(_e) => { + let h = vec![0]; + marshall_to_haskell_var(&h, out, out_len, RW); + } + } + } + None => { + let h = vec![0]; + marshall_to_haskell_var(&h, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_create_transaction( + sap_wit: *const u8, + sap_wit_len: usize, + orch_wit: *const u8, + orch_wit_len: usize, + t_input: *const u8, + t_input_len: usize, + s_input: *const u8, + s_input_len: usize, + o_input: *const u8, + o_input_len: usize, + out_list: *const u8, + out_list_len: usize, + sapspend: *const u8, + sapspend_len: usize, + sapoutput: *const u8, + sapoutput_len: usize, + net: bool, + bl_height: u32, + build: bool, + out: *mut u8, + out_len: &mut usize){ + let sap_wit_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); + let sap_wit_reader = Cursor::new(sap_wit_in); + let sap_iw = read_commitment_tree::>, SAPLING_DEPTH>(sap_wit_reader); + let sap_anchor = match sap_iw { + Ok(s_iw) => { + Some(SaplingAnchor::from(s_iw.root())) + }, + Err(_e) => { + None + } + }; + //println!("{:?}", sap_anchor); + let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); + let orch_wit_reader = Cursor::new(orch_wit_in); + let orch_iw = read_commitment_tree::>, 32>(orch_wit_reader); + let orch_anchor = match orch_iw { + Ok(o_iw) => { + Some(OrchardAnchor::from(o_iw.root())) + }, + Err(_e) => { + None + } + }; + let build_config = BuildConfig::Standard {sapling_anchor: sap_anchor, orchard_anchor: orch_anchor}; + let mut main_builder = Builder::new(MainNetwork, BlockHeight::from(bl_height), build_config); + let mut test_builder = Builder::new(TestNetwork, BlockHeight::from(bl_height), build_config); + let trans_input: Vec = marshall_from_haskell_var(t_input, t_input_len, RW); + for t_in in trans_input { + if t_in.sk.len() > 1 { + let k = SecretKey::from_slice(&t_in.sk).unwrap(); + if net { + match main_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) { + Ok(()) => { + //println!("added t-input in main"); + continue; + }, + Err(_e) => { println!("Error reading transparent input"); } + } + } else { + match test_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) { + Ok(()) => { + //println!("added t-input in test"); + continue; + }, + Err(_e) => { println!("Error reading transparent input"); } + } + } + } + } + let sap_input: Vec = marshall_from_haskell_var(s_input, s_input_len, RW); + for s_in in sap_input { + if s_in.sk.len() > 1 { + let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk); + match sp_key { + Ok(sk) => { + let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap(); + let rseed = if s_in.note.rseed.kind == 1 { + Rseed::BeforeZip212(Fr::from_bytes(&to_array(s_in.note.rseed.bytes)).unwrap()) + } else { + Rseed::AfterZip212(to_array(s_in.note.rseed.bytes)) + }; + let note = SaplingNote::from_parts(pay_addr, SaplingNoteValue::from_raw(s_in.note.note), rseed); + let wit_reader = Cursor::new(s_in.iw); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let merkle_path = iw.path().unwrap(); + if net { + let _mb = main_builder.add_sapling_spend::(&sk, note, merkle_path).unwrap(); + } else { + let _tb = test_builder.add_sapling_spend::(&sk, note, merkle_path).unwrap(); + } + }, + Err(_e) => { + continue; + } + } + } + } + let orch_input: Vec = marshall_from_haskell_var(o_input, o_input_len, RW); + for o_in in orch_input { + if o_in.sk.len() > 1 { + let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap(); + let pay_addr = OrchardAddress::from_raw_address_bytes(&to_array(o_in.note.recipient)).unwrap(); + let rho = Nullifier::from_bytes(&to_array(o_in.note.rho)).unwrap(); + let rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap(); + let val = NoteValue::from_raw(o_in.note.note); + let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap(); + let wit_reader = Cursor::new(o_in.iw); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let merkle_path = OrchardMerklePath::from(iw.path().unwrap()); + if net { + let _mb = main_builder.add_orchard_spend::(&sp_key, note, merkle_path).unwrap(); + } else { + let _tb = test_builder.add_orchard_spend::(&sp_key, note, merkle_path).unwrap(); + } + } + } + let outputs: Vec = marshall_from_haskell_var(out_list, out_list_len, RW); + for output in outputs { + match output.kind { + 1 => { + let recipient = TransparentAddress::PublicKeyHash(to_array(output.to)); + let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + if net { + let _mb = main_builder.add_transparent_output(&recipient, val); + } else { + let _tb = test_builder.add_transparent_output(&recipient, val); + } + }, + 2 => { + let recipient = TransparentAddress::ScriptHash(to_array(output.to)); + let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + if net { + let _mb = main_builder.add_transparent_output(&recipient, val); + } else { + let _tb = test_builder.add_transparent_output(&recipient, val); + } + }, + 3 => { + let ovk = Some(ExpandedSpendingKey::from_spending_key(&output.ovk).ovk); + let recipient = PaymentAddress::from_bytes(&to_array(output.to)).unwrap(); + let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + let memo = MemoBytes::from_bytes(&output.memo).unwrap(); + if net { + let _mb = main_builder.add_sapling_output::(ovk, recipient, val, memo); + } else { + let _tb = test_builder.add_sapling_output::(ovk, recipient, val, memo); + } + }, + 4 => { + let sk = SpendingKey::from_bytes(output.ovk[0..32].try_into().unwrap()).unwrap(); + let ovk = if output.chg { + Some(FullViewingKey::from(&sk).to_ovk(Scope::Internal)) + } else { + Some(FullViewingKey::from(&sk).to_ovk(Scope::External)) + }; + let recipient = OrchardAddress::from_raw_address_bytes(&to_array(output.to)).unwrap(); + let val = output.amt; + let memo = MemoBytes::from_bytes(&output.memo).unwrap(); + if net { + let _mb = main_builder.add_orchard_output::(ovk, recipient, val, memo); + } else { + let _tb = test_builder.add_orchard_output::(ovk, recipient, val, memo); + } + }, + _ => { + continue; + } + } + } + if build { + let spend_params_in: Vec = marshall_from_haskell_var(sapspend, sapspend_len, RW); + let spend_params_reader = Cursor::new(spend_params_in); + let spend_prover = SpendParameters::read(spend_params_reader, false).unwrap(); + let output_params_in: Vec = marshall_from_haskell_var(sapoutput, sapoutput_len, RW); + let output_params_reader = Cursor::new(output_params_in); + let output_prover = OutputParameters::read(output_params_reader, false).unwrap(); + let result = if net { + main_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard()) + } else { + test_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard()) + }; + match result { + Ok(r) => { + let mut out_bytes: Vec = Vec::new(); + let _t = r.transaction().write_v5(&mut out_bytes); + let h = Hhex {bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(e) => { + match e { + Error::InsufficientFunds(y) => { + let x = Hhex {bytes: vec![0]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::ChangeRequired(y1) => { + let x = Hhex {bytes: vec![1]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::Fee(_y2) => { + let x = Hhex {bytes: vec![2]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::Balance(y3) => { + let x = Hhex {bytes: vec![3]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::TransparentBuild(y4) => { + let x = Hhex {bytes: vec![4]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::SaplingBuild(y5) => { + let x = Hhex {bytes: vec![5]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::OrchardBuild(y7) => { + let x = Hhex {bytes: vec![6]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::OrchardSpend(y8) => { + let x = Hhex {bytes: vec![7]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::OrchardRecipient(y9) => { + let x = Hhex {bytes: vec![8]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::SaplingBuilderNotAvailable => { + let x = Hhex {bytes: vec![9]}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Error::OrchardBuilderNotAvailable => { + let x = Hhex {bytes: vec![10]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } + } + } + } else { + let result = if net { + main_builder.get_fee(&FeeRule::standard()) + } else { + test_builder.get_fee(&FeeRule::standard()) + }; + match result { + Ok(r) => { + let x = Hhex {bytes: r.to_i64_le_bytes().to_vec()}; + marshall_to_haskell_var(&x, out, out_len, RW); + }, + Err(e) => { + let x = Hhex {bytes: vec![2]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } + } +} diff --git a/package.yaml b/package.yaml deleted file mode 100644 index 4856888..0000000 --- a/package.yaml +++ /dev/null @@ -1,55 +0,0 @@ -name: zcash-haskell -version: 0.3.0 -git: "https://git.vergara.tech/Vergara_Tech/zcash-haskell" -license: LGPL-3 -author: "Rene Vergara" -maintainer: "pitmutt@vergara.tech" -copyright: "(c)2022-2024 Vergara Technologies LLC" - -extra-source-files: -- README.md -- CHANGELOG.md -- configure - -# Metadata used when publishing your package -synopsis: Utilities to interact with the Zcash blockchain -category: Blockchain - -# To avoid duplicated efforts in documentation and dealing with the -# complications of embedding Haddock markup inside cabal files, it is -# common to point users to the README.md file. -description: Please see the README on the repo at - -dependencies: -- base >= 4.7 && < 5 - -library: - source-dirs: src - dependencies: - - bytestring - - borsh >= 0.2 - - text - - foreign-rust - - generics-sop - - aeson - - http-conduit - - base58-bytestring - - cryptonite - - memory - pkg-config-dependencies: - - rustzcash_wrapper-uninstalled - -tests: - zcash-haskell-test: - main: Spec.hs - source-dirs: test - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N - dependencies: - - zcash-haskell - - hspec - - bytestring - - text - - aeson diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index ebda582..0f35be4 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -1,19 +1,7 @@ {- Copyright 2022-2024 Vergara Technologies LLC This file is part of Zcash-Haskell. - - Zcash-Haskell is free software: you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) any - later version. - - Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - details. - You should have received a copy of the GNU Lesser General Public License along with - Zcash-Haskell. If not, see . -} {-# LANGUAGE DerivingStrategies #-} @@ -29,10 +17,11 @@ module C.Zcash where import qualified Data.ByteString as BS import Codec.Borsh -import Data.Text (Text) +import qualified Data.Text as T import Data.Word import Data.Int import Data.Structured +import Data.HexString (HexString(..)) import Foreign.C.Types import Foreign.Rust.Marshall.External import Foreign.Rust.Marshall.Fixed @@ -43,6 +32,20 @@ import qualified Generics.SOP as SOP import qualified GHC.Generics as GHC import ZcashHaskell.Types +{# fun unsafe rust_wrapper_bech32decode as rustWrapperBech32Decode + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer RawData'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_bech32m_encode as rustWrapperBech32mEncode + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer (T.Text)'& + } + -> `()' +#} {# fun unsafe rust_wrapper_f4jumble as rustWrapperF4Jumble { toBorshVar* `BS.ByteString'& @@ -71,13 +74,6 @@ import ZcashHaskell.Types -> `Bool' #} -{# fun unsafe rust_wrapper_bech32decode as rustWrapperBech32Decode - { toBorshVar* `BS.ByteString'& - , getVarBuffer `Buffer RawData'& - } - -> `()' -#} - {# fun pure unsafe rust_wrapper_svk_decode as rustWrapperSaplingVkDecode { toBorshVar* `BS.ByteString'& } @@ -100,12 +96,23 @@ import ZcashHaskell.Types {# fun unsafe rust_wrapper_sapling_note_decrypt_v2 as rustWrapperSaplingNoteDecode { toBorshVar* `BS.ByteString'& - , toBorshVar* `BS.ByteString'& + , toBorshVar* `ShieldedOutput'& , getVarBuffer `Buffer DecodedNote'& } -> `()' #} +{# fun unsafe rust_wrapper_sapling_esk_decrypt as rustWrapperSaplingDecodeEsk + { toBorshVar* `BS.ByteString'& + , toBorshVar* `ShieldedOutput'& + , `Bool' + , `Bool' + , `Word64' + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer UnifiedFullViewingKey'& @@ -121,9 +128,167 @@ import ZcashHaskell.Types -> `()' #} -{# fun unsafe rust_wrapper_tx_parse as rustWrapperTxParse +{# fun unsafe rust_wrapper_orchard_note_decrypt_sk as rustWrapperOrchardNoteDecodeSK { toBorshVar* `BS.ByteString'& - , getVarBuffer `Buffer [BS.ByteString]'& + , toBorshVar* `OrchardAction'& + , `Bool' + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} +{# fun unsafe rust_wrapper_tx_parse as rustWrapperTxParse + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer [ShieldedOutput]'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_tx_read as rustWrapperTxRead + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer RawZebraTx'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_gen_seed_phrase as rustWrapperGenSeedPhrase + { getVarBuffer `Buffer Phrase'& } -> `()' +#} + +{# fun unsafe rust_wrapper_recover_seed as rustWrapperGetSeed + { toBorshVar* `Phrase'& + , getVarBuffer `Buffer Seed'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_spendingkey as rustWrapperSaplingSpendingkey + { toBorshVar* `BS.ByteString'& + , `Word32' + , `Word32' + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_paymentaddress as rustWrapperSaplingPaymentAddress + { toBorshVar* `BS.ByteString'& + , `Word32' + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_chgpaymentaddress as rustWrapperSaplingChgPaymentAddress + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_derive_orchard_spending_key as rustWrapperGenOrchardSpendKey + { toBorshVar* `BS.ByteString'& + , `Word32' + , `Word32' + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_derive_orchard_receiver as rustWrapperGenOrchardReceiver + { toBorshVar* `BS.ByteString'& + , `Word32' + , `Bool' + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_sapling_commitment_tree as rustWrapperReadSaplingCommitmentTree + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_sapling_witness as rustWrapperReadSaplingWitness + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun pure unsafe rust_wrapper_read_sapling_position as rustWrapperReadSaplingPosition + { toBorshVar* `BS.ByteString'& + } + -> `Word64' +#} + +{# fun unsafe rust_wrapper_bech32_encode as rustWrapperBech32Encode + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer (T.Text)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_decode_sapling_address as rustWrapperDecodeSaplingAddress + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_orchard_commitment_tree as rustWrapperReadOrchardCommitmentTree + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_orchard_witness as rustWrapperReadOrchardWitness + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun pure unsafe rust_wrapper_read_orchard_position as rustWrapperReadOrchardPosition + { toBorshVar* `BS.ByteString'& + } + -> `Word64' +#} + +{# fun unsafe rust_wrapper_update_sapling_witness as rustWrapperUpdateSaplingWitness + { toBorshVar* `BS.ByteString'& + , toBorshVar* `[BS.ByteString]'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_update_orchard_witness as rustWrapperUpdateOrchardWitness + { toBorshVar* `BS.ByteString'& + , toBorshVar* `[BS.ByteString]'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_create_transaction as rustWrapperCreateTx + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , toBorshVar* `[TransparentTxSpend]'& + , toBorshVar* `[SaplingTxSpend]'& + , toBorshVar* `[OrchardTxSpend]'& + , toBorshVar* `[OutgoingNote]'& + , toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , `Bool' + , `Word64' + , `Bool' + , getVarBuffer `Buffer HexString'& } -> `()' #} diff --git a/src/ZcashHaskell/Keys.hs b/src/ZcashHaskell/Keys.hs new file mode 100644 index 0000000..30484e7 --- /dev/null +++ b/src/ZcashHaskell/Keys.hs @@ -0,0 +1,38 @@ +-- Copyright 2022-2024 Vergara Technologies LLC +-- This file is part of Zcash-Haskell. +-- +-- | +-- Module : ZcashHaskell.Keys +-- Copyright : 2022-2024 Vergara Technologies +-- License : MIT +-- +-- Maintainer : pitmutt@vergara.tech +-- Stability : experimental +-- Portability : unknown +-- +-- Functions to generate keys for the Zcash blockchain +-- +module ZcashHaskell.Keys where + +import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed) +import qualified Data.ByteString as BS +import qualified Data.Text as T +import Foreign.Rust.Marshall.Variable + ( withBorshVarBuffer + , withPureBorshVarBuffer + ) +import ZcashHaskell.Types (Phrase, Seed(..), ToBytes(..)) + +-- | Generate a random seed that can be used to generate private keys for shielded addresses and transparent addresses. +generateWalletSeedPhrase :: IO Phrase +generateWalletSeedPhrase = withBorshVarBuffer rustWrapperGenSeedPhrase + +-- | Derive a cryptographic seed from the given seed phrase. +getWalletSeed :: Phrase -> Maybe Seed +getWalletSeed p = + if BS.length (getBytes result) > 0 + then Just result + else Nothing + where + result :: Seed + result = (withPureBorshVarBuffer . rustWrapperGetSeed) p diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 44d17a5..d7c3e38 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -1,24 +1,13 @@ -{- Copyright 2022-2024 Vergara Technologies LLC +{-# LANGUAGE OverloadedStrings #-} - This file is part of Zcash-Haskell. - - Zcash-Haskell is free software: you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) any - later version. - - Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - details. - - You should have received a copy of the GNU Lesser General Public License along with - Zcash-Haskell. If not, see . --} +-- Copyright 2022-2024 Vergara Technologies LLC +-- +-- This file is part of Zcash-Haskell. +-- -- | -- Module : ZcashHaskell.Orchard -- Copyright : 2022-2024 Vergara Technologies --- License : LGPL-3 +-- License : MIT -- -- Maintainer : rene@vergara.network -- Stability : experimental @@ -29,14 +18,63 @@ module ZcashHaskell.Orchard where import C.Zcash - ( rustWrapperOrchardCheck + ( rustWrapperGenOrchardReceiver + , rustWrapperGenOrchardSpendKey + , rustWrapperOrchardCheck , rustWrapperOrchardNoteDecode + , rustWrapperOrchardNoteDecodeSK + , rustWrapperReadOrchardCommitmentTree + , rustWrapperReadOrchardPosition + , rustWrapperReadOrchardWitness , rustWrapperUADecode , rustWrapperUfvkDecode + , rustWrapperUpdateOrchardWitness ) import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as C +import Data.HexString (HexString(..), fromRawBytes, toBytes) +import qualified Data.Text as T +import qualified Data.Text.Encoding as E +import Data.Word import Foreign.Rust.Marshall.Variable import ZcashHaskell.Types +import ZcashHaskell.Utils (encodeBech32, encodeBech32m, f4Jumble) + +-- | Derives an Orchard spending key for the given seed and account ID +genOrchardSpendingKey :: + Seed -- ^ The cryptographic seed for the wallet + -> CoinType -- ^ The coin type constant + -> AccountId -- ^ The index of the account to be used + -> Maybe OrchardSpendingKey +genOrchardSpendingKey s coinType accountId = + if BS.length k /= 32 + then Nothing + else Just $ OrchardSpendingKey k + where + k = + withPureBorshVarBuffer $ + rustWrapperGenOrchardSpendKey + (getBytes s) + (getValue coinType) + (fromIntegral accountId) + +-- | Derives an Orchard receiver for the given spending key and index +genOrchardReceiver :: + Int -- ^ The index of the address to be created + -> Scope -- ^ `External` for wallet addresses, `Internal` for change addresses + -> OrchardSpendingKey -- ^ The spending key + -> Maybe OrchardReceiver +genOrchardReceiver i scope osk = + if BS.length k /= 43 + then Nothing + else Just $ OrchardReceiver k + where + k = + withPureBorshVarBuffer $ + rustWrapperGenOrchardReceiver + (getBytes osk) + (fromIntegral i) + (scope == External) -- | Checks if given bytestring is a valid encoded unified address isValidUnifiedAddress :: BS.ByteString -> Maybe UnifiedAddress @@ -54,14 +92,48 @@ isValidUnifiedAddress str = makeUA x = UnifiedAddress whichNet - (raw_o x) - (raw_s x) + (if BS.length (raw_o x) == 43 + then Just $ OrchardReceiver (raw_o x) + else Nothing) + (if BS.length (raw_s x) == 43 + then Just $ SaplingReceiver (raw_s x) + else Nothing) (if not (BS.null (raw_t x)) - then Just $ TransparentAddress P2PKH whichNet (raw_t x) + then Just $ TransparentReceiver P2PKH (fromRawBytes $ raw_t x) else if not (BS.null (raw_to x)) - then Just $ TransparentAddress P2SH whichNet (raw_to x) + then Just $ TransparentReceiver P2SH (fromRawBytes $ raw_to x) else Nothing) +-- | Encode a 'UnifiedAddress' per [ZIP-316](https://zips.z.cash/zip-0316) +encodeUnifiedAddress :: UnifiedAddress -> T.Text +encodeUnifiedAddress ua = encodeBech32m (E.encodeUtf8 hr) b + where + hr = + case ua_net ua of + MainNet -> uniPaymentAddressHrp + TestNet -> uniTestPaymentAddressHrp + b = f4Jumble $ tReceiver <> sReceiver <> oReceiver <> padding + tReceiver = + case t_rec ua of + Nothing -> BS.empty + Just t -> + case tr_type t of + P2SH -> packReceiver 0x01 $ Just $ toBytes $ tr_bytes t + P2PKH -> packReceiver 0x00 $ Just $ toBytes $ tr_bytes t + sReceiver = packReceiver 0x02 $ getBytes <$> s_rec ua + oReceiver = packReceiver 0x03 $ getBytes <$> o_rec ua + padding = E.encodeUtf8 $ T.justifyLeft 16 '\NUL' hr + packReceiver :: Word8 -> Maybe BS.ByteString -> BS.ByteString + packReceiver typeCode receiver' = + case receiver' of + Just receiver -> + if BS.length receiver > 1 + then BS.singleton typeCode `BS.append` + (BS.singleton . toEnum . BS.length) receiver `BS.append` + receiver + else BS.empty + Nothing -> BS.empty + -- | Attempts to decode the given bytestring into a Unified Full Viewing Key decodeUfvk :: BS.ByteString -> Maybe UnifiedFullViewingKey decodeUfvk str = @@ -86,3 +158,71 @@ decryptOrchardAction key encAction = decodedAction = withPureBorshVarBuffer $ rustWrapperOrchardNoteDecode (o_key key) encAction + +getSaplingFromUA :: BS.ByteString -> Maybe T.Text +getSaplingFromUA uadd = do + let a = isValidUnifiedAddress uadd + case a of + Nothing -> Nothing + Just a -> do + let sraw = s_rec a + case sraw of + Nothing -> Nothing + Just sraw -> do + let net = ua_net a + case net of + MainNet -> + Just $ encodeBech32 (C.pack sapPaymentAddressHrp) (getBytes sraw) + TestNet -> + Just $ + encodeBech32 (C.pack sapTestPaymentAddressHrp) (getBytes sraw) + +-- | Attemtps to decode the given @OrchardAction@ using the given @OrchardSpendingKey@ +decryptOrchardActionSK :: + OrchardSpendingKey -> Scope -> OrchardAction -> Maybe DecodedNote +decryptOrchardActionSK sk scope oa = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = + withPureBorshVarBuffer $ + rustWrapperOrchardNoteDecodeSK (getBytes sk) oa (scope == External) + +-- | Update a Orchard commitment tree +updateOrchardCommitmentTree :: + OrchardCommitmentTree -- ^ the base tree + -> HexString -- ^ the new note commitment + -> Maybe OrchardCommitmentTree +updateOrchardCommitmentTree tree cmx = + if BS.length (hexBytes updatedTree) > 1 + then Just $ OrchardCommitmentTree updatedTree + else Nothing + where + updatedTree = + withPureBorshVarBuffer $ + rustWrapperReadOrchardCommitmentTree + (hexBytes $ orchTree tree) + (hexBytes cmx) + +-- | Get the Orchard incremental witness from a commitment tree +getOrchardWitness :: OrchardCommitmentTree -> Maybe OrchardWitness +getOrchardWitness tree = + if BS.length (hexBytes wit) > 1 + then Just $ OrchardWitness wit + else Nothing + where + wit = + withPureBorshVarBuffer $ + rustWrapperReadOrchardWitness (hexBytes $ orchTree tree) + +-- | Get the Sapling note position from a witness +getOrchardNotePosition :: OrchardWitness -> Integer +getOrchardNotePosition = + fromIntegral . rustWrapperReadOrchardPosition . hexBytes . orchWit + +updateOrchardWitness :: OrchardWitness -> [HexString] -> OrchardWitness +updateOrchardWitness wit cmus = + OrchardWitness $ + withPureBorshVarBuffer $ + rustWrapperUpdateOrchardWitness (toBytes $ orchWit wit) (map toBytes cmus) diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 2847529..5ba501e 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -1,61 +1,66 @@ -{- Copyright 2022-2024 Vergara Technologies LLC - - This file is part of Zcash-Haskell. - - Zcash-Haskell is free software: you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) any - later version. - - Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - details. - - You should have received a copy of the GNU Lesser General Public License along with - Zcash-Haskell. If not, see . - --} -{-# LANGUAGE OverloadedStrings #-} - +-- Copyright 2022-2024 Vergara Technologies LLC +-- +-- This file is part of Zcash-Haskell. +-- -- | -- Module : ZcashHaskell.Sapling -- Copyright : 2022-2024 Vergara Technologies --- License : LGPL-3 +-- License : MIT -- --- Maintainer : rene@vergara.network +-- Maintainer : pitmutt@vergara.tech -- Stability : experimental -- Portability : unknown -- -- Functions to interact with the Sapling shielded pool of the Zcash blockchain. -- +{-# LANGUAGE OverloadedStrings #-} + module ZcashHaskell.Sapling where import C.Zcash - ( rustWrapperIsShielded + ( rustWrapperDecodeSaplingAddress + , rustWrapperIsShielded + , rustWrapperReadSaplingCommitmentTree + , rustWrapperReadSaplingPosition + , rustWrapperReadSaplingWitness , rustWrapperSaplingCheck + , rustWrapperSaplingChgPaymentAddress + , rustWrapperSaplingDecodeEsk , rustWrapperSaplingNoteDecode + , rustWrapperSaplingPaymentAddress + , rustWrapperSaplingSpendingkey , rustWrapperSaplingVkDecode , rustWrapperTxParse + , rustWrapperUpdateSaplingWitness ) import Data.Aeson import qualified Data.ByteString as BS -import Foreign.Rust.Marshall.Variable (withPureBorshVarBuffer) -import ZcashHaskell.Types - ( DecodedNote(..) - , RawData(..) - , RawTxResponse(..) - , ShieldedOutput(..) - , decodeHexText +import qualified Data.ByteString.Char8 as C +import Data.HexString (HexString(..), fromText, hexString, toBytes, toText) +import qualified Data.Text as T +import Data.Word +import Foreign.Rust.Marshall.Variable + ( withPureBorshVarBuffer + , withPureBorshVarBuffer ) -import ZcashHaskell.Utils (decodeBech32) +import ZcashHaskell.Types +import ZcashHaskell.Utils (decodeBech32, encodeBech32, encodeBech32m) -- | Check if given bytesting is a valid encoded shielded address isValidShieldedAddress :: BS.ByteString -> Bool isValidShieldedAddress = rustWrapperIsShielded -getShieldedOutputs :: BS.ByteString -> [BS.ByteString] -getShieldedOutputs t = withPureBorshVarBuffer $ rustWrapperTxParse t +getShieldedOutputs :: HexString -> [ShieldedOutput] +getShieldedOutputs t = withPureBorshVarBuffer $ rustWrapperTxParse $ toBytes t + +serializeShieldedOutput :: ShieldedOutput -> BS.ByteString +serializeShieldedOutput so = + hexBytes . fromText $ + toText (s_cv so) <> + toText (s_cmu so) <> + toText (s_ephKey so) <> + toText (s_encCipherText so) <> + toText (s_outCipherText so) <> toText (s_proof so) -- | Check if given bytestring is a valid Sapling viewing key isValidSaplingViewingKey :: BS.ByteString -> Bool @@ -71,7 +76,7 @@ matchSaplingAddress :: BS.ByteString -> BS.ByteString -> Bool matchSaplingAddress = rustWrapperSaplingCheck -- | Attempt to decode the given raw tx with the given Sapling viewing key -decodeSaplingOutput :: BS.ByteString -> BS.ByteString -> Maybe DecodedNote +decodeSaplingOutput :: BS.ByteString -> ShieldedOutput -> Maybe DecodedNote decodeSaplingOutput key out = case a_value decodedAction of 0 -> Nothing @@ -89,25 +94,136 @@ instance FromJSON RawTxResponse where ht <- obj .: "height" c <- obj .: "confirmations" b <- obj .: "blocktime" + sSpend <- obj .: "vShieldedSpend" case o of Nothing -> - pure $ - RawTxResponse - i - (decodeHexText h) - (getShieldedOutputs (decodeHexText h)) - [] - ht - c - b + pure $ RawTxResponse i h sSpend (getShieldedOutputs h) [] ht c b Just o' -> do a <- o' .: "actions" - pure $ - RawTxResponse - i - (decodeHexText h) - (getShieldedOutputs (decodeHexText h)) - a - ht - c - b + pure $ RawTxResponse i h sSpend (getShieldedOutputs h) a ht c b + +-- | Attempt to decode the given raw tx with the given Sapling spending key +decodeSaplingOutputEsk :: + SaplingSpendingKey + -> ShieldedOutput + -> ZcashNet + -> Scope + -> Integer + -> Maybe DecodedNote +decodeSaplingOutputEsk key out znet scope pos = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = + withPureBorshVarBuffer $ + rustWrapperSaplingDecodeEsk + (getBytes key) + out + (scope == External) + (znet == MainNet) + (fromIntegral pos) + +-- | Attempts to obtain a sapling SpendingKey using a HDSeed +genSaplingSpendingKey :: Seed -> CoinType -> Int -> Maybe SaplingSpendingKey +genSaplingSpendingKey seed c i = do + if BS.length res == 169 + then Just $ SaplingSpendingKey res + else Nothing + where + res = + withPureBorshVarBuffer + (rustWrapperSaplingSpendingkey + (getBytes seed) + (fromIntegral $ getValue c) + (fromIntegral i)) + +-- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey and a Diversifier Index +genSaplingPaymentAddress :: Int -> SaplingSpendingKey -> Maybe SaplingReceiver +genSaplingPaymentAddress i extspk = + if BS.length res == 43 + then Just $ SaplingReceiver res + else Nothing + where + res = + withPureBorshVarBuffer + (rustWrapperSaplingPaymentAddress + (getBytes extspk) + (fromIntegral (i * 111))) + +-- | Generate an internal Sapling address +genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingReceiver +genSaplingInternalAddress sk = + if BS.length res == 43 + then Just $ SaplingReceiver res + else Nothing + where + res = + withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk) + +-- | Update a Sapling commitment tree +updateSaplingCommitmentTree :: + SaplingCommitmentTree -- ^ the base tree + -> HexString -- ^ the new note commitment + -> Maybe SaplingCommitmentTree +updateSaplingCommitmentTree tree cmu = + if BS.length (hexBytes updatedTree) > 1 + then Just $ SaplingCommitmentTree updatedTree + else Nothing + where + updatedTree = + withPureBorshVarBuffer $ + rustWrapperReadSaplingCommitmentTree + (hexBytes $ sapTree tree) + (hexBytes cmu) + +-- | Get the Sapling incremental witness from a commitment tree +getSaplingWitness :: SaplingCommitmentTree -> Maybe SaplingWitness +getSaplingWitness tree = + if BS.length (hexBytes wit) > 1 + then Just $ SaplingWitness wit + else Nothing + where + wit = + withPureBorshVarBuffer $ + rustWrapperReadSaplingWitness (hexBytes $ sapTree tree) + +-- | Get the Sapling note position from a witness +getSaplingNotePosition :: SaplingWitness -> Integer +getSaplingNotePosition = + fromIntegral . rustWrapperReadSaplingPosition . hexBytes . sapWit + +updateSaplingWitness :: SaplingWitness -> [HexString] -> SaplingWitness +updateSaplingWitness wit cmus = + SaplingWitness $ + withPureBorshVarBuffer $ + rustWrapperUpdateSaplingWitness (toBytes $ sapWit wit) (map toBytes cmus) + +-- | Encode a SaplingReceiver into HRF text +encodeSaplingAddress :: ZcashNet -> SaplingReceiver -> Maybe T.Text +encodeSaplingAddress net sr = do + case net of + MainNet -> Just $ encodeBech32 (C.pack sapPaymentAddressHrp) (getBytes sr) + TestNet -> + Just $ encodeBech32 (C.pack sapTestPaymentAddressHrp) (getBytes sr) + +-- | Helper to get de Nework Id from FFI response +getNetId :: [Word8] -> ZcashNet +getNetId [x] = do + case x of + 1 -> MainNet + 2 -> TestNet + +-- | decode a Sapling address +decodeSaplingAddress :: BS.ByteString -> Maybe SaplingAddress +decodeSaplingAddress sapling_address = do + if BS.length sa > 1 + then do + let sa0 = BS.unpack sa + Just $ + SaplingAddress (getNetId (take 1 sa0)) $ + SaplingReceiver (BS.pack (drop 1 sa0)) + else Nothing + where + sa = + withPureBorshVarBuffer $ rustWrapperDecodeSaplingAddress sapling_address diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index bbc10d6..878a7d8 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -1,27 +1,11 @@ -{- Copyright 2022-2024 Vergara Technologies LLC - - This file is part of Zcash-Haskell. - - Zcash-Haskell is free software: you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) any - later version. - - Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - details. - - You should have received a copy of the GNU Lesser General Public License along with - Zcash-Haskell. If not, see . - --} -{-# LANGUAGE OverloadedStrings #-} - +-- Copyright 2022-2024 Vergara Technologies LLC +-- +-- This file is part of Zcash-Haskell. +-- -- | -- Module : ZcashHaskell.Transparent -- Copyright : 2022-2024 Vergara Technologies --- License : LGPL-3 +-- License : MIT -- -- Maintainer : pitmutt@vergara.tech -- Stability : experimental @@ -31,4 +15,198 @@ -- module ZcashHaskell.Transparent where +import Control.Exception (throwIO) +import Crypto.Hash +import Crypto.Secp256k1 +import qualified Data.ByteArray as BA import qualified Data.ByteString as BS +import Data.ByteString.Base58 (bitcoinAlphabet, decodeBase58, encodeBase58) +import qualified Data.ByteString.Char8 as BC +import Data.Char (chr) +import Data.HexString +import qualified Data.Text as T +import qualified Data.Text.Encoding as E +import Data.Word +import Haskoin.Address (Address(..)) +import qualified Haskoin.Crypto.Hash as H +import Haskoin.Crypto.Keys.Extended +import ZcashHaskell.Types + +-- ( AccountId +-- , CoinType(..) +-- , Scope(..) +-- , Seed(..) +-- , ToBytes(..) +-- , TransparentAddress(..) +-- , TransparentReceiver(..) +-- , TransparentSpendingKey(..) +-- , TransparentType(..) +-- , ZcashNet(..) +-- , getTransparentPrefix +-- , getValue +-- ) +import ZcashHaskell.Utils (decodeBech32, encodeBech32m) + +-- | Required for `TransparentReceiver` encoding and decoding +sha256 :: BS.ByteString -> BS.ByteString +sha256 bs = BA.convert (hash bs :: Digest SHA256) + +-- | Encodes a `TransparentReceiver` into the human-readable format per the Zcash Protocol section 5.6.1.1 +encodeTransparentReceiver :: + ZcashNet -- ^ The network, `MainNet` or `TestNet` + -> TransparentReceiver -- ^ The address to encode + -> T.Text +encodeTransparentReceiver zNet t = + encodeTransparent' (getTransparentPrefix zNet (tr_type t)) $ + toBytes $ tr_bytes t + where + encodeTransparent' :: (Word8, Word8) -> BS.ByteString -> T.Text + encodeTransparent' (a, b) h = + E.decodeUtf8 $ encodeBase58 bitcoinAlphabet $ digest <> BS.take 4 checksum + where + digest = BS.pack [a, b] <> h + checksum = sha256 $ sha256 digest + +-- | Generate an Extended Private Key from a known HDSeed. +genTransparentPrvKey :: + Seed -- ^ The cryptographic seed of the wallet + -> CoinType -- ^ The coin type constant to be used + -> AccountId -- ^ The index of the account to be used + -> IO TransparentSpendingKey +genTransparentPrvKey hdseed ctype accid = do + let coin = getValue ctype + ioCtx <- createContext + let path = Deriv :| 44 :| coin :| fromIntegral accid :: DerivPath + let prvKey = makeXPrvKey $ getBytes hdseed + return $ derivePath ioCtx path prvKey + +-- | Generate a transparent receiver +genTransparentReceiver :: + Int -- ^ The index of the address to be created + -> Scope -- ^ `External` for wallet addresses or `Internal` for change addresses + -> XPrvKey -- ^ The transparent private key + -> IO TransparentReceiver +genTransparentReceiver i scope xprvk = do + ioCtx <- createContext + let s = + case scope of + External -> 0 + Internal -> 1 + let path = Deriv :/ s :/ fromIntegral i :: DerivPath + let childPrvKey = derivePath ioCtx path xprvk + let childPubKey = deriveXPubKey ioCtx childPrvKey + let x = xPubAddr ioCtx childPubKey + case x of + PubKeyAddress k -> return $ TransparentReceiver P2PKH $ fromBinary k + ScriptAddress j -> return $ TransparentReceiver P2SH $ fromBinary j + _anyOtherKind -> throwIO $ userError "Unsupported transparent address type" + +-- | Generate a transparent receiver +genTransparentSecretKey :: + Int -- ^ The index of the address to be created + -> Scope -- ^ `External` for wallet addresses or `Internal` for change addresses + -> XPrvKey -- ^ The transparent private key + -> IO TransparentSpendingKey +genTransparentSecretKey i scope xprvk = do + ioCtx <- createContext + let s = + case scope of + External -> 0 + Internal -> 1 + let path = Deriv :/ s :/ fromIntegral i :: DerivPath + return $ derivePath ioCtx path xprvk + +-- | decode a Transparent Address in HRF and return a TransparentAddress object +decodeTransparentAddress :: BS.ByteString -> Maybe TransparentAddress +decodeTransparentAddress taddress = do + if BS.length taddress < 34 + then Nothing -- Not a valid transparent address + else do + let maybeDecoded = decodeBase58 bitcoinAlphabet taddress + case maybeDecoded of + Nothing -> Nothing + Just decoded -> do + let digest = BS.take 22 decoded + let chksum = BS.drop 22 decoded + let chksumd = BS.take 4 (sha256 $ sha256 digest) + if chksum /= chksumd + then Nothing -- Invalid address ( invalid checksum ) + -- build the TransparentAddress Object + else do + let addressType = BS.take 2 digest + let transparentReceiver = BS.drop 2 digest + let fb = BS.index addressType 0 + let sb = BS.index addressType 1 + case fb of + 28 -> + case sb of + 189 -> + Just $ + TransparentAddress MainNet $ + TransparentReceiver + P2SH + (fromRawBytes transparentReceiver) + 186 -> + Just $ + TransparentAddress TestNet $ + TransparentReceiver + P2SH + (fromRawBytes transparentReceiver) + 184 -> + Just $ + TransparentAddress MainNet $ + TransparentReceiver + P2PKH + (fromRawBytes transparentReceiver) + _ -> Nothing + 29 -> + if sb == 37 + then Just $ + TransparentAddress TestNet $ + TransparentReceiver + P2PKH + (fromRawBytes transparentReceiver) + else Nothing + _ -> Nothing + +-- | Encode an Exchange Addresss into HRF from TransparentReceiver +encodeExchangeAddress :: ZcashNet -> TransparentReceiver -> Maybe T.Text +encodeExchangeAddress net tr = do + case (tr_type tr) of + P2PKH -> do + case net of + MainNet -> do + let vhash = encodeBech32m (BC.pack "tex") (toBytes (tr_bytes tr)) + Just vhash + TestNet -> do + let vhash = encodeBech32m (BC.pack "textest") (toBytes (tr_bytes tr)) + Just vhash + _ -> Nothing + +-- | Decode an Exchange Address into a ExchangeAddress +decodeExchangeAddress :: T.Text -> Maybe ExchangeAddress +decodeExchangeAddress ex = do + if (T.length ex) > 1 + then do + let rawd = decodeBech32 (E.encodeUtf8 ex) + let tMain = BS.unpack (BC.pack "tex") + let tTest = BS.unpack (BC.pack "textest") + let tFail = BS.unpack (BC.pack "fail") + let hr = BS.unpack (hrp rawd) + if hr /= tFail + then do + let transparentReceiver = bytes rawd + if hr == tMain + then Just $ + ExchangeAddress MainNet $ + TransparentReceiver P2PKH (fromRawBytes transparentReceiver) + else do + if hr == tTest + then Just $ + ExchangeAddress TestNet $ + TransparentReceiver + P2PKH + (fromRawBytes transparentReceiver) + else Nothing + else Nothing + else Nothing diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 47df822..af60f0f 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -1,74 +1,205 @@ -{- Copyright 2022-2024 Vergara Technologies LLC - - This file is part of Zcash-Haskell. - - Zcash-Haskell is free software: you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) any - later version. - - Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - details. - - You should have received a copy of the GNU Lesser General Public License along with - Zcash-Haskell. If not, see . - --} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DerivingVia #-} -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE OverloadedStrings #-} - +-- Copyright 2022-2024 Vergara Technologies LLC +-- +-- This file is part of Zcash-Haskell. +-- -- | -- Module : ZcashHaskell.Types --- Copyright : Vergara Technologies 2023 --- License : BOSL +-- Copyright : 2022-2024 Vergara Technologies +-- License : MIT -- --- Maintainer : rene@vergara.network +-- Maintainer : pitmutt@vergara.tech -- Stability : experimental -- Portability : unknown -- -- The types used by the ZcashHaskell library to interact with the Zcash blockchain -- +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE GeneralisedNewtypeDeriving #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE OverloadedStrings #-} + module ZcashHaskell.Types where import Codec.Borsh -import Control.Exception (MaskingState(Unmasked)) import Crypto.Hash import Data.Aeson import qualified Data.ByteArray as BA import qualified Data.ByteString as BS -import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58) import qualified Data.ByteString.Char8 as C +import qualified Data.ByteString.Lazy.UTF8 as US +import qualified Data.ByteString.Short as BS (ShortByteString, toShort) +import Data.HexString import Data.Int +import Data.Maybe (fromJust, fromMaybe) import Data.Structured import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word import qualified GHC.Generics as GHC import qualified Generics.SOP as SOP +import Haskoin.Address (Address) +import qualified Haskoin.Crypto.Hash as H (Hash256(..)) +import Haskoin.Crypto.Keys.Extended (XPrvKey) +import qualified Haskoin.Transaction.Common as H -- * General +-- +-- | A seed for generating private keys +newtype Seed = + Seed BS.ByteString + deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct Seed + +instance ToBytes Seed where + getBytes (Seed x) = x + +-- | A mnemonic phrase used to derive seeds +newtype Phrase = + Phrase C.ByteString + deriving stock (Eq, Prelude.Show, GHC.Generic, Read) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct Phrase + +instance ToBytes Phrase where + getBytes (Phrase x) = x + +-- | Scope for addresses/receivers +data Scope + = External -- ^ Addresses used publically to receive payments + | Internal -- ^ Addresses used internally by wallets for change and shielding + deriving (Eq, Prelude.Show, Read) + -- | Type to represent data after Bech32 decoding data RawData = RawData - { hrp :: BS.ByteString -- ^ Human-readable part of the Bech32 encoding - , bytes :: BS.ByteString -- ^ Decoded bytes + { hrp :: !BS.ByteString -- ^ Human-readable part of the Bech32 encoding + , bytes :: !BS.ByteString -- ^ Decoded bytes } deriving stock (Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawData --- * `zcashd` RPC +-- | Type for the different networks of the Zcash blockchain +data ZcashNet + = MainNet + | TestNet + | RegTestNet + deriving (Eq, Prelude.Show, Read) + +type AccountId = Int + +-- | Function to get the Base58 prefix for encoding a 'TransparentReceiver' +getTransparentPrefix :: ZcashNet -> TransparentType -> (Word8, Word8) +getTransparentPrefix n t = + case t of + P2SH -> + case n of + MainNet -> (0x1c, 0xbd) + _ -> (0x1c, 0xba) + P2PKH -> + case n of + MainNet -> (0x1c, 0xb8) + _ -> (0x1d, 0x25) + +-- ** Constants +-- | Type for coin types on the different networks +data CoinType + = MainNetCoin + | TestNetCoin + | RegTestNetCoin + deriving (Eq, Prelude.Show, Ord) + +getValue :: CoinType -> Word32 +getValue c = + case c of + MainNetCoin -> 133 + TestNetCoin -> 1 + RegTestNetCoin -> 1 + +-- | A Zcash transaction +data Transaction = Transaction + { tx_id :: !HexString + , tx_height :: !Int + , tx_conf :: !Int + , tx_expiry :: !Int + , tx_transpBundle :: !(Maybe TransparentBundle) + , tx_saplingBundle :: !(Maybe SaplingBundle) + , tx_orchardBundle :: !(Maybe OrchardBundle) + } deriving (Prelude.Show, Eq, Read) + +-- | The transparent portion of a Zcash transaction +data TransparentBundle = TransparentBundle + { tb_vin :: ![H.TxIn] + , tb_vout :: ![H.TxOut] + , tb_coinbase :: !Bool + } deriving (Eq, Prelude.Show, Read) + +-- | Read a raw transparent bundle into the Haskell type +fromRawTBundle :: RawTBundle -> Maybe TransparentBundle +fromRawTBundle rtb = + if ztb_empty rtb + then Nothing + else Just $ + TransparentBundle + (map fromRawTxIn $ ztb_vin rtb) + (map fromRawTxOut $ ztb_vout rtb) + (ztb_coinbase rtb) + +fromRawTxIn :: RawTxIn -> H.TxIn +fromRawTxIn t = H.TxIn op (rti_script t) (rti_seq t) + where + op = + if rop_hash (rti_outpoint t) == + "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL" + then H.nullOutPoint + else H.OutPoint + ((fromJust . + H.hexToTxHash . toText . fromRawBytes . rop_hash . rti_outpoint) + t) + (rop_n $ rti_outpoint t) + +fromRawTxOut :: RawTxOut -> H.TxOut +fromRawTxOut t = H.TxOut (rto_amt t) (rto_script t) + +-- *** Constants for Sapling Human-readable part +sapExtSpendingKeyHrp = "secret-extended-key-main" :: String + +sapExtFullViewingKeyHrp = "zxviews" :: String + +sapPaymentAddressHrp = "zs" :: String + +sapTestExtSpendingKeyHrp = "secret-extended-key-test" :: String + +sapTestExtFullViewingKeyHrp = "zxviewtestsapling" :: String + +sapTestPaymentAddressHrp = "ztestsapling" :: String + +-- *** Constants for Unified Human-readable part +uniPaymentAddressHrp = "u" :: T.Text + +uniFullViewingKeyHrp = "uview" :: T.Text + +uniIncomingViewingKeyHrp = "uivk" :: T.Text + +uniTestPaymentAddressHrp = "utest" :: T.Text + +uniTestFullViewingKeyHrp = "uviewtest" :: T.Text + +uniTestIncomingViewingKeyHrp = "uivktest" :: T.Text + +-- * RPC -- | A type to model Zcash RPC calls data RpcCall = RpcCall - { jsonrpc :: T.Text - , callId :: T.Text - , method :: T.Text - , parameters :: [Data.Aeson.Value] + { jsonrpc :: !T.Text + , callId :: !T.Text + , method :: !T.Text + , parameters :: ![Data.Aeson.Value] } deriving stock (Prelude.Show, GHC.Generic) instance ToJSON RpcCall where @@ -77,24 +208,24 @@ instance ToJSON RpcCall where -- | A type to model the response of the Zcash RPC data RpcResponse r = MakeRpcResponse - { err :: Maybe RpcError - , respId :: T.Text - , result :: Maybe r + { err :: !(Maybe RpcError) + , respId :: !T.Text + , result :: !(Maybe r) } deriving stock (Prelude.Show, GHC.Generic) deriving anyclass (ToJSON) instance (FromJSON r) => FromJSON (RpcResponse r) where parseJSON = withObject "RpcResponse" $ \obj -> do - e <- obj .: "error" + e <- obj .:? "error" i <- obj .: "id" - r <- obj .: "result" + r <- obj .:? "result" pure $ MakeRpcResponse e i r -- | A type to model the errors from the Zcash RPC data RpcError = RpcError - { ecode :: Double - , emessage :: T.Text + { ecode :: !Double + , emessage :: !T.Text } deriving stock (Prelude.Show, GHC.Generic) deriving anyclass (ToJSON) @@ -105,12 +236,13 @@ instance FromJSON RpcError where m <- obj .: "message" pure $ RpcError c m +-- ** `zcashd` -- | Type to represent response from the `zcashd` RPC `getblock` method data BlockResponse = BlockResponse - { bl_confirmations :: Integer -- ^ Block confirmations - , bl_height :: Integer -- ^ Block height - , bl_time :: Integer -- ^ Block time - , bl_txs :: [T.Text] -- ^ List of transaction IDs in the block + { bl_confirmations :: !Integer -- ^ Block confirmations + , bl_height :: !Integer -- ^ Block height + , bl_time :: !Integer -- ^ Block time + , bl_txs :: ![HexString] -- ^ List of transaction IDs in the block } deriving (Prelude.Show, Eq) instance FromJSON BlockResponse where @@ -118,72 +250,277 @@ instance FromJSON BlockResponse where withObject "BlockResponse" $ \obj -> do c <- obj .: "confirmations" h <- obj .: "height" - t <- obj .: "time" + t <- obj .:? "time" txs <- obj .: "tx" - pure $ BlockResponse c h t txs + pure $ BlockResponse c h (fromMaybe 0 t) txs -- | Type to represent response from the `zcashd` RPC `getrawtransaction` data RawTxResponse = RawTxResponse - { rt_id :: T.Text - , rt_hex :: BS.ByteString - , rt_shieldedOutputs :: [BS.ByteString] - , rt_orchardActions :: [OrchardAction] - , rt_blockheight :: Integer - , rt_confirmations :: Integer - , rt_blocktime :: Integer + { rt_id :: !HexString + , rt_hex :: !HexString + , rt_shieldedSpends :: ![ShieldedSpend] + , rt_shieldedOutputs :: ![ShieldedOutput] + , rt_orchardActions :: ![OrchardAction] + , rt_blockheight :: !Integer + , rt_confirmations :: !Integer + , rt_blocktime :: !Integer + } deriving (Prelude.Show, Eq, Read) + +-- ** `zebrad` +data ZebraTxResponse = ZebraTxResponse + { ztr_blockheight :: !Int + , ztr_conf :: !Int + , ztr_hex :: !HexString + } deriving (Prelude.Show, Eq, Read) + +instance FromJSON ZebraTxResponse where + parseJSON = + withObject "ZebraTxResponse" $ \obj -> do + hex <- obj .: "hex" + height <- obj .: "height" + c <- obj .: "confirmations" + pure $ ZebraTxResponse height c hex + +-- | Type to represent a raw deserialized Zebra transaction +data RawZebraTx = RawZebraTx + { zt_id :: !HexString + , zt_locktime :: !Word32 + , zt_expiry :: !Word32 + , zt_tBundle :: !RawTBundle + , zt_sBundle :: !RawSBundle + , zt_oBundle :: !RawOBundle + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawZebraTx + +-- | Type for a raw deserialized Zebra transparent bundle +data RawTBundle = RawTBundle + { ztb_empty :: !Bool + , ztb_vin :: ![RawTxIn] + , ztb_vout :: ![RawTxOut] + , ztb_coinbase :: !Bool + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTBundle + +-- | Type for a raw deserialized Zebra Sapling bundle +data RawSBundle = RawSBundle + { zsb_empty :: !Bool + , zsb_spends :: ![ShieldedSpend] + , zsb_outputs :: ![ShieldedOutput] + , zsb_value :: !Int64 + , zsb_sig :: !HexString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawSBundle + +data SaplingBundle = SaplingBundle + { sbSpends :: ![ShieldedSpend] + , sbOutputs :: ![ShieldedOutput] + , sbValue :: !Int64 + , sbSig :: !HexString + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) + +fromRawSBundle :: RawSBundle -> Maybe SaplingBundle +fromRawSBundle b = + if zsb_empty b + then Nothing + else Just $ + SaplingBundle (zsb_spends b) (zsb_outputs b) (zsb_value b) (zsb_sig b) + +-- | Type for a raw deseralized Zebra Orchard bundle +data RawOBundle = RawOBundle + { zob_empty :: !Bool + , zob_actions :: ![OrchardAction] + , zob_flags :: !OrchardFlags + , zob_value :: !Int64 + , zob_anchor :: !HexString + , zob_proof :: !HexString + , zob_sig :: !HexString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawOBundle + +-- | Type for an Orchard Bundle +data OrchardBundle = OrchardBundle + { obActions :: ![OrchardAction] + , obFlags :: !OrchardFlags + , obValue :: !Int64 + , obAnchor :: !HexString + , obProof :: !HexString + , obSig :: !HexString + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) + +fromRawOBundle :: RawOBundle -> Maybe OrchardBundle +fromRawOBundle b = + if zob_empty b + then Nothing + else Just $ + OrchardBundle + (zob_actions b) + (zob_flags b) + (zob_value b) + (zob_anchor b) + (zob_proof b) + (zob_sig b) + +-- | Type for the Orchard bundle flags +data OrchardFlags = OrchardFlags + { of_spends :: !Bool + , of_outputs :: !Bool + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardFlags + +-- | Type for the response from the `zebrad` RPC method `getinfo` +data ZebraGetInfo = ZebraGetInfo + { zgi_build :: !T.Text + , zgi_subversion :: !T.Text } deriving (Prelude.Show, Eq) -data ZcashNet - = MainNet - | TestNet - | RegTestNet - deriving (Eq, Prelude.Show) +instance FromJSON ZebraGetInfo where + parseJSON = + withObject "ZebraGetInfo" $ \obj -> do + b <- obj .: "build" + s <- obj .: "subversion" + pure $ ZebraGetInfo b s + +-- | Type for the response from the `zebrad` RPC method `getblockchaininfo` +data ZebraGetBlockChainInfo = ZebraGetBlockChainInfo + { zgb_best :: !HexString + , zgb_blocks :: !Int + , zgb_net :: !ZcashNet + } deriving (Prelude.Show, Eq) + +instance FromJSON ZebraGetBlockChainInfo where + parseJSON = + withObject "ZebraGetBlockChainInfo" $ \obj -> do + be <- obj .: "bestblockhash" + b <- obj .: "blocks" + c <- obj .: "chain" + pure $ + ZebraGetBlockChainInfo + be + b + (case (c :: String) of + "main" -> MainNet + "test" -> TestNet + _ -> RegTestNet) -- * Transparent -- | Type to represent the two kinds of transparent addresses data TransparentType = P2SH | P2PKH - deriving (Eq) + deriving (Eq, Prelude.Show, Read) + +-- | Type for transparent spending key +type TransparentSpendingKey = XPrvKey + +-- | Type to represent a transparent Zcash addresses +data TransparentReceiver = TransparentReceiver + { tr_type :: !TransparentType + , tr_bytes :: !HexString + } deriving (Eq, Prelude.Show, Read) -- | Type to represent a transparent Zcash addresses data TransparentAddress = TransparentAddress - { ta_type :: TransparentType - , ta_net :: ZcashNet - , ta_bytes :: BS.ByteString - } deriving (Eq) + { ta_network :: !ZcashNet + , ta_receiver :: !TransparentReceiver + } deriving (Eq, Prelude.Show, Read) -instance Prelude.Show TransparentAddress where - show t = - case ta_type t of - P2SH -> - case ta_net t of - MainNet -> Prelude.show $ encodeTransparent (0x1c, 0xbd) $ ta_bytes t - _ -> Prelude.show $ encodeTransparent (0x1c, 0xba) $ ta_bytes t - P2PKH -> - case ta_net t of - MainNet -> Prelude.show $ encodeTransparent (0x1c, 0xb8) $ ta_bytes t - _ -> Prelude.show $ encodeTransparent (0x1d, 0x25) $ ta_bytes t - where - encodeTransparent :: (Word8, Word8) -> BS.ByteString -> BS.ByteString - encodeTransparent (a, b) h = - encodeBase58 bitcoinAlphabet $ digest <> BS.take 4 checksum - where - sha256 :: BS.ByteString -> BS.ByteString - sha256 bs = BA.convert (hash bs :: Digest SHA256) - digest = BS.pack [a, b] <> h - checksum = sha256 $ sha256 digest +-- | Type to represent a TEX Zcash addresses +data ExchangeAddress = ExchangeAddress + { ex_network :: !ZcashNet + , ex_address :: !TransparentReceiver + } deriving (Eq, Prelude.Show, Read) + +-- | Wrapper types for transparent elements +data RawTxIn = RawTxIn + { rti_outpoint :: !RawOutPoint + , rti_script :: !BS.ByteString + , rti_seq :: !Word32 + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTxIn + +data RawTxOut = RawTxOut + { rto_amt :: !Word64 + , rto_script :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawTxOut + +data RawOutPoint = RawOutPoint + { rop_hash :: !BS.ByteString + , rop_n :: !Word32 + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawOutPoint -- * Sapling +-- | A spending key for Sapling +newtype SaplingSpendingKey = + SaplingSpendingKey BS.ByteString + deriving stock (Eq, Prelude.Show, Read) + +instance ToBytes SaplingSpendingKey where + getBytes (SaplingSpendingKey s) = s + +-- | A Sapling receiver +newtype SaplingReceiver = + SaplingReceiver BS.ByteString + deriving stock (Eq, Prelude.Show, Read) + +instance ToBytes SaplingReceiver where + getBytes (SaplingReceiver s) = s + +data SaplingAddress = SaplingAddress + { net_type :: !ZcashNet + , sa_receiver :: !SaplingReceiver + } deriving (Eq, Prelude.Show, Read) + +-- | Type to represent a Sapling Shielded Spend as provided by the @getrawtransaction@ RPC method +data ShieldedSpend = ShieldedSpend + { sp_cv :: !HexString + , sp_anchor :: !HexString + , sp_nullifier :: !HexString + , sp_rk :: !HexString + , sp_proof :: !HexString + , sp_auth :: !HexString + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct ShieldedSpend + +instance FromJSON ShieldedSpend where + parseJSON = + withObject "ShieldedSpend" $ \obj -> do + cv <- obj .: "cv" + anchor <- obj .: "anchor" + nullifier <- obj .: "nullifier" + rk <- obj .: "rk" + p <- obj .: "proof" + sig <- obj .: "spendAuthSig" + pure $ ShieldedSpend cv anchor nullifier rk p sig + -- | Type to represent a Sapling Shielded Output as provided by the @getrawtransaction@ RPC method of @zcashd@. data ShieldedOutput = ShieldedOutput - { s_cv :: BS.ByteString -- ^ Value commitment to the input note - , s_cmu :: BS.ByteString -- ^ The u-coordinate of the note commitment for the output note - , s_ephKey :: BS.ByteString -- ^ Ephemeral Jubjub public key - , s_encCipherText :: BS.ByteString -- ^ The output note encrypted to the recipient - , s_outCipherText :: BS.ByteString -- ^ A ciphertext enabling the sender to recover the output note - , s_proof :: BS.ByteString -- ^ Zero-knowledge proof using the Sapling Output circuit - } deriving stock (Eq, Prelude.Show, GHC.Generic) + { s_cv :: !HexString -- ^ Value commitment to the input note + , s_cmu :: !HexString -- ^ The u-coordinate of the note commitment for the output note + , s_ephKey :: !HexString -- ^ Ephemeral Jubjub public key + , s_encCipherText :: !HexString -- ^ The output note encrypted to the recipient + , s_outCipherText :: !HexString -- ^ A ciphertext enabling the sender to recover the output note + , s_proof :: !HexString -- ^ Zero-knowledge proof using the Sapling Output circuit + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct ShieldedOutput @@ -197,31 +534,50 @@ instance FromJSON ShieldedOutput where encText <- obj .: "encCiphertext" outText <- obj .: "outCiphertext" p <- obj .: "proof" - pure $ - ShieldedOutput - (decodeHexText cv) - (decodeHexText cmu) - (decodeHexText ephKey) - (decodeHexText encText) - (decodeHexText outText) - (decodeHexText p) + pure $ ShieldedOutput cv cmu ephKey encText outText p + +-- | Type for a Sapling note commitment tree +newtype SaplingCommitmentTree = SaplingCommitmentTree + { sapTree :: HexString + } deriving (Eq, Prelude.Show, Read) + +-- | Type for a Sapling incremental witness +newtype SaplingWitness = SaplingWitness + { sapWit :: HexString + } deriving (Eq, Prelude.Show, Read) -- * Orchard +-- | A spending key for Orchard +newtype OrchardSpendingKey = + OrchardSpendingKey BS.ByteString + deriving stock (Eq, Prelude.Show, Read) + +instance ToBytes OrchardSpendingKey where + getBytes (OrchardSpendingKey o) = o + +-- | An Orchard receiver +newtype OrchardReceiver = + OrchardReceiver BS.ByteString + deriving stock (Eq, Prelude.Show, Read) + +instance ToBytes OrchardReceiver where + getBytes (OrchardReceiver o) = o + -- | Type to represent a Unified Address data UnifiedAddress = UnifiedAddress - { ua_net :: ZcashNet - , o_rec :: BS.ByteString - , s_rec :: BS.ByteString - , t_rec :: Maybe TransparentAddress - } deriving (Prelude.Show, Eq) + { ua_net :: !ZcashNet + , o_rec :: !(Maybe OrchardReceiver) + , s_rec :: !(Maybe SaplingReceiver) + , t_rec :: !(Maybe TransparentReceiver) + } deriving (Prelude.Show, Eq, Read) -- | Helper type for marshalling UAs data RawUA = RawUA - { raw_net :: Word8 - , raw_o :: BS.ByteString - , raw_s :: BS.ByteString - , raw_t :: BS.ByteString - , raw_to :: BS.ByteString + { raw_net :: !Word8 + , raw_o :: !BS.ByteString + , raw_s :: !BS.ByteString + , raw_t :: !BS.ByteString + , raw_to :: !BS.ByteString } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) @@ -229,10 +585,10 @@ data RawUA = RawUA -- | Type to represent a Unified Full Viewing Key data UnifiedFullViewingKey = UnifiedFullViewingKey - { net :: Word8 -- ^ Number representing the network the key belongs to. @1@ for @mainnet@, @2@ for @testnet@ and @3@ for @regtestnet@. - , o_key :: BS.ByteString -- ^ Raw bytes of the Orchard Full Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) - , s_key :: BS.ByteString -- ^ Raw bytes of the Sapling Full Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) - , t_key :: BS.ByteString -- ^ Raw bytes of the P2PKH chain code and public key as specified in [ZIP-316](https://zips.z.cash/zip-0316) + { net :: !Word8 -- ^ Number representing the network the key belongs to. @1@ for @mainnet@, @2@ for @testnet@ and @3@ for @regtestnet@. + , o_key :: !BS.ByteString -- ^ Raw bytes of the Orchard Full Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) + , s_key :: !BS.ByteString -- ^ Raw bytes of the Sapling Full Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316) + , t_key :: !BS.ByteString -- ^ Raw bytes of the P2PKH chain code and public key as specified in [ZIP-316](https://zips.z.cash/zip-0316) } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) @@ -240,15 +596,15 @@ data UnifiedFullViewingKey = UnifiedFullViewingKey -- | Type to represent an Orchard Action as provided by the @getrawtransaction@ RPC method of @zcashd@, and defined in the [Zcash Protocol](https://zips.z.cash/protocol/protocol.pdf) data OrchardAction = OrchardAction - { nf :: BS.ByteString -- ^ The nullifier of the input note - , rk :: BS.ByteString -- ^ The randomized validating key for @auth@ - , cmx :: BS.ByteString -- ^ The x-coordinate of the note commitment for the output note - , eph_key :: BS.ByteString -- ^ An encoding of an ephemeral Pallas public key - , enc_ciphertext :: BS.ByteString -- ^ The output note encrypted to the recipient - , out_ciphertext :: BS.ByteString -- ^ A ciphertext enabling the sender to recover the output note - , cv :: BS.ByteString -- ^ A value commitment to the net value of the input note minus the output note - , auth :: BS.ByteString -- ^ A signature authorizing the spend in this Action - } deriving stock (Eq, Prelude.Show, GHC.Generic) + { nf :: !HexString -- ^ The nullifier of the input note + , rk :: !HexString -- ^ The randomized validating key for @auth@ + , cmx :: !HexString -- ^ The x-coordinate of the note commitment for the output note + , eph_key :: !HexString -- ^ An encoding of an ephemeral Pallas public key + , enc_ciphertext :: !HexString -- ^ The output note encrypted to the recipient + , out_ciphertext :: !HexString -- ^ A ciphertext enabling the sender to recover the output note + , cv :: !HexString -- ^ A value commitment to the net value of the input note minus the output note + , auth :: !HexString -- ^ A signature authorizing the spend in this Action + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardAction @@ -264,27 +620,106 @@ instance FromJSON OrchardAction where outText <- obj .: "outCiphertext" cval <- obj .: "cv" a <- obj .: "spendAuthSig" - pure $ - OrchardAction - (decodeHexText n) - (decodeHexText r) - (decodeHexText c) - (decodeHexText ephKey) - (decodeHexText encText) - (decodeHexText outText) - (decodeHexText cval) - (decodeHexText a) + pure $ OrchardAction n r c ephKey encText outText cval a + +-- | Type for a Orchard note commitment tree +newtype OrchardCommitmentTree = OrchardCommitmentTree + { orchTree :: HexString + } deriving (Eq, Prelude.Show, Read) + +-- | Type for a Sapling incremental witness +newtype OrchardWitness = OrchardWitness + { orchWit :: HexString + } deriving (Eq, Prelude.Show, Read) -- | Type to represent a decoded note data DecodedNote = DecodedNote - { a_value :: Int64 -- ^ The amount of the transaction in _zatoshis_. - , a_recipient :: BS.ByteString -- ^ The recipient Orchard receiver. - , a_memo :: BS.ByteString -- ^ The decoded shielded memo field. + { a_value :: !Int64 -- ^ The amount of the transaction in _zatoshis_. + , a_recipient :: !BS.ByteString -- ^ The recipient Orchard receiver. + , a_memo :: !BS.ByteString -- ^ The decoded shielded memo field. + , a_nullifier :: !HexString -- ^ The calculated nullifier + , a_rho :: !BS.ByteString + , a_rseed :: !Rseed } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct DecodedNote +data Rseed = Rseed + { rs_kind :: !Word8 + , rs_bytes :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic, Read) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct Rseed + +data TransparentTxSpend = TransparentTxSpend + { ts_sk :: !BS.ByteString + , ts_utxo :: !RawOutPoint + , ts_coin :: !RawTxOut + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct TransparentTxSpend + +data SaplingTxSpend = SaplingTxSpend + { ss_sk :: !BS.ByteString + , ss_note :: !DecodedNote + , ss_iw :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct SaplingTxSpend + +data OrchardTxSpend = OrchardTxSpend + { ss_sk :: !BS.ByteString + , ss_note :: !DecodedNote + , ss_iw :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardTxSpend + +data OutgoingNote = OutgoingNote + { on_kind :: !Word8 + , on_key :: !BS.ByteString + , on_recipient :: !BS.ByteString + , on_amt :: !Word64 + , on_memo :: !BS.ByteString + , on_chg :: !Bool + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OutgoingNote + +newtype SaplingSpendParams = SaplingSpendParams + { sapSParams :: BS.ByteString + } deriving newtype (Eq, Prelude.Show, Read) + +newtype SaplingOutputParams = SaplingOutputParams + { sapOParams :: BS.ByteString + } deriving newtype (Eq, Prelude.Show, Read) + +data TxError + = InsufficientFunds + | ChangeRequired + | Fee + | Balance + | TransparentBuild + | SaplingBuild + | OrchardBuild + | OrchardSpend + | OrchardRecipient + | SaplingBuilderNotAvailable + | OrchardBuilderNotAvailable + | ZHError + deriving (Eq, Prelude.Show, Read) + +-- * Classes +-- | Class to represent types with a bytestring representation +class ToBytes a where + getBytes :: a -> BS.ByteString + -- * Helpers -- | Helper function to turn a hex-encoded string to bytestring decodeHexText :: String -> BS.ByteString diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index d7577ca..59b38ed 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -1,27 +1,13 @@ -{- Copyright 2022-2024 Vergara Technologies LLC - - This file is part of Zcash-Haskell. - - Zcash-Haskell is free software: you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) any - later version. - - Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - details. - - You should have received a copy of the GNU Lesser General Public License along with - Zcash-Haskell. If not, see . - --} +-- Copyright 2022-2024 Vergara Technologies LLC +-- +-- This file is part of Zcash-Haskell. +-- -- | -- Module : ZcashHaskell.Utils --- Copyright : Vergara Technologies (c)2023 --- License : BOSL +-- Copyright : 2022-2024 Vergara Technologies LLC +-- License : MIT -- --- Maintainer : rene@vergara.network +-- Maintainer : pitmutt@vergara.tech -- Stability : experimental -- Portability : unknown -- @@ -33,21 +19,40 @@ module ZcashHaskell.Utils where import C.Zcash ( rustWrapperBech32Decode + , rustWrapperBech32Encode + , rustWrapperBech32mEncode + , rustWrapperCreateTx , rustWrapperF4Jumble , rustWrapperF4UnJumble + , rustWrapperTxRead ) +import Control.Exception (SomeException(..), try) import Control.Monad.IO.Class import Data.Aeson +import Data.Binary.Get import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as LBS +import Data.HexString (HexString(..), toBytes) import qualified Data.Text as T +import qualified Data.Text.Encoding as E import Foreign.Rust.Marshall.Variable +import Network.HTTP.Client (HttpException(..)) import Network.HTTP.Simple import ZcashHaskell.Types +-- * Utility functions -- | Decode the given bytestring using Bech32 decodeBech32 :: BS.ByteString -> RawData decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode +-- | Encode the given Human Readable Part and bytestring as a Bech32m string +encodeBech32m :: BS.ByteString -> BS.ByteString -> T.Text +encodeBech32m h d = withPureBorshVarBuffer $ rustWrapperBech32mEncode h d + +-- | Encode the given Human Readable Part and bytestring as a Bech32 string +encodeBech32 :: BS.ByteString -> BS.ByteString -> T.Text +encodeBech32 h d = withPureBorshVarBuffer $ rustWrapperBech32Encode h d + -- | Apply the F4Jumble transformation to the given bytestring f4Jumble :: BS.ByteString -> BS.ByteString f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble @@ -56,6 +61,7 @@ f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble f4UnJumble :: BS.ByteString -> BS.ByteString f4UnJumble = withPureBorshVarBuffer . rustWrapperF4UnJumble +-- * Node interaction -- | Make a Zcash RPC call makeZcashCall :: (MonadIO m, FromJSON a) @@ -72,3 +78,95 @@ makeZcashCall username password m p = do setRequestBasicAuth username password $ setRequestMethod "POST" defaultRequest httpJSON myRequest + +-- | Make a Zebra RPC call +makeZebraCall :: + FromJSON a + => T.Text -- ^ Hostname for `zebrad` + -> Int -- ^ Port for `zebrad` + -> T.Text -- ^ RPC method to call + -> [Data.Aeson.Value] -- ^ List of parameters + -> IO (Either String a) +makeZebraCall host port m params = do + let payload = RpcCall "2.0" "zh" m params + let myRequest = + setRequestBodyJSON payload $ + setRequestPort port $ + setRequestHost (E.encodeUtf8 host) $ + setRequestMethod "POST" defaultRequest + r <- + try $ httpJSON myRequest :: FromJSON a1 => + IO (Either SomeException (Response (RpcResponse a1))) + case r of + Left ex -> return $ Left $ show ex + Right res -> do + let zebraResp = getResponseBody res + case err zebraResp of + Just zErr -> return $ Left $ T.unpack $ emessage zErr + Nothing -> + case result zebraResp of + Nothing -> return $ Left "Empty response from Zebra" + Just zR -> return $ Right zR + +getBlockTime :: HexString -> Int +getBlockTime hex_block = + fromIntegral $ + runGet getInt32le $ + LBS.fromStrict $ BS.take 4 (BS.drop 100 $ hexBytes hex_block) + +readZebraTransaction :: HexString -> Maybe RawZebraTx +readZebraTransaction hex = + if BS.length (hexBytes $ zt_id rawTx) < 1 + then Nothing + else Just rawTx + where + rawTx = (withPureBorshVarBuffer . rustWrapperTxRead) $ hexBytes hex + +createTransaction :: + Maybe SaplingCommitmentTree -- ^ to obtain the Sapling anchor + -> Maybe OrchardCommitmentTree -- ^ to obtain the Orchard anchor + -> [TransparentTxSpend] -- ^ the list of transparent notes to spend + -> [SaplingTxSpend] -- ^ the list of Sapling notes to spend + -> [OrchardTxSpend] -- ^ the list of Orchard notes to spend + -> [OutgoingNote] -- ^ the list of outgoing notes, including change notes + -> SaplingSpendParams -- ^ the Sapling circuit spending parameters + -> SaplingOutputParams -- ^ the Sapling circuit output parameters + -> ZcashNet -- ^ the network to be used + -> Int -- ^ target block height + -> Bool -- ^ True to build, False to estimate fee + -> Either TxError HexString +createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing sParams oParams znet bh build = + if BS.length (hexBytes txResult) > 1 + then Right txResult + else case head (BS.unpack $ hexBytes txResult) of + 0 -> Left InsufficientFunds + 1 -> Left ChangeRequired + 2 -> Left Fee + 3 -> Left Balance + 4 -> Left TransparentBuild + 5 -> Left SaplingBuild + 6 -> Left OrchardBuild + 7 -> Left OrchardSpend + 8 -> Left OrchardRecipient + 9 -> Left SaplingBuilderNotAvailable + 10 -> Left OrchardBuilderNotAvailable + _ -> Left ZHError + where + txResult = + withPureBorshVarBuffer $ + rustWrapperCreateTx + (case sapAnchor of + Nothing -> "0" + Just sA -> toBytes $ sapTree sA) + (case orchAnchor of + Nothing -> "0" + Just oA -> toBytes $ orchTree oA) + tSpend + sSpend + oSpend + outgoing + (sapSParams sParams) + (sapOParams oParams) + (znet == MainNet) + (fromIntegral bh) + build diff --git a/stack.yaml b/stack.yaml deleted file mode 100644 index 33603d4..0000000 --- a/stack.yaml +++ /dev/null @@ -1,75 +0,0 @@ -# This file was automatically generated by 'stack init' -# -# Some commonly used options have been documented as comments in this file. -# For advanced use and comprehensive documentation of the format, please see: -# https://docs.haskellstack.org/en/stable/yaml_configuration/ - -# Resolver to choose a 'specific' stackage snapshot or a compiler version. -# A snapshot resolver dictates the compiler version and the set of packages -# to be used for project dependencies. For example: -# -# resolver: lts-3.5 -# resolver: nightly-2015-09-21 -# resolver: ghc-7.10.2 -# -# The location of a snapshot can be provided as a file or url. Stack assumes -# a snapshot provided as a file might change, whereas a url resource does not. -# -# resolver: ./custom-snapshot.yaml -# resolver: https://example.com/snapshots/2018-01-01.yaml -resolver: lts-21.21 - -# User packages to be built. -# Various formats can be used as shown in the example below. -# -# packages: -# - some-directory -# - https://example.com/foo/bar/baz-0.0.2.tar.gz -# subdirs: -# - auto-update -# - wai -packages: -- . -# Dependency packages to be pulled from upstream that are not in the resolver. -# These entries can reference officially published versions as well as -# forks / in-progress versions pinned to a git hash. For example: -# -extra-deps: -# - acme-missiles-0.3 - - git: https://git.vergara.tech/Vergara_Tech/haskell-foreign-rust.git - commit: 787c2e813eb3a5d16c375d4b37dfefbd2adcdf05 - - git: https://github.com/well-typed/borsh.git - commit: d2fcfa159e0a844b1ec5e8ed3e232d4b380fa831 - - vector-0.13.0.0@sha256:fa5cac81a17a5af388716792e8b99c24b3b66770086756d0d8b23f8272a0244c,9112 - - aeson-2.1.2.1@sha256:f10f3c661bd5cf57aee46b94420e47736240b8e209ac15f4bfc1a4e4d55831fa,6344 - - generically-0.1.1 - - semialign-1.2.0.1@sha256:ee3468e349e72ec0a604ae05573a4de7181d97d10366254244a0cca8a76d6c35,2852 - - strict-0.4.0.1@sha256:d6205a748eb8db4cd17a7179be970c94598809709294ccfa43159c7f3cc4bf5d,4187 - - these-1.1.1.1@sha256:2991c13e264b0c35c696c8f5f85c428c53bc42e93b1dfbd19a582052112d948a,2748 - - assoc-1.0.2@sha256:9decd0933cb6b903a40a8ace02d634bf90048ee2e5b0a514dccad7056c041881,1253 -# -# extra-deps: [] - -# Override default flag values for local packages and extra-deps -# flags: {} - -# Extra package databases containing global packages -# extra-package-dbs: [] - -# Control whether we use the GHC we find on the path -# system-ghc: true -# -# Require a specific version of Stack, using version ranges -# require-stack-version: -any # Default -# require-stack-version: ">=2.9" -# -# Override the architecture used by Stack, especially useful on Windows -# arch: i386 -# arch: x86_64 -# -# Extra directories used by Stack for building -# extra-include-dirs: [/path/to/dir] -# extra-lib-dirs: [/path/to/dir] -# -# Allow a newer minor version of GHC than the snapshot specifies -# compiler-check: newer-minor diff --git a/test/Spec.hs b/test/Spec.hs index 8007b19..7b0aac0 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -17,49 +17,112 @@ -} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeSynonymInstances #-} import C.Zcash (rustWrapperUADecode) +import Control.Exception (throwIO) +import Control.Monad.IO.Class (liftIO) import Data.Aeson import Data.Bool (Bool(True)) import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as C +import Data.Either (isRight) +import Data.Foldable (sequenceA_) +import Data.HexString import Data.Maybe import qualified Data.Text as T import qualified Data.Text.Encoding as E import qualified Data.Text.Lazy.Encoding as LE import qualified Data.Text.Lazy.IO as LTIO import Data.Word +import Foreign.C.Types import GHC.Float.RealFracMethods (properFractionDoubleInteger) +import Haskoin.Crypto.Hash (ripemd160) +import Haskoin.Crypto.Keys.Extended +import Haskoin.Transaction.Common +import Test.HUnit import Test.Hspec +import Test.Hspec.QuickCheck +import Test.QuickCheck +import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed) import ZcashHaskell.Orchard import ZcashHaskell.Sapling - ( decodeSaplingOutput + ( decodeSaplingAddress + , decodeSaplingOutput + , decodeSaplingOutputEsk + , encodeSaplingAddress + , genSaplingInternalAddress + , genSaplingPaymentAddress + , genSaplingSpendingKey + , getSaplingNotePosition + , getSaplingWitness , getShieldedOutputs , isValidSaplingViewingKey , isValidShieldedAddress , matchSaplingAddress + , updateSaplingCommitmentTree + , updateSaplingWitness ) +import ZcashHaskell.Transparent import ZcashHaskell.Types - ( BlockResponse(..) + ( AccountId + , BlockResponse(..) + , CoinType(..) , DecodedNote(..) , OrchardAction(..) + , OrchardBundle(..) + , OrchardCommitmentTree(..) + , OrchardSpendingKey(..) + , OrchardWitness(..) + , Phrase(..) , RawData(..) + , RawOutPoint(..) + , RawTBundle(..) + , RawTxIn(..) + , RawTxOut(..) , RawTxResponse(..) + , RawZebraTx(..) + , SaplingAddress(..) + , SaplingBundle(..) + , SaplingCommitmentTree(..) + , SaplingReceiver(..) + , SaplingSpendingKey(..) + , SaplingWitness(..) + , Scope(..) + , Seed(..) , ShieldedOutput(..) + , ToBytes(..) + , TransparentAddress(..) + , TransparentBundle(..) + , TransparentReceiver(..) + , TransparentType(..) , UnifiedAddress(..) , UnifiedFullViewingKey(..) + , ZcashNet(..) + , ZebraTxResponse(..) , decodeHexText + , fromRawOBundle + , fromRawSBundle + , fromRawTBundle + , getValue ) import ZcashHaskell.Utils +m2bs :: Maybe BS.ByteString -> BS.ByteString +m2bs x = fromMaybe "" x + main :: IO () main = do hspec $ do describe "Bech32" $ do - let s = "bech321qqqsyrhqy2a" + let s = "abc14w46h2at4w46h2at4w46h2at4w46h2at958ngu" let decodedString = decodeBech32 s - it "hrp matches" $ do hrp decodedString `shouldBe` "bech32" - it "data matches" $ do + it "hrp matches" $ do hrp decodedString `shouldBe` "abc" + xit "data matches" $ do bytes decodedString `shouldBe` BS.pack ([0x00, 0x01, 0x02] :: [Word8]) + it "encoding works" $ do + encodeBech32m "abc" (bytes decodedString) `shouldBe` + E.decodeUtf8Lenient s describe "F4Jumble" $ do it "jumble a string" $ do let input = @@ -278,8 +341,20 @@ main = do case t of Left s -> s `shouldBe` "" Right x -> - rt_id x `shouldBe` + toText (rt_id x) `shouldBe` "5242b51f22a7d6fe9dee237137271cde704d306a5fff6a862bffaebb6f0e7e56" + describe "Seeds" $ do + it "generate seed phrase" $ do + s <- generateWalletSeedPhrase + BS.length (getBytes s) `shouldNotBe` 0 + it "get seed from phrase" $ do + s <- generateWalletSeedPhrase + let x = getWalletSeed s + let result = + case x of + Nothing -> False + Just s' -> True + result `shouldBe` True describe "Sapling address" $ do it "succeeds with valid address" $ do let sa = @@ -320,6 +395,11 @@ main = do let ua = "u1salpdyefbreakingtheaddressh0h9v6qjr478k80amtkqkws5pr408lxt2953dpprvu06mahxt99cv65fgsm7sw8hlchplfg5pl89ur" isValidUnifiedAddress ua `shouldBe` Nothing + it "encodes UA correctly" $ do + let ua = + "u1salpdyefywvsg2dlmxg9589yznh0h9v6qjr478k80amtkqkws5pr408lxt2953dpprvu06mahxt99cv65fgsm7sw8hlchplfg5pl89ur" + (encodeUnifiedAddress <$> isValidUnifiedAddress ua) `shouldBe` + Just (E.decodeUtf8Lenient ua) describe "Decode UVK from YWallet" $ do let uvk = "uview1u833rp8yykd7h4druwht6xp6k8krle45fx8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm" @@ -353,7 +433,7 @@ main = do let rawKey = decodeBech32 svk let badKey = decodeBech32 badvk let rawTx = - decodeHexText + fromText "050000800a27a726b4d0d6c200000000ff8e210000000001146cc65bd6d252d09b8eb0a8ab0aab6d7a798325aefc1d3032fc6d31373a85a25a3a16b447a698f720ade1bc290a74d85574b5b20515391035a67f8d5883dc65ea3ba4a17b009d6f325d41072b3ce240270959a7ffd040e5f16c697d8148973c62ffe037fc83aded21e4c91722b52520a2395c23e9c1a896f4b0f12d32ae8e31833d9d95adae40f6ecf7aff52af184efd390a4c1aa76b5fb1cab6003b1a8a004016f385926661f56d38273ec2c3df7775210310a65fff5fa9ac5509f0784eefea28bdcc67b0ff69eef930335f3b9768529e2bfe733024492101f642f989de8cbf04dd66638e9317780bce47085079675b772664c8007e96597dba83ea9af22ddf07ff1c212983d4a902914431245357527294e69ea5616e720ef1e9215bbfa33ba108b8d07efff2bad1850525d7725c681761c9b8c844a5548afabf176863de7b4cde3901defc3e83d31086d3c6e6af9a5fcc3cfb38b52ac7de84f91df5e0587f7603773401a62eeef10cd3ccf4d927ef42402c32f32280abbeaac33e73ceda52089820a186e9a1adfea81453998c6bbaa0deb41bc4f94586bfee80bad25fc71abe7c6dd44bcb1a6929a0112c7e4f8fcadb9745bde9422b954f72954c4d22db48719de61f383d620935b647337f73d119d79fd208e1d5a92f0855447df5782cd4764ba91efa65d9e4ebaa34e2eccb7aac93a5b0efe0c7664f3cd9384b3ff706ad3019c907cdcfa084351c9f6a0bfa8c78c91272ca66ac86dd6e1d0d6ba9704ea7dc54f71a053dce91f844c1ca62b5ddfe6b53834f4a816b1b01460810d9b87517659f4915adf4b84783a60ecf3bd71569259f1ff90a91a0b314bd4c77976d7893bf42e5d6ad0f8df95eb6d6c69d41490be8e39b2452df3bebfc297d5b0fc97f081890390fb0727a96898585f0120a7da9a798f2032590553f724d8756c67c5b0d1c0d23301c4ed60fa283994fd712aab17ca6360256fd5aef0ebc48f0256e3eda5894b53981d0d46768aefdc85b48c1525b7f134dce5d4ec2d76c03c821513f1652d9671219d744bdce5e69b9a74ca0c7c837668f0d8ffffffffffff9534b3d594e1609b3bace18608750b35a066c57f85e291d194400cb351430bbbe212abba32be071e747b7310863bd5fd989855a6567a351b288144b6e9f838c6a517db94673246ef0010b65f9c0be8aca654f6f57b83d893663cfd389ab96ce50e8077fe588c16b1b5989c6cc262e6658efb9b88ac800e49e9e5999e2651b8fff28fa77071d63790df155ed8344e2581ac5205b31d4f17bd748fcf60e35a9d6048d23c94c7aca8d4e541fda497aa268df9c173af5877a5da56d8fa2a42166900c734b62e56792f6c8bed48e4f108a817e83d64d6a59e38cfdb55c0f8a89bc7507c89326266f7ac03a3941f448cb879bd792bb116d0be8876c0856a76ddec0f0c02e16f0338626013ee5f6037fc6a3c69fa291204039d04d17c11295ee3024aea8f5d381e9b7eb3f938b6f9182bf4f889f1e53e30f998b1cdd23f45cfaa20aaef058248cc2e1c487fcdf54a4bc22a68a17cb6fa7b2fbf333b99feb84643d321398b675634929602126b2fb40171e514769bf82f18c267ce9cda0c24300caa9a5a361144d3b7b9ab2243ee9811d9b2e72c8bb1d145cdfcf6b29994a969b41c47208f5dba8d6d871e490e9b970afec4d8bca40ba51825cdc78cc7cde6b6f235a4105b1d1b5e2765efd753095ce770f070b02cce3316721b9345680c146c2f428c0bbca90d5a8cd0a1c4c31cbfa8ec165ea9f9c71d2d05e3cf8bae5e779786f179c45a3cd8087d820cae812aded04f8acda9068af80ea834f79f1bd03bfd66f8a19074649a85ce877df1a621a867debb423ec0d19015b326fcf6f143aba34029c1da2fc7b099378a366c38c9609ef6a9d9e175e21b0c1ab94a84e28ee7f1a00e39cb6fb59f44e4567e9f85f8f98158263c52ec433c042397c784edb07c28d2bca036f59090e819157375d610acb1993a4107b48da13a371f5383429baee209b2c0cc150fcef79a042749668ba1f89ad24a8c746142191ed0e8fd63624a331d98d50daa84ccf9043076947cf5115b9f8787acd36000c5e72c8d783b29bb28a3e46036d0a592ce8a158ee5a7ac210be72d3a6185c13645d96a8446021b64043ab8b589a20091c152e7d5a993ba94770eea988e289e1536d0d81dbc7046ca9c6d918446bf970894f073c920006681ccf6d1a3f138519c68eba0296069e42dc60f2bcd0f17c400efe4f4e87de8606606dc4fdf31494df4d454d14a440b1d9db4265c7aa9bc8683c68cb149f2cc826427575e2af82e842199a9cb9fdc7243b3bc12f1a71c37eac5cf88ba830cb95728897fa4c177a290d6b2b3814173262da14db9b4ef39fc54f888a6ffef4221ae672fb03bc78ebef479360a682ddb12ea0369a428a6c2960ff8327e9a2f5e5d98ce1eae748db8f6a4631c789b4d751d6b99c97c149a813998d44a7b57ba06c8bcb8a6c73c6388cdcfeb1346cec8fee7bdebf2a2388d9722183eb2d2e0e183cdd092152ef640880f4514f3c5e836cc3a8249413500630aa8da85f9e3cd92bdadbb69a2bab8d71f0b3ec5832a7ddbddd67b34c33b2e12a0c8468e852e4a8f7df45657e9632088aa7c6c5048a2686019cfec33b27fc88e23759938dd55a5dff589c1c21a37da617609e9d8be37dbf9bd6e84ee160fe10268171d969e4611afe9d3482ed4b132dcdd11ee516f36d512a333da20266fd984caebf4937fdfd18ed07b4a45771cf5c8c16c6b258b289a07d136a22acc766011f366c420bafb8fc1a10e42219bede5a3d1166c525491ab60bbd1f973fd3fb2e94cea888e24d5fb0adce51faeda75d62de70094d4b36d38d03cd824d284fad577c3ead4d98bcc8ceccd18174a889b22380bfcc12656e764ea0b8fe1409971283008ed02cbef89d6f544c62c3b001bfe96723fda9190deecba534d69cfa358036fdaf16127b89f925c52d4e750919ffb7182b6a8ad13d0a8e00e0b906978dd24ee11869c1a63837a80e46e1216e2e273aba07aa5b0d97558db0ba7f9ac4c89403c65f1719394e479311f5cf84746e6be6f1abcac03194aa8bf1735811198b5df90dd6cac345779c185c24beda0101b932048dc4144af664a63acc0c395052882ee1f18bd0ddf13bb583861923bc00ed5ae815b964698ca097eda1c4281e039139fa3091890244f926cc4ab773ca8a35d5263d3bb48fd6ac53a4bb4d7d60b36446dbc714c35b5e13a17c5b0c70f67207839d1f7404604aff63b2fa83a4da7dac92aac96b3f250412f8d04a9e298004313b02edefd076c67d8a1316355777814e2e1ab03690e426b672d32ff65c03c592ecce6a70e34fea2e15b9a6b4fd092d027199caf27e84e25c09380b38a5eb8985355b3259aa1d94be74269b84f953053b02ba3be9df872ae5fb2d893188575bdfe222ba267b5461a0d0be274a7d9e6ee51490d98e4cd97978804c4f0f8e9f4908fd8c102b01080f5a02b7578591e95d60f3f56d8e48514b1ce7ea6894f55a32c8ac8564985d18c6b82f8dcde5b315624e9321bdd49dd350c87907cc373c0238a79321e6250e38a0ceb2c060ecee6708c11cb30a49687da9923bcdf011f9aca27e6eb5a8477a2bae2dcff9884cc2349b51a66b5179ed2d8f69e4bbba74c694194e83d04a8566228227eb732a95180c6788483d1f259d52c52fe43357656d50a1cf2902c3124d60d15fc85f0447a1203f824c1106452cfec1c92b18de003f82a0000000000001cbd27436a221a53d08c4838831d1bc60ff7e93df41a51412ef6096eec98bb28fd601c53a5371b23a497062635b5cdde715c23840d37f1cf328f0a2ba96260357689ae3f84a80dbdca1520df68513be1285177d3c0da664c64944de78d8b8d5864f5ac15444cd3204adc4fe487503066c18fbbef8d0515248b0a97577f5aea1d255788ed4bb66d4d56303efe135063392c312b4671963daa20e0ade262984e11263a1588eba3cf829e6131ab506e6a850aacce603e8ecfd6e794c90a772603d80fd2aad6027b34854072a0d23079252adb1ba637bbc650ed4afd35d977e1498d998020bc1c814718b48ba7378a92c56827d3c2f20daa231fa51f0a9188520e2a11149e162489f0d6dbd27cf94fd5775311d3dfbcfeb431bafc3515bbb8c4ba4488c320dca0dfec548fe9f46d8810b3f6b16bb3e3eb0ea130747d3d127c5953ca8d561f8d425a35dc3f2cd831743139fbdcada42308b524313782e23b32d5d54a265eae408623e3b2779fe60e13cf47d54dfe520f9f4e57c68aed31f78629a9074d72ab87bea993a38f95ab40df3ef01735e7d44ad365a786e0d3032f1c1dc4e6839c974185dbe63f8725e79831ebe269f94c96705639ab38d5d0700da04c6a9f686e1ea13391885287ba43cf3ccef1c2227918f15ed55441c45adca84153530bbfea3cf37adbf84831a2bfcbf0ca4a4bbd90e623789fe993dc17503ec11b1ef3049f27b27ff778af364d634a46165cda1dd8241cb88740bce74a73e7e3d656df2dee05bb561a85e64671b191ec802c5bfaca49b8168e44271cf13df756395896ff41a99654f55b6951f20d04b2007938a420218db8e37445ef3267130e288e3270b13a92596a26043e1ae84f3934cdb13363bc2843f74a0f6608a36b52c985132aa427c56b7275a864b3c76502c37b8abb8d0286b3199c78492ba8103f5a23c6cdca2292c75d7d6d7080108850807f78af3dc7e418371c6b8951bd89b79fa586af4e16096b08ac1f4dc2b1e4feaa5c040bb002b57311523197b6e2bef5b79ac9c9b4a339be6f6bf7fbe9b5c93862c87be6647949c70bb2c7e268e2ab39cbadd69de628376b3af744eeabc85b599bbdd09defacefa443e05c9b5f259a7783743fecf1a749c57cacc85703269ed67db1d8d475f6fe25d66f84a77379411ba123d98fcb3ae4eec306489a08372893616a91268ea6bf34ddbf0fdef1360ab9e82f4ac80a24e41f439af06fadc223c61f445b7261eda5e1320e269d1277631ee2245cf930244bf8c04050c514e2d59035b80827586cbfeb7da7a59c1208aa86390b9dc7a9b6ef38879ba4deea5eef47c5c98d9167594cd730abdfaf082090efe759d1b13199d739c112ae324ba24b275bf1d89867b81f4580a7ea3a8d3d07b45e2de6c1c7099de3606873b13f3083ecd1e84456c9a1b1d358075c68b1a7cf0b1f26031a2909e226f5da7877d0085b879165ec4b1d9abb7b0732ab4a6f22d9a7bbd0d494ef3f9af4903dc733fe92c6b2f557d1406d223a93e8ad6e579ebcde9c39a5652ad31335df924e5b6a09a0191821b4a0c8f886e2d7860b75ae79ad9dfbebf3500c8b9762dcd131eb5c8b866b5efb4fbfdcc5e31605c2b7d2ff8db5198a6c41bcf880065ff232ff8f84ca3f8022d3428359dc9fb19f57a6ad3f3d174d8a348879a754b37095f01d9a7f6f873798b97dfc5d7c7eaf0383b3fdccdcc11b30dbb3a0fe3186a36c4ddc9674624e38a81cca60a9bbc1b124021b61a383b7547d6af187022c133ba9d6dadf711a3af3b0255b859b214ef6c5dec592248fc94339a64f19196ca0fdad80f7f8e3d78b1f783b1f038008d0d106bd86e23e33ae5728872d42a555bb36d0e3303f0b4ab41180f4251590ee3ee244b77191c31b9f3f990f71c6e237b9dbcf7ca21c9b4c2446b856c67861785bb9edb920b8f530a7a088313ef044419a879f26db137e1557d079315844f9f60bae03d8cffa7a28bd2857a001fd5d2d999fca95ff91df0e228567f6c9ff592b77b7ccdc93a951f7e34910361a8f4fb517e1c9fb956a3bb50ddf37ed37e8d26adfc0f71e059ba95ec77a1e34e1b3420c893f89b79fc72e3a1d864fd35526727f939badc29740f5ef9c0bb9a3f72e1e08b2ef2ab366f80d8e14e03e92162736e2ccb7cde82b2af08de8a6a81c03c63e2396974ec29fb122d818a2d2d5b29d11b704d3ac3b39431099b7b2f6aae04d28a2182b55380503127e4986ee9e8d5c0c2058e09e4592d08d013a4f088e45403720160622236bd56fbd9cc240efceb1b23c19ceafec49e9d5776ca9da5f7810ac979cf6ec5f678c09257abf79c9ab55dea00054e11b62c0a0ed4363d0a96a37ae1a323aa93bb1af253885afb684ed30caf5cf3b37afe6a6463a16f34cc28b4530c6bc6281f597bea476cd9a773205b96d47ed4b0bfeeee39b7ea44ff194911bcddbb161c2c0ce9488978b99e880d8e43624dbb4a567483ed293348d752634b2f46219575175e3c249b8e4e853142b66491aa1c142e7bb558955747cf2bd61ac802a2a4784d9fe080f771dd537d0ff928b3b04029d9ac03175c2d535ad7f3c123eff30c0437b32dd9fa31b2976e369b89b79a2a95e31aee15a462c5fe25fd937ce6b0795808d16163f2cea8f19b7c83913cb4a793576aa5c0ddfb6415326d8f2be0621017616c85ee46aa768b077dbec72311cb8a0f78ba0621a277a79af6607063839a52c6825a20e1c5818b24628862aff5fbbfe87311866b9956eeea7412ef69a3e4da84699b8d8b45c74ac96c3356989fd4962ba79fc26a92488304fd9f42486266b433eeb57368d26ae91ba4e7ed812581f790314cc7f44639aa7f6775618111369e8e2d68a6ab24388824bbbe3c3b0fbfb88635c1fc3216af1af40eba3555c0b0390a18ccba9e68afbcd21ecb212aaf82846f0a55793945902c48cac5d19332e23248a464529f4cca177137c508b6b13637e7df523254f24b8343d19164174202bcb00d5fe618b760c374f69b5065b1f9acf91af95abd7eb271586cae14fc835f633aacb4cd2ecd0f0ac08b688ef4d13b8a7b4c487ad46485bde0e340309672dc38af275e6ab525971409a39eff0ad134b1674db5d1f9725874e36d8730dc034b0a596c6e0a26e521c199d3e3f86815a64d148ffc394290b19f15390934b5d0da27dc8365360511628b93ebc375d2a531e4f4cfa031eeb501afe96d201c7b6078bbceb8e5d8615599f4c613bbb81a88f4eeaa57a9c008125073a30154044c422eaf2a32cecb15aab0774bb44e52b1792d154b8036ff9224af53e023fc011ab251d47d7d76e55c5015db1926d43c56d055feca1259de53ce98a2faecb5843ce17a3e83ccf678f5d13a8321f278a670c684e62b720e1eefd0abb2e9d0a3ddea81d5eb6e380a1c22af5587daac852e93a86f5e4293c18bb26b32035c7e5ca20cd2e3eabd3de0e55092a79a42c7ccc0aef033d6683043c2a29531a2ef1f503595c0e464ac042153c4685b062275f88bab93cc017f1ef9dc6f8aeab7b0b234f1c543420324512554f1786c82b37836238a4dfdf86f97d09ae466eb39d5f3ab159e2060be309d1284be133ab40abd61468c1706f7f9e57a7bc747479693a03edc8863dd196fd7cb2721260e42f4f606389c4972c74d357e7467b61cb7f455562015d29a59c7cafe0df03f26b77bca81c2bbac8cbccf8a65190b0c4e5ca832e82ce4e11044433aa397106cafc05634ad778270d20d8a13068586bc6b582ea24524fd921a5ee22dbae5296ee86d80f12b78bba26f8b42c1401b75d5cfcc4775c5cd1cb0a9248dabc8f82d216787b2a2780f7d13a5ee8c6ab56399a8dd5db3a152677d01eb8ee98d1927ca5069e0d1ba3907971a2199ba3634b48e570dd97d93729a6c43e4f359e2d89795218d52270a338a1f511b1f008cd04553c1a89caf987fd18c329be7ac2282084ef1789615d7eb7afd2261f606d3953b8863abe57796289e7761b01c3ca0faf2291287f9ad7027d7f0876b5f77d2a7f87dfa6ad4db905d4bcad042f403824aee3b4f8d7b5ab027fe1eda9d683db24f56f694b0b10ec72ba0df40bdd6e52b4a7b8d064ad46c7490c4705c14b06ef55222435d2d6316c7dfee83d225eaa431c11a4b85b0bafbe66fda1abadaec8eabcc2f8c688a7b9cb2942597f20cfafd7520892c535bbf6359a6989a84dd89989d95b8e5222c3aca2e8b0f8881d759e450466b75b5f36b7b723b0a212edf52abd591e7e545a3974b8b31b84b523af7b47e3804b5a268c86ae0bd7c80bc6b578b79f749eccdb4a00813925ef40259ac10bbd0fc4f2fc536c30f7c1efe68a52bee22f57021d23f445211b36fee6302202c9b62c6cf9064a2df424563f9f805e51c4092482253e4c258d53b80a2d26eec9fcdc104f7124d876a3ad573a7f419b0d67a41a34dac9d8f28cf9519b9c2677c9d1e720667d5fce26091d64c6df8b46c98b58017de0d055651e8caa3a230f57aef214ae2f27fa85400e34ce7087538fb6b854a6ad534780052210b8b8c90b4de4c2afbad9f58a71770ec186cfa44b61b53876bf904972078673845ec3181caaccf11f71a8e2a502deaf144f16df1df3bd81277dfbf6e1ae5a17363725ec31759b743066fbd4cafb5eda6e09418bc375f42e0dcbd4624dbe26c5fabb77152124f400e677004fcf3c862a9b5576140cbeb800fcde4409caae06286cb643842687bc6b89738374c7c759c911d7bbec8613f1fdd996d4b970fb6f2a9290a84c34d5fea6b8006357c8e6e9d4048d5a8ec476dab0b55e8358ecf7d27c31da86681f3fa74d072b1150223eaa21c4027378a99c8a2dbdc07d4062c401e92eddfff82841292bda2798c4e2ee9e09f618cc181c91a4dbefa44c410dff5cb705ee005e3a0470c13baddf9066109797a3b51e73a0ef229796d330aee0c0160529a4ca3b39e861ee5c4a0f78619007ecdf32266c7c42f0c972b91cccc45f6f8688b2692f298721cb5da39cbca9a5adef6969a2592ba421680241f8a5384bb92e70acf79c2f41d1171dfc6e1939887b9c8ea94429bcbf3532919fdcfcd0f443d3c95515b41e3f9c84bdcd3de1fd481f98482f667f2d017e3579208341e9a225f85516c8ca133cbada77598b6f596e6151eca377fcc8029cc99a879b26d975684173c0874509117ece4136bd2d69848f858c05e8ba3421499d7fb5e3e7645fc135117d8fdd1dc46bebaedfbad4dc7cc23fad6e696fe349712cf7579b4e63b38cc7d02a4c6a33ee4117d7ccdb86ea02cd791756b2a3c516d59d39ed83a8c328823f1934731820c187624219b487ca86edcc2f61a064e4e8d17f58f4a71462f3f0cfef6be95c0eb3737616de5954096d761a51534b36d798c651541acfe2e5bed6e58c45c46e014923a342409d49e782054f2a4877332e0adb0663dce0e84df0ff0d71c4c5ef18d9cacc8b8d47c78d53fc7649bab719334601c79a345d2101e65d4c3f1616741b24fdd9f7d6569fb8de67799648b323856c2ca96ac91823aa12249934c7050505258c43763ac9b174d55fee71c7817d65056c30d7853500656040621eff9f291e9db198772451cd3d58bc9421f6191a863777edf49125543b22ba127ea3063e1989bc4635d50bb22270948ba594b2066d91e1589aa0c1f476af8b170820ae0d2409df83819579621161ba55cc8d020e1ea68cedbccc9a737746d824b06e12cd2e3730260fdc52a68b6e142bdb997d8f93f25d241360285e7372939828fd54c015ba90bb4d553558ffa2558a43098644357860785addc455bf1ef4ffa5aebb8f002eb0f6a8ddd6f45edcc7cfe88d7ae7141b9113a4ab851b5fa8ff39c7024d2b3202841266f256b1f4ce4e4ea8b83d0067555ddd56fb1f8ed6cb5ce7340cdc403fac1dd3b48c373629202c70f1f95c0001b4f94a9b4bad9e1c64429b541ff57a2f2ea0c1fb2af8f0e490fac415cc3bcdd5739f2069e2bfb873206b8c8811619f1db39386cd2dd748c6301239b4e873f3d4618a43c52ef7d8abc0ca127881dc8809dc20a8730718c64c5a132cf638ec1e3b3cedaae55763637b4c6341baeff17e14c1e3c34e5d23befdddec0c7e25f0ebfa90a0bca07a0f5446507b7fb9cf91cb3cb8d24a111f46e7dbe3ea62b384eebecaf8de49b15b17f9cf151c4c3d33220b3adcf9f87222813ff2125120e77c638ab19c1daf9631d53b3efee67c3c40c9c23495848d4c5ecafc0998bf2704bb78ade666f414e9c6bb100b90451d93397d062b741a3ef20e7e6ebebab8238e2adf415775179c866eb4a2f628924674b20f711d2515d3b024e7e0fd3af6e116cfb0030709fb9bed4a441646c03dfa0b11cb460415ccb3c0999b738a0b07d55be741f3d2646a3d9e22bbfeb46b87acb6fcc2d83cdf44b656d2269a0734259a738d83a7429bfc99da7238b9497fbb10e0f623dc1f793e3be2326a7e55677e20ebb152f71794fd70d7698d0cff1334341b3a8eee38d66a021cae6f8dfe67913fdeb8d46efd9b77f28950aee7ce112f8f0dfc04132a99d95dfa923d391e06a578056ec8d15ddfb0fe8cd2790ed55f8254c6351d471c7273c2e61c2694bf6b0a1173cdc2d422246ca5cc51a9b8669dc331fe33bfe330d5167a244cf041f8444775562eaa8ddb92e0623d4c689db0919ece87a7feeb1ddd2d034a0d884d6bc8b5fa5fc7df00cf667dfc82c9e98e9b57fbe07fed391729034c467b46d3d16ed72c8ded069a117ca2d28e71ef4c37f1b1939590af19433ff2a784ff38134aacc59719673c53ed047d79e0c36f3acc03f64458826107853ff8d02de9362ffada79feabc597d019de6afb6577dbbefcaeaee801a49b1718151690b43a4d70b2fcb73ece3e3ea5fe2a9616cc6a9a494b8c6c7121497688652eb854e7bae19af3e5ae1a8df75138ce18621d11f15054ac8971f6ac084bd90e3909b46db321916c9e3c8950f3cc8e0a10dd255bd6b359c73dfde7de6e01538d03609253968177af14d327337bc1bc4e866c75ab5b69125f9e9a758f061418110a615b97f733f899382e2a14fa0ff0d1bda3b7449e1f54362697aef064fc9dd50692ad2b9ec286a1b7c1f845b09f4b1ccd46142f9e272d018eabf76c23c26b1831762a8a2c9152ef0cc843ebba20bf979e851e76141f41e817cca0ac60c1baf239a6c920a244b0be031a3b65d76eebae15bd9d49c53dd3e4303ca379f26840901f1da886d330d8ab4408a25eaa280ddb545b968b4aa59ca6f9cecbf82984264a3232b97498be0729f3d97dbd64823393b42794ec1676244212af382c37d53d66019a5db34c3d8ac156cac852ecb1c57f0f6057815460eb9f666c368acca6dfee0d2b7bdacd2148d74cea3bbac8b528f00809f403b1964b9d199d2db93363a53732efd97c66f28cb51442dbfb1c912dce078944e02889f9395ff2655e2d7118a11848e3ca64efb5eca119240b537b5d4548a744d96962e9b2a99af73bc4d753df16fe75a08005da7773b7bef68a44f56951a3a93995c5d0516a8b2c39abd2057b3d86ba7fe511dc33292596fcc4d3c67df88812e26d3628527752a81c8ce6eba711ae7ea8748229caa2580e18937f45cbfb0041f0a9254eade41c95ef4f390db6174e6b0ba89af344c215393b627348ebf00901e343c1599c832bd2cfb751750808719f18119ab3fee6baf5fd4b8bf7c92a271ff24576984610abaf1b2297c18a2784f57d7aad26f52c0d60fa6cd2484492a1524dd14924eebf53348ee2dca0643ef47381541b91d1843817067a94bb7c79e4e08c4d81c7266afefeda4a0db21b0db629cedf13b4f6ba11e6480116a7c9780009ee0e1e2bd543dbb68bea356a6010a1e3ded4229c4d8035420833fc83337dce4bc483eec76f479f33be2d6a8fb013813952eb66bc247f37f9def7e9bea0f277f380079aeb48c192b9c2c24f909435586bcb994ae6f75eea111c8842b855f90df21e956f839aef89bb1b96899e343cc66814ac08eed4defe5e2a62c54cf83a27f0499224e02bac652aebe6529143372c483edcdbfbdd5a02106fd3651875f23724d2154c0138ace745d038d6cf6b233ae2bbb8313c8ab59dc896844da5a6610845d9147e57518ada61b7debcd66111d609587632cccb936751d099639735f09f77b2b9aa1177e95ac0f8070bcbeb01bbfb3e7a638811d919016ed628865e2405aa17f5129ea43a836a76ec7cbd943e20b1e388d47db2df3394785e27dd7efbe350ccec6057af7483303a6a588d4005c05ff7f3c90dc5b9a0f95c3c541ea40615f6b0ed6cd3787e608e7612b52199062991a63511b3055dfab25b18ab55948d9d529aff91a0d8aa7705afc187e32d1107c07623a309390f32c62e1abb830bb090a7dc4c6817b554720fce354563662d78cc2bcd0483149b304ab86502f0ad36ba2c262fa60b1b84be2499e56fd72129c0fdf1961214df8dd6c9b831109fadc7464df090aac96d221ab387b6eee3080ea0d94e195ca795b5d80c42946c0c353e39075bb1aa4f3fac36a77445cd57b11b3c7ef0e89aca911be35fbb72e1e2fff84b7ece66bb8b3dea52b0aff473475d298f730e79ff77e955856817514908baaec1b53f39f9d5dcbb1cef29536a483a1845d8538e5ec43f634914bae4f2319579adcf3f0fbb151c80b54f02be54be612f9417f916579863dc16da5af07fab05db2094c77612b445b7b9784198abbfc8387486449969118d3a83142022017d69b939d30fd6b1c5ba360c0e37f264b5d604c7ebdaf327874b22d4092dcf35aad93f0f0b3037f90000d4a1892dab827d3c2f1ddede948d330706ef17bf442f8228c168401541ea89a837f81cfffde8480e1b6014f2f5d3132ee740e04622600ad93dba41e39267f17554b29102390f8196094de19e8d966d3c03a9e3348494b54045629224612734a1304ed76d17a465c305675bf99b7aff1950dd2012e6cbe8d15a9e35a7c16081bf647e2766c7b8e9d12ef8a2c94fd7531f6d1a7ac7ce6bac028917a2a71c8eded4f3ce3be8c7d98422094e88414338fc25839a582b5965c71179ce3ddf7a645bb163f3265f4bc4ebe91ff25d86520696d237741ba73084070e23494d6a890e1ae748091a6936a3137d16dd14571f86ee118bcb17c8ecd2ed20c25cbab79a78c53fba1d5db4fdb15e6a124bdb2d41fef080e7318f78c67451beff04b58a182f592fec3feab6011be4663dca8b62ad2a8e40c716b3c8a170e1574517c44e146109d223529b0de480aa168b5a4b0522cea03dd26a684b08c58aa2e1fe41f44ba8837737a74a4681b0508d9604aa7484c5ec68ccb369c056702b03f75722033597ce5b55e5a88f14e3ee23b1c96f5d38604128cf2e901a745996f4a29d412e5ce1b4ee82cf2f248aa80f8c8f2eb7b6951f1b0910857a17edc319bc76b1a59fe05f9394a5b16a91ef15afba9ee2e115c9c9afc51a8d7c4a61cb335724855007f564fec341342d53717023118917304c2dfcf0bd79e456c94718b8388767564d6055aad1d505f36506fd6e8cb1a10f6fe8a67e772030dab78b43ec15fae0322fe991944be86cd0bd78ba00110474dec84dd9597a602c4a6c97a27f13159ff1e32fa63e28f63278b09c9d051cabf8bb49f2780d4c33ca061ff16c6288ff29d46a22e31c719dfaf6087590ea8197c066f9057e0c24b40f5eb205b730985daaf7e7d85fecd7449677c0ec1abd7111d795de6de4a6ab30cf266987dd55569f2a705ae16f51de738fcc4cc218f2cb9dab8af509ba26ed93ddc7a73028d9187527fa6d554b491faade05852eb50847affb81b58d2501989d5b4a9de4237b33bea154c4d016b08f3e1c39ee61382fb500be2123ccfcd30e37c88979a19d636c9dcf5fcec822e5558cb2939e40e1af039aa942b503c9fce95f8ff51a2977f58e4d5032f9099fb4783b7764dd1299a1a89f21c1f5e38038f219e9a4e9d6c6edaef70132ee1f2a580227b256c9139d339183e278277a9dd260bf7d8a9853c4185f9b6509ab9b67a3563ed0b9afca6f2db28258025d8c2036cc4cd9fb4f7007dca96597c26476def29191cb0c77c44df80994ad3c79833820ce2f1108d17a083fa857397d2839c37899ce2e634578b3eee0135f66cfe1aeb7bcb8732fedba16093a6e666321236f56e285058d00c1bba2db3263844e4b0ea013e7a8d370ecae9ac7a9620eaaa244b25be1eb6e71368f550736d3ca13747edfb6254c9a373aadcc30a148907d39e81bdda8a53e8644b39a1625de851fc5d759686c63c76bb162b244415175eb0ccaf3a63d4ad910369e30313bdeb175ace63ad33e400e642dbdcb472a336d82d2f818e6888e5aef472d03fd298a5d14b8da432b72e6c5f5694887a7e4e165bc41055379bd204298d65796d5351a9121f949904bd6c87510649127ef5797d432c6c759939c99026d57076db225d7d0df01ab9b9e3e1221e7e323ff752eb7988cd2b2bbfc8ae7fdd23c3f7cfd2ae2679014e986e3c01b6c66a67fbf69483ad3e89c1f2dd0867f411151e048d81212c0b5e2fbba9d317f283f97c9576180121f41856355b906a22c69869bc27729db5974c5b1257b35d186dcb4e9f7bc51dc331c0ff9baed5955e5052a6ac7743b" let x = getShieldedOutputs rawTx describe "extract Shielded Output bytes" $ do @@ -362,7 +442,8 @@ main = do let a = decodeSaplingOutput (bytes rawKey) (head x) it "amount should match" $ do maybe 0 a_value a `shouldBe` 10000 it "memo should match" $ do - maybe "" a_memo a `shouldBe` "Tx with Sapling and Orchard" + C.filter (/= '\NUL') (maybe "" a_memo a) `shouldBe` + "Tx with Sapling and Orchard" describe "fails with incorrect key" $ do let a = decodeSaplingOutput (bytes badKey) (head x) it "amount should not match" $ do maybe 0 a_value a `shouldNotBe` 10000 @@ -374,39 +455,39 @@ main = do let res = decodeUfvk uvk let a = OrchardAction - (decodeHexText + (fromText "248b16d98dfa33f7ba69a0610a63b606699da76c288840b81d7691ee42764416") - (decodeHexText + (fromText "17fcc27cce560733edaf91439a8020c4a029a4e7d5893ce024d5ff4b40bbd0a9") - (decodeHexText + (fromText "34796d541864832acca43f083892e98a46c912802d5643672d3f25bea177c61c") - (decodeHexText + (fromText "a6d2ca10e3fc7446e372266ef45ee3dc0ba373bd378e6bf3092519a7f272bd8c") - (decodeHexText + (fromText "08beafdf59110b5d045e4acc13731ef1a27bfa3a9cabe1d575640c18f18ee6697fbb132d36e982ae3eadf5f37fd35f42c2bb07def14759deab1fbe2f98dc1d5913e4a6ef388b714e2cfd6d89ba2302800e02ab5f45e0e02e3895448518cd8afd2c37bb48a66d8b988a37de9d0838d92876894a311bb9f314ba842e5c18ff7a3d8c7f0ff1a7209e2d661595db8f4a4aa267b9593258914bf63c09286eeda7c9b27ddbb4646208c0d03a8fbdc5d96633335a5a65316f5b25189bdce735bdea7e900de56d3b475ae51b7c35eb7ae79ba104baeb0a5a09d1cd8bb347ab34fb26d62ddbf024f5394710626ec0a665b9c917e65b00256db635145164a0329db7bc5358f435d573b2662adf8a6128801825ec8fb7d8aeef567d35c875ddd784fceb7620355e3f056a648b39b4b2d29a1f5e7b7c4ec5fd2b1874ff1e832b308f8644e83878d90582b9a6fd6c293e19dd3e24dbe1b4c15c96608169843d46551900a8cb787b15f0f1696b736dd4c8ebacf1e3288b14e469bdc004fa8557d6b1395700eaba59334906bb012f876e4cd7acd2157719ebd2e28bd0cd4ab4ac458f8848e1c30e729803dd47102200fe703932a15c3618862ec83b40d3aa0ec2343641bcb9afbf931ab21aa4afdbe7e51deca24283c2ccab0eef6e07aac5a4bf3a775bf7d2ddfc8d8766c3bf8e35df1435cf515d93c3b9549477bd9f53d133f05dd256fbcc0b13a63e3e7f8cce6301ab4f19c114f5af079f8c581537458e861b553218a890ea3e77fb99781c7088cd43c67c155ec611c1148721cab5fd0168e4a5ec390b506ec44145474c") - (decodeHexText + (fromText "1e40d33446d9f0f0fad40f8829c1ffe860c11c3439e2c15d37c6c40282f9e933dc01798c800e6c92edb4d20478b92559510eda67f3855f68f5ab22ca31e1885c7fa9d4c9ebfb62ceb5e73267bcad0ba7") - (decodeHexText + (fromText "63d0d6e8e92691f700bf8af246dcd4ae1041b13e3969f7a9d819a06e0f9429bc") - (decodeHexText + (fromText "fe362be160accf2794841c244e8d80bbeb80b9bc95bb653d297a98d32bddf5a05dd5f874891d55924a83f722f75f576f63796770c31074067694cffb2cce7a2a") let b = OrchardAction - (decodeHexText + (fromText "8921446787f1bd28fa0e4cc5c945ea7fc71165a25f40cd2a325dae0c4467d12c") - (decodeHexText + (fromText "240b08b7861aa78989c68cbedd0038af9b3e3456bdc7ff582d597df571d54da2") - (decodeHexText + (fromText "e1bc8ccba69ab9f429bf735417aa005cf439d27500b0d3086dbf1be764b42a36") - (decodeHexText + (fromText "c89c58ef8553e7d09ba4090654edd1a8c98763c44d3dfb9dad18286c7ef363ae") - (decodeHexText + (fromText "0eee1ca5a3a4959cd4b8bc277e6e633f950680c4acb978c14ad8d944a784f46771c9d666a203ca3ac693943d79dd23f8b76a734a62e81932cbe98e8c851f47a11aaef50249e53151f38f88262a4bae8cf26f5f8b2db1d165aff9b57b64713a677c167608585c038e34ca7bbe468e5f86475ccec0a4a8b9a43b56e342e77a6bd09415787c9f4a1c6f20599f57545f1ac32c3a338d7a5bb2d35456adb880cb65c1455969e10df5d94b8c74b244e7093b1a88cc10697a7c2f4d34b6eae3296e64b820573b4d52e06b4427af5b8f5d6722d3a93fd85da615fceac732976ad2c1be4150b4821c149521f5419ea0746fb132d47f593cfc8a3aab6b2b4480c12fadf21280ccd3142e7188d9e5aef3fcd8c5dc0c066dc975bead023ef7f89a486b615b146110ae68b703a8349a5fc225b26a08b2adaf36fb44c9ad1be59d7ced134eb84e3f0b4aec19b71b2d26e910628a11446b97c5e6bbf97e1befa4e04b5947f83c65161b92f58088d28e57adc2a2873e27008e29772c5803502842045cb355d1ea5a9d27c2683dcb38cb49d26af39625ba99b1342f700387b939e7ff6c129417ca8836fe1e96331e35c8bc0763879e8c17cd4535fbcb27a2785c0a47294e07cb54837bb997df34882ce0bececc6adca365c76fc7533cf0503458937dcfb6058b016dbbd399b9f0cca44cbc881016f4957b5e10daada3393d5b2a4cb15ed983506d4d264f9855ce2ef87a7d4a1fc03293a22c28a53c4455447d546813fa33008e5d2d81848825fae2f437ab9575ba99c230e78f4b23e575e7647beff0e4c4e2b0a1f7320e9460") - (decodeHexText + (fromText "d727aeec27bb0f7463c6ed4f5b3f4085cfd3e7218478db0dcebfca875e025320fb64bc4062251823859e963446cadd9924c559e5f981480df5a4f036daf5a8033d4c8241e128902aa1aeaf6adc149730") - (decodeHexText + (fromText "98e72813aeb6ea05347798e35379bc881d9cf2b37d38850496ee956fbecd8eab") - (decodeHexText + (fromText "cb9926f519041343c957a74f2f67900ed3d250c4dbcd26b9e2addd5247b841a9fde2219d2ef8c9ae8145fecc7792ca6770830c58c95648087f3c8a0a69369402") let decryptedNote = (`decryptOrchardAction` a) =<< res let decryptedNote2 = (`decryptOrchardAction` b) =<< res @@ -420,12 +501,782 @@ main = do let msg = maybe "" a_memo decryptedNote2 msg `shouldBe` "Hello World!\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL" - describe "Address tests" $ do + describe "Wallet seed phrase:" $ do + prop "Generated phrases are valid" $ again prop_PhraseLength + prop "Derived seeds are valid" $ again prop_SeedLength + before getSeed $ + describe "Optimized spending key tests:" $ do + it "Transparent spending keys are valid" $ \s -> + property $ prop_TransparentSpendingKey s + it "Transparent receivers are valid" $ \s -> + property $ prop_TransparentReceiver s + it "Sapling spending keys are valid" $ \s -> + property $ prop_SaplingSpendingKey s + it "Sapling receivers are valid" $ \s -> + property $ prop_SaplingReceiver s + it "Sapling receivers are distinct" $ \s -> + property $ prop_SaplingRecRepeated s + it "Orchard spending keys are valid" $ \s -> + property $ prop_OrchardSpendingKey s + it "Orchard receivers are valid" $ \s -> + property $ prop_OrchardReceiver s + it "Orchard receivers are distinct" $ \s -> + property $ prop_OrchardRecRepeated s + describe "Address tests:" $ do it "Encode transparent" $ do let ua = "u17n7hpwaujyq7ux8f9jpyymtnk5urw7pyrf60smp5mawy7jgz325hfvz3jn3zsfya8yxryf9q7ldk8nu8df0emra5wne28zq9d9nm2pu4x6qwjha565av9aze0xgujgslz74ufkj0c0cylqwjyrh9msjfh7jzal6d3qzrnhkkqy3pqm8j63y07jxj7txqeac982778rmt64f32aum94x" let msg = case isValidUnifiedAddress ua of Nothing -> "Bad UA" - Just u -> maybe "No transparent" show $ t_rec u - msg `shouldBe` "Got it" + Just u -> + maybe "No transparent" (encodeTransparentReceiver (ua_net u)) $ + t_rec u + msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD" + it "Recover UA from YWallet" $ + ioProperty $ do + let p = + Phrase + "security expect junk hour people bind law hub between topic wink cliff spirit scissors auction idle figure option wide useful swift prison cushion round" + let targetUA = + isValidUnifiedAddress + "u1qsylqauvnhw8tsfe3cldcsj3mjrfqzgaf3mt8yzlkjuvsf5wzj223yvrt8q66qukfqcc80x3z0mk6ym6pm2f0hukzkp6t4wj78h85t6kfr2u9mqsfhdd73g3sc7ezy2ut3rtq5jmejatwv4xqqd6l8tt9fycer8kdw0gz6e607nkssqsc7kd7nk2yfz2hpvpqhdg39wxalpjzhe34j7" + let s = getWalletSeed p + case s of + Nothing -> return $ expectationFailure "Failed to generate seed" + Just s' -> do + let oK = genOrchardSpendingKey s' MainNetCoin 0 + let sK = genSaplingSpendingKey s' MainNetCoin 0 + let tK = genTransparentPrvKey s' MainNetCoin 0 + let oR = genOrchardReceiver 0 External =<< oK + let sR = genSaplingPaymentAddress 0 =<< sK + tR <- genTransparentReceiver 0 External =<< tK + let newUA = UnifiedAddress MainNet oR sR $ Just tR + return $ Just newUA `shouldBe` targetUA + it "Recover UA from Zingo:" $ + ioProperty $ do + let p = + Phrase + "cloth swing left trap random tornado have great onion element until make shy dad success art tuition canvas thunder apple decade elegant struggle invest" + let targetUA = + isValidUnifiedAddress + "u1trd8cvc6265ywwj4mmvuznsye5ghe2dhhn3zy8kcuyg4vx3svskw9r2dedp5hu6m740vylkqc34t4w9eqkl9fyu5uyzn3af72jg235440ke6tu5cf994eq85n97x69x9824hqejmwz3d8qqthtesrd6gerjupdymldhl9xccejjwfj0dhh9mt4rw4kytp325twlutsxd20rfqhzxu3m" + let s = getWalletSeed p + case s of + Nothing -> return $ expectationFailure "Failed to generate seed" + Just s' -> do + let oK = genOrchardSpendingKey s' MainNetCoin 0 + let sK = genSaplingSpendingKey s' MainNetCoin 0 + let tK = genTransparentPrvKey s' MainNetCoin 0 + let oR = genOrchardReceiver 0 External =<< oK + let sR = genSaplingPaymentAddress 0 =<< sK + tR <- genTransparentReceiver 0 External =<< tK + let newUA = UnifiedAddress MainNet oR sR $ Just tR + return $ Just newUA `shouldBe` targetUA + describe "Sapling Change Payment Address generation test" $ do + it "Call genSaplingInternalAddress" $ do + let sk = + [ 3 + , 183 + , 26 + , 151 + , 89 + , 0 + , 0 + , 0 + , 128 + , 199 + , 189 + , 33 + , 169 + , 114 + , 194 + , 50 + , 0 + , 139 + , 140 + , 162 + , 100 + , 100 + , 35 + , 58 + , 226 + , 6 + , 47 + , 232 + , 34 + , 214 + , 11 + , 173 + , 142 + , 40 + , 45 + , 163 + , 190 + , 207 + , 49 + , 130 + , 158 + , 113 + , 232 + , 251 + , 79 + , 98 + , 77 + , 195 + , 196 + , 40 + , 42 + , 113 + , 133 + , 35 + , 211 + , 68 + , 146 + , 104 + , 5 + , 56 + , 244 + , 162 + , 55 + , 239 + , 55 + , 112 + , 37 + , 38 + , 189 + , 183 + , 121 + , 201 + , 1 + , 60 + , 158 + , 151 + , 141 + , 123 + , 250 + , 95 + , 169 + , 123 + , 208 + , 56 + , 103 + , 74 + , 85 + , 49 + , 152 + , 207 + , 245 + , 216 + , 58 + , 37 + , 0 + , 127 + , 186 + , 245 + , 234 + , 47 + , 68 + , 11 + , 78 + , 76 + , 12 + , 171 + , 37 + , 63 + , 172 + , 90 + , 111 + , 94 + , 88 + , 152 + , 211 + , 53 + , 243 + , 142 + , 16 + , 195 + , 142 + , 50 + , 14 + , 13 + , 32 + , 91 + , 5 + , 82 + , 182 + , 121 + , 136 + , 109 + , 125 + , 165 + , 125 + , 123 + , 226 + , 54 + , 60 + , 34 + , 62 + , 111 + , 167 + , 88 + , 254 + , 113 + , 204 + , 47 + , 181 + , 97 + , 18 + , 220 + , 46 + , 51 + , 160 + , 62 + , 16 + , 199 + , 143 + , 184 + , 200 + , 209 + , 124 + , 154 + , 175 + , 29 + , 216 + , 48 + , 201 + ] :: [Word8] + let cAdr = + [ 31 + , 232 + , 31 + , 17 + , 196 + , 178 + , 208 + , 227 + , 206 + , 199 + , 105 + , 55 + , 147 + , 23 + , 151 + , 206 + , 117 + , 59 + , 249 + , 162 + , 218 + , 140 + , 189 + , 17 + , 60 + , 116 + , 106 + , 56 + , 64 + , 203 + , 152 + , 52 + , 155 + , 133 + , 179 + , 118 + , 47 + , 161 + , 70 + , 155 + , 21 + , 22 + , 41 + ] :: [Word8] + let bscAdr = SaplingReceiver $ BS.pack cAdr + let ca = genSaplingInternalAddress (SaplingSpendingKey $ BS.pack sk) + fromMaybe (SaplingReceiver "") ca `shouldBe` bscAdr + describe "Zebra response processing: " $ do + it "Read block time" $ do + let blkdata = + "04000000c732575a3e94b7464c84b35935c2fca26e40d37b6f3278bda3d941877b192a0048cb59da8eeaf9f622d4537ec2a27918415f444d0bb143e8091b470a18a438c117fd96323fd1a9c775ed793154de0e8a2023b551431e89b5ce6f30ef047388f0bdbb0266fe81481f2c006046a8be3bca5b8819209d053a88184bb48753171accb807c38f46d00000fd400500b6a304cbc4e6caecc9b3a29ce42d71752bf5396944e28465e29d1bd7e7f3c680db3c4b85a1488d7c4e21a70c1cf50cfe4e7f70a4f1abb638f144afb90dfa69adfc7c981fd8792df19926da695356699ab76213094431bf6491b80572c310ab8e51c27cda39c771371dbf7adc60089ff5f9614577a92ce3a1e452d5d8280e3d970322e1fc5937c53369623d5e58e57672b48c2edc7a86f7d7de62e06668097547b782a01d3794a50f131695f7de3e4bf038b12abffd29b8eba618820e143ba0be57ccaa67f64511460027efb065c6ddff501376744c6b68ca01d884025e7f939521d230b1a46016d66ad3ab2262dbd477b38bbec234a19425b939dc0f1d868035c3ef99eee635c9dfd62235ede6d62f652fec499127a152e7be4f959a9bb7fcca9b335de98d14aee1330fe37f1ba28be26902a1cddbc3f67f2f6317093d3f278c1c0156e5319a0b35360e38719911b8012a8cc34893f05dc28a22787f3e0461836aef602110e621c8b9d78e991ccc0160deb8c8921f6fd13bd00d47a170b9d0b09ffd57e37467400c138823dce7c12027eb9f919b8d5f4072b331a042f6f5ebb814c6a903110cc97161787d1eb2716c8fb98d747abf321a0b079f3b6a1a0329337b4922a0d739886a03f0d475d41005733f26c675dd9694712d6ee07772a5e8f6b5871789ac14159beced65d3b2196725baa4ce04f059a7034ff0d7ce081e94d23e0115b9d8f0d99fa5b4f9771b4feaac881ac80f178c3575cc70858e40f53f1d0406253edba75e2bb6f40eb2ef4ece3df2b8dadbd3ac168b1743a0cfb68ba81ca4c838435002587852e81e0a0c3c77e11218df892ac0c2f343967b127fbb822712ceabc459ded7f3ca3e95ed5ddfe6cdfe5657da1b12ee47eb63af7a839cdbc1f52f51e349dbf2bd7d8529f814108723d97fcfda7563c4fbe3a24118352e4706f9f91406f11acde3a570e10a258fa6a5415f57a26988249a87e57f192d0408b6f56c2ef65d5e37597d144e8fe1014a4f552dc8c213b0a70b2ccff6747f6f1e0d598715d8cb0da37e0331c01da1f9e0505e9d1907628427d5d032a29fa0e14b75289b10c7d3109b590aec2a4debda9eabcbd3163f6df67efd92687d0c492a71d48b2995a658b34e96814e5674a62e3738efe53e0daf4bfe9d1031e45a246bd3b32c6d581b71c0b60ad711570bca3848c38ba5aed74d977a185d199db721c2677d4868e5afaa6cc2c7369898f58f93738681118d529194d41f0cb6103915d2e99422db387fb90046eae764a79dfe8733a7f68439d0f37e773d16ac7f0ba3a8ce0fd6746d038704b2a0a979659a859682581fee0be3fc50eed13e85e6b52f398d16b59f5f738c1eb289b3861d636b961d9220c1f76bf8a8be3ffd1e2472461202285aa1c5b7eac58564aaab0beedb65e1078517e031888aa3cb8843876aaf19311d5150529a10c9d9bce1538f7533aff126e518ef1ebd919f26aa3869ed1ced167cb1de6075897ad2dc461b9156922c4ee387f8e1635e95564d3964e47d39e6e82079e0220df00614ecd4fd31a5f3d13c9191fc65037ad3cd651f055cc795260f21bb0ba8acd7e3b65b5ff81d5a1b187a777c826f96637e00f8147c4bd43753fe9e0cd73564b53a0e9eab27745505ccd2e4a1a217307c66030ca115580ce6ecb9a8b043ca41bd2de15abd13838d130d3cfecd8d0e3bb6c982bf1f9cdc799c3256e2d722d3f13cd7acdb9d75d7e36483b2ae01e5dcb70db824eadd9c3955d79557d612a879fa6da1da479807640e37bf25a22091f1cf6fb5293e60e5616416fb2adb1d9892258e8ac6859978159338d3b6e2063b9d842a24d61d0616506d5088fe76025df35619febd57f92e30699a0df8731bdbad0775922bb12fb1697d9da833010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603204c2a010cffffffff0480b2e60e000000001976a914278aff0c0f8734638ce81aaef4ab0afddd36552888ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000204c2a000000000000000000000000" + getBlockTime (hexString blkdata) `shouldBe` 1711455165 + describe "Raw transaction from faucet" $ do + let h = + hexString + "0400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02" + let h1 = + hexString + "0400008085202f89014b6ded3d2ee43e54135c04f9a305b473d7d741bbe6f383aa636e03e948705e0d01000000fdfd0000483045022100b34ca0fb34959230b95a48e9de95521dc4c013bdc0e6bd2a77a67b373fadea42022030a1b13f16993ab212d9f674b7b8dd3c6723137c183dbd41a9bbfcf66d7811c40147304402200f0bd12663a52ccb5e6211ec633932bbc7f0151ab51194038534abf88f689025022051fe62591dfa11a96095b4aeee2114c77a0d773242939f7ac2fa8e5262ff0865014c6952210231ee0c161e846bc133ad41d295f560d7e24829415afc78ead24d4b10198bb7342103b0b11f2dd10611d06cd6232233678dce736ce2b2141c62bdf0b4fe6f79d736da21029cbeda22b05352f66a338561604cbe11e8332f836385f16abf5088045c39d4dd53aefdffffff02b63500000000000017a91499a8b5b1f3d3a1a12e92dd4f3d94bee8fc34c527873362fa020000000017a914d75fe4791fcd50f2874f1b7457fca5833293b3a38700000000000000000000000000000000000000" + let t = readZebraTransaction h + let t1 = readZebraTransaction h1 + it "Transparent output is identified" $ do + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let a = + "utest13dq4u4dnf3yddw8lq2n6zdclshra6xsp8zgkc5ydyu6k20zrsscmuex46qa4vh84rgd78sqnlleapznnz7mnzx9wv0unts8pv32paj8se5ca3kves2u4a89uy6e8cf4hnarxydxh7hq2e9uu39punfmm53k5h45xn9k3dx35la8j7munh9td7774m8gkqgc4mn40t69w20uu2gtks7a" + let a' = isValidUnifiedAddress a + let tb = zt_tBundle t' + let myTb = fromRawTBundle tb + case a' of + Nothing -> assertFailure "Couldn't read address" + Just addr -> do + case myTb of + Nothing -> assertFailure "Couldn't read transparent bundle" + Just myTb' -> do + let pkHash = + BS.take 20 $ + BS.drop 3 $ + (\(TxOut v s) -> s) (head (tb_vout myTb')) + pkHash `shouldBe` + maybe "" (hexBytes . tr_bytes) (t_rec addr) + it "Transparent input is identified" $ do + case t1 of + Nothing -> assertFailure "Couldn't decode" + Just t1' -> do + let tb1 = zt_tBundle t1' + let txInHex = + HexString + "Km\237=.\228>T\DC3\\\EOT\249\163\ENQ\180s\215\215A\187\230\243\131\170cn\ETX\233Hp^\r" + toText txInHex `shouldBe` + "4b6ded3d2ee43e54135c04f9a305b473d7d741bbe6f383aa636e03e948705e0d" + it "Sapling component is read" $ do + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let sb = zt_sBundle t' + fromRawSBundle sb `shouldNotBe` Nothing + describe "Raw transaction with Sap spend" $ do + let h = + hexString + "050000800a27a726b4d0d6c200000000c3c02a0000000111ac7f9600d034fec892f50600006b09ad88c990a8f3c5bffb6856beebd0ce356c5d1413c63a9a88db71c3f41dc12cd60197ee742fc75b217215e7144db48bd3fa55de211e0c8d7da70aba0efe17124b5250fc9fa095c8e50b53168142eb7ec402289a84ab762ddafb0f972fc07c61eb45249922752dfac17fd704a3b6156a6955286c04587f37fc781a9a0b28e92664dde2300c6321dd21d367c16b4c2b510e5d0a4358624e1ceb816da155e588726f783ed6bf396eaecada3d3bde7a0722419014c509a7fe264003af5388c5c8cdf2076137b26cf66383b41dea2af415f3d16767124616609b96789e2811f7bc8b36c0b746d225c0aa2c98b370fd39b2015eea43d523e5fbccc7ca127e59bc92ac8ad32d05243d278a26f07f33f45df23c3e8dd40d72af35a70068cc3f8445478c9cd3a56d3515e207a1b95c39c9d8b45e66827cf63e6018bf1012a47aa23ec0174735949f62036df3f677f92d3aa10365e2d358d92f77e9a6294c245eb8536493e8480f5215cdf7c863623727efa188fd8679f95318d0072915dc2be8e77b5778c48d3a2ea614990984c29dfaf7c582d269f0e9ffe6e3c475c99dbf5f8756c334b3c7b5954d68549c65fc2648bb389ba63397525829c48f7f32e43e866dfbc8ad86f54a867e592524f9c43b007328b78220cab4537c2d65f64c819ecc19ae09e17b3c84208cc169c2eaf8831d452dc83527a966b288aacce6f261aa8ed23e2c0613c48161c1eb0199915ee9c1f9cf0e7e5445539f5016b3ff701408b45557154a53e48998b49a2d149a67202caa6b585348b42fbeda770e084128760c742537743ba8cec4f997fe575db9cb370d89278c607a598ff06f1ece047855ea0a3d808d84272e22bcdeb2eb5bbe5754947be5d6fe42038d26f13313e039adf7e1091a901a6daa24b247b13c810eb5b50cd6040463c6bfa209a792d77eafb0f063167b11777d97ad05ca7eec1f1a45698e33f8e8b11a0ab0ad16ec10e4abba6de69255e0e00c7e712503a107012a46fcb9c5186a1fc5be96dcc279fa3dd18e69ee4a8ba75edaff105716b4751717aa818d514620ddd13a6716729417571a342dcc8a6f341d05db7614e729491095e4bcc6fbdec2908b6f74d7db81106ab2e1c0ee6217ba3cbb174232a42f2e6aba7e92eb2934dcd91c5a36eae1aac5160bf6076a39baeea84b8bad043c565eb8c4206a74b68c9342f9d7b1b31eec86bb9c5c73549313dd2cbe3bce55ecfdd952f3eb4dd5fa7c2000727b62a0faeb0fac1cf05246221c37560359c44610ee8f93cdd1179fc35fbd808152f507e790eb2140faccb2f6c9897a3a219d8752388e846c7277d82dc80850aeef2de46eb2d81563d53714d39e3553f5133abeae6c487d10664e1b10a90a5319adfa7471aa429095bd272ff4d3df227bdc6075e0cafba54201bddc44edcdfd2317f682f40a25e582273c4845b550b17724eca81e89268eafef9e98533b85bb2c54cdcb6beb4d6f2fc0459a2b36cbb43fcbab720678553fc930063c64a09445ff637c12d0ade4acdef1e19e8481a0b7c2dd968a2ceb7f50efbb76fe21f49d8829d8a26b10453626593a694586a828705c5ab02cccd60e351171aea3e1aaeda07cd960e0f5205019766ff3afe124a47f4ef16f0e671e1968e662c7974ee54872538c7019cc5396f165013f4c482d400ed4ff5fb34189c75e92d336cb25563867215dc3aa195f736e06657c9e27f1468acd580ee51ca44220be83c215c1223758a7676d376ea0d0aa74b73032b85a45f69e6f8d4ec3efb6861d5adc35f57e6112dfa7334ba6fbd64b14498a89d8596423125b6486d9208937e9aa010831edcc168be8b7abe7ac21cae4794fe5f019abe1715d5d8601b646767d396e1b7fb8ff50aa43d88a2426545270093ce80be1aaa849dd2edc0310e3cb0bc13a23108776a44e3250fe5aa89b6d62159206377e03e302e3dabff9ecd8675870c7d55e33cb915d7e73b765d1c61f9ba0d504429fe99516df0534cf6a5ad53424b9be39e4835dfb301ae5cbd9edd628f0cdb6c54bd4986a9fba65f60e833bf17b461746ffcc312d9584a55c55c42c8e760d017be56f54a4e1423648c3877f8a44e3a56c21bf2bf85550f3d8a8f040c8c6687aadb2c4d1e1613626a11d30fb25340f5f14b6a7752c5e6c7b866b10f1155360f35ddc8f07ed591fa3366948fbcde1a16c35a589457ded90b69675bad01a1fa9386e68dd2001498d41033a6effe882d9e864ea7991e0c71b3e65c158cba64014f800000000003b38927526d5fc5a1b2f16addcd4aa162272f280e977087de02ec2969f10d25ea3de9a5d3d3975b4b683897bf828be631813bd5b949ddf9c3c5b103b69aa483f5fc46485bc7a5dd51a47543f57d346e680e12d9429ef7c21cacf407656779235d205dffae23666e01d92fb18c063d1bd7cd00935f029149c7e20b3d72b1b951117f0629b1ef1e49047532a59fd8bc1817343d45e832078806eb10304b8e034fdce70464d33cf6c66e857fbda170573a7965f8eed58e6cb7edec4e9f8a666fbca0aeeca2d4d161caae23005f6e0adf6859c8f1bece409aa5d44d285a8e8e9fe86365af4e92384dce318724443e8a95cc790df47cdd3e8aa9ccf19acc5eb19b4cb517c3f63e59512c037a66312786cb6cd2bcd4480e0a21ac63a83b7b9c84d3207a6410aa51a60d15b5526e3ffb4cbb02ee9f43f7ce316ef01dd75544224481d4b8ad2a8944928e2b4432812d1b20aeae3b51808028321588952ea70afeef1709eff5a75463a2201299484ecc421c8452e1700ca9f4932b0aba6b5d46f36e80f2f0297eaf7f44a79cbd21046ca53073ee60d5f2ffe2352b2538326e3e7ed851e8a62612a9b974ceb5757835b974892b049a7a0c531390a4455657b5b5afc5f0f09f8bf838313f403c96c4c2c72c4ced6e642e86b0eff220c1cd3388ffc902af68f87a1b7e8920d7f014e6cd2694b926fcbef23dc645cd75b051add0fc2f99c0346d45c2521b27a00a13dc9754306222f54a2b4f3058e80e0821d6c78e5e80a509525631f34c343567513636855d1c318c49debc651212938c487e069c390868ce700c4d19e1e52eb7b536e1568d03421c3769b920224113bea4d345510bd0393efa1ec22b87e973c663be75c84e7278108ada31af0311796de927257f59823f1ee9955f0f39b618054476658c165ef443d169afe46007ac80a0efa3d678c2707a7a001812f02dac999796872d3328eeaab4b61cadb8be9c4ee0ff9104d417ba71076054735c12fabd5d78f6de83a57f46b8d071e3c273c60a226fa6d555fa3580002cf3b6f3e50a8f5616eef66e7bb28523858315b3cde381003ab885ece89973aa4e228cd5da0d24ebd876097e7664a2b903430963517bb895435703029ea30081a874d15f4997dec3b89cd728075a0b21d838a57052f02fad03c1a3f74427d149c6225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631585288b7ac6c67346b1d9ab1ee7d24e8cb7e35f2d2b5401a89af6a1648d19f8fcd14973c4c2db543d772b81bc363e707bc534f00b872df0e3d4c73e9bc513b2e6c49e07d4475f251c21d60cf8dc2c9aed2922e4efbe8cad80db14531a63e629f4cfb01df3f61fc470123a7caa141f104fb37d2712628648eec56759bd59cff1da5e7cac043539a2040b6d364f1449ec104bec6f487dd1f784dfe377d070fe704bcfaf4ca99cabf23f4ac1f58afdcd7c8a58ede5023034cb324722e349135c0125f49a7b4fcb22d81d641c06dd0cad8d0bcadd5c4ab7c2015ad7f98b1e4db7b89dd2aa3d057b13456f4ba3b0d52d3d0c4efb35deae8d1bfa39f593be4cd8311f6e89ac462a116ab4776576c1f87b7aff41ddc728fbc601aac334148307d9a3be7c11c4b122045ef2ff913eea3af62bff2b71433f3b65f8ea939fdc19877e98a70c8cce6b94eb2bf4d696392183f8224132b004a29db1fc0702b3c84e0d345d82cd622ff9fcf567b7dd88163ca21a32122f7c2c98c8d9d0861c02548a831e5e99a064b2be13774f76669b80f90cf8c1c0749dd646dabd68d18bb6e6f84dd8ee32acc2edce1c7d02b3b145f416b54b7bbeece8087eb03f75dd317caacfe8cb2c4573f4ff9beaa2c5ddc75b427db079c7951b88acdb8a7f4dbde154cd399cf579aa683e30bb430533b3241c8703bc6576666ffd299fb4d623f277b1f8be372f705ffe594644eed3ef2bb7070e843851b11252ae8aa8414ba17c94e63685815376246361ddc46301c995a2a6857004376bdee9c92f908595977109a0074ea3a8ea89131454429deb02d6431e3514e50632000117106dba12cae3925f260d527a7bb2cd6851cc91e5d4162a2b66b88540c1110cee4672b23b32a9269eeb86063df9d076ece42a9c7938c6fd5d037a4e4ac46942c365c75b75aa08c403690de155f96ddd23a3183653a9db614f2bf069620be62842947f6aa8abc340140aea22fa3b1fa7fb552a4f0a43fc89b3790cd3721967624451f94983d90d65063d4d9c60bad78f7bb11bdb8e6af9f806bcb34d7fdb8a2da4f6c3bc27ce4a48ad529f6a5b6e055f35c344a1dd2d6a085d1fa5bbfe9fcf6bda46b320e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e7f01c4200214e2969fb6c0fab8dc4ddb8475e28a21eb6b902d542dba5e4ad0b3b89b76ea777e6d9152feab5b0dd55f4f2d3db5f4e4013e8eb6f637f661f4f5b6ea8a73437437d14190bab1fc44df4fa78558c2b53e23283f4553101daf28f7c4ec5d9d93c47ace2ccf6d0514e0d33ea597b07f88ba4c272def6ba368f0239ba59003728fc20e9cc3e3c367a04f8faefb5a9725f0c8fa298aae306089908168cd53ea4fe769175863360f8838dbb3a185612d35b97aabd00c4fcc40245349196190a01f3b74fe6ac7f1dc854be928108bb23c67742fe4845b79b6286d99771d85bd9145b20d94b38ad9b273ccbad0ddeb5b7ba1ac322a20619b4d1ca9476ce741a0cca4c90b3b783f3b3d5064a7c15f72b97836db164d77661d415b12df263b1ad2e2efe92c5ccc50ba10c2d5d2debf89132649aa3163a23648f9d53bf14f183b920d0379ea04897a6b9245aa22ed2e4d913bace308307ea05d31753a43f62968f2653a6aad5fb483e4e78bbc42b4b853d92c81bb9c4c03934dafc8daf9ad16b3252d29028a9e5ea4278e928570c1bd112d6900df3da90a8eea441fcd1a73a1d982b010e6e7f73023dc36fbff9600f788da14b1410b31fff4ab2cd895f318aa9cc7baf9aaff1066ae4be169f663b6bdca53fa462174b56c6883e1b4b84df12b1f246de72e1af85f913000407ac30410149d1e4255ce94bc26b6ae62223f238be472f0dc9c812d137d88a7fa3fc0808693f9add9db0827d40d3dfca3dac9000a12b56d8803aff5517682e9cda163128fe166033397b79afee3f74528ccc31e3c5289812ee730cf5011ae15b0c86740644806048ac2282b7ddaed2e469c289cc70689a385e2c0459affae17a3e6d3815433363be215da2c110369a6ae12325d3c40340df4d77d09106f75d51d0c481bcf8228a4152c4dc45026f4e8de1270e04b60ac57ba0bf116fa003cc65d8ecc210e757365287203582608ffffffffffbc221f01e2f8306f906e82bb3b0952ca54890e06591275b726215e27c828761bfd601ce344fd29dfbfb9b37d62c86723a0d6a75fa6d2d8bd0c6ce822109d0ce816c8add2ea6343855d5e9ad63f3a730020eb46909af6d606ee3b5811cb807b9bfe2b2033d9ac8732b2b0eb9b28bec9eb6133f6c20b4c05e1dfa77155d289bbaa3ea6042e65326d32224e00b96f4a7af04461e1dd662284d566d6a9de2e435caca9c51fee6b665d6a1f641509b176cb29ff3fc67401e766f5ca1fab7584b36396eaba2f05e5ad0cbbba29c5372d5443f9afb4e2411f1284da03f9cbd2827ca725afe395024bc9436910053f8f8c4cef37acfc78cc05d7de0c77fc1492ef46469d93c49aadd42036c5fd6ef8d0c70d7c57e0e0e9f75ac94bbfaac9373ddc202f3393bc0f37b0a7769fca64300cd3594a29536692e575c9ef4e5dd544e108cad4ff895b24b431a86f1da264ede7b4b452e600035a38c902557b1ebb2abead1783765e778e7bec298ac9e021c82fbca82acf80094093afaa499f55a164237303d2ddfd0f8726320a0260dc2d59961c33c2d89ba40e65d46075b2d3206d76b27d00575a963489d2f168a53a0b5f04227d4cf935a2e82917c018270b7714b5598c838ed82e9ddfbdaa4448858db050cd14c500ac0e5fd566610e1d4ae2534565715cc96e499e5c190be37eea9d8fd23814658038fcc761b887568097da83232ee39b47e73c9ac854b8e70f38f6999e6f889a16ade97b9b206eae237f597f8ba0d2f2bf8f3794ea782f2264e9688a717f20431734d1bb989bfb4e40d3f4e8ccf1b8e938802a27402bd3a02d3d073e555acc5c4d941d5fbb3fef890b2e59a6a745b2e966c1903a988d586b53f63bc2a9253c7afb73f204fd3aa4a82332b0b010da159f29176ba146769d340399225d61bfad40ee21f765c641552d41b46bf5113de89318a6b0ab66ce02aea59ee302d8db4b2b1b4c71693b5159fd53627844f3b24288f585f536a52a3dcc4c71227b3a1489a5d31fc37e5a708bd724a889efec04c0c57730352493c5aead0eeb412b03a7514c24f4f3e659d529796218bb825c901da92869bc2b93afd359e24c8d55006d883e2380be6aa77d55218e831a7d5c4d566e293e4110b02ccc0508902a3d876dfd31f83b7b51b7d0d82d5838f6ed96ebb7dacf438526387a0b5053b522db03e5241b8069e9d43042922d4cb2a8c1112b05ccaae37e23511cdc90c6895713f7574f615df37c425d7cfa368ec72703a464b1d347c6db13d6496ffbb868594a330c213fd222a0ac618f1b312f4d6f5dd078ceafa20691aec35fd3b8d6479a63b82bff06a6eb619ae87ade1a5542b9582b1effde9e5d1db3c619458fab459ea2f91232089b716afa2de4ac9830830ce49284de17556de89400e2555960fee44db6379085636a5e4b938b1cd067f8d5bece171d463b432225a1290c306727187e1d2d482662c783736700be93e3f9304e0113d244e8fe4e1b38be3ec342a5a5c062cf1e20f3dc60f519947156221ee244b7e14c39f495720e4b727daf2e39c2e352b0a247660550ca8f82796db4e424be367d3d1ab70eb9b37649d9634c456d889354be44a45c61c37b5def297fc36a76d9dd676c2f643d997ab97b7735f914fbdd38f86508406a8de7dac15a1cb5ac5d9f90eb1f8b2301906c52d5c21674fe09e7f3b9c5990250efb5a890c420aeb1c5fe137755416d69aecc7549168876eb00f71865d04c946f11976cae4085eebd4e73be71ed8505470b0bfb446d735fe1afa33380ceeece76a8347e8fe2f58e34a579fd57468968d712218cbc99a68c3377a6ac542888542fc36fef2cf55c0a99266766abe6ecdbd00675c0935afe8cf8d938c1df6f6f4c849d72f354da1f09b6e7fc9b497544cf6930050d6ab8980e09feb965eebd50e91efebdb234e533372a67445de2088803328f429a8d6923899b849fb7f0af2132808544cb6bf99d0b18abe3df119bb5fdf5818e836064e5a0b329339c35ec7481628a71d35a7f5021001f72bc2b98a15aaabb4c5c462b44db100efb3a3589ec4b9f5e7900dc293443d9e655c8a50e51a00980be62f3821174c918500f9f06a25e109d3f7d63d588271a2a502e89a2d084350af0089aa4ca95be15133fac659f79df3bdde5384c2c25cdc4f94cffccfc125aba0fcdbfaa5fa9a18821f17a807aae39e4307f3b6d207a45f81877a988615473bbab0a580ae41e911fc54edd3b0cfa2391ba4c88945d2fdfd63c9a9905c2f847beb833d429af910fecd4a301e09244743d21439a66ec026ccf1ef1fd5f89d8600eaa5e07328c315205c005ed4caa4514df692a59db7b218e7543499323d4f90004398e425f77dccb3e35374bdbfc1daf35a4eed54758a1cdbe35a52d597f9cf0b3414d00d2dd5cf8dcfe51b2af90ddb2c0c59b391bead7b8fa2073061b4dd04406f2e7cda52ff000bc15dc02bf2bf22838e0ef641845bc4ce6c143d56e9a3b833e5cf4186978bf7c0e13b40e5e7f1d0ef28a3b388fa16619c13bb47d5eab57fb1c4ecc0b2ebfbf26200075b0cf5137e8f13703b7fdebd72218f32b8a0050c6633b5f1a3b9c62c83e81fc1c55741e759c1274d77a029eb7af4c3fd689331b69f80343332f541d1026a66c49fc5bd7012847d4d48be2b41064293785105872300f2ef8cd8f8b9892e96309456ecebfc893404da002867264d0a3caab84867488bb269e1ecc346d1ef7c1c76b6d16882dc5ea9dbee597dfa2d3be8e79091236dd750ad10a50e2f9f6adc1722f6131cc4986b41cd4acbe479f6ece8eb0892345662a2a0064ed74cd74957e796c4fac9c7145b0103e60750bc351f9d6d72faf2858ef23b107c07caff3940696ccf74e41001f17aa29edb068eeb6ec0b0bff10fab2571356ff5a3cff63767cad097f9dd4a5919700bd7e24182acdabf903171788800f17d5005dc07d6d5bed1dec81e13bb33196852dbcf978b45932bc28e60a83900502b11f09ff61feb208682500654cc9d2b42b60ecf99724bec398dd6829bd44a30151ab5d05c39284be657e7dde539668b5dd0b8ddcb331c4fcacb7b9c9520cc01c421c9955d7b168a9416e36c6e436e6d328d9e0fadd441fbd0d1d5bdbebbe9c19a3607746d06282c0d440ac8a243c47c27f04ac2d24620ee83fa6b1319fb4a137d1c82c436676f8d75f1b9d99b01c6f9cb4a91027631cc5717d07961179bac417ef8ae8bec969828872794cdda7b6f2e7472c50497a35782ca5841d5419ef3805c9fb30b8205f8fad2226e67048a4923de049ff3ae1f804b3ed33637c81e7530e970e2f51f9bc435e2d4ce3ebc8aea207115a4ec369a4caa7a3fc814224e8fa0e9a85f1c928ba24507d2ba16e176ad8cac7c125b5edef7d8efe1423c857949a1768748377a988d68098502720e75e138abda3f73eb9b2afdd50ef2f8d20ed5f36343fddeb59a8a591582d2d4c66ec0a89dccab3bc6cdc5aaf93a7518dd1c3a92ca486b6833bed70294d6d04b02803e4841a7faf9ab06a9c05ef672ba2b61c5e004bdfd6e490da458ccfa58be7dfef9dcc0dc79179f14659d8c4456045e48ce0039982580b9d7240209d4e5767b0bb391ef1f8929bf06d5e3c8226ecbdf8844902eab10f22b9f23381ba1b5f5c241a614b0a3cde02559669e0520ba5116f58e12fca16603e129e2e5315d581acc0d2c6ff9caaa7742b6c3e28d37f0766ed08a33f39cff6919af762d4ad2168614a190700aa87a95759951364d8445e680cc9ad0b75827810546a9510926d97835d62a65a3e5a375150b6e9a96fdca74a59269b2dd788320b96d92186f3226031684c45ab69c26cb754296a2a40c0026eb95c4f0b64db9872710d731d42c7510922dc4459600886d44586099fb957f0b6158ad703d11569699664e839d8acff3a5261f8b72cc43763f02568dea203b65be9cb06161a09d17203f168cd8469d805475dccc7c76ae2ef216e068f39ec7344f05c3d2ad794cacca891b3d0bfed335c1519203d289f74b896cb6c33c15ca0791ffeaf11c9d91b95438be68769f91d08997b77905d5a89460a3619dd44d5d92b5f88542521763c815ce15bf4f34c018297d777a602e27276021619a82a21be68cb5f4b321bca1baa7befd5cdffc80121a1fa379004f441148057d6f1f66863921c3bdd3117bb5d9ad52c45ac9a45391ec83cee3c6dcdfc40ddb3ed25a87f793430dc440a6d33218a04426b5bb7031d33b2439f01ab2fc2f64a43d2a482b1428ff5417d2d33c818166c20effbf46811d50d9d05f7ef1a37d249f64689cb37d8cbca2dd7086e141caea4c00b35286c9601f5f1cb6186452405c4a975e1c13cc10cf33e5908287f5f3fe36af89f374d172403dadf01bd06055582753b0a0a387361b91f233e6beb112b9b1eaf0a835b4d14c82477ef46868d09d03601a88f4da2dc7cef2a04a62e61311cc0284e8e48c571f622bf5ee95992ef425227d6b9272b6727b10005c53778ae7a6b155f3a7f00086e1e952527cee0d40ddd5e0caefdb19a082ea43d85dec0168d5641b570fbcbbe9058e49ed4dc546fec49d1c2041b6343dee68a09beb95df54eacdfe2792307c88b20408692eef15bb3e370ac877e927cf58ace1931e2dde4ddbd228de70fc718a5bb30d68e0196bd2d00c1a3ef138adf1640403c513c07a0e8aefdca2bfd3a9404f4c9af683bf52980c43d48912ac5347c438100eb50613a082270e1bdb9a1a2241310fcf422611ff6b67cca4c9988489d344215b9b8f8b6247903edbd5689733427c555656a916d106fe6ebba66c25c1c659d27f86b712bfac6705301f03584da09b9da428092504dae0bda0b95cb7efbed8224349462ddd2a6966cffc455ddf78d3964d1b320dddd31e3e7edd4aa40afbe0d1d01ef0b1b2eab7a266201b3ed078199c33167da82795a3dca83aa06e921a4802fc34ff1e66bf31b2e047734ae2eaf765184498e127a296c5226af7a7a03ab5815efd8ee2660a4ae67a26b88d90665f5143b503998374a0175f2e2f6ac5bd25f21fc62ff01bf96ea49a15b1585441c91bde7e634ced455dec7d3a6ba115ff3b12bcfe04cbecf6af5afb4fc5b98392d2fc2deb203ad0c5f3b7ffc2fe116d7a4c40487f5d56421b031f17ef5592e9d2f052fbd8e62dc004b9caf7ced9204fc49cb2ba40ab94bffebc09851f785230ff2137e992a04aa7f63305c9d2e3b6f07f94a09b17fb79eb6cbd5f76f84ec738e80b75a229df1215931a14bc22f14a7d85a2f2f9ba9e7cb01be437e6117178c7640b766eed27820449bb3a477d3bc9f75acd433701d2a01d2dd0d7d29deaafce62cc16b83e0fa72229c045af4f16d9cf33e1e3e8a137e7da245a9906e0968fde8037a6c6a6de0ca425650c10ce8311fc1290f02ad70256bec8a70501866c727c1abd2ec9e38bee97010e2f6727888ec24d15235f58458f6436545aee826d06569824efc250d8064406cbeb735e6e2a5c0c04b2fb267952091813646018655d1bc38828d4ff512fa4c127245ee0436ad4f39272cf0024eacfeefc8adabb6947d7ca619a5fdf16e45cce1bab1b93881eba5827332e4f6449871e1281e94b61e8e4a0827314b502af91e4dc372034dc4a76beabd158014fe0909c4218308ec23258a09de7c2761bb90c07736faa900e44c1d46252fdeaa69dff7de1b235950537043d28b8f55faac068f7cc307716524d691d11d2f9243ed14b10686e7ad6507d91ba599f76e4b82b8b7b3aeb91355cae7595b913b280b6e2063544a350d855e06732d72bbeae44c4aeb3c2617b137ae71ec54f0073b0aea31ddc7f9cc230542410be56f25ff12bfad25a02753a051bdbb793fc1388ef1a294b525afb5d1f00909d9b267ae4ccc7e62cb6418edb56a9752619ada13cf24c7c383c68f33daf425bca04b9b991cafaa21932e248ede5f0ba666ddc92dfc894627ef08847726002572e51a952e48c875a9f3daf5529b80734622d2ff33c1361dbcf2db76b5a76408eaf5060428763de6150cb883997488cd2936b1f92ff9bbe5eea42c55f090e11affba3154c01261267b65ab13ceebd58ed5676ccd1340e922109ec3a5b7dc115511486c308db7de52b192f642f2e9f5344b66e57d0f380b2bd318c435a9c095db5133ed53c9dde09879743b172c3f3464a0526cc3083d04c8da3b467eb9197d696e3888e3cd59bdd01261b529eb7da5ac57143c130195f5b5d7c0cf75c2bdbfc9e98b56b449f8e9b4eec249a140a3bf22498bcba318c35a7394acd36f79d67942147df440b6c249ad6f4a650ad12479fc7dbd474b34415fe6ed5b1ae62116f2d0738e84cb74814365c2eaae75d75c1e97ed02e65012e3f78505e42017786cbbaa290db6104da5df3ce5eb84dbe2f4cf2a0a36e5391814e4d05b2fb4a876636367e9c44fc975e77e4f56e62edc1cecfe6bc75550c00f2a0d3629f3b2314cb8dbda80b03437fd88eff616a3a4a03000ba3936b2d33229b09823de81235d67577d92e61a9bc8f31f5656db69ff54c97c1e410d2281c40e8ea799a3a8fe19383398856c12fa95693d545567d3df118494c401c9d77236045bd62a651af5c4842f0b119996312a2b555cb4799bec2890ba4a4624ce3eaf38e988510a7f5bf9573611d0b9b88b8606cfd1b7e57355a28110f9f0a8b46a2f235f9470ba45f0063556084640f79d837c8071b34408ff7bb78d08ffcac535fa3de0685b5daa94a89ced1a1c6d32ab63bbbb1b50c1d6033e9e4c377d5eccd0533b53587f4728291d7bba94c9dc15c64df469f29bdfd5cc621fee43bb33b5969111de0aa7cd65dcf0c927edf37ca242bc64f810c9c7c6ea9652f876d05219ea660688d07d7e0e85625e47afd3652b4bf13d5e28dc94076b12f7640edfe303bd5a1dd07ddfcd29675632f62843b8268a3291f38c53fb115f5325f8e71b5f728319296d7181d8dd5df7524b1d0908edc699c48fa5868f5e7f6015f649b6d82998d43e061cf94fbfc3562c72dccfd085fd0842be56d141c081d4431d7efe66c3c9581e68f74c739bec99b003890c5f9e37497353d6937da0a355128c4ba0d657177518157e811454c0000a340b6ea94d644fc967a3b1934806e4ffb2b6c44fd68f0d0771854cd92db6a4cacb4a51cba72e98077b8de6867885151c9d5a6875764e230f2ca135d546f10057a2f243c0227c92ce23cb1c219cc8225ced38a987ec5c80148bb8fcd36c5fe12d1b097cca1c669739ed04641d14a5614852b71e600f39f100b48bc384befa6e9184eda4515f30e48f2d8e7da182994a2d8039a5f167743e3a83ae225772c31dc15ed64829b285dce99d29d8ffc28b77bae6a5db570b676327eaca9a081330b3d950b25cf9b0199dbd9d734407a7b6788eb1c7b190b326161f86133f7061573dab25df10bdb9ad0327bcd8e6376d60b011265bb48603bf2730e8737fd3675bb1fbacf0971ac3818eaef2e48d264d80b74033992b6dccaa3309b771cacbabc31f1b5df9c5ca982c8cdc813956b9c7afa7651e781f31161bb61e68141b0bc9bb46de37e468170cdeaea20c730b81eaef4a7d51506a6e8d40e6355aeb1e3b51d43cd39ff4eeba80cdb70a6a0d85dbadcd64a80898df76cedb8506902f2e63206225147df250d4226fd9cf1c6f5cea87167a81630d7234e4048216a7758da572a26702d5083b1e1906281bf92eedfc2f988ec2828dd7079430bd2f0111edcac208beea9c4bb01ad70123a7d335d191fabfd6c637e9899fbf50262addadf5a3cf4a138954738b456bb9c8605625b2aae94128991e91c0e62828ad204e522ba6a55ab1c3064d56514da5c46943a1d8421fac70d584daea333df201203be96e5b6effe1725e541deedbedfb1c99d73d857321b5c79bc7e5078d34763f5ecdf30bf8ce3509263ff8516cc11b0d4e7892442f6756e23f05a98759de5209589c86c716d28ec30376b1aa2603a35615d9f712aa112c19b4364cdd55947c12dd8704ed1e6d804a6f3947fedb1c22e6b5d9ec4182ce82248484f1c077d8fb18ea2633caeff87daa396089a27078c0670fcdaba1d61091e2565950799be18b329f57e4cf4c80010c48ce4d970bffb525ba672ef04cf1ccd1feaf4d91a7f4b60011945364a4888d78e0538abfc2c85c4079e0895718a5802098fbca9570219d11d695164cc61e58502be4f8f3a3a2f4b5765d870ff98bb237b86180fef9029d0093b7fe806893d1a25440698b239a1ff3db8f7c8a687739f3d180d81fbe75bb323585016d69348c8d20c9704547fedfbcb90f82bfd3337e280f81fa7cefc7fc2b71645de84bb5e75a52227b773ed01aa743182c1716e3b8fb62984300696c2c29a14cdeb8033ff2952105b54d50e4a2bfb590f59a794dc6e3cd293106f806e838451e7a45d953a214dc1d630563b895880aabfce34ce9261c87a6be925eb37316382a0f8c5440254be1aad69e7685730d42bbcd0576627c38848bdc92b16ef61366fb6632202f483f25032043f06f85009b4e13015865a387bfdd7c6aa4436b32ab04041848b968bb97e0d19a0c0e0c0899c1f463547e9250ba8d13109dc52e216b614f1193a6f44d58959e8b4acd4511d9d1c4adedb9aa9c3cdaaa5e65c6641b8194bca4a574ea98a907b79f0e10b1d729c9a1e42a87b8df7abf1d73cb835a3df2469b47696d9512576d9aef15ae943e48cb86c535bee7370589f9b288f1062f9c16fd7d5e9033b8a1efe20773a313c68ee4219f9d449fbd9e67f55d276f6a21c305edddfeefae11444635ef64f2a9e69e6de505fa9dc82822033265a1d86a1a301695d0ae9310d9e29b253f31af878631b6e750e150004b1aea96be32d6a4380e2952c0b67ccdbc2d363cac6cb66bde674cc0e413a195459503b78c9ab8a606da64a170c63479a96dd3051b1e97153be047747cb20fccaeac1512d28b8126136b95a4379f59382bef28ff51f6b04ff085ee6762b7772b81dcdb4efcb95b52175d52325c8e954c6864096e93b87ea0cbf575ac028eccc698a5fc6f4a07ceea0ade09f88abd662057f7fb3a520d2c473cccb2a7047fc28f797936c16d993f2011e9524fca9d46832acdcd6ef1a8027bbe285d916bd31617d59ed53c7e1289661106b25a937c68e755d23cfa9491056c2c3e12c503c4d2c72ed942e55f2359243c85569cc240b7d9a81eb5e7836431cb4b5c5e26fd627e24a9e7d1487c4e7f64232ca1f23048158092016ef5e7647e0ae4d313a6422be0e998446fbf79cb7d70b33962e0f6c2ceea814a8ce8d8ea72186fec4f5dcc63ef82a577f5a8e84579623f6993b5b6c76e4a5390b5557d7b8a085229827c08f2ee968dfa49997e59ed068d4118b0339605305b8a2b1ba3a54cd9eb915acf75eeb0748da73e2b20c81de83de1b74181574e8648315a12a39276361845bbeaad5604229d7f4e3a08af4a9eaf93fbb8db90b0015992587e6520cc603516374320ab70b61c9ac5c1e8e81c97b1fe15916f7791b7f657c27f5fa5c0db41245a9615a4b50d3802fbdad494b6eb22abc3bcf030622c7e4067dc6941b961ec685fb554acde232580c42608d4eb9c0dd0659567348696cb99e33efc5063455cc0820f886e4b3b2cce680c0f77dd77098eb87d7f35474f1e5f2dba3329dccba450c6f97f5d2298056afd4218aca1ee835621896838a791e2e04d17f0b4f7f3a3f30a0fb717e054bf11f775e20d7a5aac4841c31cb8d3570855504be775c4582963bb2538189e204a8bfdfa59955a2bac2705dd78a6f8c9f3259326febcf70a337166ee934856fc8234a2d61b044578b940179b590b40363159538658a9b39ce9d39d51aface156c9149f47912f50dfb5b8e241c2e60f78bfa45e2996a981bf51afe1989bb2ad6a8cd6ecab3424893eb760aa4ac1ad724b15d7859915bcd9d625624570e454b5e1eb69b441c729d42a2755bfc3fc8d7e7a5c38fc07b576ae9904eae0104652be03e99b8e98183b175596708f40aced3e814c940b590a26d34206808438a41d169a6a33b6b47f3d3c4a2c8ae643d8cf184833cd6c9a0de44070557be274e0cbe89991ee768df8a98bcf180159187fafc44d9a91e3fabbd81e2d66ea303b963059c215562aba48b8e76c215b06757bbe70d2452fc57d003f4a5081fc4c4849bccf0ac8cf9504f34fb2c383eebad5a6d105c16d83a902b1adbd347dff749bec214d209cfddc171b6ee5ea3b201c07c622d2b7e4ecaf989ff8ecf7b4682b83b0c84d0766547e77ea1d63cb8e45e86b6df0e912b4ac70c3e15784484d714f8873e91ef724ec0b5396e2700d35607cd73847cb966bd95a18ce5b3d32456a76cb1a96172034f8391f2b63c305132e745cbe5426c5c31a30e77108459507674ac0aa22c2e6df15f11285529ddf11e1865d4b3616fe0f04aa186eb8d9cfcce8440fac9fee9cf0fc1872f4a0ad28236f3e66d8123d80d08590d5ad6d31dd69ea10b762b3698ece119d190d53f004b65c947d46b3967db026d76b9460bd78f67077eb78e8828f2d6a2fe2452ccbe006f201977461bf38e3b136e04293b65b07bb1f08070bbd8cf284b76ee4b21faa01c880ee6376b9746f03c9ad034fefce1fd5df27ad9073299966771be915e68636" + let t = readZebraTransaction h + it "Sapling component is read" $ do + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let sb = zt_sBundle t' + fromRawSBundle sb `shouldNotBe` Nothing + describe "Raw transaction with Orchard" $ do + let h = + hexString + "050000800a27a726b4d0d6c200000000ff8e210000000001146cc65bd6d252d09b8eb0a8ab0aab6d7a798325aefc1d3032fc6d31373a85a25a3a16b447a698f720ade1bc290a74d85574b5b20515391035a67f8d5883dc65ea3ba4a17b009d6f325d41072b3ce240270959a7ffd040e5f16c697d8148973c62ffe037fc83aded21e4c91722b52520a2395c23e9c1a896f4b0f12d32ae8e31833d9d95adae40f6ecf7aff52af184efd390a4c1aa76b5fb1cab6003b1a8a004016f385926661f56d38273ec2c3df7775210310a65fff5fa9ac5509f0784eefea28bdcc67b0ff69eef930335f3b9768529e2bfe733024492101f642f989de8cbf04dd66638e9317780bce47085079675b772664c8007e96597dba83ea9af22ddf07ff1c212983d4a902914431245357527294e69ea5616e720ef1e9215bbfa33ba108b8d07efff2bad1850525d7725c681761c9b8c844a5548afabf176863de7b4cde3901defc3e83d31086d3c6e6af9a5fcc3cfb38b52ac7de84f91df5e0587f7603773401a62eeef10cd3ccf4d927ef42402c32f32280abbeaac33e73ceda52089820a186e9a1adfea81453998c6bbaa0deb41bc4f94586bfee80bad25fc71abe7c6dd44bcb1a6929a0112c7e4f8fcadb9745bde9422b954f72954c4d22db48719de61f383d620935b647337f73d119d79fd208e1d5a92f0855447df5782cd4764ba91efa65d9e4ebaa34e2eccb7aac93a5b0efe0c7664f3cd9384b3ff706ad3019c907cdcfa084351c9f6a0bfa8c78c91272ca66ac86dd6e1d0d6ba9704ea7dc54f71a053dce91f844c1ca62b5ddfe6b53834f4a816b1b01460810d9b87517659f4915adf4b84783a60ecf3bd71569259f1ff90a91a0b314bd4c77976d7893bf42e5d6ad0f8df95eb6d6c69d41490be8e39b2452df3bebfc297d5b0fc97f081890390fb0727a96898585f0120a7da9a798f2032590553f724d8756c67c5b0d1c0d23301c4ed60fa283994fd712aab17ca6360256fd5aef0ebc48f0256e3eda5894b53981d0d46768aefdc85b48c1525b7f134dce5d4ec2d76c03c821513f1652d9671219d744bdce5e69b9a74ca0c7c837668f0d8ffffffffffff9534b3d594e1609b3bace18608750b35a066c57f85e291d194400cb351430bbbe212abba32be071e747b7310863bd5fd989855a6567a351b288144b6e9f838c6a517db94673246ef0010b65f9c0be8aca654f6f57b83d893663cfd389ab96ce50e8077fe588c16b1b5989c6cc262e6658efb9b88ac800e49e9e5999e2651b8fff28fa77071d63790df155ed8344e2581ac5205b31d4f17bd748fcf60e35a9d6048d23c94c7aca8d4e541fda497aa268df9c173af5877a5da56d8fa2a42166900c734b62e56792f6c8bed48e4f108a817e83d64d6a59e38cfdb55c0f8a89bc7507c89326266f7ac03a3941f448cb879bd792bb116d0be8876c0856a76ddec0f0c02e16f0338626013ee5f6037fc6a3c69fa291204039d04d17c11295ee3024aea8f5d381e9b7eb3f938b6f9182bf4f889f1e53e30f998b1cdd23f45cfaa20aaef058248cc2e1c487fcdf54a4bc22a68a17cb6fa7b2fbf333b99feb84643d321398b675634929602126b2fb40171e514769bf82f18c267ce9cda0c24300caa9a5a361144d3b7b9ab2243ee9811d9b2e72c8bb1d145cdfcf6b29994a969b41c47208f5dba8d6d871e490e9b970afec4d8bca40ba51825cdc78cc7cde6b6f235a4105b1d1b5e2765efd753095ce770f070b02cce3316721b9345680c146c2f428c0bbca90d5a8cd0a1c4c31cbfa8ec165ea9f9c71d2d05e3cf8bae5e779786f179c45a3cd8087d820cae812aded04f8acda9068af80ea834f79f1bd03bfd66f8a19074649a85ce877df1a621a867debb423ec0d19015b326fcf6f143aba34029c1da2fc7b099378a366c38c9609ef6a9d9e175e21b0c1ab94a84e28ee7f1a00e39cb6fb59f44e4567e9f85f8f98158263c52ec433c042397c784edb07c28d2bca036f59090e819157375d610acb1993a4107b48da13a371f5383429baee209b2c0cc150fcef79a042749668ba1f89ad24a8c746142191ed0e8fd63624a331d98d50daa84ccf9043076947cf5115b9f8787acd36000c5e72c8d783b29bb28a3e46036d0a592ce8a158ee5a7ac210be72d3a6185c13645d96a8446021b64043ab8b589a20091c152e7d5a993ba94770eea988e289e1536d0d81dbc7046ca9c6d918446bf970894f073c920006681ccf6d1a3f138519c68eba0296069e42dc60f2bcd0f17c400efe4f4e87de8606606dc4fdf31494df4d454d14a440b1d9db4265c7aa9bc8683c68cb149f2cc826427575e2af82e842199a9cb9fdc7243b3bc12f1a71c37eac5cf88ba830cb95728897fa4c177a290d6b2b3814173262da14db9b4ef39fc54f888a6ffef4221ae672fb03bc78ebef479360a682ddb12ea0369a428a6c2960ff8327e9a2f5e5d98ce1eae748db8f6a4631c789b4d751d6b99c97c149a813998d44a7b57ba06c8bcb8a6c73c6388cdcfeb1346cec8fee7bdebf2a2388d9722183eb2d2e0e183cdd092152ef640880f4514f3c5e836cc3a8249413500630aa8da85f9e3cd92bdadbb69a2bab8d71f0b3ec5832a7ddbddd67b34c33b2e12a0c8468e852e4a8f7df45657e9632088aa7c6c5048a2686019cfec33b27fc88e23759938dd55a5dff589c1c21a37da617609e9d8be37dbf9bd6e84ee160fe10268171d969e4611afe9d3482ed4b132dcdd11ee516f36d512a333da20266fd984caebf4937fdfd18ed07b4a45771cf5c8c16c6b258b289a07d136a22acc766011f366c420bafb8fc1a10e42219bede5a3d1166c525491ab60bbd1f973fd3fb2e94cea888e24d5fb0adce51faeda75d62de70094d4b36d38d03cd824d284fad577c3ead4d98bcc8ceccd18174a889b22380bfcc12656e764ea0b8fe1409971283008ed02cbef89d6f544c62c3b001bfe96723fda9190deecba534d69cfa358036fdaf16127b89f925c52d4e750919ffb7182b6a8ad13d0a8e00e0b906978dd24ee11869c1a63837a80e46e1216e2e273aba07aa5b0d97558db0ba7f9ac4c89403c65f1719394e479311f5cf84746e6be6f1abcac03194aa8bf1735811198b5df90dd6cac345779c185c24beda0101b932048dc4144af664a63acc0c395052882ee1f18bd0ddf13bb583861923bc00ed5ae815b964698ca097eda1c4281e039139fa3091890244f926cc4ab773ca8a35d5263d3bb48fd6ac53a4bb4d7d60b36446dbc714c35b5e13a17c5b0c70f67207839d1f7404604aff63b2fa83a4da7dac92aac96b3f250412f8d04a9e298004313b02edefd076c67d8a1316355777814e2e1ab03690e426b672d32ff65c03c592ecce6a70e34fea2e15b9a6b4fd092d027199caf27e84e25c09380b38a5eb8985355b3259aa1d94be74269b84f953053b02ba3be9df872ae5fb2d893188575bdfe222ba267b5461a0d0be274a7d9e6ee51490d98e4cd97978804c4f0f8e9f4908fd8c102b01080f5a02b7578591e95d60f3f56d8e48514b1ce7ea6894f55a32c8ac8564985d18c6b82f8dcde5b315624e9321bdd49dd350c87907cc373c0238a79321e6250e38a0ceb2c060ecee6708c11cb30a49687da9923bcdf011f9aca27e6eb5a8477a2bae2dcff9884cc2349b51a66b5179ed2d8f69e4bbba74c694194e83d04a8566228227eb732a95180c6788483d1f259d52c52fe43357656d50a1cf2902c3124d60d15fc85f0447a1203f824c1106452cfec1c92b18de003f82a0000000000001cbd27436a221a53d08c4838831d1bc60ff7e93df41a51412ef6096eec98bb28fd601c53a5371b23a497062635b5cdde715c23840d37f1cf328f0a2ba96260357689ae3f84a80dbdca1520df68513be1285177d3c0da664c64944de78d8b8d5864f5ac15444cd3204adc4fe487503066c18fbbef8d0515248b0a97577f5aea1d255788ed4bb66d4d56303efe135063392c312b4671963daa20e0ade262984e11263a1588eba3cf829e6131ab506e6a850aacce603e8ecfd6e794c90a772603d80fd2aad6027b34854072a0d23079252adb1ba637bbc650ed4afd35d977e1498d998020bc1c814718b48ba7378a92c56827d3c2f20daa231fa51f0a9188520e2a11149e162489f0d6dbd27cf94fd5775311d3dfbcfeb431bafc3515bbb8c4ba4488c320dca0dfec548fe9f46d8810b3f6b16bb3e3eb0ea130747d3d127c5953ca8d561f8d425a35dc3f2cd831743139fbdcada42308b524313782e23b32d5d54a265eae408623e3b2779fe60e13cf47d54dfe520f9f4e57c68aed31f78629a9074d72ab87bea993a38f95ab40df3ef01735e7d44ad365a786e0d3032f1c1dc4e6839c974185dbe63f8725e79831ebe269f94c96705639ab38d5d0700da04c6a9f686e1ea13391885287ba43cf3ccef1c2227918f15ed55441c45adca84153530bbfea3cf37adbf84831a2bfcbf0ca4a4bbd90e623789fe993dc17503ec11b1ef3049f27b27ff778af364d634a46165cda1dd8241cb88740bce74a73e7e3d656df2dee05bb561a85e64671b191ec802c5bfaca49b8168e44271cf13df756395896ff41a99654f55b6951f20d04b2007938a420218db8e37445ef3267130e288e3270b13a92596a26043e1ae84f3934cdb13363bc2843f74a0f6608a36b52c985132aa427c56b7275a864b3c76502c37b8abb8d0286b3199c78492ba8103f5a23c6cdca2292c75d7d6d7080108850807f78af3dc7e418371c6b8951bd89b79fa586af4e16096b08ac1f4dc2b1e4feaa5c040bb002b57311523197b6e2bef5b79ac9c9b4a339be6f6bf7fbe9b5c93862c87be6647949c70bb2c7e268e2ab39cbadd69de628376b3af744eeabc85b599bbdd09defacefa443e05c9b5f259a7783743fecf1a749c57cacc85703269ed67db1d8d475f6fe25d66f84a77379411ba123d98fcb3ae4eec306489a08372893616a91268ea6bf34ddbf0fdef1360ab9e82f4ac80a24e41f439af06fadc223c61f445b7261eda5e1320e269d1277631ee2245cf930244bf8c04050c514e2d59035b80827586cbfeb7da7a59c1208aa86390b9dc7a9b6ef38879ba4deea5eef47c5c98d9167594cd730abdfaf082090efe759d1b13199d739c112ae324ba24b275bf1d89867b81f4580a7ea3a8d3d07b45e2de6c1c7099de3606873b13f3083ecd1e84456c9a1b1d358075c68b1a7cf0b1f26031a2909e226f5da7877d0085b879165ec4b1d9abb7b0732ab4a6f22d9a7bbd0d494ef3f9af4903dc733fe92c6b2f557d1406d223a93e8ad6e579ebcde9c39a5652ad31335df924e5b6a09a0191821b4a0c8f886e2d7860b75ae79ad9dfbebf3500c8b9762dcd131eb5c8b866b5efb4fbfdcc5e31605c2b7d2ff8db5198a6c41bcf880065ff232ff8f84ca3f8022d3428359dc9fb19f57a6ad3f3d174d8a348879a754b37095f01d9a7f6f873798b97dfc5d7c7eaf0383b3fdccdcc11b30dbb3a0fe3186a36c4ddc9674624e38a81cca60a9bbc1b124021b61a383b7547d6af187022c133ba9d6dadf711a3af3b0255b859b214ef6c5dec592248fc94339a64f19196ca0fdad80f7f8e3d78b1f783b1f038008d0d106bd86e23e33ae5728872d42a555bb36d0e3303f0b4ab41180f4251590ee3ee244b77191c31b9f3f990f71c6e237b9dbcf7ca21c9b4c2446b856c67861785bb9edb920b8f530a7a088313ef044419a879f26db137e1557d079315844f9f60bae03d8cffa7a28bd2857a001fd5d2d999fca95ff91df0e228567f6c9ff592b77b7ccdc93a951f7e34910361a8f4fb517e1c9fb956a3bb50ddf37ed37e8d26adfc0f71e059ba95ec77a1e34e1b3420c893f89b79fc72e3a1d864fd35526727f939badc29740f5ef9c0bb9a3f72e1e08b2ef2ab366f80d8e14e03e92162736e2ccb7cde82b2af08de8a6a81c03c63e2396974ec29fb122d818a2d2d5b29d11b704d3ac3b39431099b7b2f6aae04d28a2182b55380503127e4986ee9e8d5c0c2058e09e4592d08d013a4f088e45403720160622236bd56fbd9cc240efceb1b23c19ceafec49e9d5776ca9da5f7810ac979cf6ec5f678c09257abf79c9ab55dea00054e11b62c0a0ed4363d0a96a37ae1a323aa93bb1af253885afb684ed30caf5cf3b37afe6a6463a16f34cc28b4530c6bc6281f597bea476cd9a773205b96d47ed4b0bfeeee39b7ea44ff194911bcddbb161c2c0ce9488978b99e880d8e43624dbb4a567483ed293348d752634b2f46219575175e3c249b8e4e853142b66491aa1c142e7bb558955747cf2bd61ac802a2a4784d9fe080f771dd537d0ff928b3b04029d9ac03175c2d535ad7f3c123eff30c0437b32dd9fa31b2976e369b89b79a2a95e31aee15a462c5fe25fd937ce6b0795808d16163f2cea8f19b7c83913cb4a793576aa5c0ddfb6415326d8f2be0621017616c85ee46aa768b077dbec72311cb8a0f78ba0621a277a79af6607063839a52c6825a20e1c5818b24628862aff5fbbfe87311866b9956eeea7412ef69a3e4da84699b8d8b45c74ac96c3356989fd4962ba79fc26a92488304fd9f42486266b433eeb57368d26ae91ba4e7ed812581f790314cc7f44639aa7f6775618111369e8e2d68a6ab24388824bbbe3c3b0fbfb88635c1fc3216af1af40eba3555c0b0390a18ccba9e68afbcd21ecb212aaf82846f0a55793945902c48cac5d19332e23248a464529f4cca177137c508b6b13637e7df523254f24b8343d19164174202bcb00d5fe618b760c374f69b5065b1f9acf91af95abd7eb271586cae14fc835f633aacb4cd2ecd0f0ac08b688ef4d13b8a7b4c487ad46485bde0e340309672dc38af275e6ab525971409a39eff0ad134b1674db5d1f9725874e36d8730dc034b0a596c6e0a26e521c199d3e3f86815a64d148ffc394290b19f15390934b5d0da27dc8365360511628b93ebc375d2a531e4f4cfa031eeb501afe96d201c7b6078bbceb8e5d8615599f4c613bbb81a88f4eeaa57a9c008125073a30154044c422eaf2a32cecb15aab0774bb44e52b1792d154b8036ff9224af53e023fc011ab251d47d7d76e55c5015db1926d43c56d055feca1259de53ce98a2faecb5843ce17a3e83ccf678f5d13a8321f278a670c684e62b720e1eefd0abb2e9d0a3ddea81d5eb6e380a1c22af5587daac852e93a86f5e4293c18bb26b32035c7e5ca20cd2e3eabd3de0e55092a79a42c7ccc0aef033d6683043c2a29531a2ef1f503595c0e464ac042153c4685b062275f88bab93cc017f1ef9dc6f8aeab7b0b234f1c543420324512554f1786c82b37836238a4dfdf86f97d09ae466eb39d5f3ab159e2060be309d1284be133ab40abd61468c1706f7f9e57a7bc747479693a03edc8863dd196fd7cb2721260e42f4f606389c4972c74d357e7467b61cb7f455562015d29a59c7cafe0df03f26b77bca81c2bbac8cbccf8a65190b0c4e5ca832e82ce4e11044433aa397106cafc05634ad778270d20d8a13068586bc6b582ea24524fd921a5ee22dbae5296ee86d80f12b78bba26f8b42c1401b75d5cfcc4775c5cd1cb0a9248dabc8f82d216787b2a2780f7d13a5ee8c6ab56399a8dd5db3a152677d01eb8ee98d1927ca5069e0d1ba3907971a2199ba3634b48e570dd97d93729a6c43e4f359e2d89795218d52270a338a1f511b1f008cd04553c1a89caf987fd18c329be7ac2282084ef1789615d7eb7afd2261f606d3953b8863abe57796289e7761b01c3ca0faf2291287f9ad7027d7f0876b5f77d2a7f87dfa6ad4db905d4bcad042f403824aee3b4f8d7b5ab027fe1eda9d683db24f56f694b0b10ec72ba0df40bdd6e52b4a7b8d064ad46c7490c4705c14b06ef55222435d2d6316c7dfee83d225eaa431c11a4b85b0bafbe66fda1abadaec8eabcc2f8c688a7b9cb2942597f20cfafd7520892c535bbf6359a6989a84dd89989d95b8e5222c3aca2e8b0f8881d759e450466b75b5f36b7b723b0a212edf52abd591e7e545a3974b8b31b84b523af7b47e3804b5a268c86ae0bd7c80bc6b578b79f749eccdb4a00813925ef40259ac10bbd0fc4f2fc536c30f7c1efe68a52bee22f57021d23f445211b36fee6302202c9b62c6cf9064a2df424563f9f805e51c4092482253e4c258d53b80a2d26eec9fcdc104f7124d876a3ad573a7f419b0d67a41a34dac9d8f28cf9519b9c2677c9d1e720667d5fce26091d64c6df8b46c98b58017de0d055651e8caa3a230f57aef214ae2f27fa85400e34ce7087538fb6b854a6ad534780052210b8b8c90b4de4c2afbad9f58a71770ec186cfa44b61b53876bf904972078673845ec3181caaccf11f71a8e2a502deaf144f16df1df3bd81277dfbf6e1ae5a17363725ec31759b743066fbd4cafb5eda6e09418bc375f42e0dcbd4624dbe26c5fabb77152124f400e677004fcf3c862a9b5576140cbeb800fcde4409caae06286cb643842687bc6b89738374c7c759c911d7bbec8613f1fdd996d4b970fb6f2a9290a84c34d5fea6b8006357c8e6e9d4048d5a8ec476dab0b55e8358ecf7d27c31da86681f3fa74d072b1150223eaa21c4027378a99c8a2dbdc07d4062c401e92eddfff82841292bda2798c4e2ee9e09f618cc181c91a4dbefa44c410dff5cb705ee005e3a0470c13baddf9066109797a3b51e73a0ef229796d330aee0c0160529a4ca3b39e861ee5c4a0f78619007ecdf32266c7c42f0c972b91cccc45f6f8688b2692f298721cb5da39cbca9a5adef6969a2592ba421680241f8a5384bb92e70acf79c2f41d1171dfc6e1939887b9c8ea94429bcbf3532919fdcfcd0f443d3c95515b41e3f9c84bdcd3de1fd481f98482f667f2d017e3579208341e9a225f85516c8ca133cbada77598b6f596e6151eca377fcc8029cc99a879b26d975684173c0874509117ece4136bd2d69848f858c05e8ba3421499d7fb5e3e7645fc135117d8fdd1dc46bebaedfbad4dc7cc23fad6e696fe349712cf7579b4e63b38cc7d02a4c6a33ee4117d7ccdb86ea02cd791756b2a3c516d59d39ed83a8c328823f1934731820c187624219b487ca86edcc2f61a064e4e8d17f58f4a71462f3f0cfef6be95c0eb3737616de5954096d761a51534b36d798c651541acfe2e5bed6e58c45c46e014923a342409d49e782054f2a4877332e0adb0663dce0e84df0ff0d71c4c5ef18d9cacc8b8d47c78d53fc7649bab719334601c79a345d2101e65d4c3f1616741b24fdd9f7d6569fb8de67799648b323856c2ca96ac91823aa12249934c7050505258c43763ac9b174d55fee71c7817d65056c30d7853500656040621eff9f291e9db198772451cd3d58bc9421f6191a863777edf49125543b22ba127ea3063e1989bc4635d50bb22270948ba594b2066d91e1589aa0c1f476af8b170820ae0d2409df83819579621161ba55cc8d020e1ea68cedbccc9a737746d824b06e12cd2e3730260fdc52a68b6e142bdb997d8f93f25d241360285e7372939828fd54c015ba90bb4d553558ffa2558a43098644357860785addc455bf1ef4ffa5aebb8f002eb0f6a8ddd6f45edcc7cfe88d7ae7141b9113a4ab851b5fa8ff39c7024d2b3202841266f256b1f4ce4e4ea8b83d0067555ddd56fb1f8ed6cb5ce7340cdc403fac1dd3b48c373629202c70f1f95c0001b4f94a9b4bad9e1c64429b541ff57a2f2ea0c1fb2af8f0e490fac415cc3bcdd5739f2069e2bfb873206b8c8811619f1db39386cd2dd748c6301239b4e873f3d4618a43c52ef7d8abc0ca127881dc8809dc20a8730718c64c5a132cf638ec1e3b3cedaae55763637b4c6341baeff17e14c1e3c34e5d23befdddec0c7e25f0ebfa90a0bca07a0f5446507b7fb9cf91cb3cb8d24a111f46e7dbe3ea62b384eebecaf8de49b15b17f9cf151c4c3d33220b3adcf9f87222813ff2125120e77c638ab19c1daf9631d53b3efee67c3c40c9c23495848d4c5ecafc0998bf2704bb78ade666f414e9c6bb100b90451d93397d062b741a3ef20e7e6ebebab8238e2adf415775179c866eb4a2f628924674b20f711d2515d3b024e7e0fd3af6e116cfb0030709fb9bed4a441646c03dfa0b11cb460415ccb3c0999b738a0b07d55be741f3d2646a3d9e22bbfeb46b87acb6fcc2d83cdf44b656d2269a0734259a738d83a7429bfc99da7238b9497fbb10e0f623dc1f793e3be2326a7e55677e20ebb152f71794fd70d7698d0cff1334341b3a8eee38d66a021cae6f8dfe67913fdeb8d46efd9b77f28950aee7ce112f8f0dfc04132a99d95dfa923d391e06a578056ec8d15ddfb0fe8cd2790ed55f8254c6351d471c7273c2e61c2694bf6b0a1173cdc2d422246ca5cc51a9b8669dc331fe33bfe330d5167a244cf041f8444775562eaa8ddb92e0623d4c689db0919ece87a7feeb1ddd2d034a0d884d6bc8b5fa5fc7df00cf667dfc82c9e98e9b57fbe07fed391729034c467b46d3d16ed72c8ded069a117ca2d28e71ef4c37f1b1939590af19433ff2a784ff38134aacc59719673c53ed047d79e0c36f3acc03f64458826107853ff8d02de9362ffada79feabc597d019de6afb6577dbbefcaeaee801a49b1718151690b43a4d70b2fcb73ece3e3ea5fe2a9616cc6a9a494b8c6c7121497688652eb854e7bae19af3e5ae1a8df75138ce18621d11f15054ac8971f6ac084bd90e3909b46db321916c9e3c8950f3cc8e0a10dd255bd6b359c73dfde7de6e01538d03609253968177af14d327337bc1bc4e866c75ab5b69125f9e9a758f061418110a615b97f733f899382e2a14fa0ff0d1bda3b7449e1f54362697aef064fc9dd50692ad2b9ec286a1b7c1f845b09f4b1ccd46142f9e272d018eabf76c23c26b1831762a8a2c9152ef0cc843ebba20bf979e851e76141f41e817cca0ac60c1baf239a6c920a244b0be031a3b65d76eebae15bd9d49c53dd3e4303ca379f26840901f1da886d330d8ab4408a25eaa280ddb545b968b4aa59ca6f9cecbf82984264a3232b97498be0729f3d97dbd64823393b42794ec1676244212af382c37d53d66019a5db34c3d8ac156cac852ecb1c57f0f6057815460eb9f666c368acca6dfee0d2b7bdacd2148d74cea3bbac8b528f00809f403b1964b9d199d2db93363a53732efd97c66f28cb51442dbfb1c912dce078944e02889f9395ff2655e2d7118a11848e3ca64efb5eca119240b537b5d4548a744d96962e9b2a99af73bc4d753df16fe75a08005da7773b7bef68a44f56951a3a93995c5d0516a8b2c39abd2057b3d86ba7fe511dc33292596fcc4d3c67df88812e26d3628527752a81c8ce6eba711ae7ea8748229caa2580e18937f45cbfb0041f0a9254eade41c95ef4f390db6174e6b0ba89af344c215393b627348ebf00901e343c1599c832bd2cfb751750808719f18119ab3fee6baf5fd4b8bf7c92a271ff24576984610abaf1b2297c18a2784f57d7aad26f52c0d60fa6cd2484492a1524dd14924eebf53348ee2dca0643ef47381541b91d1843817067a94bb7c79e4e08c4d81c7266afefeda4a0db21b0db629cedf13b4f6ba11e6480116a7c9780009ee0e1e2bd543dbb68bea356a6010a1e3ded4229c4d8035420833fc83337dce4bc483eec76f479f33be2d6a8fb013813952eb66bc247f37f9def7e9bea0f277f380079aeb48c192b9c2c24f909435586bcb994ae6f75eea111c8842b855f90df21e956f839aef89bb1b96899e343cc66814ac08eed4defe5e2a62c54cf83a27f0499224e02bac652aebe6529143372c483edcdbfbdd5a02106fd3651875f23724d2154c0138ace745d038d6cf6b233ae2bbb8313c8ab59dc896844da5a6610845d9147e57518ada61b7debcd66111d609587632cccb936751d099639735f09f77b2b9aa1177e95ac0f8070bcbeb01bbfb3e7a638811d919016ed628865e2405aa17f5129ea43a836a76ec7cbd943e20b1e388d47db2df3394785e27dd7efbe350ccec6057af7483303a6a588d4005c05ff7f3c90dc5b9a0f95c3c541ea40615f6b0ed6cd3787e608e7612b52199062991a63511b3055dfab25b18ab55948d9d529aff91a0d8aa7705afc187e32d1107c07623a309390f32c62e1abb830bb090a7dc4c6817b554720fce354563662d78cc2bcd0483149b304ab86502f0ad36ba2c262fa60b1b84be2499e56fd72129c0fdf1961214df8dd6c9b831109fadc7464df090aac96d221ab387b6eee3080ea0d94e195ca795b5d80c42946c0c353e39075bb1aa4f3fac36a77445cd57b11b3c7ef0e89aca911be35fbb72e1e2fff84b7ece66bb8b3dea52b0aff473475d298f730e79ff77e955856817514908baaec1b53f39f9d5dcbb1cef29536a483a1845d8538e5ec43f634914bae4f2319579adcf3f0fbb151c80b54f02be54be612f9417f916579863dc16da5af07fab05db2094c77612b445b7b9784198abbfc8387486449969118d3a83142022017d69b939d30fd6b1c5ba360c0e37f264b5d604c7ebdaf327874b22d4092dcf35aad93f0f0b3037f90000d4a1892dab827d3c2f1ddede948d330706ef17bf442f8228c168401541ea89a837f81cfffde8480e1b6014f2f5d3132ee740e04622600ad93dba41e39267f17554b29102390f8196094de19e8d966d3c03a9e3348494b54045629224612734a1304ed76d17a465c305675bf99b7aff1950dd2012e6cbe8d15a9e35a7c16081bf647e2766c7b8e9d12ef8a2c94fd7531f6d1a7ac7ce6bac028917a2a71c8eded4f3ce3be8c7d98422094e88414338fc25839a582b5965c71179ce3ddf7a645bb163f3265f4bc4ebe91ff25d86520696d237741ba73084070e23494d6a890e1ae748091a6936a3137d16dd14571f86ee118bcb17c8ecd2ed20c25cbab79a78c53fba1d5db4fdb15e6a124bdb2d41fef080e7318f78c67451beff04b58a182f592fec3feab6011be4663dca8b62ad2a8e40c716b3c8a170e1574517c44e146109d223529b0de480aa168b5a4b0522cea03dd26a684b08c58aa2e1fe41f44ba8837737a74a4681b0508d9604aa7484c5ec68ccb369c056702b03f75722033597ce5b55e5a88f14e3ee23b1c96f5d38604128cf2e901a745996f4a29d412e5ce1b4ee82cf2f248aa80f8c8f2eb7b6951f1b0910857a17edc319bc76b1a59fe05f9394a5b16a91ef15afba9ee2e115c9c9afc51a8d7c4a61cb335724855007f564fec341342d53717023118917304c2dfcf0bd79e456c94718b8388767564d6055aad1d505f36506fd6e8cb1a10f6fe8a67e772030dab78b43ec15fae0322fe991944be86cd0bd78ba00110474dec84dd9597a602c4a6c97a27f13159ff1e32fa63e28f63278b09c9d051cabf8bb49f2780d4c33ca061ff16c6288ff29d46a22e31c719dfaf6087590ea8197c066f9057e0c24b40f5eb205b730985daaf7e7d85fecd7449677c0ec1abd7111d795de6de4a6ab30cf266987dd55569f2a705ae16f51de738fcc4cc218f2cb9dab8af509ba26ed93ddc7a73028d9187527fa6d554b491faade05852eb50847affb81b58d2501989d5b4a9de4237b33bea154c4d016b08f3e1c39ee61382fb500be2123ccfcd30e37c88979a19d636c9dcf5fcec822e5558cb2939e40e1af039aa942b503c9fce95f8ff51a2977f58e4d5032f9099fb4783b7764dd1299a1a89f21c1f5e38038f219e9a4e9d6c6edaef70132ee1f2a580227b256c9139d339183e278277a9dd260bf7d8a9853c4185f9b6509ab9b67a3563ed0b9afca6f2db28258025d8c2036cc4cd9fb4f7007dca96597c26476def29191cb0c77c44df80994ad3c79833820ce2f1108d17a083fa857397d2839c37899ce2e634578b3eee0135f66cfe1aeb7bcb8732fedba16093a6e666321236f56e285058d00c1bba2db3263844e4b0ea013e7a8d370ecae9ac7a9620eaaa244b25be1eb6e71368f550736d3ca13747edfb6254c9a373aadcc30a148907d39e81bdda8a53e8644b39a1625de851fc5d759686c63c76bb162b244415175eb0ccaf3a63d4ad910369e30313bdeb175ace63ad33e400e642dbdcb472a336d82d2f818e6888e5aef472d03fd298a5d14b8da432b72e6c5f5694887a7e4e165bc41055379bd204298d65796d5351a9121f949904bd6c87510649127ef5797d432c6c759939c99026d57076db225d7d0df01ab9b9e3e1221e7e323ff752eb7988cd2b2bbfc8ae7fdd23c3f7cfd2ae2679014e986e3c01b6c66a67fbf69483ad3e89c1f2dd0867f411151e048d81212c0b5e2fbba9d317f283f97c9576180121f41856355b906a22c69869bc27729db5974c5b1257b35d186dcb4e9f7bc51dc331c0ff9baed5955e5052a6ac7743b" + let t = readZebraTransaction h + it "Sapling component is read" $ do + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let sb = zt_sBundle t' + fromRawSBundle sb `shouldNotBe` Nothing + it "Orchard component is read" $ do + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let ob = zt_oBundle t' + fromRawOBundle ob `shouldNotBe` Nothing + it "Orchard CMX is present" $ do + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let ob = zt_oBundle t' + case fromRawOBundle ob of + Nothing -> assertFailure "Couldn't open the orchard bundle" + Just s -> toBytes (cmx (head (obActions s))) `shouldNotBe` "" + describe "Raw transaction with Transparent inputs" $ do + let h = + hexString + "0400008085202f89014b6ded3d2ee43e54135c04f9a305b473d7d741bbe6f383aa636e03e948705e0d01000000fdfd0000483045022100b34ca0fb34959230b95a48e9de95521dc4c013bdc0e6bd2a77a67b373fadea42022030a1b13f16993ab212d9f674b7b8dd3c6723137c183dbd41a9bbfcf66d7811c40147304402200f0bd12663a52ccb5e6211ec633932bbc7f0151ab51194038534abf88f689025022051fe62591dfa11a96095b4aeee2114c77a0d773242939f7ac2fa8e5262ff0865014c6952210231ee0c161e846bc133ad41d295f560d7e24829415afc78ead24d4b10198bb7342103b0b11f2dd10611d06cd6232233678dce736ce2b2141c62bdf0b4fe6f79d736da21029cbeda22b05352f66a338561604cbe11e8332f836385f16abf5088045c39d4dd53aefdffffff02b63500000000000017a91499a8b5b1f3d3a1a12e92dd4f3d94bee8fc34c527873362fa020000000017a914d75fe4791fcd50f2874f1b7457fca5833293b3a38700000000000000000000000000000000000000" + let t = readZebraTransaction h + it "TxIn parsing" $ do + case t of + Nothing -> assertFailure "Couldn't decode" + Just t' -> do + let tb = zt_tBundle t' + show tb `shouldNotBe` "" + describe "Sapling commitment trees" $ do + let tree = + SaplingCommitmentTree $ + hexString + "01916df07670600aefa3b412a120d6b8d9a3d2ff9466a7ec770cd52d34ddb42313001000013c60b031a5e44650059fcc7101a3f551b807ab8b3a116a5a9c7fa0f3babbe735017c0d36686294ff19d59e58b6a2ac6a7ad607a804bc202c84012d8e94f233970c0128dbde5180af5304d8577376d78297130b615a327974c10881f6d876869aea05011b80b4ca60f74dfe33c78b062df73c84b8b44dab4604db16f5b61eea40134373010c96e4cc8a6a80fba0d41e4eb3070d80769104dc33fb61133b1304c15bf9e23e000107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" + let cmu1 = + hexString + "45e47c5df6f5c5e48aa3526e977b2d1b57eda57214e36f06128008cb17b0125f" + let cmu2 = + hexString + "426ef44b3b22e0eeda7e4d2b62bac63966572b224e50f97ee56c9490cde4910d" + let tree2 = + hexString + "01a47029e9b43722c57143a5d07681bff3e2315c9a28ad49d69e7c1f2f6e81ac160010000000000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" + it "Commitment tree is updated correctly" $ do + let t1 = updateSaplingCommitmentTree tree cmu1 + t1 `shouldNotBe` Nothing + it "Incremental witness is generated" $ do + let t1 = updateSaplingCommitmentTree tree cmu1 + case t1 of + Nothing -> assertFailure "Failed to append node to tree" + Just t -> getSaplingWitness t `shouldNotBe` Nothing + it "Position of note is obtained" $ do + let p = + getSaplingNotePosition <$> + (getSaplingWitness =<< updateSaplingCommitmentTree tree cmu1) + p `shouldBe` Just 129405 + describe "Orchard commitment trees" $ do + let tree = + OrchardCommitmentTree $ + hexString + "01d5c803729654208f33d33dc68ef539ea098abc5aec215ae67c4d8aa10a14e11d01bb83047c72eb4f71813d00dee37082169546df2d7097bf7fd187ef6a93063b281f015e710ed46b53b48b12733652e150f9dcbc7e7b571cf64f294cf903864c78882f01cac32bc901f501f714a028f7ebe44c1dd8b42661be1c96730066a6fa6ede653600000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" + let cmx = + hexString + "d5c803729654208f33d33dc68ef539ea098abc5aec215ae67c4d8aa10a14e11d" + it "Commitment tree is updated correctly" $ do + let t1 = updateOrchardCommitmentTree tree cmx + t1 `shouldNotBe` Nothing + it "Incremental witness is generated" $ do + let t1 = updateOrchardCommitmentTree tree cmx + case t1 of + Nothing -> assertFailure "Failed to append node to tree" + Just t -> getOrchardWitness t `shouldNotBe` Nothing + it "Position of note is obtained" $ do + let p = + getOrchardNotePosition <$> + (getOrchardWitness =<< updateOrchardCommitmentTree tree cmx) + p `shouldBe` Just 39432 + describe "Extract Sapling Address - UA Valid" $ do + let sr = + getSaplingFromUA + "u14a5c4ufn9feqvxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" + it "Extract sapling address" $ do + case sr of + Nothing -> + assertFailure "UA invalid or does not contain a Sapling receiver" + Just t -> do + print t + t `shouldBe` + "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" + describe "Extract Sapling Address - UA Invalid" $ do + let sr = + getSaplingFromUA + "u14a5c4ufn9qfevxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" + it "Try to extract sapling address from invalid UA" $ do + sr `shouldBe` Nothing + describe "Decode a Transparent Address" $ do + let ta = decodeTransparentAddress "t1dMjvesbzdG41xgKaGU3HgwYJwSgbCK54e" + it "Try to decode a valid Transparent Address" $ do + print ta + ta `shouldNotBe` Nothing + it "Encode and decode should be the same" $ do + let ua = + "u17n7hpwaujyq7ux8f9jpyymtnk5urw7pyrf60smp5mawy7jgz325hfvz3jn3zsfya8yxryf9q7ldk8nu8df0emra5wne28zq9d9nm2pu4x6qwjha565av9aze0xgujgslz74ufkj0c0cylqwjyrh9msjfh7jzal6d3qzrnhkkqy3pqm8j63y07jxj7txqeac982778rmt64f32aum94x" + case isValidUnifiedAddress ua of + Nothing -> assertFailure "Bad UA" + Just u -> do + let tAdd = + maybe + "No transparent" + (encodeTransparentReceiver (ua_net u)) $ + t_rec u + (ta_receiver <$> decodeTransparentAddress (E.encodeUtf8 tAdd)) `shouldBe` + t_rec u + describe "Decode a Sapling Address (MainNet)" $ do + let sa = + decodeSaplingAddress + "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" + it "Try to decode a valid MainNet Sapling Address" $ do + case sa of + Nothing -> assertFailure "Failed to decode MainNet SaplingAddress" + Just s -- Sapling address decoded succesfully + -> do + let sh = encodeSaplingAddress (net_type s) (sa_receiver s) + case sh of + Nothing -> + assertFailure "Failed to encode MainNet SaplingAddress" + Just zsh -> do + print zsh + zsh `shouldBe` + "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" + describe "Decode a Sapling Address (TestNet)" $ do + let sa = + decodeSaplingAddress + "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5" + it "Try to decode a valid TestNet Sapling Address " $ do + case sa of + Nothing -> assertFailure "Failed to decode TestNet SaplingAddress" + Just s -- Sapling address decoded succesfully + -> do + let sh = encodeSaplingAddress (net_type s) (sa_receiver s) + case sh of + Nothing -> + assertFailure "Failed to encode TestNet SaplingAddress" + Just zsh -> do + print zsh + zsh `shouldBe` + "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5" + describe "Decode Sapling Output with spending key" $ do + let p = + Phrase + "cloth swing left trap random tornado have great onion element until make shy dad success art tuition canvas thunder apple decade elegant struggle invest" + let seed = getWalletSeed p + let sK = genSaplingSpendingKey (fromJust seed) MainNetCoin 0 + let txHex = + hexString + "050000800a27a726b4d0d6c20000000005bd2500000001efdca98904a8b34a0fcfefc47240249a18a530289f2ff6a15bb9885abf6e541aa6c9a851b63df5cbd7473b5b5bc5efbc4a3130c15ce18b92b87b67de2cc72846babecb9030390f0ab02d54960636633695c0c1631f9e87625b92976b12492fcb02d260bd68e7c21cc8ffaad1c3ea5182dee524a09f7b5babcd7d6d85270d6d0d3d48aa620513b632cc17eca91e237b38b1212739e8df6ddcb85c2314cd76c52e26791e2d99fd7ea468b2fb4df3e9bce602daa6a6ad8a57fcee27ce5eee3da998902b5ecf4651adaed9bfd17d0d6de3f4d8c1d391f91c571a341f105e3afd913c25012c623f274406cccda52535a0b4ab883b763d1c7405e6aac941575a965ef0b9b0114588d9d7a2be82a3eb11c662c1fc85a31e8f07fa2c2a9dcb295d5530e893b2aa43c04a508eb182257c791aa4f75d6f5d77a8fbace89666ee80b572015ae4c33b8510e88a8a338da305237d1fc5d267a4ea6914d6f9cacd1a5e158a1acd2722284ff71fd870d92143872def753e2ecde9c04c3b80b513609d47a523243508548c8873426c52eaab76b9cf2b90e63fe9c5485eddfcacac65ca811c634df22a01610b22770ba3d294f566de7e0764641985b62ce2cd71735999111bb323b14d87e482201798672881da255062dc68f912343b911154f7c96442a01be25219f0315bc78c1c3693f80e9a9cd062eee2de8a37cf073f505d936f1be9ef9a78f95e8f70ef20e0e23c2c59c37cd94cfdc5c0d0d14849aea4aa6b6a7b33a0fc57a2a91a5950ba92fec1465db65b02e36fff82d2cacb4cc9038d1a8f30559a6211c52507f2dba723d54e08e82b127cd63fad97e44292e90aa87a1e798504d0f67f242561eecc3ab40911472d75457afecaf9a026e9e0ebcd07c7e78c0bd2963674fa51d6d2c157791c5d60574047a0a4ef496f1465117ed6b9408a107cd77daa0501bb49f548b97d6eb45ace8c0620b0361031f6031af5ff53015c6755fd3c76e9e45c8694d0f38722aa9bd31987fb503a0fef5f3f97033a19212e9d549b7f7de12c2dd3f1b5db353cb40c486a0ab00ecd80d1bc6ef96ba9855bbae57c6bb9965e1ac01d4d2cccd150a44a1e511bf8f9956e7472339306f7c14d90e61e4b01d6e3b98a6b5008ce102b4646d31fc11af90d4834dd8e0005fdc8ca2310fd6cdd4180def97074fedfe645e3c9f399ca9bd282b7743d943c95e4c76a7deae192be69b80f71f7f3dd9ff5199f7c82f743030ffdd69fe88463ef6efd62d329662ab48325d4d67bed52bcdf16d1991adf5fdeadb02e81bf627d5c58d013277f1ab04fec34985917a7495d4056d47986ebcdd18c08cd9f6ba3a1487f646c1a8d99714db89d3d7708cb3455d72943a609f3db3097b7ab295316c2bcf88c73ca159e995acddcc149fd186bd82cd34f02af8e4b68ad39db8621ae92db4d41f53d2567948a423ed2a7fe38b1ff77c0b1cf46d90077bd062b3c2edb9caab921b01c546866a376d29feeaee6ff8e7583c8b4a4f631c4cb339172e5b217ee75793f7608ea6d600c680f86ce63a89a5963ec5e20ade9464f3142c0100c8ce4426b34f887b68c9658cad2dbf8a1c5da4eb7b576bab32b5af129437cd34e3c8b8d17bece85cb661ed20224539ed3eb1d61eec727deb79585f8630a807acd44978dd6ec190312d1c8dacc4cf02783136d730ab0216f7105a9f11d090c5a6e69eb726ba5e785de2f99dc7a8051250544cc661b56913715fdd31cbb5fff3f0b5000a325db631ff60ad241051511dd32e322900df8f5707a59bbe8c1ac85f4be97cfe6700a9193458fa23a70b6639f0bdfd879493520d8e4e593002f5cd9d4bb60aad67610cfb03142a5fc9cefe08cf76a23706288779cefa08ba26e33b9ca58109e0e8abf150dceec584ceabe74e3c838f86a4c3920a37d8dac148317a3c3e132ecaa551ddb7085a8702dc970b5dee5264ee1bf8bc89e92912ff1f3b4e5d10627f3e55b7f6216c0249cb2aabd1267762cad1a88b6c81bc42ac47b6f53e5598ea1dce228165f0e748c0af90eaa421813dc38a521daf83e688c298ddc0a1adac1418f04a7739a6d4f7fdfae64320d8d8107f5bacb87596b7f777a85002d9dcf66c930bfa427a6508198d8cf625279b3c6b438e098f04453d9607bb83271b2ce1f11bcb4c59f0edc4135cf4d5bade66c422c654f9dff4fa720589975d05ae7b10b1d6d2ed72c26534be10a229605f4e946c21abdfd629b1ec18e5819a1be5c89fbf446c65b39fdb32ad5cd9079beee1b73bb3b3a37c3cc10270000000000007a2aed01922521f492d80b45e7d267745f3423c1760982418b9f69390660fb12a6ec696bed9fb392633c496f576a910a0c4d5e37ad76839d951d4895f76948c72fcd580590e014c9b0b3a091ec19ab40a5d59131024055636725f98c29303bab5718006e7154379227c58d18b04938b43c8fa4fe793db395754c3270d71f90dd0fd907c39c848446f99de6efefebe414f981c29f2eb448d1d15ff314ee0c763b66dbf6917e44e18310e0fa1340069512867157552c696a3f89c909668e0698e83825b1b0d73a38a2d99531a8c053a48acdcf9fa9bafcd8ce1f3c3b784bbe23775be45176a5b0a42978008ba5c60020714ba3c0174c9a068b45081267609f7ca31e32d7367cdb0ffb88c7cb762e55b48e4edc639da5ec739a1a5ae0d7b9a16000adb66352c377e3a316a18c66f3c994c0e62b81b3ebc485e4e3438426f1a32a3a875483def774eb45e13df7a14c9ac4f9ac22bcebf3515a6dafe9aad745451708180fadcce8870302e65d90954fd9cae32e3374c8734385c6b4bc3581d997e0f20e6db537a39c8f9b5e54e0a2bcc41e260387e4faabc2f09e7613e26c472d295ad56ee97affb96021f6e882eed25481628f5fab8bb4705cb7df163eb73ab45323dfaebf8e2108fb8c444833d640203455338459c1a68a0a8314f476ab6b230547ade1d2f2df907bb9fb0d2b6b0361e4d8a20118d8328274c71f84ee6aeb761c36fdae537fa747ab9a251fae846197d356b90913b00440e2c034c7cce498a01c5495dbda102d9151fb8f84bc55728a72d2fb5edf660d7df5a155212f6c10e12649028b172f8e4ad32e9f2898cf8e352c7898a2bdb30ff22e903371ec4fc47c49f1b56f0bae3975d80063f251203a5e15afa9af7bfd278e7af0cb5fc3d522639e8f4e3db7d8d5c483dfdc4207c36f0fa72df98f8970abeb8d872cd04a4113e6863db9dd9c72ce08ac8bb01da342fd43ac27a02a4b2db787efe8c98fc55a7730e43f98a85a13942a95545f630191fe4e879b515999c0a63f9eb318d6083de8bed00600" + let txHex2 = + hexString + "0400008085202f8900000000000045b52a00e80300000000000001638ca17ebf9c8fa40eb74be6cc0f702c8a009695b0ae1debc87f4623baa2fdb3f7d7639e3fc8c696cf5ee19d3efccf50e38546de089c4d95a4a3a4a5dd53ef4465bf1d0f111e8a87f1f499ba1d7acaa523f344046ed86c1a42d412fdfb6803cf8e155df11d1beec2d11aecc2cda622b4028db0691fd61d74969a96021bec1f45855f06c8e2c1e02aed41c4549f2e4227572f873847542e1e29077269460e2885cc0a1775378a20e7b1ef40d1e358020ea3c24991ea5bc150932d538b126ea8cdbe5fef9728efedf40912b75634c4ba8288a2ab6f09ad4b8081d395bb4dc49f221011891a8b4af3cc831bf5bdf013950d6a4c4442d314f62d18913a2e18f041cac9535ca8a973e38c3e9796f4eac80d4d88a39c701b17aaf452c19410fe45188d86d91fdee3799ba7bce793c3d0bfba9139c006eb4b279b48dbed04ff75564a068d4b022ac523d40f0a4c65d955873e7498df63bbd87b2518aaac084c8bd8d44d620a07d994ee09ae8e1792d47393b6b570ae1a9122dbf7bcc1f494120d20ca0c028e7443befa6ec5956c3b0c593c6065c7ee909f90de6188e38c24e57d8f0f4cc8fa430c51bb108db782764cff55de9c6b11bbecd2493d2e0fa9f646428feef858d322258e2bc7f6072ad014d8b249567b14276280bab8f2fe6b62ead86cda2dbf1264a89d07964845acb2ac5f9187f01f3980f0edfcee5f4082ad30f0c9512766063a55974c876cfb1b2115cbd46d9d49a3929ba42f4f9ba5ab15ddb106376a24786ba6631860f1e04df72a8a9dde04fb9128d453436f34eb560f52629deded87d46f0a6355da1070f14a0ea10fcae274bbc1b3f190858eb32ef47cf4fbca4d86fabe737ca1291f40979b6cda84bb43231598363ac9fc3d9af0d74a0a0797eb0dacc6dbd2687f3c8cd92468d39915edf9e45161ed10d78e17b629c5d5c73a09ff737fb370ce80a710bc97c391936223e926563f0ba27ec983c09477e23f8c288e35c57da1ce05ab740dd2d7afc800257c17276e4b9970f08288623c506275e0b0c8d5f60aab32974c24164d1b547e756218acf68849eea30480f529c8c05e62a65c9d070285b46c4a477ca5df220c696364523cc5d6db9ca852b1eb036846b537f4b566913660ad8b83d9c72daeb45274a55032cab9a3e0b3a1e386829dd10c6ba01dbc393843d67c553d74be7969b1879bf0a2ce05f108159a0df47f279b490d5a45ca1d90ff1a61650c6ae87611746d35f8410b39f2301a1be54711d58243cbc3afa29df2a7be5565cff1cad0bd34c370c345b2f38ac7020ac79f9d6c9eeaf2c7165acade98faf18a4e1b55a4e2a18a6dd4790057008ad40c51b0967b87a8d54cde958801ef98ae4a6c9f0e94f5c02a498a9173780b5b0790c248241125fe762c4697a320f1d08d782029c4fcb34fc8580c8c9885ba8339c5693170c157d1ebc663cdccd0a4bf6e77bdc3026252696ae0317ff7a3d9c190476e4605128e50eb42d3d5d86459d6fe8c22e5482822b61f0bcbfbc28374ed93ccbe97e3af400265f02ea525a8c4853dba3d667d7502e914f99ab5062f81fe1c699663694944c8a2fe4679a6866d1d3fc07acf31041881dad5faa08385f51c5880265fe6f6bbd7961cfb9c8ceb8d1f5e1a99f7bc606585136c18d2eb3b9222576614c7092ff47d32696b50faa67c0170a2b3212a32f7bd79daa69a84fe2ef3b62081d78418d0a5902dc65d9442f1824366d8906d514ff1db6bfcb39202257e9e523cea9c311c0d4c13efc6fe6bea7dbd0c1c9ad39594ebca64586701a2384d40e62198cf379ecf21ddd8522e98a447f1c0f79e03abd232da7c8b9c39260a21097eedff86744445c7785ba0251fea0572d5ec7f78134c75fd03fc3d62d283384bb097120b47b780b4a60b5c04d53e90c9ff2e781ac9b039384fbd48d1d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843e007aea2e07f40f139e24d0e497eac26727a2b9aad4f223887cd423b9fd6cf58353f0b011ea73b2a9843c25529df7b88f2357fba75353218012d1ba4b89b45d50564e9e7f16101c69c4232d2de683bd0960cc87a016dce7bb088720f8f6c092bdf2f2954e937168b780b2f8a7828d49f5b0d8809f25d74e6374065c4596c5a8ef2d2c4a18a0bbbb2ae9cb6d0f6332614270c4438ef07effd094df92b74cbb828305debaac629cb01a98e32d16e04d0b8c52c8a9a2c526481fd627d453e97bf33ea717775dc06abb09f0b95a435daf64e41d198ef6c686baffe891e9a1da8df943b12d1b5f192387b5b367db75593ace3399439621ec9d4195c188be12781cb165e559161381482bc513bb9a35d3746c30d88cc475642d63016c8a75347c1bb706bb3276218e6269e6e1a1c8912b077b5e82235ba837899c6c23bec2917457442f9f607d3d63b8c8ea5f065ca5e7a4ec037665a40003d9a0c95d97234ac9d50efa385cadfc79820a017abfd3f678ba5d7a8b890f5ca6c10ffbea407c6ac18638bde1806f739b7fd6ed49c665884f8cd4cdf77a9c02cad0ffa8971cdbe664ea7539521c87d9658808b112d1ea22b155208dfd693a48d5dd703279192ae5d9acab817c53193e17542db27d63764daccefb0be38b2bbdbd01345919e2f08811f99fb54a33033f6abc76c5ab4ebc9056f0e8d3005f80ffaf403ea744ff86a30fdc23f319972c4ccc81b3f6a23ad9fb00e480cd0b7d138e9db6e7cd25e424d29205782b06e4b0c5b932820ee2035fe657ec36489f60829d8c31dfe36accbc0fb78176508b6024b60909dba12747745d454c150f54945ef6a9a085e3a6b6539ace61f767e1e5fb1054355bf26622674d58d4b5037457138e50abfefe92b515c74c608fcd2a5af50eab0bc85d57ba3bda52fa00c9c15d44a24586b2cacc6be00209ae49202430645d4a5022703b1cace87829dc783d5092d97e5f003d16720844ba1bd157688a7697133f4bb4a33a7c91974937a1351d7af56d16d4a10bd196ddda700fcd8be517f8f9e39a17ba0eea235d98450a626be3a998ac31f35e8e082106a31fe94da11d02b73748db4aa519df6bbf25c1d62a2cf0b192c6a486bca2632fee9e4124ce2dba6f3366a14850f6a3b784d863119f52458ed774f8d63105b4f6a3d2e09cc74e3a02ec8386213087b4c849172ded6724a45c9c12744ec4a0f86a29b803b17187df5dd5f90e71d1f3f4578d4e1496e889200fa5344b9491cd5afeb2dcc03364b8a9380fa11a5c4ea09bb60d13aa217acc38fe3e83b5569bf75576dacb84cfc28cec75ee724fcb8b5d4fdb12948b1098ebc07" + let tree = + SaplingCommitmentTree $ + hexString + "01fef9aa4cfdc8c26eb2693907e96eccaacd61ed04ed7860f1d83260e6e383b936001001bdd6575663c970df26d8eb84b0be6411f65337912c90b1a0a8ba2b9303326d350000000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" + let sk = + SaplingSpendingKey + "\ETX\189\201\190\128\NUL\NUL\NUL\128$q\CAN+P\196\229VM\129:+\SI\171W\248)\216\196\215\141\229\239\141*Y\175}\253\232s\156(K\179\GS\139\232\246\ETB\181`l\226i\156\211#\tdNT\248\138i\220\136\209>U\150cl\f+\201i\132\250\220\208\137\178\196\238\141\\,\208\194*\245\145b\FS\244\164\230\194C`\ACK\140\220\194\ACK\190\224u\FS\167CK\181\DC2\r\US\152\237\238\137\209S\238\240D'\248 \255\164F\254)n\a\231r\128!\177\168\210\141\234\STX5\189\b=\191\ETX:\USgvB\155 \162\234\200\DC1$z\252\177C+\252" + let so1 = + ShieldedOutput + (hexString + "8e7443befa6ec5956c3b0c593c6065c7ee909f90de6188e38c24e57d8f0f4cc8") + (hexString + "fa430c51bb108db782764cff55de9c6b11bbecd2493d2e0fa9f646428feef858") + (hexString + "d322258e2bc7f6072ad014d8b249567b14276280bab8f2fe6b62ead86cda2dbf") + (hexString + "1264a89d07964845acb2ac5f9187f01f3980f0edfcee5f4082ad30f0c9512766063a55974c876cfb1b2115cbd46d9d49a3929ba42f4f9ba5ab15ddb106376a24786ba6631860f1e04df72a8a9dde04fb9128d453436f34eb560f52629deded87d46f0a6355da1070f14a0ea10fcae274bbc1b3f190858eb32ef47cf4fbca4d86fabe737ca1291f40979b6cda84bb43231598363ac9fc3d9af0d74a0a0797eb0dacc6dbd2687f3c8cd92468d39915edf9e45161ed10d78e17b629c5d5c73a09ff737fb370ce80a710bc97c391936223e926563f0ba27ec983c09477e23f8c288e35c57da1ce05ab740dd2d7afc800257c17276e4b9970f08288623c506275e0b0c8d5f60aab32974c24164d1b547e756218acf68849eea30480f529c8c05e62a65c9d070285b46c4a477ca5df220c696364523cc5d6db9ca852b1eb036846b537f4b566913660ad8b83d9c72daeb45274a55032cab9a3e0b3a1e386829dd10c6ba01dbc393843d67c553d74be7969b1879bf0a2ce05f108159a0df47f279b490d5a45ca1d90ff1a61650c6ae87611746d35f8410b39f2301a1be54711d58243cbc3afa29df2a7be5565cff1cad0bd34c370c345b2f38ac7020ac79f9d6c9eeaf2c7165acade98faf18a4e1b55a4e2a18a6dd4790057008ad40c51b0967b87a8d54cde958801ef98ae4a6c9f0e94f5c02a498a9173780b5b0790c248241125fe762c4697a320f1d08d782029c4fcb34fc8580c8c9885ba8339c5693170c157d1ebc663cdccd0a4bf6e77bdc3026252696ae0317ff7a3d9c190476e4605128e50eb42d3d5d8") + (hexString + "6459d6fe8c22e5482822b61f0bcbfbc28374ed93ccbe97e3af400265f02ea525a8c4853dba3d667d7502e914f99ab5062f81fe1c699663694944c8a2fe4679a6866d1d3fc07acf31041881dad5faa083") + (hexString + "85f51c5880265fe6f6bbd7961cfb9c8ceb8d1f5e1a99f7bc606585136c18d2eb3b9222576614c7092ff47d32696b50faa67c0170a2b3212a32f7bd79daa69a84fe2ef3b62081d78418d0a5902dc65d9442f1824366d8906d514ff1db6bfcb39202257e9e523cea9c311c0d4c13efc6fe6bea7dbd0c1c9ad39594ebca64586701a2384d40e62198cf379ecf21ddd8522e98a447f1c0f79e03abd232da7c8b9c39260a21097eedff86744445c7785ba0251fea0572d5ec7f78134c75fd03fc3d62") + let so2 = + ShieldedOutput + (hexString + "d283384bb097120b47b780b4a60b5c04d53e90c9ff2e781ac9b039384fbd48d1") + (hexString + "d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843") + (hexString + "e007aea2e07f40f139e24d0e497eac26727a2b9aad4f223887cd423b9fd6cf58") + (hexString + "353f0b011ea73b2a9843c25529df7b88f2357fba75353218012d1ba4b89b45d50564e9e7f16101c69c4232d2de683bd0960cc87a016dce7bb088720f8f6c092bdf2f2954e937168b780b2f8a7828d49f5b0d8809f25d74e6374065c4596c5a8ef2d2c4a18a0bbbb2ae9cb6d0f6332614270c4438ef07effd094df92b74cbb828305debaac629cb01a98e32d16e04d0b8c52c8a9a2c526481fd627d453e97bf33ea717775dc06abb09f0b95a435daf64e41d198ef6c686baffe891e9a1da8df943b12d1b5f192387b5b367db75593ace3399439621ec9d4195c188be12781cb165e559161381482bc513bb9a35d3746c30d88cc475642d63016c8a75347c1bb706bb3276218e6269e6e1a1c8912b077b5e82235ba837899c6c23bec2917457442f9f607d3d63b8c8ea5f065ca5e7a4ec037665a40003d9a0c95d97234ac9d50efa385cadfc79820a017abfd3f678ba5d7a8b890f5ca6c10ffbea407c6ac18638bde1806f739b7fd6ed49c665884f8cd4cdf77a9c02cad0ffa8971cdbe664ea7539521c87d9658808b112d1ea22b155208dfd693a48d5dd703279192ae5d9acab817c53193e17542db27d63764daccefb0be38b2bbdbd01345919e2f08811f99fb54a33033f6abc76c5ab4ebc9056f0e8d3005f80ffaf403ea744ff86a30fdc23f319972c4ccc81b3f6a23ad9fb00e480cd0b7d138e9db6e7cd25e424d29205782b06e4b0c5b932820ee2035fe657ec36489f60829d8c31dfe36accbc0fb78176508b6024b60909dba12747745d454c150f54945ef6a9a085e3a6b6539ace61f767e1e5fb1") + (hexString + "054355bf26622674d58d4b5037457138e50abfefe92b515c74c608fcd2a5af50eab0bc85d57ba3bda52fa00c9c15d44a24586b2cacc6be00209ae49202430645d4a5022703b1cace87829dc783d5092d") + (hexString + "97e5f003d16720844ba1bd157688a7697133f4bb4a33a7c91974937a1351d7af56d16d4a10bd196ddda700fcd8be517f8f9e39a17ba0eea235d98450a626be3a998ac31f35e8e082106a31fe94da11d02b73748db4aa519df6bbf25c1d62a2cf0b192c6a486bca2632fee9e4124ce2dba6f3366a14850f6a3b784d863119f52458ed774f8d63105b4f6a3d2e09cc74e3a02ec8386213087b4c849172ded6724a45c9c12744ec4a0f86a29b803b17187df5dd5f90e71d1f3f4578d4e1496e8892") + it "Sap output 1" $ do + let pos = + getSaplingNotePosition <$> + (getSaplingWitness =<< + updateSaplingCommitmentTree + tree + (fromText + "fa430c51bb108db782764cff55de9c6b11bbecd2493d2e0fa9f646428feef858")) + case pos of + Nothing -> assertFailure "couldn't get note position" + Just p -> do + let dn = decodeSaplingOutputEsk sk so1 TestNet External p + dn `shouldBe` Nothing + it "Sap output 2" $ do + case readZebraTransaction txHex2 of + Nothing -> assertFailure "Failed to read Tx" + Just tx -> do + let sBundle = fromRawSBundle $ zt_sBundle tx + case sBundle of + Nothing -> assertFailure "Failed to get sapling bundle" + Just sB -> do + let sOuts = sbOutputs sB + let pos = + getSaplingNotePosition <$> + (getSaplingWitness =<< + updateSaplingCommitmentTree + tree + (fromText + "d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843")) + case pos of + Nothing -> assertFailure "couldn't get note position" + Just p -> do + let dn = + decodeSaplingOutputEsk + sk + (head . tail $ sOuts) + TestNet + External + p + dn `shouldBe` Nothing + it "Decode Sapling Output from Zingo" $ do + case readZebraTransaction txHex of + Nothing -> assertFailure "Failed to read Tx" + Just tx -> do + case sK of + Nothing -> assertFailure "Failed to generate spending key" + Just sK' -> do + let sBundle = fromRawSBundle $ zt_sBundle tx + case sBundle of + Nothing -> assertFailure "Failed to get sapling bundle" + Just sB -> do + let sOuts = sbOutputs sB + let pos = + getSaplingNotePosition <$> + (getSaplingWitness =<< + updateSaplingCommitmentTree + tree + (fromText + "d163c69029e8cb05d874b798c7973b3b1b1b0e04f984a252b73c848698320843")) + case pos of + Nothing -> assertFailure "couldn't get note position" + Just p -> do + let dn = + decodeSaplingOutputEsk + sK' + (head . tail $ sOuts) + MainNet + External + p + dn `shouldNotBe` Nothing + describe "Generate an ExchangeAddress (MainNet) from transparent address" $ do + let ta = decodeTransparentAddress "t1dMjvesbzdG41xgKaGU3HgwYJwSgbCK54e" + it "Try to generate valid ExchangeAddress from Transparent Address" $ do + case ta of + Nothing -> assertFailure "Failed to decode transparent address" + Just t -> do + case (tr_type (ta_receiver t)) of + P2SH -> + assertFailure + "P2SH not supported for ExchengeAddress generation" + P2PKH -> do + let exch = + encodeExchangeAddress (ta_network t) (ta_receiver t) + case exch of + Nothing -> assertFailure "Failed to encode Exchange address" + Just addr -> do + let eadr = decodeExchangeAddress addr + eadr `shouldNotBe` Nothing + describe "Witness updates" $ do + it "Sapling" $ do + let wit = + SaplingWitness $ + hexString + "01bd8a3f3cfc964332a2ada8c09a0da9dfc24174befb938abb086b9be5ca049e49013607f5e51826c8e5f660571ddfae14cd6fb1dc026bcd6855459b4e9339b20521100000019f0d7efb00169bb2202152d3266059d208ab17d14642c3339f9075e997160657000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39038cd7f6e2238d16ef49420963348dd4e4c7d23d5e5dac69507fba8937f63eb626f6856115bea2fa8db3a65a0ab294db41c51435d3b7ea27c7b2835aca28e82a2c1d9634efe07449a47c251518ac6f92c49f3a1ef119948f6a824d1e7ff7d0443e0101e57ec972a9b9383dc9cb228980d2d7752bb2abebc4a604ca48c5457039d2e05b000301392bed8592185dde5ab7fc81aed75e98fcf041f1a3fda55ad0b0b139ba9380130001808304b4d7c4fc407f5ce28247a7119013aeaaf1481902419c42bc8b21575c15" + let cmus = + [ hexString + "958ccdc752f2f593f6c1c8e2d7201348cd896e54c6d3c92200bdbe8b859eac44" + , hexString + "e49992fdd071d90bf56242d1aa625bbe267a34e0debd4307818a686d05b45447" + , hexString + "0c4b26766d89bf6cdb4fd3b0317b4e9a2fb3850f6a24869f32fe7cb0fd512e18" + ] + updateSaplingWitness wit cmus `shouldBe` + SaplingWitness + (hexString + "01bd8a3f3cfc964332a2ada8c09a0da9dfc24174befb938abb086b9be5ca049e49013607f5e51826c8e5f660571ddfae14cd6fb1dc026bcd6855459b4e9339b20521100000019f0d7efb00169bb2202152d3266059d208ab17d14642c3339f9075e997160657000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39038cd7f6e2238d16ef49420963348dd4e4c7d23d5e5dac69507fba8937f63eb626f6856115bea2fa8db3a65a0ab294db41c51435d3b7ea27c7b2835aca28e82a2c1d9634efe07449a47c251518ac6f92c49f3a1ef119948f6a824d1e7ff7d0443e0101e49992fdd071d90bf56242d1aa625bbe267a34e0debd4307818a686d05b45447010c4b26766d89bf6cdb4fd3b0317b4e9a2fb3850f6a24869f32fe7cb0fd512e1803000121c06ee1f1584f79d50785797a694c742be2ded600367ab7d54f3ed49e3adf7201808304b4d7c4fc407f5ce28247a7119013aeaaf1481902419c42bc8b21575c15") + it "Orchard" $ do + let wit = + OrchardWitness $ + hexString + "016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e000200019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301" + let cmxs = + [ hexString + "712ba86615ff4447e8d7c7b59f3873f03c03a173438b8e4c8d416756ed4fae10" + , hexString + "c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e" + , hexString + "ac20b8170b008888c19fc6e16f5e30a5ef1653e5219d0cd0c9353c3aa8f79823" + ] + updateOrchardWitness wit cmxs `shouldBe` + OrchardWitness + (hexString + "016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e0002010cfb50d8c877eb39e9c07082a032dd99d34be7c19fa7f30e9fecf5f14736240f019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301") + +-- | Properties +prop_PhraseLength :: Property +prop_PhraseLength = + ioProperty $ do + p <- generateWalletSeedPhrase + return $ BS.length (getBytes p) >= 95 + +prop_SeedLength :: Property +prop_SeedLength = + ioProperty $ do + p <- generateWalletSeedPhrase + let s = getWalletSeed p + return $ maybe 0 (BS.length . getBytes) s === 64 + +prop_OrchardSpendingKey :: Seed -> CoinType -> NonNegative Int -> Property +prop_OrchardSpendingKey s c (NonNegative i) = + genOrchardSpendingKey s c i =/= Nothing + +prop_OrchardReceiver :: + Seed -> CoinType -> NonNegative Int -> NonNegative Int -> Scope -> Property +prop_OrchardReceiver s c (NonNegative i) (NonNegative j) scope = + genOrchardReceiver + j + scope + (fromMaybe (OrchardSpendingKey "") $ genOrchardSpendingKey s c i) =/= + Nothing + +prop_SaplingSpendingKey :: Seed -> CoinType -> NonNegative Int -> Property +prop_SaplingSpendingKey s c (NonNegative i) = + genSaplingSpendingKey s c i =/= Nothing + +prop_SaplingReceiver :: + Seed -> CoinType -> NonNegative Int -> NonNegative Int -> Property +prop_SaplingReceiver s c (NonNegative i) (NonNegative j) = + genSaplingPaymentAddress + i + (fromMaybe (SaplingSpendingKey "") $ genSaplingSpendingKey s c j) =/= + Nothing + +prop_SaplingRecRepeated :: Seed -> CoinType -> NonNegative Int -> Property +prop_SaplingRecRepeated s c (NonNegative i) = + genSaplingPaymentAddress + i + (fromMaybe (SaplingSpendingKey "") $ genSaplingSpendingKey s c 1) =/= + genSaplingPaymentAddress + (i + 1) + (fromMaybe (SaplingSpendingKey "") $ genSaplingSpendingKey s c 1) + +prop_OrchardRecRepeated :: + Seed -> CoinType -> NonNegative Int -> NonNegative Int -> Scope -> Property +prop_OrchardRecRepeated s c (NonNegative i) (NonNegative j) scope = + genOrchardReceiver + j + scope + (fromMaybe (OrchardSpendingKey "") $ genOrchardSpendingKey s c i) =/= + genOrchardReceiver + (j + 1) + scope + (fromMaybe (OrchardSpendingKey "") $ genOrchardSpendingKey s c i) + +prop_TransparentSpendingKey :: Seed -> CoinType -> NonNegative Int -> Property +prop_TransparentSpendingKey s coinType (NonNegative i) = + ioProperty $ do + k <- genTransparentPrvKey s coinType i + return $ xPrvChild k == fromIntegral i + +prop_TransparentReceiver :: + Seed -> CoinType -> Scope -> NonNegative Int -> NonNegative Int -> Property +prop_TransparentReceiver s coinType scope (NonNegative i) (NonNegative j) = + ioProperty $ do + k <- genTransparentPrvKey s coinType i + r <- genTransparentReceiver j scope k + return $ tr_type r == P2PKH + +-- | Generators +genOrcArgs :: Gen (CoinType, Int, Int) +genOrcArgs = do + i <- arbitrarySizedNatural + j <- arbitrarySizedNatural + c <- elements [MainNetCoin, TestNetCoin, RegTestNetCoin] + return (c, i, j) + +genSapArgs :: Gen Int +genSapArgs = choose (1, 50) + +getSeed :: IO Seed +getSeed = do + p <- generateWalletSeedPhrase + let s = getWalletSeed p + case s of + Nothing -> throwIO $ userError "Couldn't generate seed" + Just s' -> return s' + +-- | Arbitrary instances +instance Arbitrary CoinType where + arbitrary = elements [MainNetCoin, TestNetCoin, RegTestNetCoin] + +instance Arbitrary Scope where + arbitrary = elements [External, Internal] diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 30803ce..361d33e 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -1,42 +1,47 @@ -cabal-version: 1.12 +cabal-version: 3.0 --- This file has been generated from package.yaml by hpack version 0.35.2. +-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack name: zcash-haskell -version: 0.3.0 +version: 0.6.1.1 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain author: Rene Vergara maintainer: pitmutt@vergara.tech copyright: (c)2022-2024 Vergara Technologies LLC -license: LGPL-3 -build-type: Simple +license: MIT +license-file: LICENSE +build-type: Custom extra-source-files: + Setup.hs +extra-doc-files: README.md CHANGELOG.md - configure -source-repository head - type: git - location: https://git.vergara.tech/Vergara_Tech/zcash-haskell +custom-setup + setup-depends: + base >= 4.7 && < 5 + , Cabal >= 3.0.0.0 + , directory >= 1.3.6.0 + , filepath >= 1.3.0.2 + , envy + , regex-compat + , regex-base library exposed-modules: C.Zcash + ZcashHaskell.Keys ZcashHaskell.Orchard ZcashHaskell.Sapling ZcashHaskell.Transparent ZcashHaskell.Types ZcashHaskell.Utils - other-modules: - Paths_zcash_haskell hs-source-dirs: src - pkgconfig-depends: - rustzcash_wrapper-uninstalled build-depends: aeson , base >=4.7 && <5 @@ -46,16 +51,22 @@ library , cryptonite , foreign-rust , generics-sop + , hexstring >=0.12.1 , http-conduit + , http-client , memory + , binary , text + , haskoin-core + , secp256k1-haskell + , utf8-string + build-tool-depends: + c2hs:c2hs default-language: Haskell2010 test-suite zcash-haskell-test type: exitcode-stdio-1.0 main-is: Spec.hs - other-modules: - Paths_zcash_haskell hs-source-dirs: test ghc-options: -threaded -rtsopts -with-rtsopts=-N @@ -63,7 +74,16 @@ test-suite zcash-haskell-test aeson , base >=4.7 && <5 , bytestring + , haskoin-core + , hexstring >= 0.12.1 , hspec + , HUnit + , QuickCheck + , quickcheck-transformer , text , zcash-haskell + , binary + , cryptonite + , secp256k1-haskell + pkgconfig-depends: rustzcash_wrapper default-language: Haskell2010 diff --git a/zebrablock.json b/zebrablock.json new file mode 100644 index 0000000..5665f92 --- /dev/null +++ b/zebrablock.json @@ -0,0 +1,20 @@ +{ + "result": { + "hash": "0041ee9cb0e256a73c92bb72d830143c402ea350152f56f19f74d23cf51418fb", + "confirmations": 3583, + "height": 2767099, + "tx": [ + "d169ec3eda57dc750edfc1aa6b8ffb4ed2065780bfd5964de34b529503ec372f", + "987fcdb9bd37cbb5b205a8336de60d043f7028bebaa372828d81f3da296c7ef9" + ], + "trees": { + "sapling": { + "size": 129349 + }, + "orchard": { + "size": 39382 + } + } + }, + "id": 123 +} diff --git a/zebrahexblock.json b/zebrahexblock.json new file mode 100644 index 0000000..19d6b90 --- /dev/null +++ b/zebrahexblock.json @@ -0,0 +1,4 @@ +{ + "result": "040000006deacb36066fbbf99f00436c5a384e885c0cecff393b922ccfb22ed81b9e3600722039ae03cfdbee344a18774168795a936a720db144408a9abd92c55eb4a276f1b96e93c15bc2943a1f2483e466c456d2ea22ad82eea3d5788dbb5448bcb30c33d0fd658002451f6c005c8fa5b5d0cb7a24d326de3fd34422e22718a0c5e23972800292ed830000fd4005022b23ffe50b150fe854961e44e5d752975857fbc5026bc06a7550448e858e71dc7d927b44a04a325df027973f510de1ebb56327d7ed086b7419ff71db198940760dcede94a775fd67f515836ec4caa7d17bdc6e0ec8924c9dc74945496e673d104b85d35df91ec33942c84b7404579023b24d78b30bcde7563740f3a10a159918daafa18d955b0bb74c94782195ff5195936b21e6ec853a8ef269b21ac325675445b1815dfab18904ad6e2ad19ec8a753d1f0fd3b9171e0ea29f274bb261b028508d984fcdb9ed3df6e77ba3eb038d606d517b7461ebee25b57de47b2f2dde11c7504bacd271f2f44e405f79faaf99962145aa537b2b1f5f4f55ea30657f48ae51f0aff8f1bc1669311607cf941717e561b5e7e44bd48adc9aafba49d947deabe07e475c52e3eb13d485e60790db1a04540b4dfe992578b9758ed42ad8bee87a60b71cc88357f2d654a368cad751455057ed219766206094c899100ffc2c1b306409931d608f99a4ba850403eeaf092ee8b47b24dfad9fe18520d3ecaa98d7cf915f270629ab3fa8214f439cd0a160df22be1d99d7a979bbbd1fcb2dfd8ccfafa1207691013f2a524e1613150e0c679ac5950cb08211defa839fc537d5d941c6141e9d724ff468b9759ec7eb825132a2a78f5b9c413ee9674b44dd25f8bb65d1e839a24b36ee0eca01129935e73437e4a08edc8a2ff97d10752867f0e46cbc0ba2ae7459a7f9fbf41da3ea7be2618caa57728aae1d378f2a408f9569da556d0fa630c183d94dd542052dbc9b52ef75585ca16cf5793240f61b15dca17bead2cc524856b36c12a74c7187480090e8c01f6c79c3cafe4e50eac4c89f6991df7ff040913e3f70058aaa123f015a86b433b8a621b555f4d1abacf785c477257cc3f43ba13220e29ef653ab6be34ce223455e9bb5d775a19200267035efe861ce55503545cc85db40f59cbb4f049154983509e19877d181c3ffeff7c8fc24f1ea3f22e2513de60f340a8c7d019b76175055bc87f94dfb6381a5319e237ab5d269e1fdbe7a9e38d7fcadbf32528e1f295f5fafd1bde5b09a24c0d04d2a401e8efd352ac51b24da627ed62c85c5fefdd1019b0f9ffd86a2c30ed03d2b1b3181e261f7b05accb95a8b1109b72dd1759b7158a1b7a2c913b0fd6b5ad1dba25740139070648641739985e9e82036e9941c15580832dc4258f425c44296cc7b0938910b29cbdfcdc48df4b753744297fc656397ff504c50ea77f0cf229eb25849621e417275bb58f0b773a173d69bdf1339438b5cd5a44871436b8f56e37b059ae06ae61517e2e2857d5b422846f15d440d46fe8be4217836c545d56cfa75d46539cd0b71311e40abe30c21766ca99310bb5bab07061d69b250fc99f855c7b1218275dbe69d2d1b0aba51815126e08f68553d99de9078aad6a732b16618578127604aa0c565b2a5f0f1f15f9916ee72ffb43ace3a615bf74c461ac150fc5fa3537c78537d3aec997a1b426df2db27ad4b2dad1d34cabe61c16a9b9118ec017b650c4fc1669ecb725330c1849136e503d719b081157704377b8c778cf79031e1d25d98f4d3eb537aa564191404132231db19a64102a4c37395a86c6d557715379e39b6ec3bd7e5e4611ceeb45198e577f3efd92dea57f694ccbb20b0b44082a22cb61c758c063c862156ba6093aeed8986e1f0afd3a26f46022d515af226bdac94fb0f61f57708c2285c39c8df04a65cbdbb3fa06a53482131ed1bd8a66ed9fcdb7a995a37de76a67d75932deb989fdf565084b10954caca8919dc125483d722a02207838ccb94bb9df367e23ae37efe8c55d23bdf9cdb31d5defbc0fe25fd6dd1bf53bf30112c3babb9161d42cd5a58330cc4a44c999b2bfc701a45e7ec3b5ef493c9e1887020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603fb382a011cffffffff0468b6e60e000000001976a91401cb2de6a35b807a6e75d7fbfcc8107b2b9d39f488ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8740787d010000000017a91471e1df05024288a00802de81e08c437859586c878738c94d010000000017a91493916098d2a161a91f3ddebab69dd5db9587b6248700000000fb382a0000000000000000000000000400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02", + "id": 123 +} diff --git a/zebratx.json b/zebratx.json new file mode 100644 index 0000000..83f6b4b --- /dev/null +++ b/zebratx.json @@ -0,0 +1,8 @@ +{ + "result": { + "hex": "0400008085202f8900010829d200000000001976a91484ae5002305847e7176362d7c12c19c5bdbbaf8088ac0000000023392a00f02cd200000000000192331caef004cc758fb666bed1908e61daa82d5c9835c0544afd8369589d350b04a7488a9870983860779ca2e0079a286fe71f60d5c583c3427d24ff968bad3246c1c838b90f465becc1ddfea5839b730ec219d577ed182f6da8f493350b422c86943b7c8ff42de8aee0fe01f4b91c8bb204008f06f85c3dffdb622632d2d4e8b8f0c7457cfa0f4238c7ef4c8903a89559e9307c26e844747ccb9b8dd5e7e83637983746b2fec3de051312306eb8b15db4766b3ef5fe3086d53d388cf2b3b209389ff3644e47d6bfdbe2fafef1bc2311093ad0b49f4600925f55328da337e73f01f83097acd8f2aca7a85f28e75fb4efec6551e026a1ebb35c25efde455cc44002bb8cc79288ed738423432558ebb583874aa5c356abe5be794e1bfaeaf6a7eccf67e5d938751a3a351bc21d4422d2ff0f36f5b30759d79b1ef2d83618d9c1769694454002d2f2be74de3ac10d39829369c87a70e1e9769e7d5ae7c865282a04487a8ae4cf5beeecaea6a3be1c864bdd8d61df88f08a76ac49d28a3a069d2c0d02068a10e88674b39c9d03da49256d914319d267c0d1db08ee7777668e90a94c50a065977222ee620f2291f6ca3fa464fafe8fc3fedf64a836eef5a2ca16aaae5573ee082a77f046d388750fa4ce3853c846ae3f338741c7976f72db4ade4abd4211e8d335ec8c83309bc7d7140a99dfb64a29839b9acc74de4ac0949bcbec4e76be9096a45ab6ca19b165f4097e24ab92d7b58694b0897789c3cdcca2b3d4b0a9da153fafe68f940031b6548d3c37c1301faa9adcfc41c417e613c0838340e28801f72610289d7435910fd276ca243d119541e0a121d263fdda149ac40f293e6fee6d5ddc32532ad947548eb5d20a5bfea97543965fe09313f1a5a78ce51ecac9c36b54cb573780da15d197f5ffacf1fa0d2b5495057a29104d610936c1898d1058f6f7b90e614bc2e3ff56b1e75aa4708128e3782f602dbdd29ece268311965592ddd536ea63841ea953b20677e0dd911852d23b85a3382420d22cd276b216e81638540b04966210a9308e8f9fb46958c967e3c2e36ae081a95cec8865a87d85d5689f660fe6c616ebfc2dab0f6e41d3e8c2906405fb98a506d90a8e8c6201d520a0deaa65e92e91f965288128101427d58e0b1e3ad8a49526feed27f3bcc6d505591483e2e4cc4a9b678d63f3abc905f26f91083bc595b89ff0b6cc3caa9d93013127ab7b30fbe18fad6f7f380fd6d5668fb6c3fdea3771fdd3004994e5752275ff7b186f9ad95f9d7ff01263f1165de34c1ae867e8954d66186880a90d73eace4dc1b8b17c76815242342821b4fab93755c3dc24e60aafd1cd3e283a7414de3af18c61328d92e9141916b8bb816de024a5a047a66508340a3287f698a41804e297916ff04f2921a0eeb8fcc5690c7fc024f57ab1fb6c6bc9a0caf9bf9e0e9aad64ceb2634bedbda6716235e4b93b67cd07ae06fde6abd2893143b55628be83fd4b347ce407dabf28e288f99d23b031376bfc1b1552cac1557e4730b03be581a92feae7d39fa2cf1c565a6cbe59a83b64b90ef8fc73ff6f8b9562d77fae1221df8f5ddb029f12ae80c3f128b87e56f78224b875af54a2fa1434749bb2e1c7ad9331497a71015ae0fc63903f36023e7f34b97c6ec5976ba3740845e5870c85f1b2042cdca86620881e08595215332de7d5828844e9e44124e42e1c60f6821cb71640c6643b01681553c932d310632a8b21154445176eb1a9a3c87dff22508bdbe4f1500e19131a072c42ff1d106ade135722a9e37e95e7e93917378e7907aae4be92dab78b1cd5a771d6064f6e3afc26ff84943a84de7f6ca6b0ab5993d1013b061da4053d77398cbeb329a6ae16f76493f85df1164b4f1fdff69bf113c8f18274a4ce6a05dd4c1ccbacb8d2c3760210e312c3a344294b43b23d06b7ce7263d3178e4fd530ba5838dc0e517b7d6fff2a0d9c4d69105a8fdab3f0c51a219c1ec10337b7cf05f8f3b1fb0a09f600308e5c21ae6ae06d6f87a6766d29e3a34f331f520d80524d580bd54b25716b6b937534233b856e022d20e53779b3a4a3615a3d62d1824c2bfa906e7804d629cc6712a3aee8c3703e99ec807cdb2d381acf126d63b83a2ce1d8f5cb768270bf41ae5637976acbaad8a1fa52cfb7a2f012966f3d29867cf2c28e504043a09eeff91917f6e96dc35a7df124074da73a20b87c7c8e2196f344cc08bd4c2406daaf6064488b5f9983131d90141fba82b13b0b1ff60565be66d53c36df3a9b4c772bffd428b34f94060ad32c59c9c029eba5fabd7a01b4e7252406c0ce7bb93c831034b100cc71090b37a436f96ce902973e2dca9594886b602ed6142697413aa448652529fe688a2e62fa96f8031ade066bb2bdc682f0ae3a526c7ad3c5d01e243b999a58aa5f6816dcd7a0cdd49202e128b99436f71e7fb7033bf96d8e3930e39e024530ec4b7932d334e54a66bfc3630b472336b6719d5a38e6e9bed938f71fe49e0af0b20c5db5408cabb3227b1690e904ea3116ee568330f56a5a698b914570962da4d831f5f5acde9acb257d272d0cd14e3133c89307f2d1575e32b8cc1582d1e4a680d35a1a2cace6233dfb4b0a7fea26f41785e1ac6007dd20d8b6dc3bd6857fa487c52b39f86647a67931b33910b746331305199d20ecd2e4d3b454226a134240831ea5a35c1e2d603c48eea209868b839c79a9318b6fd1078bc0f2bb9b0e931b64d63fbbcbf22b41e3cf7bee5cecb3c0e7b3ae39cf736fce8645ab33becbc9586a9154e29dd88f42ec7deecb2a4c08ac020ce54607f8006d2aa05a689ea688419215f0a10043820d85965a0001f102915fa6b2edfc4d6db7011a725db79b3974e9c1fc1636781bc9609359cfb0c5c921b83fc1115f7ed2568e49991ef93f8b8ff93a0d778251f0bcaa00ad64de8438d40aa05adbd1d1d1d2bca05ea9471a2c1a3733e92bcdf896d47dbe41b9f0d8b8b75de1ccd7cd7b7802fc01c4536a1a7b52ce70736e2cdfc547b58401023e34a608c1b09d0f13ab83d7b3fcde0e050c8cb4635508ddc143a9e6edb1e5a489a48ae0f4d5b0cede7d1b0ed8177709edbd61d859f6d9bad93a4c640684b7b8d994d8f5c0c8773da2b7a5b57d28b58d3f00c53430671d4af1537a262e8ea44a1b943c9bfc5082ad86d6690de32bb6527c815da065061bf79562d292e3d4799aa0df968fb939f64203f541dd4d006e5bd0b34b39215a972c36b229fc2f8e7f10e154b369d7b8f85f89daaaba6ec9836ad748dd79be4a58210341a458202a16e152ca2b0338a116a8490a7fa52c02", + "height": 2767099, + "confirmations": 3582 + }, + "id": 123 +}