Skip to content

Commit

Permalink
Merge pull request #454 from bloxbean/cip30_hash_satya
Browse files Browse the repository at this point in the history
Refactor COSESign builders for external payload and hashing
  • Loading branch information
matiwinnetou committed Oct 9, 2024
2 parents 117a275 + d3c7de9 commit 779d9fa
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* CIP30 signData() implementation to create and verify signature
*/
public enum CIP30DataSigner {

INSTANCE();

CIP30DataSigner() {
Expand Down Expand Up @@ -46,16 +47,16 @@ public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payl
* @param addressBytes Address bytes
* @param payload payload bytes to sign
* @param signer signing account
* @param hashedPayload indicates if the payload is hashed
* @param hashPayload indicates if the payload is hashed
* @return DataSignature
* @throws DataSignError
*/
public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull Account signer, boolean hashedPayload)
public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull Account signer, boolean hashPayload)
throws DataSignError {
byte[] pvtKey = signer.privateKeyBytes();
byte[] pubKey = signer.publicKeyBytes();

return signData(addressBytes, payload, pvtKey, pubKey, hashedPayload);
return signData(addressBytes, payload, pvtKey, pubKey, hashPayload);
}

/**
Expand All @@ -78,28 +79,23 @@ public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payl
* @param payload payload bytes to sign
* @param pvtKey private key bytes
* @param pubKey public key bytes to add
* @param hashedPayload indicates if the payload is hashed
* @param hashPayload indicates if the payload is hashed
* @return DataSignature
* @throws DataSignError
*/
public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull byte[] pvtKey, @NonNull byte[] pubKey, boolean hashedPayload)
public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull byte[] pvtKey, @NonNull byte[] pubKey, boolean hashPayload)
throws DataSignError {
try {
HeaderMap protectedHeaderMap = new HeaderMap()
.algorithmId(ALG_EdDSA) //EdDSA
.keyId(addressBytes)
.addOtherHeader(ADDRESS_KEY, new ByteString(addressBytes));

HeaderMap unprotectedHeaderMap = new HeaderMap();
if (hashedPayload) {
unprotectedHeaderMap.addOtherHeader("hashed", SimpleValue.TRUE);
}

Headers headers = new Headers()
._protected(new ProtectedHeaderMap(protectedHeaderMap))
.unprotected(unprotectedHeaderMap);
.unprotected(new HeaderMap());

COSESign1Builder coseSign1Builder = new COSESign1Builder(headers, payload, false).hashed(hashedPayload);
COSESign1Builder coseSign1Builder = new COSESign1Builder(headers, payload, false).hashed(hashPayload);

SigStructure sigStructure = coseSign1Builder.makeDataToSign();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ void verifySignDataHashedPayload() {

@Test
void signDataHashedPayload() throws DataSignError {
byte[] payload = Blake2bUtil.blake2bHash224("Hello World".getBytes());
byte[] payload = "Hello World".getBytes();

Address address = new Address(account.baseAddress());
DataSignature dataSignature = CIP30DataSigner.INSTANCE.signData(address.getBytes(), payload, account, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static ProtectedHeaderMap deserialize(DataItem dataItem) {
} else {
throw new CborRuntimeException(
String.format("Deserialization error: Invalid type for ProtectedHeaderMap, type: %s, " +
"expected type: ByteString" + dataItem.getMajorType()));
"expected type: ByteString", dataItem.getMajorType()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,20 @@ public COSESign1Builder(Headers headers, byte[] payload, boolean isPayloadExtern

public SigStructure makeDataToSign() {
Headers headersCopy = headers.copy();
headersCopy.unprotected().addOtherHeader("hashed", hashed ? SimpleValue.TRUE : SimpleValue.FALSE);

byte[] finalPayload;
if (isPayloadExternal) {
finalPayload = payload.clone();
} else {
finalPayload = hashed ? Blake2bUtil.blake2bHash224(payload): payload.clone();
}

return new SigStructure()
.sigContext(SigContext.Signature1)
.bodyProtected(headersCopy._protected())
.externalAad(externalAad != null ? externalAad.clone() : new byte[0])
.payload(payload.clone());
.payload(finalPayload);
}

public COSESign1 build(byte[] signedSigStructure) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ public COSESignBuilder(Headers headers, byte[] payload, boolean isPayloadExterna
public SigStructure makeDataToSign() {
Headers headersCopy = headers.copy();

byte[] finalPayload;
if (isPayloadExternal) {
finalPayload = payload.clone();
} else
finalPayload = hashed? Blake2bUtil.blake2bHash224(payload): payload.clone();

return new SigStructure()
.sigContext(SigContext.Signature)
.bodyProtected(headersCopy._protected())
.externalAad(externalAad != null ? externalAad.clone() : new byte[0])
.payload(payload.clone());
.payload(finalPayload);
}

public COSESign build(List<COSESignature> coseSignatures) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import com.bloxbean.cardano.client.cip.cip8.*;
import com.bloxbean.cardano.client.common.model.Networks;
import com.bloxbean.cardano.client.config.Configuration;
import com.bloxbean.cardano.client.crypto.Blake2bUtil;
import com.bloxbean.cardano.client.util.HexUtil;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -48,8 +47,8 @@ void buildCOSESign1() throws CborException {
COSESign1 coseSign1 = coseSign1Builder.build(signedSigStructure);
String serHex = HexUtil.encodeHexString(coseSign1.serializeAsBytes());

//This hex is the result from message-signing rust impl.
String expected = "8447a2010e033903e7a2386371536f6d65206865616465722076616c756566686173686564f5581c19790463ef4ad09bdb724e3a6550c640593d4870f6e192ac8147f35d58400a448415208ba496d5cd58407a05269b8f0fd14a3c690b761b03c58e2ac70dd36a6bb9d0e03c5baa9d68da99af4be2a8245892325535ec3656435505ba182703";
//This hex is the result from message-signing rust impl. (Check cose_sign1_builder.rs)
String expected = "8447a2010e033903e7a2386371536f6d65206865616465722076616c756566686173686564f5581c19790463ef4ad09bdb724e3a6550c640593d4870f6e192ac8147f35d58400a810f4fef824d98bb3d08a93f32b2bffb236ecc87100142911605509b953701b0680ce347a13d54e6f626c1f368e69e422d75870db21f8c8ad9f1e40f51ca04";
COSESign1 coseSign12 = COSESign1.deserialize(CborDecoder.decode(HexUtil.decodeHexString(serHex)).get(0));

System.out.println("Serialized Hex: " + serHex.length());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ void buildCOSESign() throws CborException {
String serHex = HexUtil.encodeHexString(coseSign.serializeAsBytes());
System.out.println(serHex);

//This hex is the result from message-signing rust impl.
String expected = "8447a2010e033903e7a2386371536f6d65206865616465722076616c756566686173686564f5581c19790463ef4ad09bdb724e3a6550c640593d4870f6e192ac8147f35d828340a238c77819616e6f74686572206164646974696f6e616c20686561646572646b6579316a6b6579312076616c756558408a991fa149aa4ac06cfea4a36f798b06e86cd7231e5dada423893a302de2e7278b7589de9bada77a8e597c5a1916d28787a052f5f19c510c980faae1d4e909078340a239018f781a616e6f74686572206164646974696f6e616c2068656164657232646b6579326a6b6579322076616c7565584093279db72ff01b677f4cdef33fefc0ac48932b5f15e4eb2427553e83127cc3bac4e7b459ff3c39b0d874c22c5250130aba15e3981eccfc0a2f58f53dcb06a90e";
COSESign coseSign2 = COSESign.deserialize(CborDecoder.decode(HexUtil.decodeHexString(serHex)).get(0));

assertThat(serHex).isEqualTo(expected);
assertThat(coseSign2).isEqualTo(coseSign);
//This hex is the result from message-signing rust impl. (Check cose_sign_builder.rs)
String expected = "8447a2010e033903e7a1386371536f6d65206865616465722076616c7565581c19790463ef4ad09bdb724e3a6550c640593d4870f6e192ac8147f35d828340a238c77819616e6f74686572206164646974696f6e616c20686561646572646b6579316a6b6579312076616c7565584098b74a575e435c5506ec80bc4b47aceba462a4edaf785c345c022acb80957ddbdb36177f3a95cee97efdb474bbdcb66db0fe93e9b011523a8a36d8b443dbb5008340a239018f781a616e6f74686572206164646974696f6e616c2068656164657232646b6579326a6b6579322076616c756558406161857b10b1bfa62bdf6f3ae9d751cc361446af41ec79fa2fca8fe67d27f3d8622ad99786539aa3dedd4d7456d5e13d5474f3d72babd37f6dbe09bfc8c12701";
COSESign expectedCoseSign2 = COSESign.deserialize(CborDecoder.decode(HexUtil.decodeHexString(expected)).get(0));

//rust message-signing lib doesn't add hashed key to the unprotected headers. So, we are just checking the signatures here
//But rust COSESign1Builder adds hashed key to the unprotected headers. Not sure why?
assertThat(coseSign.signatures().get(0).signature()).endsWith(expectedCoseSign2.signatures().get(0).signature());
assertThat(coseSign.signatures().get(1).signature()).endsWith(expectedCoseSign2.signatures().get(1).signature());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//Using https://github.com/Emurgo/message-signing
//This is just here for reference. So that we can compare the output of the rust code with the output of the java code.
use cardano_message_signing as ms;
use cardano_message_signing::builders::COSESign1Builder;
use cardano_message_signing::cbor::CBORValue;
use cardano_message_signing::utils::{Int, ToBytes};
use cardano_message_signing::{HeaderMap, Headers, Label, ProtectedHeaderMap};
use cardano_serialization_lib as csl;

fn main() {
use ms::utils::ToBytes;
let pvt_key_hex = "a09afd74a50ea23fe8607f71766cd56fd78e44e4ee7563e53a690de1d74da15e41802f82cfe0718ade23369800857b885abc1c512aa43059be6e4ce2e8f43ce73d16a13b6998d256de5835612f44fdd56e2f24ab2694b7b70c68f8fd77325c83";
let sk_bytes = hex::decode(pvt_key_hex).unwrap();

let sk = csl::crypto::Bip32PrivateKey::from_bytes(&sk_bytes).unwrap();
let pk = sk.to_public();
let mut headerMap = HeaderMap::new();
headerMap.set_algorithm_id(&Label::new_int(&Int::new_i32(14)));
headerMap.set_content_type(&Label::new_int(&Int::new_i32(-1000)));
let protected = ProtectedHeaderMap::new(&headerMap);

let mut unprotected = HeaderMap::new();
unprotected.set_header(&Label::new_int(&Int::new_i32(-100)), &CBORValue::new_text(String::from("Some header value")));
let headers = Headers::new(&protected, &unprotected);

let payload = String::from("Hello World").into_bytes();

let mut builder = COSESign1Builder::new(&headers, payload, false);
builder.hash_payload();

let sig_structure = builder.make_data_to_sign();

let signed_sig_struct = sk.to_raw_key().sign(&sig_structure.to_bytes()).to_bytes();
let cose_sign1 = builder.build(signed_sig_struct);

let serialized = cose_sign1.to_bytes();
let hex = hex::encode(serialized.clone());
println!("serialized = {:?}", hex);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//Using https://github.com/Emurgo/message-signing
//This is just here for reference. So that we can compare the output of the rust code with the output of the java code.
use cardano_message_signing as ms;
use cardano_serialization_lib as csl;
use cardano_message_signing::{COSESignature, COSESignatures, HeaderMap, Headers, Label, ProtectedHeaderMap};
use cardano_message_signing::builders::{COSESign1Builder, COSESignBuilder};
use cardano_message_signing::cbor::CBORValue;
use cardano_message_signing::utils::{Int, ToBytes};

fn main() {
use ms::utils::ToBytes;
//Mnemonic: "nice orient enjoy teach jump office alert inquiry apart unaware seat tumble unveil device have bullet morning eyebrow time image embody divide version uniform"
let pvt_key_hex1 = "a09afd74a50ea23fe8607f71766cd56fd78e44e4ee7563e53a690de1d74da15e41802f82cfe0718ade23369800857b885abc1c512aa43059be6e4ce2e8f43ce73d16a13b6998d256de5835612f44fdd56e2f24ab2694b7b70c68f8fd77325c83";
let sk_bytes1 = hex::decode(pvt_key_hex1).unwrap();
let bip32SK1 = csl::crypto::Bip32PrivateKey::from_bytes(&sk_bytes1).unwrap();

//Mnemonic: ""carbon time empty obey bicycle choice mind kitchen shadow call strike skull check flag series deal garlic wing uphold problem bamboo winner install price"
let pvt_key_hex2 = "a0bcf0bee440970644e0f7850776c38040e6be001a8847f07b2cc1f982153b55cf1b9e9c1ed5881bcb1546e1176e4852c41df5f72ccc876dcc5e227328e103a64d992baad3ce8e9b4d97541423535db5f82edf5b19e3a2ed602466824cfae9ce";
let sk_bytes2 = hex::decode(pvt_key_hex2).unwrap();
let bip32SK2 = csl::crypto::Bip32PrivateKey::from_bytes(&sk_bytes2).unwrap();

let mut headerMap = HeaderMap::new();
headerMap.set_algorithm_id(&Label::new_int(&Int::new_i32(14)));
headerMap.set_content_type(&Label::new_int(&Int::new_i32(-1000)));
let protected = ProtectedHeaderMap::new(&headerMap);

let mut unprotected = HeaderMap::new();
unprotected.set_header(&Label::new_int(&Int::new_i32(-100)), &CBORValue::new_text(String::from("Some header value")));
let headers = Headers::new(&protected, &unprotected);

let payload = String::from("Hello World").into_bytes();

let mut builder = COSESignBuilder::new(&headers, payload, false);
builder.hash_payload();

//Build sig structure
let sig_structure = builder.make_data_to_sign();

//cose_signature1
let sk1 = bip32SK1.to_raw_key();
let signature1 = sk1.sign(&sig_structure.to_bytes()).to_bytes();

let mut cose_sig_unprotected_header1 = HeaderMap::new();
cose_sig_unprotected_header1.set_header(&Label::new_int(&Int::new_i32(-200)), &CBORValue::new_text(String::from("another additional header")));
cose_sig_unprotected_header1.set_header(&Label::new_text(String::from("key1")), &CBORValue::new_text(String::from("key1 value")));

let cose_sig_header1 = Headers::new(&ProtectedHeaderMap::new(&HeaderMap::new()), &cose_sig_unprotected_header1);

let coseSignature1 = COSESignature::new(&cose_sig_header1, signature1);

//cose_signature2
let sk2 = bip32SK2.to_raw_key();
let signature2 = sk2.sign(&sig_structure.to_bytes()).to_bytes();

let mut cose_sig_unprotected_header2 = HeaderMap::new();
cose_sig_unprotected_header2.set_header(&Label::new_int(&Int::new_i32(-400)), &CBORValue::new_text(String::from("another additional header2")));
cose_sig_unprotected_header2.set_header(&Label::new_text(String::from("key2")), &CBORValue::new_text(String::from("key2 value")));

let cose_sig_header2 = Headers::new(&ProtectedHeaderMap::new(&HeaderMap::new()), &cose_sig_unprotected_header2);

let coseSignature2 = COSESignature::new(&cose_sig_header2, signature2);

//Build cose_signatures
let mut cose_signatures = COSESignatures::new();
cose_signatures.add(&coseSignature1);
cose_signatures.add(&coseSignature2);

//Build cose_sign
let cose_sign = builder.build(&cose_signatures);

let serialized = cose_sign.to_bytes();
let hex = hex::encode(serialized.clone());
println!("serialized = {:?}", hex);
}

0 comments on commit 779d9fa

Please sign in to comment.