Compare commits

...

2 commits

Author SHA1 Message Date
cfa862ec94
Viewing Keys (#104)
This PR contains the code to generate Unified Full Viewing Keys and Unified Incoming Viewing Keys.

Reviewed-on: #104
Co-authored-by: Rene Vergara <rene@vergara.network>
Co-committed-by: Rene Vergara <rene@vergara.network>
2025-01-02 18:36:21 +00:00
7d3ae36d2b
Rust dependencies update (#103)
This PR updates the Rust dependencies and updates the code for the latest version of `zcash_primitives`.

Reviewed-on: #103
Co-authored-by: Rene Vergara <rene@vergara.network>
Co-committed-by: Rene Vergara <rene@vergara.network>
2024-12-19 15:23:12 +00:00
12 changed files with 891 additions and 495 deletions

View file

@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.7.8.0]
## Added
- New `UnifiedIncomingViewingKey` type
- Functions to derive Orchard full viewing key
- Functions to derive Sapling full viewing key
- Functions to derive transparent "full viewing key"
- Functions to encode Unified Full Viewing Keys
- Functions to encode Unified Incoming Viewing Keys
## [0.7.7.0]
### Changed
- Updated Rust crates
## [0.7.6.0] ## [0.7.6.0]
### Changed ### Changed

File diff suppressed because it is too large Load diff

View file

@ -9,13 +9,13 @@ haskell-ffi.git = "https://github.com/BeFunctional/haskell-rust-ffi.git"
haskell-ffi.rev = "2bf292e2e56eac8e9fb0fb2e1450cf4a4bd01274" haskell-ffi.rev = "2bf292e2e56eac8e9fb0fb2e1450cf4a4bd01274"
f4jumble = "0.1" f4jumble = "0.1"
zcash_address = "0.2.0" zcash_address = "0.2.0"
borsh = "0.10" borsh = "0.9"
bech32 = "0.11" bech32 = "0.11"
orchard = "0.10.0" orchard = "0.10.0"
zcash_note_encryption = "0.4.0" zcash_note_encryption = "0.4.0"
zcash_primitives = { version = "0.19.0", features = ["transparent-inputs"]} zcash_primitives = { version = "0.21.0", features = ["transparent-inputs"]}
zcash_client_backend = "0.14.0" zcash_client_backend = "0.16.0"
sapling-crypto = "0.3" sapling-crypto = "0.4"
zip32 = "0.1.2" zip32 = "0.1.2"
proc-macro2 = "1.0.66" proc-macro2 = "1.0.66"
nonempty = "0.7.0" nonempty = "0.7.0"
@ -25,6 +25,7 @@ jubjub = "0.10.0"
rand_core = { version = "0.6.4", features = ["getrandom"]} rand_core = { version = "0.6.4", features = ["getrandom"]}
wagyu-zcash-parameters = "0.2.0" wagyu-zcash-parameters = "0.2.0"
bip0039 = "0.12.0" bip0039 = "0.12.0"
ahash = "0.7.8"
[features] [features]

View file

@ -1,4 +1,4 @@
[toolchain] [toolchain]
channel = "nightly-2024-02-04" channel = "nightly"
components = [ "rustfmt", "rustc-dev"] components = [ "rustfmt", "rustc-dev"]
profile = "minimal" profile = "minimal"

View file

@ -116,6 +116,7 @@ use zcash_primitives::{
}, },
transparent::{ transparent::{
Bundle as TransparentBundle, Bundle as TransparentBundle,
builder::TransparentSigningSet,
TxIn, TxIn,
TxOut, TxOut,
OutPoint, OutPoint,
@ -134,7 +135,7 @@ use zcash_primitives::{
use zcash_address::{ use zcash_address::{
Network, Network,
unified::{Address, Encoding, Ufvk, Container, Fvk, Receiver}, unified::{Address, Encoding, Ufvk, Uivk, Ivk, Container, Fvk, Receiver},
ZcashAddress ZcashAddress
}; };
@ -156,7 +157,7 @@ use orchard::{
Flags Flags
}, },
Action, Action,
keys::{SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope}, keys::{SpendAuthorizingKey, SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope},
note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment},
note_encryption::OrchardDomain, note_encryption::OrchardDomain,
primitives::redpallas::{VerificationKey, SpendAuth, Signature}, primitives::redpallas::{VerificationKey, SpendAuth, Signature},
@ -689,6 +690,35 @@ impl Hufvk {
} }
} }
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct Huivk {
net: u8,
orchard: Vec<u8>,
sapling: Vec<u8>,
transparent: Vec<u8>
}
impl<RW> ToHaskell<RW> for Huivk {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
impl Huivk {
fn add_key_section(&mut self, ivk: &Ivk) {
if let Ivk::Orchard(v) = ivk {
self.orchard = v.to_vec();
}
if let Ivk::Sapling(w) = ivk {
self.sapling = w.to_vec();
}
if let Ivk::P2pkh(x) = ivk {
self.transparent = x.to_vec();
}
}
}
#[derive(Debug, BorshSerialize, BorshDeserialize)] #[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct Hsvk { pub struct Hsvk {
vk: Vec<u8>, vk: Vec<u8>,
@ -967,6 +997,34 @@ pub extern "C" fn rust_wrapper_ufvk_decode(
} }
} }
#[no_mangle]
pub extern "C" fn rust_wrapper_uivk_decode(
input: *const u8,
input_len: usize,
out: *mut u8,
out_len: &mut usize
) {
let input: String = marshall_from_haskell_var(input, input_len, RW);
let dec_key = Uivk::decode(&input);
match dec_key {
Ok((n, uivk)) => {
let x = match n {
Network::Main => 1,
Network::Test => 2,
Network::Regtest => 3
};
let mut hk = Huivk { net: x, orchard: vec![0], sapling: vec![0], transparent: vec![0] };
let ivks = uivk.items();
ivks.iter().for_each(|k| hk.add_key_section(k));
marshall_to_haskell_var(&hk, out, out_len, RW);
}
Err(_e) => {
let hk0 = Hufvk { net: 0, orchard: vec![0], sapling: vec![0], transparent: vec![0] };
marshall_to_haskell_var(&hk0, out, out_len, RW);
}
}
}
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_sapling_esk_decrypt( pub extern "C" fn rust_wrapper_sapling_esk_decrypt(
key: *const u8, key: *const u8,
@ -2173,12 +2231,13 @@ pub extern "C" fn rust_wrapper_create_transaction(
let mut main_builder = Builder::new(MainNetwork, BlockHeight::from(bl_height), build_config); let mut main_builder = Builder::new(MainNetwork, BlockHeight::from(bl_height), build_config);
let mut test_builder = Builder::new(TestNetwork, BlockHeight::from(bl_height), build_config); let mut test_builder = Builder::new(TestNetwork, BlockHeight::from(bl_height), build_config);
let trans_input: Vec<HtransparentInput> = marshall_from_haskell_var(t_input, t_input_len, RW); let trans_input: Vec<HtransparentInput> = marshall_from_haskell_var(t_input, t_input_len, RW);
let mut tss = TransparentSigningSet::new();
for t_in in trans_input { for t_in in trans_input {
if t_in.sk.len() > 1 { if t_in.sk.len() > 1 {
//println!("t inp: {:?}", t_in); //println!("t inp: {:?}", t_in);
let k = SecretKey::from_slice(&t_in.sk).unwrap(); let k = SecretKey::from_slice(&t_in.sk).unwrap();
if net { if net {
match main_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) { match main_builder.add_transparent_input(tss.add_key(k), t_in.utxo.unpack(), t_in.coin.unpack()) {
Ok(()) => { Ok(()) => {
//println!("added t-input in main"); //println!("added t-input in main");
continue; continue;
@ -2186,7 +2245,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
Err(_e) => { println!("Error reading transparent input"); } Err(_e) => { println!("Error reading transparent input"); }
} }
} else { } else {
match test_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) { match test_builder.add_transparent_input(tss.add_key(k), t_in.utxo.unpack(), t_in.coin.unpack()) {
Ok(()) => { Ok(()) => {
//println!("added t-input in test"); //println!("added t-input in test");
continue; continue;
@ -2196,12 +2255,14 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
} }
} }
let mut sap_key_array = vec![];
for s_in in sap_input { for s_in in sap_input {
if s_in.sk.len() > 1 { if s_in.sk.len() > 1 {
//println!("s inp: {:?}", s_in); //println!("s inp: {:?}", s_in);
let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk); let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk);
match sp_key { match sp_key {
Ok(sk) => { Ok(sk) => {
sap_key_array.push(sk.clone());
let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap(); let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap();
let rseed = if s_in.note.rseed.kind == 1 { let rseed = if s_in.note.rseed.kind == 1 {
Rseed::BeforeZip212(Fr::from_bytes(&to_array(s_in.note.rseed.bytes)).unwrap()) Rseed::BeforeZip212(Fr::from_bytes(&to_array(s_in.note.rseed.bytes)).unwrap())
@ -2216,8 +2277,9 @@ pub extern "C" fn rust_wrapper_create_transaction(
Node::empty_leaf() Node::empty_leaf()
}).collect(), Position::from(u64::from(s_in.iw.position))); }).collect(), Position::from(u64::from(s_in.iw.position)));
let merkle_path = mk_path.unwrap(); let merkle_path = mk_path.unwrap();
let fvk = sk.to_diversifiable_full_viewing_key().fvk().clone();
if net { if net {
let mb = main_builder.add_sapling_spend::<String>(&sk, note, merkle_path); let mb = main_builder.add_sapling_spend::<String>(fvk, note, merkle_path);
match mb { match mb {
Ok(()) => { Ok(()) => {
continue; continue;
@ -2228,7 +2290,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
} }
} else { } else {
let tb = test_builder.add_sapling_spend::<String>(&sk, note, merkle_path); let tb = test_builder.add_sapling_spend::<String>(fvk, note, merkle_path);
match tb { match tb {
Ok(()) => { Ok(()) => {
continue; continue;
@ -2247,9 +2309,11 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
} }
} }
let mut orch_keys = vec![];
for o_in in orch_input { for o_in in orch_input {
if o_in.sk.len() > 1 { if o_in.sk.len() > 1 {
let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap(); let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap();
orch_keys.push(SpendAuthorizingKey::from(&sp_key));
let pay_addr = OrchardAddress::from_raw_address_bytes(&to_array(o_in.note.recipient)).unwrap(); let pay_addr = OrchardAddress::from_raw_address_bytes(&to_array(o_in.note.recipient)).unwrap();
let rho = Rho::from_bytes(&to_array(o_in.note.rho)).unwrap(); let rho = Rho::from_bytes(&to_array(o_in.note.rho)).unwrap();
let rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap(); let rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap();
@ -2264,7 +2328,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
).collect())); ).collect()));
if net { if net {
let mb = main_builder.add_orchard_spend::<String>(&sp_key, note, merkle_path); let mb = main_builder.add_orchard_spend::<String>(FullViewingKey::from(&sp_key), note, merkle_path);
match mb { match mb {
Ok(()) => { Ok(()) => {
//println!("added orchard inp: {:?}", val); //println!("added orchard inp: {:?}", val);
@ -2277,7 +2341,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
} }
} else { } else {
let tb = test_builder.add_orchard_spend::<String>(&sp_key, note, merkle_path); let tb = test_builder.add_orchard_spend::<String>(FullViewingKey::from(&sp_key), note, merkle_path);
match tb { match tb {
Ok(()) => { Ok(()) => {
//println!("added orchard inp: {:?}", val); //println!("added orchard inp: {:?}", val);
@ -2421,9 +2485,9 @@ pub extern "C" fn rust_wrapper_create_transaction(
let output_params_reader = Cursor::new(output_params_in); let output_params_reader = Cursor::new(output_params_in);
let output_prover = OutputParameters::read(output_params_reader, false).unwrap(); let output_prover = OutputParameters::read(output_params_reader, false).unwrap();
let result = if net { let result = if net {
main_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard()) main_builder.build(&tss, &sap_key_array, &orch_keys, OsRng, &spend_prover, &output_prover, &FeeRule::standard())
} else { } else {
test_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard()) test_builder.build(&tss, &sap_key_array, &orch_keys,OsRng, &spend_prover, &output_prover, &FeeRule::standard())
}; };
match result { match result {
Ok(r) => { Ok(r) => {
@ -2501,3 +2565,87 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
} }
} }
#[no_mangle]
pub extern "C" fn rust_wrapper_create_orchard_fvk(
orch_in: *const u8,
orch_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(orch_in, orch_in_len, RW);
let sk = SpendingKey::from_bytes(to_array(input));
if sk.is_some().into() {
let fvk = FullViewingKey::from(&sk.unwrap());
let x = Hhex {bytes: fvk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
} else {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_create_orchard_ivk(
orch_in: *const u8,
orch_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(orch_in, orch_in_len, RW);
let sk = SpendingKey::from_bytes(to_array(input));
if sk.is_some().into() {
let fvk = FullViewingKey::from(&sk.unwrap()).to_ivk(Scope::External);
let x = Hhex {bytes: fvk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
} else {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_create_sapling_fvk(
sap_in: *const u8,
sap_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(sap_in, sap_in_len, RW);
let in_bytes: [u8; 169] = to_array(input);
let sk = ExtendedSpendingKey::from_bytes(&in_bytes);
match sk {
Ok(k) => {
let fvk = k.to_diversifiable_full_viewing_key();
let x = Hhex {bytes: fvk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
},
Err(_e) => {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_create_sapling_ivk(
sap_in: *const u8,
sap_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(sap_in, sap_in_len, RW);
let in_bytes: [u8; 169] = to_array(input);
let sk = ExtendedSpendingKey::from_bytes(&in_bytes);
match sk {
Ok(k) => {
let ivk = k.to_diversifiable_full_viewing_key().to_external_ivk();
let x = Hhex {bytes: ivk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
},
Err(_e) => {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
}

View file

@ -120,6 +120,13 @@ import ZcashHaskell.Types
-> `()' -> `()'
#} #}
{# fun unsafe rust_wrapper_uivk_decode as rustWrapperUivkDecode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer UnifiedIncomingViewingKey'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_orchard_note_decrypt as rustWrapperOrchardNoteDecode {# fun unsafe rust_wrapper_orchard_note_decrypt as rustWrapperOrchardNoteDecode
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, toBorshVar* `OrchardAction'& , toBorshVar* `OrchardAction'&
@ -410,3 +417,31 @@ import ZcashHaskell.Types
} }
-> `()' -> `()'
#} #}
{# fun unsafe rust_wrapper_create_orchard_fvk as rustWrapperCreateOrchardFvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_create_orchard_ivk as rustWrapperCreateOrchardIvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_create_sapling_fvk as rustWrapperCreateSaplingFvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_create_sapling_ivk as rustWrapperCreateSaplingIvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}

View file

@ -15,13 +15,42 @@
module ZcashHaskell.Keys where module ZcashHaskell.Keys where
import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed) import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed)
import Crypto.Secp256k1 (createContext)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.HexString (hexBytes)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import Data.Word (Word8(..))
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
( withBorshVarBuffer ( withBorshVarBuffer
, withPureBorshVarBuffer , withPureBorshVarBuffer
) )
import ZcashHaskell.Types (Phrase, Seed(..), ToBytes(..)) import Haskoin.Address.Base58 (decodeBase58)
import Haskoin.Crypto.Keys.Extended
( DerivPath(..)
, DerivPathI(..)
, XPubKey(..)
, derivePath
, deriveXPubKey
, xPubExport
)
import Haskoin.Network.Constants (btc)
import ZcashHaskell.Orchard (deriveOrchardFvk, deriveOrchardIvk)
import ZcashHaskell.Sapling (deriveSaplingFvk, deriveSaplingIvk)
import ZcashHaskell.Types
( OrchardSpendingKey(..)
, Phrase
, SaplingSpendingKey(..)
, Seed(..)
, ToBytes(..)
, TransparentSpendingKey(..)
, ZcashNet(..)
, uniFullViewingKeyHrp
, uniIncomingViewingKeyHrp
, uniTestFullViewingKeyHrp
, uniTestIncomingViewingKeyHrp
)
import ZcashHaskell.Utils (encodeBech32m, f4Jumble)
-- | Generate a random seed that can be used to generate private keys for shielded addresses and transparent addresses. -- | Generate a random seed that can be used to generate private keys for shielded addresses and transparent addresses.
generateWalletSeedPhrase :: IO Phrase generateWalletSeedPhrase :: IO Phrase
@ -36,3 +65,97 @@ getWalletSeed p =
where where
result :: Seed result :: Seed
result = (withPureBorshVarBuffer . rustWrapperGetSeed) p result = (withPureBorshVarBuffer . rustWrapperGetSeed) p
-- | Derive a transparent root node for unified viewing keys
deriveFullTransparentNode :: TransparentSpendingKey -> IO BS.ByteString
deriveFullTransparentNode sk = do
ioCtx <- createContext
let tPubKey = deriveXPubKey ioCtx sk
let tPubKeyBytes = decodeBase58 $ xPubExport btc ioCtx tPubKey
case tPubKeyBytes of
Nothing -> fail "Unable to get transparent key bytes"
Just pb -> return $ BS.takeEnd 65 pb
-- | Derive a transparent incoming root node for unified incoming viewing keys
deriveIncomingTransparentNode :: TransparentSpendingKey -> IO BS.ByteString
deriveIncomingTransparentNode sk = do
ioCtx <- createContext
let path = Deriv :/ 0 :: DerivPath
let childPrvKey = derivePath ioCtx path sk
let tPubKey = deriveXPubKey ioCtx childPrvKey
let tPubKeyBytes = decodeBase58 $ xPubExport btc ioCtx tPubKey
case tPubKeyBytes of
Nothing -> fail "Unable to get transparent key bytes"
Just pb -> return $ BS.takeEnd 65 pb
-- | Derive a Unified Full Viewing Key
deriveUfvk ::
ZcashNet
-> OrchardSpendingKey
-> SaplingSpendingKey
-> TransparentSpendingKey
-> IO T.Text
deriveUfvk net okey skey tkey = do
tSec <- deriveFullTransparentNode tkey
let oSec = deriveOrchardFvk okey
let sSec = deriveSaplingFvk skey
case oSec of
Nothing -> fail "Unable to derive Orchard viewing key"
Just oSec' -> do
case sSec of
Nothing -> fail "Unable to derive Sapling viewing key"
Just sSec' ->
return $ encodeVK (hexBytes oSec') (hexBytes sSec') tSec net True
-- | Derive a Unified Incoming Viewing Key
deriveUivk ::
ZcashNet
-> OrchardSpendingKey
-> SaplingSpendingKey
-> TransparentSpendingKey
-> IO T.Text
deriveUivk net okey skey tkey = do
tSec <- deriveIncomingTransparentNode tkey
let oSec = deriveOrchardIvk okey
let sSec = deriveSaplingIvk skey
case oSec of
Nothing -> fail "Unable to derive Orchard viewing key"
Just oSec' -> do
case sSec of
Nothing -> fail "Unable to derive Sapling viewing key"
Just sSec' ->
return $ encodeVK (hexBytes oSec') (hexBytes sSec') tSec net False
-- | Encode a Unified Viewing Key per [ZIP-316](https://zips.z.cash/zip-0316)
encodeVK ::
BS.ByteString -- ^ Orchard FVK
-> BS.ByteString -- ^ Sapling FVK
-> BS.ByteString -- ^ Transparent root node
-> ZcashNet -- ^ Network
-> Bool -- ^ Full?
-> T.Text
encodeVK ovk svk tvk net full = encodeBech32m (E.encodeUtf8 hr) b
where
tReceiver = packReceiver 0x00 $ Just tvk
b = f4Jumble $ tReceiver <> sReceiver <> oReceiver <> padding
hr =
if full
then case net of
MainNet -> uniFullViewingKeyHrp
TestNet -> uniTestFullViewingKeyHrp
else case net of
MainNet -> uniIncomingViewingKeyHrp
TestNet -> uniTestIncomingViewingKeyHrp
sReceiver = packReceiver 0x02 $ Just svk
oReceiver = packReceiver 0x03 $ Just ovk
padding = E.encodeUtf8 $ T.justifyLeft 16 '\NUL' hr
packReceiver :: Word8 -> Maybe BS.ByteString -> BS.ByteString
packReceiver typeCode receiver' =
case receiver' of
Just receiver ->
if BS.length receiver > 1
then BS.singleton typeCode `BS.append`
(BS.singleton . toEnum . BS.length) receiver `BS.append`
receiver
else BS.empty
Nothing -> BS.empty

View file

@ -19,6 +19,8 @@ module ZcashHaskell.Orchard where
import C.Zcash import C.Zcash
( rustWrapperCombineOrchardNodes ( rustWrapperCombineOrchardNodes
, rustWrapperCreateOrchardFvk
, rustWrapperCreateOrchardIvk
, rustWrapperGenOrchardReceiver , rustWrapperGenOrchardReceiver
, rustWrapperGenOrchardSpendKey , rustWrapperGenOrchardSpendKey
, rustWrapperGetOrchardRootTest , rustWrapperGetOrchardRootTest
@ -37,6 +39,7 @@ import C.Zcash
, rustWrapperReadOrchardWitnessAnchor , rustWrapperReadOrchardWitnessAnchor
, rustWrapperUADecode , rustWrapperUADecode
, rustWrapperUfvkDecode , rustWrapperUfvkDecode
, rustWrapperUivkDecode
, rustWrapperUpdateOrchardWitness , rustWrapperUpdateOrchardWitness
) )
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
@ -157,6 +160,15 @@ decodeUfvk str =
where where
decodedKey = (withPureBorshVarBuffer . rustWrapperUfvkDecode) str decodedKey = (withPureBorshVarBuffer . rustWrapperUfvkDecode) str
-- | Attempts to decode the given bytestring into a Unified Full Viewing Key
decodeUivk :: BS.ByteString -> Maybe UnifiedIncomingViewingKey
decodeUivk str =
case i_net decodedKey of
0 -> Nothing
_ -> Just decodedKey
where
decodedKey = (withPureBorshVarBuffer . rustWrapperUivkDecode) str
-- | Check if the given UVK matches the UA given -- | Check if the given UVK matches the UA given
matchOrchardAddress :: BS.ByteString -> BS.ByteString -> Bool matchOrchardAddress :: BS.ByteString -> BS.ByteString -> Bool
matchOrchardAddress = rustWrapperOrchardCheck matchOrchardAddress = rustWrapperOrchardCheck
@ -337,3 +349,25 @@ compareAddress a u =
Sapling s -> s_rec u == Just (sa_receiver s) && ua_net u == net_type s Sapling s -> s_rec u == Just (sa_receiver s) && ua_net u == net_type s
Transparent t -> t_rec u == Just (ta_receiver t) && ua_net u == ta_network t Transparent t -> t_rec u == Just (ta_receiver t) && ua_net u == ta_network t
Exchange x -> False Exchange x -> False
-- | Derive an Orchard Full Viewing Key
deriveOrchardFvk ::
OrchardSpendingKey -- ^ The Orchard spending key
-> Maybe HexString
deriveOrchardFvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateOrchardFvk $ getBytes sk
-- | Derive an Orchard Incoming Viewing Key
deriveOrchardIvk ::
OrchardSpendingKey -- ^ The Orchard spending key
-> Maybe HexString
deriveOrchardIvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateOrchardIvk $ getBytes sk

View file

@ -19,6 +19,8 @@ module ZcashHaskell.Sapling where
import C.Zcash import C.Zcash
( rustWrapperCombineSaplingNodes ( rustWrapperCombineSaplingNodes
, rustWrapperCreateSaplingFvk
, rustWrapperCreateSaplingIvk
, rustWrapperDecodeSaplingAddress , rustWrapperDecodeSaplingAddress
, rustWrapperGetSaplingRootTest , rustWrapperGetSaplingRootTest
, rustWrapperIsShielded , rustWrapperIsShielded
@ -318,3 +320,25 @@ decodeSaplingAddress sapling_address = do
where where
sa = sa =
withPureBorshVarBuffer $ rustWrapperDecodeSaplingAddress sapling_address withPureBorshVarBuffer $ rustWrapperDecodeSaplingAddress sapling_address
-- | Derive a Sapling Full Viewing Key
deriveSaplingFvk ::
SaplingSpendingKey -- ^ The Sapling spending key
-> Maybe HexString
deriveSaplingFvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateSaplingFvk $ getBytes sk
-- | Derive a Sapling Incoming Viewing Key
deriveSaplingIvk ::
SaplingSpendingKey -- ^ The Sapling spending key
-> Maybe HexString
deriveSaplingIvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateSaplingIvk $ getBytes sk

View file

@ -696,6 +696,18 @@ data UnifiedFullViewingKey = UnifiedFullViewingKey
deriving anyclass (Data.Structured.Show) deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct UnifiedFullViewingKey deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct UnifiedFullViewingKey
-- | Type to represent a Unified Incoming Viewing Key
data UnifiedIncomingViewingKey = UnifiedIncomingViewingKey
{ i_net :: !Word8 -- ^ Number representing the network the key belongs to. @1@ for @mainnet@, @2@ for @testnet@ and @3@ for @regtestnet@.
, i_o_key :: !BS.ByteString -- ^ Raw bytes of the Orchard Incoming Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316)
, i_s_key :: !BS.ByteString -- ^ Raw bytes of the Sapling Incoming Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316)
, i_t_key :: !BS.ByteString -- ^ Raw bytes of the P2PKH chain code and public key as specified in [ZIP-316](https://zips.z.cash/zip-0316)
} deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct
UnifiedIncomingViewingKey
-- | Type to represent an Orchard Action as provided by the @getrawtransaction@ RPC method of @zcashd@, and defined in the [Zcash Protocol](https://zips.z.cash/protocol/protocol.pdf) -- | Type to represent an Orchard Action as provided by the @getrawtransaction@ RPC method of @zcashd@, and defined in the [Zcash Protocol](https://zips.z.cash/protocol/protocol.pdf)
data OrchardAction = OrchardAction data OrchardAction = OrchardAction
{ nf :: !HexString -- ^ The nullifier of the input note { nf :: !HexString -- ^ The nullifier of the input note

View file

@ -45,7 +45,12 @@ import Test.HUnit
import Test.Hspec import Test.Hspec
import Test.Hspec.QuickCheck import Test.Hspec.QuickCheck
import Test.QuickCheck import Test.QuickCheck
import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed) import ZcashHaskell.Keys
( deriveUfvk
, deriveUivk
, generateWalletSeedPhrase
, getWalletSeed
)
import ZcashHaskell.Orchard import ZcashHaskell.Orchard
import ZcashHaskell.Sapling import ZcashHaskell.Sapling
( decodeSaplingAddress ( decodeSaplingAddress
@ -1162,77 +1167,33 @@ main = do
Just addr -> do Just addr -> do
let eadr = decodeExchangeAddress (E.encodeUtf8 addr) let eadr = decodeExchangeAddress (E.encodeUtf8 addr)
eadr `shouldNotBe` Nothing eadr `shouldNotBe` Nothing
describe "Tree updates" $ do describe "Generate Viewing Keys" $ do
it "Orchard" $ do let p =
let tree = Phrase
OrchardCommitmentTree $ "cloth swing left trap random tornado have great onion element until make shy dad success art tuition canvas thunder apple decade elegant struggle invest"
hexString let seed = getWalletSeed p
"0136a7886d7d73bc1845223165fd9cb0cef02046c707e8f88a7f61564720bd0f3501dca1fbdd7b5ba92a0809af5e85874626ce0db14d0532a48e41dde6f0f81b46011f0001fb48c27bd07e68f27aba47cd6e93fa961e0ef8c63f993963a614e56855d2013c0001ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" let oK = genOrchardSpendingKey (fromJust seed) MainNetCoin 0
let cmx1 = let sK = genSaplingSpendingKey (fromJust seed) MainNetCoin 0
hexString it "Generate FVK" $ do
"1712ead46028d4349e234abf59e94e0640fe7a0829e2e2e17e1a931631810400" tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
let cmx2 = case oK of
hexString Nothing -> assertFailure "Failed to generate Orchard SK"
"39f5ad39817fb432fa07c5feb3a957189fbe7662a4b5555ca95093b6d853cf07" Just o ->
let cmx3 = case sK of
hexString Nothing -> assertFailure "Failed to generate Sapling SK"
"84f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f" Just s -> do
let cmx4 = fvk <- deriveUfvk MainNet o s tK
hexString decodeUfvk (E.encodeUtf8 fvk) `shouldNotBe` Nothing
"e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d429" it "Generate IVK" $ do
let finalTree = tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
getOrchardFrontier $ case oK of
OrchardCommitmentTree $ Nothing -> assertFailure "Failed to generate Orchard SK"
hexString Just o ->
"0184f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f01e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d4291f0000014b1a76d3820087b26cd087ca84e17f3067a25ebed82ad23a93fa485affb5530b01ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" case sK of
case getOrchardFrontier tree of Nothing -> assertFailure "Failed to generate Sapling SK"
Nothing -> assertFailure "Failed to get frontier" Just s -> do
Just t1 -> ivk <- deriveUivk MainNet o s tK
case updateOrchardCommitmentTree t1 cmx1 of decodeUivk (E.encodeUtf8 ivk) `shouldNotBe` Nothing
Nothing -> assertFailure "Failed to update frontier with cmx"
Just t2 -> do
case getOrchardWitness t2 of
Nothing -> assertFailure "Failed to get witness"
Just wit -> do
let uWit = updateOrchardWitness wit [cmx2, cmx3, cmx4]
Just (getOrchardWitnessAnchor uWit) `shouldBe`
getOrchardTreeAnchor <$>
finalTree
describe "Witness updates" $ do
it "Sapling" $ do
let wit =
SaplingWitness $
hexString
"01bd8a3f3cfc964332a2ada8c09a0da9dfc24174befb938abb086b9be5ca049e49013607f5e51826c8e5f660571ddfae14cd6fb1dc026bcd6855459b4e9339b20521100000019f0d7efb00169bb2202152d3266059d208ab17d14642c3339f9075e997160657000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39038cd7f6e2238d16ef49420963348dd4e4c7d23d5e5dac69507fba8937f63eb626f6856115bea2fa8db3a65a0ab294db41c51435d3b7ea27c7b2835aca28e82a2c1d9634efe07449a47c251518ac6f92c49f3a1ef119948f6a824d1e7ff7d0443e0101e57ec972a9b9383dc9cb228980d2d7752bb2abebc4a604ca48c5457039d2e05b000301392bed8592185dde5ab7fc81aed75e98fcf041f1a3fda55ad0b0b139ba9380130001808304b4d7c4fc407f5ce28247a7119013aeaaf1481902419c42bc8b21575c15"
let cmus =
[ hexString
"958ccdc752f2f593f6c1c8e2d7201348cd896e54c6d3c92200bdbe8b859eac44"
, hexString
"e49992fdd071d90bf56242d1aa625bbe267a34e0debd4307818a686d05b45447"
, hexString
"0c4b26766d89bf6cdb4fd3b0317b4e9a2fb3850f6a24869f32fe7cb0fd512e18"
]
updateSaplingWitness wit cmus `shouldBe`
SaplingWitness
(hexString
"01bd8a3f3cfc964332a2ada8c09a0da9dfc24174befb938abb086b9be5ca049e49013607f5e51826c8e5f660571ddfae14cd6fb1dc026bcd6855459b4e9339b20521100000019f0d7efb00169bb2202152d3266059d208ab17d14642c3339f9075e997160657000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39038cd7f6e2238d16ef49420963348dd4e4c7d23d5e5dac69507fba8937f63eb626f6856115bea2fa8db3a65a0ab294db41c51435d3b7ea27c7b2835aca28e82a2c1d9634efe07449a47c251518ac6f92c49f3a1ef119948f6a824d1e7ff7d0443e0101e49992fdd071d90bf56242d1aa625bbe267a34e0debd4307818a686d05b45447010c4b26766d89bf6cdb4fd3b0317b4e9a2fb3850f6a24869f32fe7cb0fd512e1803000121c06ee1f1584f79d50785797a694c742be2ded600367ab7d54f3ed49e3adf7201808304b4d7c4fc407f5ce28247a7119013aeaaf1481902419c42bc8b21575c15")
it "Orchard" $ do
let wit =
OrchardWitness $
hexString
"016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e000200019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301"
let cmxs =
[ hexString
"712ba86615ff4447e8d7c7b59f3873f03c03a173438b8e4c8d416756ed4fae10"
, hexString
"c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e"
, hexString
"ac20b8170b008888c19fc6e16f5e30a5ef1653e5219d0cd0c9353c3aa8f79823"
]
updateOrchardWitness wit cmxs `shouldBe`
OrchardWitness
(hexString
"016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e01ac20b8170b008888c19fc6e16f5e30a5ef1653e5219d0cd0c9353c3aa8f7982302010cfb50d8c877eb39e9c07082a032dd99d34be7c19fa7f30e9fecf5f14736240f019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301")
-- | Properties -- | Properties
prop_PhraseLength :: Property prop_PhraseLength :: Property

View file

@ -5,7 +5,7 @@ cabal-version: 3.0
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: zcash-haskell name: zcash-haskell
version: 0.7.6.0 version: 0.7.8.0
synopsis: Utilities to interact with the Zcash blockchain synopsis: Utilities to interact with the Zcash blockchain
description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme> description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme>
category: Blockchain category: Blockchain