mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
ADD: wallets message sign/verify
This commit is contained in:
parent
7367b58716
commit
fd61fa6bb2
@ -1,7 +1,6 @@
|
||||
import bip39 from 'bip39';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import b58 from 'bs58check';
|
||||
import bitcoinMessage from 'bitcoinjs-message';
|
||||
|
||||
import { randomBytes } from '../rng';
|
||||
import { AbstractHDWallet } from './abstract-hd-wallet';
|
||||
@ -820,12 +819,18 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
return false;
|
||||
}
|
||||
|
||||
_getIndexByAddress(address) {
|
||||
/**
|
||||
* Finds WIF corresponding to address and returns it
|
||||
*
|
||||
* @param address {string} Address that belongs to this wallet
|
||||
* @returns {string|false} WIF or false
|
||||
*/
|
||||
_getWIFbyAddress(address) {
|
||||
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
|
||||
if (this._getExternalAddressByIndex(c) === address) return { internal: false, index: c };
|
||||
if (this._getExternalAddressByIndex(c) === address) return this._getWIFByIndex(false, c);
|
||||
}
|
||||
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
|
||||
if (this._getInternalAddressByIndex(c) === address) return { internal: true, index: c };
|
||||
if (this._getInternalAddressByIndex(c) === address) return this._getWIFByIndex(true, c);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -1107,26 +1112,4 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
|
||||
return { tx };
|
||||
}
|
||||
|
||||
signMessage(message, address) {
|
||||
const search = this._getIndexByAddress(address);
|
||||
if (search === null) throw new Error('Invalid address');
|
||||
|
||||
let options;
|
||||
switch (this.type) {
|
||||
case 'HDsegwitBech32':
|
||||
case 'HDAezeedWallet':
|
||||
options = { segwitType: 'p2wpkh' };
|
||||
break;
|
||||
case 'HDsegwitP2SH':
|
||||
options = { segwitType: 'p2sh(p2wpkh)' };
|
||||
break;
|
||||
}
|
||||
|
||||
const wif = this._getWIFByIndex(search.internal, search.index);
|
||||
const keyPair = bitcoin.ECPair.fromWIF(wif);
|
||||
const privateKey = keyPair.privateKey;
|
||||
const signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed, options);
|
||||
return signature.toString('base64');
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export class AbstractWallet {
|
||||
constructor() {
|
||||
this.type = this.constructor.type;
|
||||
this.typeReadable = this.constructor.typeReadable;
|
||||
this.segwitType = this.constructor.segwitType;
|
||||
this.label = '';
|
||||
this.secret = ''; // private key or recovery phrase
|
||||
this.balance = 0;
|
||||
|
@ -15,6 +15,7 @@ const { CipherSeed } = require('aezeed');
|
||||
export class HDAezeedWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDAezeedWallet';
|
||||
static typeReadable = 'HD Aezeed';
|
||||
static segwitType = 'p2wpkh';
|
||||
|
||||
setSecret(newSecret) {
|
||||
this.secret = newSecret.trim();
|
||||
|
@ -8,6 +8,7 @@ import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
||||
export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDsegwitBech32';
|
||||
static typeReadable = 'HD SegWit (BIP84 Bech32 Native)';
|
||||
static segwitType = 'p2wpkh';
|
||||
|
||||
allowSend() {
|
||||
return true;
|
||||
|
@ -12,6 +12,7 @@ const HDNode = require('bip32');
|
||||
export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDsegwitP2SH';
|
||||
static typeReadable = 'HD SegWit (BIP49 P2SH)';
|
||||
static segwitType = 'p2sh(p2wpkh)';
|
||||
|
||||
allowSend() {
|
||||
return true;
|
||||
|
@ -495,23 +495,41 @@ export class LegacyWallet extends AbstractWallet {
|
||||
return false;
|
||||
}
|
||||
|
||||
signMessage(message) {
|
||||
let options;
|
||||
switch (this.type) {
|
||||
case 'segwitBech32':
|
||||
options = { segwitType: 'p2wpkh' };
|
||||
break;
|
||||
case 'segwitP2SH':
|
||||
options = { segwitType: 'p2sh(p2wpkh)' };
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Finds WIF corresponding to address and returns it
|
||||
*
|
||||
* @param address {string} Address that belongs to this wallet
|
||||
* @returns {string|false} WIF or false
|
||||
*/
|
||||
_getWIFbyAddress(address) {
|
||||
return this.getAddress() === address ? this.secret : null;
|
||||
}
|
||||
|
||||
const keyPair = bitcoin.ECPair.fromWIF(this.secret);
|
||||
/**
|
||||
* Signes text message using address private key and returs signature
|
||||
*
|
||||
* @param message {string}
|
||||
* @param address {string}
|
||||
* @returns {string} base64 encoded signature
|
||||
*/
|
||||
signMessage(message, address) {
|
||||
const wif = this._getWIFbyAddress(address);
|
||||
if (wif === null) throw new Error('Invalid address');
|
||||
const keyPair = bitcoin.ECPair.fromWIF(wif);
|
||||
const privateKey = keyPair.privateKey;
|
||||
const options = this.segwitType ? { segwitType: this.segwitType } : undefined;
|
||||
const signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed, options);
|
||||
return signature.toString('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies text message signature by address
|
||||
*
|
||||
* @param message {string}
|
||||
* @param address {string}
|
||||
* @param signature {string}
|
||||
* @returns {boolean} base64 encoded signature
|
||||
*/
|
||||
verifyMessage(message, address, signature) {
|
||||
// null, true so it can verify Electrum signatores without errors
|
||||
return bitcoinMessage.verify(message, address, signature, null, true);
|
||||
|
@ -4,6 +4,7 @@ const bitcoin = require('bitcoinjs-lib');
|
||||
export class SegwitBech32Wallet extends LegacyWallet {
|
||||
static type = 'segwitBech32';
|
||||
static typeReadable = 'P2 WPKH';
|
||||
static segwitType = 'p2wpkh';
|
||||
|
||||
getAddress() {
|
||||
if (this._address) return this._address;
|
||||
|
@ -19,6 +19,7 @@ function pubkeyToP2shSegwitAddress(pubkey, network) {
|
||||
export class SegwitP2SHWallet extends LegacyWallet {
|
||||
static type = 'segwitP2SH';
|
||||
static typeReadable = 'SegWit (P2SH)';
|
||||
static segwitType = 'p2sh(p2wpkh)';
|
||||
|
||||
static witnessToAddress(witness) {
|
||||
try {
|
||||
|
@ -95,13 +95,13 @@ describe('HDAezeedWallet', () => {
|
||||
let signature;
|
||||
|
||||
// external address
|
||||
signature = aezeed.signMessage('vires is numeris', 'bc1qdjj7lhj9lnjye7xq3dzv3r4z0cta294xy78txn');
|
||||
signature = aezeed.signMessage('vires is numeris', aezeed._getExternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'J9zF7mdGGdc/9HMlvor6Zl7ap1qseQpiBDJ4oaSpkzbQGGhdfkM6LHo6m9BV8o/BlqiQI1vuODaNlBFyeyIWgfE=');
|
||||
assert.strictEqual(aezeed.verifyMessage('vires is numeris', 'bc1qdjj7lhj9lnjye7xq3dzv3r4z0cta294xy78txn', signature), true);
|
||||
assert.strictEqual(aezeed.verifyMessage('vires is numeris', aezeed._getExternalAddressByIndex(0), signature), true);
|
||||
|
||||
// internal address
|
||||
signature = aezeed.signMessage('vires is numeris', 'bc1qzyjq8sjj56n8v9fgw5klsc8sq8yuy0jx03hzzp');
|
||||
signature = aezeed.signMessage('vires is numeris', aezeed._getInternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'KIda06aSswmo9NiAYNUBRADA9q1v39raSmHHVg56+thtah5xL7hVw/x+cZgydFNyel2bXfyGluJRaP1uRQfJtzo=');
|
||||
assert.strictEqual(aezeed.verifyMessage('vires is numeris', 'bc1qzyjq8sjj56n8v9fgw5klsc8sq8yuy0jx03hzzp', signature), true);
|
||||
assert.strictEqual(aezeed.verifyMessage('vires is numeris', aezeed._getInternalAddressByIndex(0), signature), true);
|
||||
});
|
||||
});
|
||||
|
@ -140,13 +140,13 @@ describe('Legacy HD (BIP44)', () => {
|
||||
let signature;
|
||||
|
||||
// external address
|
||||
signature = hd.signMessage('vires is numeris', '1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA');
|
||||
signature = hd.signMessage('vires is numeris', hd._getExternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'H5J8DbqvuBy8lqRW7+LTVrrtrsaqLSwRDyj+5XtCrZpdCgPlxKM4EKRD6qvdKeyEh1fiSfIVB/edPAum3gKcJZo=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', '1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', signature), true);
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getExternalAddressByIndex(0), signature), true);
|
||||
|
||||
// internal address
|
||||
signature = hd.signMessage('vires is numeris', '1J3J6EvPrv8q6AC3VCjWV45Uf3nssNMRtH');
|
||||
signature = hd.signMessage('vires is numeris', hd._getInternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'H98hmvtyPFUbR6E5Tcsqmc+eSjlYhP2vy41Y6IyHS9DVKEI5n8VEMpIEDtvlMARVce96nOqbRHXo9nD05WXH/Eo=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', '1J3J6EvPrv8q6AC3VCjWV45Uf3nssNMRtH', signature), true);
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getInternalAddressByIndex(0), signature), true);
|
||||
});
|
||||
});
|
||||
|
@ -112,25 +112,25 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
||||
let signature;
|
||||
|
||||
// external address
|
||||
signature = hd.signMessage('vires is numeris', 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu');
|
||||
signature = hd.signMessage('vires is numeris', hd._getExternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'KGW4FfrptS9zV3UptUWxbEf65GhC2mCUz86G0GpN/H4MUC29Y5TsRhWGIqG2lettEpZXZETuc2yL+O7/UvDhxhM=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu', signature), true);
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getExternalAddressByIndex(0), signature), true);
|
||||
|
||||
// internal address
|
||||
signature = hd.signMessage('vires is numeris', 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el');
|
||||
signature = hd.signMessage('vires is numeris', hd._getInternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'KJ5B9JkZ042FhtGeObU/MxLCzQWHbrpXNQxhfJj9wMboa/icLIIaAlsKaSkS27fZLvX3WH0qyj3aAaXscnWsfSw=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el', signature), true);
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getInternalAddressByIndex(0), signature), true);
|
||||
|
||||
// multiline message
|
||||
signature = hd.signMessage('vires\nis\nnumeris', 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu');
|
||||
signature = hd.signMessage('vires\nis\nnumeris', hd._getExternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'KFI22tlJVGq2HGQM5rcBtYu+Jq8oc7QyjSBP1ZQup3a/GEw1Khu2qFbL/iLzqw95wN22a/Tll1oMLdWxg9cWMYM=');
|
||||
assert.strictEqual(hd.verifyMessage('vires\nis\nnumeris', 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu', signature), true);
|
||||
assert.strictEqual(hd.verifyMessage('vires\nis\nnumeris', hd._getExternalAddressByIndex(0), signature), true);
|
||||
|
||||
// can't sign if address doesn't belong to wallet
|
||||
assert.throws(() => hd.signMessage('vires is numeris', '186FBQmCV5W1xY7ywaWtTZPAQNciVN8Por'));
|
||||
|
||||
// can't verify wrong signature
|
||||
assert.throws(() => hd.verifyMessage('vires is numeris', 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el', 'wrong signature'));
|
||||
assert.throws(() => hd.verifyMessage('vires is numeris', hd._getInternalAddressByIndex(0), 'wrong signature'));
|
||||
|
||||
// can verify electrum message signature
|
||||
// bech32 segwit (p2wpkh)
|
||||
|
@ -173,13 +173,13 @@ describe('P2SH Segwit HD (BIP49)', () => {
|
||||
let signature;
|
||||
|
||||
// external address
|
||||
signature = hd.signMessage('vires is numeris', '37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf');
|
||||
signature = hd.signMessage('vires is numeris', hd._getExternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'JMgoRSlLLLw6mw/Gbbg8Uj3fACkIJ85CZ52T5ZQfBnpUBkz0myRju6Rmgvmq7ugytc4WyYbzdGEc3wufNbjP09g=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', '37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf', signature), true);
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getExternalAddressByIndex(0), signature), true);
|
||||
|
||||
// internal address
|
||||
signature = hd.signMessage('vires is numeris', '34K56kSjgUCUSD8GTtuF7c9Zzwokbs6uZ7');
|
||||
signature = hd.signMessage('vires is numeris', hd._getInternalAddressByIndex(0));
|
||||
assert.strictEqual(signature, 'I5WkniWTnJhTW74t3kTAkHq3HdiupTNgOZLpMp0hvUfAJw2HMuyRiNLl2pbNWobNCCrmvffSWM7IgkOBz/J9fYA=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', '34K56kSjgUCUSD8GTtuF7c9Zzwokbs6uZ7', signature), true);
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getInternalAddressByIndex(0), signature), true);
|
||||
});
|
||||
});
|
||||
|
@ -73,8 +73,8 @@ describe('Legacy wallet', () => {
|
||||
const l = new LegacyWallet();
|
||||
l.setSecret('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1'); // from bitcoinjs-message examples
|
||||
|
||||
const signature = l.signMessage('This is an example of a signed message.');
|
||||
const signature = l.signMessage('This is an example of a signed message.', l.getAddress());
|
||||
assert.strictEqual(signature, 'H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=');
|
||||
assert.strictEqual(l.verifyMessage('This is an example of a signed message.', '1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV', signature), true);
|
||||
assert.strictEqual(l.verifyMessage('This is an example of a signed message.', l.getAddress(), signature), true);
|
||||
});
|
||||
});
|
||||
|
@ -41,10 +41,8 @@ describe('Segwit P2SH wallet', () => {
|
||||
const l = new SegwitBech32Wallet();
|
||||
l.setSecret('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1'); // from bitcoinjs-message examples
|
||||
|
||||
console.info('add', l.getAddress())
|
||||
|
||||
const signature = l.signMessage('This is an example of a signed message.');
|
||||
const signature = l.signMessage('This is an example of a signed message.', l.getAddress());
|
||||
assert.strictEqual(signature, 'J9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=');
|
||||
assert.strictEqual(l.verifyMessage('This is an example of a signed message.', 'bc1qngw83fg8dz0k749cg7k3emc7v98wy0c74dlrkd', signature), true);
|
||||
assert.strictEqual(l.verifyMessage('This is an example of a signed message.', l.getAddress(), signature), true);
|
||||
});
|
||||
});
|
||||
|
@ -42,8 +42,8 @@ describe('Segwit P2SH wallet', () => {
|
||||
const l = new SegwitP2SHWallet();
|
||||
l.setSecret('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1'); // from bitcoinjs-message examples
|
||||
|
||||
const signature = l.signMessage('This is an example of a signed message.');
|
||||
const signature = l.signMessage('This is an example of a signed message.', l.getAddress());
|
||||
assert.strictEqual(signature, 'I9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=');
|
||||
assert.strictEqual(l.verifyMessage('This is an example of a signed message.', '3DnW8JGpPViEZdpqat8qky1zc26EKbXnmM', signature), true);
|
||||
assert.strictEqual(l.verifyMessage('This is an example of a signed message.', l.getAddress(), signature), true);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user