-
Notifications
You must be signed in to change notification settings - Fork 4
Manuals
Following sections will try to provide basic understanding on how the bindings layer works for high level operations. How to derive keys and how to build transactions at an API level.
Its not the intention of this manuals to fully go into the understanding of the zcash protocol. For that, we recommend the official protocol specification. Also, the Api surface analysis section can help on getting some more knowledge.
The FFI layer doesn't full coverage of the entire librustzcash API. Its an estimation of the elements that we understand should be used more frequently. If your case is not implemented and you think it should, we recommend to read our contributing guidelines.
Sprout
transactions are out of the target of this project.
Before going through each language examples, its assumed a minimal language tools configuration and a minimal project setup is present. For the later, examples can be found in the project templates folder which are being used by our CLI for testing the packages:
If its intended to interact with Sapling
transactions, its needed to download the Sapling
crypto setup. For doing that there are multiple options:
-
Invoke this repository CLI setup commands, that will place the params in your home dir automatically. The Zcash code will look for it if on that place if default location based local provers are selected. See the local prover interface for more details.
$ cargo run -p uniffi-zcash-cli setup saplingparams
-
Indicate the paths to the params files in runtime, by using the local prover interface , which is available in all binding generated languages.
-
Pass the bytes to the Indicate the paths to the params files in runtime, by using the local prover interface , which is available in all binding generated languages.
Kotlin
has some limitations regarding finalizers, which are responsible of freeing the objects in the Rust side of the allocations. An explicit destructor must be called close()
on kotlin objects in order to not incurr in memory leaks. More information here.
In our examples, we are omitting this part for brevity.
Image borrowed from the original official protocol specification. Section 3.1.
Zcash works with a system of keys derivation in order to facilitate granular read access to certain parts of a transaction to pre-determined parties.
This key derivation protocols are evolving in time along with the blockchain, making as a product the different "key eras" we can make use of, like Sapling
and Orchard
. The main difference among this two kinds of shielded transactions is the underlying proving technology. At a very high level, main difference is Orchard
does not require a pre-calculated crypto setup like Sapling
. Such differences clearly affects pre-requisites and how we use them in the APIs.
During this document, we will use the following abbreviates for reducing verbosity:
SK
- Spending key .
FVK
- Full viewing key.
USK
- Unified spending key
UFVK
- Unified full viewing key.
OVK
- Outgoing viewing key.
IVK
- Incoming viewing key.
Lets check how to do key derivation for the different available Eras.
We start from a SK
.
- Generate an
Expanded Spending Key
- This is derived directly from theSK
. - Generate a
Proof Authorizing Key
- This is used to build zero knowledge proofs for shielded transactions. - Generate the
FVK
- Needed to view incoming or outgoing transactions. - Generate
IVK
- Needed to view incoming transactions only (not outgoing). - Payment address - A shielded address which may be used as recipient for a shielded transaction.
Ruby
require "zcash"
seed = [0] * 32 ## Dummy data, set a proper seed here.
ext_sk = Zcash::ZcashExtendedSpendingKey.master(seed)
expanded_sk = Zcash::ZcashExpandedSpendingKey.from_spending_key(ext_sk.to_bytes())
proof_authorizing_key = expanded_sk.proof_generation_key()
fvk = ext_sk.to_diversifiable_full_viewing_key()
## requires a scope, INTERNAL or EXTERNAL
ivk = fvk.to_ivk(Zcash::ZcashScope::INTERNAL)
## requires a diversifier
diversifier_bytes = [0] * 11 ## Dummy data, set a proper diversifier here.
diversifier = Zcash::ZcashDiversifier.new(diversifier_bytes)
## this is the address struct
shielded_address = ivk.to_payment_address(diversifier)
## to derive the string form, we need the network params: MAIN_NETWORK or TEST_NETWORK
params = Zcash::ZcashConsensusParameters::MAIN_NETWORK
shielded_address_stringified = shielded_address.encode(params)
print(shielded_address_stringified)
Python
from zcash import *
def sapling_key_derivation():
seed = [0] * 32 ## Dummy data, set a proper seed here.
ext_sk = ZcashExtendedSpendingKey.master(seed)
expanded_sk = ZcashExpandedSpendingKey.from_spending_key(ext_sk.to_bytes())
proof_authorizing_key = expanded_sk.proof_generation_key()
fvk = ext_sk.to_diversifiable_full_viewing_key()
## requires a scope, INTERNAL or EXTERNAL
ivk = fvk.to_ivk(ZcashScope.INTERNAL)
## requires a diversifier
diversifier_bytes = [0] * 11 ## Dummy data, set a proper diversifier here.
diversifier = ZcashDiversifier(diversifier_bytes)
## this is the address struct
shielded_address = ivk.to_payment_address(diversifier)
## to derive the string form, we need the network params: MAIN_NETWORK or TEST_NETWORK
params = ZcashConsensusParameters.MAIN_NETWORK
shielded_address_stringified = shielded_address.encode(params)
print(shielded_address_stringified)
if __name__ == '__main__':
sapling_key_derivation()
Kotlin
import uniffi.zcash.*
fun main() {
val seed = listOf(0).map { it.toUByte() } // Dummy data, set a proper seed here.
val extSK = ZcashExtendedSpendingKey.master(seed)
val expandedSK = ZcashExpandedSpendingKey.fromSpendingKey(extSK.toBytes())
val proofAuthorizingKey = expandedSK.proofGenerationKey()
val fvk = extSK.toDiversifiableFullViewingKey()
// requires a scope, INTERNAL or EXTERNAL
val ivk = fvk.toIvk(ZcashScope.INTERNAL)
// requires a diversifier
val diversifierBytes = listOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).map { it.toUByte() } // Dummy data, set a proper diversifier here.
val diversifier = ZcashDiversifier(diversifierBytes)
// this is the address struct
val shieldedAddress = ivk.toPaymentAddress(diversifier)
// to derive the string form, we need the network params: MAIN_NETWORK or TEST_NETWORK
val params = ZcashConsensusParameters.MAIN_NETWORK
val shieldedAddressStringified = shieldedAddress!!.encode(params)
println(shieldedAddressStringified)
}
Swift
import Zcash
let seed: [UInt8] = Array(repeating: 0, count: 32) // Dummy data, set a proper seed here.
let extSK = ZcashExtendedSpendingKey.master(data: seed)
let expandedSK = ZcashExpandedSpendingKey.fromSpendingKey(sk: extSK.toBytes())
let proofAuthorizingKey = expandedSK.proofGenerationKey()
let fvk = extSK.toDiversifiableFullViewingKey()
// requires a scope, INTERNAL or EXTERNAL
let ivk = fvk.toIvk(scope: ZcashScope.internal)
// requires a diversifier
let diversifierBytes: [UInt8] = Array(repeating: 0, count: 11) // Dummy data, set a proper diversifier here.
let diversifier = try! ZcashDiversifier(bytes: diversifierBytes)
// this is the address struct
let shieldedAddress = ivk.toPaymentAddress(diversifier: diversifier)!
// to derive the string form, we need the network params: MAIN_NETWORK or TEST_NETWORK
let params = ZcashConsensusParameters.mainNetwork
let shieldedAddressStringified = shieldedAddress.encode(params: params)
print(shieldedAddressStringified)
Orchard
has a simpler model, but it still has a hierarchy. Some functions were adequately called with Orchard
when they enter in conflict with the homologous Sapling
ones.
-
All starts for generating an
Orchard
spending key from a seed. -
Then we can derive to the
FVK
, from which we can derive the other 2 keys:- The
IVK
that will allow to see incoming transactions. - The
OVK
that will allow to see outgoing transactions.
- The
-
Once we have this keys, we can properly generate addresses
- With a diversifier.
- With a diversifier and index.
Python
from zcash import *
def orchard_key_derivation():
## Generating the SK.
seed = [1] * 32 ## Dummy data, set a proper seed here.
account = 0
coin_type = 1
orchard_spending_key = ZcashOrchardSpendingKey.from_zip32_seed(seed, coin_type, account)
## Derivating FVK to IVK and OVK.
fvk = orchard_spending_key.to_fvk()
ivk = fvk.to_ivk(ZcashOrchardScope.INTERNAL)
ovk = fvk.to_ovk(ZcashOrchardScope.INTERNAL)
## Two ways of generating addresses.
div_bytes = [0] * 11
diversifier = ZcashOrchardDiversifier.from_bytes(div_bytes)
address_struct1 = fvk.address(diversifier, ZcashOrchardScope.INTERNAL)
div_index = ZcashOrchardDiversifierIndex.from_u32(0)
address_struct2 = fvk.address_at(div_index, ZcashOrchardScope.INTERNAL)
if __name__ == '__main__':
orchard_key_derivation()
Ruby
require "zcash"
## Generating the SK.
seed = [1] * 32 ## Dummy data, set a proper seed here.
account = 0
coin_type = 1
orchard_spending_key = Zcash::ZcashOrchardSpendingKey.from_zip32_seed(seed, coin_type, account)
## Derivating FVK to IVK and OVK.
fvk = orchard_spending_key.to_fvk()
ivk = fvk.to_ivk(Zcash::ZcashOrchardScope::INTERNAL)
ovk = fvk.to_ovk(Zcash::ZcashOrchardScope::INTERNAL)
## Two ways of generating addresses.
div_bytes = [0] * 11
diversifier = Zcash::ZcashOrchardDiversifier.from_bytes(div_bytes)
address_struct1 = fvk.address(diversifier, Zcash::ZcashOrchardScope::INTERNAL)
div_index = Zcash::ZcashOrchardDiversifierIndex.from_u32(0)
address_struct2 = fvk.address_at(div_index, Zcash::ZcashOrchardScope::INTERNAL)
Kotlin
import uniffi.zcash.*
fun main() {
// Generating the SK.
val seed = listOf(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).map { it.toUByte() } // Dummy data, set a proper seed here.
val account = 0u
val coinType = 1u
val orchardSpendingKey = ZcashOrchardSpendingKey.fromZip32Seed(seed, coinType, account)
// Derivating FVK to IVK and OVK.
val fvk = orchardSpendingKey.toFvk()
val ivk = fvk.toIvk(ZcashOrchardScope.INTERNAL)
val ovk = fvk.toOvk(ZcashOrchardScope.INTERNAL)
// Two ways of generating addresses.
val divBytes = listOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).map { it.toUByte() }
val diversifier = ZcashOrchardDiversifier.fromBytes(divBytes)
val addressStruct1 = fvk.address(diversifier, ZcashOrchardScope.INTERNAL)
val divIndex = ZcashOrchardDiversifierIndex.fromU32(0u)
val addressStruct2 = fvk.addressAt(divIndex, ZcashOrchardScope.INTERNAL)
}
Swift
import Zcash
// Generating the SK.
let seed: [UInt8] = Array(repeating: 0, count: 32) // Dummy data, set a proper seed here.
let account: UInt32 = 0
let coinType: UInt32 = 1
let orchardSpendingKey = try! ZcashOrchardSpendingKey.fromZip32Seed(seed: seed, coinType: coinType, account: account)
// Derivating FVK to IVK and OVK.
let fvk = orchardSpendingKey.toFvk()
let ivk = fvk.toIvk(scope: ZcashOrchardScope.internal)
let ovk = fvk.toOvk(scope: ZcashOrchardScope.internal)
// Two ways of generating addresses.
let divBytes: [UInt8] = Array(repeating: 0, count: 11) // Dummy data, set a proper diversifier here.
let diversifier = try! ZcashOrchardDiversifier.fromBytes(bytes: divBytes)
let addressStruct1 = fvk.address(d: diversifier, scope: ZcashOrchardScope.internal)
let divIndex = ZcashOrchardDiversifierIndex.fromU32(i: 0)
let addressStruct2 = fvk.addressAt(j: divIndex, scope: ZcashOrchardScope.internal)
The unified framework coming, it is important to understand how to go from a USK
and back from the parts to the whole again, understanding its limitations. Overall, the use of unified key sets is the new way to go. Theres more information here . Some of the operations this framework allows are:
graph LR;
style UFVK fill:#66b2ff
style USK fill:#66b2ff
USK-->TransparentAccountPrivKey
TransparentAccountPrivKey-->TransparentAccountPubKey
USK-->SaplingExtendedSK
SaplingExtendedSK-->SaplingExtendedFVK
SaplingExtendedFVK-->SaplingDiversifiableFVK
SaplingDiversifiableFVK-->SaplingIVK
SaplingIVK-->PaymentAddress
SaplingDiversifiableFVK-->SaplingOVK
SaplingExtendedSK-->SaplingDiversifiableFVK
USK-->OrchardSK
OrchardSK-->OrchardFVK
OrchardFVK-->OrchardIVK
OrchardIVK--->OrchardAddress
OrchardFVK-->OrchardOVK
UFVK-->TransparentAccountPubKey
UFVK-->SaplingDiversifiableFVK
UFVK-->OrchardFVK
USK-->UFVK
- Generate an
USK
from a seed. - From the
USK
, we can generateSK
for each other pool:Transparent
,Sapling
andOrchard
. - We can derive an
USK
to anUFVK
, which brings access to the respectiveFVK
of each pool,Transparent
,Sapling
andOrchard
. -
FVK's
can be built up from the base keys of each specific pools. - A
UFVK
can be composed by theFVK
of each pool,Transparent
,Sapling
andOrchard
. - Then we can generate
unified addresses
from theUFVK
. - Finally from an
unified addresses
we can generate the specific addresses for each pool,Transparent
,Sapling
andOrchard
.
We can also see this interactions in code here:
Python
from zcash import *
def unified_key_derivation():
## Generating the unified SK.
seed = [1] * 32 ## Dummy data, set a proper seed here.
params = ZcashConsensusParameters.MAIN_NETWORK
usk = ZcashUnifiedSpendingKey.from_seed(params, seed, ZcashAccountId(0))
## Deriving to each pool specific SK, from the USK.
transparent_sk = usk.transparent()
sapling_sk = usk.sapling()
orchard_sk = usk.orchard()
## Derive from USK, to UFVK.
ufvk = usk.to_unified_full_viewing_key()
## Deriving to each pool specific FVK, from the UFVK.
transparent_fvk = ufvk.transparent()
sapling_fvk = ufvk.sapling()
orchard_fvk = ufvk.orchard()
## The unified spending key, can be composed again from previous public keys.
## Where the important thing for them to be equivalent is to be from the same seed.
## Building a sapling FVK from sapling specific keys.
ext_sk = ZcashExtendedSpendingKey.master(seed)
sapling_fvk_from_specific = ext_sk.to_diversifiable_full_viewing_key()
## Also, building a sapling FVK from sapling specific keys.
account = 0
coin_type = 1
orchard_sk_from_parts = ZcashOrchardSpendingKey.from_zip32_seed(seed, coin_type, account)
orchard_fvk_from_specific = orchard_sk_from_parts.to_fvk()
## The unified UFVK again. Built up from base elements.
ufvk_from_parts = ZcashUnifiedFullViewingKey(transparent_fvk, sapling_fvk, orchard_fvk)
## Generating unified addresses.
div_index = ZcashDiversifierIndex.from_u32(9701970)
address_struct = ufvk.address(div_index)
## Unified addreses, which may be encoded with params as above:
encoded_addr = address_struct.encode(params) ## string address.
print(encoded_addr) ## Begins with u , for unified.
## Also Unified addreses from which we could extract the specific pool addresses:
sapling_addr = address_struct.sapling()
orchard_addr = address_struct.orchard()
transparent_addr = address_struct.transparent()
if __name__ == '__main__':
unified_key_derivation()
Ruby
require "zcash"
## Generating the unified SK.
seed = [1] * 32 ## Dummy data, set a proper seed here.
params = Zcash::ZcashConsensusParameters::MAIN_NETWORK
usk = Zcash::ZcashUnifiedSpendingKey.from_seed(params, seed, Zcash::ZcashAccountId.new(0))
## Deriving to each pool specific SK, from the USK.
transparent_sk = usk.transparent()
sapling_sk = usk.sapling()
orchard_sk = usk.orchard()
## Derive from USK, to UFVK.
ufvk = usk.to_unified_full_viewing_key()
## Deriving to each pool specific FVK, from the UFVK.
transparent_fvk = ufvk.transparent()
sapling_fvk = ufvk.sapling()
orchard_fvk = ufvk.orchard()
## The unified spending key, can be composed again from previous public keys.
## Where the important thing for them to be equivalent is to be from the same seed.
## Building a sapling FVK from sapling specific keys.
ext_sk = Zcash::ZcashExtendedSpendingKey.master(seed)
sapling_fvk_from_specific = ext_sk.to_diversifiable_full_viewing_key()
## Also, building a sapling FVK from sapling specific keys.
account = 0
coin_type = 1
orchard_sk_from_parts = Zcash::ZcashOrchardSpendingKey.from_zip32_seed(seed, coin_type, account)
orchard_fvk_from_specific = orchard_sk_from_parts.to_fvk()
## The unified UFVK again. Built up from base elements.
ufvk_from_parts = Zcash::ZcashUnifiedFullViewingKey.new(transparent_fvk, sapling_fvk, orchard_fvk)
## Generating unified addresses.
div_index = Zcash::ZcashDiversifierIndex.from_u32(9701970)
address_struct = ufvk.address(div_index)
## Unified addreses, which may be encoded with params as above:
encoded_addr = address_struct.encode(params) ## string address.
print(encoded_addr) ## Begins with u , for unified.
## Also Unified addreses from which we could extract the specific pool addresses:
sapling_addr = address_struct.sapling()
orchard_addr = address_struct.orchard()
transparent_addr = address_struct.transparent()
Kotlin
import uniffi.zcash.*
fun main() {
// Generating the unified SK.
val seed = listOf(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).map { it.toUByte() } // Dummy data, set a proper seed here.
val params = ZcashConsensusParameters.MAIN_NETWORK
val usk = ZcashUnifiedSpendingKey.fromSeed(params, seed, ZcashAccountId(0u))
// Deriving to each pool specific SK, from the USK.
val transparentSK = usk.transparent()
val saplingSK = usk.sapling()
val orchardSK = usk.orchard()
// Derive from USK, to UFVK.
val ufvk = usk.toUnifiedFullViewingKey()
// Deriving to each pool specific FVK, from the UFVK.
val transparentFvk = ufvk.transparent()
val saplingFvk = ufvk.sapling()
val orchardFvk = ufvk.orchard()
// The unified spending key, can be composed again from previous public keys.
// Where the important thing for them to be equivalent is to be from the same seed.
// Building a sapling FVK from sapling specific keys.
val extSK = ZcashExtendedSpendingKey.master(seed)
val saplingFvkFromSpecific = extSK.toDiversifiableFullViewingKey()
// Also, building a sapling FVK from sapling specific keys.
val account = 0u
val coinType = 1u
val orchardSKFromParts = ZcashOrchardSpendingKey.fromZip32Seed(seed, coinType, account)
val orchardFvkFromSpecific = orchardSKFromParts.toFvk()
// The unified UFVK again. Built up from base elements.
val ufvkFromParts = ZcashUnifiedFullViewingKey(transparentFvk, saplingFvk, orchardFvk)
// Generating unified addresses.
val divIndex = ZcashDiversifierIndex.fromU32(9701970u)
val addressStruct = ufvk.address(divIndex)
// Unified addreses, which may be encoded with params as above:
val encodedAddr = addressStruct!!.encode(params) // string address.
println(encodedAddr) // Begins with u , for unified.
// Also Unified addreses from which we could extract the specific pool addresses:
val saplingAddr = addressStruct.sapling()
val orchardAddr = addressStruct.orchard()
val transparentAddr = addressStruct.transparent()
}
Swift
import Zcash
// Generating the unified SK.
let seed: [UInt8] = Array(repeating: 1, count: 32) // Dummy data, set a proper seed here.
let params = ZcashConsensusParameters.mainNetwork
let usk = try! ZcashUnifiedSpendingKey.fromSeed(params: params, seed: seed, accountId: ZcashAccountId(id: 0))
// Deriving to each pool specific SK, from the USK.
let transparentSK = usk.transparent()
let saplingSK = usk.sapling()
let orchardSK = usk.orchard()
// Derive from USK, to UFVK.
let ufvk = usk.toUnifiedFullViewingKey()
// Deriving to each pool specific FVK, from the UFVK.
let transparentFvk = ufvk.transparent()
let saplingFvk = ufvk.sapling()
let orchardFvk = ufvk.orchard()
// The unified spending key, can be composed again from previous public keys.
// Where the important thing for them to be equivalent is to be from the same seed.
// Building a sapling FVK from sapling specific keys.
let extSK = ZcashExtendedSpendingKey.master(data: seed)
let saplingFvkFromSpecific = extSK.toDiversifiableFullViewingKey()
// Also, building a sapling FVK from sapling specific keys.
let account: UInt32 = 0
let coinType: UInt32 = 1
let orchardSKFromParts = try! ZcashOrchardSpendingKey.fromZip32Seed(seed: seed, coinType: coinType, account: account)
let orchardFvkFromSpecific = orchardSKFromParts.toFvk()
// The unified UFVK again. Built up from base elements.
let ufvkFromParts = try! ZcashUnifiedFullViewingKey(transparent: transparentFvk, sapling: saplingFvk, orchard: orchardFvk)
// Generating unified addresses.
let divIndex = ZcashDiversifierIndex.fromU32(i: 9701970)
let addressStruct = ufvk.address(j: divIndex)!
// Unified addreses, which may be encoded with params as above:
let encodedAddr = addressStruct.encode(params: params) // string address.
print(encodedAddr) // Begins with u , for unified.
// Also Unified addreses from which we could extract the specific pool addresses:
let saplingAddr = addressStruct.sapling()
let orchardAddr = addressStruct.orchard()
let transparentAddr = addressStruct.transparent()
The central idea of a wallet is being able to send and receive transactions, i.e. the main reason to have for example a skin wallet with you was to have (bank)notes to spend. In the same way, a crypto wallet exists mainly to spend and receive (in our case) Zcash private notes.
As ZIP-317 introduces proportional fees, a high level of combinations for building transactions are available. This guide will focus on a basic, latest Orchard
example, while other types and uses can be found in integration tests.
Let's go through it step by step. Let's see the examples in code:
Python code
from zcash import *
def transaction():
## Prepare all the needed keys
params = ZcashConsensusParameters.MAIN_NETWORK
usk = ZcashUnifiedSpendingKey.from_seed(params, [0] * 32, ZcashAccountId(0))
fvk = usk.to_unified_full_viewing_key().orchard()
## External scope, as this an outgoing vieweing key, not subject to internal wallet operations.
ovk = fvk.to_ovk(ZcashOrchardScope.EXTERNAL)
diversifier = ZcashOrchardDiversifier.from_bytes([0] * 11)
note_value = ZcashOrchardNoteValue.from_raw(15)
nullifier = ZcashOrchardNullifier.from_bytes([0] * 32)
rseed = ZcashOrchardRandomSeed.from_bytes([0] * 32, nullifier)
address = fvk.to_ivk(ZcashOrchardScope.INTERNAL).address(diversifier) ## this is a struct, not the encoded version yet
## note struct creation
note = ZcashOrchardNote.from_parts(address, note_value, nullifier, rseed)
note_commitment = note.commitment().to_extracted_note_commitment()
## Configure the merkle path/auth path.
merkle_hash = ZcashOrchardMerkleHash.from_bytes([0] * 32)
auth_path = [ merkle_hash ] * 32
merkle_path = ZcashOrchardMerklePath.from_parts(0, auth_path)
block_height = 2030820
target_block_height = ZcashBlockHeight(block_height)
expiry_block_height = ZcashBlockHeight(block_height+100)
flags = ZcashOrchardFlags.from_parts(True, True) ## spends enabled, outputs enabled.
anchor = merkle_path.root(note_commitment)
builder = ZcashOrchardTransactionBuilder(params, target_block_height, expiry_block_height, anchor, flags)
builder.add_spend(fvk, note, merkle_path)
## Sending the note to themselves this time for the example.
builder.add_recipient(ovk, address, note_value, None) ## No memo field so null.
orchard_sk = usk.orchard()
sig_hash = [0] * 32
transaction_struct = builder.build([orchard_sk], sig_hash)
if __name__ == '__main__':
transaction()
Ruby code
require "zcash"
## Prepare all the needed keys
params = Zcash::ZcashConsensusParameters::MAIN_NETWORK
usk = Zcash::ZcashUnifiedSpendingKey.from_seed(params, [0] * 32, Zcash::ZcashAccountId.new(0))
fvk = usk.to_unified_full_viewing_key().orchard()
## External scope, as this an outgoing vieweing key, not subject to internal wallet operations.
ovk = fvk.to_ovk(Zcash::ZcashOrchardScope::EXTERNAL)
diversifier = Zcash::ZcashOrchardDiversifier.from_bytes([0] * 11)
note_value = Zcash::ZcashOrchardNoteValue.from_raw(15)
nullifier = Zcash::ZcashOrchardNullifier.from_bytes([0] * 32)
rseed = Zcash::ZcashOrchardRandomSeed.from_bytes([0] * 32, nullifier)
address = fvk.to_ivk(Zcash::ZcashOrchardScope::INTERNAL).address(diversifier) ## this is a struct, not the encoded version yet
## note struct creation
note = Zcash::ZcashOrchardNote.from_parts(address, note_value, nullifier, rseed)
note_commitment = note.commitment().to_extracted_note_commitment()
## Configure the merkle path/auth path.
merkle_hash = Zcash::ZcashOrchardMerkleHash.from_bytes([0] * 32)
auth_path = [ merkle_hash ] * 32
merkle_path = Zcash::ZcashOrchardMerklePath.from_parts(0, auth_path)
block_height = 2030820
target_block_height = Zcash::ZcashBlockHeight.new(block_height)
expiry_block_height = Zcash::ZcashBlockHeight.new(block_height+100)
flags = Zcash::ZcashOrchardFlags.from_parts(true, true) ## spends enabled, outputs enabled.
anchor = merkle_path.root(note_commitment)
builder = Zcash::ZcashOrchardTransactionBuilder.new(params, target_block_height, expiry_block_height, anchor, flags)
builder.add_spend(fvk, note, merkle_path)
## Sending the note to themselves this time for the example.
builder.add_recipient(ovk, address, note_value, nil) ## No memo field so null.
orchard_sk = usk.orchard()
sig_hash = [0] * 32
transaction_struct = builder.build([orchard_sk], sig_hash)
Kotlin code
import uniffi.zcash.*
fun main() {
// Prepare all the needed keys
val params = ZcashConsensusParameters.MAIN_NETWORK
val usk = ZcashUnifiedSpendingKey.fromSeed(params, List(32) { 0u }, ZcashAccountId(0u))
val fvk = usk.toUnifiedFullViewingKey().orchard()!!
// External scope, as this an outgoing vieweing key, not subject to internal wallet operations.
val ovk = fvk.toOvk(ZcashOrchardScope.EXTERNAL)
val diversifier = ZcashOrchardDiversifier.fromBytes(List(11) { 0u })
val noteValue = ZcashOrchardNoteValue.fromRaw(15u)
val nullifier = ZcashOrchardNullifier.fromBytes(List(32) { 0u })
val rseed = ZcashOrchardRandomSeed.fromBytes(List(32) { 0u }, nullifier)
val address = fvk.toIvk(ZcashOrchardScope.INTERNAL).address(diversifier) // this is a struct, not the encoded version yet
// note struct creation
val note = ZcashOrchardNote.fromParts(address, noteValue, nullifier, rseed)
val noteCommitment = note.commitment().toExtractedNoteCommitment()
// Configure the merkle path/auth path.
val merkleHash = ZcashOrchardMerkleHash.fromBytes(List(32) { 0u })
val authPath = List(32) { merkleHash }
val merklePath = ZcashOrchardMerklePath.fromParts(0u, authPath)
val blockHeight = 2030820u
val targetBlockHeight = ZcashBlockHeight(blockHeight)
val expiryBlockHeight = ZcashBlockHeight(blockHeight+100u)
val flags = ZcashOrchardFlags.fromParts(true, true) // spends enabled, outputs enabled.
val anchor = merklePath.root(noteCommitment)
val builder = ZcashOrchardTransactionBuilder(params, targetBlockHeight, expiryBlockHeight, anchor, flags)
builder.addSpend(fvk, note, merklePath)
// Sending the note to themselves this time for the example.
builder.addRecipient(ovk, address, noteValue, null) // No memo field so null.
val orchardSK = usk.orchard()
val sigHash: List<UByte> = List(32) { 0u }
val transactionStruct = builder.build(listOf(orchardSK), sigHash)
}
Swift code
import Zcash
// Prepare all the needed keys
let params = ZcashConsensusParameters.mainNetwork
let usk = try! ZcashUnifiedSpendingKey.fromSeed(params: params, seed: Array(repeating: 0, count: 32), accountId: ZcashAccountId(id: 0))
let fvk = usk.toUnifiedFullViewingKey().orchard()!
// External scope, as this an outgoing vieweing key, not subject to internal wallet operations.
let ovk = fvk.toOvk(scope: ZcashOrchardScope.external)
let diversifier = try! ZcashOrchardDiversifier.fromBytes(bytes: Array(repeating: 0, count: 11))
let noteValue = ZcashOrchardNoteValue.fromRaw(value: 15)
let nullifier = try! ZcashOrchardNullifier.fromBytes(data: Array(repeating: 0, count: 32))
let rseed = try! ZcashOrchardRandomSeed.fromBytes(data: Array(repeating: 0, count: 32), rho: nullifier)
let address = fvk.toIvk(scope: ZcashOrchardScope.internal).address(diversifier: diversifier) // this is a struct, not the encoded version yet
// note struct creation
let note = try! ZcashOrchardNote.fromParts(recipient: address, value: noteValue, rho: nullifier, rseed: rseed)
let noteCommitment = note.commitment().toExtractedNoteCommitment()
// Configure the merkle path/auth path.
let merkleHash = try! ZcashOrchardMerkleHash.fromBytes(data: Array(repeating: 0, count: 32))
let authPath = Array(repeating: merkleHash, count: 32)
let merklePath = try! ZcashOrchardMerklePath.fromParts(position: 0, authPath: authPath)
let blockHeight = 2030820
let targetBlockHeight = ZcashBlockHeight(v: UInt32(blockHeight))
let expiryBlockHeight = ZcashBlockHeight(v: UInt32(blockHeight+100))
let flags = ZcashOrchardFlags.fromParts(spendsEnabled: true, outputsEnabled: true) // spends enabled, outputs enabled.
let anchor = merklePath.root(cmx: noteCommitment)
let builder = ZcashOrchardTransactionBuilder(parameters: params, targetHeight: targetBlockHeight, expiryHeight: expiryBlockHeight, anchor: anchor, flags: flags)
try! builder.addSpend(fvk: fvk, note: note, merklePath: merklePath)
// Sending the note to themselves this time for the example.
try! builder.addRecipient(ovk: ovk, recipient: address, value: noteValue, memo: nil) // No memo field so null.
let orchardSK = usk.orchard()
let sigHash: [UInt8] = Array(repeating: 0, count: 32)
let transactionStruct = try! builder.build(keys: [orchardSK], sighash: sigHash)
-
To build a transaction, you need control over a wallet, i.e. you need to have "signature power", which means that you need access to a spending key.
-
The core part of a transaction in Bitcoin is the UTXO - in Zcash, it's a
note
. To build anote
, according to the protocol, you need four parts:-
value
: The value inZEC
you want to transfer. -
nullifier
: Represents the commitment of a note, preventing double spending. -
rseed
: The ZIP 212 seed randomness for a note. -
address
: The address from which the note is sent.
-
-
We need to get its commitment for inclusion in the merkle tree of the ledger.
-
Merkle path rebuild with this commitment included, i.e. anchor creation.
-
Finally build the transaction.