Skip to content

Manuals

MeerKatDev edited this page Jun 12, 2023 · 10 revisions

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.

Pre-requisites

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.

Important Kotlin notes ⚠️

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.

Key derivation

image

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.

Sapling

We start from a SK.

  1. Generate an Expanded Spending Key - This is derived directly from the SK.
  2. Generate a Proof Authorizing Key - This is used to build zero knowledge proofs for shielded transactions.
  3. Generate the FVK - Needed to view incoming or outgoing transactions.
  4. Generate IVK - Needed to view incoming transactions only (not outgoing).
  5. 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

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.

  1. All starts for generating an Orchard spending key from a seed.

  2. Then we can derive to the FVK , from which we can derive the other 2 keys:

    1. The IVK that will allow to see incoming transactions.
    2. The OVK that will allow to see outgoing transactions.
  3. Once we have this keys, we can properly generate addresses

    1. With a diversifier.
    2. 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)

Unified key sets

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
Loading
  1. Generate an USK from a seed.
  2. From the USK , we can generate SK for each other pool: Transparent , Sapling and Orchard.
  3. We can derive an USK to an UFVK, which brings access to the respective FVK of each pool, Transparent , Sapling and Orchard.
  4. FVK's can be built up from the base keys of each specific pools.
  5. A UFVK can be composed by the FVK of each pool, Transparent , Sapling and Orchard.
  6. Then we can generate unified addresses from the UFVK .
  7. Finally from an unified addresses we can generate the specific addresses for each pool, Transparent , Sapling and Orchard.

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()

Building transactions

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)
  1. 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.

  2. The core part of a transaction in Bitcoin is the UTXO - in Zcash, it's a note. To build a note, according to the protocol, you need four parts:

    • value : The value in ZEC 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.

  3. We need to get its commitment for inclusion in the merkle tree of the ledger.

  4. Merkle path rebuild with this commitment included, i.e. anchor creation.

  5. Finally build the transaction.

Clone this wiki locally