I am playing around with the nami cardano wallet. I am able to link the wallet in the frontend and now want to link it to the backend.
First action the frontend sends an auth request with the cardano address to the backend. The backend returns a nonce to the frontend. The front end then lets nami wallet sign this nonce and sends the signature back to the backend.
In this step, the backend should validate if the signature is valid.
Now, how can I let the backend validate this signature?
This is the information I have access to:
- balance
- change address
- network id
- reward address
- unused addresses
- used addresses
Can I use this info to validate the signature? Or do I need to access the blockchain from the backend code?
(I will most likely be adding blockfrost api later, so nami-wallet + blockfrost will be how I access the blockchain. Unless there are other libraries available for the backend (most likely node.js), nami+blockfrost will be how I access the blockchain.
I am also using @emurgo/cardano-serialization-lib-browser - maybe here something is possible?
Based on response below I made a quick node script:
const {COSESign1, Label} = require("./extra_modules/@emurgo/cardano-message-signing-nodejs");
const {Address} = require("@emurgo/cardano-serialization-lib-nodejs");
const {Buffer} = require('buffer');
let signedRaw = "845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609";
let message = COSESign1.from_bytes(Buffer.from(signedRaw, 'hex'));
let headers = message.headers().protected().deserialized_headers();
let address = Address.from_bytes(headers.header(Label.new_text('address')).as_bytes());
let publicKey = headers.key_id();
let signature = message.signature();
OK, finally got a working solution, sharing a node script that works for the next person with the same problem:
const {COSESign1, Label} = require("./extra_modules/@emurgo/cardano-message-signing-nodejs");
const {Address, Ed25519Signature, PublicKey, BaseAddress, StakeCredential, RewardAddress} = require('@emurgo/cardano-serialization-lib-nodejs');
const {Buffer} = require('buffer');
class SignedData
{
constructor(signed)
{
let message = COSESign1.from_bytes(Buffer.from(Buffer.from(signed, 'hex'), 'hex'));
let headermap = message.headers().protected().deserialized_headers();
this.headers = {
algorithmId: headermap.algorithm_id().as_int().as_i32(),
address: Address.from_bytes(headermap.header(Label.new_text('address')).as_bytes()),
publicKey: PublicKey.from_bytes(headermap.key_id())
};
this.payload = message.payload();
this.signature = Ed25519Signature.from_bytes(message.signature());
this.data = message.signed_data().to_bytes();
}
verify(address, payload)
{
if (!this.verifyPayload(payload)) {
throw new Error('Payload does not match');
}
if (!this.verifyAddress(address)) {
throw new Error('Could not verify because of address mismatch');
}
return this.headers.publicKey.verify(this.data, this.signature);
};
verifyPayload(payload)
{
return Buffer.from(this.payload, 'hex').compare(Buffer.from(payload, 'hex'));
}
verifyAddress(address)
{
const checkAddress = Address.from_bech32(address);
if (this.headers.address.to_bech32() !== checkAddress.to_bech32()) {
console.log('FASLE1');
return false;
}
// check if BaseAddress
try {
const baseAddress = BaseAddress.from_address(this.headers.address);
//reconstruct address
const paymentKeyHash = this.headers.publicKey.hash();
const stakeKeyHash = baseAddress.stake_cred().to_keyhash();
const reconstructedAddress = BaseAddress.new(
checkAddress.network_id(),
StakeCredential.from_keyhash(paymentKeyHash),
StakeCredential.from_keyhash(stakeKeyHash)
);
return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
} catch (e) {
}
try {
const stakeKeyHash = this.headers.address.hash();
const reconstructedAddress = RewardAddress.new(
checkAddress.network_id(),
StakeCredential.from_keyhash(stakeKeyHash)
);
return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
} catch (e) {
}
return false;
};
}
let signed = "845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609";
let signedData = new SignedData(signed);
let address = 'addr1q8fwhqcudjk54wnsp6e4lp5kd7l07xwsw725gv8r9nn9ard8ngatap85e6qhltgxdtxpgdd79l7xh47uutkpe3kv5m9smpyc34';
let payload = 'hello';
console.log(signedData.verify(address, payload));