Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 1.0.4
__added__
- Verification support for eCash signed messages
- Verification support for eCash address format
__removed__
- Verification support for segwit
- Verification support for bitcoin legacy address format

# 2.2.0
__added__
- Signer and SignerAsync interfaces
Expand Down
124 changes: 14 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,123 +1,27 @@
# bitcoinjs-message
[![NPM Package](https://img.shields.io/npm/v/bitcoinjs-message.svg?style=flat-square)](https://www.npmjs.org/package/bitcoinjs-message)
[![Build Status](https://img.shields.io/travis/bitcoinjs/bitcoinjs-message.svg?branch=master&style=flat-square)](https://travis-ci.org/bitcoinjs/bitcoinjs-message)
[![Dependency status](https://img.shields.io/david/bitcoinjs/bitcoinjs-message.svg?style=flat-square)](https://david-dm.org/bitcoinjs/bitcoinjs-message#info=dependencies)

# xecjs-message
[![NPM Package](https://img.shields.io/npm/v/bitcoinjs-message.svg?style=flat-square)](https://www.npmjs.com/package/xecjs-message)
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)

## Examples (Note about Electrum support at the bottom)

``` javascript
var bitcoin = require('bitcoinjs-lib') // v4.x.x
var bitcoinMessage = require('bitcoinjs-message')
```

> sign(message, privateKey, compressed[, network.messagePrefix, sigOptions])
> - If you pass the sigOptions arg instead of messagePrefix it will dynamically replace.
> - sigOptions contains two attributes
> - `segwitType` should be one of `'p2sh(p2wpkh)'` or `'p2wpkh'`
> - `extraEntropy` will be used to create non-deterministic signatures using the RFC6979 extra entropy parameter. R value reuse is not an issue.

Sign a Bitcoin message
``` javascript
var keyPair = bitcoin.ECPair.fromWIF('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1')
var privateKey = keyPair.privateKey
var message = 'This is an example of a signed message.'

var signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed)
console.log(signature.toString('base64'))
// => 'H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk='
```

To produce non-deterministic signatures you can pass an extra option to sign()
``` javascript
var { randomBytes } = require('crypto')
var keyPair = bitcoin.ECPair.fromWIF('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1')
var privateKey = keyPair.privateKey
var message = 'This is an example of a signed message.'

var signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed, { extraEntropy: randomBytes(32) })
console.log(signature.toString('base64'))
// => different (but valid) signature each time
```

Sign a Bitcoin message (with segwit addresses)
``` javascript
// P2SH(P2WPKH) address 'p2sh(p2wpkh)'
var signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed, { segwitType: 'p2sh(p2wpkh)' })
console.log(signature.toString('base64'))
// => 'I9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk='

// P2WPKH address 'p2wpkh'
var signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed, { segwitType: 'p2wpkh' })
console.log(signature.toString('base64'))
// => 'J9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk='
```

Sign a Bitcoin message using a Signer interface.
``` javascript
var keyPair = bitcoin.ECPair.fromWIF('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1')
var privateKey = keyPair.privateKey
var message = 'This is an example of a signed message.'
This is a fork of [bitcoinjs-message](https://github.com/bitcoinjs/bitcoinjs-message/).

var secp256k1 = require('secp256k1')
// Notice we are using the privateKey var from the outer scope inside the sign function.
var signer = { sign: (hash, extraData) => secp256k1.sign(hash, privateKey, { data: extraData }) }
## Summary of changes from `bitcoinjs-message`

var signature = bitcoinMessage.sign(message, signer, keyPair.compressed)
console.log(signature.toString('base64'))
// => 'H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk='
```
1. Segwit support removed from `verify`
2. `verify` accepts a valid XEC address

> signAsync(message, privateKey, compressed[, network.messagePrefix, sigOptions])
> Same as sign, except returns a promise, and can accept a SignerAsync interface instead of privateKey
## Examples

Sign a Bitcoin message asynchronously
``` javascript
var keyPair = bitcoin.ECPair.fromWIF('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1')
var privateKey = keyPair.privateKey
var message = 'This is an example of a signed message.'

bitcoinMessage.signAsync(message, privateKey, keyPair.compressed).then(signature => {
console.log(signature.toString('base64'))
})
// => 'H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk='
```
import xecMessage from 'xecjs-message';

Sign a Bitcoin message asynchronously using SignerAsync interface
``` javascript
var keyPair = bitcoin.ECPair.fromWIF('L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1')
var privateKey = keyPair.privateKey
var message = 'This is an example of a signed message.'
const verification = xecMessage.verify(
msg,
xecAddress,
signature,
);

var secp256k1 = require('secp256k1')
// Note that a Signer will also work
var signerAsync = { sign: (hash, extraData) => Promise.resolve(secp256k1.sign(hash, privateKey, { data: extraData })) }
var signer = { sign: (hash, extraData) => secp256k1.sign(hash, privateKey, { data: extraData }) }

bitcoinMessage.signAsync(message, signerAsync, keyPair.compressed).then(signature => {
console.log(signature.toString('base64'))
})
// => 'H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk='
bitcoinMessage.signAsync(message, signer, keyPair.compressed).then(signature => {
console.log(signature.toString('base64'))
})
// => 'H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk='
// returns true for valid signature, false for invalid
```

> verify(message, address, signature[, network.messagePrefix, checkSegwitAlways])

Verify a Bitcoin message
``` javascript
var address = '1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV'

console.log(bitcoinMessage.verify(message, address, signature))
// => true
```

## About Electrum segwit signature support

- For Signing: Use the non-segwit compressed signing parameters for both segwit types (p2sh-p2wpkh and p2wpkh)
- For Verifying: Pass the checkSegwitAlways argument as true. (messagePrefix should be set to null to default to Bitcoin messagePrefix)

## LICENSE [MIT](LICENSE)
70 changes: 28 additions & 42 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const bufferEquals = require('buffer-equals')
const createHash = require('create-hash')
const secp256k1 = require('secp256k1')
const varuint = require('varuint-bitcoin')
const ecashaddr = require('ecashaddrjs')

const SEGWIT_TYPES = {
P2WPKH: 'p2wpkh',
Expand Down Expand Up @@ -178,58 +179,43 @@ function decodeBech32 (address) {
return Buffer.from(data)
}

function verify (message, address, signature, messagePrefix, checkSegwitAlways) {
function verify(message, xecAddress, signature, messagePrefix) {

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 hashBitcoinSigned = magicHash(message, messagePrefix)
const publicKeyBitcoinSigned = secp256k1.recover(
hashBitcoinSigned,
parsed.signature,
parsed.recovery,
parsed.compressed
)
const publicKeyHashBitcoinSigned = hash160(publicKeyBitcoinSigned)

const hash = magicHash(message, messagePrefix)
const publicKey = secp256k1.recover(
hash,
// Also test ecash: signed
const hashEcashSigned = magicHash(message, '\u0016eCash Signed Message:\n')
const publicKeyEcashSigned = secp256k1.recover(
hashEcashSigned,
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)
}
}
const publicKeyHashEcashSigned = hash160(publicKeyEcashSigned)

let actualBitcoinSigned, actualEcashSigned, expected

actualBitcoinSigned = publicKeyHashBitcoinSigned
actualEcashSigned = publicKeyHashEcashSigned

// Decode from XEC address instead of bs58 legacy format
const decodedAddress = ecashaddr.decode(xecAddress)

expected = Buffer.alloc(decodedAddress.hash.length)
expected.set(decodedAddress.hash)

return bufferEquals(actual, expected)
return bufferEquals(actualEcashSigned, expected) || bufferEquals(actualBitcoinSigned, expected)
}

module.exports = {
Expand Down
Loading