Skip to content

Commit

Permalink
refactor!: makeTxIn.address required, add NewTxIn that has no address
Browse files Browse the repository at this point in the history
refactor NewTxAlonzo to use NewTxBodyAlonzo that uses NewTxIn
  • Loading branch information
mkazlauskas committed Apr 5, 2022
1 parent a2b45db commit 83cd354
Show file tree
Hide file tree
Showing 35 changed files with 167 additions and 112 deletions.
6 changes: 3 additions & 3 deletions packages/core/src/CSL/coreToCsl/coreToCsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const value = ({ coins, assets }: Cardano.Value): Value => {
return result;
};

export const txIn = (core: Cardano.TxIn): TransactionInput =>
export const txIn = (core: Cardano.NewTxIn): TransactionInput =>
TransactionInput.new(TransactionHash.from_bytes(Buffer.from(core.txId, 'hex')), core.index);

export const txOut = (core: Cardano.TxOut): TransactionOutput =>
Expand Down Expand Up @@ -166,7 +166,7 @@ export const txAuxiliaryData = (auxiliaryData?: Cardano.AuxiliaryData): Auxiliar
return result;
};

const txInputs = (coreInputs: Cardano.TxIn[]) => {
const txInputs = (coreInputs: Cardano.NewTxIn[]) => {
const cslInputs = TransactionInputs.new();
for (const input of coreInputs) {
cslInputs.add(txIn(input));
Expand Down Expand Up @@ -207,7 +207,7 @@ export const txBody = (
collaterals,
requiredExtraSignatures,
scriptIntegrityHash
}: Cardano.TxBodyAlonzo,
}: Cardano.NewTxBodyAlonzo,
auxiliaryData?: Cardano.AuxiliaryData
): TransactionBody => {
const cslOutputs = TransactionOutputs.new();
Expand Down
11 changes: 5 additions & 6 deletions packages/core/src/CSL/cslToCore/cslToCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ export const value = (cslValue: CSL.Value): Cardano.Value => {
return result;
};

export const txIn = (input: CSL.TransactionInput, address?: Cardano.Address): Cardano.TxIn => ({
address,
export const txIn = (input: CSL.TransactionInput): Cardano.NewTxIn => ({
index: input.index(),
txId: Cardano.TransactionId.fromHexBlob(util.bytesToHex(input.transaction_id().to_bytes()))
});
Expand All @@ -77,10 +76,10 @@ export const txOutputs = (outputs: CSL.TransactionOutputs): Cardano.TxOut[] => {
return result;
};

export const txInputs = (inputs: CSL.TransactionInputs, address?: Cardano.Address): Cardano.TxIn[] => {
const result: Cardano.TxIn[] = [];
export const txInputs = (inputs: CSL.TransactionInputs): Cardano.NewTxIn[] => {
const result: Cardano.NewTxIn[] = [];
for (let i = 0; i < inputs.len(); i++) {
result.push(txIn(inputs.get(i), address));
result.push(txIn(inputs.get(i)));
}
return result;
};
Expand Down Expand Up @@ -118,7 +117,7 @@ export const txMint = (assets?: CSL.Mint): Cardano.TokenMap | undefined => {
return assetMap;
};

export const txBody = (body: CSL.TransactionBody): Cardano.TxBodyAlonzo => {
export const txBody = (body: CSL.TransactionBody): Cardano.NewTxBodyAlonzo => {
const cslScriptDataHash = body.script_data_hash();
const cslCollaterals = body.collateral();

Expand Down
18 changes: 13 additions & 5 deletions packages/core/src/Cardano/types/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export interface TxBodyAlonzo {
requiredExtraSignatures?: Cardano.Ed25519KeyHash[];
}

export interface NewTxBodyAlonzo extends Omit<TxBodyAlonzo, 'inputs' | 'collaterals'> {
inputs: Cardano.NewTxIn[];
collaterals?: Cardano.NewTxIn[];
}

/**
* Implicit coin quantities used in the transaction
*/
Expand Down Expand Up @@ -81,15 +86,18 @@ export type Witness = Omit<Partial<BlockBodyAlonzo['witness']>, 'redeemers' | 's
redeemers?: Redeemer[];
signatures: Signatures;
};
export interface TxAlonzo {

export interface NewTxAlonzo<TBody extends NewTxBodyAlonzo = NewTxBodyAlonzo> {
id: TransactionId;
body: TBody;
witness: Witness;
auxiliaryData?: AuxiliaryData;
}

export interface TxAlonzo extends NewTxAlonzo<TxBodyAlonzo> {
index: number;
blockHeader: PartialBlockHeader;
body: TxBodyAlonzo;
implicitCoin: ImplicitCoin;
txSize: number;
witness: Witness;
auxiliaryData?: AuxiliaryData;
}

export type NewTxAlonzo = Omit<TxAlonzo, 'blockHeader' | 'implicitCoin' | 'txSize' | 'index'>;
11 changes: 5 additions & 6 deletions packages/core/src/Cardano/types/Utxo.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Address, Hash32ByteBase16, TransactionId } from '.';
import { Value } from './Value';

export interface TxIn {
export interface NewTxIn {
txId: TransactionId;
index: number;
/**
* Might or might not be present based on source.
* Not present in serialized tx.
*/
address?: Address;
}

export interface TxIn extends NewTxIn {
address: Address;
}

export interface TxOut {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/CSL/coreToCsl.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable max-len */
import { Asset, CSL, Cardano, SerializationFailure, coreToCsl } from '../../src';
import { BigNum } from '@emurgo/cardano-serialization-lib-nodejs';
import { signature, tx, txBody, txIn, txOut, valueCoinOnly, valueWithAssets, vkey } from './testData';
import { signature, tx, txBody, txIn, txInWithAddress, txOut, valueCoinOnly, valueWithAssets, vkey } from './testData';

const txOutByron = {
...txOut,
Expand All @@ -19,7 +19,7 @@ describe('coreToCsl', () => {
expect(coreToCsl.txOut(txOutByron)).toBeInstanceOf(CSL.TransactionOutput);
});
it('utxo', () => {
expect(coreToCsl.utxo([[txIn, txOut]])[0]).toBeInstanceOf(CSL.TransactionUnspentOutput);
expect(coreToCsl.utxo([[txInWithAddress, txOut]])[0]).toBeInstanceOf(CSL.TransactionUnspentOutput);
});
describe('value', () => {
it('coin only', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/CSL/testData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const txOut: Cardano.TxOut = {
value: valueWithAssets
};

export const txBody: Cardano.TxBodyAlonzo = {
export const txBody: Cardano.NewTxBodyAlonzo = {
certificates: [
{
__typename: Cardano.CertificateType.PoolRetirement,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Cardano } from '../../../src';
import { TxSubmission } from '@cardano-ogmios/client';
import { TxSubmissionErrors } from '../../../dist/Cardano';
import { TxSubmissionErrors } from '../../../src/Cardano';

describe('Cardano/types/TxSuubmissionErrors', () => {
test('TxSubmissionError can be narrowed down with "instanceof"', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ describe('Cardano.util.computeImplicitCoin', () => {
},
{
__typename: Cardano.CertificateType.StakeDelegation,
epoch: 500,
poolId: Cardano.PoolId('pool1zuevzm3xlrhmwjw87ec38mzs02tlkwec9wxpgafcaykmwg7efhh'),
stakeKeyHash
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/Cardano/util/metadatum.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Cardano } from '@cardano-sdk/core';
import { Cardano } from '../../../src';

describe('Cardano.util.metadatum', () => {
describe('asMetadatumMap', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/wallet/src/KeyManagement/InMemoryKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class InMemoryKeyAgent extends KeyAgentBase {
this.#getPassword = getPassword;
}

async signBlob({ index, type }: AccountKeyDerivationPath, blob: Cardano.util.HexBlob): Promise<SignBlobResult> {
async signBlob({ index, role: type }: AccountKeyDerivationPath, blob: Cardano.util.HexBlob): Promise<SignBlobResult> {
const rootPrivateKey = await this.#decryptRootPrivateKey();
const accountKey = deriveAccountPrivateKey(rootPrivateKey, this.accountIndex);
const signingKey = accountKey.derive(type).derive(index).to_raw_key();
Expand Down
27 changes: 19 additions & 8 deletions packages/wallet/src/KeyManagement/KeyAgentBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import {
AccountKeyDerivationPath,
GroupedAddress,
KeyAgent,
KeyType,
KeyRole,
SerializableKeyAgentData,
SignBlobResult
SignBlobResult,
SignTransactionOptions
} from './types';
import { CSL, Cardano, util } from '@cardano-sdk/core';
import { STAKE_KEY_DERIVATION_PATH, ownSignatureKeyPaths } from './util';
import { TxInternals } from '../Transaction';
import { uniqBy } from 'lodash-es';

export abstract class KeyAgentBase implements KeyAgent {
readonly #serializableData: SerializableKeyAgentData;

get knownAddresses(): GroupedAddress[] {
return this.#serializableData.knownAddresses;
}
Expand Down Expand Up @@ -41,7 +44,7 @@ export abstract class KeyAgentBase implements KeyAgent {
async deriveAddress({ index, type }: AccountAddressDerivationPath): Promise<GroupedAddress> {
const derivedPublicPaymentKey = await this.deriveCslPublicKey({
index,
type: type as unknown as KeyType
role: type as unknown as KeyRole
});

// Possible optimization: memoize/cache stakeKeyCredential, because it's always the same
Expand All @@ -67,14 +70,22 @@ export abstract class KeyAgentBase implements KeyAgent {
return groupedAddress;
}

async signTransaction({ body, hash }: TxInternals): Promise<Cardano.Signatures> {
async signTransaction(
{ body, hash }: TxInternals,
{ inputAddressResolver, additionalKeyPaths = [] }: SignTransactionOptions
): Promise<Cardano.Signatures> {
// Possible optimization is casting strings to OpaqueString types directly and skipping validation
const blob = Cardano.util.HexBlob(hash.toString());
const derivationPaths = ownSignatureKeyPaths(body, this.knownAddresses);
const derivationPaths = ownSignatureKeyPaths(body, this.knownAddresses, inputAddressResolver);
const keyPaths = uniqBy([...derivationPaths, ...additionalKeyPaths], ({ role, index }) => `${role}.${index}`);
// TODO:
// if (keyPaths.length === 0) {
// throw new ProofGenerationError();
// }
return new Map<Cardano.Ed25519PublicKey, Cardano.Ed25519Signature>(
await Promise.all(
derivationPaths.map(async ({ role, index }) => {
const { publicKey, signature } = await this.signBlob({ index, type: role }, blob);
keyPaths.map(async ({ role, index }) => {
const { publicKey, signature } = await this.signBlob({ index, role }, blob);
return [publicKey, signature] as const;
})
)
Expand All @@ -86,7 +97,7 @@ export abstract class KeyAgentBase implements KeyAgent {
return Cardano.Ed25519PublicKey.fromHexBlob(util.bytesToHex(cslPublicKey.as_bytes()));
}

protected async deriveCslPublicKey({ index, type }: AccountKeyDerivationPath): Promise<CSL.PublicKey> {
protected async deriveCslPublicKey({ index, role: type }: AccountKeyDerivationPath): Promise<CSL.PublicKey> {
const accountPublicKeyBytes = Buffer.from(this.extendedAccountPublicKey, 'hex');
const accountPublicKey = CSL.Bip32PublicKey.from_bytes(accountPublicKeyBytes);
return accountPublicKey.derive(type).derive(index).to_raw_key();
Expand Down
4 changes: 2 additions & 2 deletions packages/wallet/src/KeyManagement/cip8/cip30signData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Cardano, parseCslAddress, util } from '@cardano-sdk/core';
import { Cip30DataSignature } from '@cardano-sdk/cip30';
import { CoseLabel } from './util';
import { CustomError } from 'ts-custom-error';
import { KeyAgent, KeyType } from '../types';
import { KeyAgent, KeyRole } from '../types';
import { STAKE_KEY_DERIVATION_PATH } from '../util';

export interface Cip30SignDataRequest {
Expand Down Expand Up @@ -58,7 +58,7 @@ const getDerivationPath = (signWith: Cardano.Address | Cardano.RewardAccount, ke
if (!knownAddress) {
throw new Cip30DataSignError(Cip30DataSignErrorCode.ProofGeneration, 'Unknown address');
}
return { index: knownAddress.index, type: knownAddress.type as number as KeyType };
return { index: knownAddress.index, role: knownAddress.type as number as KeyRole };
};

const createSigStructureHeaders = (addressBytes: Uint8Array) => {
Expand Down
1 change: 1 addition & 0 deletions packages/wallet/src/KeyManagement/restoreKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { InMemoryKeyAgent } from './InMemoryKeyAgent';
import { InvalidSerializableDataError } from './errors';
import { LedgerKeyAgent } from './LedgerKeyAgent';

// TODO: use this type as 2nd parameter of restoreKeyAgent
export interface RestoreInMemoryKeyAgentProps {
/**
* Required for InMemoryKeyAgent
Expand Down
13 changes: 10 additions & 3 deletions packages/wallet/src/KeyManagement/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export enum KeyAgentType {
Ledger = 'Ledger'
}

export enum KeyType {
export enum KeyRole {
External = 0,
Internal = 1,
Stake = 2
}

export interface AccountKeyDerivationPath {
type: KeyType;
role: KeyRole;
index: number;
}
/** Internal = change address & External = receipt address */
Expand Down Expand Up @@ -89,6 +89,13 @@ export type TransportType = TransportWebHID | TransportNodeHid;
*/
export type GetPassword = (noCache?: true) => Promise<Uint8Array>;

export type InputAddressResolver = (txIn: Cardano.NewTxIn) => Cardano.Address | null;

export interface SignTransactionOptions {
inputAddressResolver: InputAddressResolver;
additionalKeyPaths?: AccountKeyDerivationPath[];
}

export interface KeyAgent {
get networkId(): Cardano.NetworkId;
get accountIndex(): number;
Expand All @@ -102,7 +109,7 @@ export interface KeyAgent {
/**
* @throws AuthenticationError
*/
signTransaction(txInternals: TxInternals): Promise<Cardano.Signatures>;
signTransaction(txInternals: TxInternals, options: SignTransactionOptions): Promise<Cardano.Signatures>;
/**
* @throws AuthenticationError
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/wallet/src/KeyManagement/util/key.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { AccountKeyDerivationPath, KeyType } from '../types';
import { AccountKeyDerivationPath, KeyRole } from '../types';
import { CSL } from '@cardano-sdk/core';

export const harden = (num: number): number => 0x80_00_00_00 + num;

export const STAKE_KEY_DERIVATION_PATH: AccountKeyDerivationPath = {
index: 0,
type: KeyType.Stake
role: KeyRole.Stake
};

export const deriveAccountPrivateKey = (rootPrivateKey: CSL.Bip32PrivateKey, accountIndex: number) =>
Expand Down
26 changes: 14 additions & 12 deletions packages/wallet/src/KeyManagement/util/ownSignatureKeyPaths.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { AccountKeyDerivationPath, GroupedAddress, InputAddressResolver, KeyRole } from '../types';
import { Cardano, util } from '@cardano-sdk/core';
import { GroupedAddress, KeyType } from '../types';
import { uniq } from 'lodash-es';

export interface PartialDerivationPath {
role: KeyType;
index: number;
}

/**
* Assumes that a single staking key is used for all addresses (index=0)
*
* @returns {PartialDerivationPath[]} derivation paths for keys to sign transaction with
* @returns {AccountKeyDerivationPath[]} derivation paths for keys to sign transaction with
*/
export const ownSignatureKeyPaths = (
txBody: Cardano.TxBodyAlonzo,
knownAddresses: GroupedAddress[]
): PartialDerivationPath[] => {
txBody: Cardano.NewTxBodyAlonzo,
knownAddresses: GroupedAddress[],
resolveInputAddress: InputAddressResolver
): AccountKeyDerivationPath[] => {
const paymentKeyPaths = uniq(
txBody.inputs.map((input) => knownAddresses.find(({ address }) => address === input.address)).filter(util.isNotNil)
txBody.inputs
.map((input) => {
const ownAddress = resolveInputAddress(input);
if (!ownAddress) return null;
return knownAddresses.find(({ address }) => address === ownAddress);
})
.filter(util.isNotNil)
).map(({ type, index }) => ({ index, role: Number(type) }));
const isStakingKeySignatureRequired = txBody.certificates?.length;
if (isStakingKeySignatureRequired) {
return [...paymentKeyPaths, { index: 0, role: KeyType.Stake }];
return [...paymentKeyPaths, { index: 0, role: KeyRole.Stake }];
}
return paymentKeyPaths;
};
9 changes: 5 additions & 4 deletions packages/wallet/src/KeyManagement/util/stubSignTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Cardano } from '@cardano-sdk/core';
import { GroupedAddress } from '../types';
import { GroupedAddress, InputAddressResolver } from '../types';
import { ownSignatureKeyPaths } from './ownSignatureKeyPaths';

const randomHexChar = () => Math.floor(Math.random() * 16).toString(16);
const randomPublicKey = () => Cardano.Ed25519PublicKey(Array.from({ length: 64 }).map(randomHexChar).join(''));

export const stubSignTransaction = (
txBody: Cardano.TxBodyAlonzo,
knownAddresses: GroupedAddress[]
txBody: Cardano.NewTxBodyAlonzo,
knownAddresses: GroupedAddress[],
inputAddressResolver: InputAddressResolver
): Cardano.Signatures =>
new Map(
ownSignatureKeyPaths(txBody, knownAddresses).map(() => [
ownSignatureKeyPaths(txBody, knownAddresses, inputAddressResolver).map(() => [
randomPublicKey(),
Cardano.Ed25519Signature(
// eslint-disable-next-line max-len
Expand Down
Loading

0 comments on commit 83cd354

Please sign in to comment.