Skip to content

Commit

Permalink
Merge remote-tracking branch 'ArkEcosystem/core/develop' into enforce…
Browse files Browse the repository at this point in the history
…-nonce-in-block-validation

* ArkEcosystem/core/develop:
  refactor: return all registered transaction types (#2878)
  refactor(crypto): signature deserialization (#2877)
  • Loading branch information
vasild committed Aug 16, 2019
2 parents 6200839 + ee80ac5 commit f22710b
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 38 deletions.
24 changes: 13 additions & 11 deletions __tests__/integration/core-api/handlers/transactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,17 +157,19 @@ describe("API 2.0 - Transactions", () => {
expect(response).toBeSuccessfulResponse();
expect(response.data.data).toBeObject();
expect(response.data.data).toEqual({
Transfer: 0,
SecondSignature: 1,
DelegateRegistration: 2,
Vote: 3,
MultiSignature: 4,
Ipfs: 5,
MultiPayment: 6,
DelegateResignation: 7,
HtlcLock: 8,
HtlcClaim: 9,
HtlcRefund: 10,
Core: {
Transfer: 0,
SecondSignature: 1,
DelegateRegistration: 2,
Vote: 3,
MultiSignature: 4,
Ipfs: 5,
MultiPayment: 6,
DelegateResignation: 7,
HtlcLock: 8,
HtlcClaim: 9,
HtlcRefund: 10,
},
});
});
});
Expand Down
208 changes: 201 additions & 7 deletions __tests__/unit/crypto/transactions/deserializer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import "jest-extended";

import ByteBuffer from "bytebuffer";
import { Enums, Utils } from "../../../../packages/crypto/src";
import { Hash } from "../../../../packages/crypto/src/crypto";
import {
MalformedTransactionBytesError,
TransactionSchemaError,
TransactionVersionError,
UnkownTransactionError,
} from "../../../../packages/crypto/src/errors";
import { Keys } from "../../../../packages/crypto/src/identities";
import { ITransaction, ITransactionData } from "../../../../packages/crypto/src/interfaces";
import { Address, Keys, PublicKey } from "../../../../packages/crypto/src/identities";
import { IKeyPair, ITransaction, ITransactionData } from "../../../../packages/crypto/src/interfaces";
import { configManager } from "../../../../packages/crypto/src/managers";
import { TransactionFactory, Utils as TransactionUtils, Verifier } from "../../../../packages/crypto/src/transactions";
import { BuilderFactory } from "../../../../packages/crypto/src/transactions/builders";
Expand Down Expand Up @@ -209,6 +210,14 @@ describe("Transaction serializer / deserializer", () => {
checkCommonFields(deserialized, multiSignatureRegistration);
});

it("should fail to verify", () => {
const transaction = TransactionFactory.fromData(multiSignatureRegistration);
configManager.getMilestone().aip11 = false;
expect(transaction.verify()).toBeFalse();
configManager.getMilestone().aip11 = true;
expect(transaction.verify()).toBeTrue();
});

it("should not deserialize a malformed signature", () => {
const transaction = TransactionFactory.fromData(multiSignatureRegistration);
transaction.serialized = transaction.serialized.slice(0, transaction.serialized.length - 2);
Expand All @@ -220,6 +229,8 @@ describe("Transaction serializer / deserializer", () => {
});

describe("ser/deserialize - ipfs", () => {
let ipfsTransaction;

const ipfsIds = [
"QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w",
"QmYSK2JyM3RyDyB52caZCTKFR3HKniEcMnNJYdk8DQ6KKB",
Expand All @@ -231,21 +242,31 @@ describe("Transaction serializer / deserializer", () => {
configManager.setFromPreset("testnet");
});

it("should ser/deserialize giving back original fields", () => {
const ipfs = BuilderFactory.ipfs()
beforeEach(() => {
ipfsTransaction = BuilderFactory.ipfs()
.fee("50000000")
.version(2)
.network(23)
.ipfsAsset(ipfsIds[0])
.sign("dummy passphrase")
.getStruct();
});

const serialized = TransactionFactory.fromData(ipfs).serialized.toString("hex");
it("should ser/deserialize giving back original fields", () => {
const serialized = TransactionFactory.fromData(ipfsTransaction).serialized.toString("hex");
const deserialized = deserializer.deserialize(serialized);

checkCommonFields(deserialized, ipfs);
checkCommonFields(deserialized, ipfsTransaction);

expect(deserialized.data.asset).toEqual(ipfs.asset);
expect(deserialized.data.asset).toEqual(ipfsTransaction.asset);
});

it("should fail to verify", () => {
const transaction = TransactionFactory.fromData(ipfsTransaction);
configManager.getMilestone().aip11 = false;
expect(transaction.verify()).toBeFalse();
configManager.getMilestone().aip11 = true;
expect(transaction.verify()).toBeTrue();
});
});

Expand All @@ -262,6 +283,19 @@ describe("Transaction serializer / deserializer", () => {

checkCommonFields(deserialized, delegateResignation);
});

it("should fail to verify", () => {
const delegateResignation = BuilderFactory.delegateResignation()
.fee("50000000")
.network(23)
.sign("dummy passphrase")
.build();

configManager.getMilestone().aip11 = false;
expect(delegateResignation.verify()).toBeFalse();
configManager.getMilestone().aip11 = true;
expect(delegateResignation.verify()).toBeTrue();
});
});

describe("ser/deserialize - multi payment", () => {
Expand All @@ -283,6 +317,21 @@ describe("Transaction serializer / deserializer", () => {

checkCommonFields(deserialized, multiPayment);
});

it("should fail to verify", () => {
const multiPayment = BuilderFactory.multiPayment()
.fee("50000000")
.network(23)
.addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "1555")
.addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "5000")
.sign("dummy passphrase")
.build();

configManager.getMilestone().aip11 = false;
expect(multiPayment.verify()).toBeFalse();
configManager.getMilestone().aip11 = true;
expect(multiPayment.verify()).toBeTrue();
});
});

describe("ser/deserialize - htlc lock", () => {
Expand Down Expand Up @@ -315,6 +364,22 @@ describe("Transaction serializer / deserializer", () => {

expect(deserialized.data.asset).toEqual(htlcLock.asset);
});

it("should fail to verify", () => {
const htlcLock = BuilderFactory.htlcLock()
.recipientId("AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff")
.amount("10000")
.fee("50000000")
.network(23)
.htlcLockAsset(htlcLockAsset)
.sign("dummy passphrase")
.build();

configManager.getMilestone().aip11 = false;
expect(htlcLock.verify()).toBeFalse();
configManager.getMilestone().aip11 = true;
expect(htlcLock.verify()).toBeTrue();
});
});

describe("ser/deserialize - htlc claim", () => {
Expand Down Expand Up @@ -342,6 +407,20 @@ describe("Transaction serializer / deserializer", () => {

expect(deserialized.data.asset).toEqual(htlcClaim.asset);
});

it("should fail to verify", () => {
const htlcClaim = BuilderFactory.htlcClaim()
.fee("0")
.network(23)
.htlcClaimAsset(htlcClaimAsset)
.sign("dummy passphrase")
.build();

configManager.getMilestone().aip11 = false;
expect(htlcClaim.verify()).toBeFalse();
configManager.getMilestone().aip11 = true;
expect(htlcClaim.verify()).toBeTrue();
});
});

describe("ser/deserialize - htlc refund", () => {
Expand All @@ -368,6 +447,20 @@ describe("Transaction serializer / deserializer", () => {

expect(deserialized.data.asset).toEqual(htlcRefund.asset);
});

it("should fail to verify", () => {
const htlcRefund = BuilderFactory.htlcRefund()
.fee("0")
.network(23)
.htlcRefundAsset(htlcRefundAsset)
.sign("dummy passphrase")
.build();

configManager.getMilestone().aip11 = false;
expect(htlcRefund.verify()).toBeFalse();
configManager.getMilestone().aip11 = true;
expect(htlcRefund.verify()).toBeTrue();
});
});

describe("deserialize - others", () => {
Expand Down Expand Up @@ -402,6 +495,107 @@ describe("Transaction serializer / deserializer", () => {
});
});

describe("deserialize Schnorr / ECDSA", () => {
const builderWith = (
hasher: (buffer: Buffer, keys: IKeyPair) => string,
hasher2?: (buffer: Buffer, keys: IKeyPair) => string,
) => {
const keys = Keys.fromPassphrase("secret");

const builder = BuilderFactory.transfer()
.senderPublicKey(keys.publicKey)
.recipientId(Address.fromPublicKey(keys.publicKey))
.amount("10000")
.fee("50000000");

const buffer = TransactionUtils.toHash(builder.data, {
excludeSignature: true,
excludeSecondSignature: true,
});

builder.data.signature = hasher(buffer, keys);

if (hasher2) {
const keys = Keys.fromPassphrase("secret 2");
const buffer = TransactionUtils.toHash(builder.data, {
excludeSecondSignature: true,
});

builder.data.secondSignature = hasher2(buffer, keys);
}

return builder;
};

it("should deserialize a V2 transaction signed with Schnorr", () => {
const builder = builderWith(Hash.signSchnorr);

let transaction: ITransaction;
expect(builder.data.version).toBe(2);
expect(() => (transaction = builder.build())).not.toThrow();
expect(transaction.verify()).toBeTrue();
});

it("should deserialize a V2 transaction signed with ECDSA", () => {
const builder = builderWith(Hash.signECDSA);

let transaction: ITransaction;
expect(builder.data.version).toBe(2);
expect(builder.data.signature).not.toHaveLength(64);
expect(() => (transaction = builder.build())).not.toThrow();
expect(transaction.verify()).toBeTrue();
});

it("should deserialize a V2 transaction when signed with Schnorr/Schnorr", () => {
const builder = builderWith(Hash.signSchnorr, Hash.signSchnorr);

let transaction: ITransaction;
expect(builder.data.version).toBe(2);
expect(() => (transaction = builder.build())).not.toThrow();

expect(transaction.verify()).toBeTrue();
expect(Verifier.verifySecondSignature(transaction.data, PublicKey.fromPassphrase("secret 2"))).toBeTrue();
expect(Verifier.verifySecondSignature(transaction.data, PublicKey.fromPassphrase("secret 3"))).toBeFalse();
});

it("should throw when V2 transaction is signed with Schnorr and ECDSA", () => {
let builder = builderWith(Hash.signSchnorr, Hash.signECDSA);
expect(builder.data.version).toBe(2);
expect(() => builder.build()).toThrow();

builder = builderWith(Hash.signECDSA, Hash.signSchnorr);
expect(builder.data.version).toBe(2);
expect(() => builder.build()).toThrow();
});

it("should throw when V2 transaction is signed with Schnorr and AIP11 not active", () => {
const builder = builderWith(Hash.signSchnorr);

configManager.getMilestone().aip11 = false;
expect(builder.data.version).toBe(2);
expect(() => builder.build()).toThrow();

configManager.getMilestone().aip11 = true;
});

it("should throw when V1 transaction is signed with Schnorr", () => {
configManager.getMilestone().aip11 = false;

const builder = builderWith(Hash.signSchnorr);
const buffer = TransactionUtils.toHash(builder.data, {
excludeSignature: true,
excludeSecondSignature: true,
});

builder.data.signature = builder.data.signature = Hash.signSchnorr(buffer, Keys.fromPassphrase("secret"));

expect(builder.data.version).toBe(1);
expect(() => builder.build()).toThrow();

configManager.getMilestone().aip11 = true;
});
});

describe("serialize - others", () => {
it("should throw if type is not supported", () => {
const transactionWrongType = BuilderFactory.transfer()
Expand Down
1 change: 1 addition & 0 deletions packages/core-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@arkecosystem/core-http-utils": "^2.5.0-next.10",
"@arkecosystem/core-interfaces": "^2.5.0-next.10",
"@arkecosystem/core-transaction-pool": "^2.5.0-next.10",
"@arkecosystem/core-transactions": "^2.5.0-next.10",
"@arkecosystem/core-utils": "^2.5.0-next.10",
"@arkecosystem/crypto": "^2.5.0-next.10",
"@arkecosystem/utils": "^0.3.0",
Expand Down
24 changes: 15 additions & 9 deletions packages/core-api/src/handlers/transactions/controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { app } from "@arkecosystem/core-container";
import { P2P, TransactionPool } from "@arkecosystem/core-interfaces";
import { Handlers } from "@arkecosystem/core-transactions";
import { Enums, Interfaces } from "@arkecosystem/crypto";
import Boom from "@hapi/boom";
import Hapi from "@hapi/hapi";
Expand Down Expand Up @@ -107,17 +108,22 @@ export class TransactionsController extends Controller {

public async types(request: Hapi.Request, h: Hapi.ResponseToolkit) {
try {
// Remove reverse mapping from TransactionType enum.
const { TransactionType } = Enums;
const data = Object.assign({}, TransactionType);
const activatedTransactionTypes: Handlers.TransactionHandler[] = await Handlers.Registry.getActivatedTransactions();
const typeGroups: Record<string | number, Record<string, number>> = {};

// tslint:disable-next-line: ban
Object.values(TransactionType)
.filter(value => typeof value === "string")
.map((type: string) => data[type])
.forEach((key: string) => delete data[key]);
for (const handler of activatedTransactionTypes) {
const constructor = handler.getConstructor();

return { data };
const { type, typeGroup, key } = constructor;
const groupName: string | number = Enums.TransactionTypeGroup[typeGroup] || typeGroup;
if (typeGroups[groupName] === undefined) {
typeGroups[groupName] = {};
}

typeGroups[groupName][key[0].toUpperCase() + key.slice(1)] = type;
}

return { data: typeGroups };
} catch (error) {
return Boom.badImplementation(error);
}
Expand Down
Loading

0 comments on commit f22710b

Please sign in to comment.