diff --git a/CHANGELOG.md b/CHANGELOG.md index 81a19dd..461c67f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.1.0-alpha.5](https://github.com/thenewboston-developers/thenewboston-js/compare/v1.1.0-alpha.4...v1.1.0-alpha.5) (2021-06-30) + + +### Features + +* Verify Signatures ([#164](https://github.com/thenewboston-developers/thenewboston-js/issues/164)) ([267e89b](https://github.com/thenewboston-developers/thenewboston-js/commit/267e89bae70466c8ff2550fdc0aa5e7005d30389)) + ## [1.1.0-alpha.4](https://github.com/thenewboston-developers/thenewboston-js/compare/v1.1.0-alpha.3...v1.1.0-alpha.4) (2021-04-29) diff --git a/README.md b/README.md index cd1dedd..47f2499 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,9 @@ JavaScript library for thenewboston. Find out how you can contribute [here](https://github.com/thenewboston-developers/thenewboston-js/blob/master/docs/CONTRIBUTING.md). -### Testing - -Some day... - ## Documentation -Check out the [documentation here](https://github.com/thenewboston-developers/thenewboston-js/blob/master/docs/index.md). +Check out the [documentation here](https://thenewboston-developers.github.io/thenewboston-js/). ## Community diff --git a/docs/account.md b/docs/account.md index 0926158..7f561f6 100644 --- a/docs/account.md +++ b/docs/account.md @@ -70,6 +70,21 @@ From running that code, you can see that the `createSignature` method returned a > If you re-run that code multiple times, you will then notice that the generated signature is different. That is because the signature depends on two variables: the account signing key and the message. The account signing key also is changing on every run because we are generating a random `Account` to use, thus resulting in different outcomes. +## Verifying Signatures + +If you need to verify a signature for a message, then you can easily use the `Account.verifySignature` static method. The method takes in the message first, the signature (signed message) second and then the account number last. Here is example of how one might use this method: + +```ts +const account = new Account("SIGNING_KEY"); + +const message = "Hello, world!" or JSON.stringify({greeting: "Hello World!"}); + +const signature = account.createSignature(message); + +Account.verifySignature(message,signature, account.accountNumberHex); +// returns true only if the account number was used to sign the message and the signed message matches the signature +``` + ## Verifying Account Keys If you need to verify that the given signing key and account number are paired together, then you can easily use the `Account.isValidPair` static method. The method takes in the signing key first and the account number second. Here is example of how one might use this method: diff --git a/docs/index.md b/docs/index.md index 188b7b4..a4fd010 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,6 +25,12 @@ There are two ways to use the library. - [Using Block Data and Block Messages](account.md#using-block-data-and-block-messages) +- [Account Payment Handler](account-payment-handler.md) + + - [Sending Coins](account-payment-handler.md#sending-coins) + + - [Send Bulk Payments](account-payment-handler.md#sending-bulk-payments) + - [Bank](bank.md#bank) - [Creating Banks](bank.md#creating-banks) diff --git a/package-lock.json b/package-lock.json index 4c584d5..fed7e67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "thenewboston", - "version": "1.1.0-alpha.4", + "version": "1.1.0-alpha.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2445,16 +2445,42 @@ } }, "browserslist": { - "version": "4.14.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.7.tgz", - "integrity": "sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001157", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.591", + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", - "node-releases": "^1.1.66" + "node-releases": "^1.1.71" + }, + "dependencies": { + "caniuse-lite": { + "version": "1.0.30001230", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz", + "integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.739", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz", + "integrity": "sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==", + "dev": true + }, + "node-releases": { + "version": "1.1.72", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", + "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "dev": true + } } }, "bser": { @@ -2564,12 +2590,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "caniuse-lite": { - "version": "1.0.30001157", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001157.tgz", - "integrity": "sha512-gOerH9Wz2IRZ2ZPdMfBvyOi3cjaz4O4dgNwPGzx8EhqAs4+2IL/O+fJsbt+znSigujoZG8bVcIAUM/I/E5K3MA==", - "dev": true - }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -2694,12 +2714,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", - "dev": true - }, "combine-source-map": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", @@ -3257,12 +3271,6 @@ "safer-buffer": "^2.1.0" } }, - "electron-to-chromium": { - "version": "1.3.592", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.592.tgz", - "integrity": "sha512-kGNowksvqQiPb1pUSQKpd8JFoGPLxYOwduNRCqCxGh/2Q1qE2JdmwouCW41lUzDxOb/2RIV4lR0tVIfboWlO9A==", - "dev": true - }, "elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -4345,9 +4353,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -4544,9 +4552,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "html-encoding-sniffer": { @@ -5787,9 +5795,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.memoize": { @@ -6277,12 +6285,6 @@ } } }, - "node-releases": { - "version": "1.1.66", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz", - "integrity": "sha512-JHEQ1iWPGK+38VLB2H9ef2otU4l8s3yAMt9Xf934r6+ojCYDMHPMqvCc9TnzfeFSP1QEOeU6YZEd3+De0LTCgg==", - "dev": true - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -8891,9 +8893,9 @@ } }, "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "dev": true }, "xml-name-validator": { diff --git a/package.json b/package.json index ccd61e9..15fda01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "thenewboston", - "version": "1.1.0-alpha.4", + "version": "1.1.0-alpha.5", "description": "JavaScript library for thenewboston.", "author": { "name": "thenewboston-developers", diff --git a/src/account-payment-handler.ts b/src/account-payment-handler.ts index 6c10434..8316d0e 100644 --- a/src/account-payment-handler.ts +++ b/src/account-payment-handler.ts @@ -17,10 +17,10 @@ export class AccountPaymentHandler { } async sendCoins(recipient: Account | string, amount: number, memo = "") { - await this.client.sendCoins(new TransferDetails(this.account, recipient, amount, memo)); + return await this.client.sendCoins(new TransferDetails(this.account, recipient, amount, memo)); } async sendBulkTransactions(transactions: Transaction[]) { - await this.client.sendBulkTransactions(this.account, transactions); + return await this.client.sendBulkTransactions(this.account, transactions); } } diff --git a/src/account.ts b/src/account.ts index 18faa9f..dc4b4f3 100644 --- a/src/account.ts +++ b/src/account.ts @@ -59,6 +59,23 @@ export class Account { } } + /** + * Checks if the message was signed by a specific account number. + * @param message the message to verify + * @param signature the signed message + * @param accountNumber the account number that signed the message + */ + static verifySignature(message: string, signature: string, accountNumber: string) { + const encodedMessage = new TextEncoder().encode(message); + const encodedSignature = hexToUint8Array(signature); + const encodedAccountNumber = hexToUint8Array(accountNumber); + try { + return sign.detached.verify(encodedMessage, encodedSignature.slice(0, 64), encodedAccountNumber); + } catch { + return false; + } + } + /** The 32 byte account number as a 32 byte hex string. */ get accountNumberHex() { return uint8arrayToHex(this.accountNumber); diff --git a/src/payment-handler.ts b/src/payment-handler.ts index 74bcfa9..9107073 100644 --- a/src/payment-handler.ts +++ b/src/payment-handler.ts @@ -59,6 +59,11 @@ export class PaymentHandler { return tx; }); + if (!this.primaryValidator) + throwError( + "The Payment Handler is not initalized yet.\nTry calling the async '.init()' method on the Payment Handler before sending transactions" + ); + const { balance_lock: balanceLock } = await this.primaryValidator!.getAccountBalanceLock( sender.accountNumberHex ).catch((err) => @@ -72,7 +77,8 @@ export class PaymentHandler { fee: config.node_type, recipient: config.account_number, })), - ]; + ].sort((a, b) => (a.recipient > b.recipient ? 1 : -1)); + return { balanceLock, transactions, sender }; } @@ -85,7 +91,7 @@ export class PaymentHandler { transactions: Transaction[]; sender: Account; }) { - await this.bank.addBlocks(transaction.balanceLock!, transaction.transactions, transaction.sender); + return await this.bank.addBlocks(transaction.balanceLock!, transaction.transactions, transaction.sender); } /** @@ -95,7 +101,7 @@ export class PaymentHandler { async sendCoins({ sender, recipient, amount, memo = "" }: TransferDetails) { const recipientAccount = typeof recipient === "string" ? recipient : recipient.accountNumberHex; const transaction = await this.createTransaction(sender, [{ amount, memo, recipient: recipientAccount }]); - await this.broadcastTransaction(transaction); + return await this.broadcastTransaction(transaction); } /** @@ -105,6 +111,6 @@ export class PaymentHandler { */ async sendBulkTransactions(sender: Account, txs: Transaction[]) { const transaction = await this.createTransaction(sender, txs); - await this.broadcastTransaction(transaction); + return await this.broadcastTransaction(transaction); } } diff --git a/tests/account.test.js b/tests/account.test.js index 8569301..df6ce27 100644 --- a/tests/account.test.js +++ b/tests/account.test.js @@ -80,6 +80,32 @@ describe("Account", () => { }); }); + it("verifySignature(message: string, signature: string, accountNumber: string)", () => { + const account = createDefaultAccount(); + const message = JSON.stringify({ + balance_key: null, + txs: [ + { + amount: 1, + fee: "PRIMARY_VALIDATOR", + recipient: "4afb3eaad999e4c073be0fbde86b76f9370d53b398b9cab9d760825709a1d6b3", + }, + { + amount: 100, + memo: "Deposit", + recipient: "718b3c72e1e520479b9f9a2a582f280e549732c97c44de22c5b2a4e443154342", + }, + { amount: 1, fee: "BANK", recipient: "e3a94381f8db207ddad931391886d611d6f4c060d0db2b0e373738e2f4db96d6" }, + ], + }); + + const signature = account.createSignature(message); + + expect(Account.verifySignature(message, signature, account.accountNumberHex)).toBeTruthy(); + expect(Account.verifySignature("Wrong Message", signature, account.accountNumberHex)).toBeFalsy(); + expect(Account.verifySignature(message, "Wrong Signature", account.accountNumberHex)).toBeFalsy(); + expect(Account.verifySignature(message, signature, "Wrong Account Number")).toBeFalsy(); + }); // TODO: createBlockData // TODO: createBlockMessage