问题
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?
回答1:
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.
回答2:
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);
}
}
回答3:
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