cardano-foundation / cardano-verify-datasignature

A lightweight typescript library to verify a cip30 datasignature
Apache License 2.0
17 stars 7 forks source link

BUG - Lib failing on null/nil payloads #11

Closed gitmachtl closed 1 year ago

gitmachtl commented 1 year ago

Null/Nil payloads

According to CIP-8, the payload in a COSE_Sign1 signature can also be null / nil, if the payload/message is known by both entities (sender/receiver). https://github.com/cardano-foundation/CIPs/blob/master/CIP-0008/README.md#overview

Currently lib v1.0.6 is failing to read in such a COSE_Sign1 signature if the payload is provided via the 3rd parameter.

Example:

{
  "workMode": "sign-cip30",
  "addressHex": "617863b5c43bdf0a06608abc82f0573a549714ff69166074dcdde393d8",
  "addressType": "payment enterprise",
  "addressNetwork": "mainnet",
  "inputDataHex": "48656c6c6f20776f726c64",
  "isHashed": "false",
  "signDataHex": "846a5369676e617475726531582aa201276761646472657373581d617863b5c43bdf0a06608abc82f0573a549714ff69166074dcdde393d8404b48656c6c6f20776f726c64",
  "signDataSignature": "fc58155f0cee05bc00e7299af1df1f159ac82a46a055786b259657934eff346eec81349d4678ceabc79f213c66a2bdbfd4ea5d9ebdc630bee5ac9cce75cfc001",
  "secretKey": "16275bd6647f94a53e9fe1c71439a258a03c13cadf32935ed5388972ebd7e53f",
  "publicKey": "755b017578b701dc9ddd4eaee67015b4ca8baf66293b7b1d204df426c0ceccb9",
  "output": {
    "signedMessage": "cms_hFgqogEnZ2FkZHJlc3NYHWF4Y7XEO98KBmCKvILwVzpUlxT_aRZgdNzd45PYoWZoYXNoZWT09lhA_FgVXwzuBbwA5yma8d8fFZrIKkagVXhrJZZXk07_NG7sgTSdRnjOq8efITxmor2_1Opdnr3GML7lrJzOdc_AAQ525z8A",
    "COSE_Sign1_hex": "84582aa201276761646472657373581d617863b5c43bdf0a06608abc82f0573a549714ff69166074dcdde393d8a166686173686564f4f65840fc58155f0cee05bc00e7299af1df1f159ac82a46a055786b259657934eff346eec81349d4678ceabc79f213c66a2bdbfd4ea5d9ebdc630bee5ac9cce75cfc001",
    "COSE_Key_hex": "a4010103272006215820755b017578b701dc9ddd4eaee67015b4ca8baf66293b7b1d204df426c0ceccb9"
  }
}

Address: addr1v9ux8dwy800s5pnq327g9uzh8f2fw98ldytxqaxumh3e8kqumfr6d Payload: Hello world Payload is not hashed

console.log(verifyDataSignature(COSE_Sign1_hex, COSE_Key_hex ));
false // sure its false, because there is no payload data to do the verification with

console.log(verifyDataSignature(COSE_Sign1_hex, COSE_Key_hex, 'Hello world' ));
TypeError: Cannot read properties of null (reading 'toString')
    at u (/node_modules/@cardano-foundation/cardano-verify-datasignature/dist/index.js:2:433559)

The content of the COSE_Sign1:

84                                     # array(4)
   58 2a                               #   bytes(42)
      a201276761646472657373581d617863 #     "\xa2\x01\'gaddressX\x1daxc"
      b5c43bdf0a06608abc82f0573a549714 #     "\xb5\xc4;\xdf\n\x06`\x8a\xbc\x82\xf0W:T\x97\x14"
      ff69166074dcdde393d8             #     "\xffi\x16`t\xdc\xdd\xe3\x93\xd8"
   a1                                  #   map(1)
      66                               #     text(6)
         686173686564                  #       "hashed"
      f4                               #     false, simple(20)
   f6                                  #   null, simple(22)
   58 40                               #   bytes(64)
      fc58155f0cee05bc00e7299af1df1f15 #     "\xfcX\x15_\x0c\xee\x05\xbc\x00\xe7)\x9a\xf1\xdf\x1f\x15"
      9ac82a46a055786b259657934eff346e #     "\x9a\xc8*F\xa0Uxk%\x96W\x93N\xff4n"
      ec81349d4678ceabc79f213c66a2bdbf #     "\xec\x814\x9dFx\xce\xab\xc7\x9f!<f\xa2\xbd\xbf"
      d4ea5d9ebdc630bee5ac9cce75cfc001 #     "\xd4\xea]\x9e\xbd\xc60\xbe\xe5\xac\x9c\xceu\xcf\xc0\x01"

Expected behavior

The lib should use the given payload/message to do the verification with. Basically replacing the one in the given COSE_Sign1 if its null/nil. If there is a payload/message included in COSE_Sign1, the lib should also compare the two and only return true if all conditions matches.

gitmachtl commented 1 year ago

Strica released v1.0.4 of there CIP-8 lib now with payload null support. I am keeping Ashish busy. 😄 That might also fix your implementation?

fabianbormann commented 1 year ago

Thanks for your support. I also had to add an additional null check. 😊

fabianbormann commented 1 year ago

@gitmachtl could you please check if version 1.0.8 now works as expected? 😊
Thanks, Fabian

gitmachtl commented 1 year ago

Hi,

i tried COSE_Sign1 without a payload (null/nil), and the lib v1.0.8 is still failing to return true for this testcase:

{
  "workMode": "sign-cip30",
  "addressHex": "e0c13582aec9a44fcc6d984be003c5058c660e1d2ff1370fd8b49ba73f",
  "addressType": "stake",
  "addressNetwork": "testnet",
  "inputDataHex": "48656c6c6f20776f726c64",
  "isHashed": "false",
  "signDataHex": "846a5369676e617475726531582aa201276761646472657373581de0c13582aec9a44fcc6d984be003c5058c660e1d2ff1370fd8b49ba73f404b48656c6c6f20776f726c64",
  "signature": "0a0dd23e867292a4c2eb692f63016e3f61294686f672065fcc377f665cff6b25c430619060b536073cfd2355ab6c6bcec9d7ecbfb588f7b0aa5967f1b8559300",
  "secretKey": "c14ef0cc5e352446d6243976f51e8ffb2ae257f2a547c4fba170964a76501e7a",
  "publicKey": "9be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b27",
  "output": {
    "signedMessage": "cms_hFgqogEnZ2FkZHJlc3NYHeDBNYKuyaRPzG2YS-ADxQWMZg4dL_E3D9i0m6c_oWZoYXNoZWT09lhACg3SPoZykqTC62kvYwFuP2EpRob2cgZfzDd_Zlz_ayXEMGGQYLU2Bzz9I1WrbGvOydfsv7WI97CqWWfxuFWTAAPaOFaQ",
    "COSE_Sign1_hex": "84582aa201276761646472657373581de0c13582aec9a44fcc6d984be003c5058c660e1d2ff1370fd8b49ba73fa166686173686564f4f658400a0dd23e867292a4c2eb692f63016e3f61294686f672065fcc377f665cff6b25c430619060b536073cfd2355ab6c6bcec9d7ecbfb588f7b0aa5967f1b8559300",
    "COSE_Key_hex": "a40101032720062158209be513df12b3fabe7c1b8c3f9fab0968eb2168d5689bf981c2f7c35b11718b27"
  }
}

Payload: Hello world Address: stake_test1urqntq4wexjylnrdnp97qq79qkxxvrsa9lcnwr7ckjd6w0cr04y4p

COSE_Sign1 content without the payload:

84                                     # array(4)
   58 2a                               #   bytes(42)
      a201276761646472657373581de0c135 #     "\xa2\x01\'gaddressX\x1d\xe0\xc15"
      82aec9a44fcc6d984be003c5058c660e #     "\x82\xae\xc9\xa4O\xccm\x98K\xe0\x03\xc5\x05\x8cf\x0e"
      1d2ff1370fd8b49ba73f             #     "\x1d/\xf17\x0f\xd8\xb4\x9b\xa7?"
   a1                                  #   map(1)
      66                               #     text(6)
         686173686564                  #       "hashed"
      f4                               #     false, simple(20)
   f6                                  #   null, simple(22)
   58 40                               #   bytes(64)
      0a0dd23e867292a4c2eb692f63016e3f #     "\n\r\xd2>\x86r\x92\xa4\xc2\xebi/c\x01n?"
      61294686f672065fcc377f665cff6b25 #     "a)F\x86\xf6r\x06_\xcc7\x7ff\\\xffk%"
      c430619060b536073cfd2355ab6c6bce #     "\xc40a\x90`\xb56\x07<\xfd#U\xablk\xce"
      c9d7ecbfb588f7b0aa5967f1b8559300 #     "\xc9\xd7\xec\xbf\xb5\x88\xf7\xb0\xaaYg\xf1\xb8U\x93\x00"
console.log(verifyDataSignature( COSE_Sign1_cbor_hex, COSE_Key_cbor_hex, 'Hello world') );
false

Same test but with the payload:

84                                     # array(4)
   58 2a                               #   bytes(42)
      a201276761646472657373581de0c135 #     "\xa2\x01\'gaddressX\x1d\xe0\xc15"
      82aec9a44fcc6d984be003c5058c660e #     "\x82\xae\xc9\xa4O\xccm\x98K\xe0\x03\xc5\x05\x8cf\x0e"
      1d2ff1370fd8b49ba73f             #     "\x1d/\xf17\x0f\xd8\xb4\x9b\xa7?"
   a1                                  #   map(1)
      66                               #     text(6)
         686173686564                  #       "hashed"
      f4                               #     false, simple(20)
   4b                                  #   bytes(11)
      48656c6c6f20776f726c64           #     "Hello world"
   58 40                               #   bytes(64)
      0a0dd23e867292a4c2eb692f63016e3f #     "\n\r\xd2>\x86r\x92\xa4\xc2\xebi/c\x01n?"
      61294686f672065fcc377f665cff6b25 #     "a)F\x86\xf6r\x06_\xcc7\x7ff\\\xffk%"
      c430619060b536073cfd2355ab6c6bce #     "\xc40a\x90`\xb56\x07<\xfd#U\xablk\xce"
      c9d7ecbfb588f7b0aa5967f1b8559300 #     "\xc9\xd7\xec\xbf\xb5\x88\xf7\xb0\xaaYg\xf1\xb8U\x93\x00"
console.log(verifyDataSignature( COSE_Sign1_cbor_hex, COSE_Key_cbor_hex, 'Hello world') );
true
fabianbormann commented 1 year ago

Ah ok got it. I may misunderstood the expected case first. I need to look tomorrow into it because I think it should be end in just calling the pubkey.verifiy function from StricaHQ 🤔 but maybe I'm wrong

fabianbormann commented 1 year ago

@gitmachtl would you suggest to just ignore the optional clear text "message" argument if the payload is null and just perform the coseSign1.verifySignature which would return true?

I would expect that if the payload is null a function call with an optionally provided message should return false, because I can only check the clear text/hashed message if the payload has been provided so the user of this function should not make use of this optional message argument in case the payload is known by both entities and there is no need to check it.

gitmachtl commented 1 year ago

Hi, the Signature in the COSE_Sign1 was generated with a message. Like a normal generation. But at the end, the COSE_Sign1 is not composed with that payload included. Thats the only difference. Normally you take the payload from the COSE_Sign1, also compose the Sig_structure that includes the payload and use that as the signData for signing or verification. In case of a null payload in the COSE_Sign1 this only affects the output, but not how the signature itself is generated. The reason is that this method can be used if both (the sender and the receiver) know the message. Or the message was provided via a webservice, etc. In that case there is no need to include it in the COSE_Sign1 again. So if there is a null payload in COSE_Sign1, you need to do the verification like normal, but provide the content of the payload via the 3rd parameter 'message'. In that scenario the normal check with only the COSE_Sign1+COSE_Key would always fail correct. But with the additional message parameter and/or the optional address if can be verified to be true/false.

fabianbormann commented 1 year ago

Ahh, I think I understand now - but please correct me if I'm still wrong^^: In case the payload is null the expected output would be always false except for the scenario that a message parameter would be provided because I would replace/(re-add) the payload field of the COSE_Sign1 with this message so that the final check could return true (in case that message was correct)

gitmachtl commented 1 year ago

Yes correct 😄

fabianbormann commented 1 year ago

Nice 👍 .. I'm so close just have a problem with the byte span, but I think I will solve this in the next minutes ^^

when I set the buffer manually

payload: <Buffer 48 65 6c 6c 6f 20 77 6f 72 6c 64>,

vs.

payload: <Buffer 48 65 6c 6c 6f 20 77 6f 72 6c 64, byteSpan: [ 54, 66 ], getByteSpan: [Function (anonymous)]>,

what it should be

fabianbormann commented 1 year ago

Ah this was not the error. Had a wrong key in my test case. Works now :) I will push in a few min