Explicitly specify hash in client applications

For some signing implementations, such as ed25519, a non-default hash
implementation must be specified when creating the Gateway connection in
client applications. Rather than relying on the default hash algorithm,
it is probably good practice in general to specify an algorithm that is
compatible with your signing implementation.

This change explicitly specifies the hash algorithm to raise visibility
of the option to select the hash algorithm.

Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
This commit is contained in:
Mark S. Lewis 2024-10-07 15:48:06 +01:00 committed by Dave Enyeart
parent e37e991c4c
commit b931df3a5f
19 changed files with 170 additions and 142 deletions

View file

@ -18,6 +18,7 @@ import (
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/hash"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"github.com/hyperledger/fabric-protos-go-apiv2/gateway"
"google.golang.org/grpc"
@ -50,6 +51,7 @@ func main() {
gw, err := client.Connect(
id,
client.WithSign(sign),
client.WithHash(hash.SHA256),
client.WithClientConnection(clientConnection),
// Default timeouts for different gRPC calls
client.WithEvaluateTimeout(5*time.Second),

View file

@ -16,6 +16,7 @@ import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.GatewayException;
import org.hyperledger.fabric.client.Hash;
import org.hyperledger.fabric.client.SubmitException;
import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.client.identity.Identity;
@ -60,7 +61,11 @@ public final class App {
// this endpoint.
var channel = newGrpcConnection();
var builder = Gateway.newInstance().identity(newIdentity()).signer(newSigner()).connection(channel)
var builder = Gateway.newInstance()
.identity(newIdentity())
.signer(newSigner())
.hash(Hash.SHA256)
.connection(channel)
// Default timeouts for different gRPC calls
.evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))

View file

@ -5,7 +5,7 @@
*/
const grpc = require('@grpc/grpc-js');
const { connect, signers } = require('@hyperledger/fabric-gateway');
const { connect, hash, signers } = require('@hyperledger/fabric-gateway');
const crypto = require('node:crypto');
const fs = require('node:fs/promises');
const path = require('node:path');
@ -79,6 +79,7 @@ async function main() {
client,
identity: await newIdentity(),
signer: await newSigner(),
hash: hash.sha256,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds

View file

@ -5,7 +5,7 @@
*/
import * as grpc from '@grpc/grpc-js';
import { connect, Contract, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import { connect, Contract, hash, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
@ -46,6 +46,7 @@ async function main(): Promise<void> {
client,
identity: await newIdentity(),
signer: await newSigner(),
hash: hash.sha256,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds

View file

@ -9,6 +9,7 @@ import (
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/hash"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@ -24,6 +25,7 @@ func Initialize(setup OrgSetup) (*OrgSetup, error) {
gateway, err := client.Connect(
id,
client.WithSign(sign),
client.WithHash(hash.SHA256),
client.WithClientConnection(clientConnection),
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),

View file

@ -15,6 +15,7 @@ import (
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/hash"
)
const (
@ -35,6 +36,7 @@ func main() {
gateway, err := client.Connect(
id,
client.WithSign(sign),
client.WithHash(hash.SHA256),
client.WithClientConnection(clientConnection),
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),

View file

@ -16,6 +16,7 @@ import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.GatewayRuntimeException;
import org.hyperledger.fabric.client.Hash;
import org.hyperledger.fabric.client.Network;
import org.hyperledger.fabric.client.SubmitException;
@ -26,148 +27,149 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public final class App implements AutoCloseable {
private static final String channelName = "mychannel";
private static final String chaincodeName = "events";
private static final String channelName = "mychannel";
private static final String chaincodeName = "events";
private final Network network;
private final Contract contract;
private final String assetId = "asset" + Instant.now().toEpochMilli();
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private final ExecutorService executor = Executors.newCachedThreadPool();
private final Network network;
private final Contract contract;
private final String assetId = "asset" + Instant.now().toEpochMilli();
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private final ExecutorService executor = Executors.newCachedThreadPool();
public static void main(final String[] args) throws Exception {
var grpcChannel = Connections.newGrpcConnection();
var builder = Gateway.newInstance()
.identity(Connections.newIdentity())
.signer(Connections.newSigner())
.connection(grpcChannel)
.evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))
.submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES));
public static void main(final String[] args) throws Exception {
var grpcChannel = Connections.newGrpcConnection();
var builder = Gateway.newInstance()
.identity(Connections.newIdentity())
.signer(Connections.newSigner())
.hash(Hash.SHA256)
.connection(grpcChannel)
.evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))
.submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES));
try (var gateway = builder.connect(); var app = new App(gateway)) {
app.run();
} finally {
grpcChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
try (var gateway = builder.connect(); var app = new App(gateway)) {
app.run();
} finally {
grpcChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
public App(final Gateway gateway) {
network = gateway.getNetwork(channelName);
contract = network.getContract(chaincodeName);
}
public App(final Gateway gateway) {
network = gateway.getNetwork(channelName);
contract = network.getContract(chaincodeName);
}
public void run() throws EndorseException, SubmitException, CommitStatusException, CommitException {
// Listen for events emitted by subsequent transactions, stopping when the try-with-resources block exits
try (var eventSession = startChaincodeEventListening()) {
var firstBlockNumber = createAsset();
updateAsset();
transferAsset();
deleteAsset();
public void run() throws EndorseException, SubmitException, CommitStatusException, CommitException {
// Listen for events emitted by subsequent transactions, stopping when the try-with-resources block exits
try (var eventSession = startChaincodeEventListening()) {
var firstBlockNumber = createAsset();
updateAsset();
transferAsset();
deleteAsset();
// Replay events from the block containing the first transaction
replayChaincodeEvents(firstBlockNumber);
}
}
// Replay events from the block containing the first transaction
replayChaincodeEvents(firstBlockNumber);
}
}
private CloseableIterator<ChaincodeEvent> startChaincodeEventListening() {
System.out.println("\n*** Start chaincode event listening");
private CloseableIterator<ChaincodeEvent> startChaincodeEventListening() {
System.out.println("\n*** Start chaincode event listening");
var eventIter = network.getChaincodeEvents(chaincodeName);
executor.execute(() -> readEvents(eventIter));
var eventIter = network.getChaincodeEvents(chaincodeName);
executor.execute(() -> readEvents(eventIter));
return eventIter;
}
return eventIter;
}
private void readEvents(final CloseableIterator<ChaincodeEvent> eventIter) {
try {
eventIter.forEachRemaining(event -> {
var payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event received: " + event.getEventName() + " - " + payload);
});
} catch (GatewayRuntimeException e) {
if (e.getStatus().getCode() != Status.Code.CANCELLED) {
throw e;
}
}
}
private void readEvents(final CloseableIterator<ChaincodeEvent> eventIter) {
try {
eventIter.forEachRemaining(event -> {
var payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event received: " + event.getEventName() + " - " + payload);
});
} catch (GatewayRuntimeException e) {
if (e.getStatus().getCode() != Status.Code.CANCELLED) {
throw e;
}
}
}
private String prettyJson(final byte[] json) {
return prettyJson(new String(json, StandardCharsets.UTF_8));
}
private String prettyJson(final byte[] json) {
return prettyJson(new String(json, StandardCharsets.UTF_8));
}
private String prettyJson(final String json) {
var parsedJson = JsonParser.parseString(json);
return gson.toJson(parsedJson);
}
private String prettyJson(final String json) {
var parsedJson = JsonParser.parseString(json);
return gson.toJson(parsedJson);
}
private long createAsset() throws EndorseException, SubmitException, CommitStatusException {
System.out.println("\n--> Submit transaction: CreateAsset, " + assetId + " owned by Sam with appraised value 100");
private long createAsset() throws EndorseException, SubmitException, CommitStatusException {
System.out.println("\n--> Submit transaction: CreateAsset, " + assetId + " owned by Sam with appraised value 100");
var commit = contract.newProposal("CreateAsset")
.addArguments(assetId, "blue", "10", "Sam", "100")
.build()
.endorse()
.submitAsync();
var commit = contract.newProposal("CreateAsset")
.addArguments(assetId, "blue", "10", "Sam", "100")
.build()
.endorse()
.submitAsync();
var status = commit.getStatus();
if (!status.isSuccessful()) {
throw new RuntimeException("failed to commit transaction with status code " + status.getCode());
}
var status = commit.getStatus();
if (!status.isSuccessful()) {
throw new RuntimeException("failed to commit transaction with status code " + status.getCode());
}
System.out.println("\n*** CreateAsset committed successfully");
System.out.println("\n*** CreateAsset committed successfully");
return status.getBlockNumber();
}
return status.getBlockNumber();
}
private void updateAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: UpdateAsset, " + assetId + " update appraised value to 200");
private void updateAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: UpdateAsset, " + assetId + " update appraised value to 200");
contract.submitTransaction("UpdateAsset", assetId, "blue", "10", "Sam", "200");
contract.submitTransaction("UpdateAsset", assetId, "blue", "10", "Sam", "200");
System.out.println("\n*** UpdateAsset committed successfully");
}
System.out.println("\n*** UpdateAsset committed successfully");
}
private void transferAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: TransferAsset, " + assetId + " to Mary");
private void transferAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: TransferAsset, " + assetId + " to Mary");
contract.submitTransaction("TransferAsset", assetId, "Mary");
contract.submitTransaction("TransferAsset", assetId, "Mary");
System.out.println("\n*** TransferAsset committed successfully");
}
System.out.println("\n*** TransferAsset committed successfully");
}
private void deleteAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: DeleteAsset, " + assetId);
private void deleteAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: DeleteAsset, " + assetId);
contract.submitTransaction("DeleteAsset", assetId);
contract.submitTransaction("DeleteAsset", assetId);
System.out.println("\n*** DeleteAsset committed successfully");
}
System.out.println("\n*** DeleteAsset committed successfully");
}
private void replayChaincodeEvents(final long startBlock) {
System.out.println("\n*** Start chaincode event replay");
private void replayChaincodeEvents(final long startBlock) {
System.out.println("\n*** Start chaincode event replay");
var request = network.newChaincodeEventsRequest(chaincodeName)
.startBlock(startBlock)
.build();
var request = network.newChaincodeEventsRequest(chaincodeName)
.startBlock(startBlock)
.build();
try (var eventIter = request.getEvents()) {
while (eventIter.hasNext()) {
var event = eventIter.next();
var payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event replayed: " + event.getEventName() + " - " + payload);
try (var eventIter = request.getEvents()) {
while (eventIter.hasNext()) {
var event = eventIter.next();
var payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event replayed: " + event.getEventName() + " - " + payload);
if (event.getEventName().equals("DeleteAsset")) {
// Reached the last submitted transaction so break to close the iterator and stop listening for events
break;
}
}
}
}
if (event.getEventName().equals("DeleteAsset")) {
// Reached the last submitted transaction so break to close the iterator and stop listening for events
break;
}
}
}
}
@Override
public void close() throws Exception {
executor.shutdownNow();
}
@Override
public void close() throws Exception {
executor.shutdownNow();
}
}

View file

@ -5,7 +5,7 @@
*/
import * as grpc from '@grpc/grpc-js';
import { ChaincodeEvent, CloseableAsyncIterable, connect, Contract, GatewayError, Network } from '@hyperledger/fabric-gateway';
import { ChaincodeEvent, CloseableAsyncIterable, connect, Contract, GatewayError, hash, Network } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
import { newGrpcConnection, newIdentity, newSigner } from './connect';
@ -23,6 +23,7 @@ async function main(): Promise<void> {
client,
identity: await newIdentity(),
signer: await newSigner(),
hash: hash.sha256,
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { connect, Contract } from '@hyperledger/fabric-gateway';
import { connect, Contract, hash } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
import {
certDirectoryPathOrg1, certDirectoryPathOrg2, keyDirectoryPathOrg1, keyDirectoryPathOrg2, newGrpcConnection, newIdentity,
@ -41,6 +41,7 @@ async function main(): Promise<void> {
client: clientOrg1,
identity: await newIdentity(certDirectoryPathOrg1, mspIdOrg1),
signer: await newSigner(keyDirectoryPathOrg1),
hash: hash.sha256,
});
const clientOrg2 = await newGrpcConnection(
@ -53,6 +54,7 @@ async function main(): Promise<void> {
client: clientOrg2,
identity: await newIdentity(certDirectoryPathOrg2, mspIdOrg2),
signer: await newSigner(keyDirectoryPathOrg2),
hash: hash.sha256,
});
try {

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { connect } from '@hyperledger/fabric-gateway';
import { connect, hash } from '@hyperledger/fabric-gateway';
import { newGrpcConnection, newIdentity, newSigner, tlsCertPathOrg1, peerEndpointOrg1, peerNameOrg1, certDirectoryPathOrg1, mspIdOrg1, keyDirectoryPathOrg1, tlsCertPathOrg2, peerEndpointOrg2, peerNameOrg2, certDirectoryPathOrg2, mspIdOrg2, keyDirectoryPathOrg2 } from './connect';
import { ContractWrapper } from './contractWrapper';
@ -30,6 +30,7 @@ async function main(): Promise<void> {
client: clientOrg1,
identity: await newIdentity(certDirectoryPathOrg1, mspIdOrg1),
signer: await newSigner(keyDirectoryPathOrg1),
hash: hash.sha256,
});
// The gRPC client connection from org2 should be shared by all Gateway connections to this endpoint.
@ -43,6 +44,7 @@ async function main(): Promise<void> {
client: clientOrg2,
identity: await newIdentity(certDirectoryPathOrg2, mspIdOrg2),
signer: await newSigner(keyDirectoryPathOrg2),
hash: hash.sha256,
});

View file

@ -5,7 +5,7 @@
*/
import * as grpc from '@grpc/grpc-js';
import { connect, Gateway, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import { connect, Gateway, hash, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
@ -34,6 +34,7 @@ export async function newGatewayConnection(client: grpc.Client): Promise<Gateway
client,
identity: await newIdentity(),
signer: await newSigner(),
hash: hash.sha256,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { connect, Contract, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import { connect, Contract, hash } from '@hyperledger/fabric-gateway';
import * as path from 'path';
import { TextDecoder } from 'util';
import { ConnectionHelper } from './fabric-connection-profile';
@ -12,9 +12,9 @@ import JSONIDAdapter from './jsonid-adapter';
import { dump } from 'js-yaml';
import {config} from 'dotenv';
import { config } from 'dotenv';
import * as env from 'env-var';
config({path:'app.env'});
import * as env from 'env-var'
const channelName = env.get('CHANNEL_NAME').default('mychannel').asString();
const chaincodeName = env.get('CHAINCODE_NAME').default('conga-nft-contract').asString();
@ -46,6 +46,7 @@ async function main(): Promise<void> {
client,
identity,
signer,
hash: hash.sha256,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
@ -95,4 +96,3 @@ async function ping(contract: Contract): Promise<void> {
console.log('*** Result:');
console.log(dump(result));
}

View file

@ -5,10 +5,9 @@
*/
import { Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import { errorMonitor } from 'events';
/** Internal interface used to describe all the possible components
* of the identity

View file

@ -1,5 +1,5 @@
import * as grpc from '@grpc/grpc-js';
import { connect, Contract, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import { connect, Contract, hash, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import * as path from 'path';
@ -37,6 +37,7 @@ async function initFabric(): Promise<void> {
client,
identity: await newIdentity(),
signer: await newSigner(),
hash: hash.sha256,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds

View file

@ -5,7 +5,7 @@
*/
import * as grpc from '@grpc/grpc-js';
import { connect, Gateway, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import { connect, Gateway, hash, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
@ -34,6 +34,7 @@ export async function newGatewayConnection(client: grpc.Client): Promise<Gateway
client,
identity: await newIdentity(),
signer: await newSigner(),
hash: hash.sha256,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds

View file

@ -24,6 +24,7 @@ import (
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
"github.com/hyperledger/fabric-gateway/pkg/hash"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@ -63,7 +64,8 @@ func main() {
defer hsmSignClose()
// Create a Gateway connection for a specific client identity
gateway, err := client.Connect(id, client.WithSign(hsmSign), client.WithClientConnection(clientConnection))
gateway, err := client.Connect(id, client.WithSign(hsmSign), client.WithHash(hash.SHA256),
client.WithClientConnection(clientConnection))
if err != nil {
panic(err)
}

View file

@ -5,7 +5,7 @@
*/
import * as grpc from '@grpc/grpc-js';
import { connect, Gateway, HSMSigner, HSMSignerFactory, HSMSignerOptions, signers } from '@hyperledger/fabric-gateway';
import { connect, Gateway, hash, HSMSigner, HSMSignerFactory, HSMSignerOptions, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
@ -46,6 +46,7 @@ async function main() {
client,
identity: { mspId, credentials },
signer:hsmSigner.signer,
hash: hash.sha256,
});
await exampleTransaction(gateway);

View file

@ -9,6 +9,7 @@ import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.TlsChannelCredentials;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.Hash;
import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.client.identity.Signer;
@ -86,6 +87,7 @@ public final class Connections {
return Gateway.newInstance()
.identity(newIdentity())
.signer(newSigner())
.hash(Hash.SHA256)
.connection(grpcChannel)
.evaluateOptions(options -> options.withDeadlineAfter(EVALUATE_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.endorseOptions(options -> options.withDeadlineAfter(ENDORSE_TIMEOUT_SECONDS, TimeUnit.SECONDS))

View file

@ -5,7 +5,7 @@
*/
import * as grpc from '@grpc/grpc-js';
import { ConnectOptions, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import { ConnectOptions, hash, Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
@ -47,6 +47,7 @@ export async function newConnectOptions(client: grpc.Client): Promise<ConnectOpt
client,
identity: await newIdentity(),
signer: await newSigner(),
hash: hash.sha256,
// Default timeouts for different gRPC calls
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds