ZIP: 48
Title: Transparent Multisig Wallets
Owners: Kris Nuttycombe <kris@electriccoin.co>
Jack Grigg <jack@electriccoin.co>
Daira-Emma Hopwood <daira-emma@electriccoin.co>
Arya <arya@zfnd.org>
Credits: Fontaine
Status: Draft
Category: Wallet
Created: 2025-08-25
License: MIT
Discussions-To: <https://github.com/zcash/zips/issues/1059>
The key words “MUST”, “REQUIRED”, “MUST NOT”, “SHOULD”, and “MAY” in this document are to be interpreted as described in BCP 14 1 when, and only when, they appear in all capitals.
The terms “Mainnet” and “Testnet” in this document are to be interpreted as defined in the Zcash protocol specification 2.
This ZIP defines how wallets should implement transparent multisignature addresses in the Zcash ecosystem. It specifies how to derive the necessary key material in a deterministic way, and how to create transactions spending transparent multisig-controlled funds.
Any future changes, or new transparent protocols or scripts, will be made as Revisions to this ZIP.
There are a variety of important use cases for funds being controlled by a quorum of signers. In Bitcoin, these use cases were served via “P2SH multisig”, or more specifically a Pay-to-Multi-Signature (P2MS) script specified by a P2SH address.
A P2SM script can be constructed from any set of secp256k1 public keys, and anyone can use any mechanism for obtaining or storing the corresponding signing keys by private agreement. However, in order to ensure that funds are easily recoverable from backups, several different BIPs emerged for P2MS scripts using BIP 32 Hierarchical Derivation 3. Each of these has some problem when attempting to apply it as-written to Zcash:
BIP 45 4 uses m / purpose' / cosigner_index / change / address_index
.
coin_type' / account'
in its derivation path. If naively applied to Zcash, it would result in key reuse
between Bitcoin and Zcash, which creates the potential for cross-protocol attacks
that would be complex to rule out.BIP 48 5 uses m / purpose' / coin_type' / account' / script_type' / change / address_index
script_type
is specified solely for Bitcoin’s Native Segwit (P2WSH)
and Nested Segwit (P2SH-P2WSH). Several conflicting proposed script_type
constants
for P2SH have been considered at various points, and were rejected due to P2SH being
considered “legacy” in the context of Bitcoin.BIP 87 6 uses m / purpose' / coin_type' / account' / change / address_index
While there is verifiable usage of P2SH multisig on the Zcash chain, there has been no standard approach for public deployments (likely due to the above issues), and thus no support for it in Zcash-compatible hardware wallets. This ZIP fills the gap.
This does not have any privacy implications beyond what users already accept by using the transparent protocol and BIP 44 in particular for transparent address derivation.
Hardware wallet providers should only need to make limited changes relative to how they support Bitcoin P2SH multisig. As such, this specification should hew closely to the specifications used in the Bitcoin ecosystem for P2SH support.
This ZIP applies to both Mainnet and Testnet, using the same \(\mathit{coin\_type}\) constants as defined in 7 where applicable. Note that in keeping with that document, all cryptocurrency testnets share \(\mathit{coin\_type}\) index 1.
Zcash wallets SHOULD use the BIP 383 8 sortedmulti()
script expression to
produce Zcash P2MS scripts, which would then be contained in the BIP 381 9
sh()
script expression to produce Zcash P2SH addresses.
The following BIP 388 10 wallet descriptor template produces a 2-of-3 P2SH multisig wallet compatible with this ZIP:
sh(sortedmulti(2,@0/**,@1/**,@2/**))
Given a set of participants that each have some seed material (e.g. BIP 39 11 mnemonic phrases), and a BIP 380 12 output descriptor that encodes the desired multisig policy, wallets need to produce their individual contributions to the BIP 388 key information vector 13. This specification adopts BIP 48 5 for this purpose, with the following modification:
The \(\mathit{script\_type}\) values for Native Segwit and Nested Segwit MUST NOT be used in a Zcash context.
The following Zcash-specific \(\mathit{script\_type}\) key spaces are defined:
The transaction construction workflow for a P2SH multisig wallet is as follows:
One of the participants uses their local wallet to construct a PCZT 14 that
spends transparent UTXOs controlled by the multisig wallet, with the desired outputs,
and a change output to a P2SH address derived at the path
m/48'/coin_type'/account'/133000'/1/*
.
The PCZT is conveyed to the other wallet participants out-of-band (via a private and authenticated channel, such as a group end-to-end-encrypted messenger).
Each participant’s wallet MUST verify that the PCZT’s change outputs are to an address that the multisig wallet controls.
Each participant verifies that the PCZT performs the spend action that they expect.
Each participant modifies their copy of the PCZT to include their signature(s) using their wallet.
Each participant conveys their signed PCZT to one (or more) of the other wallet participants (likely via the same channel that the unsigned PCZT was received on).
One participant takes a threshold of signed PCZTs, combines them, and extracts the final transaction for broadcasting to the network.
Wallets SHOULD ensure they are fully synced before proposing a transaction that generates transparent change, to minimize the likelihood of reusing a P2SH change address.
Let the seeds for the three participants be (in hex):
[
"0101010101010101010101010101010101010101010101010101010101010101",
"0202020202020202020202020202020202020202020202020202020202020202",
"0303030303030303030303030303030303030303030303030303030303030303"
]
TODO: Update example below with actual key fingerprints and xpub encodings.
The wallet descriptor template for this multisig is:
sh(sortedmulti(2,@0/**,@1/**,@2/**))
The three parties each derive their transparent extended public key from their respective
wallet seed, using the path m/48'/133'/0'/133000'
. The parties then combine their
respective keys to produce a key information vector 13:
[
"[MSTKFP_0/48'/133'/0'/133000']EXTENDED_PUBLIC_KEY_0",
"[MSTKFP_1/48'/133'/0'/133000']EXTENDED_PUBLIC_KEY_1",
"[MSTKFP_2/48'/133'/0'/133000']EXTENDED_PUBLIC_KEY_2"
]
The wallet policy is the composition of the wallet descriptor template and the key information vector. The corresponding multipath descriptor 15 derived from this policy 16 is (newlines and whitespace added for legibility):
sh(
sortedmulti(
2,
[MSTKFP_0/48'/133'/0'/133000']EXTENDED_PUBLIC_KEY_0/<0;1>/*,
[MSTKFP_1/48'/133'/0'/133000']EXTENDED_PUBLIC_KEY_1/<0;1>/*,
[MSTKFP_2/48'/133'/0'/133000']EXTENDED_PUBLIC_KEY_2/<0;1>/*
)
)
The choice to use BIP 48 means that participants are not restricted to constructing a single P2SH address; once a wallet has obtained the completed wallet policy, it can (without additional coordination) produce the same deterministic sequence of P2SH addresses as every other participant.
We specify a likely-non-colliding \(\mathit{script\_type}\) instead of using a path component of either \(0'\) (the gap left in BIP 48) or \(2'\) (the next unassigned constant in BIP 48), because both of those values have in the past been proposed and rejected as referring to P2SH. This means that there is no compatibility benefit to trying to use one or the other, and the likelihood of a collision with some other use case is higher. Instead we pick a value that is prefixed (in decimal) with the SLIP 44 7 \(\mathit{coin\_type}\) for Zcash mainnet, which is highly unlikely to organically occur in another related specification.
By not using BIP 45, Zcash addresses will not be compatible with BIP 45 P2SH derivation paths. We consider this acceptable because we explicitly do not want users that use the same mnemonic phrase for both Bitcoin and Zcash to produce addresses that reuse the same public keys.
By not using BIP 45 (with a \(\mathit{cosigner\_index}\) path element), all cosigners produce the same sequence of P2SH addresses. This is good for agreement on, for example, the ZIP 1016 17 Key-Holder Organizations’ Mainnet address, but in general usage means that two cosigners proposing transactions can race and use the same P2SH address twice. This could result in a situation where different members of the signing set inadvertently give the same P2SH address to different counterparties, linking the associated payment activity. We consider this tradeoff acceptable because it aligns with what hardware wallets have done for years (implying that users are fine with the usability). We consider the risk acceptable because avoiding linkage between transparent addresses is hard in any case, and not something that Zcash actively attempts to avoid (the shielded protocols are designed for this use case).
TBD
Information on BCP 14 — “RFC 2119: Key words for use in RFCs to Indicate Requirement Levels” and “RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words” ↩︎
Zcash Protocol Specification, Version 2024.5.1. Section 3.12: Mainnet and Testnet ↩︎
BIP 45: Structure for Deterministic P2SH Multisignature Wallets ↩︎
BIP 388: Wallet Policies for Descriptor Wallets. Key information vector ↩︎
zip-pczt ↩︎
BIP 388: Wallet Policies for Descriptor Wallets. Descriptor derivation ↩︎