kuberneteshyperledger-fabrichyperledgerdigital-signaturehyperledger-bevel

Problem with hyperledger fabric offline signing


I'm having some trouble with the implementation of Hyperledger Fabric offline signing method with the fabric-gateway package. My code looks like this at the moment:

async function main() {
  const identityPath = path.join(__dirname, 'wallet/INMETROMSP/', 'te.id');
  const identityData = JSON.parse(await fs.readFileSync(identityPath, 'utf8'));
  const certificate = utf8Encoder.encode(identityData.credentials.certificate);

  const privateKeyPath = path.resolve(__dirname, 'private_key.pem');
  const privateKeyPem = fs.readFileSync(privateKeyPath, 'utf8');
  const privateKey = crypto.createPrivateKey(privateKeyPem);

  // const signer = signers.newPrivateKeySigner(privateKey);

  const configContent = fs.readFileSync('connection-org.yaml','utf-8');
  const data = yaml.load(configContent);
  const cert = data.peers['inmetro-peer0.default'].tlsCACerts.pem;
  const tlsRootCert = Buffer.from(cert);

  const client = new grpc.Client('peer0.inmetro.br:443', grpc.credentials.createSsl(tlsRootCert));
  const noOpHash = (message) => message;

  const gateway = connect({
    identity: { 
      mspId: MSPID,
      credentials: certificate,
     },
    // hash: noOpHash,
    // signer,
    client,
  });

  try {
    const network = gateway.getNetwork(CHANNEL);
    const contract = network.getContract(CC_NAME);

    const vehiclePlate = "ABC1234"
    const reportData = JSON.stringify({
      data: [4199, 4179, 2923, 2815, 3453, 3120, 1962, 1452, 3384],
    });
    
    const unsignedProposal = contract.newProposal('RegisterMeter', vehiclePlate, reportData);
    const proposalBytes = unsignedProposal.getBytes();
    const proposalDigest = unsignedProposal.getDigest();
    const proposalSignature = crypto.sign('sha256', proposalDigest, privateKey);
    const signedProposal = gateway.newSignedProposal(proposalBytes, proposalSignature);

    const unsignedTransaction = await signedProposal.endorse();
    const transactionBytes = unsignedTransaction.getBytes();
    const transactionDigest = unsignedTransaction.getDigest();
    const transactionSignature = signDigest(unsignedTransaction.getDigest());
    const signedTransaction = gateway.newSignedTransaction(transactionBytes, transactionSignature);

    const unsignedCommit = await signedTransaction.submit();
    const commitBytes = unsignedCommit.getBytes();
    const commitDigest = unsignedCommit.getDigest();
    const commitSignature = signDigest(unsignedCommit.getDigest());
    const signedCommit = gateway.newSignedCommit(commitBytes, commitSignature);

    const result = signedTransaction.getResult();
    const status = await signedCommit.getStatus();

    console.log(result,status);

However, it breaks on the first endorse of the transaction, returning an "access denied" message. This is the full error message:

EndorseError: 10 ABORTED: failed to endorse transaction, see attached details for more info
    at /home/edu/fabric-node-webserver/client/braketester/node_modules/@hyperledger/fabric-gateway/dist/client.js:37:253
    at Object.callback (/home/edu/fabric-node-webserver/client/braketester/node_modules/@hyperledger/fabric-gateway/dist/client.js:102:20)
    at Object.onReceiveStatus (/home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/client.js:192:36)
    at Object.onReceiveStatus (/home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141)
    ... 2 lines matching cause stack trace ...
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
  code: 10,
  details: [
    {
      address: 'peer0.inmetro.br:443',
      message: 'error validating proposal: access denied: channel [demo] creator org [INMETROMSP]',
      mspId: 'INMETROMSP'
    }
  ],
  cause: Error: 10 ABORTED: failed to endorse transaction, see attached details for more info
      at callErrorFromStatus (/home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/call.js:31:19)
      at Object.onReceiveStatus (/home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/client.js:192:76)
      at Object.onReceiveStatus (/home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141)
      at Object.onReceiveStatus (/home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:323:181)
      at /home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/resolving-call.js:99:78
      at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
  for call at
      at Client.makeUnaryRequest (/home/edu/fabric-node-webserver/client/braketester/node_modules/@grpc/grpc-js/build/src/client.js:160:32)
      at /home/edu/fabric-node-webserver/client/braketester/node_modules/@hyperledger/fabric-gateway/dist/client.js:37:62
      at new Promise (<anonymous>)
      at GatewayClientImpl.endorse (/home/edu/fabric-node-webserver/client/braketester/node_modules/@hyperledger/fabric-gateway/dist/client.js:37:16)
      at ProposalImpl.endorse (/home/edu/fabric-node-webserver/client/braketester/node_modules/@hyperledger/fabric-gateway/dist/proposal.js:43:52)
      at async main (/home/edu/fabric-node-webserver/client/braketester/teste.js:61:33) {
    code: 10,
    details: 'failed to endorse transaction, see attached details for more info',
    metadata: Metadata { internalRepr: [Map], options: {} }
  },
  transactionId: '9276e29c9c4e29e40eca631ad3c568415ec345cd2952c05aee5ce814d0d33a3d'
}

And this is the error shown on the peer log:

2025-05-09 18:14:17.377 UTC 02ca WARN [endorser] Validate -> access denied: creator's signature over the proposal is not valid channel=demo txID=9276e29c mspID=INMETROMSP error="The signature is invalid" errorVerbose="The signature is invalid\ngithub.com/hyperledger/fabric/msp.(*identity).Verify\n\t/msp/identities.go:202\ngithub.com/hyperledger/fabric/core/endorser.(*UnpackedProposal).Validate\n\t/core/endorser/msgvalidation.go:189\ngithub.com/hyperledger/fabric/core/endorser.(*Endorser).preProcess\n\t/core/endorser/endorser.go:258\ngithub.com/hyperledger/fabric/core/endorser.(*Endorser).ProcessProposal\n\t/core/endorser/endorser.go:335\ngithub.com/hyperledger/fabric/core/handlers/auth/filter.(*expirationCheckFilter).ProcessProposal\n\t/core/handlers/auth/filter/expiration.go:61\ngithub.com/hyperledger/fabric/core/handlers/auth/filter.(*filter).ProcessProposal\n\t/core/handlers/auth/filter/filter.go:32\ngithub.com/hyperledger/fabric/internal/pkg/gateway.(*EndorserServerAdapter).ProcessProposal\n\t/internal/pkg/gateway/gateway.go:43\ngithub.com/hyperledger/fabric/internal/pkg/gateway.(*Server).planFromFirstEndorser.func1\n\t/internal/pkg/gateway/endorse.go:205\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1700" identity="(mspid=INMETROMSP subject=CN=te,OU=client,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU issuer=CN=ca,OU=Tech,O=Kung Fu Software,STREET=Alicante,L=Alicante,C=ES serialnumber=723095516253312706461993611318417152021379977104)"
2025-05-09 18:14:17.377 UTC 02cb WARN [endorser] ProcessProposal -> Failed to preProcess proposal error="error validating proposal: access denied: channel [demo] creator org [INMETROMSP]"
2025-05-09 18:14:17.377 UTC 02cc WARN [gateway] func1 -> Endorse call to endorser failed channel=demo chaincode=braketester-external txID=9276e29c9c4e29e40eca631ad3c568415ec345cd2952c05aee5ce814d0d33a3d endorserAddress=peer0.inmetro.br:443 endorserMspid=INMETROMSP error="error validating proposal: access denied: channel [demo] creator org [INMETROMSP]"

I know that the certificate is correct, because using the Signer method works fine, but the offline signing method returns this error always. I saw some other posts about this error being caused by the certificate being sent to the client method as an string instead of an utf8 object, but mine seems correct. Is there anything I'm missing?


Solution

  • If you don't specify a hash in the options passed to the connect() call, SHA-256 is used by default. This means that unsignedProposal.getDigest() will return a SHA-256 hash of the proposal message. You are then using the Node crypto sign(), which first creates a SHA-256 hash of the supplied data, then encrypts that hash with your ECDSA private key. You have created a signature for a message hash, not the for the message itself.

    If you want to use the Node crypto library to create the signature, you will need to specify the none hash implementation (or your own noOpHash) in the connect options so that unsignedProposal.getDigest() returns the full proposal message for you to pass to crypto.sign(). Alternatively, you could use the Signer obtained from newPrivateKeySigner(), which uses a JavaScript ECDSA signing implementation that operates on a pre-computed hash / digest.