Verify Message
Signature verification depends on the type of address the message was signed with
- Payment Address
p2sh(p2wpkh)
function magicHash (message, messagePrefix) {
messagePrefix = messagePrefix || '\u0018Bitcoin Signed Message:\n'
if (!Buffer.isBuffer(messagePrefix)) {
messagePrefix = Buffer.from(messagePrefix, 'utf8')
}
if (!Buffer.isBuffer(message)) {
message = Buffer.from(message, 'utf8')
}
const messageVISize = varuint.encodingLength(message.length)
const buffer = Buffer.allocUnsafe(
messagePrefix.length + messageVISize + message.length
)
messagePrefix.copy(buffer, 0)
varuint.encode(message.length, buffer, messagePrefix.length)
message.copy(buffer, messagePrefix.length + messageVISize)
return hash256(buffer)
}
function verify (message, address, signature, messagePrefix, checkSegwitAlways) {
if (!Buffer.isBuffer(signature)) signature = Buffer.from(signature, 'base64')
const parsed = decodeSignature(signature)
if (checkSegwitAlways && !parsed.compressed) {
throw new Error('checkSegwitAlways can only be used with a compressed pubkey signature flagbyte')
}
const hash = magicHash(message, messagePrefix)
const publicKey = secp256k1.recover(
hash,
parsed.signature,
parsed.recovery,
parsed.compressed
)
const publicKeyHash = hash160(publicKey)
let actual, expected
if (parsed.segwitType) {
if (parsed.segwitType === SEGWIT_TYPES.P2SH_P2WPKH) {
actual = segwitRedeemHash(publicKeyHash)
expected = bs58check.decode(address).slice(1)
} else {
// parsed.segwitType === SEGWIT_TYPES.P2WPKH
// must be true since we only return null, P2SH_P2WPKH, or P2WPKH
// from the decodeSignature function.
actual = publicKeyHash
expected = decodeBech32(address)
}
} else {
if (checkSegwitAlways) {
try {
expected = decodeBech32(address)
// if address is bech32 it is not p2sh
return bufferEquals(publicKeyHash, expected)
} catch (e) {
const redeemHash = segwitRedeemHash(publicKeyHash)
expected = bs58check.decode(address).slice(1)
// base58 can be p2pkh or p2sh-p2wpkh
return (
bufferEquals(publicKeyHash, expected) ||
bufferEquals(redeemHash, expected)
)
}
} else {
actual = publicKeyHash
expected = bs58check.decode(address).slice(1)
}
}
- Ordinals Address
P2TR
/**
*
* @param message
* @returns Bip322 Message Hash
*
*/
export function bip0322Hash(message: string) {
const { sha256 } = crypto;
const tag = 'BIP0322-signed-message';
const tagHash = sha256(Buffer.from(tag));
const result = sha256(Buffer.concat([tagHash, tagHash, Buffer.from(message)]));
return result.toString('hex');
}
export const verifySignature = (
address: string,
message: string,
signature: string,
) => {
/*
if the address is a taproot address (ordinals address)
* decode the signature from base64 format
* generate the message hash using Bip322 Tags
* recover the public key from the signature using
the decoded signature and the message hash
* verify that the public key is valid by verifying that the one extracted
from the decoded signature matches the public key that created the signature
*/
const decodedSignature = base64.decode(signature).slice(2);
if (!decodedSignature) throw new Error('Malformed Signature');
const msgHash = bip0322Hash(message);
const pk = secp256k1.recoverPublicKey(msgHash, decodedSignature, 1);
const isValid = secp256k1.verify(decodedSignature, msgHash, pk, { strict: false });
return isValid;
};
Last modified 1mo ago