Skip to content

Commit

Permalink
feat(fabric-socketio-connector): sending transactions signed on the c…
Browse files Browse the repository at this point in the history
…lient-side

- Add functions to support entire process of sending transaction to the ledger without sharing
  any private key with the connector.
- Add test cases to check the new functionalities.
- Adjust asset-trade sample app to use new logic that doesn't send the private key to the connector.
- Move fabric signing utils to a separate file.
- Add fabric protobuf serializers and deserializers, so they can be send back and forth
  between connector and BLP without loosing type information.
- Add an option to FabricTestLedgerV1 to allow attaching to already running ledger container.
  This can speed up test development/troubleshoot process.
- Improve fabric-all-in-one ledger healthcheck, so that it waits for basic chaincode intialization.
  This is not strictly needed by current tests, but may be expected by future tests.
- Refactor duplicated logic in fabric-connector.
- Fix fabric-socketio module entry points (minor fix).

Closes: 2070

Depends on: 2047

Signed-off-by: Michal Bajer <michal.bajer@fujitsu.com>
  • Loading branch information
outSH authored and petermetz committed Nov 24, 2022
1 parent 8867830 commit 0b34ca3
Show file tree
Hide file tree
Showing 12 changed files with 1,317 additions and 389 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -451,22 +451,17 @@ export class BusinessLogicAssetTrade extends BusinessLogicBase {
transactionData,
);

// NOTE: Convert properties to binary.
// If you do not convert the following, you will get an error.
result.data["signedCommitProposal"].signature = Buffer.from(
result.data["signedCommitProposal"].signature,
);
result.data["signedCommitProposal"].proposal_bytes = Buffer.from(
result.data["signedCommitProposal"].proposal_bytes,
);

// Set Parameter
//logger.debug('secondTransaction data : ' + JSON.stringify(result.data));
const contract = { channelName: "mychannel" };
const method = { type: "sendSignedTransaction" };
const args = { args: [result.data] };
// Call sendSignedTransactionV2
const contract = {
channelName: config.assetTradeInfo.fabric.channelName,
};
const method = { type: "function", command: "sendSignedTransactionV2" };
const args = {
args: result.signedTxArgs,
};

// Run Verifier (Fabric)
logger.debug("Sending fabric.sendSignedTransactionV2");
verifierFabric
.sendAsyncRequest(contract, method, args)
.then(() => {
Expand Down
105 changes: 105 additions & 0 deletions examples/cactus-example-discounted-asset-trade/sign-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2022 Hyperledger Cactus Contributors
* SPDX-License-Identifier: Apache-2.0
*
* Util tools used for cryptography related to hyperledger fabric (e.g. signing proposals)
*/

const hash = require("fabric-client/lib/hash");
import jsrsa from "jsrsasign";
import elliptic from "elliptic";

const ellipticCurves = elliptic.curves as any;

/**
* This function comes from `CryptoSuite_ECDSA_AES.js` and will be part of the
* stand alone fabric-sig package in future.
*
*/
const ordersForCurve: Record<string, any> = {
secp256r1: {
halfOrder: ellipticCurves.p256.n.shrn(1),
order: ellipticCurves.p256.n,
},
secp384r1: {
halfOrder: ellipticCurves.p384.n.shrn(1),
order: ellipticCurves.p384.n,
},
};

/**
* This function comes from `CryptoSuite_ECDSA_AES.js` and will be part of the
* stand alone fabric-sig package in future.
*
* @param sig EC signature
* @param curveParams EC key params.
* @returns Signature
*/
function preventMalleability(sig: any, curveParams: { name: string }) {
const halfOrder = ordersForCurve[curveParams.name].halfOrder;
if (!halfOrder) {
throw new Error(
'Can not find the half order needed to calculate "s" value for immalleable signatures. Unsupported curve name: ' +
curveParams.name,
);
}

// in order to guarantee 's' falls in the lower range of the order, as explained in the above link,
// first see if 's' is larger than half of the order, if so, it needs to be specially treated
if (sig.s.cmp(halfOrder) === 1) {
// module 'bn.js', file lib/bn.js, method cmp()
// convert from BigInteger used by jsrsasign Key objects and bn.js used by elliptic Signature objects
const bigNum = ordersForCurve[curveParams.name].order;
sig.s = bigNum.sub(sig.s);
}

return sig;
}

/**
* Internal function to sign input buffer with private key.
*
* @param privateKey private key in PEM format.
* @param proposalBytes Buffer of the proposal to sign.
* @param algorithm Hash function algorithm
* @param keySize Key length
* @returns
*/
function sign(
privateKey: string,
proposalBytes: Buffer,
algorithm: string,
keySize: number,
) {
const hashAlgorithm = algorithm.toUpperCase();
const hashFunction = hash[`${hashAlgorithm}_${keySize}`];
const ecdsaCurve = ellipticCurves[`p${keySize}`];
const ecdsa = new elliptic.ec(ecdsaCurve);
const key = jsrsa.KEYUTIL.getKey(privateKey) as any;

const signKey = ecdsa.keyFromPrivate(key.prvKeyHex, "hex");
const digest = hashFunction(proposalBytes);

let sig = ecdsa.sign(Buffer.from(digest, "hex"), signKey);
sig = preventMalleability(sig, key.ecparams);

return Buffer.from(sig.toDER());
}

/**
* Sign proposal of endorsment / transaction with private key.
* Can be used to call low-level fabric sdk functions.
*
* @param proposalBytes Buffer of the proposal to sign.
* @param paramPrivateKeyPem Private key in PEM format.
* @returns Signed proposal.
*/
export function signProposal(
proposalBytes: Buffer,
paramPrivateKeyPem: string,
) {
return {
signature: sign(paramPrivateKeyPem, proposalBytes, "sha2", 256),
proposal_bytes: proposalBytes,
};
}
141 changes: 100 additions & 41 deletions examples/cactus-example-discounted-asset-trade/transaction-fabric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import { ConfigUtil } from "@hyperledger/cactus-cmd-socketio-server";
import { Verifier } from "@hyperledger/cactus-verifier-client";
import { signProposal } from "./sign-utils";

import { FileSystemWallet } from "fabric-network";

Expand All @@ -26,21 +27,11 @@ const moduleName = "TransactionFabric";
const logger = getLogger(`${moduleName}`);
logger.level = config.logLevel;

interface SendSyncRequestResult {
data: {
signedCommitProposal: {
signature: string | Buffer;
proposal_bytes: string | Buffer;
};
};
txId: string;
}

export function makeSignedProposal<T>(
ccFncName: string,
ccArgs: string[],
verifierFabric: Verifier<T>,
): Promise<SendSyncRequestResult> {
): Promise<{ signedTxArgs: unknown; txId: string }> {
return new Promise(async (resolve, reject) => {
try {
/*
Expand Down Expand Up @@ -70,42 +61,110 @@ export function makeSignedProposal<T>(
privateKeyPem = (submitterIdentity as any).privateKey;
}

const contract = {
if (!certPem || !privateKeyPem) {
throw new Error(
"Could not read certPem or privateKeyPem from BLP fabric wallet.",
);
}

// Get unsigned proposal
const contractUnsignedProp = {
channelName: config.assetTradeInfo.fabric.channelName,
};
const method = { type: "function", command: "sendSignedProposal" };
const argsParam: {
const methodUnsignedProp = {
type: "function",
command: "generateUnsignedProposal",
};
const argsUnsignedProp = {
args: {
transactionProposalReq: Record<string, unknown>;
certPem: undefined;
privateKeyPem: undefined;
};
} = {
transactionProposalReq,
certPem,
},
};

logger.debug("Sending fabric.generateUnsignedProposal");
const responseUnsignedProp = await verifierFabric.sendSyncRequest(
contractUnsignedProp,
methodUnsignedProp,
argsUnsignedProp,
);
const proposalBuffer = Buffer.from(
responseUnsignedProp.data.proposalBuffer,
);
const proposal = responseUnsignedProp.data.proposal;
const txId = responseUnsignedProp.data.txId;

// Prepare signed proposal
const signedProposal = signProposal(proposalBuffer, privateKeyPem);

// Call sendSignedProposalV2
const contractSignedProposal = {
channelName: config.assetTradeInfo.fabric.channelName,
};
const methodSignedProposal = {
type: "function",
command: "sendSignedProposalV2",
};
const argsSignedProposal = {
args: {
transactionProposalReq: transactionProposalReq,
certPem: certPem,
privateKeyPem: privateKeyPem,
signedProposal,
},
};
verifierFabric
.sendSyncRequest(contract, method, argsParam)
.then((resp) => {
logger.debug(`Successfully build endorse and commit`);

const args = {
signedCommitProposal: resp.data["signedCommitProposal"],
commitReq: resp.data["commitReq"],
};
const result: SendSyncRequestResult = {
data: args,
txId: resp.data["txId"],
};
return resolve(result);
})
.catch((err) => {
logger.error(`##makeSignedProposal: err: ${err}`);
reject(err);
});

logger.debug("Sending fabric.sendSignedProposalV2");
const responseSignedEndorse = await verifierFabric.sendSyncRequest(
contractSignedProposal,
methodSignedProposal,
argsSignedProposal,
);

if (!responseSignedEndorse.data.endorsmentStatus) {
throw new Error("Fabric TX endorsment was not OK.");
}
const proposalResponses = responseSignedEndorse.data.proposalResponses;

// Get unsigned commit (transaction) proposal
const contractUnsignedTx = {
channelName: config.assetTradeInfo.fabric.channelName,
};
const methodUnsignedTx = {
type: "function",
command: "generateUnsignedTransaction",
};
const argsUnsignedTx = {
args: {
proposal: proposal,
proposalResponses: proposalResponses,
},
};

logger.debug("Sending fabric.generateUnsignedTransaction");
const responseUnsignedTx = await verifierFabric.sendSyncRequest(
contractUnsignedTx,
methodUnsignedTx,
argsUnsignedTx,
);

const commitProposalBuffer = Buffer.from(
responseUnsignedTx.data.txProposalBuffer,
);

// Prepare signed commit proposal
const signedCommitProposal = signProposal(
commitProposalBuffer,
privateKeyPem,
);

const signedTxArgs = {
signedCommitProposal,
proposal,
proposalResponses,
};

return resolve({
txId,
signedTxArgs,
});
} catch (e) {
logger.error(`error at Invoke: err=${e}`);
return reject(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"name": "@hyperledger/cactus-plugin-ledger-connector-fabric-socketio",
"version": "1.1.2",
"license": "Apache-2.0",
"main": "dist/common/core/bin/www.js",
"module": "dist/common/core/bin/www.js",
"types": "dist/common/core/bin/www.d.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"start": "cd ./dist && node common/core/bin/www.js",
"debug": "nodemon --inspect ./dist/common/core/bin/www.js",
Expand All @@ -25,9 +25,12 @@
"fabric-network": "1.4.19",
"fast-safe-stringify": "2.1.1",
"fs-extra": "10.0.0",
"protobufjs": "5.0.3",
"grpc": "1.24.11",
"js-yaml": "3.14.1",
"jsonwebtoken": "8.5.1",
"log4js": "6.4.1",
"lodash": "4.17.21",
"morgan": "1.10.0",
"serve-favicon": "2.4.5",
"shelljs": "0.8.5",
Expand Down
Loading

0 comments on commit 0b34ca3

Please sign in to comment.