aries-askar icon indicating copy to clipboard operation
aries-askar copied to clipboard

Signature inconsistencies between python and js wrapper?

Open dbluhm opened this issue 1 year ago • 7 comments

We're having trouble getting the JS wrapper for Askar and the Python wrapper for Askar to generate the same signature over a message. Pretty stumped given that ACA-Py and Credo verify signed values from each other during connections and didexchange... Any insights would be appreciated.

We discovered this while trying to verify a JWT from ACA-Py in a node environment using Askar.

We can reliably generate the same signature on both sides but they're different from each other. Because of this, python can verify its own sig, js can verify its own sig, but python can't verify js sig and vice-versa.

To reproduce:

Python

Prereqs:

pip install aries-askar base58
python test.py

test.py:

from aries_askar import Key, KeyAlg
from base58 import b58decode

key = Key.from_seed(KeyAlg.ED25519, b58decode("7V9EE8d4DfQe5Co2usE6S4AXcwETiTmnxMvUsYxb7ciH"))
print(key.get_public_bytes().hex())
print(key.get_secret_bytes().hex())

message = "Hello, world!"
print(message.encode().hex())

sig = key.sign_message(
    message.encode("ascii"),
).hex()
print(sig)

from_js_sig = "134712924a888929381eeafad9060f60c7d7efb0f4b400144724d418ac6ce58efc3859ead2e77feffd564c4191938523e82eeaf29be018dfa17785f1e990e70d"
print(from_js_sig)

verified = key.verify_signature(
    message.encode(),
    bytes.fromhex(from_js_sig),
)
print(verified)

verified = key.verify_signature(
    message.encode(),
    bytes.fromhex(sig),
)
print(verified)

Output:

30470ef3d547407194b02ab82ecc161e9f03cbda67ea9ea1d89880a8ba884f41
23872b2c73e202f4b9cea52e75440a87d2f14e5b58a2cbdace1e1d2839cbe68c
48656c6c6f2c20776f726c6421
c2dc2b5ab8b412b5efe244f68aaf42ad951e7d461bf58b1aa5b003781b72a00b72abf6d5c06f9a455e3afd4bbcfbb97277eb70c8dae029e3992518ae72e7b406
134712924a888929381eeafad9060f60c7d7efb0f4b400144724d418ac6ce58efc3859ead2e77feffd564c4191938523e82eeaf29be018dfa17785f1e990e70d
False
True

JS/TS

Prereqs:

npm install @hyperledger/aries-askar-nodejs bs58
npx ts-node test.ts

test.ts:

import {Key, KeyAlgs} from '@hyperledger/aries-askar-nodejs'
import base58 from 'bs58'

const encodedKey = "7V9EE8d4DfQe5Co2usE6S4AXcwETiTmnxMvUsYxb7ciH"
const publicKey = base58.decode(encodedKey)
const key = Key.fromSeed({algorithm: KeyAlgs.Ed25519, seed: publicKey})
console.log(Buffer.from(key.publicBytes).toString('hex'))
console.log(Buffer.from(key.secretBytes).toString('hex'))

const message = "Hello, world!"
console.log(Buffer.from(message).toString('hex'))

const pythonSigned = "c2dc2b5ab8b412b5efe244f68aaf42ad951e7d461bf58b1aa5b003781b72a00b72abf6d5c06f9a455e3afd4bbcfbb97277eb70c8dae029e3992518ae72e7b406"
console.log(pythonSigned)

const signed = key.signMessage({message: Buffer.from(message, "ascii")})
console.log(Buffer.from(signed).toString('hex'))

let verified = key.verifySignature({message: Buffer.from(message), signature: signed})
console.log(verified)

verified = key.verifySignature({message: Buffer.from(message), signature: Buffer.from(pythonSigned, "hex")})
console.log(verified)

Output:

30470ef3d547407194b02ab82ecc161e9f03cbda67ea9ea1d89880a8ba884f41
23872b2c73e202f4b9cea52e75440a87d2f14e5b58a2cbdace1e1d2839cbe68c
48656c6c6f2c20776f726c6421
c2dc2b5ab8b412b5efe244f68aaf42ad951e7d461bf58b1aa5b003781b72a00b72abf6d5c06f9a455e3afd4bbcfbb97277eb70c8dae029e3992518ae72e7b406
134712924a888929381eeafad9060f60c7d7efb0f4b400144724d418ac6ce58efc3859ead2e77feffd564c4191938523e82eeaf29be018dfa17785f1e990e70d
true
false

dbluhm avatar Apr 11 '24 20:04 dbluhm

We're wondering if it has to do with the conversion taking place here: https://github.com/hyperledger/aries-askar/blob/main/wrappers/javascript/aries-askar-nodejs/src/ffi/conversion.ts#L23

But honestly have no idea

dbluhm avatar Apr 11 '24 20:04 dbluhm

cc @KolbyRKunz

Hoping @andrewwhitehead or @genaris might be able to help us figure out what's going on here.

dbluhm avatar Apr 11 '24 20:04 dbluhm

There's something weird going on with the wrapper, as it doesn't matter which value I pass in and it will verify in the JS wrapper

const verified = key.verifySignature({
  message: Buffer.from(message + "random"),
  signature: signed,
});

It does seem to also have different behaviour based on whether you pass in Uint8Array vs Buffer.

TimoGlastra avatar Apr 11 '24 21:04 TimoGlastra

Passing in correct Uint8arrays does correctly return true/false, so I think it has something to do with buffers. But still really weird that it will return true (@berendsliedrecht that seems like a very serious bug)

TimoGlastra avatar Apr 11 '24 21:04 TimoGlastra

This way it verifies in Node.JS

const pythonSigned =
  "c2dc2b5ab8b412b5efe244f68aaf42ad951e7d461bf58b1aa5b003781b72a00b72abf6d5c06f9a455e3afd4bbcfbb97277eb70c8dae029e3992518ae72e7b406";

verified = key.verifySignature({
  message: new TextEncoder().encode(message),
  signature: new Uint8Array(Buffer.from(pythonSigned, "hex")),
});

TimoGlastra avatar Apr 11 '24 21:04 TimoGlastra

Also this way:

verified = key.verifySignature({
  message: new Uint8Array(Buffer.from(message)),
  signature: new Uint8Array(Buffer.from(pythonSigned, "hex")),
});

TimoGlastra avatar Apr 11 '24 21:04 TimoGlastra

This line is missing a call to uint8arrayToByteBufferStruct.

Maybe it always verifies in that case as the pointer being passed is empty and thus it then only verifies the signature? I'm still not sure why it will return true if an incorrect buffer is passed.

I think the reason we're not running into this issue in Credo is because we use a different buffer implementation, and theu the instance Buffer will not trigger. So really only a bug if you're running it as Askar and not using Credo

TimoGlastra avatar Apr 11 '24 21:04 TimoGlastra