From 924c385032d78c218e9a9d5889810becf20f58c6 Mon Sep 17 00:00:00 2001 From: Nyzo Date: Sun, 20 Sep 2020 23:30:42 -0400 Subject: [PATCH] Version 2. Micropay. --- byteBuffer.js | 40 ++++- content.js | 63 ++++++-- extensionUtil.js | 66 ++++++-- manifest.json | 9 +- micropayConfiguration.js | 22 +++ nyzoString.js | 8 +- options.html | 11 +- options.js | 27 +++- popup.html | 106 ++++++++++--- popup.js | 335 ++++++++++++++++++++++++++++----------- style.css | 41 ++++- transaction.js | 17 ++ 12 files changed, 580 insertions(+), 165 deletions(-) create mode 100644 micropayConfiguration.js diff --git a/byteBuffer.js b/byteBuffer.js index 898aa52..09c7f78 100644 --- a/byteBuffer.js +++ b/byteBuffer.js @@ -5,6 +5,11 @@ class ByteBuffer { this.array = new Uint8Array(Math.max(maximumSize, 1)); } + initializeWithArray(bytes) { + this.index = 0; + this.array = bytes; + } + putBytes(bytes) { for (var i = 0; i < bytes.length; i++) { this.array[this.index++] = bytes[i]; @@ -16,7 +21,6 @@ class ByteBuffer { } putIntegerValue(value, length) { - value = Math.floor(value); for (var i = 0; i < length; i++) { this.array[this.index + length - 1 - i] = value % 256; @@ -45,4 +49,38 @@ class ByteBuffer { return result; } + + readBytes(length) { + const result = new Uint8Array(length); + for (var i = 0; i < length; i++) { + result[i] = this.array[this.index++]; + } + + return result; + } + + readByte() { + return this.array[this.index++]; + } + + readIntegerValue(length) { + var result = 0; + for (var i = 0; i < length; i++) { + result *= 256; + result += this.array[this.index + length - i - 1]; + } + this.index += length; + } + + readShort() { + return this.readIntegerValue(2); + } + + readInt() { + return this.readIntegerValue(4); + } + + readLong() { + return this.readIntegerValue(8); + } } diff --git a/content.js b/content.js index 827fbcb..6f0a98a 100644 --- a/content.js +++ b/content.js @@ -1,24 +1,65 @@ +'use strict'; + function initializeExtension() { - // Process all tip buttons. - var clientUrl = ''; - var receiverId = ''; - var tag = ''; - var buttons = document.getElementsByClassName('nyzo-tip-button'); - for (var i = 0; i < buttons.length; i++) { - var button = buttons[i]; + + // Get the tip configuration, if present. + var tipClientUrl = ''; + var tipReceiverId = ''; + var tipTag = ''; + var tipButtons = document.getElementsByClassName('nyzo-tip-button'); + for (var i = 0; i < tipButtons.length; i++) { + var button = tipButtons[i]; + + // Modify the classes to show that the extension was installed. + button.classList.add('nyzo-extension-installed'); + button.classList.remove('nyzo-extension-not-installed'); + + // If valid, store the client URL, receiver ID, and tag. + tipClientUrl = button.dataset.clientUrl; + tipReceiverId = button.dataset.receiverId; + tipTag = cleanTag(button.dataset.tag); + } + var tipConfiguration = new MicropayConfiguration(tipClientUrl, tipReceiverId, tipTag, '', 0); + + // Get Micropay configurations, if present. + var micropayConfigurations = []; + var micropayButtons = document.getElementsByClassName('nyzo-micropay-button'); + for (var i = 0; i < micropayButtons.length; i++) { + var button = micropayButtons[i]; // Modify the classes to show that the extension was installed. button.classList.add('nyzo-extension-installed'); button.classList.remove('nyzo-extension-not-installed'); // If valid, store the client URL, receiver ID, and tag. - clientUrl = button.dataset.clientUrl; - receiverId = button.dataset.receiverId; - tag = cleanTag(button.dataset.tag); + const clientUrl = button.dataset.clientUrl; + const receiverId = button.dataset.receiverId; + const tag = cleanTag(button.dataset.tag); + const displayName = cleanDisplayName(button.dataset.displayName); + const amountMicronyzos = getAmountMicronyzos(button.dataset.amount); + const configuration = new MicropayConfiguration(clientUrl, receiverId, tag, displayName, amountMicronyzos); + if (isValidConfigurationForMicropay(configuration)) { + micropayConfigurations.push(configuration); + } } chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { - sendResponse({clientUrl: clientUrl, receiverId: receiverId, tag: tag}); + if (request.action === 'getNyzoParameters') { + sendResponse({tipConfiguration: tipConfiguration, micropayConfigurations: micropayConfigurations}); + } else if (request.action === 'micropayTransactionAvailable') { + const uniqueReferenceKey = request.uniqueReferenceKey; + + chrome.storage.local.get([uniqueReferenceKey, 'privateKey'], function(extensionConfiguration) { + const privateKey = decode(extensionConfiguration.privateKey).getSeed(); + + const transaction = extensionConfiguration[uniqueReferenceKey]; + const supplementalTransaction = createSupplementalTransaction(decode(transaction), privateKey); + const detailObject = {transaction: transaction, + supplementalTransaction: nyzoStringFromTransaction(supplementalTransaction.getBytes(true))}; + const event = new CustomEvent('micropayTransactionAvailable', { detail: detailObject }); + document.dispatchEvent(event); + }); + } }); } diff --git a/extensionUtil.js b/extensionUtil.js index ecf7548..056d7c6 100644 --- a/extensionUtil.js +++ b/extensionUtil.js @@ -1,3 +1,5 @@ +const genesisBlockHash = hexStringAsUint8Array('bc4cca2a2a50a229-256ae3f5b2b5cd49-aa1df1e2d0192726-c4bb41cdcea15364'); + function isValidPrivateKey(keyString) { var valid = false; if (typeof keyString === 'string') { @@ -20,13 +22,18 @@ function isValidPublicIdentifier(identifierString) { return valid; } -function getTipAmountMicronyzos(tipString) { - return Math.floor(+tipString * 1000000); +function getAmountMicronyzos(valueString) { + return Math.floor(+valueString * 1000000); } function isValidTipAmount(tipString) { - var tipAmountMicronyzos = getTipAmountMicronyzos(tipString); - return tipAmountMicronyzos >= 2 && tipAmountMicronyzos <= 100 * 1000000; + var tipAmountMicronyzos = getAmountMicronyzos(tipString); + return tipAmountMicronyzos >= 2 && tipAmountMicronyzos <= 10 * 1000000; +} + +function isValidMaximumMicropayAmount(micropayString) { + var maximumAmountMicronyzos = getAmountMicronyzos(micropayString); + return maximumAmountMicronyzos >= 2 && maximumAmountMicronyzos <= 50 * 1000000; } function isValidClientUrl(clientUrl) { @@ -57,31 +64,44 @@ function cleanTag(tag) { if (typeof tag !== 'string') { tag = ''; } - return tag.replace(/[^\w_]/g, ''); + return tag.replace(/[^\w_]/g, '').substring(0, 32); } -function submitTransaction(timestamp, senderPrivateSeed, previousHashHeight, previousBlockHash, receiverIdentifier, - micronyzosToSend, senderData, endpoint, callback) { +function cleanDisplayName(name) { + if (typeof name !== 'string') { + name = ''; + } + return name.replace(/[^\w_ ]/g, ''); +} + +function submitTransaction(timestamp, senderPrivateSeed, receiverIdentifier, micronyzosToSend, senderData, endpoint, + callback) { // Create and sign the transaction. var transaction = new Transaction(); transaction.setTimestamp(timestamp); transaction.setAmount(micronyzosToSend); transaction.setRecipientIdentifier(receiverIdentifier); - transaction.setPreviousHashHeight(previousHashHeight); - transaction.setPreviousBlockHash(previousBlockHash); + transaction.setPreviousHashHeight(0); + transaction.setPreviousBlockHash(genesisBlockHash); transaction.setSenderData(senderData); transaction.sign(senderPrivateSeed); var httpRequest = new XMLHttpRequest(); + var transactionString = nyzoStringFromTransaction(transaction.getBytes(true)); httpRequest.onreadystatechange = function() { if (this.readyState == 4) { // 4 == "DONE" - var result = JSON.parse(this.responseText); + var result = null; + try { + result = JSON.parse(this.responseText); + } catch (exception) { + errors = ['The response from the server was not valid.']; + } var success = false; var messages = null; var warnings = null; var errors = null; - if (typeof result === 'object') { + if (typeof result === 'object' && result != null) { // Store the warnings and errors. if (typeof result.errors === 'object') { errors = result.errors; @@ -103,7 +123,7 @@ function submitTransaction(timestamp, senderPrivateSeed, previousHashHeight, pre // Ensure some feedback is provided. if (messages === null && warnings === null && errors === null) { - errors = ['The tip failed to send.']; + errors = ['The transaction failed to send.']; } if (messages === null) { messages = []; @@ -112,12 +132,30 @@ function submitTransaction(timestamp, senderPrivateSeed, previousHashHeight, pre warnings = []; } - callback(success, messages, warnings, errors); + callback(success, messages, warnings, errors, transactionString); } }; - var transactionString = nyzoStringFromTransaction(transaction.getBytes(true)); var fullUrl = endpoint + '?transaction=' + transactionString; httpRequest.open('GET', fullUrl, true); httpRequest.send(); } + +function createSupplementalTransaction(referenceTransaction, senderPrivateSeed) { + // Create and sign the transaction with a current timestamp, an amount of 0, and all other fields the same as the + // reference transaction. + const supplementalTransaction = new Transaction(); + supplementalTransaction.setTimestamp(Date.now()); + supplementalTransaction.setAmount(1); + supplementalTransaction.setRecipientIdentifier(referenceTransaction.recipientIdentifier); + supplementalTransaction.setPreviousHashHeight(0); + supplementalTransaction.setPreviousBlockHash(genesisBlockHash); + supplementalTransaction.setSenderData(referenceTransaction.senderData); + supplementalTransaction.sign(senderPrivateSeed); + + return supplementalTransaction; +} + +function isUndefined(value) { + return value === void(0); +} diff --git a/manifest.json b/manifest.json index 5b41590..75ccf55 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Nyzo", - "version": "1", - "description": "Send tips with Nyzo", + "version": "2", + "description": "Send tips and pay for content with Nyzo", "permissions": [ "activeTab", @@ -29,7 +29,10 @@ }, "content_scripts": [{ - "js": ["util.js", "extensionUtil.js", "content.js"], + "js": ["sha256.min.js", "nacl.min.js", "byteBuffer.js", "transaction.js", "util.js", "nyzoString.js", + "extensionUtil.js", "micropayConfiguration.js", "content.js"], + + "matches": [""] }], diff --git a/micropayConfiguration.js b/micropayConfiguration.js new file mode 100644 index 0000000..a7cd513 --- /dev/null +++ b/micropayConfiguration.js @@ -0,0 +1,22 @@ +class MicropayConfiguration { + constructor(clientUrl, receiverId, tag, displayName, amountMicronyzos) { + this.clientUrl = clientUrl; + this.receiverId = receiverId; + this.tag = tag; + this.displayName = displayName; + this.amountMicronyzos = amountMicronyzos; + } +} + +function isValidConfigurationForTips(configuration) { + return configuration != null && + isValidClientUrl(configuration.clientUrl) && + isValidPublicIdentifier(configuration.receiverId) && + configuration.amountMicronyzos === 0; +} + +function isValidConfigurationForMicropay(configuration) { + return isValidClientUrl(configuration.clientUrl) && + isValidPublicIdentifier(configuration.receiverId) && + configuration.amountMicronyzos > 0; +} diff --git a/nyzoString.js b/nyzoString.js index 9f265a2..560a787 100644 --- a/nyzoString.js +++ b/nyzoString.js @@ -100,13 +100,15 @@ function decode(encodedString) { var contentBytes = expandedArray.subarray(headerLength, expandedArray.length - checksumLength); // Make the object from the content array. - if (prefix == 'key_') { + if (prefix === 'key_') { result = new NyzoStringPrivateSeed(contentBytes); - } else if (prefix == 'id__') { + } else if (prefix === 'id__') { result = new NyzoStringPublicIdentifier(contentBytes); - } else if (prefix == 'pre_') { + } else if (prefix === 'pre_') { result = new NyzoStringPrefilledData(contentBytes.subarray(0, 32), contentBytes.subarray(33, contentBytes.length)); + } else if (prefix === 'tx__') { + result = Transaction.fromBytes(contentBytes); } } } diff --git a/options.html b/options.html index a4c9268..6d7069e 100644 --- a/options.html +++ b/options.html @@ -11,7 +11,7 @@

- Nyzo tip extension options + Nyzo extension options

@@ -21,13 +21,20 @@

+ + +
This displays a dialog with options for 1x, 2x, and 5x the base tip amount. So, with a base tip amount of ∩0.1, the dialog provides options of ∩0.1, ∩0.2, and ∩0.5.
- The tip amount must be at least ∩0.000002 and no more than ∩100.000000. + The tip amount must be at least ∩0.000002 and no more than ∩10.000000. +
+ +
+ The maximum Micropay amount must be at least ∩0.000002 and no more than ∩50.000000.

diff --git a/options.js b/options.js index 31044c1..26891e7 100644 --- a/options.js +++ b/options.js @@ -7,22 +7,32 @@ document.addEventListener('DOMContentLoaded', function () { var baseTipField = document.getElementById('baseTip'); baseTipField.addEventListener('input', validateAndStoreBaseTip); + // Register the listener for the maximum Micropay amount. + var maximumMicropayAmountField = document.getElementById('maximumMicropayAmount'); + maximumMicropayAmountField.addEventListener('input', validateAndStoreMaximumMicropayAmount); + // Load the values. - chrome.storage.local.get(null, function(items) { - var privateKey = items['privateKey']; + chrome.storage.local.get(['privateKey', 'baseTip', 'maximumMicropayAmount'], function(items) { + var privateKey = items.privateKey; if (typeof privateKey === 'string' && privateKey.length > 0) { privateKeyField.value = privateKey; } else { privateKeyField.value = 'key_'; } - var baseTip = items['baseTip']; + var baseTip = items.baseTip; if (baseTip != null) { baseTipField.value = baseTip; } + var maximumMicropayAmount = items.maximumMicropayAmount; + if (maximumMicropayAmount != null) { + maximumMicropayAmountField.value = maximumMicropayAmount; + } + validateAndStorePrivateKey(); validateAndStoreBaseTip(); + validateAndStoreMaximumMicropayAmount(); }); }); @@ -58,3 +68,14 @@ function validateAndStoreBaseTip() { } chrome.storage.local.set({baseTip: baseTipValue}); } + +function validateAndStoreMaximumMicropayAmount() { + var maximumMicropayAmountField = document.getElementById('maximumMicropayAmount'); + var maximumMicropayAmountValue = maximumMicropayAmountField.value; + if (isValidMaximumMicropayAmount(maximumMicropayAmountValue)) { + maximumMicropayAmountField.className = 'input input-valid'; + } else { + maximumMicropayAmountField.className = 'input input-invalid'; + } + chrome.storage.local.set({maximumMicropayAmount: maximumMicropayAmountValue}); +} diff --git a/popup.html b/popup.html index 6304d6f..22a0652 100644 --- a/popup.html +++ b/popup.html @@ -1,8 +1,9 @@ - Nyzo tip extension popup + Nyzo extension popup + @@ -13,37 +14,92 @@ - -

Send a Nyzo tip

-
-
-
send 1x tip
-
send 2x tip
-
send 5x tip
-
-

receiver ID: ---

-

client URL: ---

+ +

Nyzo

+ +
+
+ Micropay: label +
+
+
∩0.000000
+
+
+
Micropay notice 0
+
Resend tx
+
Clear tx
+
-
- Click to configure + +
+ +
+
+ Micropay: label +
+
+
∩0.000000
+
+
+
Micropay notice 0
+
Resend tx
+
Clear tx
+
-
- This page does not accept Nyzo tips + +
+ +
+
+ Micropay: label +
+
+
∩0.000000
+
+
+
Micropay notice 0
+
Resend tx
+
Clear tx
+
-