Skip to Content
📢 Applications using LedgerJS for transport implementation should migrate to the Device Management Kit (DMK). Learn more.
DocumentationDevice interactionReferencesSignersSigner Zcash

Zcash Signer Kit

This module provides the implementation of the Ledger zcash signer of the Device Management Kit. It enables interaction with the zcash application on a Ledger device including:

  • Retrieving the zcash address using a given derivation path
  • Retrieving the zcash full viewing key (UFVK or Orchard FVK)
  • Signing a zcash transaction
  • Signing a message displayed on a Ledger device
  • Retrieving the app configuration

🔹 Index

  1. How it works
  2. Installation
  3. Initialisation
  4. Use Cases
  5. Observable Behavior
  6. Example

🔹 How it works

The Ledger Zcash Signer utilizes the advanced capabilities of the Ledger device to provide secure operations for end users. It takes advantage of the interface provided by the Device Management Kit to establish communication with the Ledger device and execute various operations. The communication with the Ledger device is performed using APDU s (Application Protocol Data Units), which are encapsulated within the Command object. These commands are then organized into tasks, allowing for the execution of complex operations with one or more APDUs. The tasks are further encapsulated within DeviceAction objects to handle different real-world scenarios. Finally, the Signer exposes dedicated and independent use cases that can be directly utilized by end users.

🔹 Installation

Note: This module is not standalone; it depends on the @ledgerhq/device-management-kit  package, so you need to install it first.

To install the device-signer-kit-zcash package, run the following command:

npm install @ledgerhq/device-signer-kit-zcash

🔹 Initialisation

To initialise a Zcash signer instance, you need a Ledger Device Management Kit instance and the ID of the session of the connected device. Use the SignerZcashBuilder:

const signerZcash = new SignerZcashBuilder({ dmk, sessionId }).build();

🔹 Use Cases

The SignerZcashBuilder.build() method will return a SignerZcash instance that exposes 5 dedicated methods, each of which calls an independent use case. Each use case will return an object that contains an observable and a method called cancel.


Use Case 1: Get App Configuration

This method allows users to retrieve the app configuration from the Ledger device.

const { observable, cancel } = signerZcash.getAppConfig();

Returns

  • observable Emits DeviceActionState updates, including the following details:
type GetAppConfigCommandResponse = { // TODO: Define the app configuration response type // Example: // version: string; // flags: number; };
  • cancel A function to cancel the action on the Ledger device.

Use Case 2: Get Address

This method allows users to retrieve the zcash address based on a given derivationPath.

const { observable, cancel } = signerZcash.getAddress(derivationPath, options);

Parameters

  • derivationPath

    • Required
    • Type: string (e.g., "m/44'/0'/0'/0/0")
    • The derivation path used for the zcash address. See here  for more information.
  • options

    • Optional

    • Type: AddressOptions

      type AddressOptions = { checkOnDevice?: boolean; skipOpenApp?: boolean; };
    • checkOnDevice: An optional boolean indicating whether user confirmation on the device is required (true) or not (false).

    • skipOpenApp: An optional boolean indicating whether to skip opening the zcash app automatically (true) or not (false).

Returns

  • observable Emits DeviceActionState updates, including the following details:
type GetAddressCommandResponse = { publicKey: Uint8Array; chainCode?: Uint8Array; };
  • cancel A function to cancel the action on the Ledger device.

Use Case 3: Get Full Viewing Key

This method allows users to retrieve the zcash full viewing key based on a given ZIP-32 account derivationPath.

const { observable, cancel } = signerZcash.getFullViewingKey( derivationPath, options, );

Parameters

  • derivationPath

    • Required
    • Type: string (e.g., "32'/133'/0'")
    • The account-level ZIP-32 derivation path used for the zcash full viewing key.
  • options

    • Optional

    • Type: FullViewingKeyOptions

      type FullViewingKeyOptions = { mode?: "ufvk" | "orchardFvk"; skipOpenApp?: boolean; };
    • mode: Optional export mode. Defaults to "ufvk".

      • "ufvk" returns the Unified Full Viewing Key as a UTF-8 string.
      • "orchardFvk" returns the raw Orchard Full Viewing Key bytes (Uint8Array).
    • skipOpenApp: An optional boolean indicating whether to skip opening the zcash app automatically (true) or not (false).

Returns

  • observable Emits DeviceActionState updates, including the following details:
type GetFullViewingKeyResult = | { mode: "ufvk"; fullViewingKey: string; } | { mode: "orchardFvk"; fullViewingKey: Uint8Array; };
  • cancel A function to cancel the action on the Ledger device.

Use Case 4: Sign Transaction

This method allows users to sign a zcash transaction.

const { observable, cancel } = signerZcash.signTransaction( derivationPath, transaction, options, );

Parameters

  • derivationPath

    • Required
    • Type: string (e.g., "m/44'/0'/0'/0/0")
    • The derivation path used for the zcash transaction. See here  for more information.
  • transaction

    • Required
    • Type: Uint8Array
    • The serialized transaction to be signed.
  • options

    • Optional

    • Type: TransactionOptions

      type TransactionOptions = { skipOpenApp?: boolean; };
    • skipOpenApp: An optional boolean indicating whether to skip opening the zcash app automatically (true) or not (false).

Returns

  • observable Emits DeviceActionState updates, including the following details:
type Signature = { r: string; s: string; v?: number; };
  • cancel A function to cancel the action on the Ledger device.

Use Case 5: Sign PCZT Transaction

This method signs an Orchard shielded transaction expressed as a PCZT (Partially Constructed Zcash Transaction). The host (zcash-utils) builds the PCZT and passes the structured PcztTransaction to the signer, which streams the header, transparent inputs/outputs, and Orchard actions to the device. The device returns the raw per-action Orchard spendAuthSigs and per-input transparent signatures; the binding signature and final transaction assembly are computed host-side and never involve the device. The legacy transparent signTransaction path is unchanged.

const { observable, cancel } = signerZcash.signPcztTransaction( transaction, options, );

Parameters

  • transaction

    • Required

    • Type: PcztTransaction

    • The structured PCZT to sign. Transparent sections are always streamed (count 0 when empty); orchardBundle may be null (treated as an empty Orchard bundle). All multi-byte integer fields are little-endian on the wire, except derivation-path components (big-endian Bip32Path).

      type PcztTransaction = { global: PcztGlobal; transparentInputs: PcztTransparentInput[]; transparentOutputs: PcztTransparentOutput[]; orchardBundle: PcztOrchardBundle | null; }; type PcztGlobal = { txVersion: number; // V5 = 5 versionGroupId: number; consensusBranchId: number; fallbackLockTime: number | null; // Option<u32>; null = absent expiryHeight: number; coinType: number; // 133 mainnet, 1 testnet txModifiable: number; }; type PcztBip32Derivation = { signingPath: string; pubkey: Uint8Array; // compressed secp256k1, 33 bytes seedFingerprint?: Uint8Array; // ZIP-32, 32 bytes }; type PcztTransparentInput = { prevoutTxid: Uint8Array; // 32 bytes prevoutIndex: number; sequence: number | null; // Option<u32>; null = absent value: bigint; // zatoshis scriptPubKey: Uint8Array; sighashType: number; // must be SIGHASH_ALL (0x01) derivation: PcztBip32Derivation; }; type PcztTransparentOutput = { value: bigint; // zatoshis scriptPubKey: Uint8Array; derivation?: PcztBip32Derivation | null; // entry count 0 or 1 }; type PcztOrchardBundle = { actions: PcztOrchardAction[]; flags: number; valueBalance: bigint; // net value balance, zatoshis (signed) anchor: Uint8Array; // commitment-tree anchor, 32 bytes };

      Each PcztOrchardAction carries the spend and output halves of one Orchard action (value/nullifier/rk commitments, spent- and output-note fields, the host-supplied spend-auth randomizer alpha, the signing signingPath, and the randomized commitment value rcv). See the PcztOrchardAction type exported from @ledgerhq/device-signer-kit-zcash for the full field list.

  • options

    • Optional

    • Type: TransactionOptions

      type TransactionOptions = { skipOpenApp?: boolean; };
    • skipOpenApp: An optional boolean indicating whether to skip opening the zcash app automatically (true) or not (false).

Returns

  • observable Emits DeviceActionState updates. The final output is:
type SignPcztTransactionResult = { // one spendAuthSig per Orchard action, in action order orchard: Array<{ spendAuthSig: Uint8Array }>; // RedPallas sig, 64 bytes each // one signature per transparent input, in input order: // DER-encoded secp256k1 signature + trailing sighash_type byte (0x01) transparentInputSigs: Uint8Array[]; };

There is no bindingSig in the result — it is computed host-side from bsk = Σ rcv. On Pending, requiredUserInteraction is UserInteractionRequired.SignTransaction.

  • cancel A function to cancel the action on the Ledger device.

Use Case 6: Sign Message

This method allows users to sign a text string that is displayed on Ledger devices.

const { observable, cancel } = signerZcash.signMessage(derivationPath, message);

Parameters

  • derivationPath

    • Required
    • Type: string (e.g., "m/44'/0'/0'/0/0")
    • The derivation path used for the zcash message. See here  for more information.
  • message

    • Required
    • Type: string | Uint8Array
    • The message to be signed, which will be displayed on the Ledger device.

Returns

  • observable Emits DeviceActionState updates, including the following details:
type Signature = { r: string; s: string; v?: number; };
  • cancel A function to cancel the action on the Ledger device.

🔹 Observable Behavior

Each method returns an Observable  emitting updates structured as DeviceActionState. These updates reflect the operation’s progress and status:

  • NotStarted: The operation hasn’t started.
  • Pending: The operation is in progress and may require user interaction.
  • Stopped: The operation was canceled or stopped.
  • Completed: The operation completed successfully, with results available.
  • Error: An error occurred.

Example Observable Subscription:

observable.subscribe({ next: (state: DeviceActionState) => { switch (state.status) { case DeviceActionStatus.NotStarted: { console.log("The action is not started yet."); break; } case DeviceActionStatus.Pending: { const { intermediateValue: { requiredUserInteraction }, } = state; // Access the intermediate value here, explained below console.log( "The action is pending and the intermediate value is: ", intermediateValue, ); break; } case DeviceActionStatus.Stopped: { console.log("The action has been stopped."); break; } case DeviceActionStatus.Completed: { const { output } = state; // Access the output of the completed action here console.log("The action has been completed: ", output); break; } case DeviceActionStatus.Error: { const { error } = state; // Access the error here if occurred console.log("An error occurred during the action: ", error); break; } } }, });

Intermediate Values in Pending Status:

When the status is DeviceActionStatus.Pending, the state will include an intermediateValue object that provides useful information for interaction:

const { requiredUserInteraction } = intermediateValue; switch (requiredUserInteraction) { case UserInteractionRequired.VerifyAddress: { // User needs to verify the address displayed on the device console.log("User needs to verify the address displayed on the device."); break; } case UserInteractionRequired.SignTransaction: { // User needs to sign the transaction displayed on the device console.log("User needs to sign the transaction displayed on the device."); break; } case UserInteractionRequired.SignPersonalMessage: { // User needs to sign the message displayed on the device console.log("User needs to sign the message displayed on the device."); break; } case UserInteractionRequired.None: { // No user action required console.log("No user action needed."); break; } case UserInteractionRequired.UnlockDevice: { // User needs to unlock the device console.log("The user needs to unlock the device."); break; } case UserInteractionRequired.ConfirmOpenApp: { // User needs to confirm on the device to open the app console.log("The user needs to confirm on the device to open the app."); break; } default: // Type guard to ensure all cases are handled const uncaughtUserInteraction: never = requiredUserInteraction; console.error("Unhandled user interaction case:", uncaughtUserInteraction); }

🔹 Example

We encourage you to explore the Zcash Signer by trying it out in our online sample application . Experience how it works and see its capabilities in action. Of course, you will need a Ledger device connected.

Last updated on
Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Stax, Ledger Flex, Ledger Nano, Ledger Nano S, Ledger OS, Ledger Wallet, [LEDGER] (logo), [L] (logo) are trademarks owned by Ledger SAS.