]> zoso.dev Git - libnemo.git/commitdiff
Version 1.2.0
authorMiro Metsänheimo <miro@metsanheimo.fi>
Sat, 2 May 2020 20:06:01 +0000 (23:06 +0300)
committerMiro Metsänheimo <miro@metsanheimo.fi>
Sat, 2 May 2020 20:06:01 +0000 (23:06 +0300)
* Import wallets with legacy Nano hex seeds

README.md
index.ts
lib/address-importer.ts
lib/signer.ts
package-lock.json
package.json
test/test.js

index 44a29d08c4f1702ba51eb557d3750128df2e2704..d5c380d3d153d54da9e2d6e56d0ad6b05d78d207 100644 (file)
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ The toolkit supports creating and importing wallets and signing blocks on-device
 * BIP39/44 private key derivation
 * Mnemonic is compatible with the Ledger Nano implementation
 * Import wallets with a mnemonic phrase or a seed
+* Import wallets with the legacy Nano hex seed
 * Sign send, receive and change representative blocks with a private key
 * Runs in all web browsers and mobile frameworks built with Javascript
 * Convert Nano units
@@ -48,12 +49,18 @@ const wallet = wallet.fromMnemonic(mnemonic, seedPassword?)
 // Import a wallet with a seed
 const wallet = wallet.fromSeed(seed)
 
+// Import a wallet with a legacy hex seed
+const wallet = wallet.fromLegacySeed(seed)
+
 // Derive private keys for a seed, from and to are number indexes
 const accounts = wallet.accounts(seed, from, to)
+
+// Derive private keys for a legacy seed, from and to are number indexes
+const accounts = wallet.legacyAccounts(seed, from, to)
 ```
 
 ```javascript
-// The returned wallet JSON format is as follows:
+// The returned wallet JSON format is as follows. The mnemonic phrase will be undefined when importing with a seed.
 {
     mnemonic: 'edge defense waste choose enrich upon flee junk siren film clown finish luggage leader kid quick brick print evidence swap drill paddle truly occur',
     seed: '0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310c',
@@ -177,7 +184,7 @@ const converted = tools.convert('1000000000000000000000000000000', 'RAW', 'NANO'
 
 #### Signing any data with the private key
 
-For example implementing client side login with the password being the user's e-mail signed with their private key
+For example implementing client side login with the password being the user's e-mail signed with their private key. Make sure that you double check the signature on the back-end side with the public key.
 
 ```javascript
 import { tools } from 'nanocurrency-web'
@@ -190,7 +197,7 @@ const signed = tools.sign(privateKey, 'foo@bar.com')
 ### In web
 
 ```html
-<script src="https://unpkg.com/nanocurrency-web@1.1.1" type="text/javascript"></script>
+<script src="https://unpkg.com/nanocurrency-web@1.2.0" type="text/javascript"></script>
 <script type="text/javascript">
     NanocurrencyWeb.wallet.generate(...);
 </script>
index e6099b9aa17ab0be72ddd7775fc9e45b49799941..58d294e74c359ed9aa3b7cbe374f5638fe974440 100644 (file)
--- a/index.ts
+++ b/index.ts
@@ -28,8 +28,8 @@ const wallet = {
         * An optional seed password can be used to encrypt the mnemonic phrase so the seed
         * cannot be derived correctly without the password. Recovering the password is not possible.
         *
-        * @param {string} [entropy] Optional 64 byte hexadecimal string entropy to be used instead of the default
-        * @param {string} [seedPassword] Optional seed password
+        * @param {string} [entropy] - (Optional) 64 byte hexadecimal string entropy to be used instead of the default
+        * @param {string} [seedPassword] - (Optional) seed password
         * @returns the generated mnemonic, seed and account
         */
        generate: (entropy?: string, seedPassword?: string): Wallet => {
@@ -46,10 +46,10 @@ const wallet = {
         * The Nano address is derived from the public key using standard Nano encoding.
         * The address is prefixed with 'nano_'.
         *
-        * @param {string} mnemonic The mnemonic phrase. Words are separated with a space
-        * @param {string} [seedPassword] Optional seed password
+        * @param {string} mnemonic The mnemonic phrase. Words are separated with a space
+        * @param {string} [seedPassword] - (Optional) seed password
         * @throws Throws an error if the mnemonic phrase doesn't pass validations
-        * @returns the imported mnemonic, seed and account
+        * @returns the wallet derived from the mnemonic (mnemonic, seed, account)
         */
        fromMnemonic: (mnemonic: string, seedPassword?: string): Wallet => {
                return importer.fromMnemonic(mnemonic, seedPassword)
@@ -65,13 +65,31 @@ const wallet = {
         * The Nano address is derived from the public key using standard Nano encoding.
         * The address is prefixed with 'nano_'.
         *
-        * @param {string} seed The seed
-        * @returns the importes seed and account
+        * @param {string} seed The seed
+        * @returns {Wallet} the wallet derived from the seed (seed, account)
         */
        fromSeed: (seed: string): Wallet => {
                return importer.fromSeed(seed)
        },
 
+       /**
+        * Import Nano cryptocurrency accounts from a legacy hex seed
+        * 
+        * This function imports a wallet from a seed. The private key is derived from the seed using
+        * simply a blake2b hash function. The public key is derived from the private key using the ed25519 curve
+        * algorithm.
+        * 
+        * The Nano address is derived from the public key using standard Nano encoding.
+        * The address is prefixed with 'nano_'.
+        * 
+        * @param {string} seed - The seed
+        * @returns the wallet derived from the seed (seed, account)
+        * 
+        */
+       fromLegacySeed: (seed: string): Wallet => {
+               return importer.fromLegacySeed(seed);
+       },
+
        /**
         * Derive accounts for the seed
         *
@@ -79,14 +97,28 @@ const wallet = {
         * from the given seed with input parameters 44'/165' and indexes based on the from and to
         * parameters.
         *
-        * @param {string} seed The seed
-        * @param {number} from The start index
-        * @param {number} to The end index
+        * @param {string} seed The seed
+        * @param {number} from The start index
+        * @param {number} to The end index
         */
        accounts: (seed: string, from: number, to: number): Account[] => {
                return importer.fromSeed(seed, from, to).accounts
        },
 
+       /**
+        * Derive accounts for the legacy hex seed
+        *
+        * This function derives Nano accounts with the given seed with indexes
+        * based on the from and to parameters.
+        *
+        * @param {string} seed - The seed
+        * @param {number} from - The start index
+        * @param {number} to - The end index
+        */
+       legacyAccounts: (seed: string, from: number, to: number): Account[] => {
+               return importer.fromLegacySeed(seed, from, to).accounts
+       },
+
 }
 
 const blockSigner = new BlockSigner()
index cbad1116e85402afe5af816ea2148373fa92dca8..291538d66a671adc2b5c8cdcc0033213cfbdf261 100644 (file)
@@ -2,6 +2,8 @@ import Bip32KeyDerivation from './bip32-key-derivation'
 import Bip39Mnemonic from './bip39-mnemonic'
 import Ed25519 from './ed25519'
 import NanoAddress from './nano-address'
+import Signer from './signer'
+import Convert from './util/convert'
 
 export default class AddressImporter {
 
@@ -40,6 +42,43 @@ export default class AddressImporter {
 
                return this.nano(seed, from, to, undefined)
        }
+       
+
+       /**
+        * Import a wallet using a legacy seed
+        * 
+        * @param {string} seed - The seed to import the wallet from
+        * @param {number} [from] - (Optional) The start index of the private keys to derive from
+        * @param {number} [to] - (Optional) The end index of the private keys to derive to
+        * @returns {Wallet} The wallet derived from the seed
+        */
+       fromLegacySeed(seed: string, from: number = 0, to: number = 0): Wallet {
+               const signer = new Signer()
+
+               const accounts: Account[] = []
+               for (let i = from; i <= to; i++) {
+                       const privateKey = Convert.ab2hex(signer.generateHash([seed, Convert.dec2hex(i, 4)]))
+
+                       const ed25519 = new Ed25519()
+                       const keyPair = ed25519.generateKeys(privateKey)
+
+                       const nano = new NanoAddress()
+                       const address = nano.deriveAddress(keyPair.publicKey)
+
+                       accounts.push({
+                               accountIndex: i,
+                               privateKey: keyPair.privateKey,
+                               publicKey: keyPair.publicKey,
+                               address,
+                       })
+               }
+
+               return {
+                       mnemonic: undefined,
+                       seed,
+                       accounts,
+               }
+       }
 
        /**
         * Derives the private keys
@@ -61,6 +100,7 @@ export default class AddressImporter {
 
                        const nano = new NanoAddress()
                        const address = nano.deriveAddress(keyPair.publicKey)
+
                        accounts.push({
                                accountIndex: i,
                                privateKey: keyPair.privateKey,
index a353d980f945b2cb7376658bde8256ae0a8df400..a16d2d295fee5ebd369c29c77b8825ce61c26e9f 100644 (file)
@@ -26,7 +26,7 @@ export default class Signer {
         * 
         * @param data Data to hash
         */
-       private generateHash(data: string[]): Uint8Array {
+       generateHash(data: string[]): Uint8Array {
                const ctx = blake2bInit(32, undefined)
                data.forEach(str => blake2bUpdate(ctx, Convert.hex2ab(str)))
                return blake2bFinal(ctx)
index 1cbc803e853a1103a491fb130229125cba452ec5..72a9fbbc4cbe3ba3b78744f9d5c6ea32cf834a0d 100644 (file)
@@ -1,6 +1,6 @@
 {
        "name": "nanocurrency-web",
-       "version": "1.1.1",
+       "version": "1.2.0",
        "lockfileVersion": 1,
        "requires": true,
        "dependencies": {
index 441ef42a0b6a370beb342afbf3891d0afc3f598d..c3b1482e0885db21249d9653d6295e2524b490fe 100644 (file)
@@ -1,6 +1,6 @@
 {
        "name": "nanocurrency-web",
-       "version": "1.1.1",
+       "version": "1.2.0",
        "description": "Toolkit for Nano cryptocurrency client side offline integrations",
        "author": "Miro Metsänheimo <miro@metsanheimo.fi>",
        "license": "MIT",
index c67bd7acfc9e35935a90e6a4d18b21850edb326e..865828028a494ec2d8e55eb02035030fbf355bd3 100644 (file)
@@ -85,6 +85,31 @@ describe('import wallet with official test vectors test', () => {
                expect(result.accounts[0].address).to.equal('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d')
        })
 
+       it('should successfully import a legacy hex wallet with the a seed', () => {
+               const result = wallet.fromLegacySeed('0000000000000000000000000000000000000000000000000000000000000000')
+               expect(result).to.have.own.property('mnemonic')
+               expect(result).to.have.own.property('seed')
+               expect(result).to.have.own.property('accounts')
+               expect(result.mnemonic).to.be.undefined
+               expect(result.seed).to.equal('0000000000000000000000000000000000000000000000000000000000000000')
+               expect(result.accounts[0].privateKey).to.equal('9f0e444c69f77a49bd0be89db92c38fe713e0963165cca12faf5712d7657120f')
+               expect(result.accounts[0].publicKey).to.equal('c008b814a7d269a1fa3c6528b19201a24d797912db9996ff02a1ff356e45552b')
+               expect(result.accounts[0].address).to.equal('nano_3i1aq1cchnmbn9x5rsbap8b15akfh7wj7pwskuzi7ahz8oq6cobd99d4r3b7')
+       })
+
+       it('should successfully import legacy hex accounts with the a seed', () => {
+               const accounts = wallet.legacyAccounts('0000000000000000000000000000000000000000000000000000000000000000', 0, 3)
+               expect(accounts[0]).to.have.own.property('accountIndex')
+               expect(accounts[0]).to.have.own.property('privateKey')
+               expect(accounts[0]).to.have.own.property('publicKey')
+               expect(accounts[0]).to.have.own.property('address')
+               expect(accounts).to.have.lengthOf(4)
+               expect(accounts[2].accountIndex).to.equal(2)
+               expect(accounts[2].privateKey).to.equal('6a1804198020b080996ba45b5891f8227d7a4f41c8479824423780d234939d58')
+               expect(accounts[2].publicKey).to.equal('2fea520fe54f5d0dca79d553d9c7f5af7db6ac17586dbca6905794caadc639df')
+               expect(accounts[2].address).to.equal('nano_1dzcca9ycmtx3q79mocmu95zdduxptp3gp5fqkmb1ownscpweggzah8cb4rb')
+       })
+
        it('should throw when given a seed with an invalid length', () => {
                expect(() => wallet.generate('0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310')).to.throw(Error)
                expect(() => wallet.generate('0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310cd')).to.throw(Error)