Add Sapling output decrypting with spending key #55
6 changed files with 120 additions and 8 deletions
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [0.5.3.0]
|
## [0.5.3.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Function to decode Sapling outputs with a spending key
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Parsing of `TxIn` for FFI
|
- Parsing of `TxIn` for FFI
|
||||||
|
|
1
librustzcash-wrapper/Cargo.lock
generated
1
librustzcash-wrapper/Cargo.lock
generated
|
@ -1284,6 +1284,7 @@ dependencies = [
|
||||||
"borsh 0.10.3",
|
"borsh 0.10.3",
|
||||||
"f4jumble",
|
"f4jumble",
|
||||||
"haskell-ffi",
|
"haskell-ffi",
|
||||||
|
"incrementalmerkletree",
|
||||||
"nonempty",
|
"nonempty",
|
||||||
"orchard",
|
"orchard",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
|
@ -18,6 +18,7 @@ zcash_client_backend = "0.10.0"
|
||||||
zip32 = "0.1.0"
|
zip32 = "0.1.0"
|
||||||
proc-macro2 = "1.0.66"
|
proc-macro2 = "1.0.66"
|
||||||
nonempty = "0.7.0"
|
nonempty = "0.7.0"
|
||||||
|
incrementalmerkletree = "0.5.0"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -23,6 +23,8 @@ use haskell_ffi::{
|
||||||
FromHaskell, HaskellSize, ToHaskell
|
FromHaskell, HaskellSize, ToHaskell
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use incrementalmerkletree::frontier::CommitmentTree;
|
||||||
|
|
||||||
use zip32;
|
use zip32;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
|
@ -51,6 +53,9 @@ use zcash_primitives::{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sapling::{
|
sapling::{
|
||||||
|
Node,
|
||||||
|
MerklePath,
|
||||||
|
NOTE_COMMITMENT_TREE_DEPTH as SAPLING_DEPTH,
|
||||||
PaymentAddress,
|
PaymentAddress,
|
||||||
keys::{
|
keys::{
|
||||||
PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey,
|
PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey,
|
||||||
|
@ -63,6 +68,7 @@ use zcash_primitives::{
|
||||||
consensus::{
|
consensus::{
|
||||||
BranchId::Nu5,
|
BranchId::Nu5,
|
||||||
MainNetwork,
|
MainNetwork,
|
||||||
|
TestNetwork,
|
||||||
BlockHeight
|
BlockHeight
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -602,7 +608,7 @@ pub extern "C" fn rust_wrapper_svk_decode(
|
||||||
let input: Vec<u8> = marshall_from_haskell_var(input, input_len, RW);
|
let input: Vec<u8> = marshall_from_haskell_var(input, input_len, RW);
|
||||||
let svk = ExtendedFullViewingKey::read(&*input);
|
let svk = ExtendedFullViewingKey::read(&*input);
|
||||||
match svk {
|
match svk {
|
||||||
Ok(k) => {
|
Ok(_k) => {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -698,6 +704,64 @@ pub extern "C" fn rust_wrapper_ufvk_decode(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_wrapper_sapling_esk_decrypt(
|
||||||
|
key: *const u8,
|
||||||
|
key_len: usize,
|
||||||
|
note: *const u8,
|
||||||
|
note_len: usize,
|
||||||
|
external: bool,
|
||||||
|
net: bool,
|
||||||
|
out: *mut u8,
|
||||||
|
out_len: &mut usize
|
||||||
|
){
|
||||||
|
let sk: Vec<u8> = marshall_from_haskell_var(key, key_len, RW);
|
||||||
|
let note_input: Vec<u8> = marshall_from_haskell_var(note,note_len,RW);
|
||||||
|
let mut note_reader = Cursor::new(note_input);
|
||||||
|
let esk = ExtendedSpendingKey::from_bytes(&sk);
|
||||||
|
let main_domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(419200));
|
||||||
|
let test_domain = SaplingDomain::for_height(TestNetwork, BlockHeight::from_u32(419200));
|
||||||
|
let scope = if external {
|
||||||
|
SaplingScope::External
|
||||||
|
} else {
|
||||||
|
SaplingScope::Internal
|
||||||
|
};
|
||||||
|
match esk {
|
||||||
|
Ok(k) => {
|
||||||
|
let action = OutputDescription::read(&mut note_reader);
|
||||||
|
match action {
|
||||||
|
Ok(action2) => {
|
||||||
|
let dfvk = k.to_diversifiable_full_viewing_key();
|
||||||
|
let ivk = dfvk.to_ivk(scope);
|
||||||
|
let nk = dfvk.to_nk(scope);
|
||||||
|
let pivk = SaplingPreparedIncomingViewingKey::new(&ivk);
|
||||||
|
let result = if net { zcash_note_encryption::try_note_decryption(&main_domain, &pivk, &action2)}
|
||||||
|
else {zcash_note_encryption::try_note_decryption(&test_domain, &pivk, &action2)};
|
||||||
|
match result {
|
||||||
|
Some((n, r, m)) => {
|
||||||
|
//let nullifier = n.nf(&nk, MerklePath<Node::from_cmu(&n.cmu()), SAPLING_DEPTH>.position());
|
||||||
|
let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec() };
|
||||||
|
marshall_to_haskell_var(&hn, out, out_len, RW);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]};
|
||||||
|
marshall_to_haskell_var(&hn0, out, out_len, RW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_e1) => {
|
||||||
|
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
|
||||||
|
marshall_to_haskell_var(&hn0, out, out_len, RW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_e) => {
|
||||||
|
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
|
||||||
|
marshall_to_haskell_var(&hn0, out, out_len, RW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2(
|
pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2(
|
||||||
key: *const u8,
|
key: *const u8,
|
||||||
|
|
|
@ -101,6 +101,16 @@ import ZcashHaskell.Types
|
||||||
-> `()'
|
-> `()'
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
{# fun unsafe rust_wrapper_sapling_esk_decrypt as rustWrapperSaplingDecodeEsk
|
||||||
|
{ toBorshVar* `BS.ByteString'&
|
||||||
|
, toBorshVar* `BS.ByteString'&
|
||||||
|
, `Bool'
|
||||||
|
, `Bool'
|
||||||
|
, getVarBuffer `Buffer DecodedNote'&
|
||||||
|
}
|
||||||
|
-> `()'
|
||||||
|
#}
|
||||||
|
|
||||||
{# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode
|
{# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode
|
||||||
{ toBorshVar* `BS.ByteString'&
|
{ toBorshVar* `BS.ByteString'&
|
||||||
, getVarBuffer `Buffer UnifiedFullViewingKey'&
|
, getVarBuffer `Buffer UnifiedFullViewingKey'&
|
||||||
|
|
|
@ -21,6 +21,7 @@ import C.Zcash
|
||||||
( rustWrapperIsShielded
|
( rustWrapperIsShielded
|
||||||
, rustWrapperSaplingCheck
|
, rustWrapperSaplingCheck
|
||||||
, rustWrapperSaplingChgPaymentAddress
|
, rustWrapperSaplingChgPaymentAddress
|
||||||
|
, rustWrapperSaplingDecodeEsk
|
||||||
, rustWrapperSaplingNoteDecode
|
, rustWrapperSaplingNoteDecode
|
||||||
, rustWrapperSaplingPaymentAddress
|
, rustWrapperSaplingPaymentAddress
|
||||||
, rustWrapperSaplingSpendingkey
|
, rustWrapperSaplingSpendingkey
|
||||||
|
@ -29,7 +30,7 @@ import C.Zcash
|
||||||
)
|
)
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
import Data.HexString (HexString(..), toBytes)
|
import Data.HexString (HexString(..), fromText, toBytes, toText)
|
||||||
import Data.Word
|
import Data.Word
|
||||||
import Foreign.Rust.Marshall.Variable
|
import Foreign.Rust.Marshall.Variable
|
||||||
( withPureBorshVarBuffer
|
( withPureBorshVarBuffer
|
||||||
|
@ -43,9 +44,11 @@ import ZcashHaskell.Types
|
||||||
, RawTxResponse(..)
|
, RawTxResponse(..)
|
||||||
, SaplingReceiver(..)
|
, SaplingReceiver(..)
|
||||||
, SaplingSpendingKey(..)
|
, SaplingSpendingKey(..)
|
||||||
|
, Scope(..)
|
||||||
, Seed(..)
|
, Seed(..)
|
||||||
, ShieldedOutput(..)
|
, ShieldedOutput(..)
|
||||||
, ToBytes(..)
|
, ToBytes(..)
|
||||||
|
, ZcashNet(..)
|
||||||
, decodeHexText
|
, decodeHexText
|
||||||
, getValue
|
, getValue
|
||||||
)
|
)
|
||||||
|
@ -58,6 +61,15 @@ isValidShieldedAddress = rustWrapperIsShielded
|
||||||
getShieldedOutputs :: HexString -> [BS.ByteString]
|
getShieldedOutputs :: HexString -> [BS.ByteString]
|
||||||
getShieldedOutputs t = withPureBorshVarBuffer $ rustWrapperTxParse $ toBytes t
|
getShieldedOutputs t = withPureBorshVarBuffer $ rustWrapperTxParse $ toBytes t
|
||||||
|
|
||||||
|
serializeShieldedOutput :: ShieldedOutput -> BS.ByteString
|
||||||
|
serializeShieldedOutput so =
|
||||||
|
hexBytes . fromText $
|
||||||
|
toText (s_cv so) <>
|
||||||
|
toText (s_cmu so) <>
|
||||||
|
toText (s_ephKey so) <>
|
||||||
|
toText (s_encCipherText so) <>
|
||||||
|
toText (s_outCipherText so) <> toText (s_proof so)
|
||||||
|
|
||||||
-- | Check if given bytestring is a valid Sapling viewing key
|
-- | Check if given bytestring is a valid Sapling viewing key
|
||||||
isValidSaplingViewingKey :: BS.ByteString -> Bool
|
isValidSaplingViewingKey :: BS.ByteString -> Bool
|
||||||
isValidSaplingViewingKey k =
|
isValidSaplingViewingKey k =
|
||||||
|
@ -98,6 +110,26 @@ instance FromJSON RawTxResponse where
|
||||||
a <- o' .: "actions"
|
a <- o' .: "actions"
|
||||||
pure $ RawTxResponse i h sSpend (getShieldedOutputs h) a ht c b
|
pure $ RawTxResponse i h sSpend (getShieldedOutputs h) a ht c b
|
||||||
|
|
||||||
|
-- | Attempt to decode the given raw tx with the given Sapling spending key
|
||||||
|
decodeSaplingOutputEsk ::
|
||||||
|
SaplingSpendingKey
|
||||||
|
-> ShieldedOutput
|
||||||
|
-> ZcashNet
|
||||||
|
-> Scope
|
||||||
|
-> Maybe DecodedNote
|
||||||
|
decodeSaplingOutputEsk key out znet scope =
|
||||||
|
case a_value decodedAction of
|
||||||
|
0 -> Nothing
|
||||||
|
_ -> Just decodedAction
|
||||||
|
where
|
||||||
|
decodedAction =
|
||||||
|
withPureBorshVarBuffer $
|
||||||
|
rustWrapperSaplingDecodeEsk
|
||||||
|
(getBytes key)
|
||||||
|
(serializeShieldedOutput out)
|
||||||
|
(znet == MainNet)
|
||||||
|
(scope == External)
|
||||||
|
|
||||||
-- | Attempts to obtain a sapling SpendingKey using a HDSeed
|
-- | Attempts to obtain a sapling SpendingKey using a HDSeed
|
||||||
genSaplingSpendingKey :: Seed -> CoinType -> Int -> Maybe SaplingSpendingKey
|
genSaplingSpendingKey :: Seed -> CoinType -> Int -> Maybe SaplingSpendingKey
|
||||||
genSaplingSpendingKey seed c i = do
|
genSaplingSpendingKey seed c i = do
|
||||||
|
|
Loading…
Reference in a new issue