diff --git a/README.md b/README.md index 14d7732eba..3670209ba4 100755 --- a/README.md +++ b/README.md @@ -77,7 +77,8 @@ As a small team on a tiny budget we are working hard to make Specter better ever ### Using the Specter Desktop app The easiest way to run Specter Desktop is by installing the Specter Desktop app, which you can find on the [GitHub release page](https://github.com/cryptoadvance/specter-desktop/releases). -With this method, all you need to do is just download the right file for your operating system and install it like a normal desktop app (Debian buster is only [partially supported](https://github.com/cryptoadvance/specter-desktop/issues/769)) +With this method, all you need to do is just download the right file for your operating system and install it like a normal desktop app (Debian buster is only [partially supported](https://github.com/cryptoadvance/specter-desktop/issues/769)). +But there are a bunch of other option which you can read up in the [installation guide](docs/install_guide.md). ### Installing Specter from Pip * Specter requires Python version 3.9 to 3.10. diff --git a/docs/DeviceCreationGuide.md b/docs/DeviceCreationGuide.md new file mode 100644 index 0000000000..31574c1f2c --- /dev/null +++ b/docs/DeviceCreationGuide.md @@ -0,0 +1,179 @@ +# Device Creation Guide for Specter Hardware Wallets + +## Introduction + +Welcome to the Device Creation Guide for Specter Hardware Wallets. In this comprehensive guide, we'll walk you through the process of setting up hardware wallets within the Specter environment. Our focus is on ensuring the security of your digital assets while using various types of hardware wallets. We'll also delve into the crucial concept of derivation paths, which is essential for generating multiple keys from a single seed. Understanding these paths is key to managing and securing your Bitcoin effectively. + +## Types of Hardware Wallets + +### SD-Card Wallets + +- **Features:** SD-Card hardware wallets offer unique features that prioritize both security and portability. We'll explore these features in detail to help you understand their advantages. +- **User background:** Alice, a traveling consultant, requires a secure yet portable solution to manage her digital assets. She often moves between locations and needs a reliable way to carry her Bitcoin wallet without internet connectivity risks. +- **Use Case:** Alice opts for an SD-Card hardware wallet. Its small size and portability make it an ideal choice for her travels. She can easily carry it in her purse or securely store it in a safe. The SD-Card wallet allows her to access her digital assets on the go, without the need for an internet connection, reducing the risk of online threats. Moreover, she uses the SD-Card as a secure backup, storing a duplicate in a safe location. +- **Supported Devices:** + + - BitBox02 BitBox02 Wallet A Swiss-made hardware wallet known for its security and simplicity. It and features both SD-Card and USB interfaces for enhanced flexibility. + - Coldcard BitBox02 Wallet A popular choice for a secure and dedicated Bitcoin hardware wallet. Known for its advanced security features and ability to work with PSBT (Partially Signed Bitcoin Transactions). + - Cobo BitBox02 Wallet Designed for durability and security, Cobo is a multi-cryptocurrency hardware wallet with SD-Card support for backup and recovery. + - Passport BitBox02 Wallet This device emphasizes user-friendly design and privacy, offering air-gapped operation via QR codes and SD-Card backup. + +### QR Code Wallets + +- **Functionality:** QR code wallets operate differently, providing enhanced security through minimal direct connections with other devices. Learn how they work and why this matters. +- **User background:** Bob, a frequent user of Bitcoin for transactions, often finds himself in public places like coffee shops or conferences. He is concerned about the security risks associated with connecting his wallet to public Wi-Fi or potentially compromised devices. +- **Use Case:** Bob uses a QR Code wallet, which provides enhanced security through minimal direct connections. When making transactions, he simply scans the QR code displayed by his wallet. This method eliminates the need to connect to potentially insecure networks or devices, significantly reducing the risk of digital asset theft. The QR Code wallet’s ability to operate with minimal connectivity makes it an excellent choice for secure, hassle-free transactions in public settings. +- **Supported Devices:** + + - Jade BitBox02 Wallet A budget-friendly hardware wallet with QR code functionality for secure and offline transactions. + - SeedSigner BitBox02 Wallet An open-source project that focuses on creating a secure, offline transaction signing device using QR codes. + +### USB Wallets + +- **Characteristics:** USB wallets come with distinct features, including direct connectivity and user-friendly interfaces. Get a deeper understanding of what makes them stand out. +- **Scenarios:** Find out when and where USB wallets are your best choice. We'll showcase their versatility and compatibility with a wide range of devices. +- **User background:** Carol, a small business owner, accepts Bitcoin in her store. She needs a wallet that is both easy to use and compatible with various devices since she regularly deals with different types of transactions. +- **Use Case:** Carol chooses a USB wallet for its user-friendly interface and direct connectivity. The USB wallet's plug-and-play nature makes it simple to connect to her store's point-of-sale system or her personal computer. Its compatibility with various devices allows her to efficiently manage transactions without the need for specialized hardware. The USB wallet's intuitive interface makes it easy for Carol to navigate, making it an ideal choice for her everyday business transactions. +- **Supported Devices:** + + - BitBox02 BitBox02 Wallet Swiss-made hardware wallet, offering a blend of security and simplicity. Bitcoin Only version available. + - KeepKey Keystone Wallet A user-friendly wallet with a large display, providing a secure environment for cryptocurrency storage and transactions. + - Ledger Ledger Wallet Known for its security and sleek design, Ledger wallets support a wide range of cryptocurrencies. Less security since it contains code for other shitcoins. + - Trezor Trezor Wallet One of the first hardware wallets in the market, renowned for its ease of use and robust security measures. + - Keystone Keystone Wallet Formerly known as Cobo Vault, Keystone wallets offer a high-security solution with air-gapped QR code signing. + + +## Step-by-Step Guide for Device Creation in Specter + +### Add a new device +Select signing device +![image](https://user-images.githubusercontent.com/47259243/223428531-2f3a04d4-177d-4626-8108-b66234892541.png) +Upload public keys +![image](https://user-images.githubusercontent.com/47259243/223427859-c06faec5-78ab-4592-9ba6-4018978280cc.png) + +### Select how to connect to Bitcoin network +![image](https://user-images.githubusercontent.com/47259243/223425374-a3e68ac7-2bdb-48fe-a53b-59f235c59bd1.png) +Electrum server or... +![image](https://user-images.githubusercontent.com/47259243/223426046-dd225f00-ba18-45cb-871a-40efd7eefc1e.png) +...via Bitcoin Core node. +![image](https://user-images.githubusercontent.com/47259243/223426366-c3ba758a-34c4-4ce1-8aae-cf0cc335a892.png) + + +## Understanding Derivation Paths + +### Concept Explanation + +Understanding derivation paths is fundamental to managing the security of your digital assets. In this section, we'll provide you with an overview of what derivation paths are and why they matter. We'll also introduce key paths like BIP 44 (for multi-account hierarchy), BIP 49 (for SegWit compatibility), and BIP 84 (for native SegWit addresses). Each of these paths caters to different Bitcoin address types and plays a crucial role in organizing and securing your Bitcoin, especially within hardware wallets. + +By default, Specter Wallets are set up with: +- BIP 44 for traditional multisig wallets. +- BIP 49 or BIP 84 for SegWit singlesig wallets. + +These default settings cover the needs of most users, simplifying the wallet setup and usage process. However, understanding these paths can enhance your ability to tailor the wallet to your specific needs, especially if you have advanced security considerations. + +### Challenges and Best Practices + +1. **Complexity:** + - Derivation paths, especially when considering various Bitcoin address types like BIP 44, BIP 49, and BIP 84, can be intricate. The challenge lies in comprehending the nuances of each path and selecting the one that aligns with your specific use case. Best practice here is to educate yourself thoroughly and seek expert advice if needed. + +2. **Compatibility:** + - Using the wrong derivation path can lead to compatibility issues, making it challenging to access your funds. It's crucial to ensure that the path you choose is supported by your wallet software and the services you intend to use. Staying informed about updates and changes in the Bitcoin ecosystem is essential to avoid compatibility pitfalls. + - To assist with this, [common derivation paths for different wallets can be found at Wallets Recovery](https://walletsrecovery.org/). This resource can be useful for understanding the standard practices of various wallets and ensuring compatibility. + - Ensure that the path you choose is supported by your wallet software and the services you intend to use. + +3. **Security Risks:** + - Incorrectly managed derivation paths can introduce security risks. For instance, sharing your master public key (xpub) derived from an account with a third party may expose all the addresses generated from it. Best practice involves limiting the exposure of sensitive information and adopting a "need-to-know" approach when sharing keys or information related to derivation paths. + - Limit the exposure of sensitive information and adopt a "need-to-know" approach when sharing keys or information related to derivation paths. + +4. **Backup Strategies:** + - Derivation paths affect how you back up your wallet. Implementing a robust backup strategy that includes the derivation path information is essential. Best practice is to maintain secure backups and periodically test your recovery process to ensure you can regain access to your digital assets if the need arises. + - Implementing a robust backup strategy that includes the derivation path information is essential. + +### Example 1: BIP 84 (Hierarchical Deterministic Wallets) +#### Scenario: Multiple Account Management + +##### Context +Emily, a Bitcoin enthusiast, has diverse needs for managing her digital assets. She wants to separate her main funds from the stacking service provider she's using. For this, she needs a wallet structure that allows for clear separation while maintaining privacy and security. + +##### Use Case +Emily opts to use the BIP 84 derivation path, which is designed for native SegWit addresses, providing her with an efficient and cost-effective way to manage her Bitcoin transactions. She uses two different paths within BIP 84 to separate her funds: +- For her main wallet, where she keeps the majority of her funds, Emily uses the derivation path `m/84'/0'/0'`. This path is for her personal use, ensuring that her primary funds remain secure and private. +- For the stacking service provider, which requires her to share her extended public key (xpub) for operational purposes, she uses the derivation path `m/84'/0'/1'`. This separation allows her to maintain privacy and security, as the service provider only has visibility over the funds in the dedicated stacking account. + +##### Advantage +By using two distinct accounts under the BIP 84 standard, Emily efficiently manages her assets, keeping her main funds secure and private while still participating in stacking services. + +### Example 2: BIP 49 (SegWit Compatibility in P2SH) +#### Scenario: Balancing Efficiency with Cost in Wallet Management + +##### Context +John has been managing his Bitcoin assets using an older wallet setup. As the volume of his transactions increases, he's faced with the challenge of seeking more efficient transaction processing, both in terms of speed and reduced fees, without incurring significant costs in moving his funds. +##### Use Case +John's current wallet utilizes BIP 49, which enables SegWit compatibility through Pay to Script Hash (P2SH) addresses, identifiable by starting with '3'. His addresses follow the derivation path m/49'/0'/0'. Despite being aware of newer wallet technologies like those adhering to BIP 84, which offer even greater efficiencies by fully embracing native SegWit addresses (beginning with 'bc1'), John decides to stick with his current setup. +##### Advantage +John's decision is influenced by his desire to maintain his current UTXO set. He is cautious about the transaction fees that would be incurred in transferring his entire balance to a new wallet structure. By sticking with BIP 49, John still benefits from reduced transaction fees and improved speeds compared to legacy addresses, but he acknowledges that his setup is not as efficient as it could be with BIP 84. His choice represents a compromise between optimizing transaction efficiency and minimizing the costs associated with a complete migration to a new wallet system. + +### Example 3: BIP 84 (Native SegWit Bech32 Addresses) +#### Scenario: Maximizing Efficiency and Exploring Testnets + +##### Context +Lisa is a tech-savvy investor who keeps up with the latest developments in Bitcoin technology. She wants to use the most advanced and efficient method for managing her Bitcoin transactions. Additionally, Lisa is interested in exploring Bitcoin testnets for testing and educational purposes. + +##### Use Case +Lisa opts for a wallet that implements BIP 84, which enables the creation of native SegWit addresses that start with 'bc1'. These are Bech32 addresses, which offer benefits such as more efficient block weight usage and better error detection. For her main Bitcoin transactions, her derivation path is: `m/84'/0'/0'`. + +Moreover, Lisa is also experimenting with Bitcoin testnet. Testnets are crucial for trying out transactions without using real Bitcoin, which is an ideal environment for testing and learning. For her testnet transactions, she uses the derivation path `m/84'/1'/0'`. This path is specifically designated for testnet in BIP 84, allowing her to differentiate between real and test transactions easily. + +##### Advantage +Using BIP 84, Lisa experiences lower fees and faster transactions in her main wallet. With the addition of the testnet path, she can safely experiment and learn without risking her actual Bitcoin. This approach not only future-proofs her wallet as the industry moves towards broader adoption of SegWit but also enhances her understanding and proficiency in managing digital assets. + +### Conclusion +In each of these scenarios, the use of different derivation paths (BIP 44, BIP 49, and BIP 84) reflects a specific need and functionality in managing Bitcoin transactions: +- **BIP 44** is ideal for users like Emily, who require a structured organization for multiple types of transactions. It provides a clear hierarchical structure for different accounts under a single master seed. +- **BIP 49** benefits users like John, who seek efficiency and reduced costs in their transactions. The SegWit compatibility in P2SH format helps in lowering transaction fees and improving confirmation speeds. +- **BIP 84** is perfect for tech-savvy users like Lisa, who want to leverage the latest advancements in Bitcoin technology for optimal efficiency and future compatibility. + +For those seeking a deeper understanding of derivation paths, we recommend exploring "[Learn Me a Bitcoin". This website provides in-depth information](https://learnmeabitcoin.com/technical/derivation-paths) on the topic, and you can integrate this knowledge into our guide for a more comprehensive grasp of derivation paths. + +## Troubleshooting + +### Common Issues + +#### Check the USB Connection +- **Step 1:** Unplug the wallet from the computer. +- **Step 2:** Inspect the USB cable for any visible damage. If damaged, replace the cable. +- **Step 3:** Reconnect the wallet to the computer using a different USB port. Sometimes ports can malfunction or have poor connectivity. + +#### Restart the Wallet and Computer +- **Step 1:** Safely eject the hardware wallet from your computer. +- **Step 2:** Restart the hardware wallet. If it has a power button, turn it off and then on again. If not, disconnect and reconnect it. +- **Step 3:** Restart your computer. This can resolve issues caused by temporary software glitches. + +#### Update Wallet Firmware and Software +- **Step 1:** Check if your hardware wallet firmware is up to date. Refer to the wallet’s official website for the latest firmware version. +- **Step 2:** Update the wallet application on your computer. Ensure you're using the latest version. +- **Step 3:** After updating, reconnect the wallet and check if it is recognized. + +#### Check Device Manager (Windows) or System Report (Mac) +**For Windows:** +- **Step 1:** Open 'Device Manager'. +- **Step 2:** Look under ‘Universal Serial Bus controllers’. Check if the wallet is listed or if there are any devices with a yellow exclamation mark. +- **Step 3:** If the wallet is listed with an error, right-click on it and select ‘Update driver’. + +**For Mac:** +- **Step 1:** Click on the Apple logo and select ‘About This Mac’. +- **Step 2:** Go to ‘System Report’ and select ‘USB’. +- **Step 3:** Check if the wallet is listed under USB Device Tree. + +#### Try a Different Computer +- **Step 1:** Connect the wallet to a different computer. This can help determine if the issue is with the original computer’s hardware or software. + +#### Contact Customer Support or our Telegram Group +If none of the above steps work, the problem might be more complex or specific to the wallet. In this case, contact the customer support of the hardware wallet for further assistance. + +### Preventive Measures +- Regularly update the wallet's firmware and the computer’s software to avoid compatibility issues. +- Use high-quality USB cables and ports to ensure a stable connection. +- Avoid exposing the hardware wallet to physical damage or extreme temperatures. + +By the end of this guide, you'll be well-equipped to create and manage hardware wallets within the Specter environment and understand derivation paths. Let's get started on your journey to safeguarding your bitcoin journey. diff --git a/docs/WalletCreationGuide.md b/docs/WalletCreationGuide.md new file mode 100644 index 0000000000..9267bf74a3 --- /dev/null +++ b/docs/WalletCreationGuide.md @@ -0,0 +1,69 @@ +# Wallet Creation Guide + +## Introduction + +Specter wallets are designed to provide a user-friendly interface around Bitcoin Core, focusing on multisignature setups with airgapped signing devices, though they also support single-signature wallets. It's developed to make the interaction with Bitcoin Core more convenient for the users. + +## Types of Wallets + +### Single-Sig Wallets +- These involve one signing device or hardware wallet generating one seed. +- They are simpler and require only one secure location for seed backup. +- However, they are less secure against phishing attacks as one compromised seed can lead to lost Bitcoin. + +### Multi-Sig Wallets +- More secure than single-sig, multisig wallets require two or more signing devices to authorize transactions. +- A key feature is the quorum requirement, which specifies the number of signatures needed out of the total number of devices. For example, a common scenario is requiring 2 signatures from a total of 3 devices (2/3 quorum). This setup enhances security as it requires multiple parties or devices to agree on a transaction. +- Multisig wallets are particularly effective against phishing and theft as compromising one seed or device is not enough to access the funds. +- They offer better security, as one compromised seed doesn't result in immediate loss of funds. +- However, they are more complex to set up and require multiple secure locations for seed backups. + +#### Single-Sig Wallets Examples: + +- A user with a **Ledger Nano S** hardware wallet creating a single-signature Specter wallet for added convenience in managing their Bitcoin holdings. +- Someone who wants a simple wallet setup opting for a single-signature wallet with their **Electrum** software wallet, as they trust their computer's security. + +#### Multi-Sig Wallets Examples: + +- A group of friends setting up a multi-signature Specter wallet for a shared investment fund, where multiple devices are required to authorize transactions, enhancing security. For more detailed guidance on setting up such a wallet, [refer to our Multi-Sig Wallet Guide](multisig-guide.md). +- A Bitcoin exchange using a multi-signature setup with several hardware wallets to protect customer funds against a single point of failure. [Our Multi-Sig Wallet Guide offers](multisig-guide.md) comprehensive steps and best practices for implementing such a secure system. + + +## Step-by-Step Guide for Single-Sig Wallets + +1. **Setup and Installation** + - Download and install the Specter Desktop app from the official GitHub release page. You can [find the installation guide here](install_guide.md). + - Ensure your system meets the necessary requirements (like Python version, Bitcoin Core version). + +2. **Running the App** + - Start the Specter Desktop app. + - It should automatically detect Bitcoin Core if it's using a default data folder. + - If not, set rpcuser and rpcpassword in the Bitcoin Core's bitcoin.conf file and directly in the Specter app settings. You can [find the Node Connecting Guide here](connect-your-node.md). + +3. **Creating a Wallet** + - In Specter, navigate to the wallet creation section. + - **Important:** Before proceeding, ensure you have imported your hardware wallet device (e.g., Ledger, BitBox, Trezor) into Specter Desktop. Specter is specifically designed to enhance security by working with hardware wallets, providing an extra layer of protection compared to hot wallets. For detailed instructions on connecting your hardware wallet, [refer to the Device Creation Guide](DeviceCreationGuide.md). + - Choose the option for a single-signature wallet if you are setting up a wallet with one device. For enhanced security, consider setting up a multi-signature wallet. + - When creating a single-signature wallet, Specter will interface with your hardware wallet. **Note:** If your hardware wallet is already initialized (which is the common scenario), Specter will use the existing seed from your device to manage the wallet. There is no need to generate a new seed within Specter. It is crucial to keep your seed secure and never enter it on the computer or share it with anyone. + - **For New Hardware Wallet Users:** + If your hardware wallet is new and not yet initialized, you will need to generate a new seed as part of the hardware wallet's setup process. This is typically done directly on the hardware wallet to ensure maximum security. Follow your hardware wallet's instructions for this step. Once your hardware wallet is initialized with a new seed, you can proceed to connect it with Specter Desktop. + +4. **Backing Up the Wallet** + - After creating the wallet, it's crucial to back up the seed securely. + - For single-signature wallets, where the loss of the seed equates to the loss of funds, Specter strongly recommends storing seeds on steel. This method provides long-term durability against elements like fire and water, ensuring your backup remains intact in various scenarios. + - While still advisable for multi-signature setups, steel backups in multisig configurations are slightly less critical since the loss of a single seed doesn’t necessarily mean loss of funds, provided other signatures are available. However, it is still a best practice to secure each seed with the utmost care. + +5. **Testing the Wallet** + - It's advisable to test the wallet by sending a small amount of Bitcoin to it and then trying to access these funds using the wallet. + +## Backup PDFs + +- Specter provides backup PDFs that contain crucial information like (master) public keys and fingerprints. +- These backups do not include the seed itself. +- Keep a copy of the backup PDFs with every seed backup, but ensure they are kept private as they allow anyone to recreate the wallet and see the balance. + +## Notes + +- Always ensure the safety of your seed phrases and backup information. +- Regularly update the Specter software and your Bitcoin Core node for security and functionality improvements. +- For multisig wallet creation, a separate, more detailed guide is recommended due to its complexity. diff --git a/docs/elements.md b/docs/elements.md index 4485614ea7..2d2c85cbdb 100644 --- a/docs/elements.md +++ b/docs/elements.md @@ -6,7 +6,7 @@ This document is a description on how to get started with elements/liquid. We'll After that, we'll explain how to connect your Specter Desktop to that node, create wallets and receive some coins (via sideshift.ai). Signing transactions is nowhere different than in any other Hotwallet. ## Elements Installation -The Elements's instalaation is highly dependent to your system. Choose a fitting [artifact](https://github.com/ElementsProject/elements/releases) and install them. +The Elements installation process is highly dependent on your system. Choose a fitting [artifact](https://github.com/ElementsProject/elements/releases) and install it. ## Liquid Node In order to validate Peg-Ins, you'll need RPC-access to your Bitcoin-Core node. I did this with a fullnode, it might also work with a pruned node (not tested, though). @@ -107,4 +107,4 @@ Make sure that this is successfull by checking these two files to be existent: ```sh ls tests/elements/src/elements-cli tests/elements/src/elementsd tests/elements/src/elements-cli tests/elements/src/elementsd -``` \ No newline at end of file +``` diff --git a/docs/install_guide.md b/docs/install_guide.md new file mode 100644 index 0000000000..2dbbd5a357 --- /dev/null +++ b/docs/install_guide.md @@ -0,0 +1,233 @@ +# Installation Method Decision Guide + +This guide is crafted to address the complexities and confusion users often encounter while installing Specter, a crucial tool for enhancing Bitcoin operations. Recognizing the diversity in user expertise and system requirements, a structured approach to choosing the right installation method is essential. This guide aims to streamline the decision-making process, providing clarity and direction to both novice and advanced users in their journey to effectively utilize Specter. + +## Specter Desktop Installation Methods and Their Pros and Cons + +Specter Desktop, a versatile Bitcoin wallet management application, offers multiple installation methods to accommodate various user preferences and technical skill levels. This guide outlines the available installation options, their advantages, and disadvantages to help you choose the one that best fits your needs. + + +## OS-Specific Apps for Specter Desktop + +**Ease of Installation:** OS-specific apps offer a user-friendly approach to installing Specter, making the process accessible even for those with limited technical expertise. + +**Compatibility:** These apps are tailored to work seamlessly with the operating system, ensuring optimal performance and stability. + +**Convenience:** Setting up Specter on the same machine as the Bitcoin Core node enhances convenience, as it allows for easy integration and management within a single system. + +**Targeted Updates:** OS-specific applications can receive updates and features tailored to the needs and capabilities of the specific operating system. + +## PIP Installation + +### Overview + +The PIP installation method is tailored for users who are comfortable within Python environments. It's an ideal choice for those who prefer a Python-centric approach to software installation and management. + +#### Advantages: + +- **Simplicity for Python Users:** Installation via PIP is straightforward, especially for those familiar with Python's package management. +- **Direct Control:** Users have direct control over the installation process, including version management. +- **Integration with Python Environment:** Seamlessly integrates with existing Python setups and workflows. + +#### Disadvantages: + +- **Python Knowledge Required:** Assumes familiarity with Python and its ecosystem, which might not suit all users. +- **Manual Dependency Management:** Users might need to manage dependencies manually, depending on their Python environment. + +### Ideal Use Case: + +PIP installation is best suited for users who are already working within a Python environment and are comfortable managing Python packages. This method offers a quick and efficient way to integrate Specter Desktop into existing Python-based workflows or projects. + +This addition should enhance your guide, making it more comprehensive for users with a background in Python. + +## Running Specter Desktop Using Docker + +Docker offers a robust and efficient way to install and run Specter Desktop. This method is especially beneficial for those already familiar with Docker environments. The key advantages of using Docker include: + +- **Replicability:** Docker ensures a consistent installation process, providing uniformity across different systems. This is particularly useful for users who need to deploy Specter on multiple machines or different operating systems. + +- **Ease of Setup:** Docker simplifies the installation process. By encapsulating Specter Desktop within a container, Docker manages dependencies and system-specific configurations, reducing the complexity typically associated with traditional installations. + +- **Consistent Runtime Environment:** One of Docker's main strengths is its ability to provide a stable and consistent runtime environment for applications. This consistency is crucial for maintaining stability and reliability in software operations, a key consideration for Bitcoin wallet management and operations. + +- **Isolation:** Running Specter in a Docker container ensures that it operates in an isolated environment. This isolation minimizes conflicts with other software on your system and enhances security, a vital aspect of managing Bitcoin wallets. + +By choosing Docker for installing Specter Desktop, users benefit from a streamlined, consistent, and secure installation experience, ideal for maintaining a robust Bitcoin operation environment across various platforms. + +## Integration with Node Implementations + +Specter Desktop can be seamlessly integrated with various Bitcoin node implementations, providing a comprehensive and streamlined experience for users who are already operating these nodes. This integration is especially beneficial for those looking to manage their Bitcoin wallets in conjunction with their node's functionalities. + +**Supported Node Implementations:** + +- **Raspiblitz:** Specter integrates smoothly, enhancing the node's wallet management capabilities. +- **Citadel:** This integration offers a user-friendly interface for Citadel node operators. +- **Start9:** Ideal for Start9 users seeking an integrated wallet solution. +- **Mynode:** Connects effortlessly with Mynode, offering a robust wallet management system. +- **Umbrel:** Umbrel users can enjoy a seamless integration, combining node operation with efficient wallet management. + +This approach is recommended for users who are already running these specific nodes, as it leverages the existing infrastructure to provide an integrated, efficient, and secure wallet management solution. + + +## Using Package Managers (Homebrew for macOS and Linux) + +**Advantages:** + +- Ease of Installation: Package managers streamline the installation process, making it quick and straightforward. +- Dependency Management: They automatically handle dependencies, ensuring that all required components are installed. +- Updates: You can easily update Specter Desktop with a single command, keeping your software up to date. + +**Disadvantages:** + +- Platform-Specific: This method is only available for macOS and Linux users, leaving out Windows users. +- Limited Version Control: You might not have the latest version available through the package manager if the maintainers have not updated it yet. + +## Downloading Binaries from the Specter Release Page + +**Advantages:** + +- Cross-Platform: Suitable for Windows, macOS, and Linux users, ensuring broad accessibility. +- User-Friendly: Downloading and installing binaries is typically straightforward and requires no technical expertise. +- Version Control: You have control over which version of Specter Desktop you install. + +**Disadvantages:** + +- Manual Updates: You'll need to check for updates and download new versions manually, which may be less convenient. +- Dependency Handling: Some dependencies might still need manual installation, depending on your system configuration. + +## Manual Build and Installation from Source Code (Advanced Users) + +**Advantages:** + +- Full Control: You have complete control over the build process and can customize Specter Desktop to your needs. +- Advanced Features: This method is suitable for users with technical expertise who want to contribute to the development or implement specific modifications. + +**Disadvantages:** + +- Complexity: Building from source requires a good understanding of software development, including dependency management and compiling code. +- Time-Consuming: This method can be time-consuming, especially for users not experienced with building software from source. +- Maintenance: You are responsible for keeping your installation up to date by fetching and compiling new source code. + +## Real-World Application Examples + +Here are real-world scenarios illustrating how different installation methods for Specter Desktop are chosen based on users' preferences and needs: + +1. **PIP Installation** + + **Scenario:** Alice, a data scientist, is comfortable with Python and uses it daily for her work. She prefers installing applications through Python to keep her environment consistent. She chooses PIP installation for Specter, finding it straightforward and in line with her existing Python skills. + +2. **Docker Installation** + + **Scenario:** Bob, a software developer, frequently uses Docker for his projects. He prefers containerized applications for their replicability and isolated environments. Bob opts for Docker installation for Specter, appreciating the ease of setup and consistent runtime environment it provides. + +3. **Node Implementation Integration** + + **Scenario:** Carol, an enthusiast running a Bitcoin node on Raspiblitz, wants to integrate wallet management directly with her node. She selects the integration option with Raspiblitz for a seamless experience, valuing the integrated approach and efficiency it offers. + +4. **Manual Build from Source** + + **Scenario:** Dave, a seasoned developer and Bitcoin hobbyist, seeks deep customization for his Specter setup. He is comfortable with software development and opts to build Specter from source. This method allows him the full control he desires for specific modifications and features. + + + +### Installation Method Comparison + +| Installation Method | Ease of Installation (1-5) | Customization (1-5) | Update Frequency (1-5) | Technical Expertise (1-5) | +|-----------------------|-----------------------------|---------------------|------------------------|---------------------------| +| Package Manager | 4 | 2 | 4 | 2 | +| Direct Download | 3 | 3 | 3 | 2 | +| Docker | 3 | 4 | 4 | 4 | +| Build from Source | 1 | 5 | 5 | 5 | +| PIP Installation | 2 | 3 | 4 | 3 | +| Node Implementation | 3 | 4 | 3 | 3 | + +- A score of 5 indicates the highest level of ease, customization, etc., while 1 indicates the lowest. +- This matrix is a general guideline. Specifics can vary based on the package manager and the user's technical background. + +## Installation Decision Tree +**Start Here: Choosing Your Specter Desktop Installation Method** +- Are you comfortable with technical details and customization? + - **Yes:** + - Do you require advanced customization and control? + - **Yes:** → Build from Source (Best for seasoned developers or enthusiasts seeking deep customization) + - **No:** + - Are you familiar with Docker and containerization? + - **Yes:** → Install via Docker (Ideal for consistent, isolated environments) + - **No:** → Install via Direct Download (Suitable for users comfortable with basic technical steps) + - **No:** + - Do you prefer ease of use and simplicity? + - **Yes:** + - Are you using macOS or Linux? + - **Yes:** → Use Package Manager like Homebrew (Simple and straightforward for these OS) + - **No:** → Direct Download from Specter Release Page (Easy for Windows users) + - **No:** → Use PIP (Python Package Manager) to install to any Os + +- Are you integrating with a specific Bitcoin node? + - **Yes:** + - → Choose Node-Specific Integration (Select based on the node you are operating, like Raspiblitz or Umbrel) + - **No:** → Refer back to technical comfort level and ease of use preferences + +## Installation Method Considerations + +When choosing an installation method for Specter, consider these heuristics: + +- Familiarity with Package Managers: If you are comfortable using package managers like Homebrew, they offer a convenient and straightforward installation process. +- System Constraints: Evaluate your system's limitations or constraints. Some methods may require more resources or specific system configurations. +- Need for Customization: If you require extensive customization, consider building from source, which offers the most flexibility. +- Technical Expertise: Assess your technical skill level. Less technical users might prefer simpler methods like direct downloads, while more experienced users might opt for Docker or building from source. +- Update Preferences: If staying up-to-date effortlessly is important, package managers typically make updating easier. + +## Ideal Use Case: + +This method is ideal for users who prefer a straightforward, no-hassle installation process and plan to run Specter alongside their Bitcoin Core node on the same device. It's particularly suited for those who value ease of use and system integration. + +Adding this section to your guide will provide a complete overview of all the installation methods mentioned in the PDF presentation, making it more comprehensive and useful for your readers. + +## Access Methods for Specter Desktop + +Specter Desktop offers various access methods to cater to different user needs and security preferences. These methods include: + +1. **App Access:** You can access Specter through dedicated apps available for specific operating systems. This method offers convenience and a user-friendly interface. + +2. **Local Network Access via HTTP(S):** Specter can be accessed through your local network using HTTP or HTTPS. This method is practical for users who operate Specter on a separate machine or server within their local network. + +3. **Access via Tor:** For enhanced privacy and security, Specter supports access via the Tor network. This method is ideal for users who prioritize security and wish to access their wallet remotely without exposing their real IP address. + +Each method has its unique advantages in terms of ease of use, security, and privacy. Users can choose the access method that best suits their operational environment and security requirements. + + +## Node Options for Running Specter + +Running Specter Desktop on different types of nodes has unique pros and cons that users should consider: + +1. **Full Node on Dedicated Hardware (e.g., Raspiblitz, Umbrel):** + - **Pros:** Offers robust performance and reliability, ideal for dedicated Bitcoin operations. It allows for a more secure and stable environment. + - **Cons:** Requires investment in dedicated hardware. It may be complex for beginners to set up and maintain. + +2. **Full Node on Desktop/Laptop:** + - **Pros:** Convenient for users who prefer to use existing hardware. It's a cost-effective solution without the need for additional devices. + - **Cons:** The computer's performance might be affected, and it may not be feasible to run the node continuously. There's also a higher risk of security vulnerabilities. + +3. **Pruned Node on Desktop/Laptop:** + - **Pros:** Requires less storage space, making it suitable for users with limited hardware capacity. + - **Cons:** Does not store the entire blockchain, which may limit certain functionalities and historical data access. + +Each option offers a balance of convenience, security, and functionality. Users should choose based on their technical expertise, security needs, and available resources. + +## Recent Updates +**Enhanced Electrum Integration:** Since version 2.0.0, Specter Desktop has featured integration with Electrum servers, further enhancing connectivity and accessibility options for users. Ongoing improvements in this area are expected to streamline the experience even more. + +## Future Developments of Specter Desktop + +Specter Desktop is evolving, with upcoming features and extensions that promise to enhance its functionality and user experience. Key future developments include: + +1. **Extension Framework:** Specter Desktop will support extensions, allowing users to expand its capabilities without needing to alter the core code. This will enable a more customizable experience. + +2. **New Extensions:** Planned extensions include those for connecting Specter to Swan, issuing bonds on the Liquid sidechain, importing mining rewards history from Slush Pool, fund distribution via CSV with Exfund, and building a local price database with Spotbit. + +These upcoming developments showcase Specter's commitment to growth and adaptability, catering to an expanding range of user needs and preferences in Bitcoin wallet management. + + +## Conclusion + +In conclusion, the choice of installation method for Specter Desktop depends on your technical proficiency, platform, and preferences. Package managers and binary downloads are user-friendly and suitable for most users, while Docker provides isolation and flexibility. Manual source code installation is reserved for advanced users seeking complete control and customization. Select the method that aligns with your needs to enjoy the benefits of Specter Desktop's Bitcoin wallet management capabilities. diff --git a/docs/release-notes.md b/docs/release-notes.md index 26100174c9..17f03517e6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,24 @@ # Release Notes +## v2.0.2 September 21, 2023 +- Bugfix: Add missing signet key #2368 (Manolis Mandrapilias) +- Bugfix: Jade displaying wrong multisig addresses for descriptors using multi() #2366 (Manolis Mandrapilias) +- Bugfix: JSON parsing issues when copy & pasting wallet data from PDF #2355 (Manolis Mandrapilias) +- Bugfix: #2319 #2330 (k9ert) +- Bugfix: fix specter.node has no _get_rpc() #2327 (k9ert) +- Bugfix: Update spotbit api url and path #2372 (Benjamin B) +- Chore(deps): Bump semver from 5.7.1 to 5.7.2 #2353 (dependabot[bot]) +- Chore(deps): Bump semver from 6.3.0 to 6.3.1 in /pyinstaller/electron #2352 (dependabot[bot]) +- Chore: Regex change to capture labels in wallet data imports better #2357 (Manolis Mandrapilias) +- Chore: Use prettier for Electron app #2347 (Manolis Mandrapilias) +- Chore: Optional ENFORCE_HWI_INITIALISATION_AT_STARTUP #2383 (k9ert) +- Chore: remove SpecterUri #2358 (k9ert) +- Chore: updating flask_babel fixes #2218 #2359 (k9ert) +- Feature: Enable import of a multisig wallet that uses a multi-descriptor #2349 (Manolis Mandrapilias) +- Feature: Implement automatic wallet import via Specter URI for MacOS #2344 (Manolis Mandrapilias) +- Security: Patched Fix Electron vulnerable to out-of-package code execution when launched with arbitrary cwd #2380 (Sergev ₱) +- Security: Fix login open redirect due to next parameter manipulation #2350 (zealsham) + ## v2.0.1 March 27, 2023 - Bugfix: Keyerror in case of frozen utxos #2308 (k9ert) - Bugfix: method getaddressinfo not implemented #2313 (k9ert) diff --git a/docs/swan.md b/docs/swan.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/mkdocs.yml b/mkdocs.yml index ebb4214221..42c48b496c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,9 @@ nav: - multisig-guide.md - User Guide: - faq.md + - install_guide.md + - DeviceCreationGuide.md + - WalletCreationGuide.md - 'Using Tor': tor.md - 'Signing messages': sign-message.md - Operating Guide: diff --git a/package-lock.json b/package-lock.json index a8a2a84b95..69d6157d6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3749,9 +3749,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -4691,9 +4691,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8990,9 +8990,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -9718,9 +9718,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" diff --git a/pyinstaller/electron/build/entitlements.mac.plist b/pyinstaller/electron/build/entitlements.mac.plist index dac74b9d41..7e5286745e 100644 --- a/pyinstaller/electron/build/entitlements.mac.plist +++ b/pyinstaller/electron/build/entitlements.mac.plist @@ -8,5 +8,22 @@ com.apple.security.device.camera + diff --git a/pyinstaller/electron/package-lock.json b/pyinstaller/electron/package-lock.json index 87a2c60451..108006fbd0 100644 --- a/pyinstaller/electron/package-lock.json +++ b/pyinstaller/electron/package-lock.json @@ -247,9 +247,9 @@ } }, "node_modules/@electron/get/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -662,21 +662,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/app-builder-lib/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/app-builder-lib/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -2870,11 +2855,13 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" }, @@ -3664,9 +3651,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -4022,15 +4009,6 @@ "universalify": "^2.0.0" } }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -5748,11 +5726,13 @@ "dev": true }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "optional": true + "requires": { + "lru-cache": "^6.0.0" + } }, "semver-compare": { "version": "1.0.0", diff --git a/pyinstaller/electron/yarn.lock b/pyinstaller/electron/yarn.lock index 890a50ba02..38bd21f412 100644 --- a/pyinstaller/electron/yarn.lock +++ b/pyinstaller/electron/yarn.lock @@ -2,89 +2,83 @@ # yarn lockfile v1 +"7zip-bin@~5.1.1": + version "5.1.1" + resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz" + "@dabh/diagnostics@^2.0.2": - "integrity" "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==" - "resolved" "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz" - "version" "2.0.2" + version "2.0.2" + resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz" dependencies: - "colorspace" "1.1.x" - "enabled" "2.0.x" - "kuler" "^2.0.0" + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" "@develar/schema-utils@~2.6.5": - "integrity" "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==" - "resolved" "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz" - "version" "2.6.5" + version "2.6.5" + resolved "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz" dependencies: - "ajv" "^6.12.0" - "ajv-keywords" "^3.4.1" + ajv "^6.12.0" + ajv-keywords "^3.4.1" "@electron/get@^2.0.0": - "integrity" "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==" - "resolved" "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz" - "version" "2.0.2" - dependencies: - "debug" "^4.1.1" - "env-paths" "^2.2.0" - "fs-extra" "^8.1.0" - "got" "^11.8.5" - "progress" "^2.0.3" - "semver" "^6.2.0" - "sumchecker" "^3.0.1" + version "2.0.2" + resolved "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz" + dependencies: + debug "^4.1.1" + env-paths "^2.2.0" + fs-extra "^8.1.0" + got "^11.8.5" + progress "^2.0.3" + semver "^6.2.0" + sumchecker "^3.0.1" optionalDependencies: - "global-agent" "^3.0.0" + global-agent "^3.0.0" "@electron/universal@1.2.1": - "integrity" "sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ==" - "resolved" "https://registry.npmjs.org/@electron/universal/-/universal-1.2.1.tgz" - "version" "1.2.1" + version "1.2.1" + resolved "https://registry.npmjs.org/@electron/universal/-/universal-1.2.1.tgz" dependencies: "@malept/cross-spawn-promise" "^1.1.0" - "asar" "^3.1.0" - "debug" "^4.3.1" - "dir-compare" "^2.4.0" - "fs-extra" "^9.0.1" - "minimatch" "^3.0.4" - "plist" "^3.0.4" + asar "^3.1.0" + debug "^4.3.1" + dir-compare "^2.4.0" + fs-extra "^9.0.1" + minimatch "^3.0.4" + plist "^3.0.4" "@malept/cross-spawn-promise@^1.1.0": - "integrity" "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==" - "resolved" "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz" - "version" "1.1.1" + version "1.1.1" + resolved "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz" dependencies: - "cross-spawn" "^7.0.1" + cross-spawn "^7.0.1" "@malept/flatpak-bundler@^0.4.0": - "integrity" "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==" - "resolved" "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz" - "version" "0.4.0" + version "0.4.0" + resolved "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz" dependencies: - "debug" "^4.1.1" - "fs-extra" "^9.0.0" - "lodash" "^4.17.15" - "tmp-promise" "^3.0.2" + debug "^4.1.1" + fs-extra "^9.0.0" + lodash "^4.17.15" + tmp-promise "^3.0.2" "@sindresorhus/is@^4.0.0": - "integrity" "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" - "resolved" "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz" - "version" "4.6.0" + version "4.6.0" + resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz" "@szmarczak/http-timer@^4.0.5": - "integrity" "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==" - "resolved" "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz" - "version" "4.0.6" + version "4.0.6" + resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz" dependencies: - "defer-to-connect" "^2.0.0" + defer-to-connect "^2.0.0" "@tootallnate/once@2": - "integrity" "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" - "resolved" "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" - "version" "2.0.0" + version "2.0.0" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" "@types/cacheable-request@^6.0.1": - "integrity" "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==" - "resolved" "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz" - "version" "6.0.3" + version "6.0.3" + resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz" dependencies: "@types/http-cache-semantics" "*" "@types/keyv" "^3.1.4" @@ -92,2064 +86,1739 @@ "@types/responselike" "^1.0.0" "@types/debug@^4.1.6": - "integrity" "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==" - "resolved" "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz" - "version" "4.1.7" + version "4.1.7" + resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz" dependencies: "@types/ms" "*" "@types/fs-extra@^9.0.11": - "integrity" "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==" - "resolved" "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz" - "version" "9.0.13" + version "9.0.13" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz" dependencies: "@types/node" "*" "@types/glob@^7.1.1": - "integrity" "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==" - "resolved" "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" - "version" "7.2.0" + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" dependencies: "@types/minimatch" "*" "@types/node" "*" "@types/http-cache-semantics@*": - "integrity" "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" - "resolved" "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz" - "version" "4.0.1" + version "4.0.1" + resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz" "@types/keyv@^3.1.4": - "integrity" "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==" - "resolved" "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz" - "version" "3.1.4" + version "3.1.4" + resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz" dependencies: "@types/node" "*" "@types/minimatch@*": - "integrity" "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" - "resolved" "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" - "version" "5.1.2" + version "5.1.2" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" "@types/ms@*": - "integrity" "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" - "resolved" "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz" - "version" "0.7.31" + version "0.7.31" + resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz" "@types/node@*", "@types/node@^16.11.26": - "integrity" "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==" - "resolved" "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz" - "version" "16.18.11" + version "16.18.11" + resolved "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz" "@types/plist@^3.0.1": - "integrity" "sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==" - "resolved" "https://registry.npmjs.org/@types/plist/-/plist-3.0.2.tgz" - "version" "3.0.2" + version "3.0.2" + resolved "https://registry.npmjs.org/@types/plist/-/plist-3.0.2.tgz" dependencies: "@types/node" "*" - "xmlbuilder" ">=11.0.1" + xmlbuilder ">=11.0.1" "@types/responselike@^1.0.0": - "integrity" "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==" - "resolved" "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz" - "version" "1.0.0" + version "1.0.0" + resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz" dependencies: "@types/node" "*" "@types/verror@^1.10.3": - "integrity" "sha512-NNm+gdePAX1VGvPcGZCDKQZKYSiAWigKhKaz5KF94hG6f2s8de9Ow5+7AbXoeKxL8gavZfk4UquSAygOF2duEQ==" - "resolved" "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz" - "version" "1.10.6" + version "1.10.6" + resolved "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz" "@types/yargs-parser@*": - "integrity" "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - "resolved" "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" - "version" "21.0.0" + version "21.0.0" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" "@types/yargs@^17.0.1": - "integrity" "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==" - "resolved" "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz" - "version" "17.0.22" + version "17.0.22" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz" dependencies: "@types/yargs-parser" "*" "@types/yauzl@^2.9.1": - "integrity" "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==" - "resolved" "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz" - "version" "2.9.1" + version "2.9.1" + resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz" dependencies: "@types/node" "*" -"7zip-bin@~5.1.1": - "integrity" "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==" - "resolved" "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz" - "version" "5.1.1" - -"agent-base@6": - "integrity" "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==" - "resolved" "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" - "version" "6.0.2" - dependencies: - "debug" "4" - -"ajv-keywords@^3.4.1": - "integrity" "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - "resolved" "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" - "version" "3.5.2" - -"ajv@^6.10.0", "ajv@^6.12.0", "ajv@^6.12.3", "ajv@^6.9.1": - "integrity" "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==" - "resolved" "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - "version" "6.12.6" - dependencies: - "fast-deep-equal" "^3.1.1" - "fast-json-stable-stringify" "^2.0.0" - "json-schema-traverse" "^0.4.1" - "uri-js" "^4.2.2" - -"ansi-regex@^5.0.1": - "integrity" "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" - "version" "5.0.1" - -"ansi-styles@^4.0.0", "ansi-styles@^4.1.0": - "integrity" "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" - "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - "version" "4.3.0" - dependencies: - "color-convert" "^2.0.1" - -"any-promise@^1.0.0": - "integrity" "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - "resolved" "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" - "version" "1.3.0" - -"app-builder-bin@4.0.0": - "integrity" "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==" - "resolved" "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz" - "version" "4.0.0" - -"app-builder-lib@23.6.0": - "integrity" "sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA==" - "resolved" "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.6.0.tgz" - "version" "23.6.0" +agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + dependencies: + debug "4" + +ajv-keywords@^3.4.1: + version "3.5.2" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + +ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + dependencies: + color-convert "^2.0.1" + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + +app-builder-bin@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz" + +app-builder-lib@23.6.0: + version "23.6.0" + resolved "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.6.0.tgz" dependencies: + "7zip-bin" "~5.1.1" "@develar/schema-utils" "~2.6.5" "@electron/universal" "1.2.1" "@malept/flatpak-bundler" "^0.4.0" - "7zip-bin" "~5.1.1" - "async-exit-hook" "^2.0.1" - "bluebird-lst" "^1.0.9" - "builder-util" "23.6.0" - "builder-util-runtime" "9.1.1" - "chromium-pickle-js" "^0.2.0" - "debug" "^4.3.4" - "ejs" "^3.1.7" - "electron-osx-sign" "^0.6.0" - "electron-publish" "23.6.0" - "form-data" "^4.0.0" - "fs-extra" "^10.1.0" - "hosted-git-info" "^4.1.0" - "is-ci" "^3.0.0" - "isbinaryfile" "^4.0.10" - "js-yaml" "^4.1.0" - "lazy-val" "^1.0.5" - "minimatch" "^3.1.2" - "read-config-file" "6.2.0" - "sanitize-filename" "^1.6.3" - "semver" "^7.3.7" - "tar" "^6.1.11" - "temp-file" "^3.4.0" - -"argparse@^2.0.1": - "integrity" "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - "resolved" "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" - "version" "2.0.1" - -"asar@^3.1.0": - "integrity" "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==" - "resolved" "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz" - "version" "3.2.0" - dependencies: - "chromium-pickle-js" "^0.2.0" - "commander" "^5.0.0" - "glob" "^7.1.6" - "minimatch" "^3.0.4" + async-exit-hook "^2.0.1" + bluebird-lst "^1.0.9" + builder-util "23.6.0" + builder-util-runtime "9.1.1" + chromium-pickle-js "^0.2.0" + debug "^4.3.4" + ejs "^3.1.7" + electron-osx-sign "^0.6.0" + electron-publish "23.6.0" + form-data "^4.0.0" + fs-extra "^10.1.0" + hosted-git-info "^4.1.0" + is-ci "^3.0.0" + isbinaryfile "^4.0.10" + js-yaml "^4.1.0" + lazy-val "^1.0.5" + minimatch "^3.1.2" + read-config-file "6.2.0" + sanitize-filename "^1.6.3" + semver "^7.3.7" + tar "^6.1.11" + temp-file "^3.4.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + +asar@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz" + dependencies: + chromium-pickle-js "^0.2.0" + commander "^5.0.0" + glob "^7.1.6" + minimatch "^3.0.4" optionalDependencies: "@types/glob" "^7.1.1" -"asn1@~0.2.3": - "integrity" "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==" - "resolved" "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz" - "version" "0.2.4" - dependencies: - "safer-buffer" "~2.1.0" - -"assert-plus@^1.0.0", "assert-plus@1.0.0": - "integrity" "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - "resolved" "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - "version" "1.0.0" - -"astral-regex@^2.0.0": - "integrity" "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" - "resolved" "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" - "version" "2.0.0" - -"async-exit-hook@^2.0.1": - "integrity" "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==" - "resolved" "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz" - "version" "2.0.1" - -"async@^3.1.0", "async@^3.2.3": - "integrity" "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" - "resolved" "https://registry.npmjs.org/async/-/async-3.2.3.tgz" - "version" "3.2.3" - -"asynckit@^0.4.0": - "integrity" "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - "resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - "version" "0.4.0" - -"at-least-node@^1.0.0": - "integrity" "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" - "resolved" "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" - "version" "1.0.0" - -"aws-sign2@~0.7.0": - "integrity" "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - "resolved" "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" - "version" "0.7.0" - -"aws4@^1.8.0": - "integrity" "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" - "resolved" "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz" - "version" "1.11.0" - -"balanced-match@^1.0.0": - "integrity" "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - "version" "1.0.2" - -"base64-js@^1.3.1", "base64-js@^1.5.1": - "integrity" "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - "resolved" "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" - "version" "1.5.1" - -"bcrypt-pbkdf@^1.0.0": - "integrity" "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=" - "resolved" "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" - "version" "1.0.2" - dependencies: - "tweetnacl" "^0.14.3" - -"bluebird-lst@^1.0.9": - "integrity" "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==" - "resolved" "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz" - "version" "1.0.9" - dependencies: - "bluebird" "^3.5.5" - -"bluebird@^3.5.0", "bluebird@^3.5.5": - "integrity" "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - "resolved" "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" - "version" "3.7.2" - -"boolean@^3.0.1": - "integrity" "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" - "resolved" "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz" - "version" "3.2.0" - -"brace-expansion@^1.1.7": - "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" - "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - "version" "1.1.11" - dependencies: - "balanced-match" "^1.0.0" - "concat-map" "0.0.1" - -"brace-expansion@^2.0.1": - "integrity" "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==" - "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" - "version" "2.0.1" - dependencies: - "balanced-match" "^1.0.0" - -"buffer-alloc-unsafe@^1.1.0": - "integrity" "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" - "resolved" "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz" - "version" "1.1.0" - -"buffer-alloc@^1.2.0": - "integrity" "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==" - "resolved" "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz" - "version" "1.2.0" - dependencies: - "buffer-alloc-unsafe" "^1.1.0" - "buffer-fill" "^1.0.0" - -"buffer-crc32@~0.2.3": - "integrity" "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - "resolved" "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" - "version" "0.2.13" - -"buffer-equal@1.0.0": - "integrity" "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==" - "resolved" "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz" - "version" "1.0.0" - -"buffer-fill@^1.0.0": - "integrity" "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" - "resolved" "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz" - "version" "1.0.0" - -"buffer-from@^1.0.0": - "integrity" "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" - "version" "1.1.2" - -"buffer@^5.1.0": - "integrity" "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==" - "resolved" "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" - "version" "5.7.1" - dependencies: - "base64-js" "^1.3.1" - "ieee754" "^1.1.13" - -"builder-util-runtime@9.1.1": - "integrity" "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==" - "resolved" "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz" - "version" "9.1.1" - dependencies: - "debug" "^4.3.4" - "sax" "^1.2.4" - -"builder-util@23.6.0": - "integrity" "sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ==" - "resolved" "https://registry.npmjs.org/builder-util/-/builder-util-23.6.0.tgz" - "version" "23.6.0" +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz" + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" + +async-exit-hook@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz" + +async@^3.1.0, async@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/async/-/async-3.2.3.tgz" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + +base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" + dependencies: + tweetnacl "^0.14.3" + +bluebird-lst@^1.0.9: + version "1.0.9" + resolved "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz" + dependencies: + bluebird "^3.5.5" + +bluebird@^3.5.0, bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" + +boolean@^3.0.1: + version "3.2.0" + resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + dependencies: + balanced-match "^1.0.0" + +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz" + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz" + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" + +buffer-equal@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz" + +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + +buffer@^5.1.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +builder-util-runtime@9.1.1: + version "9.1.1" + resolved "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz" + dependencies: + debug "^4.3.4" + sax "^1.2.4" + +builder-util@23.6.0: + version "23.6.0" + resolved "https://registry.npmjs.org/builder-util/-/builder-util-23.6.0.tgz" + dependencies: + "7zip-bin" "~5.1.1" "@types/debug" "^4.1.6" "@types/fs-extra" "^9.0.11" - "7zip-bin" "~5.1.1" - "app-builder-bin" "4.0.0" - "bluebird-lst" "^1.0.9" - "builder-util-runtime" "9.1.1" - "chalk" "^4.1.1" - "cross-spawn" "^7.0.3" - "debug" "^4.3.4" - "fs-extra" "^10.0.0" - "http-proxy-agent" "^5.0.0" - "https-proxy-agent" "^5.0.0" - "is-ci" "^3.0.0" - "js-yaml" "^4.1.0" - "source-map-support" "^0.5.19" - "stat-mode" "^1.0.0" - "temp-file" "^3.4.0" - -"cacheable-lookup@^5.0.3": - "integrity" "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" - "resolved" "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz" - "version" "5.0.4" - -"cacheable-request@^7.0.2": - "integrity" "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==" - "resolved" "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz" - "version" "7.0.2" - dependencies: - "clone-response" "^1.0.2" - "get-stream" "^5.1.0" - "http-cache-semantics" "^4.0.0" - "keyv" "^4.0.0" - "lowercase-keys" "^2.0.0" - "normalize-url" "^6.0.1" - "responselike" "^2.0.0" - -"caseless@~0.12.0": - "integrity" "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - "resolved" "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" - "version" "0.12.0" - -"chalk@^4.0.2", "chalk@^4.1.1": - "integrity" "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==" - "resolved" "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - "version" "4.1.2" - dependencies: - "ansi-styles" "^4.1.0" - "supports-color" "^7.1.0" - -"chownr@^2.0.0": - "integrity" "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" - "resolved" "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" - "version" "2.0.0" - -"chromium-pickle-js@^0.2.0": - "integrity" "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==" - "resolved" "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz" - "version" "0.2.0" - -"ci-info@^3.2.0": - "integrity" "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==" - "resolved" "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz" - "version" "3.7.1" - -"cli-truncate@^2.0.0", "cli-truncate@^2.1.0": - "integrity" "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==" - "resolved" "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz" - "version" "2.1.0" - dependencies: - "slice-ansi" "^3.0.0" - "string-width" "^4.2.0" - -"cliui@^8.0.1": - "integrity" "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==" - "resolved" "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" - "version" "8.0.1" - dependencies: - "string-width" "^4.2.0" - "strip-ansi" "^6.0.1" - "wrap-ansi" "^7.0.0" - -"clone-response@^1.0.2": - "integrity" "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=" - "resolved" "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz" - "version" "1.0.2" - dependencies: - "mimic-response" "^1.0.0" - -"color-convert@^1.9.1": - "integrity" "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" - "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" - "version" "1.9.3" - dependencies: - "color-name" "1.1.3" - -"color-convert@^2.0.1": - "integrity" "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" - "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - "version" "2.0.1" - dependencies: - "color-name" "~1.1.4" - -"color-name@^1.0.0", "color-name@~1.1.4": - "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - "version" "1.1.4" - -"color-name@1.1.3": - "integrity" "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - "version" "1.1.3" - -"color-string@^1.5.2": - "integrity" "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==" - "resolved" "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz" - "version" "1.6.0" - dependencies: - "color-name" "^1.0.0" - "simple-swizzle" "^0.2.2" - -"color@3.0.x": - "integrity" "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==" - "resolved" "https://registry.npmjs.org/color/-/color-3.0.0.tgz" - "version" "3.0.0" - dependencies: - "color-convert" "^1.9.1" - "color-string" "^1.5.2" - -"colors@^1.2.1": - "integrity" "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" - "resolved" "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" - "version" "1.4.0" - -"colors@1.0.3": - "integrity" "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==" - "resolved" "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" - "version" "1.0.3" - -"colorspace@1.1.x": - "integrity" "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==" - "resolved" "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz" - "version" "1.1.2" - dependencies: - "color" "3.0.x" - "text-hex" "1.0.x" - -"combined-stream@^1.0.6", "combined-stream@^1.0.8", "combined-stream@~1.0.6": - "integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==" - "resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" - "version" "1.0.8" - dependencies: - "delayed-stream" "~1.0.0" - -"commander@^5.0.0": - "integrity" "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" - "resolved" "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" - "version" "5.1.0" - -"commander@2.9.0": - "integrity" "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==" - "resolved" "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" - "version" "2.9.0" - dependencies: - "graceful-readlink" ">= 1.0.0" - -"compare-version@^0.1.2": - "integrity" "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==" - "resolved" "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz" - "version" "0.1.2" - -"concat-map@0.0.1": - "integrity" "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - "version" "0.0.1" - -"core-util-is@~1.0.0", "core-util-is@1.0.2": - "integrity" "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - "resolved" "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - "version" "1.0.2" - -"crc@^3.8.0": - "integrity" "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==" - "resolved" "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz" - "version" "3.8.0" - dependencies: - "buffer" "^5.1.0" - -"cross-spawn@^7.0.1", "cross-spawn@^7.0.3": - "integrity" "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==" - "resolved" "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - "version" "7.0.3" - dependencies: - "path-key" "^3.1.0" - "shebang-command" "^2.0.0" - "which" "^2.0.1" - -"dashdash@^1.12.0": - "integrity" "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=" - "resolved" "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" - "version" "1.14.1" - dependencies: - "assert-plus" "^1.0.0" - -"debug@^2.6.8": - "integrity" "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" - "resolved" "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - "version" "2.6.9" - dependencies: - "ms" "2.0.0" - -"debug@^4.1.0", "debug@^4.1.1", "debug@^4.3.1", "debug@^4.3.4", "debug@4": - "integrity" "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" - "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - "version" "4.3.4" - dependencies: - "ms" "2.1.2" - -"decompress-response@^6.0.0": - "integrity" "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==" - "resolved" "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" - "version" "6.0.0" - dependencies: - "mimic-response" "^3.1.0" - -"defer-to-connect@^2.0.0": - "integrity" "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" - "resolved" "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz" - "version" "2.0.1" - -"define-properties@^1.1.3": - "integrity" "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==" - "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" - "version" "1.1.4" - dependencies: - "has-property-descriptors" "^1.0.0" - "object-keys" "^1.1.1" - -"delayed-stream@~1.0.0": - "integrity" "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - "resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - "version" "1.0.0" - -"detect-node@^2.0.4": - "integrity" "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - "resolved" "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" - "version" "2.1.0" - -"dir-compare@^2.4.0": - "integrity" "sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==" - "resolved" "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz" - "version" "2.4.0" - dependencies: - "buffer-equal" "1.0.0" - "colors" "1.0.3" - "commander" "2.9.0" - "minimatch" "3.0.4" - -"dmg-builder@23.6.0": - "integrity" "sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA==" - "resolved" "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.6.0.tgz" - "version" "23.6.0" - dependencies: - "app-builder-lib" "23.6.0" - "builder-util" "23.6.0" - "builder-util-runtime" "9.1.1" - "fs-extra" "^10.0.0" - "iconv-lite" "^0.6.2" - "js-yaml" "^4.1.0" + app-builder-bin "4.0.0" + bluebird-lst "^1.0.9" + builder-util-runtime "9.1.1" + chalk "^4.1.1" + cross-spawn "^7.0.3" + debug "^4.3.4" + fs-extra "^10.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-ci "^3.0.0" + js-yaml "^4.1.0" + source-map-support "^0.5.19" + stat-mode "^1.0.0" + temp-file "^3.4.0" + +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz" + +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz" + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" + +chalk@^4.0.2, chalk@^4.1.1: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" + +chromium-pickle-js@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz" + +ci-info@^3.2.0: + version "3.7.1" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz" + +cli-truncate@^2.0.0, cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz" + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz" + dependencies: + mimic-response "^1.0.0" + +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + +color-string@^1.5.2: + version "1.6.0" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz" + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@3.0.x: + version "3.0.0" + resolved "https://registry.npmjs.org/color/-/color-3.0.0.tgz" + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colors@1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" + +colors@^1.2.1: + version "1.4.0" + resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" + +colorspace@1.1.x: + version "1.1.2" + resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz" + dependencies: + color "3.0.x" + text-hex "1.0.x" + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + dependencies: + delayed-stream "~1.0.0" + +commander@2.9.0: + version "2.9.0" + resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + dependencies: + graceful-readlink ">= 1.0.0" + +commander@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" + +compare-version@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + +crc@^3.8.0: + version "3.8.0" + resolved "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz" + dependencies: + buffer "^5.1.0" + +cross-spawn@^7.0.1, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" + dependencies: + assert-plus "^1.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + dependencies: + ms "2.1.2" + +debug@^2.6.8: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + dependencies: + ms "2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" + dependencies: + mimic-response "^3.1.0" + +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz" + +define-properties@^1.1.3: + version "1.1.4" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz" + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" + +dir-compare@^2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz" + dependencies: + buffer-equal "1.0.0" + colors "1.0.3" + commander "2.9.0" + minimatch "3.0.4" + +dmg-builder@23.6.0: + version "23.6.0" + resolved "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.6.0.tgz" + dependencies: + app-builder-lib "23.6.0" + builder-util "23.6.0" + builder-util-runtime "9.1.1" + fs-extra "^10.0.0" + iconv-lite "^0.6.2" + js-yaml "^4.1.0" optionalDependencies: - "dmg-license" "^1.0.11" + dmg-license "^1.0.11" -"dmg-license@^1.0.11": - "integrity" "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==" - "resolved" "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz" - "version" "1.0.11" +dmg-license@^1.0.11: + version "1.0.11" + resolved "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz" dependencies: "@types/plist" "^3.0.1" "@types/verror" "^1.10.3" - "ajv" "^6.10.0" - "crc" "^3.8.0" - "iconv-corefoundation" "^1.1.7" - "plist" "^3.0.4" - "smart-buffer" "^4.0.2" - "verror" "^1.10.0" - -"dotenv-expand@^5.1.0": - "integrity" "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" - "resolved" "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz" - "version" "5.1.0" - -"dotenv@^9.0.2": - "integrity" "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==" - "resolved" "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz" - "version" "9.0.2" - -"ecc-jsbn@~0.1.1": - "integrity" "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=" - "resolved" "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" - "version" "0.1.2" - dependencies: - "jsbn" "~0.1.0" - "safer-buffer" "^2.1.0" - -"ejs@^3.1.7": - "integrity" "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==" - "resolved" "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz" - "version" "3.1.8" - dependencies: - "jake" "^10.8.5" - -"electron-builder@^23.3.1": - "integrity" "sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw==" - "resolved" "https://registry.npmjs.org/electron-builder/-/electron-builder-23.6.0.tgz" - "version" "23.6.0" + ajv "^6.10.0" + crc "^3.8.0" + iconv-corefoundation "^1.1.7" + plist "^3.0.4" + smart-buffer "^4.0.2" + verror "^1.10.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz" + +dotenv@^9.0.2: + version "9.0.2" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ejs@^3.1.7: + version "3.1.8" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz" + dependencies: + jake "^10.8.5" + +electron-builder@^23.3.1: + version "23.6.0" + resolved "https://registry.npmjs.org/electron-builder/-/electron-builder-23.6.0.tgz" dependencies: "@types/yargs" "^17.0.1" - "app-builder-lib" "23.6.0" - "builder-util" "23.6.0" - "builder-util-runtime" "9.1.1" - "chalk" "^4.1.1" - "dmg-builder" "23.6.0" - "fs-extra" "^10.0.0" - "is-ci" "^3.0.0" - "lazy-val" "^1.0.5" - "read-config-file" "6.2.0" - "simple-update-notifier" "^1.0.7" - "yargs" "^17.5.1" - -"electron-context-menu@^2.3.0": - "integrity" "sha512-XYsYkNY+jvX4C5o09qMuZoKL6e9frnQzBFehZSIiKp6zK0u3XYowJYDyK3vDKKZxYsOIGiE/Gbx40jERC03Ctw==" - "resolved" "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.3.0.tgz" - "version" "2.3.0" - dependencies: - "cli-truncate" "^2.0.0" - "electron-dl" "^3.0.0" - "electron-is-dev" "^1.0.1" - -"electron-default-menu@^1.0.2": - "integrity" "sha512-YAL/UNR3kPG58wOOlmDpTG3i6+bzwhHx6NllIOaLuVrU7uYifeYGGdk5IH2Hap4wVEx2YTA8cqQ2PGSplYwDWQ==" - "resolved" "https://registry.npmjs.org/electron-default-menu/-/electron-default-menu-1.0.2.tgz" - "version" "1.0.2" - -"electron-dl@^3.0.0": - "integrity" "sha512-pRgE9Jbhoo5z6Vk3qi+vIrfpMDlCp2oB1UeR96SMnsfz073jj0AZGQwp69EdIcEvlUlwBSGyJK8Jt6OB6JLn+g==" - "resolved" "https://registry.npmjs.org/electron-dl/-/electron-dl-3.0.2.tgz" - "version" "3.0.2" - dependencies: - "ext-name" "^5.0.0" - "pupa" "^2.0.1" - "unused-filename" "^2.1.0" - -"electron-is-dev@^1.0.1": - "integrity" "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" - "resolved" "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz" - "version" "1.2.0" - -"electron-osx-sign@^0.6.0": - "integrity" "sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==" - "resolved" "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz" - "version" "0.6.0" - dependencies: - "bluebird" "^3.5.0" - "compare-version" "^0.1.2" - "debug" "^2.6.8" - "isbinaryfile" "^3.0.2" - "minimist" "^1.2.0" - "plist" "^3.0.1" - -"electron-progressbar@^2.0.1": - "integrity" "sha512-+N60GX2q+KH5OvZXxwtjMTZB/1AyxriFd95vOnR3sOfNpvz+30LMsM0a9SnEivZE6N8Djy7F3z4TY8pLs8aopw==" - "resolved" "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.0.1.tgz" - "version" "2.0.1" - dependencies: - "extend" "^3.0.1" - -"electron-publish@23.6.0": - "integrity" "sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg==" - "resolved" "https://registry.npmjs.org/electron-publish/-/electron-publish-23.6.0.tgz" - "version" "23.6.0" + app-builder-lib "23.6.0" + builder-util "23.6.0" + builder-util-runtime "9.1.1" + chalk "^4.1.1" + dmg-builder "23.6.0" + fs-extra "^10.0.0" + is-ci "^3.0.0" + lazy-val "^1.0.5" + read-config-file "6.2.0" + simple-update-notifier "^1.0.7" + yargs "^17.5.1" + +electron-context-menu@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.3.0.tgz" + dependencies: + cli-truncate "^2.0.0" + electron-dl "^3.0.0" + electron-is-dev "^1.0.1" + +electron-default-menu@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/electron-default-menu/-/electron-default-menu-1.0.2.tgz" + +electron-dl@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/electron-dl/-/electron-dl-3.0.2.tgz" + dependencies: + ext-name "^5.0.0" + pupa "^2.0.1" + unused-filename "^2.1.0" + +electron-is-dev@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz" + +electron-osx-sign@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz" + dependencies: + bluebird "^3.5.0" + compare-version "^0.1.2" + debug "^2.6.8" + isbinaryfile "^3.0.2" + minimist "^1.2.0" + plist "^3.0.1" + +electron-progressbar@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/electron-progressbar/-/electron-progressbar-2.0.1.tgz" + dependencies: + extend "^3.0.1" + +electron-publish@23.6.0: + version "23.6.0" + resolved "https://registry.npmjs.org/electron-publish/-/electron-publish-23.6.0.tgz" dependencies: "@types/fs-extra" "^9.0.11" - "builder-util" "23.6.0" - "builder-util-runtime" "9.1.1" - "chalk" "^4.1.1" - "fs-extra" "^10.0.0" - "lazy-val" "^1.0.5" - "mime" "^2.5.2" - -"electron@^22.1.0": - "integrity" "sha512-wz5s4N6V7zyKm4YQmXJImFoxO1Doai32ShYm0FzOLPBMwLMdQBV+REY+j1opRx0KJ9xJEIdjYgcA8OSw6vx3pA==" - "resolved" "https://registry.npmjs.org/electron/-/electron-22.1.0.tgz" - "version" "22.1.0" + builder-util "23.6.0" + builder-util-runtime "9.1.1" + chalk "^4.1.1" + fs-extra "^10.0.0" + lazy-val "^1.0.5" + mime "^2.5.2" + +electron@^22.1.0: + version "22.3.24" + resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.24.tgz#14479cf11cf4709f78d324015429fa82492c2150" dependencies: "@electron/get" "^2.0.0" "@types/node" "^16.11.26" - "extract-zip" "^2.0.1" - -"emoji-regex@^8.0.0": - "integrity" "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - "version" "8.0.0" - -"enabled@2.0.x": - "integrity" "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - "resolved" "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz" - "version" "2.0.0" - -"end-of-stream@^1.1.0": - "integrity" "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==" - "resolved" "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" - "version" "1.4.4" - dependencies: - "once" "^1.4.0" - -"env-paths@^2.2.0": - "integrity" "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" - "resolved" "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" - "version" "2.2.1" - -"es6-error@^4.1.1": - "integrity" "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" - "resolved" "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz" - "version" "4.1.1" - -"escalade@^3.1.1": - "integrity" "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - "resolved" "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - "version" "3.1.1" - -"escape-goat@^2.0.0": - "integrity" "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" - "resolved" "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz" - "version" "2.1.1" - -"escape-string-regexp@^4.0.0": - "integrity" "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" - "version" "4.0.0" - -"ext-list@^2.0.0": - "integrity" "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==" - "resolved" "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz" - "version" "2.2.2" - dependencies: - "mime-db" "^1.28.0" - -"ext-name@^5.0.0": - "integrity" "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==" - "resolved" "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz" - "version" "5.0.0" - dependencies: - "ext-list" "^2.0.0" - "sort-keys-length" "^1.0.0" - -"extend@^3.0.1", "extend@~3.0.2": - "integrity" "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - "resolved" "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" - "version" "3.0.2" - -"extract-zip@^2.0.1": - "integrity" "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==" - "resolved" "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" - "version" "2.0.1" - dependencies: - "debug" "^4.1.1" - "get-stream" "^5.1.0" - "yauzl" "^2.10.0" + extract-zip "^2.0.1" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz" + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" + dependencies: + once "^1.4.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" + +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + +ext-list@^2.0.0: + version "2.2.2" + resolved "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz" + dependencies: + mime-db "^1.28.0" + +ext-name@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz" + dependencies: + ext-list "^2.0.0" + sort-keys-length "^1.0.0" + +extend@^3.0.1, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" optionalDependencies: "@types/yauzl" "^2.9.1" -"extsprintf@^1.2.0", "extsprintf@1.3.0": - "integrity" "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - "resolved" "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" - "version" "1.3.0" - -"fast-deep-equal@^3.1.1": - "integrity" "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - "resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" - "version" "3.1.3" - -"fast-json-stable-stringify@^2.0.0": - "integrity" "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - "resolved" "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" - "version" "2.1.0" - -"fd-slicer@~1.1.0": - "integrity" "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=" - "resolved" "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz" - "version" "1.1.0" - dependencies: - "pend" "~1.2.0" - -"fecha@^4.2.0": - "integrity" "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" - "resolved" "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz" - "version" "4.2.1" - -"filelist@^1.0.1": - "integrity" "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==" - "resolved" "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" - "version" "1.0.4" - dependencies: - "minimatch" "^5.0.1" - -"fn.name@1.x.x": - "integrity" "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - "resolved" "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" - "version" "1.1.0" - -"forever-agent@~0.6.1": - "integrity" "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - "resolved" "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - "version" "0.6.1" - -"form-data@^4.0.0": - "integrity" "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==" - "resolved" "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" - "version" "4.0.0" - dependencies: - "asynckit" "^0.4.0" - "combined-stream" "^1.0.8" - "mime-types" "^2.1.12" - -"form-data@~2.3.2": - "integrity" "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==" - "resolved" "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" - "version" "2.3.3" - dependencies: - "asynckit" "^0.4.0" - "combined-stream" "^1.0.6" - "mime-types" "^2.1.12" - -"fs-extra@^10.0.0": - "integrity" "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==" - "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" - "version" "10.1.0" - dependencies: - "graceful-fs" "^4.2.0" - "jsonfile" "^6.0.1" - "universalify" "^2.0.0" - -"fs-extra@^10.1.0": - "integrity" "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==" - "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" - "version" "10.1.0" - dependencies: - "graceful-fs" "^4.2.0" - "jsonfile" "^6.0.1" - "universalify" "^2.0.0" - -"fs-extra@^8.1.0": - "integrity" "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==" - "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" - "version" "8.1.0" - dependencies: - "graceful-fs" "^4.2.0" - "jsonfile" "^4.0.0" - "universalify" "^0.1.0" - -"fs-extra@^9.0.0": - "integrity" "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==" - "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" - "version" "9.1.0" - dependencies: - "at-least-node" "^1.0.0" - "graceful-fs" "^4.2.0" - "jsonfile" "^6.0.1" - "universalify" "^2.0.0" - -"fs-extra@^9.0.1": - "integrity" "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==" - "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" - "version" "9.1.0" - dependencies: - "at-least-node" "^1.0.0" - "graceful-fs" "^4.2.0" - "jsonfile" "^6.0.1" - "universalify" "^2.0.0" - -"fs-minipass@^2.0.0": - "integrity" "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==" - "resolved" "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" - "version" "2.1.0" - dependencies: - "minipass" "^3.0.0" - -"fs.realpath@^1.0.0": - "integrity" "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - "version" "1.0.0" - -"function-bind@^1.1.1": - "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" - "version" "1.1.1" - -"get-caller-file@^2.0.5": - "integrity" "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - "resolved" "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - "version" "2.0.5" - -"get-intrinsic@^1.1.1": - "integrity" "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==" - "resolved" "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz" - "version" "1.2.0" - dependencies: - "function-bind" "^1.1.1" - "has" "^1.0.3" - "has-symbols" "^1.0.3" - -"get-stream@^5.1.0": - "integrity" "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==" - "resolved" "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" - "version" "5.2.0" - dependencies: - "pump" "^3.0.0" - -"getpass@^0.1.1": - "integrity" "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=" - "resolved" "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" - "version" "0.1.7" - dependencies: - "assert-plus" "^1.0.0" - -"glob@^7.1.3", "glob@^7.1.6": - "integrity" "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==" - "resolved" "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" - "version" "7.2.3" - dependencies: - "fs.realpath" "^1.0.0" - "inflight" "^1.0.4" - "inherits" "2" - "minimatch" "^3.1.1" - "once" "^1.3.0" - "path-is-absolute" "^1.0.0" - -"global-agent@^3.0.0": - "integrity" "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==" - "resolved" "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz" - "version" "3.0.0" - dependencies: - "boolean" "^3.0.1" - "es6-error" "^4.1.1" - "matcher" "^3.0.0" - "roarr" "^2.15.3" - "semver" "^7.3.2" - "serialize-error" "^7.0.1" - -"globalthis@^1.0.1": - "integrity" "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==" - "resolved" "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" - "version" "1.0.3" - dependencies: - "define-properties" "^1.1.3" - -"got@^11.8.5": - "integrity" "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==" - "resolved" "https://registry.npmjs.org/got/-/got-11.8.6.tgz" - "version" "11.8.6" +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz" + dependencies: + pend "~1.2.0" + +fecha@^4.2.0: + version "4.2.1" + resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz" + +filelist@^1.0.1: + version "1.0.4" + resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" + dependencies: + minimatch "^5.0.1" + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fs-extra@^10.0.0, fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.0, fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + +get-intrinsic@^1.1.1: + version "1.2.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz" + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" + dependencies: + pump "^3.0.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.3, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-agent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz" + dependencies: + boolean "^3.0.1" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" + +globalthis@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" + dependencies: + define-properties "^1.1.3" + +got@^11.8.5: + version "11.8.6" + resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz" dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" - "cacheable-lookup" "^5.0.3" - "cacheable-request" "^7.0.2" - "decompress-response" "^6.0.0" - "http2-wrapper" "^1.0.0-beta.5.2" - "lowercase-keys" "^2.0.0" - "p-cancelable" "^2.0.0" - "responselike" "^2.0.0" - -"graceful-fs@^4.1.6", "graceful-fs@^4.2.0": - "integrity" "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - "resolved" "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz" - "version" "4.2.4" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.4" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz" "graceful-readlink@>= 1.0.0": - "integrity" "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==" - "resolved" "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - "version" "1.0.1" + version "1.0.1" + resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" -"har-schema@^2.0.0": - "integrity" "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - "resolved" "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" - "version" "2.0.0" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" -"har-validator@~5.1.3": - "integrity" "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==" - "resolved" "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz" - "version" "5.1.5" +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz" dependencies: - "ajv" "^6.12.3" - "har-schema" "^2.0.0" + ajv "^6.12.3" + har-schema "^2.0.0" -"has-flag@^4.0.0": - "integrity" "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - "version" "4.0.0" +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" -"has-property-descriptors@^1.0.0": - "integrity" "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==" - "resolved" "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" - "version" "1.0.0" +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" dependencies: - "get-intrinsic" "^1.1.1" + get-intrinsic "^1.1.1" -"has-symbols@^1.0.3": - "integrity" "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" - "version" "1.0.3" +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" -"has@^1.0.3": - "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" - "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" - "version" "1.0.3" +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" dependencies: - "function-bind" "^1.1.1" + function-bind "^1.1.1" -"hosted-git-info@^4.1.0": - "integrity" "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==" - "resolved" "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz" - "version" "4.1.0" +hosted-git-info@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz" dependencies: - "lru-cache" "^6.0.0" + lru-cache "^6.0.0" -"http-cache-semantics@^4.0.0": - "integrity" "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - "resolved" "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" - "version" "4.1.1" +http-cache-semantics@^4.0.0: + version "4.1.1" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" -"http-proxy-agent@^5.0.0": - "integrity" "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==" - "resolved" "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" - "version" "5.0.0" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" dependencies: "@tootallnate/once" "2" - "agent-base" "6" - "debug" "4" - -"http-signature@~1.2.0": - "integrity" "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=" - "resolved" "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" - "version" "1.2.0" - dependencies: - "assert-plus" "^1.0.0" - "jsprim" "^1.2.2" - "sshpk" "^1.7.0" - -"http2-wrapper@^1.0.0-beta.5.2": - "integrity" "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==" - "resolved" "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz" - "version" "1.0.3" - dependencies: - "quick-lru" "^5.1.1" - "resolve-alpn" "^1.0.0" - -"https-proxy-agent@^5.0.0": - "integrity" "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==" - "resolved" "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" - "version" "5.0.1" - dependencies: - "agent-base" "6" - "debug" "4" - -"iconv-corefoundation@^1.1.7": - "integrity" "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==" - "resolved" "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz" - "version" "1.1.7" - dependencies: - "cli-truncate" "^2.1.0" - "node-addon-api" "^1.6.3" - -"iconv-lite@^0.6.2": - "integrity" "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==" - "resolved" "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" - "version" "0.6.3" - dependencies: - "safer-buffer" ">= 2.1.2 < 3.0.0" - -"ieee754@^1.1.13": - "integrity" "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - "resolved" "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" - "version" "1.2.1" - -"inflight@^1.0.4": - "integrity" "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==" - "resolved" "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - "version" "1.0.6" - dependencies: - "once" "^1.3.0" - "wrappy" "1" - -"inherits@^2.0.3", "inherits@~2.0.3", "inherits@2": - "integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - "version" "2.0.4" - -"is-arrayish@^0.3.1": - "integrity" "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - "resolved" "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" - "version" "0.3.2" - -"is-ci@^3.0.0": - "integrity" "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==" - "resolved" "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz" - "version" "3.0.1" - dependencies: - "ci-info" "^3.2.0" - -"is-fullwidth-code-point@^3.0.0": - "integrity" "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - "version" "3.0.0" - -"is-plain-obj@^1.0.0": - "integrity" "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - "resolved" "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" - "version" "1.1.0" - -"is-stream@^2.0.0": - "integrity" "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - "resolved" "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" - "version" "2.0.1" - -"is-typedarray@~1.0.0": - "integrity" "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - "resolved" "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - "version" "1.0.0" - -"isarray@~1.0.0": - "integrity" "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - "resolved" "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - "version" "1.0.0" - -"isbinaryfile@^3.0.2": - "integrity" "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==" - "resolved" "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz" - "version" "3.0.3" - dependencies: - "buffer-alloc" "^1.2.0" - -"isbinaryfile@^4.0.10": - "integrity" "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==" - "resolved" "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz" - "version" "4.0.10" - -"isexe@^2.0.0": - "integrity" "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - "resolved" "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - "version" "2.0.0" - -"isstream@~0.1.2": - "integrity" "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - "resolved" "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - "version" "0.1.2" - -"jake@^10.8.5": - "integrity" "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==" - "resolved" "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz" - "version" "10.8.5" - dependencies: - "async" "^3.2.3" - "chalk" "^4.0.2" - "filelist" "^1.0.1" - "minimatch" "^3.0.4" - -"js-yaml@^4.1.0": - "integrity" "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==" - "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - "version" "4.1.0" - dependencies: - "argparse" "^2.0.1" - -"jsbn@~0.1.0": - "integrity" "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - "resolved" "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" - "version" "0.1.1" - -"json-buffer@3.0.1": - "integrity" "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - "resolved" "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" - "version" "3.0.1" - -"json-schema-traverse@^0.4.1": - "integrity" "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - "resolved" "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" - "version" "0.4.1" - -"json-schema@0.2.3": - "integrity" "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - "resolved" "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" - "version" "0.2.3" - -"json-stringify-safe@^5.0.1", "json-stringify-safe@~5.0.1": - "integrity" "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - "resolved" "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - "version" "5.0.1" - -"json5@^2.2.0": - "integrity" "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" - "resolved" "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" - "version" "2.2.3" - -"jsonfile@^4.0.0": - "integrity" "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=" - "resolved" "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" - "version" "4.0.0" - dependencies: - "graceful-fs" "^4.1.6" - -"jsonfile@^6.0.1": - "integrity" "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==" - "resolved" "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" - "version" "6.1.0" - dependencies: - "universalify" "^2.0.0" + agent-base "6" + debug "4" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz" + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + dependencies: + agent-base "6" + debug "4" + +iconv-corefoundation@^1.1.7: + version "1.1.7" + resolved "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz" + dependencies: + cli-truncate "^2.1.0" + node-addon-api "^1.6.3" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" + +is-ci@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz" + dependencies: + ci-info "^3.2.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + +isbinaryfile@^3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz" + dependencies: + buffer-alloc "^1.2.0" + +isbinaryfile@^4.0.10: + version "4.0.10" + resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz" + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + dependencies: + argparse "^2.0.1" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + +json5@^2.2.0: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" + dependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + dependencies: + universalify "^2.0.0" optionalDependencies: - "graceful-fs" "^4.1.6" - -"jsprim@^1.2.2": - "integrity" "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=" - "resolved" "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz" - "version" "1.4.1" - dependencies: - "assert-plus" "1.0.0" - "extsprintf" "1.3.0" - "json-schema" "0.2.3" - "verror" "1.10.0" - -"keyv@^4.0.0": - "integrity" "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==" - "resolved" "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz" - "version" "4.5.2" - dependencies: - "json-buffer" "3.0.1" - -"kuler@^2.0.0": - "integrity" "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - "resolved" "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" - "version" "2.0.0" - -"lazy-val@^1.0.4", "lazy-val@^1.0.5": - "integrity" "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==" - "resolved" "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz" - "version" "1.0.5" - -"lodash@^4.17.15": - "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - "version" "4.17.21" - -"logform@^2.2.0": - "integrity" "sha512-graeoWUH2knKbGthMtuG1EfaSPMZFZBIrhuJHhkS5ZseFBrc7DupCzihOQAzsK/qIKPQaPJ/lFQFctILUY5ARQ==" - "resolved" "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz" - "version" "2.3.0" - dependencies: - "colors" "^1.2.1" - "fecha" "^4.2.0" - "ms" "^2.1.1" - "safe-stable-stringify" "^1.1.0" - "triple-beam" "^1.3.0" - -"lowercase-keys@^2.0.0": - "integrity" "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - "resolved" "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" - "version" "2.0.0" - -"lru-cache@^6.0.0": - "integrity" "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==" - "resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" - "version" "6.0.0" - dependencies: - "yallist" "^4.0.0" - -"matcher@^3.0.0": - "integrity" "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==" - "resolved" "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz" - "version" "3.0.0" - dependencies: - "escape-string-regexp" "^4.0.0" - -"mime-db@^1.28.0", "mime-db@1.44.0": - "integrity" "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz" - "version" "1.44.0" - -"mime-types@^2.1.12", "mime-types@~2.1.19": - "integrity" "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==" - "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz" - "version" "2.1.27" - dependencies: - "mime-db" "1.44.0" - -"mime@^2.5.2": - "integrity" "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" - "resolved" "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" - "version" "2.6.0" - -"mimic-response@^1.0.0": - "integrity" "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - "resolved" "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz" - "version" "1.0.1" - -"mimic-response@^3.1.0": - "integrity" "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - "resolved" "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" - "version" "3.1.0" - -"minimatch@^3.0.4", "minimatch@^3.1.1", "minimatch@^3.1.2": - "integrity" "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==" - "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - "version" "3.1.2" - dependencies: - "brace-expansion" "^1.1.7" - -"minimatch@^5.0.1": - "integrity" "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==" - "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" - "version" "5.1.6" - dependencies: - "brace-expansion" "^2.0.1" - -"minimatch@3.0.4": - "integrity" "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" - "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" - "version" "3.0.4" - dependencies: - "brace-expansion" "^1.1.7" - -"minimist@^1.2.0": - "integrity" "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" - "version" "1.2.7" - -"minipass@^3.0.0": - "integrity" "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==" - "resolved" "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" - "version" "3.3.6" - dependencies: - "yallist" "^4.0.0" - -"minipass@^4.0.0": - "integrity" "sha512-V9esFpNbK0arbN3fm2sxDKqMYgIp7XtVdE4Esj+PE4Qaaxdg1wIw48ITQIOn1sc8xXSmUviVL3cyjMqPlrVkiA==" - "resolved" "https://registry.npmjs.org/minipass/-/minipass-4.0.1.tgz" - "version" "4.0.1" - -"minizlib@^2.1.1": - "integrity" "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==" - "resolved" "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" - "version" "2.1.2" - dependencies: - "minipass" "^3.0.0" - "yallist" "^4.0.0" - -"mkdirp@^1.0.3": - "integrity" "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - "resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" - "version" "1.0.4" - -"modify-filename@^1.1.0": - "integrity" "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=" - "resolved" "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz" - "version" "1.1.0" - -"ms@^2.1.1", "ms@2.1.2": - "integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - "version" "2.1.2" - -"ms@2.0.0": - "integrity" "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - "resolved" "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - "version" "2.0.0" - -"mz@^2.7.0": - "integrity" "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==" - "resolved" "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" - "version" "2.7.0" - dependencies: - "any-promise" "^1.0.0" - "object-assign" "^4.0.1" - "thenify-all" "^1.0.0" - -"node-addon-api@^1.6.3": - "integrity" "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" - "resolved" "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz" - "version" "1.7.2" - -"normalize-url@^6.0.1": - "integrity" "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - "resolved" "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz" - "version" "6.1.0" - -"oauth-sign@~0.9.0": - "integrity" "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - "resolved" "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" - "version" "0.9.0" - -"object-assign@^4.0.1": - "integrity" "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - "resolved" "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" - "version" "4.1.1" - -"object-keys@^1.1.1": - "integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - "resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" - "version" "1.1.1" - -"once@^1.3.0", "once@^1.3.1", "once@^1.4.0": - "integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" - "resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - "version" "1.4.0" - dependencies: - "wrappy" "1" - -"one-time@^1.0.0": - "integrity" "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==" - "resolved" "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz" - "version" "1.0.0" - dependencies: - "fn.name" "1.x.x" - -"p-cancelable@^2.0.0": - "integrity" "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" - "resolved" "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz" - "version" "2.1.1" - -"path-exists@^4.0.0": - "integrity" "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - "version" "4.0.0" - -"path-is-absolute@^1.0.0": - "integrity" "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - "resolved" "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - "version" "1.0.1" - -"path-key@^3.1.0": - "integrity" "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - "resolved" "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" - "version" "3.1.1" - -"pend@~1.2.0": - "integrity" "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" - "resolved" "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" - "version" "1.2.0" - -"performance-now@^2.1.0": - "integrity" "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - "resolved" "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" - "version" "2.1.0" - -"plist@^3.0.1", "plist@^3.0.4": - "integrity" "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==" - "resolved" "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz" - "version" "3.0.6" - dependencies: - "base64-js" "^1.5.1" - "xmlbuilder" "^15.1.1" - -"process-nextick-args@~2.0.0": - "integrity" "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - "resolved" "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" - "version" "2.0.1" - -"progress@^2.0.3": - "integrity" "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" - "resolved" "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" - "version" "2.0.3" - -"psl@^1.1.28": - "integrity" "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - "resolved" "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" - "version" "1.8.0" - -"pump@^3.0.0": - "integrity" "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==" - "resolved" "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" - "version" "3.0.0" - dependencies: - "end-of-stream" "^1.1.0" - "once" "^1.3.1" - -"punycode@^2.1.0", "punycode@^2.1.1": - "integrity" "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - "resolved" "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" - "version" "2.1.1" - -"pupa@^2.0.1": - "integrity" "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==" - "resolved" "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz" - "version" "2.0.1" - dependencies: - "escape-goat" "^2.0.0" - -"qs@~6.5.2": - "integrity" "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" - "resolved" "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz" - "version" "6.5.3" - -"quick-lru@^5.1.1": - "integrity" "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - "resolved" "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" - "version" "5.1.1" - -"read-config-file@6.2.0": - "integrity" "sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg==" - "resolved" "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz" - "version" "6.2.0" - dependencies: - "dotenv" "^9.0.2" - "dotenv-expand" "^5.1.0" - "js-yaml" "^4.1.0" - "json5" "^2.2.0" - "lazy-val" "^1.0.4" - -"read-last-lines@^1.8.0": - "integrity" "sha512-oPL0cnZkhsO2xF7DBrdzVhXSNajPP5TzzCim/2IAjeGb17ArLLTRriI/ceV6Rook3L27mvbrOvLlf9xYYnaftQ==" - "resolved" "https://registry.npmjs.org/read-last-lines/-/read-last-lines-1.8.0.tgz" - "version" "1.8.0" - dependencies: - "mz" "^2.7.0" - -"readable-stream@^2.3.7": - "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" - "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" - "version" "2.3.7" - dependencies: - "core-util-is" "~1.0.0" - "inherits" "~2.0.3" - "isarray" "~1.0.0" - "process-nextick-args" "~2.0.0" - "safe-buffer" "~5.1.1" - "string_decoder" "~1.1.1" - "util-deprecate" "~1.0.1" - -"readable-stream@^3.4.0": - "integrity" "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==" - "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" - "version" "3.6.0" - dependencies: - "inherits" "^2.0.3" - "string_decoder" "^1.1.1" - "util-deprecate" "^1.0.1" - -"request@^2.88.2": - "integrity" "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==" - "resolved" "https://registry.npmjs.org/request/-/request-2.88.2.tgz" - "version" "2.88.2" - dependencies: - "aws-sign2" "~0.7.0" - "aws4" "^1.8.0" - "caseless" "~0.12.0" - "combined-stream" "~1.0.6" - "extend" "~3.0.2" - "forever-agent" "~0.6.1" - "form-data" "~2.3.2" - "har-validator" "~5.1.3" - "http-signature" "~1.2.0" - "is-typedarray" "~1.0.0" - "isstream" "~0.1.2" - "json-stringify-safe" "~5.0.1" - "mime-types" "~2.1.19" - "oauth-sign" "~0.9.0" - "performance-now" "^2.1.0" - "qs" "~6.5.2" - "safe-buffer" "^5.1.2" - "tough-cookie" "~2.5.0" - "tunnel-agent" "^0.6.0" - "uuid" "^3.3.2" - -"require-directory@^2.1.1": - "integrity" "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - "resolved" "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - "version" "2.1.1" - -"resolve-alpn@^1.0.0": - "integrity" "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - "resolved" "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz" - "version" "1.2.1" - -"responselike@^2.0.0": - "integrity" "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==" - "resolved" "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz" - "version" "2.0.1" - dependencies: - "lowercase-keys" "^2.0.0" - -"rimraf@^3.0.0": - "integrity" "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==" - "resolved" "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - "version" "3.0.2" - dependencies: - "glob" "^7.1.3" - -"roarr@^2.15.3": - "integrity" "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==" - "resolved" "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz" - "version" "2.15.4" - dependencies: - "boolean" "^3.0.1" - "detect-node" "^2.0.4" - "globalthis" "^1.0.1" - "json-stringify-safe" "^5.0.1" - "semver-compare" "^1.0.0" - "sprintf-js" "^1.1.2" - -"safe-buffer@^5.0.1", "safe-buffer@^5.1.2", "safe-buffer@~5.1.0", "safe-buffer@~5.1.1": - "integrity" "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - "version" "5.1.2" - -"safe-stable-stringify@^1.1.0": - "integrity" "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" - "resolved" "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz" - "version" "1.1.1" - -"safer-buffer@^2.0.2", "safer-buffer@^2.1.0", "safer-buffer@>= 2.1.2 < 3.0.0", "safer-buffer@~2.1.0": - "integrity" "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - "resolved" "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - "version" "2.1.2" - -"sanitize-filename@^1.6.3": - "integrity" "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==" - "resolved" "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz" - "version" "1.6.3" - dependencies: - "truncate-utf8-bytes" "^1.0.0" - -"sax@^1.2.4": - "integrity" "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - "resolved" "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" - "version" "1.2.4" - -"semver-compare@^1.0.0": - "integrity" "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" - "resolved" "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" - "version" "1.0.0" - -"semver@^6.2.0": - "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - "version" "6.3.0" - -"semver@^7.3.2": - "integrity" "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" - "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz" - "version" "7.3.2" - -"semver@^7.3.7": - "integrity" "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==" - "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" - "version" "7.3.8" - dependencies: - "lru-cache" "^6.0.0" - -"semver@~7.0.0": - "integrity" "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - "resolved" "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" - "version" "7.0.0" - -"serialize-error@^7.0.1": - "integrity" "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==" - "resolved" "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz" - "version" "7.0.1" - dependencies: - "type-fest" "^0.13.1" - -"shebang-command@^2.0.0": - "integrity" "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==" - "resolved" "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" - "version" "2.0.0" - dependencies: - "shebang-regex" "^3.0.0" - -"shebang-regex@^3.0.0": - "integrity" "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - "resolved" "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" - "version" "3.0.0" - -"simple-swizzle@^0.2.2": - "integrity" "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=" - "resolved" "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - "version" "0.2.2" - dependencies: - "is-arrayish" "^0.3.1" - -"simple-update-notifier@^1.0.7": - "integrity" "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==" - "resolved" "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz" - "version" "1.1.0" - dependencies: - "semver" "~7.0.0" - -"slice-ansi@^3.0.0": - "integrity" "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==" - "resolved" "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz" - "version" "3.0.0" - dependencies: - "ansi-styles" "^4.0.0" - "astral-regex" "^2.0.0" - "is-fullwidth-code-point" "^3.0.0" - -"smart-buffer@^4.0.2": - "integrity" "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - "resolved" "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" - "version" "4.2.0" - -"sort-keys-length@^1.0.0": - "integrity" "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=" - "resolved" "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz" - "version" "1.0.1" - dependencies: - "sort-keys" "^1.0.0" - -"sort-keys@^1.0.0": - "integrity" "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=" - "resolved" "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz" - "version" "1.1.2" - dependencies: - "is-plain-obj" "^1.0.0" - -"source-map-support@^0.5.19": - "integrity" "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==" - "resolved" "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" - "version" "0.5.21" - dependencies: - "buffer-from" "^1.0.0" - "source-map" "^0.6.0" - -"source-map@^0.6.0": - "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - "version" "0.6.1" - -"sprintf-js@^1.1.2": - "integrity" "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" - "resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz" - "version" "1.1.2" - -"sshpk@^1.7.0": - "integrity" "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==" - "resolved" "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz" - "version" "1.16.1" - dependencies: - "asn1" "~0.2.3" - "assert-plus" "^1.0.0" - "bcrypt-pbkdf" "^1.0.0" - "dashdash" "^1.12.0" - "ecc-jsbn" "~0.1.1" - "getpass" "^0.1.1" - "jsbn" "~0.1.0" - "safer-buffer" "^2.0.2" - "tweetnacl" "~0.14.0" - -"stack-trace@0.0.x": - "integrity" "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" - "resolved" "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" - "version" "0.0.10" - -"stat-mode@^1.0.0": - "integrity" "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==" - "resolved" "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz" - "version" "1.0.0" - -"string_decoder@^1.1.1", "string_decoder@~1.1.1": - "integrity" "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==" - "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - "version" "1.1.1" - dependencies: - "safe-buffer" "~5.1.0" - -"string-width@^4.1.0", "string-width@^4.2.0", "string-width@^4.2.3": - "integrity" "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" - "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - "version" "4.2.3" - dependencies: - "emoji-regex" "^8.0.0" - "is-fullwidth-code-point" "^3.0.0" - "strip-ansi" "^6.0.1" - -"strip-ansi@^6.0.0", "strip-ansi@^6.0.1": - "integrity" "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" - "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - "version" "6.0.1" - dependencies: - "ansi-regex" "^5.0.1" - -"sumchecker@^3.0.1": - "integrity" "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==" - "resolved" "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz" - "version" "3.0.1" - dependencies: - "debug" "^4.1.0" - -"supports-color@^7.1.0": - "integrity" "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" - "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - "version" "7.2.0" - dependencies: - "has-flag" "^4.0.0" - -"tar@^6.1.11": - "integrity" "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==" - "resolved" "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz" - "version" "6.1.13" - dependencies: - "chownr" "^2.0.0" - "fs-minipass" "^2.0.0" - "minipass" "^4.0.0" - "minizlib" "^2.1.1" - "mkdirp" "^1.0.3" - "yallist" "^4.0.0" - -"temp-file@^3.4.0": - "integrity" "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==" - "resolved" "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz" - "version" "3.4.0" - dependencies: - "async-exit-hook" "^2.0.1" - "fs-extra" "^10.0.0" - -"text-hex@1.0.x": - "integrity" "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - "resolved" "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" - "version" "1.0.0" - -"thenify-all@^1.0.0": - "integrity" "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=" - "resolved" "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" - "version" "1.6.0" - dependencies: - "thenify" ">= 3.1.0 < 4" + graceful-fs "^4.1.6" + +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + +keyv@^4.0.0: + version "4.5.2" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz" + dependencies: + json-buffer "3.0.1" + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" + +lazy-val@^1.0.4, lazy-val@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz" + +lodash@^4.17.15: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + +logform@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz" + dependencies: + colors "^1.2.1" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^1.1.0" + triple-beam "^1.3.0" + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + dependencies: + yallist "^4.0.0" + +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz" + dependencies: + escape-string-regexp "^4.0.0" + +mime-db@1.44.0, mime-db@^1.28.0: + version "1.44.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz" + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.27" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz" + dependencies: + mime-db "1.44.0" + +mime@^2.5.2: + version "2.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" + +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz" + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" + +minimatch@3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0: + version "1.2.7" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/minipass/-/minipass-4.0.1.tgz" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + +modify-filename@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + +ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +node-addon-api@^1.6.3: + version "1.7.2" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz" + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + dependencies: + wrappy "1" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz" + dependencies: + fn.name "1.x.x" + +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + +plist@^3.0.1, plist@^3.0.4: + version "3.0.6" + resolved "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz" + dependencies: + base64-js "^1.5.1" + xmlbuilder "^15.1.1" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + +pupa@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz" + dependencies: + escape-goat "^2.0.0" + +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" + +read-config-file@6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz" + dependencies: + dotenv "^9.0.2" + dotenv-expand "^5.1.0" + js-yaml "^4.1.0" + json5 "^2.2.0" + lazy-val "^1.0.4" + +read-last-lines@^1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/read-last-lines/-/read-last-lines-1.8.0.tgz" + dependencies: + mz "^2.7.0" + +readable-stream@^2.3.7: + version "2.3.7" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +request@^2.88.2: + version "2.88.2" + resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz" + +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz" + dependencies: + lowercase-keys "^2.0.0" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + dependencies: + glob "^7.1.3" + +roarr@^2.15.3: + version "2.15.4" + resolved "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz" + dependencies: + boolean "^3.0.1" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + +safe-stable-stringify@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz" + +"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + +sanitize-filename@^1.6.3: + version "1.6.3" + resolved "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz" + dependencies: + truncate-utf8-bytes "^1.0.0" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" + +semver@^6.2.0: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + +semver@^7.3.2, semver@^7.3.7: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + dependencies: + lru-cache "^6.0.0" + +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz" + dependencies: + type-fest "^0.13.1" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" + dependencies: + is-arrayish "^0.3.1" + +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz" + dependencies: + semver "~7.0.0" + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz" + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +smart-buffer@^4.0.2: + version "4.2.0" + resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" + +sort-keys-length@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz" + dependencies: + sort-keys "^1.0.0" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz" + dependencies: + is-plain-obj "^1.0.0" + +source-map-support@^0.5.19: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz" + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" + +stat-mode@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + dependencies: + ansi-regex "^5.0.1" + +sumchecker@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz" + dependencies: + debug "^4.1.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + dependencies: + has-flag "^4.0.0" + +tar@^6.1.11: + version "6.1.13" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz" + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^4.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +temp-file@^3.4.0: + version "3.4.0" + resolved "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz" + dependencies: + async-exit-hook "^2.0.1" + fs-extra "^10.0.0" + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + dependencies: + thenify ">= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": - "integrity" "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==" - "resolved" "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" - "version" "3.3.1" - dependencies: - "any-promise" "^1.0.0" + version "3.3.1" + resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" + dependencies: + any-promise "^1.0.0" + +tmp-promise@^3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz" + dependencies: + tmp "^0.2.0" -"tmp-promise@^3.0.2": - "integrity" "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==" - "resolved" "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz" - "version" "3.0.3" +tmp@^0.2.0: + version "0.2.1" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz" dependencies: - "tmp" "^0.2.0" + rimraf "^3.0.0" -"tmp@^0.2.0": - "integrity" "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==" - "resolved" "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz" - "version" "0.2.1" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz" dependencies: - "rimraf" "^3.0.0" - -"tough-cookie@~2.5.0": - "integrity" "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==" - "resolved" "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz" - "version" "2.5.0" - dependencies: - "psl" "^1.1.28" - "punycode" "^2.1.1" + psl "^1.1.28" + punycode "^2.1.1" -"triple-beam@^1.2.0", "triple-beam@^1.3.0": - "integrity" "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" - "resolved" "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz" - "version" "1.3.0" +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz" -"truncate-utf8-bytes@^1.0.0": - "integrity" "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==" - "resolved" "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz" - "version" "1.0.2" +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz" dependencies: - "utf8-byte-length" "^1.0.1" + utf8-byte-length "^1.0.1" -"tunnel-agent@^0.6.0": - "integrity" "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" - "resolved" "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" - "version" "0.6.0" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" dependencies: - "safe-buffer" "^5.0.1" + safe-buffer "^5.0.1" -"tweetnacl@^0.14.3", "tweetnacl@~0.14.0": - "integrity" "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - "resolved" "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" - "version" "0.14.5" +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" -"type-fest@^0.13.1": - "integrity" "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" - "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz" - "version" "0.13.1" +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz" -"universalify@^0.1.0": - "integrity" "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - "resolved" "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" - "version" "0.1.2" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" -"universalify@^2.0.0": - "integrity" "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - "resolved" "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" - "version" "2.0.0" +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" -"unused-filename@^2.1.0": - "integrity" "sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==" - "resolved" "https://registry.npmjs.org/unused-filename/-/unused-filename-2.1.0.tgz" - "version" "2.1.0" +unused-filename@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/unused-filename/-/unused-filename-2.1.0.tgz" dependencies: - "modify-filename" "^1.1.0" - "path-exists" "^4.0.0" + modify-filename "^1.1.0" + path-exists "^4.0.0" -"uri-js@^4.2.2": - "integrity" "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==" - "resolved" "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz" - "version" "4.4.0" +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz" dependencies: - "punycode" "^2.1.0" + punycode "^2.1.0" -"utf8-byte-length@^1.0.1": - "integrity" "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" - "resolved" "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz" - "version" "1.0.4" +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz" -"util-deprecate@^1.0.1", "util-deprecate@~1.0.1": - "integrity" "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - "resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - "version" "1.0.2" +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" -"uuid@^3.3.2": - "integrity" "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - "resolved" "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" - "version" "3.4.0" +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" -"verror@^1.10.0", "verror@1.10.0": - "integrity" "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=" - "resolved" "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" - "version" "1.10.0" +verror@1.10.0, verror@^1.10.0: + version "1.10.0" + resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" dependencies: - "assert-plus" "^1.0.0" - "core-util-is" "1.0.2" - "extsprintf" "^1.2.0" + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" -"which@^2.0.1": - "integrity" "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==" - "resolved" "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - "version" "2.0.2" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" dependencies: - "isexe" "^2.0.0" + isexe "^2.0.0" -"winston-transport@^4.4.0": - "integrity" "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==" - "resolved" "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz" - "version" "4.4.0" +winston-transport@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz" dependencies: - "readable-stream" "^2.3.7" - "triple-beam" "^1.2.0" - -"winston@^3.3.3": - "integrity" "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==" - "resolved" "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz" - "version" "3.3.3" + readable-stream "^2.3.7" + triple-beam "^1.2.0" + +winston@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz" dependencies: "@dabh/diagnostics" "^2.0.2" - "async" "^3.1.0" - "is-stream" "^2.0.0" - "logform" "^2.2.0" - "one-time" "^1.0.0" - "readable-stream" "^3.4.0" - "stack-trace" "0.0.x" - "triple-beam" "^1.3.0" - "winston-transport" "^4.4.0" - -"wrap-ansi@^7.0.0": - "integrity" "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==" - "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - "version" "7.0.0" - dependencies: - "ansi-styles" "^4.0.0" - "string-width" "^4.1.0" - "strip-ansi" "^6.0.0" - -"wrappy@1": - "integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - "resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - "version" "1.0.2" - -"xmlbuilder@^15.1.1", "xmlbuilder@>=11.0.1": - "integrity" "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==" - "resolved" "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" - "version" "15.1.1" - -"y18n@^5.0.5": - "integrity" "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - "resolved" "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - "version" "5.0.8" - -"yallist@^4.0.0": - "integrity" "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - "resolved" "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" - "version" "4.0.0" - -"yargs-parser@^21.1.1": - "integrity" "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" - "version" "21.1.1" - -"yargs@^17.5.1": - "integrity" "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==" - "resolved" "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz" - "version" "17.6.2" - dependencies: - "cliui" "^8.0.1" - "escalade" "^3.1.1" - "get-caller-file" "^2.0.5" - "require-directory" "^2.1.1" - "string-width" "^4.2.3" - "y18n" "^5.0.5" - "yargs-parser" "^21.1.1" - -"yauzl@^2.10.0": - "integrity" "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=" - "resolved" "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" - "version" "2.10.0" - dependencies: - "buffer-crc32" "~0.2.3" - "fd-slicer" "~1.1.0" + async "^3.1.0" + is-stream "^2.0.0" + logform "^2.2.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.4.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + +xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + +yargs@^17.5.1: + version "17.6.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz" + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" diff --git a/pyproject.toml b/pyproject.toml index fb795dc370..819fe98773 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ dependencies = {file = ["requirements.in"]} test = [ "black==22.3.0", "pre-commit==2.13.0", - "pip-tools==6.12.2", + "pip-tools==6.13", "pytest==7.1.2", "PySocks==1.7.1", "pytest-cov==2.10.1", diff --git a/requirements.in b/requirements.in index c37d3d03b1..bb54763e30 100644 --- a/requirements.in +++ b/requirements.in @@ -1,15 +1,14 @@ certifi==2022.12.7 chardet==3.0.4 Click==8.1.1 -Flask==2.1.1 -Werkzeug==2.2.2 # need to pin because Flask doesn't work with newer ones -Flask-Babel==2.0.0 # needs pin see https://github.com/cryptoadvance/specter-desktop/issues/2218 +Flask==2.2.5 +Flask-Babel==3.1.0 Flask-Cors==3.0.10 -Flask-Login==0.6.2 -Flask-RESTful==0.3.9 -Flask-HTTPAuth==4.7.0 +Flask-Login==0.6.3 +Flask-RESTful==0.3.10 +Flask-HTTPAuth==4.8.0 hwi==2.1.1 -python-dotenv==0.13.0 +python-dotenv==0.21.1 requests==2.26.0 pysocks==1.7.1 six==1.16.0 @@ -17,9 +16,9 @@ stem==1.8.0 embit==0.6.1 psutil==5.9.0 pyopenssl==23.0.0 -flask_wtf==0.15.1 +flask_wtf==1.2.1 pgpy==0.6.0 -cbor==1.0.0 +cbor2==5.4.6 mnemonic==0.20 cryptography==39.0.1 Flask-APScheduler==1.12.4 @@ -29,9 +28,15 @@ protobuf==3.20.2 PyJWT==2.4.0 pytimeparse==1.1.8 psycopg2-binary==2.9.5 + # Extensions cryptoadvance-liquidissuer==0.2.4 specterext-exfund==0.1.7 specterext-faucet==0.1.2 -cryptoadvance.spectrum==0.6.4 +cryptoadvance.spectrum==0.6.5 specterext-stacktrack==0.3.0 + +# workarounds + +# greenlet is needed but not inserted as transitive dependency for some reason +greenlet==2.0.2 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 92f0b18fb9..6664d3fb2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,9 +12,9 @@ apscheduler==3.10.0 \ --hash=sha256:575299f20073c60a2cc9d4fa5906024cdde33c5c0ce6087c4e3c14be3b50fdd4 \ --hash=sha256:a49fc23269218416f0e41890eea7a75ed6b284f10630dcfe866ab659621a3696 # via flask-apscheduler -babel==2.11.0 \ - --hash=sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe \ - --hash=sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6 +babel==2.12.1 \ + --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \ + --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455 # via flask-babel base58==2.1.1 \ --hash=sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2 \ @@ -26,9 +26,45 @@ bitbox02==6.1.1 \ # via hwi cbor==1.0.0 \ --hash=sha256:13225a262ddf5615cbd9fd55a76a0d53069d18b07d2e9f19c39e6acb8609bbb6 - # via - # -r requirements.in - # hwi + # via hwi +cbor2==5.4.6 \ + --hash=sha256:0b956f19e93ba3180c336282cd1b6665631f2d3a196a9c19b29a833bf979e7a4 \ + --hash=sha256:0bd12c54a48949d11f5ffc2fa27f5df1b4754111f5207453e5fae3512ebb3cab \ + --hash=sha256:0d2b926b024d3a1549b819bc82fdc387062bbd977b0299dd5fa5e0ea3267b98b \ + --hash=sha256:1618d16e310f7ffed141762b0ff5d8bb6b53ad449406115cc465bf04213cefcf \ + --hash=sha256:181ac494091d1f9c5bb373cd85514ce1eb967a8cf3ec298e8dfa8878aa823956 \ + --hash=sha256:1835536e76ea16e88c934aac5e369ba9f93d495b01e5fa2d93f0b4986b89146d \ + --hash=sha256:1c12c0ab78f5bc290b08a79152a8621822415836a86f8f4b50dadba371736fda \ + --hash=sha256:24144822f8d2b0156f4cda9427f071f969c18683ffed39663dc86bc0a75ae4dd \ + --hash=sha256:309fffbb7f561d67f02095d4b9657b73c9220558701c997e9bfcfbca2696e927 \ + --hash=sha256:3316f09a77af85e7772ecfdd693b0f450678a60b1aee641bac319289757e3fa0 \ + --hash=sha256:3545b16f9f0d5f34d4c99052829c3726020a07be34c99c250d0df87418f02954 \ + --hash=sha256:39452c799453f5bf33281ffc0752c620b8bfa0b7c13070b87d370257a1311976 \ + --hash=sha256:3950be57a1698086cf26d8710b4e5a637b65133c5b1f9eec23967d4089d8cfed \ + --hash=sha256:456cdff668a50a52fdb8aa6d0742511e43ed46d6a5b463dba80a5a720fa0d320 \ + --hash=sha256:4b9f3924da0e460a93b3674c7e71020dd6c9e9f17400a34e52a88c0af2dcd2aa \ + --hash=sha256:4bbbdb2e3ef274865dc3f279aae109b5d94f4654aea3c72c479fb37e4a1e7ed7 \ + --hash=sha256:4ce1a2c272ba8523a55ea2f1d66e3464e89fa0e37c9a3d786a919fe64e68dbd7 \ + --hash=sha256:56dfa030cd3d67e5b6701d3067923f2f61536a8ffb1b45be14775d1e866b59ae \ + --hash=sha256:6709d97695205cd08255363b54afa035306d5302b7b5e38308c8ff5a47e60f2a \ + --hash=sha256:6e1b5aee920b6a2f737aa12e2b54de3826b09f885a7ce402db84216343368140 \ + --hash=sha256:6f9c702bee2954fffdfa3de95a5af1a6b1c5f155e39490353d5654d83bb05bb9 \ + --hash=sha256:78304df140b9e13b93bcbb2aecee64c9aaa9f1cadbd45f043b5e7b93cc2f21a2 \ + --hash=sha256:79e048e623846d60d735bb350263e8fdd36cb6195d7f1a2b57eacd573d9c0b33 \ + --hash=sha256:7bbd3470eb685325398023e335be896b74f61b014896604ed45049a7b7b6d8ac \ + --hash=sha256:80ac8ba450c7a41c5afe5f7e503d3092442ed75393e1de162b0bf0d97edf7c7f \ + --hash=sha256:9394ca49ecdf0957924e45d09a4026482d184a465a047f60c4044eb464c43de9 \ + --hash=sha256:94f844d0e232aca061a86dd6ff191e47ba0389ddd34acb784ad9a41594dc99a4 \ + --hash=sha256:96087fa5336ebfc94465c0768cd5de0fcf9af3840d2cf0ce32f5767855f1a293 \ + --hash=sha256:b893500db0fe033e570c3adc956af6eefc57e280026bd2d86fd53da9f1e594d7 \ + --hash=sha256:c285a2cb2c04004bfead93df89d92a0cef1874ad337d0cb5ea53c2c31e97bfdb \ + --hash=sha256:d2984a488f350aee1d54fa9cb8c6a3c1f1f5b268abbc91161e47185de4d829f3 \ + --hash=sha256:d54bd840b4fe34f097b8665fc0692c7dd175349e53976be6c5de4433b970daa4 \ + --hash=sha256:db9eb582fce972f0fa429d8159b7891ff8deccb7affc4995090afc61ce0d328a \ + --hash=sha256:e5094562dfe3e5583202b93ef7ca5082c2ba5571accb2c4412d27b7d0ba8a563 \ + --hash=sha256:e73ca40dd3c7210ff776acff9869ddc9ff67bae7c425b58e5715dcf55275163f \ + --hash=sha256:ff95b33e5482313a74648ca3620c9328e9f30ecfa034df040b828e476597d352 + # via -r requirements.in certifi==2022.12.7 \ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 @@ -119,9 +155,8 @@ cryptoadvance-liquidissuer==0.2.4 \ --hash=sha256:5a2c531801854c5a4a46daf184877e22f731cdb42d2cfb840785bda7371ba6fb \ --hash=sha256:9e468f3e35ecc566b3f74a2263677cf26632548abb194521dba15ad37acd1e9b # via -r requirements.in -cryptoadvance-spectrum==0.6.4 \ - --hash=sha256:226f89dfe96dac2fcd82f462706e118f017582df5565ff1924f27e4392bc6a0e \ - --hash=sha256:fa6c877b3a5ba683ccde991f020fce2f3b06e0b8f5219a6fdc592658cacb1d3d +cryptoadvance-spectrum==0.6.5 \ + --hash=sha256:200bdfbf6cf3a1e25ff6f34ef861f978e9b08e24702278d4d14a024b8dea69a6 # via -r requirements.in cryptography==39.0.1 \ --hash=sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4 \ @@ -163,9 +198,9 @@ embit==0.6.1 \ # via # -r requirements.in # cryptoadvance-spectrum -flask==2.1.1 \ - --hash=sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264 \ - --hash=sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8 +flask==2.2.5 \ + --hash=sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf \ + --hash=sha256:edee9b0a7ff26621bd5a8c10ff484ae28737a2410d99b0bb9a6850c7fb977aa0 # via # -r requirements.in # cryptoadvance-spectrum @@ -180,33 +215,99 @@ flask==2.1.1 \ flask-apscheduler==1.12.4 \ --hash=sha256:681dae34dc6cc9403ce674795e53abd0bff540472129cfd3d3c93e0e1d502da8 # via -r requirements.in -flask-babel==2.0.0 \ - --hash=sha256:e6820a052a8d344e178cdd36dd4bb8aea09b4bda3d5f9fa9f008df2c7f2f5468 \ - --hash=sha256:f9faf45cdb2e1a32ea2ec14403587d4295108f35017a7821a2b1acb8cfd9257d +flask-babel==3.1.0 \ + --hash=sha256:be015772c5d7f046f3b99c508dcf618636eb93d21b713b356db79f3e79f69f39 \ + --hash=sha256:deb3ee272d5adf97f5974ed09ab501243d63e7fb4a047501a00de4bd4aca4830 # via -r requirements.in flask-cors==3.0.10 \ --hash=sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438 \ --hash=sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de # via -r requirements.in -flask-httpauth==4.7.0 \ - --hash=sha256:a237e4c8ec1d339298a0eb88e5af2ca36117949b621631649462800e57e1ba37 \ - --hash=sha256:f7199e7bad20d5b68b3f0b62bddfca7637c55087e9d02f605ae26e0de479fd94 +flask-httpauth==4.8.0 \ + --hash=sha256:66568a05bc73942c65f1e2201ae746295816dc009edd84b482c44c758d75097a \ + --hash=sha256:a58fedd09989b9975448eef04806b096a3964a7feeebc0a78831ff55685b62b0 # via -r requirements.in -flask-login==0.6.2 \ - --hash=sha256:1ef79843f5eddd0f143c2cd994c1b05ac83c0401dc6234c143495af9a939613f \ - --hash=sha256:c0a7baa9fdc448cdd3dd6f0939df72eec5177b2f7abe6cb82fc934d29caac9c3 +flask-login==0.6.3 \ + --hash=sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333 \ + --hash=sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d # via -r requirements.in -flask-restful==0.3.9 \ - --hash=sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2 \ - --hash=sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e +flask-restful==0.3.10 \ + --hash=sha256:1cf93c535172f112e080b0d4503a8d15f93a48c88bdd36dd87269bdaf405051b \ + --hash=sha256:fe4af2ef0027df8f9b4f797aba20c5566801b6ade995ac63b588abf1a59cec37 # via -r requirements.in flask-sqlalchemy==2.5.1 \ --hash=sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912 \ --hash=sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390 # via cryptoadvance-spectrum -flask-wtf==0.15.1 \ - --hash=sha256:6ff7af73458f182180906a37a783e290bdc8a3817fe4ad17227563137ca285bf \ - --hash=sha256:ff177185f891302dc253437fe63081e7a46a4e99aca61dfe086fb23e54fff2dc +flask-wtf==1.2.1 \ + --hash=sha256:8bb269eb9bb46b87e7c8233d7e7debdf1f8b74bf90cc1789988c29b37a97b695 \ + --hash=sha256:fa6793f2fb7e812e0fe9743b282118e581fb1b6c45d414b8af05e659bd653287 + # via -r requirements.in +greenlet==2.0.2 \ + --hash=sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a \ + --hash=sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a \ + --hash=sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1 \ + --hash=sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43 \ + --hash=sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33 \ + --hash=sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8 \ + --hash=sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088 \ + --hash=sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca \ + --hash=sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343 \ + --hash=sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645 \ + --hash=sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db \ + --hash=sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df \ + --hash=sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3 \ + --hash=sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86 \ + --hash=sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2 \ + --hash=sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a \ + --hash=sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf \ + --hash=sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7 \ + --hash=sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394 \ + --hash=sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40 \ + --hash=sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3 \ + --hash=sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6 \ + --hash=sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74 \ + --hash=sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0 \ + --hash=sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3 \ + --hash=sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91 \ + --hash=sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5 \ + --hash=sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9 \ + --hash=sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417 \ + --hash=sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8 \ + --hash=sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b \ + --hash=sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6 \ + --hash=sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb \ + --hash=sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73 \ + --hash=sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b \ + --hash=sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df \ + --hash=sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9 \ + --hash=sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f \ + --hash=sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0 \ + --hash=sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857 \ + --hash=sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a \ + --hash=sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249 \ + --hash=sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30 \ + --hash=sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292 \ + --hash=sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b \ + --hash=sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d \ + --hash=sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b \ + --hash=sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c \ + --hash=sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca \ + --hash=sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7 \ + --hash=sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75 \ + --hash=sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae \ + --hash=sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47 \ + --hash=sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b \ + --hash=sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470 \ + --hash=sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c \ + --hash=sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564 \ + --hash=sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9 \ + --hash=sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099 \ + --hash=sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0 \ + --hash=sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5 \ + --hash=sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19 \ + --hash=sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1 \ + --hash=sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526 # via -r requirements.in gunicorn==20.1.0 \ --hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \ @@ -589,9 +690,9 @@ python-dateutil==2.8.2 \ # via # flask-apscheduler # pandas -python-dotenv==0.13.0 \ - --hash=sha256:25c0ff1a3e12f4bde8d592cc254ab075cfe734fc5dd989036716fd17ee7e5ec7 \ - --hash=sha256:3b9909bc96b0edc6b01586e1eed05e71174ef4e04c71da5786370cebea53ad74 +python-dotenv==0.21.1 \ + --hash=sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49 \ + --hash=sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a # via -r requirements.in pytimeparse==1.1.8 \ --hash=sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd \ @@ -602,7 +703,6 @@ pytz==2022.7.1 \ --hash=sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a # via # apscheduler - # babel # flask-babel # flask-restful # pandas diff --git a/src/cryptoadvance/specter/cli/cli_server.py b/src/cryptoadvance/specter/cli/cli_server.py index f2637e84a5..bf3d4c9c10 100644 --- a/src/cryptoadvance/specter/cli/cli_server.py +++ b/src/cryptoadvance/specter/cli/cli_server.py @@ -57,6 +57,11 @@ def cli(): is_flag=True, help="Start the hwi-bridge to use your HWWs with a remote specter.", ) +@click.option( + "--skiphwiinitialisation", + is_flag=True, + help="Skips to call HWI's enumerate() on start-up", +) @click.option( "--devstatus-threshold", type=click.Choice(["alpha", "beta", "prod"], case_sensitive=False), @@ -83,6 +88,7 @@ def server( filelog, tor, hwibridge, + skiphwiinitialisation, devstatus_threshold, specter_data_folder, config, @@ -148,6 +154,8 @@ def server( kwargs = configure_ssl(kwargs, app.config, ssl) app.app_context().push() + if skiphwiinitialisation: + app.config["SKIP_HWI_INITIALISATION_AT_STARTUP"] = True init_app(app, hwibridge=hwibridge) if filelog: diff --git a/src/cryptoadvance/specter/config.py b/src/cryptoadvance/specter/config.py index 95f6b9c43f..af73dee8dc 100644 --- a/src/cryptoadvance/specter/config.py +++ b/src/cryptoadvance/specter/config.py @@ -82,6 +82,11 @@ class BaseConfig(object): CERT = os.getenv("CERT", None) KEY = os.getenv("KEY", None) + # It might be necessary / useful for testing to skip the HWI initialisation (i.e. calling enumerate on startup) + SKIP_HWI_INITIALISATION_AT_STARTUP = _get_bool_env_var( + "SKIP_HWI_INITIALISATION_AT_STARTUP", False + ) + # This will be used to search for a bitcoin.conf in order to enable the # auth method "RPC password as pin" RASPIBLITZ_SPECTER_RPC_LOGIN_BITCOIN_CONF_LOCATION = os.getenv( diff --git a/src/cryptoadvance/specter/devices/hwi/jade.py b/src/cryptoadvance/specter/devices/hwi/jade.py index b8dfe9aee7..b9404100ed 100644 --- a/src/cryptoadvance/specter/devices/hwi/jade.py +++ b/src/cryptoadvance/specter/devices/hwi/jade.py @@ -9,33 +9,25 @@ from functools import wraps from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union -from hwilib.descriptor import PubkeyProvider, MultisigDescriptor +from hwilib.descriptor import MultisigDescriptor from hwilib.hwwclient import HardwareWalletClient from hwilib.errors import ( ActionCanceledError, BadArgumentError, DeviceConnectionError, DeviceFailureError, + DeviceNotReadyError, UnavailableActionError, common_err_msgs, handle_errors, ) -from hwilib.common import ( - AddressType, - Chain, -) -from hwilib.key import ExtendedKey, parse_path, KeyOriginInfo, is_hardened +from hwilib.common import AddressType, Chain, sha256 +from hwilib.key import ExtendedKey, KeyOriginInfo, is_hardened, parse_path from hwilib.psbt import PSBT -from hwilib.tx import CTransaction -from hwilib._script import ( - is_p2sh, - is_p2wpkh, - is_p2wsh, - is_witness, - parse_multisig, -) +from hwilib._script import is_p2sh, is_p2wpkh, is_p2wsh, is_witness, parse_multisig import logging +import semver import os import hashlib @@ -48,11 +40,17 @@ from embit.util import secp256k1 from embit.liquid.finalizer import finalize_psbt from embit.liquid.transaction import write_commitment +from embit.descriptor import Descriptor # The test emulator port -SIMULATOR_PATH = "tcp:127.0.0.1:2222" - -JADE_DEVICE_IDS = [(0x10C4, 0xEA60), (0x1A86, 0x55D4)] +SIMULATOR_PATH = "tcp:127.0.0.1:30121" + +JADE_DEVICE_IDS = [ + (0x10C4, 0xEA60), + (0x1A86, 0x55D4), + (0x0403, 0x6001), + (0x1A86, 0x7523), +] HAS_NETWORKING = hasattr(jade, "_http_request") py_enumerate = ( @@ -94,26 +92,19 @@ def func(*args: Any, **kwargs: Any) -> Any: # This class extends the HardwareWalletClient for Blockstream Jade specific things class JadeClient(HardwareWalletClient): + MIN_SUPPORTED_FW_VERSION = semver.VersionInfo(0, 1, 32) NETWORKS = { Chain.MAIN: "mainnet", Chain.TEST: "testnet", + Chain.SIGNET: "testnet", # same as far as Jade is concerned Chain.REGTEST: "localtest", } - liquid_network = None - def set_liquid_network(self, chain): - if chain == "liquidv1": - self.liquid_network = "liquid" - elif chain == "liquidtestnet": - self.liquid_network = "testnet-liquid" - else: - self.liquid_network = "localtest-liquid" - - def _network(self): - if self.liquid_network: - return self.liquid_network - return JadeClient.NETWORKS.get(self.chain, "mainnet") + def _network(self) -> str: + if self.chain not in self.NETWORKS: + raise BadArgumentError(f"Unhandled network: {self.chain}") + return self.NETWORKS[self.chain] ADDRTYPES = { AddressType.LEGACY: "pkh(k)", @@ -126,12 +117,11 @@ def _network(self): AddressType.SH_WIT: "sh(wsh(multi(k)))", } - @staticmethod - def _convertAddrType(addrType, multisig=False): - if multisig: - return JadeClient.MULTI_ADDRTYPES[addrType] - return JadeClient.ADDRTYPES[addrType] + @classmethod + def _convertAddrType(cls, addrType: AddressType, multisig: bool) -> str: + return cls.MULTI_ADDRTYPES[addrType] if multisig else cls.ADDRTYPES[addrType] + # Derive a deterministic name for a multisig registration record (ignoring bip67 key sorting) @staticmethod def _get_multisig_name( type: str, threshold: int, signers: List[Tuple[bytes, Sequence[int]]] @@ -142,31 +132,58 @@ def _get_multisig_name( summary += fingerprint.hex() + "|" + str(path) + "|" # Hash it, get the first 6-bytes as hex, prepend with 'hwi' - hash_summary = hashlib.sha256(summary.encode()).digest().hex() + hash_summary = sha256(summary.encode()).hex() return "hwi" + hash_summary[:12] - def __init__(self, path: str, password: str = "", expert: bool = False) -> None: - super(JadeClient, self).__init__(path, password, expert) - self.jade = JadeAPI.create_serial(path) + def __init__( + self, + path: str, + password: Optional[str] = None, + expert: bool = False, + chain: Chain = Chain.MAIN, + skip_unlocking: bool = False, + timeout: Optional[int] = None, + ) -> None: + super(JadeClient, self).__init__(path, password, expert, chain) + self.jade = JadeAPI.create_serial(path, timeout=timeout) self.jade.connect() - # Push some host entropy into jade - self.jade.add_entropy(os.urandom(32)) + verinfo = self.jade.get_version_info() + uninitialized = verinfo["JADE_STATE"] not in ["READY", "TEMP"] - # Do the PIN thing if required - # NOTE: uses standard 'requests' networking to connect to blind pinserver - try: - while not self.jade.auth_user(self._network()): - logging.debug("Incorrect PIN provided") - except: - try: - self.chain = Chain.TEST - while not self.jade.auth_user(self._network()): - logging.debug("Incorrect PIN provided") - except: - self.chain = Chain.REGTEST - while not self.jade.auth_user(self._network()): - logging.debug("Incorrect PIN provided") + # Check minimum supported firmware version (ignore candidate/build parts) + fw_version = semver.parse_version_info(verinfo["JADE_VERSION"]) + if self.MIN_SUPPORTED_FW_VERSION > fw_version.finalize_version(): + raise DeviceNotReadyError( + f"Jade fw version: {fw_version} - minimum required version: {self.MIN_SUPPORTED_FW_VERSION}. " + "Please update using a Blockstream Green companion app" + ) + if path == SIMULATOR_PATH: + if uninitialized: + # Connected to simulator but it appears to have no wallet set + raise DeviceNotReadyError( + "Use JadeAPI.set_[seed|mnemonic] to set simulator wallet" + ) + else: + if uninitialized: + if skip_unlocking: + # We don't want to prompt to unlock the device right now + return + if not HAS_NETWORKING: + # Wallet not initialised/unlocked nor do we have networking dependencies + # User must use 'Recovery Phrase Login' or 'QR Unlock' feature to access wallet + raise DeviceNotReadyError( + 'Use "Recovery Phrase Login" or "QR PIN Unlock" feature on Jade hw to access wallet' + ) + + # Push some host entropy into jade + self.jade.add_entropy(os.urandom(32)) + + # Authenticate the user - this may require a PIN and pinserver interaction + # (if we have required networking dependencies) + authenticated = False + while not authenticated: + authenticated = self.jade.auth_user(self._network()) # Retrieves the public key at the specified BIP 32 derivation path @jade_exception @@ -176,13 +193,6 @@ def get_pubkey_at_path(self, bip32_path: str) -> ExtendedKey: ext_key = ExtendedKey.deserialize(xpub) return ext_key - @jade_exception - def get_master_blinding_key(self) -> str: - mbk = self.jade.get_master_blinding_key() - assert len(mbk) == 32 - bkey = ec.PrivateKey(mbk) - return bkey.wif() - # Walk the PSBT looking for inputs we can sign. Push any signatures into the # 'partial_sigs' map in the input, and return the updated PSBT. @jade_exception @@ -209,12 +219,9 @@ def _split_at_last_hardened_element( prefix, suffix = _split_at_last_hardened_element(origin.path) signers.append((origin.fingerprint, prefix)) paths.append(suffix) - # sort signers and paths like in multisig registration - signers, paths = [list(a) for a in zip(*sorted(zip(signers, paths)))] - return signers, paths - c_txn = CTransaction(tx.tx) + c_txn = tx.get_unsigned_tx() master_fp = self.get_master_fingerprint() signing_singlesigs = False signing_multisigs = {} @@ -226,7 +233,7 @@ def _split_at_last_hardened_element( # Signing input details jade_inputs = [] - for n_vin, (txin, psbtin) in py_enumerate(zip(c_txn.vin, tx.inputs)): + for n_vin, psbtin in py_enumerate(tx.inputs): # Get bip32 path to use to sign, if required for this input path = None multisig_input = len(psbtin.hd_keypaths) > 1 @@ -252,7 +259,12 @@ def _split_at_last_hardened_element( if psbtin.witness_utxo: utxo = psbtin.witness_utxo if psbtin.non_witness_utxo: - utxo = psbtin.non_witness_utxo.vout[txin.prevout.n] + if psbtin.prev_txid != psbtin.non_witness_utxo.hash: + raise BadArgumentError( + f"Input {n_vin} has a non_witness_utxo with the wrong hash" + ) + assert psbtin.prev_out is not None + utxo = psbtin.non_witness_utxo.vout[psbtin.prev_out] input_txn_bytes = ( psbtin.non_witness_utxo.serialize_without_witness() ) @@ -260,6 +272,7 @@ def _split_at_last_hardened_element( raise Exception( "PSBT is missing input utxo information, cannot sign" ) + sats_value = utxo.nValue scriptcode = utxo.scriptPubKey if is_p2sh(scriptcode): @@ -311,9 +324,10 @@ def _split_at_last_hardened_element( jade_inputs.append( { "is_witness": witness_input, - "input_tx": input_txn_bytes, + "satoshi": sats_value, "script": scriptcode, "path": path, + "input_tx": input_txn_bytes, "ae_host_entropy": os.urandom(32), "ae_host_commitment": os.urandom(32), } @@ -321,157 +335,123 @@ def _split_at_last_hardened_element( # Change output details # This is optional, in that if we send it Jade validates the change output script - # and the user need not confirm that ouptut. If not passed the change output must + # and the user need not confirm that output. If not passed the change output must # be confirmed by the user on the hwwallet screen, like any other spend output. change: List[Optional[Dict[str, Any]]] = [None] * len(tx.outputs) - # If signing multisig inputs, get registered multisigs details in case we - # see any multisig outputs which may be change which we can auto-validate. - # ie. filter speculative 'signing multisigs' to ones actually registered on the hw - candidate_multisigs = {} - - if signing_multisigs: - # register multisig if xpubs are known - if tx.xpub and len(signing_multisigs) == 1: - msigname = list(signing_multisigs.keys())[0] - signers = [] - origins = [] - for xpub in tx.xpub: - hd = bip32.HDKey.parse(xpub) - origin = tx.xpub[xpub] - origins.append((origin.fingerprint, origin.path)) - - signers.append( - { - "fingerprint": origin.fingerprint, - "derivation": origin.path, - "xpub": str(hd), - "path": [], - } - ) - - # sort origins and signers together - origins, signers = [ - list(a) for a in zip(*sorted(zip(origins, signers))) - ] - - # Get a deterministic name for this multisig wallet - script_variant = signing_multisigs[msigname][0] - thresh = signing_multisigs[msigname][1] - num_signers = signing_multisigs[msigname][2] - multisig_name = self._get_multisig_name( - script_variant, thresh, origins - ) - # stupid sanity check of the fingerprints and origins - if multisig_name == msigname: - # Need to ensure this multisig wallet is registered first - # (Note: 're-registering' is a no-op) - self.jade.register_multisig( - self._network(), - multisig_name, - script_variant, - True, # always use sorted - thresh, - signers, - ) - # - registered_multisigs = self.jade.get_registered_multisigs() - signing_multisigs = { - k: v - for k, v in signing_multisigs.items() - if k in registered_multisigs - and registered_multisigs[k]["variant"] == v[0] - and registered_multisigs[k]["threshold"] == v[1] - and registered_multisigs[k]["num_signers"] == len(v[2]) - } - - # Look at every output... - for n_vout, (txout, psbtout) in py_enumerate(zip(c_txn.vout, tx.outputs)): - num_signers = len(psbtout.hd_keypaths) - - if num_signers == 1 and signing_singlesigs: - # Single-sig output - since we signed singlesig inputs this could be our change - for pubkey, origin in psbtout.hd_keypaths.items(): - # Considers 'our' outputs as potential change as far as Jade is concerned - # ie. can be verified and auto-confirmed. - # Is this ok, or should check path also, assuming bip44-like ? - if origin.fingerprint == master_fp and len(origin.path) > 0: - change_addr_type = None - if txout.is_p2pkh(): - change_addr_type = AddressType.LEGACY - elif txout.is_witness()[0] and not txout.is_p2wsh(): - change_addr_type = AddressType.WIT # ie. p2wpkh - elif ( - txout.is_p2sh() and is_witness(psbtout.redeem_script)[0] - ): - change_addr_type = AddressType.SH_WIT - else: - continue - - script_variant = self._convertAddrType( - change_addr_type, multisig=False - ) - change[n_vout] = { - "path": origin.path, - "variant": script_variant, - } - - elif num_signers > 1 and signing_multisigs: - # Multisig output - since we signed multisig inputs this could be our change - candidate_multisigs = { + # Skip automatic change validation in expert mode - user checks *every* output on hw + if not self.expert: + # If signing multisig inputs, get registered multisigs details in case we + # see any multisig outputs which may be change which we can auto-validate. + # ie. filter speculative 'signing multisigs' to ones actually registered on the hw + if signing_multisigs: + registered_multisigs = self.jade.get_registered_multisigs() + signing_multisigs = { k: v for k, v in signing_multisigs.items() - if len(v[2]) == num_signers + if k in registered_multisigs + and registered_multisigs[k]["variant"] == v[0] + and registered_multisigs[k]["threshold"] == v[1] + and registered_multisigs[k]["num_signers"] == len(v[2]) } - if not candidate_multisigs: - continue - for pubkey, origin in psbtout.hd_keypaths.items(): - if origin.fingerprint == master_fp and len(origin.path) > 0: - change_addr_type = None - if ( - txout.is_p2sh() - and not is_witness(psbtout.redeem_script)[0] - ): - change_addr_type = AddressType.LEGACY - scriptcode = psbtout.redeem_script - elif txout.is_p2wsh() and not txout.is_p2sh(): - change_addr_type = AddressType.WIT - scriptcode = psbtout.witness_script - elif ( - txout.is_p2sh() and is_witness(psbtout.redeem_script)[0] - ): - change_addr_type = AddressType.SH_WIT - scriptcode = psbtout.witness_script - else: - continue + # Look at every output... + for n_vout, (txout, psbtout) in py_enumerate( + zip(c_txn.vout, tx.outputs) + ): + num_signers = len(psbtout.hd_keypaths) + + if num_signers == 1 and signing_singlesigs: + # Single-sig output - since we signed singlesig inputs this could be our change + for pubkey, origin in psbtout.hd_keypaths.items(): + # Considers 'our' outputs as potential change as far as Jade is concerned + # ie. can be verified and auto-confirmed. + # Is this ok, or should check path also, assuming bip44-like ? + if origin.fingerprint == master_fp and len(origin.path) > 0: + change_addr_type = None + if txout.is_p2pkh(): + change_addr_type = AddressType.LEGACY + elif txout.is_witness()[0] and not txout.is_p2wsh(): + change_addr_type = AddressType.WIT # ie. p2wpkh + elif ( + txout.is_p2sh() + and is_witness(psbtout.redeem_script)[0] + ): + change_addr_type = AddressType.SH_WIT + else: + continue - parsed = parse_multisig(scriptcode) - if parsed: script_variant = self._convertAddrType( - change_addr_type, multisig=True - ) - threshold = parsed[0] - - pubkeys = parsed[1] - hd_keypath_origins = [ - psbtout.hd_keypaths[pubkey] for pubkey in pubkeys - ] - - signers, paths = _parse_signers(hd_keypath_origins) - - multisig_name = self._get_multisig_name( - script_variant, threshold, signers + change_addr_type, multisig=False ) - - matched_multisig = candidate_multisigs.get( - multisig_name - ) == (script_variant, threshold, signers) - if matched_multisig: - change[n_vout] = { - "paths": paths, - "multisig_name": multisig_name, - } + change[n_vout] = { + "path": origin.path, + "variant": script_variant, + } + + elif num_signers > 1 and signing_multisigs: + # Multisig output - since we signed multisig inputs this could be our change + candidate_multisigs = { + k: v + for k, v in signing_multisigs.items() + if len(v[2]) == num_signers + } + if not candidate_multisigs: + continue + + for pubkey, origin in psbtout.hd_keypaths.items(): + if origin.fingerprint == master_fp and len(origin.path) > 0: + change_addr_type = None + if ( + txout.is_p2sh() + and not is_witness(psbtout.redeem_script)[0] + ): + change_addr_type = AddressType.LEGACY + scriptcode = psbtout.redeem_script + elif txout.is_p2wsh() and not txout.is_p2sh(): + change_addr_type = AddressType.WIT + scriptcode = psbtout.witness_script + elif ( + txout.is_p2sh() + and is_witness(psbtout.redeem_script)[0] + ): + change_addr_type = AddressType.SH_WIT + scriptcode = psbtout.witness_script + else: + continue + + parsed = parse_multisig(scriptcode) + if parsed: + script_variant = self._convertAddrType( + change_addr_type, multisig=True + ) + threshold = parsed[0] + + pubkeys = parsed[1] + hd_keypath_origins = [ + psbtout.hd_keypaths[pubkey] + for pubkey in pubkeys + ] + + signers, paths = _parse_signers(hd_keypath_origins) + multisig_name = self._get_multisig_name( + script_variant, threshold, signers + ) + matched_multisig = candidate_multisigs.get( + multisig_name + ) + + if ( + matched_multisig + and matched_multisig[0] == script_variant + and matched_multisig[1] == threshold + and sorted(matched_multisig[2]) + == sorted(signers) + ): + change[n_vout] = { + "paths": paths, + "multisig_name": multisig_name, + } # The txn itself txn_bytes = c_txn.serialize_without_witness() @@ -499,23 +479,25 @@ def sign_message(self, message: Union[str, bytes], bip32_path: str) -> str: path = parse_path(bip32_path) if isinstance(message, bytes) or isinstance(message, bytearray): message = message.decode("utf-8") + + # NOTE: tests fail if we try to use AE signatures, so stick with default (rfc6979) signature = self.jade.sign_message(path, message) - return signature + return str(signature) - # Display address of specified type on the device. Only supports single-key based addresses atm. + # Display address of specified type on the device. @jade_exception def display_singlesig_address(self, bip32_path: str, addr_type: AddressType) -> str: path = parse_path(bip32_path) - addr_type = self._convertAddrType(addr_type) + script_variant = self._convertAddrType(addr_type, multisig=False) address = self.jade.get_receive_address( - self._network(), path, variant=addr_type + self._network(), path, variant=script_variant ) - return address + return str(address) + # Display multisig address of specified type on the device. + @jade_exception def display_multisig_address( - self, - addr_type: AddressType, - multisig: MultisigDescriptor, + self, addr_type: AddressType, multisig: MultisigDescriptor ) -> str: signer_origins = [] signers = [] @@ -531,7 +513,15 @@ def display_multisig_address( ) if pubkey.deriv_path is None: raise BadArgumentError( - "Blockstream Jade can only generate addresses for multisigs with key origin derivation path information" + "Blockstream Jade can only generate addresses for multisigs with key derivation paths" + ) + + if pubkey.origin.path and not is_hardened(pubkey.origin.path[-1]): + logging.warning( + f"Final element of origin path {pubkey.origin.path} unhardened" + ) + logging.warning( + "Blockstream Jade may not be able to identify change sent back to this descriptor" ) # Tuple to derive deterministic name for the registrtion @@ -555,12 +545,13 @@ def display_multisig_address( ) paths.append(parse_path(path)) - # sort origins, signers and paths according to origins (like in _get_multisig_name) - signer_origins, signers, paths = [ - list(a) for a in zip(*sorted(zip(signer_origins, signers, paths))) - ] + if multisig.is_sorted and paths[:-1] != paths[1:]: + logging.warning("Sorted multisig with different derivations per signer") + logging.warning( + "Blockstream Jade may not be able to validate change sent back to this descriptor" + ) - # Get a deterministic name for this multisig wallet + # Get a deterministic name for this multisig wallet (ignoring bip67 key sorting) script_variant = self._convertAddrType(addr_type, multisig=True) multisig_name = self._get_multisig_name( script_variant, multisig.thresh, signer_origins @@ -572,7 +563,7 @@ def display_multisig_address( self._network(), multisig_name, script_variant, - True, # always use sorted + multisig.is_sorted, multisig.thresh, signers, ) @@ -582,262 +573,152 @@ def display_multisig_address( return str(address) + # Custom Specter method - register multisig on the Jade + @jade_exception + def register_multisig(self, descriptor: str) -> None: + + descriptor = Descriptor.from_string(descriptor) + signer_origins = [] + signers = [] + paths = [] + for key in descriptor.keys: + # Tuple to derive deterministic name for the registration + signer_origins.append((key.origin.fingerprint, key.origin.derivation)) + + # We won't include the additional path in the multisig registration + signers.append( + { + "fingerprint": key.fingerprint, + "derivation": key.derivation, + "xpub": key.key.to_string(), + "path": [], + } + ) + + # Get a deterministic name for this multisig wallet (ignoring bip67 key sorting) + if descriptor.wsh and not descriptor.sh: + addr_type = AddressType.WIT + elif descriptor.wsh and descriptor.sh: + addr_type = AddressType.SH_WIT + elif descriptor.wsh.is_legacy: + addr_type = AddressType.LEGACY + else: + raise BadArgumentError( + "The script type of the descriptor does not match any standard type." + ) + + script_variant = self._convertAddrType(addr_type, multisig=True) + threshold = descriptor.miniscript.args[0].num # hackish ... + + multisig_name = self._get_multisig_name( + script_variant, threshold, signer_origins + ) + + # 're-registering' is a no-op + self.jade.register_multisig( + self._network(), + multisig_name, + script_variant, + descriptor.is_sorted, + threshold, + signers, + ) + # Setup a new device - def setup_device(self, label="", passphrase=""): + def setup_device(self, label: str = "", passphrase: str = "") -> bool: """ - The Blockstream Jade does not support setup via software. + Blockstream Jade does not support setup via software. :raises UnavailableActionError: Always, this function is unavailable """ - raise UnavailableActionError( - "The Blockstream Jade does not support software setup" - ) + raise UnavailableActionError("Blockstream Jade does not support software setup") # Wipe this device - def wipe_device(self): + def wipe_device(self) -> bool: """ - The Blockstream Jade does not support wiping via software. + Blockstream Jade does not support wiping via software. :raises UnavailableActionError: Always, this function is unavailable """ raise UnavailableActionError( - "The Blockstream Jade does not support wiping via software" + "Blockstream Jade does not support wiping via software" ) # Restore device from mnemonic or xprv - def restore_device(self, label="", word_count=24): + def restore_device(self, label: str = "", word_count: int = 24) -> bool: """ - The Blockstream Jade does not support restoring via software. + Blockstream Jade does not support restoring via software. :raises UnavailableActionError: Always, this function is unavailable """ raise UnavailableActionError( - "The Blockstream Jade does not support restoring via software" + "Blockstream Jade does not support restoring via software" ) # Begin backup process - def backup_device(self, label="", passphrase=""): + def backup_device(self, label: str = "", passphrase: str = "") -> bool: """ - The Blockstream Jade does not support backing up via software. + Blockstream Jade does not support backing up via software. :raises UnavailableActionError: Always, this function is unavailable """ raise UnavailableActionError( - "The Blockstream Jade does not support creating a backup via software" + "Blockstream Jade does not support creating a backup via software" ) # Close the device - def close(self): + def close(self) -> None: self.jade.disconnect() # Prompt pin - def prompt_pin(self): + def prompt_pin(self) -> bool: """ - The Blockstream Jade does not need a PIN sent from the host. + Blockstream Jade does not need a PIN sent from the host. :raises UnavailableActionError: Always, this function is unavailable """ raise UnavailableActionError( - "The Blockstream Jade does not need a PIN sent from the host" + "Blockstream Jade does not need a PIN sent from the host" ) # Send pin - def send_pin(self, pin): + def send_pin(self, pin: str) -> bool: """ - The Blockstream Jade does not need a PIN sent from the host. + Blockstream Jade does not need a PIN sent from the host. :raises UnavailableActionError: Always, this function is unavailable """ raise UnavailableActionError( - "The Blockstream Jade does not need a PIN sent from the host" + "Blockstream Jade does not need a PIN sent from the host" ) # Toggle passphrase - def toggle_passphrase(self): + def toggle_passphrase(self) -> bool: """ - The Blockstream Jade does not support toggling passphrase from the host. + Blockstream Jade does not support toggling passphrase from the host. :raises UnavailableActionError: Always, this function is unavailable """ raise UnavailableActionError( - "The Blockstream Jade does not support toggling passphrase from the host" + "Blockstream Jade does not support toggling passphrase from the host" ) - def _blind(self, pset, seed: bytes = None): - if seed is None: - seed = pset.unknown.get(b"\xfc\x07specter\x00", os.urandom(32)) - txseed = pset.txseed(seed) - # assign blinding factors to all outputs - blinding_outs = [] - commitments = [] - # because we do sha once (cause taproot), and they want sha twice - hash_prevouts = hashes.sha256(pset.blinded_tx.hash_prevouts()) - last_i = 0 - last_commitment = {} - for i, out in py_enumerate(pset.outputs): - # skip ones where we don't need blinding - if out.blinding_pubkey is None: - commitments.append(None) - continue - commitment = self.jade.get_commitments( - bytes(reversed(out.asset)), out.value, hash_prevouts, i, vbf=None - ) - commitment["blinding_key"] = out.blinding_pubkey - commitments.append(commitment) - last_i = i - last_commitment = commitments[-1] - out.asset_blinding_factor = commitment["abf"] - out.value_blinding_factor = commitment["vbf"] - blinding_outs.append(out) - if len(blinding_outs) == 0: - raise Exception("Nothing to blind") - # calculate last vbf - vals = [] - abfs = [] - vbfs = [] - for sc in pset.inputs + blinding_outs: - value = sc.value if sc.value is not None else sc.utxo.value - asset = sc.asset or sc.utxo.asset - if not (isinstance(value, int) and len(asset) == 32): - continue - vals.append(value) - abfs.append(sc.asset_blinding_factor or b"\x00" * 32) - vbfs.append(sc.value_blinding_factor or b"\x00" * 32) - last_vbf = secp256k1.pedersen_blind_generator_blind_sum( - vals, abfs, vbfs, len(vals) - len(blinding_outs) - ) - last_out = blinding_outs[-1] - new_last_commitment = self.jade.get_commitments( - bytes(reversed(last_out.asset)), - last_out.value, - hash_prevouts, - last_i, - vbf=last_vbf, - ) - # check abf didn't change - assert new_last_commitment["abf"] == last_out.asset_blinding_factor - # set new values in the last commitment - last_commitment.update(new_last_commitment) - blinding_outs[-1].value_blinding_factor = last_vbf - - # calculate commitments (surj proof etc) - in_tags = [] - in_gens = [] - for inp in pset.inputs: - if inp.asset: - in_tags.append(inp.asset) - in_gens.append(secp256k1.generator_parse(inp.utxo.asset)) - # if we have unconfidential input - elif len(inp.utxo.asset) == 32: - in_tags.append(inp.utxo.asset) - in_gens.append(secp256k1.generator_generate(inp.utxo.asset)) - - for i, out in py_enumerate(pset.outputs): - if None in [out.blinding_pubkey, out.value, out.asset_blinding_factor]: - continue - gen = secp256k1.generator_generate_blinded( - out.asset, out.asset_blinding_factor - ) - out.asset_commitment = secp256k1.generator_serialize(gen) - value_commitment = secp256k1.pedersen_commit( - out.value_blinding_factor, out.value, gen - ) - out.value_commitment = secp256k1.pedersen_commitment_serialize( - value_commitment - ) + @jade_exception + def can_sign_taproot(self) -> bool: + """ + Blockstream Jade does not currently support Taproot. - proof_seed = hashes.tagged_hash( - "liquid/surjection_proof", txseed + i.to_bytes(4, "little") - ) - proof, in_idx = secp256k1.surjectionproof_initialize( - in_tags, out.asset, proof_seed - ) - secp256k1.surjectionproof_generate( - proof, in_idx, in_gens, gen, abfs[in_idx], out.asset_blinding_factor - ) - out.surjection_proof = secp256k1.surjectionproof_serialize(proof) - del proof + :returns: False, always + """ + return False - # generate range proof - rangeproof_nonce = hashes.tagged_hash( - "liquid/range_proof", txseed + i.to_bytes(4, "little") - ) - # reblind with extra message for unblinding of change outs - extra_message = ( - out.unknown.get(b"\xfc\x07specter\x01", b"") - if out.bip32_derivations - else b"" - ) - out.reblind( - rangeproof_nonce, - extra_message=extra_message, - ) - return commitments - - def sign_pset(self, b64pset: str) -> str: - """Signs specter-desktop specific Liquid PSET transaction""" - mfp = self.get_master_fingerprint() - pset = PSET.from_string(b64pset) - commitments = self._blind(pset) - ins = [ - { - "is_witness": True, - # "input_tx": inp.non_witness_utxo.serialize(), - "script": inp.witness_script.data - if inp.witness_script - else script.p2pkh_from_p2wpkh(inp.script_pubkey).data, - "value_commitment": write_commitment(inp.utxo.value), - "path": [ - der - for der in inp.bip32_derivations.values() - if der.fingerprint == mfp - ][0].derivation, - } - for inp in pset.inputs - ] - change = [ - { - "path": [ - der - for pub, der in out.bip32_derivations.items() - if der.fingerprint == mfp - ][0].derivation, - "variant": self._get_script_type(out), - } - if out.bip32_derivations and self._get_script_type(out) is not None - else None - for out in pset.outputs - ] - rawtx = pset.blinded_tx.serialize() - - signatures = self.jade.sign_liquid_tx( - self._network(), rawtx, ins, commitments, change - ) - for i, inp in py_enumerate(pset.inputs): - inp.partial_sigs[ - [ - pub - for pub, der in inp.bip32_derivations.items() - if der.fingerprint == mfp - ][0] - ] = signatures[i] - # we must finalize here because it has different commitments and only supports singlesig - return str(finalize_psbt(pset)) - - def _get_script_type(self, out): - if out.script_pubkey.script_type() == "p2pkh": - return "pkh(k)" - elif out.script_pubkey.script_type() == "p2wpkh": - return "wpkh(k)" - elif out.script_pubkey.script_type() == "p2sh": - if out.redeem_script.script_type() == "p2wpkh": - return "sh(wpkh(k))" - # otherwise None - return None - - -def enumerate(password: str = "") -> List[Dict[str, Any]]: +def enumerate( + password: Optional[str] = None, + expert: bool = False, + chain: Chain = Chain.MAIN, + skip_unlocking=True, +) -> List[Dict[str, Any]]: results = [] def _get_device_entry(device_model: str, device_path: str) -> Dict[str, Any]: @@ -850,9 +731,13 @@ def _get_device_entry(device_model: str, device_path: str) -> Dict[str, Any]: client = None with handle_errors(common_err_msgs["enumerate"], d_data): - client = JadeClient(device_path, password, timeout=1) - d_data["fingerprint"] = client.get_master_fingerprint().hex() - + client = JadeClient( + device_path, password, expert, chain, skip_unlocking, timeout=1 + ) + # The Jade could already be unlocked upon startup (this is the only instance where unlock_required is False right now). + # But, we don't need the fingerpint then. + if client and not skip_unlocking: + d_data["fingerprint"] = client.get_master_fingerprint().hex() if client: client.close() @@ -873,9 +758,9 @@ def _get_device_entry(device_model: str, device_path: str) -> Dict[str, Any]: if verinfo is not None: results.append(_get_device_entry("jade_simulator", SIMULATOR_PATH)) - except ConnectionRefusedError as e: + except Exception as e: # If we get any sort of error do not add the simulator - logger.debug(f"Failed to connect to Jade simulator at {SIMULATOR_PATH}") - logger.debug(e) + logging.debug(f"Failed to connect to Jade simulator at {SIMULATOR_PATH}") + logging.debug(e) return results diff --git a/src/cryptoadvance/specter/devices/hwi/jadepy/__init__.py b/src/cryptoadvance/specter/devices/hwi/jadepy/__init__.py index 5079bd9b70..64e2ceb7e1 100644 --- a/src/cryptoadvance/specter/devices/hwi/jadepy/__init__.py +++ b/src/cryptoadvance/specter/devices/hwi/jadepy/__init__.py @@ -1,4 +1,4 @@ from .jade import JadeAPI from .jade_error import JadeError -__version__ = "0.0.1" +__version__ = "0.2.0" diff --git a/src/cryptoadvance/specter/devices/hwi/jadepy/jade.py b/src/cryptoadvance/specter/devices/hwi/jadepy/jade.py index 4a03dc0288..4c9b882bff 100644 --- a/src/cryptoadvance/specter/devices/hwi/jadepy/jade.py +++ b/src/cryptoadvance/specter/devices/hwi/jadepy/jade.py @@ -1,4 +1,4 @@ -import cbor +import cbor2 as cbor import hashlib import json import time @@ -9,7 +9,6 @@ import random import sys - # JadeError from .jade_error import JadeError @@ -17,10 +16,20 @@ from .jade_serial import JadeSerialImpl from .jade_tcp import JadeTCPImpl -# from .jade_ble import JadeBleImpl +# 'jade' logger +logger = logging.getLogger(__name__) +device_logger = logging.getLogger(f"{__name__}-device") + +# BLE comms backend is optional +# It relies on the BLE dependencies being available +try: + from .jade_ble import JadeBleImpl +except ImportError as e: + logger.warning(e) + logger.warning("BLE scanning/connectivity will not be available") + # Default serial connection -DEFAULT_SERIAL_DEVICE = "/dev/ttyUSB0" DEFAULT_BAUD_RATE = 115200 DEFAULT_SERIAL_TIMEOUT = 120 @@ -29,14 +38,20 @@ DEFAULT_BLE_SERIAL_NUMBER = None DEFAULT_BLE_SCAN_TIMEOUT = 60 -# 'jade' logger -logger = logging.getLogger("jade") -device_logger = logging.getLogger("jade-device") - -# Helper to map bytes-like types into hex-strings -# to make for prettier message-logging def _hexlify(data): + """ + Helper to map bytes-like types into hex-strings + to make for prettier message-logging. + + Parameters + ---------- + data : any + The object to hexlify. + - bytes or bytearrays have 'hex()' method invoked + - list and dicts (values) have this function mapped over them + - Otherwise the input is returned unchanged + """ if data is None: return None elif isinstance(data, bytes) or isinstance(data, bytearray): @@ -49,49 +64,87 @@ def _hexlify(data): return data -import requests +try: + import requests + def _http_request(params): + """ + Simple http request function which can be used when a Jade response + requires an external http call. + The default implementation used in JadeAPI._jadeRpc() below. + NOTE: Only available if the 'requests' dependency is available. -def _http_request(params): - logger.debug("_http_request: {}".format(params)) + Callers can supply their own implmentation of this call where it is required. - # Use the first non-onion url - url = [url for url in params["urls"] if not url.endswith(".onion")][0] - if params["method"] == "GET": - assert "data" not in params, "Cannot pass body to requests.get" - f = requests.get(url) - elif params["method"] == "POST": - data = json.dumps(params["data"]) - f = requests.post(url, data) + Parameters + ---------- + data : dict + A dictionary structure describing the http call to make - logger.debug("http_request received reply: {}".format(f.text)) + Returns + ------- + dict + with single key 'body', whose value is the json returned from the call - if f.status_code != 200: - logger.error("http error {} : {}".format(f.status_code, f.text)) - raise ValueError(f.status_code) + """ + logger.debug("_http_request: {}".format(params)) - assert params["accept"] == "json" - f = f.json() + # Use the first non-onion url + url = [url for url in params["urls"] if not url.endswith(".onion")][0] - return {"body": f} + if params["method"] == "GET": + assert "data" not in params, "Cannot pass body to requests.get" + + def http_call_fn(): + return requests.get(url) + + elif params["method"] == "POST": + data = json.dumps(params["data"]) + + def http_call_fn(): + return requests.post(url, data) + + else: + raise JadeError(1, "Only GET and POST methods supported", params["method"]) + + try: + f = http_call_fn() + logger.debug("http_request received reply: {}".format(f.text)) + + if f.status_code != 200: + logger.error("http error {} : {}".format(f.status_code, f.text)) + raise ValueError(f.status_code) + + assert params["accept"] == "json" + f = f.json() + except Exception as e: + logging.error(e) + f = None + + return {"body": f} + +except ImportError as e: + logger.info(e) + logger.info("Default _http_requests() function will not be available") -# -# High-Level Jade Client API -# Builds on a JadeInterface to provide a meaningful API -# -# Either: -# a) use with JadeAPI.create_[serial|ble]() as jade: -# (recommended) -# or: -# b) use JadeAPI.create_[serial|ble], then call connect() before -# using, and disconnect() when finished -# (caveat cranium) -# or: -# c) use ctor to wrap existing JadeInterface instance -# (caveat cranium) -# class JadeAPI: + """ + High-Level Jade Client API + Builds on a JadeInterface to provide a meaningful API + + Either: + a) use with JadeAPI.create_[serial|ble]() as jade: + (recommended) + or: + b) use JadeAPI.create_[serial|ble], then call connect() before + using, and disconnect() when finished + (caveat cranium) + or: + c) use ctor to wrap existing JadeInterface instance + (caveat cranium) + """ + def __init__(self, jade): assert jade is not None self.jade = jade @@ -102,44 +155,134 @@ def __enter__(self): def __exit__(self, exc_type, exc, tb): if exc_type: - logger.error("Exception causing JadeAPI context exit.") - logger.error(exc_type) - logger.error(exc) + logger.info("Exception causing JadeAPI context exit.") + logger.info(exc_type) + logger.info(exc) traceback.print_tb(tb) self.disconnect(exc_type is not None) @staticmethod def create_serial(device=None, baud=None, timeout=None): + """ + Create a JadeAPI object using the serial interface described. + + Parameters + ---------- + device : str, optional + The device identifier for the serial device. + Underlying implementation will default (to /dev/ttyUSB0) + + baud : int, optional + The communication baud rate. + Underlying implementation will default (to 115200) + + timeout : int, optional + The serial read timeout when awaiting messages. + Underlying implementation will default (to 120s) + + Returns + ------- + JadeAPI + API object configured to use given serial parameters. + NOTE: the api instance has not yet tried to contact the hw + - caller must call 'connect()' before trying to use the Jade. + """ impl = JadeInterface.create_serial(device, baud, timeout) return JadeAPI(impl) @staticmethod def create_ble(device_name=None, serial_number=None, scan_timeout=None, loop=None): + """ + Create a JadeAPI object using the BLE interface described. + NOTE: raises JadeError if BLE dependencies not installed. + + Parameters + ---------- + device_name : str, optional + The device name of the desired BLE device. + Underlying implementation will default (to 'Jade') + + serial_number : int, optional + The serial number of the desired BLE device + - used to disambiguate multiple beacons with the same 'device name' + Underlying implementation will connect to the first beacon it scans + with the matching 'device name'. + + scan_timeout : int, optional + The timeout when scanning for devices which match the device name/serial number. + Underlying implementation will default (to 60s) + + loop : optional + The asynchio event loop to use, if required. + Underlying implementation will default (to asyncio.get_event_loop()) + + Returns + ------- + JadeAPI + API object configured to use given BLE parameters. + NOTE: the api instance has not yet tried to contact the hw + - caller must call 'connect()' before trying to use the Jade. + + Raises + ------ + JadeError if BLE backend not available (ie. BLE dependencies not installed) + """ impl = JadeInterface.create_ble(device_name, serial_number, scan_timeout, loop) return JadeAPI(impl) - # Connect underlying interface def connect(self): + """ + Try to connect the underlying transport interface (eg. serial, ble, etc.) + Raises an exception on failure. + """ self.jade.connect() - # Disconnect underlying interface def disconnect(self, drain=False): + """ + Disconnect the underlying transport (eg. serial, ble, etc.) + + Parameters + ---------- + drain : bool, optional + When true log any/all remaining messages/data, otherwise silently discard. + NOTE: can prevent disconnection if data is arriving constantly. + Defaults to False. + """ self.jade.disconnect(drain) - # Drain all output from the interface def drain(self): + """ + Log any/all outstanding messages/data. + NOTE: can run indefinitely if data is arriving constantly. + """ self.jade.drain() - # Raise any returned error as an exception @staticmethod def _get_result_or_raise_error(reply): + """ + Raise any error message returned from a Jade rpc call as an exception. + + Parameters + ---------- + reply : dict + Dictionary representing a reply from a Jade rpc call. + + Returns + ------- + dict + Any nested 'result' structure, if the reply is not an error. + + Raises + ------ + JadeError + If the reply represented an error, including all details received. + """ if "error" in reply: e = reply["error"] raise JadeError(e.get("code"), e.get("message"), e.get("data")) return reply["result"] - # Helper to call wrapper interface rpc invoker def _jadeRpc( self, method, @@ -148,6 +291,45 @@ def _jadeRpc( http_request_fn=None, long_timeout=False, ): + """ + Helper to make a request/reply rpc call over the underlying transport interface. + NOTE: interface must be 'connected'. + + If the call returns an 'http_request' structure, this is handled here and the http + call is made, and the result is passed into the rpc method given in 'on reply', by + calling this function recursively. + + Parameters + ---------- + method : str + rpc method to invoke + + params : dict, optional + any parameters to pass to the rpc method + Defaults to None. + + inputid : str, optional + Any specific 'id' to use in the rpc message. + Defaults to a using a pseudo-random id generated in-situ. + + http_request_fn : function, optional + A function which accepts a dict (containing a description of the http request), makes + the described http call, and returns the body data in an element called 'body'. + Defaults to _http_request() above. + + long_timeout : bool, optional + Whether the rpc call should use an indefinitely long timeout, rather than that set on + construction. + (Useful if the call involves a non-trivial user interaction with the device.) + Defaults to False. + + Returns + ------- + dict + The reply from the rpc call. + NOTE: will return the last/final reply after a sequence of calls, where 'http_request' + was returned and remote data was fetched and passed into s subsequent call. + """ newid = inputid if inputid else str(random.randint(100000, 999999)) request = self.jade.build_request(newid, method, params) reply = self.jade.make_rpc_call(request, long_timeout) @@ -177,27 +359,150 @@ def _jadeRpc( return result - # Get version information from the hw - def get_version_info(self): - return self._jadeRpc("get_version_info") + def ping(self): + """ + RPC call to test the connection to Jade and that Jade is powered on and receiving data, and + return whether the main task is currently handling a message, handling user menu navigation + or is idle. + + NOTE: unlike all other calls this is not queued and handled in fifo order - this message is + handled immediately and the response sent as quickly as possible. This call does not block. + If this call is made in parallel with Jade processing other messages, the replies may be + out of order (although the message 'id' should still be correct). Use with caution. + + Returns + ------- + 0 if the main task is currently idle + 1 if the main task is handling a client message + 2 if the main task is handling user ui menu navigation + """ + return self._jadeRpc("ping") + + def get_version_info(self, nonblocking=False): + """ + RPC call to fetch summary details pertaining to the hardware unit and running firmware. + + Parameters + ---------- + nonblocking : bool + If True message will be handled immediately (see also ping()) *experimental feature* + + Returns + ------- + dict + Contains keys for various info describing the hw and running fw + """ + params = {"nonblocking": True} if nonblocking else None + return self._jadeRpc("get_version_info", params) - # Add client entropy to the hw rng def add_entropy(self, entropy): + """ + RPC call to add client entropy into the unit RNG entropy pool. + + Parameters + ---------- + entropy : bytes + Bytes to fold into the hw entropy pool. + + Returns + ------- + bool + True on success + """ params = {"entropy": entropy} return self._jadeRpc("add_entropy", params) - # OTA new firmware - def ota_update(self, fwcmp, fwlen, chunksize, cb): - + def set_epoch(self, epoch=None): + """ + RPC call to set the current time epoch value, required for TOTP use. + NOTE: The time is lost on each power-down and must be reset on restart/reconnect before + TOTP can be used. + + Parameters + ---------- + epoch : int, optional + Current epoch value, in seconds. Defaults to int(time.time()) value. + + Returns + ------- + bool + True on success + """ + params = {"epoch": epoch if epoch is not None else int(time.time())} + return self._jadeRpc("set_epoch", params) + + def logout(self): + """ + RPC call to logout of any wallet loaded on the Jade unit. + Any key material is freed and zero'd. + Call always returns true. + + Returns + ------- + bool + True + """ + return self._jadeRpc("logout") + + def ota_update(self, fwcmp, fwlen, chunksize, fwhash=None, patchlen=None, cb=None): + """ + RPC call to attempt to update the unit's firmware. + + Parameters + ---------- + fwcmp : bytes + The compressed firmware image to upload to the Jade unit. Can be a full firmware or + and incremental diff to be applied to the currently running firmware image. + fwlen : int + The size of the new complete (uncompressed) firmware image (after any delta is applied). + chunksize : int + The size of the chunks used to upload the compressed firmware. Each chunk is uploaded + and ack'd by the hw unit. + The maximum supported chunk size is given in the version info data, under the key + 'JADE_OTA_MAX_CHUNK'. + fwhash: 32-bytes, optional + The sha256 hash of the full uncompressed final firmware image. In the case of a full + firmware upload this should be the hash of the uncompressed file. In the case of a + delta update this is the hash of the expected final image - ie. the existing firmware + with the uploaded delta applied. ie. it is a verification of the fw image Jade will try + to boot. Optional for backward-compatibility - may become mandatory in a future release. + patchlen: int, optional + If the compressed firmware bytes are an incremental diff to be applied to the running + firmware image, this is the size of that patch when uncompressed. + Defaults to None, implying the compressed data is a full firmware image upload. + (Compare with fwlen - the size of the final fw image.) + cb : function, optional + Callback function accepting two integers - the amount of compressed firmware sent thus + far, and the total length of the compressed firmware to send. + If passed, this function is invoked each time a fw chunk is successfully uploaded and + ack'd by the hw, to notify of upload progress. + Defaults to None, and nothing is called to report upload progress. + + Returns + ------- + bool + True if no errors were reported - on next restart the hw unit will attempt to boot the + new firmware. + """ + + # Compute the sha256 hash of the compressed file being uploaded cmphasher = hashlib.sha256() cmphasher.update(fwcmp) cmphash = cmphasher.digest() cmplen = len(fwcmp) # Initiate OTA + ota_method = "ota" params = {"fwsize": fwlen, "cmpsize": cmplen, "cmphash": cmphash} - result = self._jadeRpc("ota", params) + if fwhash is not None: + params["fwhash"] = fwhash + + if patchlen is not None: + ota_method = "ota_delta" + params["patchsize"] = patchlen + + result = self._jadeRpc(ota_method, params) assert result is True # Write binary chunks @@ -216,12 +521,97 @@ def ota_update(self, fwcmp, fwlen, chunksize, cb): # All binary data uploaded return self._jadeRpc("ota_complete") - # Run (debug) healthcheck on the hw def run_remote_selfcheck(self): + """ + RPC call to run in-built tests. + NOTE: Only available in a DEBUG build of the firmware. + + Returns + ------- + int + Time in ms for the internal tests to run, as measured on the hw. + ie. excluding any messaging overhead + """ return self._jadeRpc("debug_selfcheck", long_timeout=True) - # Set the (debug) mnemonic + def capture_image_data(self, check_qr=False): + """ + RPC call to capture raw image data from the camera. + See also scan_qr() below. + NOTE: Only available in a DEBUG build of the firmware. + + Parameters + ---------- + check_qr : bool, optional + If True only images which contain a valid qr code are captured and returned. + If False, any image is considered valid and is returned. + Defaults to False + + Returns + ------- + bytes + Raw image data from the camera framebuffer + """ + params = {"check_qr": check_qr} + return self._jadeRpc("debug_capture_image_data", params) + + def scan_qr(self, image): + """ + RPC call to scan a passed image and return any data extracted from any qr image. + Exercises the camera image capture, but ignores result and uses passed image instead. + See also capture_image_data() above. + NOTE: Only available in a DEBUG build of the firmware. + + Parameters + ---------- + image : bytes + The image data (as obtained from capture_image_data() above). + + Returns + ------- + bytes + String or byte data obtained from the image (via qr code) + """ + params = {"image": image} + return self._jadeRpc("debug_scan_qr", params) + + def clean_reset(self): + """ + RPC call to clean/reset memory and storage, as much as is practical. + NOTE: Only available in a DEBUG build of the firmware. + + Returns + ------- + bool + True on success. + """ + return self._jadeRpc("debug_clean_reset") + def set_mnemonic(self, mnemonic, passphrase=None, temporary_wallet=False): + """ + RPC call to set the wallet mnemonic (in RAM only - flash storage is untouched). + NOTE: Only available in a DEBUG build of the firmware. + + Parameters + ---------- + mnemonic : str + The wallet mnemonic to set. + + passphrase : str, optional + Any bip39 passphrase to apply. + Defaults to None. + + temporary_wallet : bool, optional + Whether to treat this wallet/mnemonic as an 'Emergency Restore' temporary wallet, as + opposed to one successfully loaded from the flash storage. + NOTE: in either case the wallet is only set in RAM, and flash storage is not affected. + Defaults to False. + + Returns + ------- + bool + True on success. + """ params = { "mnemonic": mnemonic, "passphrase": passphrase, @@ -229,13 +619,81 @@ def set_mnemonic(self, mnemonic, passphrase=None, temporary_wallet=False): } return self._jadeRpc("debug_set_mnemonic", params) - # Set the (debug) seed - def set_seed(self, seed, temporary_wallet=False): - params = {"seed": seed, "temporary_wallet": temporary_wallet} + def set_seed(self, seed): + """ + RPC call to set the wallet seed. + NOTE: Only available in a DEBUG build of the firmware. + NOTE: Setting a seed always sets a 'temporary' wallet. + + Parameters + ---------- + seed : bytes + The wallet seed to set as a temporary wallet (cannot be persisted in flash). + + Returns + ------- + bool + True on success. + """ + params = {"seed": seed} return self._jadeRpc("debug_set_mnemonic", params) - # Override the pinserver details on the hww + def get_bip85_bip39_entropy(self, num_words, index, pubkey): + """ + RPC call to fetch encrypted bip85-bip39 entropy. + NOTE: Only available in a DEBUG build of the firmware. + + Parameters + ---------- + num_words : int + The number of words the entropy is required to produce. + + index : int + The index to use in the bip32 path to calcuate the entropy. + + pubkey: 33-bytes + The host ephemeral pubkey to use to generate a shared ecdh secret to use as an AES key + to encrypt the returned entropy. + + Returns + ------- + dict + pubkey - 33-bytes, Jade's ephemeral pubkey used to generate a shared ecdh secret used as + an AES key to encrypt the returned entropy + encrypted - bytes, the requested bip85 bip39 entropy, AES encrypted with the first key + derived from the ecdh shared secret, prefixed with the iv + hmac - 32-bytes, the hmac of the encrypted buffer, using the second key derived from the + ecdh shared secret + """ + params = {"num_words": num_words, "index": index, "pubkey": pubkey} + return self._jadeRpc("get_bip85_bip39_entropy", params) + def set_pinserver(self, urlA=None, urlB=None, pubkey=None, cert=None): + """ + RPC call to explicitly set (override) the details of the blind pinserver used to + authenticate the PIN entered on the Jade unit. + This data is recorded in the hw flash, and returned to the caller when authenticating + (in auth_user(), below). + + Parameters + ---------- + urlA : str, optional + The primary url of the pinserver to use. + + urlB : str, optional + Any secondary url of the pinserver to use. + + pubkey : bytes, optional + The public key used to verify pinserver signed payloads. + + cert : bytes, optional + Any additional certificate required to verify the pinserver identity. + + Returns + ------- + bool + True on success. + """ params = {} if urlA is not None or urlB is not None: params["urlA"] = urlA @@ -246,35 +704,244 @@ def set_pinserver(self, urlA=None, urlB=None, pubkey=None, cert=None): params["certificate"] = cert return self._jadeRpc("update_pinserver", params) - # Reset the pinserver details on the hww to their defaults def reset_pinserver(self, reset_details, reset_certificate): + """ + RPC call to reset any formerly overidden pinserver details to their defauts. + + Parameters + ---------- + reset_details : bool, optional + If set, any overidden urls and pubkey are reset to their defaults. + + reset_certificate : bool, optional + If set, any additional certificate is reset (to None). + + Returns + ------- + bool + True on success. + """ params = { "reset_details": reset_details, "reset_certificate": reset_certificate, } return self._jadeRpc("update_pinserver", params) - # Trigger user authentication on the hw - # Involves pinserver handshake - def auth_user(self, network, http_request_fn=None): - params = {"network": network} + def auth_user(self, network, http_request_fn=None, epoch=None): + """ + RPC call to authenticate the user on the hw device, for using with the network provided. + + Parameters + ---------- + network : str + The name of the network intended for use - eg. 'mainnet', 'liquid', 'testnet' etc. + This is verified against the networks allowed on the hardware. + + http_request_fn : function, optional + Optional http-request function to pass http requests to the Jade pinserver. + Default behaviour is to use the '_http_request()' function which defers to the + 'requests' module. + If the 'reqests' module is not available, no default http-request function is created, + and one must be supplied here. + + epoch : int, optional + Current epoch value, in seconds. Defaults to int(time.time()) value. + + Returns + ------- + bool + True is returned immediately if the hw is already unlocked for use on the given network. + True if the PIN is entered and verified with the remote blind pinserver. + False if the PIN entered was incorrect. + """ + params = { + "network": network, + "epoch": epoch if epoch is not None else int(time.time()), + } return self._jadeRpc( "auth_user", params, http_request_fn=http_request_fn, long_timeout=True ) - # Get xpub given a path + def register_otp(self, otp_name, otp_uri): + """ + RPC call to register a new OTP record on the hw device. + + Parameters + ---------- + otp_name : str + An identifying name for this OTP record + + otp_uri : str + The uri of this OTP record - must begin 'otpauth://' + + Returns + ------- + bool + True if the OTP uri was validated and persisted on the hw + """ + params = {"name": otp_name, "uri": otp_uri} + return self._jadeRpc("register_otp", params) + + def get_otp_code(self, otp_name, value_override=None): + """ + RPC call to fetch a new OTP code from the hw device. + + Parameters + ---------- + otp_name : str + An identifying name for the OTP record to use + + value_override : int + An overriding HOTP counter or TOTP timestamp to use. + NOTE: Only available in a DEBUG build of the firmware. + + Returns + ------- + bool + True if the OTP uri was validated and persisted on the hw + """ + params = {"name": otp_name} + if value_override is not None: + params["override"] = value_override + return self._jadeRpc("get_otp_code", params) + def get_xpub(self, network, path): + """ + RPC call to fetch an xpub for the given bip32 path for the given network. + + Parameters + ---------- + network : str + Network to which the xpub applies - eg. 'mainnet', 'liquid', 'testnet', etc. + + path : [int] + bip32 path for which the xpub should be generated. + + Returns + ------- + str + base58 encoded xpub + """ params = {"network": network, "path": path} return self._jadeRpc("get_xpub", params) - # Get registered multisig wallets def get_registered_multisigs(self): + """ + RPC call to fetch brief summaries of any multisig wallets registered to this signer. + + Returns + ------- + dict + Brief description of registered multisigs, keyed by registration name. + Each entry contains keys: + variant - str, script type, eg. 'sh(wsh(multi(k)))' + sorted - boolean, whether bip67 key sorting is applied + threshold - int, number of signers required,N + num_signers - total number of signatories, M + master_blinding_key - 32-bytes, any liquid master blinding key for this wallet + """ return self._jadeRpc("get_registered_multisigs") - # Register a multisig wallet + def get_registered_multisig(self, multisig_name, as_file=False): + """ + RPC call to fetch details of a named multisig wallet registered to this signer. + NOTE: the multisig wallet must have been registered with firmware v1.0.23 or later + for the full signer details to be persisted and available. + + Parameters + ---------- + multisig_name : string + Name of multsig registration record to return. + + as_file : string, optional + If true the flat file format is returned, otherwise structured json is returned. + Defaults to false. + + Returns + ------- + dict + Description of registered multisig wallet identified by registration name. + Contains keys: + is_file is true: + multisig_file - str, the multisig file as produced by several wallet apps. + eg: + Name: MainWallet + Policy: 2 of 3 + Format: P2WSH + Derivation: m/48'/0'/0'/2' + + B237FE9D: xpub6E8C7BX4c7qfTsX7urnXggcAyFuhDmYLQhwRwZGLD9maUGWPinuc9k96ej... + 249192D2: xpub6EbXynW6xjYR3crcztum6KzSWqDJoAJQoovwamwVnLaCSHA6syXKPnJo6U... + 67F90FFC: xpub6EHuWWrYd8bp5FS1XAZsMPkmCqLSjpULmygWqAqWRCCjSWQwz6ntq5KnuQ... + + is_file is false: + multisig_name - str, name of multisig registration + variant - str, script type, eg. 'sh(wsh(multi(k)))' + sorted - boolean, whether bip67 key sorting is applied + threshold - int, number of signers required,N + master_blinding_key - 32-bytes, any liquid master blinding key for this wallet + signers - dict containing keys: + fingerprint - 4 bytes, origin fingerprint + derivation - [int], bip32 path from origin to signer xpub provided + xpub - str, base58 xpub of signer + path - [int], any fixed path to always apply after the xpub - usually empty. + + """ + params = {"multisig_name": multisig_name, "as_file": as_file} + return self._jadeRpc("get_registered_multisig", params) + def register_multisig( - self, network, multisig_name, variant, sorted_keys, threshold, signers + self, + network, + multisig_name, + variant, + sorted_keys, + threshold, + signers, + master_blinding_key=None, ): + """ + RPC call to register a new multisig wallet, which must contain the hw signer. + A registration name is provided - if it already exists that record is overwritten. + + Parameters + ---------- + network : string + Network to which the multisig should apply - eg. 'mainnet', 'liquid', 'testnet', etc. + + multisig_name : string + Name to use to identify this multisig wallet registration record. + If a registration record exists with the name given, that record is overwritten. + + variant : str + The script type - one of 'sh(multi(k))', 'wsh(multi(k))', 'sh(wsh(multi(k)))' + + sorted_keys : bool + Whether this is a 'sortedmulti()' wallet - ie. whether to apply bip67 sorting to the + pubkeys when generating redeem scripts. + + threshold : int + Number of signers required. + + signers : [dict] + Description of signers - should include keys: + - 'fingerprint' - 4 bytes, origin fingerprint + - 'derivation' - [int], bip32 path from origin to signer xpub provided + - 'xpub' - str, base58 xpub of signer - will be verified for hw unit signer + - 'path' - [int], any fixed path to always apply after the xpub - usually empty. + + master_blinding_key : 32-bytes, optional + The master blinding key to use for this multisig wallet on liquid. + Optional, defaults to None. + Logically mandatory when 'network' indicates a liquid network and the Jade is to be + used to generate confidential addresses, blinding keys, blinding nonces, asset blinding + factors or output commitments. + + Returns + ------- + bool + True on success, implying the mutisig wallet can now be used. + """ params = { "network": network, "multisig_name": multisig_name, @@ -283,18 +950,146 @@ def register_multisig( "sorted": sorted_keys, "threshold": threshold, "signers": signers, + "master_blinding_key": master_blinding_key, }, } return self._jadeRpc("register_multisig", params) - # Get receive-address for parameters + def register_multisig_file(self, multisig_file): + """ + RPC call to register a new multisig wallet, which must contain the hw signer. + A registration file is provided - as produced my several wallet apps. + + Parameters + ---------- + multisig_file : string + The multisig file as produced by several wallet apps. + eg: + Name: MainWallet + Policy: 2 of 3 + Format: P2WSH + Derivation: m/48'/0'/0'/2' + + B237FE9D: xpub6E8C7BX4c7qfTsX7urnXggcAyFuhDmYLQhwRwZGLD9maUGWPinuc9k96ejhEQ1DCk... + 249192D2: xpub6EbXynW6xjYR3crcztum6KzSWqDJoAJQoovwamwVnLaCSHA6syXKPnJo6U3bVeGde... + 67F90FFC: xpub6EHuWWrYd8bp5FS1XAZsMPkmCqLSjpULmygWqAqWRCCjSWQwz6ntq5KnuQnL23No2... + + Returns + ------- + bool + True on success, implying the mutisig wallet can now be used. + """ + params = {"multisig_file": multisig_file} + return self._jadeRpc("register_multisig", params) + + def register_descriptor( + self, network, descriptor_name, descriptor_script, datavalues=None + ): + """ + RPC call to register a new descriptor wallet, which must contain the hw signer. + A registration name is provided - if it already exists that record is overwritten. + + Parameters + ---------- + network : string + Network to which the multisig should apply - eg. 'mainnet', 'liquid', 'testnet', etc. + + descriptor_name : string + Name to use to identify this descriptor wallet registration record. + If a registration record exists with the name given, that record is overwritten. + + Returns + ------- + bool + True on success, implying the descriptor wallet can now be used. + """ + params = { + "network": network, + "descriptor_name": descriptor_name, + "descriptor": descriptor_script, + "datavalues": datavalues, + } + return self._jadeRpc("register_descriptor", params) + def get_receive_address( - self, *args, recovery_xpub=None, csv_blocks=0, variant=None, multisig_name=None + self, + *args, + recovery_xpub=None, + csv_blocks=0, + variant=None, + multisig_name=None, + descriptor_name=None, + confidential=None, ): + """ + RPC call to generate, show, and return an address for the given path. + The call has three forms. + + Parameters + ---------- + network: str + Network to which the address should apply - eg. 'mainnet', 'liquid', 'testnet', etc. + + Then either: + + 1. Blockstream Green (multisig shield) addresses + subaccount : int + Blockstream Green subaccount + + branch : int + Blockstream Green derivation branch + + pointer : int + Blockstream Green address pointer + + recovery_xpub : str, optional + xpub of recovery key for 2of3 subaccounts. Otherwise should be omitted. + Defaults to None (ie. not a 2of3 subaccount). + + csv_blocks : int, optional + Number of blocks to include in csv redeem script, if this is a csv-enabled account. + Otherwise should be omitted. + Defaults to 0 (ie. does not apply/not a csv-enabled account.) + + 2. Generic single-sig addresses + path: [int] + bip32 path for which the xpub should be generated. + + variant: str + The script type - one of 'pkh(k)', 'wpkh(k)', 'sh(wpkh(k))' + + 3. Generic multisig addresses + paths: [[int]] + bip32 path suffixes, one for each signer, applied as a suffix to the registered + signer path. Usually these path suffixes will all be identical. + + multisig_name : str + The name of the registered multisig wallet record used to generate the address. + + 4. Descriptor wallet addresses + branch : int + Multi-path derivation branch, usually 0. + + pointer : int + Path index to descriptor + + descriptor_name : str + The name of the registered descriptor wallet record used to generate the address. + + Returns + ------- + str + The address generated for the given parameters. + + """ if multisig_name is not None: assert len(args) == 2 keys = ["network", "paths", "multisig_name"] args += (multisig_name,) + elif descriptor_name is not None: + assert len(args) == 3 + keys = ["network", "branch", "pointer", "descriptor_name"] + args += (descriptor_name,) elif variant is not None: assert len(args) == 2 keys = ["network", "path", "variant"] @@ -310,9 +1105,13 @@ def get_receive_address( "csv_blocks", ] args += (recovery_xpub, csv_blocks) - return self._jadeRpc("get_receive_address", dict(zip(keys, args))) - # Sign a message + params = dict(zip(keys, args)) + if confidential is not None: + params["confidential"] = confidential + + return self._jadeRpc("get_receive_address", params) + def sign_message( self, path, @@ -321,6 +1120,34 @@ def sign_message( ae_host_commitment=None, ae_host_entropy=None, ): + """ + RPC call to format and sign the given message, using the given bip32 path. + Supports RFC6979 and anti-exfil signatures. + + Parameters + ---------- + path : [int] + bip32 path for which the signature should be generated. + + message : str + Message string to format and sign. + + ae_host_commitment : 32-bytes, optional + The host-commitment to use for Antil-Exfil signatures + + ae_host_entropy : 32-bytes, optional + The host-entropy to use for Antil-Exfil signatures + + Returns + ------- + 1. Legacy/RFC6979 signatures + str + base64-encoded signature + + 2. Anti-exfil signatures + (bytes, str) + signer-commitment, base64-encoded signature + """ if use_ae_signatures: # Anti-exfil protocol: # We send the signing request and receive the signer-commitment in @@ -340,68 +1167,339 @@ def sign_message( params = {"path": path, "message": message} return self._jadeRpc("sign_message", params) - # Get a Liquid master blinding key - def get_master_blinding_key(self): - return self._jadeRpc("get_master_blinding_key") - - # Get a Liquid public blinding key for a given script - def get_blinding_key(self, script): - params = {"script": script} + def sign_message_file(self, message_file): + """ + RPC call to format and sign the given message, using the given bip32 path. + A message file is provided - as produced by eg. Specter wallet. + Supports RFC6979 only. + + Parameters + ---------- + message_file : str + Message file to parse and produce signature for. + eg: 'signmessage m/84h/0h/0h/0/0 ascii:this is a test message' + + Returns + ------- + str + base64-encoded RFC6979 signature + """ + params = {"message_file": message_file} + return self._jadeRpc("sign_message", params) + + def get_identity_pubkey(self, identity, curve, key_type, index=0): + """ + RPC call to fetch a pubkey for the given identity (slip13/slip17). + NOTE: this api returns an uncompressed public key + + Parameters + ---------- + identity : str + Identity string to format and sign. For example ssh://satoshi@bitcoin.org + + curve : str + Name of curve to use - currently only 'nist256p1' is supported + + key_type : str + Key derivation type - must be either 'slip-0013' for an identity pubkey, or 'slip-0017' + for an ecdh pubkey. + + index : int, optional + Index number (if require multiple keys/sigs per identity) + Defaults to 0 + + Returns + ------- + 65-bytes + Uncompressed public key for the given identity and index. + Consistent with 'sign_identity' or 'get_identity_shared_key', depending on the + 'key_type'. + + """ + params = { + "identity": identity, + "curve": curve, + "type": key_type, + "index": index, + } + return self._jadeRpc("get_identity_pubkey", params) + + def get_identity_shared_key(self, identity, curve, their_pubkey, index=0): + """ + RPC call to fetch a SLIP-0017 shared ecdh key for the identity and counterparty public key. + NOTE: this api takes an uncompressed public key + + Parameters + ---------- + identity : str + Identity string to format and sign. For example ssh://satoshi@bitcoin.org + + curve : str + Name of curve to use - currently only 'nist256p1' is supported + + their_pubkey : 65-bytes + The counterparty's uncompressed public key + + index : int, optional + Index number (if require multiple keys/sigs per identity) + Defaults to 0 + + Returns + ------- + 32-bytes + The shared ecdh key for the given identity and cpty public key + Consistent with 'get_identity_pubkey' with 'key_type=slip-0017' + """ + params = { + "identity": identity, + "curve": curve, + "index": index, + "their_pubkey": their_pubkey, + } + return self._jadeRpc("get_identity_shared_key", params) + + def sign_identity(self, identity, curve, challenge, index=0): + """ + RPC call to authenticate the given identity through a challenge. + Supports RFC6979. + Returns the signature and the associated SLIP-0013 pubkey + NOTE: this api returns an uncompressed public key + + Parameters + ---------- + identity : str + Identity string to format and sign. For example ssh://satoshi@bitcoin.org + + curve : str + Name of curve to use - currently only 'nist256p1' is supported + + challenge : bytes + Challenge bytes to sign + + index : int, optional + Index number (if require multiple keys/sigs per identity) + Defaults to 0 + + Returns + ------- + dict + Contains keys: + pubkey - 65-bytes, the uncompressed SLIP-0013 public key, consistent with + 'get_identity_pubkey' with 'key_type=slip-0013' + signature - 65-bytes, RFC6979 deterministic signature, prefixed with 0x00 + """ + params = { + "identity": identity, + "curve": curve, + "index": index, + "challenge": challenge, + } + return self._jadeRpc("sign_identity", params) + + def get_master_blinding_key(self, only_if_silent=False): + """ + RPC call to fetch the master (SLIP-077) blinding key for the hw signer. + May block temporarily to request the user's permission to export. Passing 'only_if_silent' + causes the call to return the 'denied' error if it would normally ask the user. + NOTE: the master blinding key of any registered multisig wallets can be obtained from + the result of `get_registered_multisigs()`. + + Parameters + ---------- + only_if_silent : boolean, optional + If True Jade will return the denied error if it would normally ask the user's permission + to export the master blinding key. Passing False (or letting default) may block while + asking the user to confirm the export on Jade. + + Returns + ------- + 32-bytes + SLIP-077 master blinding key + """ + params = {"only_if_silent": only_if_silent} + return self._jadeRpc("get_master_blinding_key", params) + + def get_blinding_key(self, script, multisig_name=None): + """ + RPC call to fetch the public blinding key for the hw signer. + + Parameters + ---------- + script : bytes + The script for which the public blinding key is required. + + multisig_name : str, optional + The name of any registered multisig wallet for which to fetch the blinding key. + Defaults to None + + Returns + ------- + 33-bytes + Public blinding key for the passed script. + """ + params = {"script": script, "multisig_name": multisig_name} return self._jadeRpc("get_blinding_key", params) - # Get the shared secret to unblind a tx, given the receiving script on - # our side and the pubkey of the sender (sometimes called "nonce" in - # Liquid). Optionally fetch our blinding pubkey also. - def get_shared_nonce(self, script, their_pubkey, include_pubkey=False): + def get_shared_nonce( + self, script, their_pubkey, include_pubkey=False, multisig_name=None + ): + """ + RPC call to get the shared secret to unblind a tx, given the receiving script and + the pubkey of the sender (sometimes called "blinding nonce" in Liquid). + Optionally fetch the hw signer's public blinding key also. + + Parameters + ---------- + script : bytes + The script for which the blinding nonce is required. + + their_pubkey : 33-bytes + The counterparty public key. + + include_pubkey : bool, optional + Whether to also return the wallet's public blinding key. + Defaults to False. + + multisig_name : str, optional + The name of any registered multisig wallet for which to fetch the blinding nonce. + Defaults to None + + Returns + ------- + 1. include_pubkey is False + 33-bytes + Public blinding nonce for the passed script and counterparty public key. + + 2. include_pubkey is True + dict + Contains keys: + shared_nonce - 32-bytes, public blinding nonce for the passed script as above. + blinding_key - 33-bytes, public blinding key for the passed script. + """ params = { "script": script, "their_pubkey": their_pubkey, "include_pubkey": include_pubkey, + "multisig_name": multisig_name, } return self._jadeRpc("get_shared_nonce", params) - # Get a "trusted" blinding factor to blind an output. Normally the blinding - # factors are generated and returned in the `get_commitments` call, but - # for the last output the VBF must be generated on the host side, so this - # call allows the host to get a valid ABF to compute the generator and - # then the "final" VBF. Nonetheless, this call is kept generic, and can - # also generate VBFs, thus the "type" parameter. - # `hash_prevouts` is computed as specified in BIP143 (double SHA of all - # the outpoints being spent as input. It's not checked right away since - # at this point Jade doesn't know anything about the tx we are referring - # to. It will be checked later during `sign_liquid_tx`. - # `output_index` is the output we are trying to blind. - # `type` can either be "ASSET" or "VALUE" to generate ABFs or VBFs. - def get_blinding_factor(self, hash_prevouts, output_index, type): + def get_blinding_factor( + self, hash_prevouts, output_index, bftype, multisig_name=None + ): + """ + RPC call to get deterministic blinding factors to blind an output. + Predicated on the host calculating the 'hash_prevouts' value correctly. + Can fetch abf, vbf, or both together. + + Parameters + ---------- + + hash_prevouts : 32-bytes + This value should be computed by the host as specified in bip143. + It is not verified by Jade, since at this point Jade does not have the tx in question. + + output_index : int + The index of the output we are trying to blind + + bftype : str + Can be "ASSET", "VALUE", or "ASSET_AND_VALUE", to generate abf, vbf, or both. + + multisig_name : str, optional + The name of any registered multisig wallet for which to fetch the blinding factor. + Defaults to None + + Returns + ------- + 32-bytes or 64-bytes + The blinding factor for "ASSET" and "VALUE" requests, or both concatenated abf|vbf + ie. the first 32 bytes being abf, the second 32 bytes being vbf. + """ params = { "hash_prevouts": hash_prevouts, "output_index": output_index, - "type": type, + "type": bftype, + "multisig_name": multisig_name, } return self._jadeRpc("get_blinding_factor", params) - # Generate the blinding factors and commitments for a given output. - # Can optionally get a "custom" VBF, normally used for the last - # input where the VBF is not random, but generated accordingly to - # all the others. - # `hash_prevouts` and `output_index` have the same meaning as in - # the `get_blinding_factor` call. - # NOTE: the `asset_id` should be passed as it is normally displayed, so - # reversed compared to the "consensus" representation. - def get_commitments(self, asset_id, value, hash_prevouts, output_index, vbf=None): + def get_commitments( + self, asset_id, value, hash_prevouts, output_index, vbf=None, multisig_name=None + ): + """ + RPC call to generate deterministic blinding factors and commitments for a given output. + Can optionally get a "custom" VBF, normally used for the last input where the vbf is not + computed here, but generated on the host according to all the other values. + The commitments generated here should be passed back into `sign_liquid_tx()`. + + Parameters + ---------- + asset_id : 32-bytes + asset_id as usually displayed - ie. reversed compared to network/consensus order + + value : int + value in 'satoshi' or equivalent atomic integral unit + + hash_prevouts : 32-bytes + This value is computed as specified in bip143. + It is verified immediately since at this point Jade doesn't have the tx in question. + It will be checked later during `sign_liquid_tx()`. + + output_index : int + The index of the output we are trying to blind + + vbf : 32-bytes, optional + The vbf to use, in preference to deterministically generating one in this call. + + multisig_name : str, optional + The name of any registered multisig wallet for which to fetch the blinding factor. + Defaults to None + + Returns + ------- + dict + Containing the blinding factors and output commitments. + """ params = { "asset_id": asset_id, "value": value, "hash_prevouts": hash_prevouts, "output_index": output_index, + "vbf": vbf, + "multisig_name": multisig_name, } - if vbf is not None: - params["vbf"] = vbf return self._jadeRpc("get_commitments", params) - # Common code for sending btc- and liquid- tx-inputs and receiving the - # signatures. Handles standard EC and AE signing schemes. def _send_tx_inputs(self, base_id, inputs, use_ae_signatures): + """ + Helper call to send the tx inputs to Jade for signing. + Handles legacy RFC6979 signatures, as well as the Anti-Exfil protocol. + + Parameters + ---------- + base_id : int + The ids of the messages sent will be increments from this base id. + + inputs : [dict] + The tx inputs - see `sign_tx()` / `sign_liquid_tx()` for details. + + use_ae_signatures : bool + Whether to use the anti-exfil protocol to generate the signatures + + Returns + ------- + 1. if use_ae_signatures is False + [bytes] + An array of signatures corresponding to the array of inputs passed. + The signatures are in DER format with the sighash appended. + 'None' placeholder elements are used for inputs not requiring a signature. + + 2. if use_ae_signatures is True + [(32-bytes, bytes)] + An array of pairs of signer-commitments and signatures corresponding to the inputs. + The signatures are in DER format with the sighash appended. + (None, None) placeholder elements are used for inputs not requiring a signature. + """ if use_ae_signatures: # Anti-exfil protocol: # We send one message per input (which includes host-commitment *but @@ -414,7 +1512,7 @@ def _send_tx_inputs(self, base_id, inputs, use_ae_signatures): host_ae_entropy_values = [] for txinput in inputs: # ae-protocol - do not send the host entropy immediately - txinput = txinput.copy() # shallow copy + txinput = txinput.copy() if txinput else {} # shallow copy host_ae_entropy_values.append(txinput.pop("ae_host_entropy", None)) base_id += 1 @@ -446,6 +1544,9 @@ def _send_tx_inputs(self, base_id, inputs, use_ae_signatures): # Send all n inputs requests = [] for txinput in inputs: + if txinput is None: + txinput = {} + base_id += 1 msg_id = str(base_id) request = self.jade.build_request(msg_id, "tx_input", txinput) @@ -464,10 +1565,107 @@ def _send_tx_inputs(self, base_id, inputs, use_ae_signatures): assert len(signatures) == len(inputs) return signatures - # Sign a Liquid txn def sign_liquid_tx( - self, network, txn, inputs, commitments, change, use_ae_signatures=False + self, + network, + txn, + inputs, + commitments, + change, + use_ae_signatures=False, + asset_info=None, + additional_info=None, ): + """ + RPC call to sign a liquid transaction. + + Parameters + ---------- + network : str + Network to which the txn should apply - eg. 'liquid', 'liquid-testnet', etc. + + txn : bytes + The transaction to sign + + inputs : [dict] + The tx inputs. + If signing this input, should contain keys: + is_witness, bool - whether this is a segwit input + script, bytes- the redeem script + path, [int] - the bip32 path to sign with + value_commitment, 33-bytes - The value commitment of ths input + + This is optional if signing this input: + sighash, int - The sighash to use, defaults to 0x01 (SIGHASH_ALL) + + These are only required for Anti-Exfil signatures: + ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures + ae_host_entropy, 32-bytes - The host-entropy for Anti-Exfil signatures + + These are only required for advanced transactions, eg. swaps, and only when the + inputs need unblinding. + Not needed for vanilla send-payment/redeposit etc: + abf, 32-bytes - asset blinding factor + asset_id, 32-bytes - the unblinded asset-id + asset_generator, 33-bytes - the (blinded) asset-generator + vbf, 32-bytes - the value blinding factor + value, int - the unblinded sats value of the input + + If not signing this input a null or an empty dict can be passed. + + commitments : [dict] + An array sized for the number of outputs. + Unblinded outputs should have a 'null' placeholder element. + The commitments as retrieved from `get_commitments()`, with the addition of: + 'blinding_key', - the output's public blinding key + (as retrieved from `get_blinding_key()`) + + change : [dict] + An array sized for the number of outputs. + Outputs which are not to this wallet should have a 'null' placeholder element. + The output scripts for the elements with data will be verified by Jade. + Unless the element also contains 'is_change': False, these outputs will automatically + be approved and not be verified by the user. + Populated elements should contain sufficient data to generate the wallet address. + See `get_receive_address()` + + use_ae_signatures : bool, optional + Whether to use the anti-exfil protocol to generate the signatures. + Defaults to False. + + asset_info : [dict], optional + Any asset-registry data relevant to the assets being transacted, such that Jade can + display a meaningful name, issuer, ticker etc. rather than just asset-id. + At the very least must contain 'asset_id', 'contract' and 'issuance_prevout' items, + exactly as in the registry data. NOTE: asset_info for the network policy-asset is + not required. + Defaults to None. + + additional_info: dict, optional + Extra data about the transaction. Only required for advanced transactions, eg. swaps. + Not needed for vanilla send-payment/redeposit etc: + tx_type, str: 'swap' indicates the tx represents an asset-swap proposal or transaction. + wallet_input_summary, dict: a list of entries containing 'asset_id' (32-bytes) and + 'satoshi' (int) showing net movement of assets out of the wallet (ie. sum of wallet + inputs per asset, minus any change outputs). + wallet_output_summary, dict: a list of entries containing 'asset_id' (32-bytes) and + 'satoshi' (int) showing net movement of assets into the wallet (ie. sum of wallet + outputs per asset, excluding any change outputs). + + Returns + ------- + 1. if use_ae_signatures is False + [bytes] + An array of signatures corresponding to the array of inputs passed. + The signatures are in DER format with the sighash appended. + 'None' placeholder elements are used for inputs not requiring a signature. + + 2. if use_ae_signatures is True + [(32-bytes, bytes)] + An array of pairs of signer-commitments and signatures corresponding to the inputs. + The signatures are in DER format with the sighash appended. + (None, None) placeholder elements are used for inputs not requiring a signature. + """ # 1st message contains txn and number of inputs we are going to send. # Reply ok if that corresponds to the expected number of inputs (n). base_id = 100 * random.randint(1000, 9999) @@ -478,6 +1676,8 @@ def sign_liquid_tx( "trusted_commitments": commitments, "use_ae_signatures": use_ae_signatures, "change": change, + "asset_info": asset_info, + "additional_info": additional_info, } reply = self._jadeRpc("sign_liquid_tx", params, str(base_id)) @@ -486,8 +1686,63 @@ def sign_liquid_tx( # Send inputs and receive signatures return self._send_tx_inputs(base_id, inputs, use_ae_signatures) - # Sign a txn def sign_tx(self, network, txn, inputs, change, use_ae_signatures=False): + """ + RPC call to sign a btc transaction. + + Parameters + ---------- + network : str + Network to which the txn should apply - eg. 'mainnet', 'testnet', etc. + + txn : bytes + The transaction to sign + + inputs : [dict] + The tx inputs. Should contain keys: + One of these is required: + input_tx, bytes - The prior transaction which created the utxo of this input + satoshi, int - The satoshi amount of this input - can be used in place of + 'input_tx' for a tx with a single segwit input + + These are only required if signing this input: + is_witness, bool - whether this is a segwit input + script, bytes- the redeem script + path, [int] - the bip32 path to sign with + + This is optional if signing this input: + sighash, int - The sighash to use, defaults to 0x01 (SIGHASH_ALL) + + These are only required for Anti-Exfil signatures: + ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures + ae_host_entropy, 32-bytes - The host-entropy for Anti-Exfil signatures + + change : [dict] + An array sized for the number of outputs. + Outputs which are not to this wallet should have a 'null' placeholder element. + The output scripts for the elements with data will be verified by Jade. + Unless the element also contains 'is_change': False, these outputs will automatically + be approved and not be verified by the user. + Populated elements should contain sufficient data to generate the wallet address. + See `get_receive_address()` + + use_ae_signatures : bool + Whether to use the anti-exfil protocol to generate the signatures + + Returns + ------- + 1. if use_ae_signatures is False + [bytes] + An array of signatures corresponding to the array of inputs passed. + The signatures are in DER format with the sighash appended. + 'None' placeholder elements are used for inputs not requiring a signature. + + 2. if use_ae_signatures is True + [(32-bytes, bytes)] + An array of pairs of signer-commitments and signatures corresponding to the inputs. + The signatures are in DER format with the sighash appended. + (None, None) placeholder elements are used for inputs not requiring a signature. + """ # 1st message contains txn and number of inputs we are going to send. # Reply ok if that corresponds to the expected number of inputs (n). base_id = 100 * random.randint(1000, 9999) @@ -505,27 +1760,74 @@ def sign_tx(self, network, txn, inputs, change, use_ae_signatures=False): # Send inputs and receive signatures return self._send_tx_inputs(base_id, inputs, use_ae_signatures) + def sign_psbt(self, network, psbt): + """ + RPC call to sign a passed psbt as required + + Parameters + ---------- + network : str + Network to which the txn should apply - eg. 'mainnet', 'testnet', etc. + + psbt : bytes + The psbt formatted as bytes + + Returns + ------- + bytes + The psbt, updated with any signatures required from the hw signer + """ + # Send PSBT message + params = {"network": network, "psbt": psbt} + msgid = str(random.randint(100000, 999999)) + request = self.jade.build_request(msgid, "sign_psbt", params) + self.jade.write_request(request) + + # Read replies until we have them all, collate data and return. + # NOTE: we send 'get_extended_data' messages to request more 'chunks' of the reply data. + psbt_out = bytearray() + while True: + reply = self.jade.read_response() + self.jade.validate_reply(request, reply) + psbt_out.extend(self._get_result_or_raise_error(reply)) + + if "seqnum" not in reply or reply["seqnum"] == reply["seqlen"]: + break + + newid = str(random.randint(100000, 999999)) + params = { + "origid": msgid, + "orig": "sign_psbt", + "seqnum": reply["seqnum"] + 1, + "seqlen": reply["seqlen"], + } + request = self.jade.build_request(newid, "get_extended_data", params) + self.jade.write_request(request) + + return psbt_out + -# -# Mid-level interface to Jade -# Wraps either a serial or a ble connection -# Calls to send and receive bytes and cbor messages over the interface. -# -# Either: -# a) use wrapped with JadeAPI -# (recommended) -# or: -# b) use with JadeInterface.create_[serial|ble]() as jade: -# ... -# or: -# c) use JadeInterface.create_[serial|ble], then call connect() before -# using, and disconnect() when finished -# (caveat cranium) -# or: -# d) use ctor to wrap existing low-level implementation instance -# (caveat cranium) -# class JadeInterface: + """ + Mid-level interface to Jade + Wraps either a serial or a ble connection + Calls to send and receive bytes and cbor messages over the interface. + + Either: + a) use wrapped with JadeAPI + (recommended) + or: + b) use with JadeInterface.create_[serial|ble]() as jade: + ... + or: + c) use JadeInterface.create_[serial|ble], then call connect() before + using, and disconnect() when finished + (caveat cranium) + or: + d) use ctor to wrap existing low-level implementation instance + (caveat cranium) + """ + def __init__(self, impl): assert impl is not None self.impl = impl @@ -536,45 +1838,124 @@ def __enter__(self): def __exit__(self, exc_type, exc, tb): if exc_type: - logger.error("Exception causing JadeInterface context exit.") - logger.error(exc_type) - logger.error(exc) + logger.info("Exception causing JadeInterface context exit.") + logger.info(exc_type) + logger.info(exc) traceback.print_tb(tb) self.disconnect(exc_type is not None) @staticmethod def create_serial(device=None, baud=None, timeout=None): + """ + Create a JadeInterface object using the serial interface described. + + Parameters + ---------- + device : str, optional + The device identifier for the serial device. + Underlying implementation will default (to /dev/ttyUSB0) + + baud : int, optional + The communication baud rate. + Underlying implementation will default (to 115200) + + timeout : int, optional + The serial read timeout when awaiting messages. + Underlying implementation will default (to 120s) + + Returns + ------- + JadeInterface + Inerface object configured to use given serial parameters. + NOTE: the instance has not yet tried to contact the hw + - caller must call 'connect()' before trying to use the Jade. + """ if device and JadeTCPImpl.isSupportedDevice(device): - impl = JadeTCPImpl(device) + impl = JadeTCPImpl(device, timeout or DEFAULT_SERIAL_TIMEOUT) else: impl = JadeSerialImpl( - device or DEFAULT_SERIAL_DEVICE, - baud or DEFAULT_BAUD_RATE, - timeout or DEFAULT_SERIAL_TIMEOUT, + device, baud or DEFAULT_BAUD_RATE, timeout or DEFAULT_SERIAL_TIMEOUT ) return JadeInterface(impl) - # @staticmethod - # def create_ble(device_name=None, serial_number=None, scan_timeout=None, loop=None): - # impl = JadeBleImpl( - # device_name or DEFAULT_BLE_DEVICE_NAME, - # serial_number or DEFAULT_BLE_SERIAL_NUMBER, - # scan_timeout or DEFAULT_BLE_SCAN_TIMEOUT, - # loop=loop, - # ) - # return JadeInterface(impl) + @staticmethod + def create_ble(device_name=None, serial_number=None, scan_timeout=None, loop=None): + """ + Create a JadeInterface object using the BLE interface described. + NOTE: raises JadeError if BLE dependencies not installed. + + Parameters + ---------- + device_name : str, optional + The device name of the desired BLE device. + Underlying implementation will default (to 'Jade') + + serial_number : int, optional + The serial number of the desired BLE device + - used to disambiguate multiple beacons with the same 'device name' + Underlying implementation will connect to the first beacon it scans + with the matching 'device name'. + + scan_timeout : int, optional + The timeout when scanning for devices which match the device name/serial number. + Underlying implementation will default (to 60s) + + loop : optional + The asynchio event loop to use, if required. + Underlying implementation will default (to asyncio.get_event_loop()) + + Returns + ------- + JadeInterface + Inerface object configured to use given BLE parameters. + NOTE: the instance has not yet tried to contact the hw + - caller must call 'connect()' before trying to use the Jade. + + Raises + ------ + JadeError if BLE backend not available (ie. BLE dependencies not installed) + """ + this_module = sys.modules[__name__] + if not hasattr(this_module, "JadeBleImpl"): + raise JadeError(1, "BLE support not installed", None) + + impl = JadeBleImpl( + device_name or DEFAULT_BLE_DEVICE_NAME, + serial_number or DEFAULT_BLE_SERIAL_NUMBER, + scan_timeout or DEFAULT_BLE_SCAN_TIMEOUT, + loop=loop, + ) + return JadeInterface(impl) def connect(self): + """ + Try to connect the underlying transport interface (eg. serial, ble, etc.) + Raises an exception on failure. + """ self.impl.connect() def disconnect(self, drain=False): + """ + Disconnect the underlying transport (eg. serial, ble, etc.) + + Parameters + ---------- + drain : bool, optional + When true log any/all remaining messages/data, otherwise silently discard. + NOTE: can prevent disconnection if data is arriving constantly. + Defaults to False. + """ if drain: self.drain() self.impl.disconnect() def drain(self): - logger.warn("Draining interface...") + """ + Log any/all outstanding messages/data. + NOTE: can run indefinitely if data is arriving constantly. + """ + logger.warning("Draining interface...") drained = bytearray() finished = False @@ -585,20 +1966,40 @@ def drain(self): if finished or byte_ == b"\n" or len(drained) > 256: try: - device_logger.warn(drained.decode("utf-8")) + device_logger.warning(drained.decode("utf-8")) except Exception as e: # Dump the bytes raw and as hex if decoding as utf-8 failed - device_logger.warn("Raw:") - device_logger.warn(drained) - device_logger.warn("----") - device_logger.warn("Hex dump:") - device_logger.warn(drained.hex()) + device_logger.warning("Raw:") + device_logger.warning(drained) + device_logger.warning("----") + device_logger.warning("Hex dump:") + device_logger.warning(drained.hex()) # Clear and loop to continue collecting drained.clear() @staticmethod def build_request(input_id, method, params=None): + """ + Build a request dict from passed parameters + + Parameters + ---------- + input_id : str + The id of the request message to construct + + method : str + rpc method to invoke + + params : dict, optional + any parameters to pass to the rpc method + Defaults to None. + + Returns + ------- + dict + The request object as a dict + """ request = {"method": method, "id": input_id} if params is not None: request["params"] = params @@ -606,6 +2007,19 @@ def build_request(input_id, method, params=None): @staticmethod def serialise_cbor_request(request): + """ + Method to format a request dict as a cbor message + + Parameters + ---------- + request : dict + The request dict + + Returns + ------- + bytes + The request formatted as cbor message bytes + """ dump = cbor.dumps(request) len_dump = len(dump) if "method" in request and "ota_data" in request["method"]: @@ -620,56 +2034,118 @@ def serialise_cbor_request(request): return dump def write(self, bytes_): + """ + Write bytes over the underlying interface + + Parameters + ---------- + bytes_ : bytes + The bytes to write + + Returns + ------- + int + The number of bytes written + """ logger.debug("Sending: {} bytes".format(len(bytes_))) wrote = self.impl.write(bytes_) logger.debug("Sent: {} bytes".format(len(bytes_))) return wrote def write_request(self, request): + """ + Write a request dict over the underlying interface, formatted as cbor. + + Parameters + ---------- + request : dict + The request dict to write + """ msg = self.serialise_cbor_request(request) written = 0 while written < len(msg): written += self.write(msg[written:]) def read(self, n): + """ + Try to read bytes from the underlying interface. + + Returns + ------- + bytes + The bytes received + """ logger.debug("Reading {} bytes...".format(n)) bytes_ = self.impl.read(n) logger.debug("Received: {} bytes".format(len(bytes_))) return bytes_ def read_cbor_message(self): + """ + Try to read a single cbor (response) message from the underlying interface. + Respects the any read timeout. + If any 'log' messages are received, logs them locally at the nearest corresponding level + and awaits the next message. Returns when it receives what appears to be a reply message. + + Returns + ------- + dict + The message received, as a dict + """ while True: # 'self' is sufficiently 'file-like' to act as a load source. # Throws EOFError on end of stream/timeout/lost-connection etc. message = cbor.load(self) - # A message response (to a prior request) - if "id" in message: - logger.info("Received msg: {}".format(_hexlify(message))) - return message - - # A log message - handle as normal - if "log" in message: - response = message["log"].decode("utf-8") - log_methods = { - "E": device_logger.error, - "W": device_logger.warn, - "I": device_logger.info, - "D": device_logger.debug, - "V": device_logger.debug, - } - log_method = device_logger.error - if len(response) > 1 and response[1] == " ": - lvl = response[0] - log_method = log_methods.get(lvl, device_logger.error) - - log_method(">> {}".format(response)) - else: - # Unknown/unhandled/unexpected message - logger.error("Unhandled message received") - device_logger.error(message) + if isinstance(message, collections.abc.Mapping): + # A message response (to a prior request) + if "id" in message: + logger.info("Received msg: {}".format(_hexlify(message))) + return message + + # A log message - handle as normal + if "log" in message: + response = message["log"] + log_method = device_logger.error + try: + response = message["log"].decode("utf-8") + log_methods = { + "E": device_logger.error, + "W": device_logger.warning, + "I": device_logger.info, + "D": device_logger.debug, + "V": device_logger.debug, + } + if len(response) > 1 and response[1] == " ": + lvl = response[0] + log_method = log_methods.get(lvl, device_logger.error) + except Exception as e: + logger.error("Error processing log message: {}".format(e)) + log_method(">> {}".format(response)) + continue + + # Unknown/unhandled/unexpected message + logger.error("Unhandled message received") + device_logger.error(message) def read_response(self, long_timeout=False): + """ + Try to read a single cbor (response) message from the underlying interface. + If any 'log' messages are received, logs them locally at the nearest corresponding level + and awaits the next message. Returns when it receives what appears to be a reply message. + If `long_timeout` is false, any read-timeout is respected. If True, the call will block + indefinitely awaiting a response message. + + Parameters + ---------- + long_timeout : bool + Whether to wait indefinitely for the next (response) message. + + Returns + ------- + dict + The message received, as a dict + """ while True: try: return self.read_cbor_message() @@ -679,11 +2155,32 @@ def read_response(self, long_timeout=False): @staticmethod def validate_reply(request, reply): + """ + Helper to minimally validate a reply message, in the context of a request. + Asserts if the reply does contain the expected minimal fields. + """ assert isinstance(reply, dict) and "id" in reply assert ("result" in reply) != ("error" in reply) assert reply["id"] == request["id"] or reply["id"] == "00" and "error" in reply def make_rpc_call(self, request, long_timeout=False): + """ + Method to send a request over the underlying interface, and await a response. + The request is minimally validated before it is sent, and the response is simialrly + validated before being returned. + Any read-timeout is respected unless 'long_timeout' is passed, in which case the call + blocks indefinitely awaiting a response. + + Parameters + ---------- + long_timeout : bool + Whether to wait indefinitely for the response. + + Returns + ------- + dict + The (minimally validated) response message received, as a dict + """ # Write outgoing request message assert isinstance(request, dict) assert "id" in request and len(request["id"]) > 0 diff --git a/src/cryptoadvance/specter/devices/hwi/jadepy/jade_ble.py b/src/cryptoadvance/specter/devices/hwi/jadepy/jade_ble.py new file mode 100644 index 0000000000..454cd0ff24 --- /dev/null +++ b/src/cryptoadvance/specter/devices/hwi/jadepy/jade_ble.py @@ -0,0 +1,244 @@ +import logging +import asyncio +import aioitertools +import collections +import subprocess +import platform +import bleak + +from .jade_error import JadeError + +logger = logging.getLogger(__name__) + + +# +# Low-level BLE backend interface to Jade +# Calls to send and receive bytes over the interface. +# Intended for use via JadeInterface wrapper. +# +# Either: +# a) use via JadeInterface.create_ble() (see JadeInterface) +# (recommended) +# or: +# b) use JadeBleImpl() directly, and call connect() before +# using, and disconnect() when finished, +# (caveat cranium) +# +class JadeBleImpl: + IO_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" + IO_TX_CHAR_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" + IO_RX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" + BLE_MAX_WRITE_SIZE = 517 - 8 + + def __init__(self, device_name, serial_number, scan_timeout, loop=None): + self.device_name = device_name + self.serial_number = serial_number + self.scan_timeout = max(1, scan_timeout) + self.inputstream = None + self.write_task = None + self.client = None + self.rx_char_handle = None + + if not loop: + loop = asyncio.get_event_loop() + self.loop = loop + + # Helper to await async coroutines + def _run(self, coro): + assert coro and self.loop and not self.loop.is_closed() + return self.loop.run_until_complete(coro) + + async def _connect_impl(self): + assert self.client is None + + # Input received, buffered awaiting external read + inbufs = collections.deque() + + # Async stream of those items for reading + async def _input_stream(): + # Poll for new input all the time client exists + while self.client is not None: + while inbufs: + buf = inbufs.popleft() + for b in buf: + yield b + + # No data, yield to event loop awaiting arrival of more data + await asyncio.sleep(0.01) + + # Stream drained and client connection no longer exists + self.inputstream = None + + self.inputstream = _input_stream() + + # Scan for expected ble device + # Match device-name only if no serial number provided + device_mac = None + while not device_mac and self.scan_timeout > 0: + logger.info("Scanning, timeout = {}s".format(self.scan_timeout)) + scan_time = min(2, self.scan_timeout) + self.scan_timeout -= scan_time + + devices = await bleak.discover(scan_time) + for dev in devices: + logger.debug("Seen: {}".format(dev.name)) + if ( + dev.name + and dev.name.startswith(self.device_name) + and ( + self.serial_number is None + or dev.name.endswith(self.serial_number) + ) + ): + # Map pretty name to mac-type address + device_mac = dev.address + full_name = dev.name + + if not device_mac: + raise JadeError( + 1, + "Unable to locate BLE device", + "Device name: {}, Serial number: {}".format( + self.device_name, self.serial_number or "" + ), + ) + + # Remove previous bt/ble pairing data for this device + if platform.system() == "Linux": + command = "bt-device --remove '{}'".format(device_mac) + process = subprocess.run(command, shell=True, stdout=subprocess.DEVNULL) + + # Connect - seems pretty flaky so allow retries + connected = False + attempts_remaining = 3 + while not connected: + try: + attempts_remaining -= 1 + client = bleak.BleakClient(device_mac) + logger.info("Connecting to: {} ({})".format(full_name, device_mac)) + await client.connect() + connected = client.is_connected + logger.info("Connected: {}".format(connected)) + except Exception as e: + logger.warning("BLE connection exception: '{}'".format(e)) + if not attempts_remaining: + logger.warning("Exhausted retries - BLE connection failed") + raise + + # Peruse services and characteristics + # Get the 'handle' of the receiving charactersitic + for service in client.services: + for char in service.characteristics: + if char.uuid == JadeBleImpl.IO_RX_CHAR_UUID: + logger.debug( + "Found RX characterisitic - handle: ".format(char.handle) + ) + self.rx_char_handle = char.handle + + if "read" in char.properties: + await client.read_gatt_char(char.uuid) + + for descriptor in char.descriptors: + await client.read_gatt_descriptor(descriptor.handle) + + # Attach handler to be notified of new data on the receiving characteristic + def _notification_handler(char_handle, data): + assert char_handle == self.rx_char_handle + inbufs.append(data) + + assert self.rx_char_handle + await client.start_notify(self.rx_char_handle, _notification_handler) + + # Attach handler called when disconnected + def _disconnection_handler(client): + + # Set the client to None - that will cause the receive + # generator to terminate and not wait forever for data. + assert client == self.client + self.client = None + + # Also cancel any running task trying to write data, + # as otherwise that hangs forever too ... + if self.write_task: + self.write_task.cancel() + self.write_task = None + + client.set_disconnected_callback(_disconnection_handler) + + # Done + self.client = client + + def connect(self): + return self._run(self._connect_impl()) + + async def _disconnect_impl(self): + try: + if self.client is not None and self.client.is_connected: + # Stop listening for incoming data + if self.rx_char_handle: + await self.client.stop_notify(self.rx_char_handle) + + # Disconnect underlying client - this should trigger the _disconnection_handler() + # above to run before this returns from the 'await' + await self.client.disconnect() + except Exception as err: + # Sometimes get an exception when testing connection + # if the client has already internally disconnected ... + logger.warning("Exception when disconnecting ble: {}".format(err)) + + # Set the client to None in any case - that will cause the receive + # generator to terminate and not wait forever for data. + self.rx_char_handle = None + self.client = None + + def disconnect(self): + return self._run(self._disconnect_impl()) + + async def _write_impl(self, bytes_): + assert self.client is not None + assert self.write_task is None + + towrite = len(bytes_) + written = 0 + + async def _write(): + if self.client is not None: + nonlocal written + + # Write out in small chunks + while written < towrite: + remaining = towrite - written + length = min(remaining, JadeBleImpl.BLE_MAX_WRITE_SIZE) + ulimit = written + length + await self.client.write_gatt_char( + JadeBleImpl.IO_TX_CHAR_UUID, + bytearray(bytes_[written:ulimit]), + response=True, + ) + + written = ulimit + + # Hold on to the write task in case we need to cancel it + # whie it is running (eg. unexpected disconnection) + self.write_task = asyncio.create_task(_write()) + try: + await self.write_task + except asyncio.CancelledError: + logger.warning( + "write() task cancelled having written " + "{} of {} bytes".format(written, towrite) + ) + finally: + self.write_task = None + + return written + + def write(self, bytes_): + return self._run(self._write_impl(bytes_)) + + async def _read_impl(self, n): + assert self.inputstream is not None + return bytes([b async for b in aioitertools.islice(self.inputstream, n)]) + + def read(self, n): + return self._run(self._read_impl(n)) diff --git a/src/cryptoadvance/specter/devices/hwi/jadepy/jade_serial.py b/src/cryptoadvance/specter/devices/hwi/jadepy/jade_serial.py index e82e4753ea..075d49cdd2 100644 --- a/src/cryptoadvance/specter/devices/hwi/jadepy/jade_serial.py +++ b/src/cryptoadvance/specter/devices/hwi/jadepy/jade_serial.py @@ -1,8 +1,9 @@ import serial import logging +from serial.tools import list_ports -logger = logging.getLogger("jade.serial") +logger = logging.getLogger(__name__) # @@ -19,8 +20,28 @@ # (caveat cranium) # class JadeSerialImpl: + # Used when searching for devices that might be a Jade/compatible hw + JADE_DEVICE_IDS = [ + (0x10C4, 0xEA60), + (0x1A86, 0x55D4), + (0x0403, 0x6001), + (0x1A86, 0x7523), + ] + + @classmethod + def _get_first_compatible_device(cls): + jades = [] + for devinfo in list_ports.comports(): + if (devinfo.vid, devinfo.pid) in cls.JADE_DEVICE_IDS: + jades.append(devinfo.device) + + if len(jades) > 1: + logger.warning(f"Multiple potential jade devices detected: {jades}") + + return jades[0] if jades else None + def __init__(self, device, baud, timeout): - self.device = device + self.device = device or self._get_first_compatible_device() self.baud = baud self.timeout = timeout self.ser = None @@ -33,12 +54,24 @@ def connect(self): self.device, self.baud, timeout=self.timeout, write_timeout=self.timeout ) assert self.ser is not None - self.ser.__enter__() + + if not self.ser.is_open: + self.ser.open() + + # Ensure RTS and DTR are not set (as this can cause the hw to reboot) + self.ser.setRTS(False) + self.ser.setDTR(False) + logger.info("Connected") def disconnect(self): assert self.ser is not None - self.ser.__exit__() + + # Ensure RTS and DTR are not set (as this can cause the hw to reboot) + # and then close the connection + self.ser.setRTS(False) + self.ser.setDTR(False) + self.ser.close() # Reset state self.ser = None diff --git a/src/cryptoadvance/specter/devices/hwi/jadepy/jade_tcp.py b/src/cryptoadvance/specter/devices/hwi/jadepy/jade_tcp.py index 0849182cf0..712201fcbd 100644 --- a/src/cryptoadvance/specter/devices/hwi/jadepy/jade_tcp.py +++ b/src/cryptoadvance/specter/devices/hwi/jadepy/jade_tcp.py @@ -2,7 +2,7 @@ import logging -logger = logging.getLogger("jade.tcp") +logger = logging.getLogger(__name__) # @@ -25,9 +25,10 @@ class JadeTCPImpl: def isSupportedDevice(cls, device): return device is not None and device.startswith(cls.PROTOCOL_PREFIX) - def __init__(self, device): + def __init__(self, device, timeout): assert self.isSupportedDevice(device) self.device = device + self.timeout = timeout self.tcp_sock = None def connect(self): @@ -36,6 +37,7 @@ def connect(self): logger.info("Connecting to {}".format(self.device)) self.tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.tcp_sock.settimeout(self.timeout) url = self.device[len(self.PROTOCOL_PREFIX) :].split(":") self.tcp_sock.connect((url[0], int(url[1]))) @@ -57,4 +59,7 @@ def write(self, bytes_): def read(self, n): assert self.tcp_sock is not None - return self.tcp_sock.recv(n) + buf = self.tcp_sock.recv(n) + while len(buf) < n: + buf += self.tcp_sock.recv(n - len(buf)) + return buf diff --git a/src/cryptoadvance/specter/devices/jade.py b/src/cryptoadvance/specter/devices/jade.py index c5ba44c05f..190a77f427 100644 --- a/src/cryptoadvance/specter/devices/jade.py +++ b/src/cryptoadvance/specter/devices/jade.py @@ -16,6 +16,7 @@ class Jade(HWIDevice): supports_qr_message_signing = True supports_hwi_toggle_passphrase = False supports_hwi_multisig_display_address = True + supports_multisig_registration = True liquid_support = True @classmethod diff --git a/src/cryptoadvance/specter/htmlsafebabel.py b/src/cryptoadvance/specter/htmlsafebabel.py index b8b091d0d0..7f10eedc66 100644 --- a/src/cryptoadvance/specter/htmlsafebabel.py +++ b/src/cryptoadvance/specter/htmlsafebabel.py @@ -1,6 +1,7 @@ from flask_babel import Babel, get_translations from jinja2.utils import markupsafe import html +from flask import session, request class HTMLSafeBabel(Babel): diff --git a/src/cryptoadvance/specter/hwi_rpc.py b/src/cryptoadvance/specter/hwi_rpc.py index 5edf40053b..0a68bb492e 100644 --- a/src/cryptoadvance/specter/hwi_rpc.py +++ b/src/cryptoadvance/specter/hwi_rpc.py @@ -66,7 +66,7 @@ class HWIBridge(JSONRPC): All methods of this class are callable over JSON-RPC, except _underscored. """ - def __init__(self): + def __init__(self, skip_hwi_initialisation=False): self.exposed_rpc = { "enumerate": self.enumerate, "detect_device": self.detect_device, @@ -79,13 +79,20 @@ def __init__(self): "sign_tx": self.sign_tx, "sign_message": self.sign_message, "extract_master_blinding_key": self.extract_master_blinding_key, + "register_multisig": self.register_multisig, # currently only Jade "bitbox02_pairing": self.bitbox02_pairing, } + if skip_hwi_initialisation: + self.is_startup = False + self.devices = [] + return # Running enumerate after beginning an interaction with a specific device # crashes python or make HWI misbehave. For now we just get all connected # devices once per session and save them. - logger.info("Initializing HWI...") # to explain user why it takes so long + logger.info("Initializing HWI...") + self.is_startup = True # to explain user why it takes so long self.enumerate() + logger.info("Finished initializing HWI!") @locked(hwilock) def enumerate(self, passphrase="", chain=""): @@ -94,37 +101,21 @@ def enumerate(self, passphrase="", chain=""): Standard HWI enumerate() command + Specter. """ devices = [] - # going through all device classes + # Call device-specific enumerate (can come from hwi lib or from the Specter code base) for each Specter device class for devcls in hwi_classes: try: - # calling device-specific enumerate - if passphrase: - devs = devcls.enumerate(passphrase) - # not sure if it will handle passphrase correctly - # so remove it if None + # Special handling of the Jade to unsure it is not prompting to unlock the device on startup + if devcls.__name__ == "Jade": + skip_unlocking = self.is_startup + client_chain = Chain.argparse(chain) # This returns an enum member + devs = devcls.enumerate( + skip_unlocking=skip_unlocking, chain=client_chain + ) else: - devs = devcls.enumerate() - # extracting fingerprint info - for dev in devs: - # we can't get fingerprint if device is locked - if "needs_pin_sent" in dev and dev["needs_pin_sent"]: - continue - # we can't get fingerprint if passphrase is not provided - if ( - "needs_passphrase_sent" in dev - and dev["needs_passphrase_sent"] - and not passphrase - ): - continue - client = None - try: - client = devcls.get_client(dev["path"], passphrase) - if isinstance(client, Bitbox02Client): - client.set_noise_config(BitBox02NoiseConfig()) - dev["fingerprint"] = client.get_master_fingerprint().hex() - finally: - if client is not None: - client.close() + if passphrase: + devs = devcls.enumerate(passphrase) + else: + devs = devcls.enumerate() devices += devs except USBError as e: logger.warning( @@ -135,6 +126,7 @@ def enumerate(self, passphrase="", chain=""): ) self.devices = devices + self.is_startup = False return self.devices def detect_device( @@ -313,6 +305,34 @@ def display_address( else: raise Exception("Failed to validate address on device: Unknown Error") + @locked(hwilock) + def register_multisig( + self, + device_type=None, + path=None, + passphrase="", + fingerprint=None, + descriptor="", + chain="", + ): + if descriptor == "": + raise Exception("Descriptor must not be empty") + + with self._get_client( + device_type=device_type, + fingerprint=fingerprint, + path=path, + passphrase=passphrase, + chain=chain, + ) as client: + try: + return client.register_multisig(descriptor) + except Exception as e: + logger.exception(e) + raise Exception( + f"Failed to register multisig on the device. Error: {e}" + ) + @locked(hwilock) def sign_tx( self, @@ -423,22 +443,24 @@ def _get_client( ) devcls = get_device_class(device["type"]) if devcls: - client = devcls.get_client(device["path"], passphrase) + # Jade needs the chain/network already here for the the auth_call + if devcls.__name__ == "Jade": + client_chain = Chain.argparse(chain) + client = devcls.get_client( + path=device["path"], + password=passphrase, + expert=False, + chain=client_chain, + ) + else: + client = devcls.get_client(device["path"], passphrase) if not client: raise Exception( "The device was identified but could not be reached. Please check it is properly connected and try again" ) try: - if is_liquid(chain): - client.chain = Chain.TEST if is_testnet(chain) else Chain.MAIN - else: + if type(client) is not JadeClient: client.chain = Chain.argparse(chain) - # hack for Jade - if type(client) is JadeClient: - if is_liquid(chain): - client.set_liquid_network(chain) - elif chain == "signet": - client.chain = Chain.TEST yield client finally: client.close() diff --git a/src/cryptoadvance/specter/node.py b/src/cryptoadvance/specter/node.py index 5fadf963e4..464fe57617 100644 --- a/src/cryptoadvance/specter/node.py +++ b/src/cryptoadvance/specter/node.py @@ -668,10 +668,12 @@ def delete_wallet_file(self, wallet) -> bool: ) except RpcError: pass - if self.chain != "main": - path = os.path.join(datadir, f"{self.chain}/wallets", wallet_rpc_path) - else: + if self.chain == "test": + path = os.path.join(datadir, "testnet3/wallets", wallet_rpc_path) + elif self.chain == "main": path = os.path.join(datadir, wallet_rpc_path) + else: + path = os.path.join(datadir, f"{self.chain}/wallets", wallet_rpc_path) try: shutil.rmtree(path, ignore_errors=False) logger.debug(f"Removing wallet file at: {path}") diff --git a/src/cryptoadvance/specter/rpc.py b/src/cryptoadvance/specter/rpc.py index f749c8b2a5..abfda573ae 100644 --- a/src/cryptoadvance/specter/rpc.py +++ b/src/cryptoadvance/specter/rpc.py @@ -60,7 +60,13 @@ def get_rpcconfig(datadir=get_default_datadir()) -> dict: } """ config = { - "bitcoin.conf": {"default": {}, "main": {}, "test": {}, "regtest": {}}, + "bitcoin.conf": { + "default": {}, + "main": {}, + "test": {}, + "regtest": {}, + "signet": {}, + }, "cookies": {}, } if not os.path.isdir(datadir): # we don't know where to search for files diff --git a/src/cryptoadvance/specter/server.py b/src/cryptoadvance/specter/server.py index 9222955c55..84dde61822 100644 --- a/src/cryptoadvance/specter/server.py +++ b/src/cryptoadvance/specter/server.py @@ -50,12 +50,17 @@ def get_language_code(self): """ Helper for Babel and other related language selection tasks. """ - if "language_code" in session: - # Explicit selection - return session["language_code"] - else: - # autodetect - return request.accept_languages.best_match(self.supported_languages.keys()) + try: + if "language_code" in session: + # Explicit selection + return session["language_code"] + else: + # autodetect + return request.accept_languages.best_match( + self.supported_languages.keys() + ) + except: # RuntimeError: Working outside of request context. + return "en" def set_language_code(self, language_code): session["language_code"] = language_code @@ -177,7 +182,7 @@ def service_manager_cleanup_on_exit(signum, frame): specter.initialize() # HWI - specter.hwi = HWIBridge() + specter.hwi = HWIBridge(app.config["SKIP_HWI_INITIALISATION_AT_STARTUP"]) login_manager = LoginManager() login_manager.session_protection = app.config.get("SESSION_PROTECTION", "strong") @@ -262,13 +267,13 @@ def inject_tor(): app.config["BABEL_TRANSLATION_DIRECTORIES"] = os.path.join( sys._MEIPASS, "translations" ) - babel = HTMLSafeBabel(app) - @babel.localeselector def get_language_code(): # Enables Babel to auto-detect current language return app.get_language_code() + babel = HTMLSafeBabel(app, locale_selector=get_language_code) + @app.route("/set_language", methods=["POST"]) def set_language_code(): json_data = request.get_json() diff --git a/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py b/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py index 654f6cea60..e7bce93eae 100644 --- a/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py +++ b/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py @@ -788,6 +788,13 @@ def addresses(wallet_alias): @login_required def settings(wallet_alias): wallet: Wallet = app.specter.wallet_manager.get_by_alias(wallet_alias) + + # Check whether wallet has at least one device which supports multisig registrations (currently only Jade) + has_device_for_multisig_registration = any( + getattr(device, "supports_multisig_registration", False) + for device in wallet.devices + ) + if request.method == "POST": action = request.form["action"] # Would like to refactor this to another endpoint as well @@ -812,6 +819,7 @@ def settings(wallet_alias): purposes=purposes, wallet_alias=wallet_alias, wallet=wallet, + has_device_for_multisig_registration=has_device_for_multisig_registration, specter=app.specter, rand=rand, scroll_to_rescan_blockchain=request.args.get("rescan_blockchain"), diff --git a/src/cryptoadvance/specter/server_endpoints/wallets/wallets_api.py b/src/cryptoadvance/specter/server_endpoints/wallets/wallets_api.py index b2534af996..0ba25bec1d 100644 --- a/src/cryptoadvance/specter/server_endpoints/wallets/wallets_api.py +++ b/src/cryptoadvance/specter/server_endpoints/wallets/wallets_api.py @@ -916,7 +916,7 @@ def txlist_to_csv( tx["blockheight"] = "Unconfirmed" # For txs, the relevant amount is flow_amount if amount_logic == "flow": - tx["amount"] = tx.flow_amount + tx["amount"] = tx["flow_amount"] amount_price = "not supported" rate = "not supported" if tx.get("blocktime"): diff --git a/src/cryptoadvance/specter/static/hwi.js b/src/cryptoadvance/specter/static/hwi.js index 709359e3b2..a966e266aa 100644 --- a/src/cryptoadvance/specter/static/hwi.js +++ b/src/cryptoadvance/specter/static/hwi.js @@ -1,3 +1,7 @@ +function generateId() { + return Date.now().toString(36) + Math.random().toString(36).substring(2); +} + class HWIBridge { constructor(url, chain) { this.url = url; @@ -5,7 +9,7 @@ class HWIBridge { this.chain = chain; this.in_progress = false; } - async fetch(command, params={}, timeout=0){ + async requestToHwiBridge(command, params={}, timeout=0){ if(this.in_progress){ throw "HWI is busy processing previous request."; } @@ -17,9 +21,10 @@ class HWIBridge { const timeoutId = setTimeout(() => controller.abort(), timeout); } try{ - if (command != 'detect_device' && command != 'enumerate') { + if (command != 'detect_device') { params.chain = this.chain; } + const requestId = generateId() data = await fetch(this.url, { method: 'POST', headers: { @@ -30,7 +35,7 @@ class HWIBridge { body: JSON.stringify({ 'jsonrpc': '2.0', 'method': command, - 'id': 1, + 'id': requestId, params, forwarded_request: (this.url !== '/hwi/api/'), }) @@ -44,13 +49,13 @@ class HWIBridge { return data.result; } async enumerate(passphrase="", useTimeout){ - return await this.fetch("enumerate", { + return await this.requestToHwiBridge("enumerate", { passphrase }, (useTimeout ? 60000 : 0)); } async detectDevice(type, rescan=true){ // TODO: fingerprint, path, type - return await this.fetch("detect_device", + return await this.requestToHwiBridge("detect_device", { device_type: type, rescan_devices: rescan }); } @@ -59,7 +64,7 @@ class HWIBridge { Tells the server to send the 'togglepassphrase' command to the device. KeepKey and Trezor only. **/ - return await this.fetch('toggle_passphrase', { + return await this.requestToHwiBridge('toggle_passphrase', { device_type: device.type, path: device.path }); @@ -70,7 +75,7 @@ class HWIBridge { Asks the HWI server for a pairing code for BitBox02. Returns {"code": ""} with the code or an empty string if none found. **/ - return await this.fetch('bitbox02_pairing', {}); + return await this.requestToHwiBridge('bitbox02_pairing', {}); } async promptPin(device, passphrase="") { @@ -81,7 +86,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('prompt_pin', { + return await this.requestToHwiBridge('prompt_pin', { device_type: device.type, path: device.path, passphrase: device.passphrase, @@ -96,7 +101,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('send_pin', { + return await this.requestToHwiBridge('send_pin', { device_type: device.type, path: device.path, passphrase: device.passphrase, @@ -111,7 +116,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('sign_tx', { + return await this.requestToHwiBridge('sign_tx', { device_type: device.type, path: device.path, passphrase: device.passphrase, @@ -126,7 +131,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('sign_message', { + return await this.requestToHwiBridge('sign_message', { device_type: device.type, path: device.path, passphrase: device.passphrase, @@ -139,7 +144,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('extract_xpubs', { + return await this.requestToHwiBridge('extract_xpubs', { device_type: device.type, account: account, path: device.path, @@ -153,7 +158,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('extract_xpub', { + return await this.requestToHwiBridge('extract_xpub', { device_type: device.type, derivation: derivation, path: device.path, @@ -166,7 +171,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('extract_master_blinding_key', { + return await this.requestToHwiBridge('extract_master_blinding_key', { device_type: device.type, path: device.path, passphrase: device.passphrase, @@ -179,7 +184,7 @@ class HWIBridge { if(!('passphrase' in device)){ device.passphrase = passphrase; } - return await this.fetch('display_address', { + return await this.requestToHwiBridge('display_address', { device_type: device.type, path: device.path, passphrase: device.passphrase, @@ -187,4 +192,14 @@ class HWIBridge { xpubs_descriptor: xpubs_descriptor, }); } + + async registerMultisig(device, descriptor, fingerprint) { + return await this.requestToHwiBridge('register_multisig', { + device_type: device.type, + path: device.path, + passphrase: device.passphrase, + fingerprint: device.fingerprint, + descriptor: descriptor, + }) + } } diff --git a/src/cryptoadvance/specter/templates/components/price_bar.jinja b/src/cryptoadvance/specter/templates/components/price_bar.jinja index 2168353a62..f47ce0f36f 100644 --- a/src/cryptoadvance/specter/templates/components/price_bar.jinja +++ b/src/cryptoadvance/specter/templates/components/price_bar.jinja @@ -79,57 +79,29 @@ diff --git a/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja b/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja index ac99ff8a06..d29a329fdc 100644 --- a/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja +++ b/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja @@ -1,13 +1,13 @@ {% if wallet is defined %} -