Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
cfa862ec94 | |||
7d3ae36d2b |
12 changed files with 891 additions and 495 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -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/),
|
||||
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]
|
||||
|
||||
### Changed
|
||||
|
|
853
librustzcash-wrapper/Cargo.lock
generated
853
librustzcash-wrapper/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,13 +9,13 @@ haskell-ffi.git = "https://github.com/BeFunctional/haskell-rust-ffi.git"
|
|||
haskell-ffi.rev = "2bf292e2e56eac8e9fb0fb2e1450cf4a4bd01274"
|
||||
f4jumble = "0.1"
|
||||
zcash_address = "0.2.0"
|
||||
borsh = "0.10"
|
||||
borsh = "0.9"
|
||||
bech32 = "0.11"
|
||||
orchard = "0.10.0"
|
||||
zcash_note_encryption = "0.4.0"
|
||||
zcash_primitives = { version = "0.19.0", features = ["transparent-inputs"]}
|
||||
zcash_client_backend = "0.14.0"
|
||||
sapling-crypto = "0.3"
|
||||
zcash_primitives = { version = "0.21.0", features = ["transparent-inputs"]}
|
||||
zcash_client_backend = "0.16.0"
|
||||
sapling-crypto = "0.4"
|
||||
zip32 = "0.1.2"
|
||||
proc-macro2 = "1.0.66"
|
||||
nonempty = "0.7.0"
|
||||
|
@ -25,6 +25,7 @@ jubjub = "0.10.0"
|
|||
rand_core = { version = "0.6.4", features = ["getrandom"]}
|
||||
wagyu-zcash-parameters = "0.2.0"
|
||||
bip0039 = "0.12.0"
|
||||
ahash = "0.7.8"
|
||||
|
||||
|
||||
[features]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[toolchain]
|
||||
channel = "nightly-2024-02-04"
|
||||
channel = "nightly"
|
||||
components = [ "rustfmt", "rustc-dev"]
|
||||
profile = "minimal"
|
||||
|
|
|
@ -116,6 +116,7 @@ use zcash_primitives::{
|
|||
},
|
||||
transparent::{
|
||||
Bundle as TransparentBundle,
|
||||
builder::TransparentSigningSet,
|
||||
TxIn,
|
||||
TxOut,
|
||||
OutPoint,
|
||||
|
@ -134,7 +135,7 @@ use zcash_primitives::{
|
|||
|
||||
use zcash_address::{
|
||||
Network,
|
||||
unified::{Address, Encoding, Ufvk, Container, Fvk, Receiver},
|
||||
unified::{Address, Encoding, Ufvk, Uivk, Ivk, Container, Fvk, Receiver},
|
||||
ZcashAddress
|
||||
};
|
||||
|
||||
|
@ -156,7 +157,7 @@ use orchard::{
|
|||
Flags
|
||||
},
|
||||
Action,
|
||||
keys::{SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope},
|
||||
keys::{SpendAuthorizingKey, SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope},
|
||||
note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment},
|
||||
note_encryption::OrchardDomain,
|
||||
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)]
|
||||
pub struct Hsvk {
|
||||
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]
|
||||
pub extern "C" fn rust_wrapper_sapling_esk_decrypt(
|
||||
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 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 mut tss = TransparentSigningSet::new();
|
||||
for t_in in trans_input {
|
||||
if t_in.sk.len() > 1 {
|
||||
//println!("t inp: {:?}", t_in);
|
||||
let k = SecretKey::from_slice(&t_in.sk).unwrap();
|
||||
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(()) => {
|
||||
//println!("added t-input in main");
|
||||
continue;
|
||||
|
@ -2186,7 +2245,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
|
|||
Err(_e) => { println!("Error reading transparent input"); }
|
||||
}
|
||||
} 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(()) => {
|
||||
//println!("added t-input in test");
|
||||
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 {
|
||||
if s_in.sk.len() > 1 {
|
||||
//println!("s inp: {:?}", s_in);
|
||||
let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk);
|
||||
match sp_key {
|
||||
Ok(sk) => {
|
||||
sap_key_array.push(sk.clone());
|
||||
let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap();
|
||||
let rseed = if s_in.note.rseed.kind == 1 {
|
||||
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()
|
||||
}).collect(), Position::from(u64::from(s_in.iw.position)));
|
||||
let merkle_path = mk_path.unwrap();
|
||||
let fvk = sk.to_diversifiable_full_viewing_key().fvk().clone();
|
||||
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 {
|
||||
Ok(()) => {
|
||||
continue;
|
||||
|
@ -2228,7 +2290,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
|
|||
}
|
||||
}
|
||||
} 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 {
|
||||
Ok(()) => {
|
||||
continue;
|
||||
|
@ -2247,9 +2309,11 @@ pub extern "C" fn rust_wrapper_create_transaction(
|
|||
}
|
||||
}
|
||||
}
|
||||
let mut orch_keys = vec![];
|
||||
for o_in in orch_input {
|
||||
if o_in.sk.len() > 1 {
|
||||
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 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();
|
||||
|
@ -2264,7 +2328,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
|
|||
}
|
||||
).collect()));
|
||||
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 {
|
||||
Ok(()) => {
|
||||
//println!("added orchard inp: {:?}", val);
|
||||
|
@ -2277,7 +2341,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
|
|||
}
|
||||
}
|
||||
} 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 {
|
||||
Ok(()) => {
|
||||
//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_prover = OutputParameters::read(output_params_reader, false).unwrap();
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{ toBorshVar* `BS.ByteString'&
|
||||
, 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'&
|
||||
}
|
||||
-> `()'
|
||||
#}
|
||||
|
|
|
@ -15,13 +15,42 @@
|
|||
module ZcashHaskell.Keys where
|
||||
|
||||
import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed)
|
||||
import Crypto.Secp256k1 (createContext)
|
||||
import qualified Data.ByteString as BS
|
||||
import Data.HexString (hexBytes)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as E
|
||||
import Data.Word (Word8(..))
|
||||
import Foreign.Rust.Marshall.Variable
|
||||
( withBorshVarBuffer
|
||||
, 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.
|
||||
generateWalletSeedPhrase :: IO Phrase
|
||||
|
@ -36,3 +65,97 @@ getWalletSeed p =
|
|||
where
|
||||
result :: Seed
|
||||
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
|
||||
|
|
|
@ -19,6 +19,8 @@ module ZcashHaskell.Orchard where
|
|||
|
||||
import C.Zcash
|
||||
( rustWrapperCombineOrchardNodes
|
||||
, rustWrapperCreateOrchardFvk
|
||||
, rustWrapperCreateOrchardIvk
|
||||
, rustWrapperGenOrchardReceiver
|
||||
, rustWrapperGenOrchardSpendKey
|
||||
, rustWrapperGetOrchardRootTest
|
||||
|
@ -37,6 +39,7 @@ import C.Zcash
|
|||
, rustWrapperReadOrchardWitnessAnchor
|
||||
, rustWrapperUADecode
|
||||
, rustWrapperUfvkDecode
|
||||
, rustWrapperUivkDecode
|
||||
, rustWrapperUpdateOrchardWitness
|
||||
)
|
||||
import qualified Data.ByteString as BS
|
||||
|
@ -157,6 +160,15 @@ decodeUfvk str =
|
|||
where
|
||||
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
|
||||
matchOrchardAddress :: BS.ByteString -> BS.ByteString -> Bool
|
||||
matchOrchardAddress = rustWrapperOrchardCheck
|
||||
|
@ -337,3 +349,25 @@ compareAddress a u =
|
|||
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
|
||||
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
|
||||
|
|
|
@ -19,6 +19,8 @@ module ZcashHaskell.Sapling where
|
|||
|
||||
import C.Zcash
|
||||
( rustWrapperCombineSaplingNodes
|
||||
, rustWrapperCreateSaplingFvk
|
||||
, rustWrapperCreateSaplingIvk
|
||||
, rustWrapperDecodeSaplingAddress
|
||||
, rustWrapperGetSaplingRootTest
|
||||
, rustWrapperIsShielded
|
||||
|
@ -318,3 +320,25 @@ decodeSaplingAddress sapling_address = do
|
|||
where
|
||||
sa =
|
||||
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
|
||||
|
|
|
@ -696,6 +696,18 @@ data UnifiedFullViewingKey = UnifiedFullViewingKey
|
|||
deriving anyclass (Data.Structured.Show)
|
||||
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)
|
||||
data OrchardAction = OrchardAction
|
||||
{ nf :: !HexString -- ^ The nullifier of the input note
|
||||
|
|
105
test/Spec.hs
105
test/Spec.hs
|
@ -45,7 +45,12 @@ import Test.HUnit
|
|||
import Test.Hspec
|
||||
import Test.Hspec.QuickCheck
|
||||
import Test.QuickCheck
|
||||
import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
|
||||
import ZcashHaskell.Keys
|
||||
( deriveUfvk
|
||||
, deriveUivk
|
||||
, generateWalletSeedPhrase
|
||||
, getWalletSeed
|
||||
)
|
||||
import ZcashHaskell.Orchard
|
||||
import ZcashHaskell.Sapling
|
||||
( decodeSaplingAddress
|
||||
|
@ -1162,77 +1167,33 @@ main = do
|
|||
Just addr -> do
|
||||
let eadr = decodeExchangeAddress (E.encodeUtf8 addr)
|
||||
eadr `shouldNotBe` Nothing
|
||||
describe "Tree updates" $ do
|
||||
it "Orchard" $ do
|
||||
let tree =
|
||||
OrchardCommitmentTree $
|
||||
hexString
|
||||
"0136a7886d7d73bc1845223165fd9cb0cef02046c707e8f88a7f61564720bd0f3501dca1fbdd7b5ba92a0809af5e85874626ce0db14d0532a48e41dde6f0f81b46011f0001fb48c27bd07e68f27aba47cd6e93fa961e0ef8c63f993963a614e56855d2013c0001ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
let cmx1 =
|
||||
hexString
|
||||
"1712ead46028d4349e234abf59e94e0640fe7a0829e2e2e17e1a931631810400"
|
||||
let cmx2 =
|
||||
hexString
|
||||
"39f5ad39817fb432fa07c5feb3a957189fbe7662a4b5555ca95093b6d853cf07"
|
||||
let cmx3 =
|
||||
hexString
|
||||
"84f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f"
|
||||
let cmx4 =
|
||||
hexString
|
||||
"e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d429"
|
||||
let finalTree =
|
||||
getOrchardFrontier $
|
||||
OrchardCommitmentTree $
|
||||
hexString
|
||||
"0184f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f01e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d4291f0000014b1a76d3820087b26cd087ca84e17f3067a25ebed82ad23a93fa485affb5530b01ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
case getOrchardFrontier tree of
|
||||
Nothing -> assertFailure "Failed to get frontier"
|
||||
Just t1 ->
|
||||
case updateOrchardCommitmentTree t1 cmx1 of
|
||||
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")
|
||||
describe "Generate Viewing Keys" $ do
|
||||
let p =
|
||||
Phrase
|
||||
"cloth swing left trap random tornado have great onion element until make shy dad success art tuition canvas thunder apple decade elegant struggle invest"
|
||||
let seed = getWalletSeed p
|
||||
let oK = genOrchardSpendingKey (fromJust seed) MainNetCoin 0
|
||||
let sK = genSaplingSpendingKey (fromJust seed) MainNetCoin 0
|
||||
it "Generate FVK" $ do
|
||||
tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
|
||||
case oK of
|
||||
Nothing -> assertFailure "Failed to generate Orchard SK"
|
||||
Just o ->
|
||||
case sK of
|
||||
Nothing -> assertFailure "Failed to generate Sapling SK"
|
||||
Just s -> do
|
||||
fvk <- deriveUfvk MainNet o s tK
|
||||
decodeUfvk (E.encodeUtf8 fvk) `shouldNotBe` Nothing
|
||||
it "Generate IVK" $ do
|
||||
tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
|
||||
case oK of
|
||||
Nothing -> assertFailure "Failed to generate Orchard SK"
|
||||
Just o ->
|
||||
case sK of
|
||||
Nothing -> assertFailure "Failed to generate Sapling SK"
|
||||
Just s -> do
|
||||
ivk <- deriveUivk MainNet o s tK
|
||||
decodeUivk (E.encodeUtf8 ivk) `shouldNotBe` Nothing
|
||||
|
||||
-- | Properties
|
||||
prop_PhraseLength :: Property
|
||||
|
|
|
@ -5,7 +5,7 @@ cabal-version: 3.0
|
|||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: zcash-haskell
|
||||
version: 0.7.6.0
|
||||
version: 0.7.8.0
|
||||
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>
|
||||
category: Blockchain
|
||||
|
|
Loading…
Reference in a new issue