Add Sapling output decrypting with spending key

This commit is contained in:
Rene Vergara 2024-04-08 12:54:05 -05:00
parent 2709d42266
commit e00cf21709
Signed by: pitmutt
GPG key ID: 65122AD495A7F5B2
6 changed files with 120 additions and 8 deletions

View file

@ -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

View file

@ -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",

View file

@ -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]

View file

@ -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,
@ -722,11 +786,11 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2(
let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action3); let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action3);
match result { match result {
Some((n, r, m)) => { Some((n, r, m)) => {
let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec() }; 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); marshall_to_haskell_var(&hn, out, out_len, RW);
} }
None => { None => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]};
marshall_to_haskell_var(&hn0, out, out_len, RW); marshall_to_haskell_var(&hn0, out, out_len, RW);
} }
} }
@ -738,7 +802,7 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2(
} }
} }
Err(_e) => { Err(_e) => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]};
marshall_to_haskell_var(&hn0, out, out_len, RW); marshall_to_haskell_var(&hn0, out, out_len, RW);
} }
} }
@ -772,17 +836,17 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt(
let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action);
match result { match result {
Some((n, r, m)) => { Some((n, r, m)) => {
let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec() }; let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec()};
marshall_to_haskell_var(&hn, out, out_len, RW); marshall_to_haskell_var(&hn, out, out_len, RW);
} }
None => { None => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]};
marshall_to_haskell_var(&hn0, out, out_len, RW); marshall_to_haskell_var(&hn0, out, out_len, RW);
} }
} }
}, },
None => { None => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]};
marshall_to_haskell_var(&hn0, out, out_len, RW); marshall_to_haskell_var(&hn0, out, out_len, RW);
} }
} }

View file

@ -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'&

View file

@ -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