Skip to content

Commit

Permalink
fix: Added nativescript checks for ownSignatureKeyPaths
Browse files Browse the repository at this point in the history
  • Loading branch information
Riley-Kilgore committed Feb 24, 2025
1 parent d0e939e commit 85befa6
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 17 deletions.
4 changes: 2 additions & 2 deletions packages/key-management/src/InMemoryKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {

async signTransaction(
txBody: Serialization.TransactionBody,
{ txInKeyPathMap, knownAddresses }: SignTransactionContext,
{ txInKeyPathMap, knownAddresses, scripts }: SignTransactionContext,
{ additionalKeyPaths = [] }: SignTransactionOptions = {}
): Promise<Cardano.Signatures> {
// Possible optimization is casting strings to OpaqueString types directly and skipping validation
Expand All @@ -143,7 +143,7 @@ export class InMemoryKeyAgent extends KeyAgentBase implements KeyAgent {
const dRepKeyHash = (
await Crypto.Ed25519PublicKey.fromHex(await this.derivePublicKey(DREP_KEY_DERIVATION_PATH)).hash()
).hex();
const derivationPaths = ownSignatureKeyPaths(body, knownAddresses, txInKeyPathMap, dRepKeyHash);
const derivationPaths = ownSignatureKeyPaths(body, knownAddresses, txInKeyPathMap, dRepKeyHash, scripts);
const keyPaths = uniqBy([...derivationPaths, ...additionalKeyPaths], ({ role, index }) => `${role}.${index}`);
// TODO:
// if (keyPaths.length === 0) {
Expand Down
1 change: 1 addition & 0 deletions packages/key-management/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export interface SignTransactionContext {
handleResolutions?: HandleResolution[];
dRepKeyHashHex?: Crypto.Ed25519KeyHashHex;
sender?: MessageSender;
scripts?: Cardano.Script[];
}

export type SignDataContext = Cip8SignDataContext & { sender?: MessageSender };
Expand Down
81 changes: 79 additions & 2 deletions packages/key-management/src/util/ownSignatureKeyPaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,81 @@ const getRequiredSignersKeyPaths = (
return paths;
};

const checkStakeCredential = (address: GroupedAddress, keyHash: Crypto.Ed25519KeyHashHex): SignatureCheck =>
address.stakeKeyDerivationPath &&
Cardano.RewardAccount.toHash(address.rewardAccount) === Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(keyHash)
? { derivationPaths: [address.stakeKeyDerivationPath], requiresForeignSignatures: false }
: { derivationPaths: [], requiresForeignSignatures: true };

const checkPaymentCredential = (address: GroupedAddress, keyHash: Crypto.Ed25519KeyHashHex): SignatureCheck => {
const paymentCredential = Cardano.Address.fromBech32(address.address)?.asBase()?.getPaymentCredential();
return paymentCredential?.type === Cardano.CredentialType.KeyHash &&
paymentCredential.hash === Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(keyHash)
? {
derivationPaths: [{ index: address.index, role: Number(address.type) }],
requiresForeignSignatures: false
}
: { derivationPaths: [], requiresForeignSignatures: true };
};

const combineSignatureChecks = (a: SignatureCheck, b: SignatureCheck): SignatureCheck => ({
derivationPaths: [...a.derivationPaths, ...b.derivationPaths],
requiresForeignSignatures: a.requiresForeignSignatures || b.requiresForeignSignatures
});

const processSignatureScript = (
script: Cardano.RequireSignatureScript,
groupedAddresses: GroupedAddress[]
): SignatureCheck => {
let signatureCheck: SignatureCheck = { derivationPaths: [], requiresForeignSignatures: false };

for (const address of groupedAddresses) {
if (address.stakeKeyDerivationPath) {
signatureCheck = checkStakeCredential(address, script.keyHash);
}
signatureCheck = combineSignatureChecks(signatureCheck, checkPaymentCredential(address, script.keyHash));
}

return signatureCheck;
};

const getNativeScriptKeyPaths = (
groupedAddresses: GroupedAddress[],
nativeScripts?: Cardano.Script[]
): SignatureCheck => {
const signatureCheck: SignatureCheck = { derivationPaths: [], requiresForeignSignatures: false };
if (!nativeScripts?.length) return signatureCheck;

const processScript = (script: Cardano.Script): SignatureCheck => {
if (!Cardano.isNativeScript(script)) {
return { derivationPaths: [], requiresForeignSignatures: false };
}

switch (script.kind) {
case Cardano.NativeScriptKind.RequireSignature: {
return processSignatureScript(script as Cardano.RequireSignatureScript, groupedAddresses);
}
case Cardano.NativeScriptKind.RequireAllOf:
case Cardano.NativeScriptKind.RequireAnyOf:
case Cardano.NativeScriptKind.RequireNOf: {
const scriptWithScripts = script as Cardano.RequireAllOfScript | Cardano.RequireAnyOfScript;
return scriptWithScripts.scripts.reduce<SignatureCheck>(
(acc, subScript) => combineSignatureChecks(acc, processScript(subScript)),
{ derivationPaths: [], requiresForeignSignatures: false }
);
}
case Cardano.NativeScriptKind.RequireTimeBefore:
case Cardano.NativeScriptKind.RequireTimeAfter:
return { derivationPaths: [], requiresForeignSignatures: false };
}
};

return nativeScripts.reduce<SignatureCheck>(
(acc, script) => combineSignatureChecks(acc, processScript(script)),
signatureCheck
);
};

/** Check if there are certificates that require DRep credentials and if we own them */
export const getDRepCredentialKeyPaths = ({
dRepKeyHash,
Expand Down Expand Up @@ -357,7 +432,8 @@ export const ownSignatureKeyPaths = (
txBody: Cardano.TxBody,
knownAddresses: GroupedAddress[],
txInKeyPathMap: TxInKeyPathMap,
dRepKeyHash?: Crypto.Ed25519KeyHashHex
dRepKeyHash?: Crypto.Ed25519KeyHashHex,
scripts?: Cardano.Script[]
): AccountKeyDerivationPath[] => {
// TODO: add `proposal_procedure` witnesses.

Expand All @@ -368,7 +444,8 @@ export const ownSignatureKeyPaths = (
...getStakeCredentialKeyPaths(knownAddresses, txBody).derivationPaths,
...getDRepCredentialKeyPaths({ dRepKeyHash, txBody }).derivationPaths,
...getRequiredSignersKeyPaths(knownAddresses, txBody.requiredExtraSignatures),
...getVotingProcedureKeyPaths({ dRepKeyHash, groupedAddresses: knownAddresses, txBody }).derivationPaths
...getVotingProcedureKeyPaths({ dRepKeyHash, groupedAddresses: knownAddresses, txBody }).derivationPaths,
...getNativeScriptKeyPaths(knownAddresses, scripts).derivationPaths
],
isEqual
);
Expand Down
4 changes: 2 additions & 2 deletions packages/key-management/src/util/stubSignTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export interface StubSignTransactionProps {

export const stubSignTransaction = async ({
txBody,
context: { knownAddresses, txInKeyPathMap, dRepKeyHashHex: dRepKeyHash },
context: { knownAddresses, txInKeyPathMap, dRepKeyHashHex: dRepKeyHash, scripts },
signTransactionOptions: { extraSigners, additionalKeyPaths = [] } = {}
}: StubSignTransactionProps): Promise<Cardano.Signatures> => {
const mockSignature = Crypto.Ed25519SignatureHex(
// eslint-disable-next-line max-len
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
);
const signatureKeyPaths = uniqWith(
[...ownSignatureKeyPaths(txBody, knownAddresses, txInKeyPathMap, dRepKeyHash), ...additionalKeyPaths],
[...ownSignatureKeyPaths(txBody, knownAddresses, txInKeyPathMap, dRepKeyHash, scripts), ...additionalKeyPaths],
deepEquals
);

Expand Down
3 changes: 2 additions & 1 deletion packages/key-management/test/InMemoryKeyAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ describe('InMemoryKeyAgent', () => {
knownAddresses,
txInKeyPathMap
});
expect(ownSignatureKeyPaths).toBeCalledWith(body.toCore(), knownAddresses, txInKeyPathMap, expect.anything());
const expectedArgs = [body.toCore(), knownAddresses, txInKeyPathMap, expect.anything(), undefined] as const;
expect(ownSignatureKeyPaths).toBeCalledWith(...expectedArgs);
expect(witnessSet.size).toBe(2);
expect(typeof [...witnessSet.values()][0]).toBe('string');
});
Expand Down
74 changes: 65 additions & 9 deletions packages/key-management/test/util/ownSignaturePaths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,45 @@ const toStakeCredential = (stakeKeyHash: Crypto.Hash28ByteBase16): Cardano.Crede
type: Cardano.CredentialType.KeyHash
});

const createBaseGroupedAddress = (
address: Cardano.PaymentAddress,
rewardAccount: Cardano.RewardAccount,
type: AddressType,
index: number
) => ({
address,
index,
rewardAccount,
type
});

const createGroupedAddress = (
address: Cardano.PaymentAddress,
rewardAccount: Cardano.RewardAccount,
type: AddressType,
index: number,
stakeKeyDerivationPath: AccountKeyDerivationPath
// eslint-disable-next-line max-params
): GroupedAddress =>
) =>
({
address,
index,
rewardAccount,
stakeKeyDerivationPath,
type
...createBaseGroupedAddress(address, rewardAccount, type, index),
stakeKeyDerivationPath
} as GroupedAddress);

describe('KeyManagement.util.ownSignaturePaths', () => {
const ownRewardAccount = Cardano.RewardAccount('stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27');
const createTestAddresses = () => {
const otherRewardAccount = Cardano.RewardAccount('stake_test1uqrw9tjymlm8wrwq7jk68n6v7fs9qz8z0tkdkve26dylmfc2ux2hj');
const ownRewardAccount = Cardano.RewardAccount('stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27');
const address1 = Cardano.PaymentAddress(
'addr_test1qra788mu4sg8kwd93ns9nfdh3k4ufxwg4xhz2r3n064tzfgxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flkns6cy45x'
);
const address2 = Cardano.PaymentAddress(
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp'
);

return { address1, address2, otherRewardAccount, ownRewardAccount };
};

describe('KeyManagement.util.ownSignaturePaths', () => {
const { address1, address2, ownRewardAccount, otherRewardAccount } = createTestAddresses();
const ownStakeKeyHash = Cardano.RewardAccount.toHash(ownRewardAccount);
const ownStakeCredential = {
hash: ownStakeKeyHash,
Expand Down Expand Up @@ -578,4 +591,47 @@ describe('KeyManagement.util.ownSignaturePaths', () => {
]);
});
});

describe('Native scripts', () => {
it('includes derivation paths from native scripts when scripts are provided', async () => {
const txBody: Cardano.TxBody = {
fee: BigInt(0),
inputs: [{}, {}, {}] as Cardano.TxIn[],
outputs: []
};

const scripts: Cardano.Script[] = [
{
__type: Cardano.ScriptType.Native,
keyHash: Ed25519KeyHashHex(ownStakeKeyHash),
kind: Cardano.NativeScriptKind.RequireSignature
}
];

expect(util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, undefined, scripts)).toEqual([
{
index: 0,
role: KeyRole.Stake
}
]);
});

it('does not include derivation paths from native scripts with foreign key hashes', async () => {
const txBody: Cardano.TxBody = {
fee: BigInt(0),
inputs: [{}, {}, {}] as Cardano.TxIn[],
outputs: []
};

const scripts: Cardano.Script[] = [
{
__type: Cardano.ScriptType.Native,
keyHash: Ed25519KeyHashHex(otherStakeKeyHash),
kind: Cardano.NativeScriptKind.RequireSignature
}
];

expect(util.ownSignatureKeyPaths(txBody, [knownAddress1], {}, undefined, scripts)).toEqual([]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('KeyManagement.util.stubSignTransaction', () => {
})
).size
).toBe(2);
expect(ownSignatureKeyPaths).toBeCalledWith(txBody, knownAddresses, txInKeyPathMap, dRepKeyHash);
const expectedArgs = [txBody, knownAddresses, txInKeyPathMap, dRepKeyHash, undefined] as const;
expect(ownSignatureKeyPaths).toBeCalledWith(...expectedArgs);
});
});

0 comments on commit 85befa6

Please sign in to comment.