I have started playing atound with Hyperledger Sawtooth recently, and having trouble to submit transactions on java, while python code seems okay.
I have prepared the python code based on the api docs here and then tried to write one in java as well. Below is the code in java
import com.google.protobuf.ByteString;
import com.mashape.unirest.http.Unirest;
import sawtooth.sdk.processor.Utils;
import sawtooth.sdk.protobuf.*;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
public class BatchSender {
public static void main(String[] args) throws Exception{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec parameterSpec = new ECGenParameterSpec("secp256k1");
keyPairGenerator.initialize(parameterSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA");
ecdsaSign.initSign(keyPair.getPrivate());
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
String publicKeyHex = Utils.hash512(publicKeyBytes);
ByteString publicKeyByteString = ByteString.copyFrom(new String(publicKeyBytes),"UTF-8");
String payload = "{'key':1, 'value':'value comes here'}";
String payloadBytes = Utils.hash512(payload.getBytes());
ByteString payloadByteString = ByteString.copyFrom(payload.getBytes());
TransactionHeader txnHeader = TransactionHeader.newBuilder().
setBatcherPubkeyBytes(publicKeyByteString).
setFamilyName("plain_info").
setFamilyVersion("1.0").
addInputs("1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7").
setNonce("1").
addOutputs("1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7").
setPayloadEncoding("application/json").
setPayloadSha512(payloadBytes).
setSignerPubkey(publicKeyHex).build();
ByteString txnHeaderBytes = txnHeader.toByteString();
ecdsaSign.update(txnHeaderBytes.toByteArray());
byte[] txnHeaderSignature = ecdsaSign.sign();
Transaction txn = Transaction.newBuilder().setHeader(txnHeaderBytes).setPayload(payloadByteString).setHeaderSignature(Utils.hash512(txnHeaderSignature)).build();
BatchHeader batchHeader = BatchHeader.newBuilder().setSignerPubkey(publicKeyHex).addTransactionIds(txn.getHeaderSignature()).build();
ByteString batchHeaderBytes = batchHeader.toByteString();
ecdsaSign.update(batchHeaderBytes.toByteArray());
byte[] batchHeaderSignature = ecdsaSign.sign();
Batch batch = Batch.newBuilder().setHeader(batchHeaderBytes).setHeaderSignature(Utils.hash512(batchHeaderSignature)).addTransactions(txn).build();
BatchList batchList = BatchList.newBuilder().addBatches( batch).build();
ByteString batchBytes = batchList.toByteString();
String serverResponse = Unirest.post("http://rest-api:8080/batches").header("Content-Type","application/octet-stream").body(batchBytes.toByteArray()).asString().getBody();
System.out.println(serverResponse);
}
}
Once I run it, I am getting
{
"error": {
"code": 30,
"message": "The submitted BatchList was rejected by the validator. It was poorly formed, or has an invalid signature.",
"title": "Submitted Batches Invalid"
}
}
On the dockers logs, I can see
sawtooth-validator-default | [2017-11-21 08:20:09.842 DEBUG interconnect] ServerThread receiving CLIENT_BATCH_SUBMIT_REQUEST message: 1242 bytes
sawtooth-validator-default | [2017-11-21 08:20:09.844 DEBUG signature_verifier] batch failed signature validation: 30a2f4a24be3e624f5a35b17cb505b65cb8dd41600545c6dcfac7534205091552e171082922d4eb71f1bb186fe49163f349c604b631f64fa8f1cfea1c8bb2818
sawtooth-validator-default | [2017-11-21 08:20:09.844 DEBUG interconnect] ServerThread sending CLIENT_BATCH_SUBMIT_RESPONSE to b'50b094689ac14b39'
I have checked the key sizes and verify the signature, and it seems all ok, however, i couldnt find why the batch is rejected...
Anyone had similar error response from sawtooth before? is it the batch format or still signature issue for the code above?
The problem is in setting the batch header signature and the transaction header signature. Those should not have the sha-512 hash taken of them. Those bytes should be encoded as hex strings.
Using the apache-commons-codec library that can be done as:
import org.apache.commons.codec.binary.Hex;
Transaction txn = Transaction.newBuilder()
.setHeader(txnHeaderBytes)
.setPayload(payloadByteString)
.setHeaderSignature(Hex.encodeHexString(txnHeaderSignature))
.build();
The Utils.sha512 should only be used on the payload bytes.
The Sawtooth Java SDK comes with a Signing
class that can be helpful in
generating private/public key pairs and signing messages.
I have modified your code to use the specified class and included the changes from Boyd Johnson's answer for a working example (this is using Sawtooth Java SDK version 1.0):
import com.google.protobuf.ByteString;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.bitcoinj.core.ECKey;
import sawtooth.sdk.client.Signing;
import sawtooth.sdk.processor.Utils;
import sawtooth.sdk.protobuf.Batch;
import sawtooth.sdk.protobuf.BatchHeader;
import sawtooth.sdk.protobuf.BatchList;
import sawtooth.sdk.protobuf.Transaction;
import sawtooth.sdk.protobuf.TransactionHeader;
import java.security.SecureRandom;
public class BatchSender {
public static void main(String []args) throws UnirestException {
ECKey privateKey = Signing.generatePrivateKey(new SecureRandom());
String publicKey = Signing.getPublicKey(privateKey);
ByteString publicKeyByteString = ByteString.copyFromUtf8(publicKey);
String payload = "{'key':1, 'value':'value comes here'}";
String payloadBytes = Utils.hash512(payload.getBytes());
ByteString payloadByteString = ByteString.copyFrom(payload.getBytes());
TransactionHeader txnHeader = TransactionHeader.newBuilder()
.setBatcherPublicKeyBytes(publicKeyByteString)
.setSignerPublicKeyBytes(publicKeyByteString)
.setFamilyName("plain_info")
.setFamilyVersion("1.0")
.addInputs("1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7")
.setNonce("1")
.addOutputs("1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7")
.setPayloadSha512(payloadBytes)
.setSignerPublicKey(publicKey)
.build();
ByteString txnHeaderBytes = txnHeader.toByteString();
String txnHeaderSignature = Signing.sign(privateKey, txnHeaderBytes.toByteArray());
Transaction txn = Transaction.newBuilder()
.setHeader(txnHeaderBytes)
.setPayload(payloadByteString)
.setHeaderSignature(txnHeaderSignature)
.build();
BatchHeader batchHeader = BatchHeader.newBuilder()
.setSignerPublicKey(publicKey)
.addTransactionIds(txn.getHeaderSignature())
.build();
ByteString batchHeaderBytes = batchHeader.toByteString();
String batchHeaderSignature = Signing.sign(privateKey, batchHeaderBytes.toByteArray());
Batch batch = Batch.newBuilder()
.setHeader(batchHeaderBytes)
.setHeaderSignature(batchHeaderSignature)
.addTransactions(txn)
.build();
BatchList batchList = BatchList.newBuilder()
.addBatches(batch)
.build();
ByteString batchBytes = batchList.toByteString();
String serverResponse = Unirest.post("http://localhost:8008/batches")
.header("Content-Type","application/octet-stream")
.body(batchBytes.toByteArray())
.asString()
.getBody();
System.out.println(serverResponse);
}
}
I've just have the same issue. In my case the problem was in setting payload data.
Be sure that:
- You made a sha512 hash of your PayloadByteArray and pass it to TransactionHeader creation
- You pass PayloadByteArray to transaction creation.
So:
- PayloadByteArray -> Transaction creation
- sha512 Hash of PayloadByteArray -> TransactionHeader creation
来源:https://stackoverflow.com/questions/47408385/sawtooth-invalid-batch-or-signature