My librustzcash "Hello World" project

Yep, that was a steeper part of the learning curve.

I’m using Perl with FFI::Platypus so can pass a pointer & tag it as ‘opaque’ - this lets rust modify contents without upsetting perl. I have that working to get ‘ak’ & ‘nk’ from an extended viewkey with librustzcash.

I’ve always been fond of Perl - even though its old & was only cool when building websites in the 90’s.

Well, String also have to be properly allocated/freed. Rust FFI has some helper classes for them though (CStr, CString)

Is this what you are looking for? I omitted the FFI part since you said you know how to do it.

Run it with cargo test

lib.rs

use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_client_backend::proto::compact_formats::CompactOutput;
use zcash_primitives::consensus::BlockHeight;
use zcash_primitives::consensus::Network::MainNetwork;
use zcash_primitives::constants::mainnet::{
    HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_PAYMENT_ADDRESS,
};
use zcash_primitives::memo::MemoBytes;
use zcash_primitives::note_encryption::try_sapling_note_decryption;
use zcash_primitives::sapling::{Note, PaymentAddress, SaplingIvk};

pub fn try_decrypt_note(
    height: u32,
    fvk: &str,
    mut cmu: Vec<u8>,
    mut epk: Vec<u8>,
    ciphertext: Vec<u8>,
) -> Option<(Note, PaymentAddress, MemoBytes)> {
    cmu.reverse(); // bytes are reversed in decode raw tx
    epk.reverse();

    let fvk = decode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, fvk)
        .unwrap()
        .unwrap(); // two unwraps because the result is Result<Option<T>>
    let ivk = fvk.fvk.vk.ivk();
    let output = CompactOutput {
        // Use this class to help us with decoding cmu, epk
        cmu,
        epk,
        ciphertext: vec![], // the content does not matter
        unknown_fields: Default::default(),
        cached_size: Default::default(),
    };

    let cmu = output.cmu().unwrap();
    let epk = output.epk().unwrap();

    try_sapling_note_decryption(
        &MainNetwork,
        BlockHeight::from_u32(height),
        &ivk,
        &epk,
        &cmu,
        &ciphertext,
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use zcash_client_backend::encoding::encode_payment_address;

    #[test]
    fn test_vector1() -> anyhow::Result<()> {
        let height = 1202824;
        let FVK = "zxviews1q0kl7tavzyqqpq8efe0vpgzwc37zj0zr9j2quurncpsy74tdvh9c3racve9yfv6gkssvekw4sz6ueenvup6whupguzkg5rgp0kma37r4uxz9472w4zwra4jv6fm5dc2cevfpjsxdgndagslmgdwudhv4stklzfeszrlcnsqxyr2qt8tsf4yxs3he4rzllcly7xagfmnlycvvnvhhr9l9j6ad693rkueqys9f7mkc7aacxwp3tfc9hpvlckxnj4nwu6jef2x98jefhcgmpkrmn";
        let CMU = "337fcf043e1f7e4ddb0d37d533e1995ddef860bf218484624143c5d75f70a5e0";
        let EPK = "a29816cd86810601c9a9eb61c1e2f146816848b1513b9b7c4ff8b9e8f4695b43";
        let CIPHER = "18ef22d92037b418241aa3ab234785431766cc98fc92c97ced405883d5f7287fe030cfc4e1c000d56a5b8a56e9fe3191b32e5bf8f660bd7708868a3fa39ca4133dfa8beace75fe1fe9e85f2a14c7654778e864e80e08a5e37b2d4bfd74ded6e01ced184cf42abae1d50b297d6658d2eab1f73256dcfb0037ce599a88a811fffa4ec300ef993868a906468fc0ea984753be5ab2642be0e829ca9e883a608717471b531aba487ea08e3376b0927b931bbae207640258e76c08ea1a613fa6a4a068951ec86b8c085f002ac62b446935377fbae8006b8c3477f9de28eefa34193c3591df6ec30ce800f228700af4ba57c533abdb9a8530631f8414c1548b8cc28eaace5270c4f2716f2014641b2cc7aa0e19b4eb0e7e197d077fc8f61027a33c804601a4feb2e093374e1983e4370219e1236d3fedbfd8b7a54a4f6f22742f7b064883c35de02543b258eb716bef757b5551a8fce938f8640e922161a00c38c39de49f21e9d4561aa234010a0e98557bd8596ebd7c029f088814fbd4300e4b36c183905efa040a85446282752dd30edb39c44c4dfb06f6680daf6d5cfbd5ddb938ebdd9e57d7b8ca22e0402b10fb28a727c8a9dd0b50576a593846d38efe104ee4d51d97e94f33bc880913150bcd66d2159757aaf80b96752481aef12127d41aba1e614f0764286a3930a5dc8c48cb6ce7c99951f0efe35e199400f6230e60c6cca088e9dd4eb5139978bf6ba7df2022132fd08def2c466313e9c987e834b97acda2d820cc00aeee50d078d7433dc4f1c9e7590e7a920923a35366ef93657ae6ff06f01e526f";

        // this one does not decrypt
        assert!(try_decrypt_note(
            height,
            FVK,
            hex::decode(CMU)?,
            hex::decode(EPK)?,
            hex::decode(CIPHER)?,
        )
        .is_none());

        Ok(())
    }

    #[test]
    fn test_vector2() -> anyhow::Result<()> {
        let height = 1202824;
        let FVK = "zxviews1q0kl7tavzyqqpq8efe0vpgzwc37zj0zr9j2quurncpsy74tdvh9c3racve9yfv6gkssvekw4sz6ueenvup6whupguzkg5rgp0kma37r4uxz9472w4zwra4jv6fm5dc2cevfpjsxdgndagslmgdwudhv4stklzfeszrlcnsqxyr2qt8tsf4yxs3he4rzllcly7xagfmnlycvvnvhhr9l9j6ad693rkueqys9f7mkc7aacxwp3tfc9hpvlckxnj4nwu6jef2x98jefhcgmpkrmn";
        let CMU = "263a4c43290ce7d644c0a3ab694bb4710a4c3b20a528e2297ac1d360b017f704";
        let EPK = "d8360fc851709bb8d53e1f7ad2bab2c28c70d2c3c570af6620599f078ab37e02";
        let CIPHER = "c9c2479a4c936b25c4848a15fc5debad377f0305f7e744cfb550bc09da12922669b6a4d82d2c8d56d9c804682bae459474467aad9417739f3eea7f6526c344b789c493c53186909b128f29dfe571cd3f9dc9d5cbaff371e5cd20a813bda9e3b2465522a3665f1bc33af61011438173dde65777627505bc79a4aea00d1437631a73538fd35faadeb44a5e781791a2008a6b079895b7f8c7f8dfe6d7b1ecdb1ca5b44980841b500f582d991ba8f68479d9927bc9e04c2cf364decc8dc5d6bc0eca67e6ea7e8fe96788944210bf6c537852655badfa64c362aa0baa2765d47623a5542a93f62d06721b05fa3129077d8f13c95304d720cc8c7241f804593c51767c9df043204c75a8e7eefe12aedb1af5cb7a907831b3e99e09649dcebfbc8b3b82726cdf67aa1e0f578f384bed3a8d037c44664589326e640fad6dd5376f26152c993fcec92e6b13f596e8133b3077da5048303cbce41f1c66f4f78f97d280b40274b8770dbe1206d62ba2df99a0e7138be3488b66d5c2b8ecfbc46c72eb052d0854a7aa4f8d92b83fa080b24c031f0aa9c889fc78ee7787ff76569bf42b1190c9c98afa33aa1e6070bcd0470da01ead7f4db1687950e483bd62d5ec848a90fade6241944b2bf459a1b85406c137d6ffa6dd6ae27afc9df1a4b762e23ad21a8971102383ffea77ad4c68d168fcc7b2d55b06d6dd94ebe4f90f22010a8f26923b66d95d77273e84026f108c8a4420959437b3166e0c1011c584870719eafdb2476d98322987e06940c471fd591a4ca954a873b7cb9fa4e41925de0cff927172d98cbd87e5f7";

        let (note, address, memo) = try_decrypt_note(
            height,
            FVK,
            hex::decode(CMU)?,
            hex::decode(EPK)?,
            hex::decode(CIPHER)?,
        )
        .unwrap();

        assert_eq!(note.value, 100000);
        assert_eq!(
            encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &address),
            "zs1a7qnkg8hr74ujj08jhjcdfs7s62yathqlyn5vd2e8ww96ln28m3t2jkxun5fp7hxjntcg8ccuvs"
        );
        assert_eq!(
            std::str::from_utf8(memo.as_array())?
                .to_string()
                .trim_end_matches(char::from(0)),
            "Hello world!!!"
        );

        Ok(())
    }
}

Cargo.toml

[package]
name = "zcash_test"
version = "0.1.0"
edition = "2018"

[dependencies]
hex = "0.4.3"
anyhow = "1.0.40"

[dependencies.zcash_primitives]
git = "https://github.com/zcash/librustzcash.git"
branch = "master"

[dependencies.zcash_client_backend]
git = "https://github.com/zcash/librustzcash.git"
branch = "master"
2 Likes

oh yes…and you have to be so careful to use exactly the allocated memory in exactly the way its supposed to be use…its all very unforgiving. I call that part of my learning process ‘Segfault Sunday’.

1 Like

To avoid issues like this, wrap incoming strings into CStr like
let database_path = unsafe { CStr::from_ptr(your_c_string) };

outgoing strings are tricker
CString::new(outgoing_rust_string).unwrap().into_raw()

because technically you are leaking the string. If you want to avoid that, you have to expose a “free this string” function because you are not allowed to free from C (they have different allocators).

There are ways to return structs too. After you incorporate cbindgen to your build system, it gets quite neat. You can actually have type safety with complex data structures.

Great to know ! Hey! Thanks for the help with this, I’ve been trying to figure it out for ages.

No problem, I actually don’t see any problem with your code. Whatever it is, it must be a small bug.

A little bit of advice, try to keep the unsafe to a minimum. You can have extern “C” functions that simply convert from C to rust data types and then call the real function. This way, rust can help you with memory management.

1 Like

looks down, shuffles feet & says in a small voice… “yeah, the problem is me”

EDIT: Perfect !! It works !! Thanks again :grinning:

1 Like


(So using a syllable stressing form of phoenetics I’m starting to pick up Morse and that diagram above is this, an audio tone generator with an amplifier and lines in and out. The phoenetics is just silly little phrases that associate a letter with a syllable pattern that associates to dots and dashes such as CO-ca-Co-la for ‘C’ or -·-· I thought there would be no way and I’m still really slow but am getting better!)

1 Like

I see Lego :slight_smile:

1 Like

Lol yeah the diameter of the leds was about identical to the lego circle nubs and at that point I figured why not use them for mounting the switches too