Skip to content

Commit

Permalink
feat: disposable signer for shielded transfers using ledger
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszjasiuk committed Jan 16, 2025
1 parent 22f8929 commit b548323
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 143 deletions.
2 changes: 1 addition & 1 deletion apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export class ApprovalsService {
): Promise<void> {
const pendingTx = await this.txStore.get(msgId);
if (!pendingTx) {
throw new Error(ApprovalErrors.PendingTxNotFound(msgId));
throw new Error(ApprovalErrors.TransactionDataNotFound(msgId));
}

const { tx: sdkTx } = this.sdkService.getSdk();
Expand Down
1 change: 0 additions & 1 deletion apps/extension/src/background/approvals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export const ApprovalErrors = {
`Pending signing data not found for ${msgId}!`,
PendingSignArbitaryDataNotFound: (msgId: string) =>
`Pending sign arbitrary data not found for ${msgId}!`,
PendingTxNotFound: (msgId: string) => `Pending tx not found for ${msgId}`,
TransactionDataNotFound: (msgId: string) =>
`Transaction data not found for ${msgId}`,
InvalidLedgerSignature: (msgId: string) =>
Expand Down
24 changes: 21 additions & 3 deletions apps/namadillo/src/atoms/transfer/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,22 @@ export const createShieldedTransferTx = async (
rpcUrl: string,
memo?: string
): Promise<TransactionPair<ShieldedTransferProps> | undefined> => {
// For now we only support disposableSigner for non-ledger accounts
const { address: signerAddress, publicKey: signerPublicKey } =
account.type === AccountType.Ledger ? account : await getDisposableSigner();
await getDisposableSigner();
const source = props[0]?.data[0]?.source;
const destination = props[0]?.data[0]?.target;
const token = props[0]?.data[0]?.token;
const amount = props[0]?.data[0]?.amount;

let bparams: BparamsMsgValue[] | undefined;

if (account.type === AccountType.Ledger) {
const sdk = await getSdkInstance();
const ledger = await sdk.initLedger();
bparams = await ledger.getBparams();
ledger.closeTransport();
}

return await workerBuildTxPair({
rpcUrl,
token,
Expand All @@ -120,6 +128,7 @@ export const createShieldedTransferTx = async (
const msgValue = new ShieldedTransferMsgValue({
gasSpendingKey: source,
data: [{ source, target: destination, token, amount }],
bparams,
});
const msg: ShieldedTransfer = {
type: "shielded-transfer",
Expand Down Expand Up @@ -153,6 +162,15 @@ export const createShieldingTransferTx = async (
const token = props[0]?.data[0]?.token;
const amount = props[0]?.data[0]?.amount;

let bparams: BparamsMsgValue[] | undefined;

if (account.type === AccountType.Ledger) {
const sdk = await getSdkInstance();
const ledger = await sdk.initLedger();
bparams = await ledger.getBparams();
ledger.closeTransport();
}

return await workerBuildTxPair({
rpcUrl,
token,
Expand All @@ -161,6 +179,7 @@ export const createShieldingTransferTx = async (
const msgValue = new ShieldingTransferMsgValue({
target: destination,
data: [{ source, token, amount }],
bparams,
});
const msg: Shield = {
type: "shield",
Expand All @@ -186,7 +205,6 @@ export const createUnshieldingTransferTx = async (
rpcUrl: string,
memo?: string
): Promise<TransactionPair<UnshieldingTransferProps> | undefined> => {
// For now we only support disposableSigner for non-ledger accounts
const { address: signerAddress, publicKey: signerPublicKey } =
await getDisposableSigner();

Expand Down
11 changes: 10 additions & 1 deletion packages/sdk/src/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,18 @@ export class Ledger {
// to ensure that the randomness is not reused
await this.namadaApp.cleanRandomnessBuffers();
const results: Bparams[] = [];
let tries = 0;

// TODO: not sure why ledger sometimes returns errors, so we try to get 15 valid responses
// This should not happen usually, but in case some of the responses are not valid, we will retry.
// 15 is a maximum number of spend/output/convert description randomness parameters that can be
// generated on the hardware wallet. This also means that ledger can sign maximum of 15 spend, output
// and convert descriptions in one tx.
while (results.length < 15) {
tries++;
if (tries === 20) {
throw new Error("Could not get valid Bparams, too many tries");
}

const spend_response = await this.namadaApp.getSpendRandomness();
const output_response = await this.namadaApp.getOutputRandomness();
const convert_response = await this.namadaApp.getConvertRandomness();
Expand Down
175 changes: 86 additions & 89 deletions packages/shared/lib/src/sdk/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,34 @@ pub fn transparent_transfer_tx_args(
Ok(args)
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsSpendMsg {
rcv: Vec<u8>,
alpha: Vec<u8>,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsOutputMsg {
rcv: Vec<u8>,
rcm: Vec<u8>,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsConvertMsg {
rcv: Vec<u8>,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsMsg {
spend: BparamsSpendMsg,
output: BparamsOutputMsg,
convert: BparamsConvertMsg,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct ShieldedTransferDataMsg {
Expand All @@ -515,6 +543,7 @@ pub struct ShieldedTransferDataMsg {
pub struct ShieldedTransferMsg {
data: Vec<ShieldedTransferDataMsg>,
gas_spending_key: Option<String>,
bparams: Option<Vec<BparamsMsg>>,
}

/// Maps serialized tx_msg into TxShieldedTransfer args.
Expand All @@ -531,11 +560,12 @@ pub struct ShieldedTransferMsg {
pub fn shielded_transfer_tx_args(
shielded_transfer_msg: &[u8],
tx_msg: &[u8],
) -> Result<args::TxShieldedTransfer, JsError> {
) -> Result<(args::TxShieldedTransfer, Option<StoredBuildParams>), JsError> {
let shielded_transfer_msg = ShieldedTransferMsg::try_from_slice(shielded_transfer_msg)?;
let ShieldedTransferMsg {
data,
gas_spending_key,
bparams: bparams_msg,
} = shielded_transfer_msg;

let gas_spending_key = gas_spending_key.map(|v| PseudoExtendedKey::decode(v).0);
Expand All @@ -559,6 +589,7 @@ pub fn shielded_transfer_tx_args(
}

let tx = tx_msg_into_args(tx_msg)?;
let bparams = bparams_msg_into_bparams(bparams_msg);

let args = args::TxShieldedTransfer {
data: shielded_transfer_data,
Expand All @@ -569,7 +600,7 @@ pub fn shielded_transfer_tx_args(
gas_spending_key,
};

Ok(args)
Ok((args, bparams))
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
Expand All @@ -585,6 +616,7 @@ pub struct ShieldingTransferDataMsg {
pub struct ShieldingTransferMsg {
target: String,
data: Vec<ShieldingTransferDataMsg>,
bparams: Option<Vec<BparamsMsg>>,
}

/// Maps serialized tx_msg into TxShieldingTransfer args.
Expand All @@ -601,9 +633,13 @@ pub struct ShieldingTransferMsg {
pub fn shielding_transfer_tx_args(
shielding_transfer_msg: &[u8],
tx_msg: &[u8],
) -> Result<args::TxShieldingTransfer, JsError> {
) -> Result<(args::TxShieldingTransfer, Option<StoredBuildParams>), JsError> {
let shielding_transfer_msg = ShieldingTransferMsg::try_from_slice(shielding_transfer_msg)?;
let ShieldingTransferMsg { target, data } = shielding_transfer_msg;
let ShieldingTransferMsg {
target,
data,
bparams: bparams_msg,
} = shielding_transfer_msg;
let target = PaymentAddress::from_str(&target)?;

let mut shielding_transfer_data: Vec<args::TxShieldingTransferData> = vec![];
Expand All @@ -623,6 +659,7 @@ pub fn shielding_transfer_tx_args(
}

let tx = tx_msg_into_args(tx_msg)?;
let bparams = bparams_msg_into_bparams(bparams_msg);

let args = args::TxShieldingTransfer {
data: shielding_transfer_data,
Expand All @@ -631,7 +668,7 @@ pub fn shielding_transfer_tx_args(
tx_code_path: PathBuf::from("tx_transfer.wasm"),
};

Ok(args)
Ok((args, bparams))
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
Expand All @@ -642,34 +679,6 @@ pub struct UnshieldingTransferDataMsg {
amount: String,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsSpendMsg {
rcv: Vec<u8>,
alpha: Vec<u8>,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsOutputMsg {
rcv: Vec<u8>,
rcm: Vec<u8>,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsConvertMsg {
rcv: Vec<u8>,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct BparamsMsg {
spend: BparamsSpendMsg,
output: BparamsOutputMsg,
convert: BparamsConvertMsg,
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
#[borsh(crate = "namada_sdk::borsh")]
pub struct UnshieldingTransferMsg {
Expand Down Expand Up @@ -720,45 +729,7 @@ pub fn unshielding_transfer_tx_args(
}

let tx = tx_msg_into_args(tx_msg)?;

let bparams = bparams_msg.map(|bparams_msg| {
let mut bparams = StoredBuildParams::default();
for bpm in bparams_msg {
bparams
.spend_params
.push(sapling::builder::SpendBuildParams {
rcv: masp_primitives::jubjub::Fr::from_bytes(
&bpm.spend.rcv.try_into().unwrap(),
)
.unwrap(),
alpha: masp_primitives::jubjub::Fr::from_bytes(
&bpm.spend.alpha.try_into().unwrap(),
)
.unwrap(),
});

bparams
.output_params
.push(sapling::builder::OutputBuildParams {
rcv: masp_primitives::jubjub::Fr::from_bytes(
&bpm.output.rcv.try_into().unwrap(),
)
.unwrap(),
rseed: bpm.output.rcm.try_into().unwrap(),
..sapling::builder::OutputBuildParams::default()
});

bparams
.convert_params
.push(sapling::builder::ConvertBuildParams {
rcv: masp_primitives::jubjub::Fr::from_bytes(
&bpm.convert.rcv.try_into().unwrap(),
)
.unwrap(),
});
}
bparams
});
let bparams = bparams_msg_into_bparams(bparams_msg);

let args = args::TxUnshieldingTransfer {
data: unshielding_transfer_data,
Expand Down Expand Up @@ -1030,26 +1001,11 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result<args::Tx, JsError> {

pub enum BuildParams {
RngBuildParams(RngBuildParams<OsRng>),
// TODO: HD Wallet support
#[allow(dead_code)]
StoredBuildParams(StoredBuildParams),
}

pub async fn generate_masp_build_params(
// TODO: those will be needed for HD Wallet support
_spend_len: usize,
_convert_len: usize,
_output_len: usize,
args: &args::Tx,
) -> Result<BuildParams, error::Error> {
// Construct the build parameters that parameterized the Transaction
// authorizations
if args.use_device {
// HD Wallet support
Err(error::Error::Other("Device not supported".into()))
} else {
Ok(BuildParams::RngBuildParams(RngBuildParams::new(OsRng)))
}
pub fn generate_rng_build_params() -> BuildParams {
BuildParams::RngBuildParams(RngBuildParams::new(OsRng))
}

// Sign the given transaction's MASP component using real signatures
Expand Down Expand Up @@ -1116,6 +1072,47 @@ where
Ok(())
}

fn bparams_msg_into_bparams(bparams_msg: Option<Vec<BparamsMsg>>) -> Option<StoredBuildParams> {
bparams_msg.map(|bparams_msg| {
let mut bparams = StoredBuildParams::default();
for bpm in bparams_msg {
bparams
.spend_params
.push(sapling::builder::SpendBuildParams {
rcv: masp_primitives::jubjub::Fr::from_bytes(
&bpm.spend.rcv.try_into().unwrap(),
)
.unwrap(),
alpha: masp_primitives::jubjub::Fr::from_bytes(
&bpm.spend.alpha.try_into().unwrap(),
)
.unwrap(),
});

bparams
.output_params
.push(sapling::builder::OutputBuildParams {
rcv: masp_primitives::jubjub::Fr::from_bytes(
&bpm.output.rcv.try_into().unwrap(),
)
.unwrap(),
rseed: bpm.output.rcm.try_into().unwrap(),
..sapling::builder::OutputBuildParams::default()
});

bparams
.convert_params
.push(sapling::builder::ConvertBuildParams {
rcv: masp_primitives::jubjub::Fr::from_bytes(
&bpm.convert.rcv.try_into().unwrap(),
)
.unwrap(),
});
}
bparams
})
}

pub struct MapSaplingSigAuth(
pub HashMap<usize, <sapling::Authorized as sapling::Authorization>::AuthSig>,
);
Expand Down
Loading

0 comments on commit b548323

Please sign in to comment.