Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added support for unprotected header section, e.g. hashed: true… #27

Merged
merged 1 commit into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/org/cardanofoundation/cip30/CIP30Verifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ public Cip30VerificationResult verify() {
var dataItems = ((Array) coseCbor).getDataItems();
var protectedHeader = (ByteString) dataItems.get(0); // 1

var unprotectedHeaderMap = (Map) dataItems.get(1); // 2

if (unprotectedHeaderMap.getMajorType() != MAP) {
logger.error("Invalid CIP-30 signature. unprotected header structure is not a map.");
return Cip30VerificationResult.createInvalid(CIP8_FORMAT_ERROR);
}

var isHashed = unprotectedHeaderMap.get(new UnicodeString("hashed")) == SimpleValue.TRUE;

var messageByteString = (ByteString) dataItems.get(2); // 3

var ed25519SignatureByteString = (ByteString) dataItems.get(3); // 4
Expand Down Expand Up @@ -179,6 +188,7 @@ public Cip30VerificationResult verify() {
}

var b = Cip30VerificationResult.Builder.newBuilder();
b.isHashed(isHashed);

if (isSignatureVerified && isAddressVerified) {
b.valid();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public class Cip30VerificationResult {
*/
private byte[] cosePayload;

/**
* whether body of the message is hashed rather than full body
*/
private boolean isHashed;

public static class Builder {


Expand All @@ -65,6 +70,8 @@ public static class Builder {

private byte[] cosePayload;

private boolean isHashed;

/**
* Creates an object {@code Builder} in charge of building the class
* {@code Cip30VerificationResult}.
Expand Down Expand Up @@ -114,6 +121,11 @@ public Builder cosePayload(byte[] cosePayload) {
return Builder.this;
}

public Builder isHashed(boolean isHashed) {
this.isHashed = isHashed;
return Builder.this;
}

/**
* Creates an instance of the class {@code Cip30VerificationResult} using the information
* stored.
Expand All @@ -135,6 +147,7 @@ private Cip30VerificationResult(Builder builder) {
this.ed25519Signature = builder.ed25519Signature;
this.message = builder.message;
this.cosePayload = builder.cosePayload;
this.isHashed = builder.isHashed;
}

/**
Expand Down Expand Up @@ -209,6 +222,14 @@ public Optional<String> getAddress(AddressFormat format) {
return cosePayload;
}

/**
* return whether body is hashed or not (e.g. hardware wallet scenario)
* @return
*/
public boolean isHashed() {
return isHashed;
}

/**
* Returns the Ed25519 public key in a specific encoding format and charset.
* <p>
Expand Down Expand Up @@ -380,6 +401,7 @@ public String toString() {
", ed25519Signature=" + ed25519Signature +
", message=" + message +
", cosePayload=" + cosePayload +
", isHashed=" + isHashed +
'}';
}

Expand Down
70 changes: 42 additions & 28 deletions src/test/java/org/cardanofoundation/cip30/CIP30VerifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,35 @@ void validSignatureWithAddressAndPublicKey1() {
var sig = "84584aa3012704581de11d813fd4ab9c1e5f7a35da16f75c2e664edfb2a127fc17a4a7ebbfee6761646472657373581de11d813fd4ab9c1e5f7a35da16f75c2e664edfb2a127fc17a4a7ebbfeea166686173686564f45901740a202020207b0a202020202020202022757269223a202268747470733a2f2f65766f74696e672e63617264616e6f2e6f72672f766f6c7461697265222c0a202020202020202022616374696f6e223a20224c4f47494e222c0a202020202020202022616374696f6e54657874223a20224c6f67696e222c0a202020202020202022736c6f74223a2022313034323536313535222c0a20202020202020202264617461223a207b0a2020202020202020202020202261646472657373223a20227374616b6531757977637a3037353477777075686d36786864706461367539656e796168616a35796e6c63396179356c346d6c6d736a74777a7134222c0a202020202020202020202020226576656e74223a202243465f53554d4d49545f323032335f5445535432222c0a202020202020202020202020226e6574776f726b223a20224d41494e222c0a20202020202020202020202022726f6c65223a2022564f544552220a20202020202020207d0a2020207d0a5840ed875c0a7067c6a50a73195d9e86e6d8d4908de0bb209126a5193ff974844332b307c9a4f9a38978b973b400268cf1bc40bb8326f2ad954ffa262f7c663ff706";
var key = "a5010102581de01d813fd4ab9c1e5f7a35da16f75c2e664edfb2a127fc17a4a7ebbfee03272006215820c4821499cef96eda9c00cdd0bfbcd2abf7d09436ad424ac7288653a8b4252014";

var p = new CIP30Verifier(sig, key);
var cip30Verifier = new CIP30Verifier(sig, key);

var result = p.verify();
var result = cip30Verifier.verify();

assertTrue(result.isValid());

assertTrue(result.getAddress().isPresent(), "Optional address is included in the signature...");

assertEquals("stake1uywcz0754wwpuhm6xhdpda6u9enyahaj5ynlc9ay5l4mlmsjtwzq4", result.getAddress(AddressFormat.TEXT).orElseThrow());
assertTrue(result.getMessage(MessageFormat.TEXT).contains("LOGIN"));
assertFalse(result.isHashed());
}

@Test
void validSignatureWithAddressAndPublicKey2() {
var sig = "84582aa201276761646472657373581de1b83abf370a14870fdfd6ccb35f8b3e62a68e465ed1e096c5a6f5b9d6a166686173686564f4565468697320697320612074657374206d657373616765584042e2bfc4e1929769a0501b884f66794ae3485860f42c01b70fac37f75e40af074c6b2a61b04c6cf8a493c0dced1455b4f1129dbf653ad9801c52ce49ff6d5a0e";
var key = "a40101032720062158202f1867873147cf53c442435723c17e83beeb8e2153851cd73ccfb1b5e68994a4";

var p = new CIP30Verifier(sig, key);
var cip30Verifier = new CIP30Verifier(sig, key);

var result = p.verify();
var result = cip30Verifier.verify();

assertTrue(result.isValid());

assertTrue(result.getAddress().isPresent(), "Optional address is included in the signature...");

assertArrayEquals(decodeHexString("e1b83abf370a14870fdfd6ccb35f8b3e62a68e465ed1e096c5a6f5b9d6"), result.getAddress().orElseThrow());
assertEquals(sig, p.getCOSESign1());
assertEquals(key, p.getCoseKey().orElseThrow());
assertEquals(sig, cip30Verifier.getCOSESign1());
assertEquals(key, cip30Verifier.getCoseKey().orElseThrow());

assertArrayEquals(decodeHexString("2f1867873147cf53c442435723c17e83beeb8e2153851cd73ccfb1b5e68994a4"), result.getEd25519PublicKey());
assertArrayEquals(decodeHexString("42e2bfc4e1929769a0501b884f66794ae3485860f42c01b70fac37f75e40af074c6b2a61b04c6cf8a493c0dced1455b4f1129dbf653ad9801c52ce49ff6d5a0e"), result.getEd25519Signature());
Expand All @@ -58,15 +59,16 @@ void validSignatureWithAddressAndPublicKey2() {

assertEquals("2f1867873147cf53c442435723c17e83beeb8e2153851cd73ccfb1b5e68994a4", result.getEd25519PublicKey(HEX));
assertEquals("846a5369676e617475726531582aa201276761646472657373581de1b83abf370a14870fdfd6ccb35f8b3e62a68e465ed1e096c5a6f5b9d640565468697320697320612074657374206d657373616765", result.getCosePayload(HEX));
assertFalse(result.isHashed());
}

@Test
void validSignatureWithAddressWithEmptyAddressAndPublicKey() {
var sig = "844ca20127676164647265737340a166686173686564f4565468697320697320612074657374206d6573736167655840a6cec002ecec0c7140a029feb9152edb444bbd8a58c6a0a4eceac6a0e30943e53f9ebe029d766a08b4198aaae71d656319fff25780eab816ab0937e6704bb001";
var key = "a401010327200621582052b92d51dc638d085f8663103d5509f0da29bbee418d75f1f2dc7025d69c9643";

var p = new CIP30Verifier(sig, key);
var result = p.verify();
var cip30Verifier = new CIP30Verifier(sig, key);
var result = cip30Verifier.verify();

assertTrue(result.isValid());
assertTrue(result.getAddress().isEmpty(), "address is NOT baked in (serialised in CIP-30).");
Expand All @@ -76,43 +78,46 @@ void validSignatureWithAddressWithEmptyAddressAndPublicKey() {

assertEquals("846a5369676e6174757265314ca2012767616464726573734040565468697320697320612074657374206d657373616765", result.getCosePayload(HEX));
assertEquals("a6cec002ecec0c7140a029feb9152edb444bbd8a58c6a0a4eceac6a0e30943e53f9ebe029d766a08b4198aaae71d656319fff25780eab816ab0937e6704bb001", result.getEd25519Signature(HEX));
assertFalse(result.isHashed());
}

@Test
void validSignatureWitPublicKeyWithEmptyMessage() {
var sig = "84582aa201276761646472657373581de01d813fd4ab9c1e5f7a35da16f75c2e664edfb2a127fc17a4a7ebbfeea166686173686564f44058406ad1822a992684ed10c2802f2c689516254511e92559f19d5288df96f05d002c560d02e0130f73fe2c762170b185d9f9193c3e1efec5f599cb99dfee662d4f0e";
var key = "a4010103272006215820c4821499cef96eda9c00cdd0bfbcd2abf7d09436ad424ac7288653a8b4252014";

var p = new CIP30Verifier(sig, key);
var result = p.verify();
var cip30Verifier = new CIP30Verifier(sig, key);
var result = cip30Verifier.verify();

assertTrue(result.isValid());

assertEquals("", result.getMessage(MessageFormat.TEXT), "message not available.");
assertEquals("stake_test1uqwcz0754wwpuhm6xhdpda6u9enyahaj5ynlc9ay5l4mlms4pyqyg", result.getAddress(AddressFormat.TEXT).orElseThrow());
assertFalse(result.isHashed());
}

@Test
void validSignatureWithoutPublicKeyInKid4_1() {
var sig = "84582aa201276761646472657373581de19090058641fa866e47d656f62be510cb10a90d48b0aafc868f25291ea166686173686564f458ae7b2270726f706f73616c223a2231366436623066393930663563353266393765323338363235623464356362633138333866326439353334313138313664323466643362613234363364666462222c227265717565737465644174223a223734363935373136222c22766f746572223a227374616b6531757867667170767867386167766d6a383665743076326c397a72393370326764667a6332346c797833756a6a6a3873663678763376227d5840ae514d8d246790d728855f69a0ae32b0c5e59f44e00183b20bf110a42d83fa7c209a290b60a65571648220fc36c4efcb9d472e319bf0afdae42fb078085e4206";

var p = new CIP30Verifier(sig);
var result = p.verify();
var cip30Verifier = new CIP30Verifier(sig);
var result = cip30Verifier.verify();

assertFalse(result.isValid());
assertTrue(result.getAddress().isPresent(), "address is available.");

assertEquals("stake1uxgfqpvxg8agvmj86et0v2l9zr93p2gdfzc24lyx3ujjj8sf6xv3v", result.getAddress(AddressFormat.TEXT).orElseThrow());

assertEquals("{\"proposal\":\"16d6b0f990f5c52f97e238625b4d5cbc1838f2d953411816d24fd3ba2463dfdb\",\"requestedAt\":\"74695716\",\"voter\":\"stake1uxgfqpvxg8agvmj86et0v2l9zr93p2gdfzc24lyx3ujjj8sf6xv3v\"}", result.getMessage(MessageFormat.TEXT));
assertFalse(result.isHashed());
}

@Test
void validSignatureWithoutPublicKeyInKid4_2() {
var sig = "84582aa201276761646472657373581de19090058641fa866e47d656f62be510cb10a90d48b0aafc868f25291ea166686173686564f458ae7b2270726f706f73616c223a2231366436623066393930663563353266393765323338363235623464356362633138333866326439353334313138313664323466643362613234363364666462222c227265717565737465644174223a223734363935373136222c22766f746572223a227374616b6531757867667170767867386167766d6a383665743076326c397a72393370326764667a6332346c797833756a6a6a3873663678763376227d5840ae514d8d246790d728855f69a0ae32b0c5e59f44e00183b20bf110a42d83fa7c209a290b60a65571648220fc36c4efcb9d472e319bf0afdae42fb078085e4206";

var p = new CIP30Verifier(sig, (String) null);
var result = p.verify();
var cip30Verifier = new CIP30Verifier(sig, (String) null);
var result = cip30Verifier.verify();

assertFalse(result.isValid());
assertEquals(UNKNOWN, result.getValidationError().orElseThrow());
Expand All @@ -121,15 +126,16 @@ void validSignatureWithoutPublicKeyInKid4_2() {
assertEquals("stake1uxgfqpvxg8agvmj86et0v2l9zr93p2gdfzc24lyx3ujjj8sf6xv3v", result.getAddress(AddressFormat.TEXT).orElseThrow());

assertEquals("{\"proposal\":\"16d6b0f990f5c52f97e238625b4d5cbc1838f2d953411816d24fd3ba2463dfdb\",\"requestedAt\":\"74695716\",\"voter\":\"stake1uxgfqpvxg8agvmj86et0v2l9zr93p2gdfzc24lyx3ujjj8sf6xv3v\"}", result.getMessage(MessageFormat.TEXT));
assertFalse(result.isHashed());
}

@Test
void checkIfExceptionsAreThrown() {
var sig = "844ca20127676164647265737340a166686173686564f4565468697320697320612074657374206d6573736167655840a6cec002ecec0c7140a029feb9152edb444bbd8a58c6a0a4eceac6a0e30943e53f9ebe029d766a08b4198aaae71d656319fff25780eab816ab0937e6704bb001";
var key = "a401010327200621582052b92d51dc638d085f8663103d5509f0da29bbee418d75f1f2dc7025d69c9643";

var p = new CIP30Verifier(sig, key);
var result = p.verify();
var cip30Verifier = new CIP30Verifier(sig, key);
var result = cip30Verifier.verify();

assertThrows(IllegalArgumentException.class, () -> {
result.getEd25519PublicKey(null, UTF_8);
Expand All @@ -148,10 +154,11 @@ void invalidSignatureCheck() {
var sig = "84582aa201276761646472657373581de1b8344f370a14870fdfd6ccb35f8b3e62a68e465ed1e096c5a6f5b9d6a166686173686564f4565468697320697320612074657374206d657373616765584042e2bfc4e1929769a0501b884f66794ae3485860f42c01b70fac37f75e40af074c6b2a61b04c6cf8a493c0dced1455b4f1129dbf653ad9801c52ce49ff6d5a0e";
var key = "a401010327200621582052b92d51dc638d085f8663103d5509f0da29bbee418d75f1f2dc7025d69c9643";

var p = new CIP30Verifier(sig, key);
var result = p.verify();
var cip30Verifier = new CIP30Verifier(sig, key);
var result = cip30Verifier.verify();

assertFalse(result.isValid(), "signature is invalid and should fail to validate");
assertFalse(result.isHashed());
}

@Test
Expand All @@ -160,36 +167,43 @@ void publicKeysMismatch() {
//var key = "c4821499cef96eda9c00cdd0bfbcd2abf7d09436ad424ac7288653a8b4252014"; // correct key
var key = "a4010103272006215820a5f73966e73d0bb9eadc75c5857eafd054a0202d716ac6dde00303ee9c0019e3"; // incorrect key

var p = new CIP30Verifier(sig, key);
var cip30Verifier = new CIP30Verifier(sig, key);

var result = p.verify();
var result = cip30Verifier.verify();

assertFalse(result.isValid(), "ED 25519 public key within signature doesn't match with passed in key");
assertFalse(result.isHashed());
}

@Test
void publicKeysMismatch2() {
var sig = "84584aa3012704581de01d813fd4ab9c1e5f7a35da16f75c2e664edfb2a127fc17a4a7ebbfee6761646472657373581de01d813fd4ab9c1e5f7a35da16f75c2e664edfb2a127fc17a4a7ebbfeea166686173686564f458e67b22616374696f6e223a2246554c4c5f4d455441444154415f5343414e222c22616374696f6e54657874223a2246554c4c5f4d455441444154415f5343414e222c22757269223a22687474703a2f2f6c6f63616c686f73743a383038302f6170692f61646d696e2f66756c6c2d6d657461646174612d7363616e222c2264617461223a7b2261646472657373223a227374616b655f7465737431757177637a3037353477777075686d36786864706461367539656e796168616a35796e6c63396179356c346d6c6d73347079717967222c226e6574776f726b223a2250524550524f44227d7d5840b69fbe15912f0dabd46f6a3a5eebae58acefc7c80e82da70803c52860293d1fa53c9cb0a04758cf36fbec726835cac5e519b60ebd2c2cd04d66ad94c46b07604";
var key = "a4010103272006215820c4821499cef96eda9c00cdd0bfbcd2abf7d09436ad424ac7288653a8b4252014";

var p = new CIP30Verifier(sig, key);
var cip30Verifier = new CIP30Verifier(sig, key);

var result = p.verify();
var result = cip30Verifier.verify();

assertFalse(result.isValid(), "ED 25519 public key within signature doesn't match with passed in key");
assertFalse(result.isHashed());
}


@Test
// typhon 3.0.14 wallet case -> we should be ignoring now KID4 in protected headers
void signatureWithOptionalKid4SetIncorrectly() {
var sig = "84584da30127045820c4821499cef96eda9c00cdd0bfbcd2abf7d09436ad424ac7288653a8b42520146761646472657373581de01d813fd4ab9c1e5f7a35da16f75c2e664edfb2a127fc17a4a7ebbfeea166686173686564f44568656c6c6f5840c0d5f54ab847cb78e8aeff10691bb1dcc5eec9a52fbf9011a0cfe89a51c53c2e22f408708da3a2fb35bf8f518f63e79ae8388f12b198cb1bdbd0d40b72081b0d";
var key = "a4010103272006215820c4821499cef96eda9c00cdd0bfbcd2abf7d09436ad424ac7288653a8b4252014";
// hardware wallet, e.g. ledger with Eternl
void signatureWithHashed() {
var sig = "84582aa201276761646472657373581de103d205532089ad2f7816892e2ef42849b7b52788e41b3fd43a6e01cfa166686173686564f5581c1c1afc33a1ed48205eadcbbda2fc8e61442af2e04673616f21b7d0385840954858f672e9ca51975655452d79a8f106011e9535a2ebfb909f7bbcce5d10d246ae62df2da3a7790edd8f93723cbdfdffc5341d08135b1a40e7a998e8b2ed06";
var key = "a4010103272006215820c13745be35c2dfc3fa9523140030dda5b5346634e405662b1aae5c61389c55b3";

var p = new CIP30Verifier(sig, key);
var cip30Verifier = new CIP30Verifier(sig, key);

var result = p.verify();
var result = cip30Verifier.verify();

assertTrue(result.isValid());

assertEquals("stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz", result.getAddress(AddressFormat.TEXT).orElseThrow());
assertEquals("1c1afc33a1ed48205eadcbbda2fc8e61442af2e04673616f21b7d038", result.getMessage(HEX));
assertTrue(result.isHashed());
}

}
Loading