Skip to content

Commit

Permalink
Merge pull request #1504 from input-output-hk/feat/lw-11589-make-auxi…
Browse files Browse the repository at this point in the history
…liary-data-and-witness-available-in-sign-request

Feat/lw 11589 make auxiliary data and witness available in sign request
  • Loading branch information
AngelCastilloB authored Oct 7, 2024
2 parents 31b0137 + c18a551 commit 8f69434
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 25 deletions.
32 changes: 18 additions & 14 deletions packages/wallet/src/Wallets/BaseWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
SignDataProps,
SyncStatus,
WalletAddress,
WalletNetworkInfoProvider
WalletNetworkInfoProvider,
isTxBodyWithHash
} from '../types';
import {
AddressDiscovery,
Expand Down Expand Up @@ -619,26 +620,29 @@ export class BaseWallet implements ObservableWallet {
}: FinalizeTxProps): Promise<Cardano.Tx> {
const knownAddresses = await firstValueFrom(this.addresses$);
const dRepPublicKey = await this.governance.getPubDRepKey();
const emptyWitness = { signatures: new Map() };

let transaction: Serialization.Transaction;
if (isTxBodyWithHash(tx)) {
// Reconstruct transaction from parts
transaction = new Serialization.Transaction(
bodyCbor ? Serialization.TransactionBody.fromCbor(bodyCbor) : Serialization.TransactionBody.fromCore(tx.body),
Serialization.TransactionWitnessSet.fromCore({ ...emptyWitness, ...witness }),
auxiliaryData ? Serialization.AuxiliaryData.fromCore(auxiliaryData) : undefined
);
if (isValid !== undefined) transaction.setIsValid(isValid);
} else {
// Transaction CBOR is available. Use as is.
transaction = Serialization.Transaction.fromCbor(tx);
}

const context = {
...signingContext,
dRepPublicKey,
knownAddresses,
txInKeyPathMap: await util.createTxInKeyPathMap(tx.body, knownAddresses, this.util)
txInKeyPathMap: await util.createTxInKeyPathMap(transaction.body().toCore(), knownAddresses, this.util)
};

const emptyWitness = { signatures: new Map() };

// The Witnesser takes a serializable transaction. We cant build that from the hash alone, if
// the bodyCbor is available, use that instead of the coreTx type to build the transaction.
const transaction = new Serialization.Transaction(
bodyCbor ? Serialization.TransactionBody.fromCbor(bodyCbor) : Serialization.TransactionBody.fromCore(tx.body),
Serialization.TransactionWitnessSet.fromCore({ ...emptyWitness, ...witness }),
auxiliaryData ? Serialization.AuxiliaryData.fromCore(auxiliaryData) : undefined
);

if (isValid !== undefined) transaction.setIsValid(isValid);

const result = await this.witnesser.witness(transaction, context, signingOptions);

this.#newTransactions.signed$.next(result);
Expand Down
9 changes: 4 additions & 5 deletions packages/wallet/src/cip30.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,10 +465,10 @@ const baseCip30WalletApi = (
},
signTx: async ({ sender }: SenderContext, tx: Cbor, partialSign?: Boolean): Promise<Cbor> => {
const scope = new ManagedFreeableScope();
logger.debug('signTx');
const txDecoded = Serialization.Transaction.fromCbor(Serialization.TxCBOR(tx));
logger.debug('signTx', tx);
const txCbor = Serialization.TxCBOR(tx);
const txDecoded = Serialization.Transaction.fromCbor(txCbor);
const wallet = await firstValueFrom(wallet$);
const hash = txDecoded.getId();
const coreTx = txDecoded.toCore();

const needsForeignSignature = await requiresForeignSignatures(coreTx, wallet);
Expand All @@ -493,9 +493,8 @@ const baseCip30WalletApi = (
witness: { signatures }
} = await signOrCancel(
wallet.finalizeTx({
bodyCbor: txDecoded.body().toCbor(),
signingContext: { sender },
tx: { ...coreTx, hash }
tx: txCbor
}),
confirmationResult,
() => new TxSignError(TxSignErrorCode.UserDeclined, 'user declined signing tx')
Expand Down
10 changes: 9 additions & 1 deletion packages/wallet/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,13 @@ export interface SyncStatus extends Shutdown {
isSettled$: Observable<boolean>;
}

/**
* If tx is the transaction CBOR, the auxiliary data, witness and isValid properties are ignored.
* If tx is `Cardano.TxBodyWithHash`, the transaction is reconstructed from along with the other
* provided properties.
*/
export type FinalizeTxProps = Omit<TxContext, 'signingContext'> & {
tx: Cardano.TxBodyWithHash;
tx: Cardano.TxBodyWithHash | Serialization.TxCBOR;
bodyCbor?: HexBlob;
signingContext?: Partial<SignTransactionContext>;
};
Expand Down Expand Up @@ -78,6 +83,9 @@ export const isScriptAddress = (address: WalletAddress): address is ScriptAddres

export const isKeyHashAddress = (address: WalletAddress): address is GroupedAddress => !isScriptAddress(address);

export const isTxBodyWithHash = (tx: Serialization.TxCBOR | Cardano.TxBodyWithHash): tx is Cardano.TxBodyWithHash =>
typeof tx === 'object' && 'hash' in tx && 'body' in tx;

export interface ObservableWallet {
readonly balance: BalanceTracker;
readonly delegation: DelegationTracker;
Expand Down
48 changes: 43 additions & 5 deletions packages/wallet/test/PersonalWallet/methods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,18 @@ describe('BaseWallet methods', () => {
});

describe('finalizeTx', () => {
let witnessSpy: jest.SpyInstance;

beforeEach(() => {
witnessSpy = jest.spyOn(witnesser, 'witness');
});

afterEach(() => {
witnessSpy.mockClear();
witnessSpy.mockReset();
witnessSpy.mockRestore();
});

it('resolves with TransactionWitnessSet', async () => {
const txInternals = await wallet.initializeTx(props);
const unhydratedTxBody = Serialization.TransactionBody.fromCore(txInternals.body).toCore();
Expand All @@ -241,24 +253,50 @@ describe('BaseWallet methods', () => {

it('passes through sender to witnesser', async () => {
const sender = { url: 'https://lace.io' };
const witnessSpy = jest.spyOn(witnesser, 'witness');
const txInternals = await wallet.initializeTx(props);
await wallet.finalizeTx({ signingContext: { sender }, tx: txInternals });

// Reset witness calls from wallet.initializeTx
witnessSpy.mockClear();

await wallet.finalizeTx({ signingContext: { sender }, tx: txInternals });
expect(witnessSpy).toHaveBeenCalledTimes(1);
expect(witnessSpy).toBeCalledWith(expect.anything(), expect.objectContaining({ sender }), void 0);
});

it('uses the original CBOR to create the serializable transaction if given', async () => {
const sender = { url: 'https://lace.io' };
const witnessSpy = jest.spyOn(witnesser, 'witness');
const txInternals = await wallet.initializeTx(props);

// Reset witness calls from wallet.initializeTx
witnessSpy.mockClear();

await wallet.finalizeTx({
auxiliaryData: geniusYieldTx.auxiliaryData()?.toCore(),
bodyCbor: geniusYieldTx.body().toCbor(),
signingContext: { sender },
tx: txInternals
tx: txInternals,
witness: geniusYieldTx.witnessSet()?.toCore()
});

expect(witnessSpy).toHaveBeenCalledTimes(1);
const tx: Serialization.Transaction = witnessSpy.mock.calls[0][0];
expect(tx.body().toCbor()).toEqual(geniusYieldTx.body().toCbor());
// The transaction CBOR will not match due to reencoding witnessSet and auxiliaryData
// expect(tx.toCbor()).toEqual(geniusYieldTx.toCbor());
});

it('uses the complete transaction CBOR, ignoring auxiliaryData, witness and isValid', async () => {
const sender = { url: 'https://lace.io' };
await wallet.finalizeTx({
auxiliaryData: 'ignored auxiliary data' as Cardano.AuxiliaryData,
signingContext: { sender },
tx: geniusYieldTx.toCbor(),
witness: 'ignored witness set' as Partial<Cardano.Witness>
});

expect(witnessSpy).toBeCalledWith(geniusYieldTx, expect.objectContaining({ sender }), void 0);
expect(witnessSpy).toHaveBeenCalledTimes(1);
const tx: Serialization.Transaction = witnessSpy.mock.calls[0][0];
expect(tx.toCbor()).toEqual(geniusYieldTx.toCbor());
});
});

Expand Down

0 comments on commit 8f69434

Please sign in to comment.