Skip to content

Commit

Permalink
#446 Add asynchronous transaction completion methods
Browse files Browse the repository at this point in the history
  • Loading branch information
satran004 committed Oct 16, 2024
2 parents af685c1 + 43e4436 commit 3a3f49b
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bloxbean.cardano.client.cip.cip30;

import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.SimpleValue;
import co.nstant.in.cbor.model.UnsignedInteger;
import com.bloxbean.cardano.client.account.Account;
import com.bloxbean.cardano.client.address.Address;
Expand All @@ -17,17 +18,18 @@
* CIP30 signData() implementation to create and verify signature
*/
public enum CIP30DataSigner {

INSTANCE();

CIP30DataSigner() {

}

/**
* Sign and create DataSignature in CIP30's signData() format
*
* @param addressBytes Address bytes
* @param payload payload bytes to sign
* @param signer signing account
* @param payload payload bytes to sign
* @param signer signing account
* @return DataSignature
* @throws DataSignError
*/
Expand All @@ -36,7 +38,39 @@ public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payl
byte[] pvtKey = signer.privateKeyBytes();
byte[] pubKey = signer.publicKeyBytes();

return signData(addressBytes, payload, pvtKey, pubKey);
return signData(addressBytes, payload, pvtKey, pubKey, false);
}

/**
* Sign and create DataSignature in CIP30's signData() format
*
* @param addressBytes Address bytes
* @param payload payload bytes to sign
* @param signer signing account
* @param hashPayload indicates if the payload is expected to be hashed
* @return DataSignature
* @throws DataSignError
*/
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, hashPayload);
}

/**
* Sign and create DataSignature in CIP30's signData() format
*
* @param addressBytes Address bytes
* @param payload payload bytes to sign
* @param pvtKey private key bytes
* @param pubKey public key bytes to add
* @return DataSignature
* @throws DataSignError
*/
public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull byte[] pvtKey, @NonNull byte[] pubKey) throws DataSignError {
return signData(addressBytes, payload, pvtKey, pubKey, false);
}

/**
Expand All @@ -45,10 +79,11 @@ 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 hashPayload indicates if the payload is expected to be hashed
* @return DataSignature
* @throws DataSignError
*/
public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull byte[] pvtKey, @NonNull byte[] pubKey)
public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull byte[] pvtKey, @NonNull byte[] pubKey, boolean hashPayload)
throws DataSignError {
try {
HeaderMap protectedHeaderMap = new HeaderMap()
Expand All @@ -60,12 +95,11 @@ public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payl
._protected(new ProtectedHeaderMap(protectedHeaderMap))
.unprotected(new HeaderMap());

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

SigStructure sigStructure = coseSign1Builder.makeDataToSign();

byte[] signature;

if (pvtKey.length >= 64) { //64 bytes expanded pvt key
signature = Configuration.INSTANCE.getSigningProvider().signExtended(sigStructure.serializeAsBytes(), pvtKey);
} else { //32 bytes pvt key
Expand All @@ -74,23 +108,25 @@ public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payl

COSESign1 coseSign1 = coseSign1Builder.build(signature);

//COSEKey
COSEKey coseKey = new COSEKey()
.keyType(OKP) //OKP
.keyId(addressBytes)
.algorithmId(ALG_EdDSA) //EdDSA
.addOtherHeader(CRV_KEY, new UnsignedInteger(CRV_Ed25519)) //crv Ed25519
.addOtherHeader(X_KEY, new ByteString(pubKey)); //x pub key used to sign sig_structure

return new DataSignature(HexUtil.encodeHexString(coseSign1.serializeAsBytes()),
HexUtil.encodeHexString(coseKey.serializeAsBytes()));
String sig = HexUtil.encodeHexString(coseSign1.serializeAsBytes());
String key = HexUtil.encodeHexString(coseKey.serializeAsBytes());

return new DataSignature(sig, key);
} catch (Exception e) {
throw new DataSignError("Error signing data", e);
}
}

/**
* Verify CIP30 signData signature
*
* @param dataSignature
* @return true if verification is successful, otherwise false
*/
Expand All @@ -106,8 +142,9 @@ public boolean verify(@NonNull DataSignature dataSignature) {
.verify(signature, sigStructure.serializeAsBytes(), pubKey);

//Verify address
byte[] addressBytes = coseSign1.headers()._protected().getAsHeaderMap().otherHeaderAsBytes(ADDRESS_KEY);
byte[] addressBytes = coseSign1.headers()._protected().getAsHeaderMap().otherHeaderAsBytes(ADDRESS_KEY);
Address address = new Address(addressBytes);

boolean addressVerified = AddressProvider.verifyAddress(address, pubKey);

return sigVerified && addressVerified;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import com.bloxbean.cardano.client.address.Address;
import com.bloxbean.cardano.client.cip.cip8.COSEKey;
import com.bloxbean.cardano.client.common.model.Networks;
import com.bloxbean.cardano.client.crypto.Blake2bUtil;
import com.bloxbean.cardano.client.util.HexUtil;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class CIP30DataSignerTest {

String 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";

Account account = new Account(Networks.testnet(), mnemonic);

@Test
Expand Down Expand Up @@ -68,5 +71,50 @@ void verifyNamiSignature_invalidKey() {
assertThat(verified).isFalse();
}

}
@Test
void verifyHashedLedgerHardwareWallet() {
DataSignature dataSignature = new DataSignature()
.signature("84582aa201276761646472657373581de103d205532089ad2f7816892e2ef42849b7b52788e41b3fd43a6e01cfa166686173686564f5581c1c1afc33a1ed48205eadcbbda2fc8e61442af2e04673616f21b7d0385840954858f672e9ca51975655452d79a8f106011e9535a2ebfb909f7bbcce5d10d246ae62df2da3a7790edd8f93723cbdfdffc5341d08135b1a40e7a998e8b2ed06")
.key("a4010103272006215820c13745be35c2dfc3fa9523140030dda5b5346634e405662b1aae5c61389c55b3");

boolean verified = CIP30DataSigner.INSTANCE.verify(dataSignature);

assertThat(verified).isTrue();
}

@Test
void verifySignDataHashedPayload() {
DataSignature dataSignature = new DataSignature()
.signature("845846a2012767616464726573735839003175d03902583e82037438cc86732f6e539f803f9a8b2d4ee164b9d0c77e617030631811f60a1f8a8be26d65a57ff71825b336cc6b76361da166686173686564f44b48656c6c6f20576f726c64584036c2151e1230364b0bf9e40cb65dbdca4c5decf4187e3c5511945d410ea59a1e733b5e68178c234979053ed75b0226ba826fb951c5a79fabf10bddcabda8dc05")
.key("a4010103272006215820a5f73966e73d0bb9eadc75c5857eafd054a0202d716ac6dde00303ee9c0019e3");

boolean verified = CIP30DataSigner.INSTANCE.verify(dataSignature);
assertThat(verified).isTrue();
}

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

Address address = new Address(account.baseAddress());
DataSignature dataSignature = CIP30DataSigner.INSTANCE.signData(address.getBytes(), payload, account, true);

assertThat(dataSignature).isNotNull();
assertThat(dataSignature.signature()).isEqualTo("845882a3012704583900327d065c4c135860b9ac6a758c9ef032100a724865998a6b1b8219f3d11c3061dfc0c16e14f5b6779fef214eab7aaa3dffdc5e30c1272f0e6761646472657373583900327d065c4c135860b9ac6a758c9ef032100a724865998a6b1b8219f3d11c3061dfc0c16e14f5b6779fef214eab7aaa3dffdc5e30c1272f0ea166686173686564f5581c19790463ef4ad09bdb724e3a6550c640593d4870f6e192ac8147f35d5840d6348538f8c69f5ac30615700b78597dc29795d5fef2aa6165f17ac208b3163b2d2d55405beb6cd8fc66e3beaac1d08b91fae7b9679cc0ae212c65cfe277d608");
assertThat(dataSignature.key()).isEqualTo("a5010102583900327d065c4c135860b9ac6a758c9ef032100a724865998a6b1b8219f3d11c3061dfc0c16e14f5b6779fef214eab7aaa3dffdc5e30c1272f0e03272006215820097c8507b71063f99e38147f09eacf76f25576a2ddfac2f40da8feee8dab2d5d");
assertThat(HexUtil.encodeHexString(dataSignature.address())).isEqualTo("00327d065c4c135860b9ac6a758c9ef032100a724865998a6b1b8219f3d11c3061dfc0c16e14f5b6779fef214eab7aaa3dffdc5e30c1272f0e");
}

@Test
public void verifySignedHashedPayload() {
String sig = "845882a3012704583900327d065c4c135860b9ac6a758c9ef032100a724865998a6b1b8219f3d11c3061dfc0c16e14f5b6779fef214eab7aaa3dffdc5e30c1272f0e6761646472657373583900327d065c4c135860b9ac6a758c9ef032100a724865998a6b1b8219f3d11c3061dfc0c16e14f5b6779fef214eab7aaa3dffdc5e30c1272f0ea166686173686564f5581c19790463ef4ad09bdb724e3a6550c640593d4870f6e192ac8147f35d5840d6348538f8c69f5ac30615700b78597dc29795d5fef2aa6165f17ac208b3163b2d2d55405beb6cd8fc66e3beaac1d08b91fae7b9679cc0ae212c65cfe277d608";
String key = "a5010102583900327d065c4c135860b9ac6a758c9ef032100a724865998a6b1b8219f3d11c3061dfc0c16e14f5b6779fef214eab7aaa3dffdc5e30c1272f0e03272006215820097c8507b71063f99e38147f09eacf76f25576a2ddfac2f40da8feee8dab2d5d";

DataSignature dataSig = new DataSignature().signature(sig).key(key);

boolean isVerified = CIP30DataSigner.INSTANCE.verify(dataSig);

assertThat(isVerified).isTrue();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,4 @@ public SigStructure signedData(byte[] externalAad, byte[] externalPayload) {
.externalAad(externalAad);
}
}

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 @@ -35,6 +35,7 @@ void buildCOSESign1() throws CborException {
.unprotected(unpHeadermap);

byte[] payload = "Hello World".getBytes();
System.out.println("Payload: " + HexUtil.encodeHexString(payload));
COSESign1Builder coseSign1Builder = new COSESign1Builder(headers, payload, false)
.hashed(true);

Expand All @@ -45,12 +46,14 @@ void buildCOSESign1() throws CborException {

COSESign1 coseSign1 = coseSign1Builder.build(signedSigStructure);
String serHex = HexUtil.encodeHexString(coseSign1.serializeAsBytes());
System.out.println(serHex);

//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());
System.out.println("Expected Hex: " + expected.length());

assertThat(serHex).isEqualTo(expected);
assertThat(coseSign12).isEqualTo(coseSign1);
}
Expand Down Expand Up @@ -130,7 +133,6 @@ void buildCOSESign1_withPayLoadExTrue_additionalHeaders() throws CborException {

COSESign1 coseSign1 = coseSign1Builder.build(signedSigStructure);
String serHex = HexUtil.encodeHexString(coseSign1.serializeAsBytes());
System.out.println(serHex);

//This hex is the result from message-signing rust impl.
String expected = "8458baa7010e02816d637269746963616c6974792d31033903e704430102030543040506064304050607828340a10281055840030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303038340a20103028108584005050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505aa01181802816d637269746963616c6974792d32033907cf04430102030543040506064304050607828340a10281145840030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303038340a20103028108584005050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505386371736f6d65206865616465722076616c7565646b6579314307080966686173686564f5f65840ba0e7cb56486b33c1fc3fb2730968cf46b9215bcf57dfdec15102cad72391b4ac2f5c6a23fe3e2545b6d0d2381fc5fbb090467e02f74d57eee8380b8cf9d1605";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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.crypto.api.SigningProvider;
import com.bloxbean.cardano.client.util.HexUtil;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -45,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));
//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));

assertThat(serHex).isEqualTo(expected);
assertThat(coseSign2).isEqualTo(coseSign);
//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
Loading

0 comments on commit 3a3f49b

Please sign in to comment.