1
0

BetterMessage.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /* Import core modules. */
  2. const _ = require('lodash')
  3. const bch = require('bitcore-lib-cash')
  4. const $ = bch.util.preconditions
  5. const Address = bch.Address
  6. // const PublicKey = bch.PublicKey
  7. const PrivateKey = bch.PrivateKey
  8. const BufferWriter = bch.encoding.BufferWriter
  9. const ECDSA = bch.crypto.ECDSA
  10. const Signature = bch.crypto.Signature
  11. const sha256sha256 = bch.crypto.Hash.sha256sha256
  12. const JSUtil = bch.util.js
  13. /* Set magic bytes. */
  14. const MAGIC_BYTES = Buffer.from('Bitcoin Signed Message:\n')
  15. /**
  16. * (Better) Message (Class)
  17. *
  18. * Constructs a new message to sign and verify.
  19. * Now featuring typed buffers!
  20. */
  21. class Message {
  22. constructor (message, messageEncoding) {
  23. messageEncoding = messageEncoding || 'utf8'
  24. if (!(this instanceof Message)) {
  25. return new Message(message, messageEncoding)
  26. }
  27. $.checkArgument(
  28. _.isString(message), 'First argument should be a string')
  29. this.message = message
  30. this.messageEncoding = messageEncoding
  31. return this
  32. }
  33. /**
  34. * Magic Hash
  35. */
  36. get magicHash () {
  37. /* Initialize first prefix. */
  38. const prefix1 = BufferWriter.varintBufNum(MAGIC_BYTES.length)
  39. /* Set buffer message. */
  40. const messageBuffer = Buffer.from(this.message, this.messageEncoding)
  41. /* Initialize second prefix. */
  42. const prefix2 = BufferWriter.varintBufNum(messageBuffer.length)
  43. /* Set (complete) buffer. */
  44. const buf = Buffer
  45. .concat([prefix1, MAGIC_BYTES, prefix2, messageBuffer])
  46. /* Set buffer hash. */
  47. const hash = sha256sha256(buf)
  48. /* Return hash. */
  49. return hash
  50. }
  51. /**
  52. * Sign
  53. *
  54. * Will sign a message with a given bitcoin private key.
  55. */
  56. sign (privateKey) {
  57. $.checkArgument(privateKey instanceof PrivateKey,
  58. 'First argument should be an instance of PrivateKey')
  59. /* Initialize hash. */
  60. const hash = this.magicHash
  61. /* Initialize ECDSA. */
  62. const ecdsa = new ECDSA()
  63. /* Set hash buffer. */
  64. ecdsa.hashbuf = hash
  65. /* Set private key. */
  66. ecdsa.privkey = privateKey
  67. /* Set public key. */
  68. ecdsa.pubkey = privateKey.toPublicKey()
  69. /* Sign. */
  70. ecdsa.signRandomK()
  71. /* Calculate. */
  72. ecdsa.calci()
  73. /* Return signature. */
  74. return ecdsa.sig.toCompact().toString('base64')
  75. }
  76. /**
  77. * Verify
  78. *
  79. * Will return a boolean of the signature is valid for a given
  80. * bitcoin address. If it isn't the specific reason is accessible via
  81. * the "error" member.
  82. */
  83. verify (bitcoinAddress, signatureString) {
  84. $.checkArgument(bitcoinAddress)
  85. $.checkArgument(signatureString && _.isString(signatureString))
  86. if (_.isString(bitcoinAddress)) {
  87. bitcoinAddress = Address.fromString(bitcoinAddress)
  88. }
  89. /* Set signature. */
  90. const signature = Signature
  91. .fromCompact(Buffer.from(signatureString, 'base64'))
  92. /* Initialize ECDSA. */
  93. const ecdsa = new ECDSA()
  94. /* Set hash buffer. */
  95. ecdsa.hashbuf = this.magicHash
  96. /* Set signature. */
  97. ecdsa.sig = signature
  98. /* Set public key. */
  99. const publicKey = ecdsa.toPublicKey()
  100. /* Set signature address. */
  101. const signatureAddress = Address
  102. .fromPublicKey(publicKey, bitcoinAddress.network)
  103. /* Validate addresses. */
  104. if (bitcoinAddress.toString() !== signatureAddress.toString()) {
  105. this.error = 'The signature did not match the message digest'
  106. return false
  107. }
  108. /* Set verification. */
  109. const verified = ECDSA.verify(this.magicHash, signature, publicKey)
  110. /* Validate verification. */
  111. if (!verified) {
  112. this.error = 'The signature was invalid'
  113. }
  114. /* Return verification. */
  115. return verified
  116. }
  117. /**
  118. * To Object
  119. *
  120. * Returns a plain object with the message information.
  121. */
  122. toObject () {
  123. return {
  124. message: this.message
  125. }
  126. }
  127. /**
  128. * To JSON
  129. *
  130. * Returns a JSON representation of the message information.
  131. */
  132. toJSON () {
  133. return JSON.stringify(this.toObject())
  134. }
  135. /**
  136. * To String
  137. *
  138. * Returns a string representation of the message.
  139. */
  140. toString () {
  141. return this.message
  142. }
  143. /**
  144. * Inspect
  145. *
  146. * Returns a string formatted for the console.
  147. */
  148. inspect () {
  149. return '<Message: ' + this.toString() + '>'
  150. }
  151. }
  152. /**
  153. * From String
  154. *
  155. * Instantiate a message from a message string.
  156. */
  157. Message.prototype.fromString = function (str) {
  158. return new Message(str)
  159. }
  160. /**
  161. * From JSON
  162. *
  163. * Instantiate a message from JSON.
  164. */
  165. Message.prototype.fromJSON = function fromJSON (json) {
  166. if (JSUtil.isValidJSON(json)) {
  167. json = JSON.parse(json)
  168. }
  169. return new Message(json.message)
  170. }
  171. /**
  172. * Magic Bytes
  173. */
  174. Message.prototype.MAGIC_BYTES = MAGIC_BYTES
  175. /* Export module. */
  176. module.exports = Message