Issue with message signature verification using PHP

Hi, I hope you can advise me on this one. Thank you in advance.

I’m trying to verify a signed message using PHP in backend

this is the reactjs code to sign it


import ConnectButton from '@/components/Concordium/Connect';
const signature = await connection.signMessage(accountAddress, stringMessage(`${nonce}`));

const signedNonce = Buffer.from(
  typeof signature === 'object' && signature !== null ? JSON.stringify(signature) : signature,
  'utf-8'
).toString('base64');

and this is a prototype to verify it with php

<?php
// Inputs
$message = "Login with nonce: 92207783";
$signature = 'bf1feaee0373734ced29c14b3f0e8aaea46cb7b1fc42ee9b4824e648fad62760ff9383477aed713915db9ce7a23e04424e77aec699814b3d686f1b5cda1cbf07';

 $binarySignature  = hex2bin(($signature)); // Example: signature is passed as array of base64 strings

$publicKeyBase64 = "gJd16o0GoVNte8gxbk82C/GthMR8WeT4g43gZ26JEQI=";

// Decode the public key
$publicKey = base64_decode($publicKeyBase64);
 
// Verify public key length
if (strlen($publicKey) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES) {
    die("Error: Public key must be " . SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES . " bytes long. Decoded key length: " . strlen($publicKey) . " bytes.");
}

 
// Verify the signature
$isValid = sodium_crypto_sign_verify_detached($binarySignature, $message, $publicKey);

if ($isValid) {
    echo "GOOD";
} else {
    echo "Signature is invalid.";
}

but the signature is always invalid. So I guess I’m using the wrong public key . I’m using the ed25519 key from the getaccountinfo api response

{
    "sequenceNumber": {
        "value": "1"
    },
    "amount": {},
    "schedule": {
        "total": {}
    },
    "creds": {
        "0": {
            "normal": {
                "keys": {
                    "keys": {
                        "0": {
                            "ed25519Key": "gJd16o0GoVNte8gxbk82C/GthMR8WeT4g43gZ26JEQI="
                        }
                    },
                    "threshold": {
                        "value": 1
                    }
                },
                "credId": {
                    "value": "r4v6CH4PwCqB7OT3F3T99w7pkF/oylORg87Q1ET7SMK2YJKDdlz/ZH49ysF+E8mH"
                },
                "ipId": {
                    "value": 1
                },
                "policy": {
                    "createdAt": {
                        "year": 2024,
                        "month": 12
                    },
                    "validTo": {
                        "year": 2029,
                        "month": 12
                    }
                },
                "arThreshold": {
                    "value": 2
                },
                "arData": {
                    "3": {
                        "encIdCredPubShare": "q/0sJsP3zvGsin9s1DDl/eKLdt1s5JMN259bPH3c4sp6uGQh0//YAii3XSFzdolAo2c4zg5sV3pH8ejfUn4ivYZfI7DT+SsLX+sofXaNmUXxm4HgTDCBvLKhailByOjE"
                    },
                    "2": {
                        "encIdCredPubShare": "seEGatLuvRmmluZh5azm4rPkPxtdBuvXGem5tena5uPzmMhlcjLXytDrbY+0Z8/Dp8D41rI+ddggWjW3Uz/dDifvMNj8yCtGr6rbnzLJtQQoQlOJvftrTSmdaGzwNWtl"
                    },
                    "1": {
                        "encIdCredPubShare": "sj9h0Pzan7RmMqRmqfEoz1fxKrpE3uozz3tI1xUEayMJODy7v43ky3quVjsCPwVtpdYQWLX5hR7UFj8bI6NDvWpHH+Xj252mmjUQ6G9XxMqIhCvi/QTq64Bvu/h2Ltdl"
                    }
                },
                "commitments": {
                    "prf": {
                        "value": "qCIKPtMwYi+pznn6q7A5mgI7NMmP9GVBq+0IZR7BX2SWnVtcUJZ/3Rygom9WJ7kR"
                    },
                    "credCounter": {
                        "value": "qFiSBm+lou/8Jw5W8dbzICkmwrIcoGD0SAfAN/uofGklpNTLl9KblpF3wbFn5xMQ"
                    },
                    "maxAccounts": {
                        "value": "rciHXkfFIec1wbWB1m+tq4fpu0PZr7eAbwxSeyQA9t0bmz0/nkP1ZRegpk50SItg"
                    },
                    "attributes": {
                        "5": {
                            "value": "rjdwg6ojp2pkIo4EgDPTNk6fIndOo1IvcwrdwYOZYcN/YxDe3DlAaHnBGLDNJDfz"
                        },
                        "0": {
                            "value": "iEH6lfa0/z79nyVSOXtSuh+B1qosb+b4slfJDGMUxH5B8kF0lOkJuQPTnG2lqkqy"
                        },
                        "2": {
                            "value": "jMWv8vK0eeFVJq4R26NsCL9XCBL2s6vv9wpx76NagI8uMAV1d0t3luzV4ejw6uad"
                        },
                        "10": {
                            "value": "tsNJ9a6Wy+f5M093SNSC7lbDkZHavu6rCP3tPh5Wy7XJu8V8UWd/fmTT9oCal/ja"
                        },
                        "8": {
                            "value": "tTfwatr5Che22TTDpAaL0xtc1iFre8fU/n+EBDM3Gj8rjOg59U4s5CeSNjAABWx9"
                        },
                        "3": {
                            "value": "tbZgmlRp91d/QneMrraQGLhBAicm08aE3DlRziCl/q09EfIEtzPkTEk0OMKzJEr/"
                        },
                        "6": {
                            "value": "qJpa1ZHXusdzMaFgbi5rScp0COfnoygw9SaRd6/f0FHvKW0stRLj3+LEYUHwK+MP"
                        },
                        "7": {
                            "value": "ri5MD4WlB4Qxl2OVU+9ur+nLA6voIkbNSCZNveoCc31AQgRPOIl3hkrcEZ/N02Bl"
                        },
                        "1": {
                            "value": "hMokaiF2Mvl5cQynkjZyNW+BFyFqhJGiguEhK8uPB01n4w3Q6HHrzlI/JldfKYcs"
                        }
                    },
                    "idCredSecSharingCoeff": [
                        {
                            "value": "rENDCUkWCDhaXV3/T5MEP3VJxuacjGD/j7epV70vgdXbo20dgZYu8SPLJ6ofM0VO"
                        },
                        {
                            "value": "sC3Ue/w+03xMBiARkUqi3a75A8GVjC7BfVMTozuNA+rEGCVXIsCbWApJONbZN2D9"
                        }
                    ]
                }
            }
        }
    },
    "threshold": {
        "value": 1
    },
    "encryptedBalance": {
        "selfAmount": {
            "value": "wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
        }
    },
    "encryptionKey": {
        "value": "sUy/5EoCxrH3hxEXbV9DcpU2eqTyqMJVHuENJaA63GnWGjMqBYlxkZ2tcxLh/JTFr4v6CH4PwCqB7OT3F3T99w7pkF/oylORg87Q1ET7SMK2YJKDdlz/ZH49ysF+E8mH"
    },
    "index": {
        "value": "13351"
    },
    "address": {
        "value": "P5niAsdi6XBZCGBz6gzEfpoPxwF0j0GPJG3EDngSk94="
    },
    "availableBalance": {}
}

Hi there
The wallet does not sign the message directly, but rather a ‘message digest’ produced from the message and the account address (for security reasons).

To verify the message, you first have to construct the same ‘message digest’ and then check the signature for this.

The digest is computed as a sha256 hash of:

  • 32 bytes representing the account address
  • 8 bytes of zeroes
  • the utf-8 encoding of the message

See JS SDK implementation of the message digest and the verification.

1 Like