Skip to content

Message signing

Message produces and checks Bitcoin Signed Messages. These are compact Signatures, base64-encoded, over a prefixed hash of the text. Wallets use them to prove control of an Address without moving any coins. The guide’s Bitcoin Signed Messages page describes the format.

Message takes the message as bytes, and utf8.encode turns our text into bytes. Signing is deterministic, so the same key and text always give the same Signature.

final privateKey = SVPrivateKey.fromWIF('cPBn5A4ikZvBTQ8D7NnvHZYCAxzDZ5Z2TSGW2LkyPiLxqYaJPBW4');
final message = Message(utf8.encode('hello, world'));
final String signature = message.sign(privateKey); // base64, 65 bytes decoded
// H2gd4ydfFx6yqKpHyTfvru+sG/Rh18ayKn91G/I2OrLbWYkJtYboGX/IlM47+uMbr0IQobpkRI1P+73Agdip98o=

We can verify against an Address, which is usually what we have for the other party, or against a Public Key. The Signature contains enough information to recover the signer’s Public Key, so verification needs nothing else. The following example verifies a Signature made by a different wallet implementation for the same key:

// A Signature made by another wallet over the same text, for the same key.
final signer = Address('n1ZCYg9YXtB5XCZazLxSmPDa8iwJRZHhGx');
const received = 'H/DIn8uA1scAuKLlCx+/9LnAcJtwQQ0PmcPrJUq90aboLv3fH5fFvY+vmbfOSFEtGarznYli6ShPr9RXwY9UrIY=';
final byAddress = Message(utf8.encode('hello, world')).verifyFromAddress(signer, received); // true
final byKey = Message(utf8.encode('hello, world')).verifyFromPublicKey(privateKey.publicKey, received); // true
final tampered = Message(utf8.encode('hello, world!')).verifyFromAddress(signer, received); // false

Both methods return false rather than throwing when the Signature does not match.