Skip to content

Commit 90d3ed2

Browse files
committed
fix: Added nativescript checks for ownSignatureKeyPaths
1 parent d0e939e commit 90d3ed2

File tree

5 files changed

+119
-6
lines changed

5 files changed

+119
-6
lines changed

Diff for: packages/key-management/src/InMemoryKeyAgent.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
134134

135135
async signTransaction(
136136
txBody: Serialization.TransactionBody,
137-
{ txInKeyPathMap, knownAddresses }: SignTransactionContext,
137+
{ txInKeyPathMap, knownAddresses, scripts }: SignTransactionContext,
138138
{ additionalKeyPaths = [] }: SignTransactionOptions = {}
139139
): Promise<Cardano.Signatures> {
140140
// Possible optimization is casting strings to OpaqueString types directly and skipping validation
@@ -143,7 +143,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
143143
const dRepKeyHash = (
144144
await Crypto.Ed25519PublicKey.fromHex(await this.derivePublicKey(DREP_KEY_DERIVATION_PATH)).hash()
145145
).hex();
146-
const derivationPaths = ownSignatureKeyPaths(body, knownAddresses, txInKeyPathMap, dRepKeyHash);
146+
const derivationPaths = ownSignatureKeyPaths(body, knownAddresses, txInKeyPathMap, dRepKeyHash, scripts);
147147
const keyPaths = uniqBy([...derivationPaths, ...additionalKeyPaths], ({ role, index }) => `${role}.${index}`);
148148
// TODO:
149149
// if (keyPaths.length === 0) {

Diff for: packages/key-management/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ export interface SignTransactionContext {
175175
handleResolutions?: HandleResolution[];
176176
dRepKeyHashHex?: Crypto.Ed25519KeyHashHex;
177177
sender?: MessageSender;
178+
scripts?: Cardano.Script[];
178179
}
179180

180181
export type SignDataContext = Cip8SignDataContext & { sender?: MessageSender };

Diff for: packages/key-management/src/util/ownSignatureKeyPaths.ts

+94-2
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,96 @@ const getRequiredSignersKeyPaths = (
294294
return paths;
295295
};
296296

297+
const checkStakeCredential = (
298+
address: GroupedAddress,
299+
keyHash: Crypto.Ed25519KeyHashHex,
300+
): SignatureCheck => {
301+
if (
302+
address.stakeKeyDerivationPath &&
303+
Cardano.RewardAccount.toHash(address.rewardAccount) === Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(keyHash)
304+
) {
305+
return { derivationPaths: [address.stakeKeyDerivationPath], requiresForeignSignatures: false };
306+
} else {
307+
return { derivationPaths: [], requiresForeignSignatures: true };
308+
}
309+
};
310+
311+
const checkPaymentCredential = (
312+
address: GroupedAddress,
313+
keyHash: Crypto.Ed25519KeyHashHex,
314+
): SignatureCheck => {
315+
const paymentCredential = Cardano.Address.fromBech32(address.address)?.asBase()?.getPaymentCredential();
316+
if (
317+
paymentCredential?.type === Cardano.CredentialType.KeyHash &&
318+
paymentCredential.hash === Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(keyHash)
319+
) {
320+
return {
321+
derivationPaths: [{ index: address.index, role: Number(address.type) }],
322+
requiresForeignSignatures: false
323+
};
324+
} else {
325+
return {derivationPaths: [], requiresForeignSignatures: true}
326+
}
327+
};
328+
329+
const combineSignatureChecks = (a: SignatureCheck, b: SignatureCheck): SignatureCheck => ({
330+
derivationPaths: [...a.derivationPaths, ...b.derivationPaths],
331+
requiresForeignSignatures: a.requiresForeignSignatures || b.requiresForeignSignatures
332+
});
333+
334+
const processSignatureScript = (
335+
script: Cardano.RequireSignatureScript,
336+
groupedAddresses: GroupedAddress[],
337+
): SignatureCheck => {
338+
let signatureCheck: SignatureCheck = { derivationPaths: [], requiresForeignSignatures: false };
339+
340+
for (const address of groupedAddresses) {
341+
if (address.stakeKeyDerivationPath) {
342+
signatureCheck = checkStakeCredential(address, script.keyHash);
343+
}
344+
signatureCheck = combineSignatureChecks(signatureCheck, checkPaymentCredential(address, script.keyHash));
345+
}
346+
347+
return signatureCheck;
348+
};
349+
350+
const getNativeScriptKeyPaths = (
351+
groupedAddresses: GroupedAddress[],
352+
nativeScripts?: Cardano.Script[]
353+
): SignatureCheck => {
354+
const signatureCheck: SignatureCheck = { derivationPaths: [], requiresForeignSignatures: false };
355+
if (!nativeScripts?.length) return signatureCheck;
356+
357+
const processScript = (script: Cardano.Script): SignatureCheck => {
358+
if (!Cardano.isNativeScript(script)) {
359+
return { derivationPaths: [], requiresForeignSignatures: false };
360+
}
361+
362+
switch (script.kind) {
363+
case Cardano.NativeScriptKind.RequireSignature: {
364+
return processSignatureScript(script as Cardano.RequireSignatureScript, groupedAddresses);
365+
}
366+
case Cardano.NativeScriptKind.RequireAllOf:
367+
case Cardano.NativeScriptKind.RequireAnyOf:
368+
case Cardano.NativeScriptKind.RequireNOf: {
369+
const scriptWithScripts = script as Cardano.RequireAllOfScript | Cardano.RequireAnyOfScript;
370+
return scriptWithScripts.scripts.reduce<SignatureCheck>(
371+
(acc, subScript) => combineSignatureChecks(acc, processScript(subScript)),
372+
{ derivationPaths: [], requiresForeignSignatures: false }
373+
);
374+
}
375+
case Cardano.NativeScriptKind.RequireTimeBefore:
376+
case Cardano.NativeScriptKind.RequireTimeAfter:
377+
return { derivationPaths: [], requiresForeignSignatures: false };
378+
}
379+
};
380+
381+
return nativeScripts.reduce<SignatureCheck>(
382+
(acc, script) => combineSignatureChecks(acc, processScript(script)),
383+
signatureCheck
384+
);
385+
};
386+
297387
/** Check if there are certificates that require DRep credentials and if we own them */
298388
export const getDRepCredentialKeyPaths = ({
299389
dRepKeyHash,
@@ -357,7 +447,8 @@ export const ownSignatureKeyPaths = (
357447
txBody: Cardano.TxBody,
358448
knownAddresses: GroupedAddress[],
359449
txInKeyPathMap: TxInKeyPathMap,
360-
dRepKeyHash?: Crypto.Ed25519KeyHashHex
450+
dRepKeyHash?: Crypto.Ed25519KeyHashHex,
451+
scripts?: Cardano.Script[]
361452
): AccountKeyDerivationPath[] => {
362453
// TODO: add `proposal_procedure` witnesses.
363454

@@ -368,7 +459,8 @@ export const ownSignatureKeyPaths = (
368459
...getStakeCredentialKeyPaths(knownAddresses, txBody).derivationPaths,
369460
...getDRepCredentialKeyPaths({ dRepKeyHash, txBody }).derivationPaths,
370461
...getRequiredSignersKeyPaths(knownAddresses, txBody.requiredExtraSignatures),
371-
...getVotingProcedureKeyPaths({ dRepKeyHash, groupedAddresses: knownAddresses, txBody }).derivationPaths
462+
...getVotingProcedureKeyPaths({ dRepKeyHash, groupedAddresses: knownAddresses, txBody }).derivationPaths,
463+
...getNativeScriptKeyPaths(knownAddresses, scripts).derivationPaths
372464
],
373465
isEqual
374466
);

Diff for: packages/key-management/src/util/stubSignTransaction.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ export interface StubSignTransactionProps {
1717

1818
export const stubSignTransaction = async ({
1919
txBody,
20-
context: { knownAddresses, txInKeyPathMap, dRepKeyHashHex: dRepKeyHash },
20+
context: { knownAddresses, txInKeyPathMap, dRepKeyHashHex: dRepKeyHash, scripts },
2121
signTransactionOptions: { extraSigners, additionalKeyPaths = [] } = {}
2222
}: StubSignTransactionProps): Promise<Cardano.Signatures> => {
2323
const mockSignature = Crypto.Ed25519SignatureHex(
2424
// eslint-disable-next-line max-len
2525
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
2626
);
2727
const signatureKeyPaths = uniqWith(
28-
[...ownSignatureKeyPaths(txBody, knownAddresses, txInKeyPathMap, dRepKeyHash), ...additionalKeyPaths],
28+
[...ownSignatureKeyPaths(txBody, knownAddresses, txInKeyPathMap, dRepKeyHash, scripts), ...additionalKeyPaths],
2929
deepEquals
3030
);
3131

Diff for: packages/key-management/test/util/ownSignaturePaths.test.ts

+20
Original file line numberDiff line numberDiff line change
@@ -578,4 +578,24 @@ describe('KeyManagement.util.ownSignaturePaths', () => {
578578
]);
579579
});
580580
});
581+
582+
it('includes derivation paths from native scripts when scripts are provided', () => {
583+
const txBody: Cardano.TxBody = {
584+
inputs: [],
585+
outputs: [],
586+
fee: BigInt(0)
587+
};
588+
const scripts: Cardano.Script[] = [{
589+
__type: Cardano.ScriptType.Native,
590+
kind: Cardano.NativeScriptKind.RequireSignature,
591+
keyHash: Ed25519KeyHashHex(ownStakeKeyHash)
592+
}];
593+
594+
const paths = util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, undefined, scripts);
595+
596+
expect(paths).toEqual([{
597+
index: 0,
598+
role: KeyRole.Stake
599+
}]);
600+
});
581601
});

0 commit comments

Comments
 (0)