aries-askar
aries-askar copied to clipboard
Signature inconsistencies between python and js wrapper?
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
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
cc @KolbyRKunz
Hoping @andrewwhitehead or @genaris might be able to help us figure out what's going on here.
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.
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)
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")),
});
Also this way:
verified = key.verifySignature({
message: new Uint8Array(Buffer.from(message)),
signature: new Uint8Array(Buffer.from(pythonSigned, "hex")),
});
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