Version 1.3.3
* New feature: verify signatures with the public key * New feature: convert nano address to a public key * Add documentation about how to verify ownership of user's Nano address by doing a signature challenge * npm audit fix
This commit is contained in:
parent
1134d96e28
commit
51966303ec
10 changed files with 230 additions and 57 deletions
28
README.md
28
README.md
|
|
@ -174,6 +174,32 @@ const data = {
|
|||
|
||||
// Returns a correctly formatted and signed block ready to be sent to the blockchain
|
||||
const signedBlock = block.representative(data, privateKey)
|
||||
```
|
||||
#### Verifying signatures
|
||||
Cryptocurrencies rely on public key cryptographgy. This means that you can use the public key to validate the signature of the block that is signed with the private key.
|
||||
```javascript
|
||||
import { tools } from 'nanocurrency-web'
|
||||
|
||||
const valid = tools.verifyBlock(publicKey, block)
|
||||
```
|
||||
##### Using signature verification to prove ownership of the address
|
||||
You are able to challenge an user to prove ownership of a Nano address simply by making the user sign any string with the private key and validating the signature.
|
||||
```javascript
|
||||
import { tools } from 'nanocurrency-web'
|
||||
|
||||
const nanoAddress = 'nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d'
|
||||
const privateKey = '3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143'
|
||||
const data = 'sign this'
|
||||
|
||||
// Make the user sign the data
|
||||
const signature = tools.sign(privateKey, data)
|
||||
|
||||
// Infer the user's public key from the address (if not already known)
|
||||
const publicKey = tools.addressToPublicKey(nanoAddress)
|
||||
|
||||
// Verify the signature using the public key, the signature and the original data
|
||||
const validSignature = tools.verify(publicKey, signature, data)
|
||||
|
||||
```
|
||||
|
||||
#### Converting units
|
||||
|
|
@ -217,7 +243,7 @@ const valid = tools.validateMnemonic('edge defense waste choose enrich upon flee
|
|||
### In web
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/nanocurrency-web@1.3.2" type="text/javascript"></script>
|
||||
<script src="https://unpkg.com/nanocurrency-web@1.3.3" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
NanocurrencyWeb.wallet.generate(...);
|
||||
</script>
|
||||
|
|
|
|||
54
index.ts
54
index.ts
|
|
@ -1,8 +1,10 @@
|
|||
import { TextDecoder } from 'util'
|
||||
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import AddressGenerator from './lib/address-generator'
|
||||
import AddressImporter, { Account, Wallet } from './lib/address-importer'
|
||||
import BlockSigner, { ReceiveBlock, RepresentativeBlock, SendBlock, SignedBlock } from './lib/block-signer'
|
||||
import BlockSigner, { BlockData, ReceiveBlock, RepresentativeBlock, SendBlock, SignedBlock } from './lib/block-signer'
|
||||
import NanoAddress from './lib/nano-address'
|
||||
import NanoConverter from './lib/nano-converter'
|
||||
import Signer from './lib/signer'
|
||||
|
|
@ -127,7 +129,7 @@ const wallet = {
|
|||
*
|
||||
*/
|
||||
fromLegacySeed: (seed: string): Wallet => {
|
||||
return importer.fromLegacySeed(seed);
|
||||
return importer.fromLegacySeed(seed)
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -260,7 +262,36 @@ const tools = {
|
|||
*/
|
||||
sign: (privateKey: string, ...input: string[]): string => {
|
||||
const data = input.map(Convert.stringToHex)
|
||||
return signer.sign(privateKey, ...data);
|
||||
return signer.sign(privateKey, ...data)
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies the signature of any input string
|
||||
*
|
||||
* @param {string} publicKey The public key to verify with
|
||||
* @param {string} signature The signature to verify
|
||||
* @param {...string} input Data to verify
|
||||
*/
|
||||
verify: (publicKey: string, signature: string, ...input: string[]): boolean => {
|
||||
const data = input.map(Convert.stringToHex)
|
||||
return signer.verify(publicKey, signature, ...data)
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies the signature of any input string
|
||||
*
|
||||
* @param {string} publicKey The public key to verify with
|
||||
* @param {BlockData} block The block to verify
|
||||
*/
|
||||
verifyBlock: (publicKey: string, block: BlockData) => {
|
||||
const preamble = 0x6.toString().padStart(64, '0')
|
||||
return signer.verify(publicKey, block.signature,
|
||||
preamble,
|
||||
nanoAddress.nanoAddressToHexString(block.account),
|
||||
block.previous,
|
||||
nanoAddress.nanoAddressToHexString(block.representative),
|
||||
Convert.dec2hex(block.balance, 16).toUpperCase(),
|
||||
block.link)
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -269,7 +300,7 @@ const tools = {
|
|||
* @param {string} input The address to validate
|
||||
*/
|
||||
validateAddress: (input: string): boolean => {
|
||||
return nanoAddress.validateNanoAddress(input);
|
||||
return nanoAddress.validateNanoAddress(input)
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -278,7 +309,20 @@ const tools = {
|
|||
* @param {string} input The address to validate
|
||||
*/
|
||||
validateMnemonic: (input: string): boolean => {
|
||||
return importer.validateMnemonic(input);
|
||||
return importer.validateMnemonic(input)
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a Nano address to a public key
|
||||
*
|
||||
* @param {string} input Nano address to convert
|
||||
*/
|
||||
addressToPublicKey: (input: string): string => {
|
||||
const cleaned = input
|
||||
.replace('nano_', '')
|
||||
.replace('xrb_', '')
|
||||
const publicKeyBytes = nanoAddress.decodeNanoBase32(cleaned)
|
||||
return Convert.ab2hex(publicKeyBytes).slice(0, 64)
|
||||
},
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//@ts-ignore
|
||||
import { enc, algo } from 'crypto-js'
|
||||
import { algo, enc } from 'crypto-js'
|
||||
|
||||
import Convert from './util/convert'
|
||||
|
||||
const ED25519_CURVE = 'ed25519 seed'
|
||||
|
|
|
|||
|
|
@ -58,9 +58,9 @@ export default class BlockSigner {
|
|||
const newBalanceNano = new BigNumber(balanceNano).plus(new BigNumber(amountNano))
|
||||
const newBalanceRaw = NanoConverter.convert(newBalanceNano, 'NANO', 'RAW')
|
||||
const newBalanceHex = Convert.dec2hex(newBalanceRaw, 16).toUpperCase()
|
||||
const account = this.nanoAddressToHexString(data.toAddress)
|
||||
const account = this.nanoAddress.nanoAddressToHexString(data.toAddress)
|
||||
const link = data.transactionHash
|
||||
const representative = this.nanoAddressToHexString(data.representativeAddress)
|
||||
const representative = this.nanoAddress.nanoAddressToHexString(data.representativeAddress)
|
||||
|
||||
const signature = this.signer.sign(
|
||||
privateKey,
|
||||
|
|
@ -125,9 +125,9 @@ export default class BlockSigner {
|
|||
const newBalanceNano = new BigNumber(balanceNano).minus(new BigNumber(amountNano))
|
||||
const newBalanceRaw = NanoConverter.convert(newBalanceNano, 'NANO', 'RAW')
|
||||
const newBalanceHex = Convert.dec2hex(newBalanceRaw, 16).toUpperCase()
|
||||
const account = this.nanoAddressToHexString(data.fromAddress)
|
||||
const link = this.nanoAddressToHexString(data.toAddress)
|
||||
const representative = this.nanoAddressToHexString(data.representativeAddress)
|
||||
const account = this.nanoAddress.nanoAddressToHexString(data.fromAddress)
|
||||
const link = this.nanoAddress.nanoAddressToHexString(data.toAddress)
|
||||
const representative = this.nanoAddress.nanoAddressToHexString(data.representativeAddress)
|
||||
|
||||
const signature = this.signer.sign(
|
||||
privateKey,
|
||||
|
|
@ -150,23 +150,6 @@ export default class BlockSigner {
|
|||
}
|
||||
}
|
||||
|
||||
private nanoAddressToHexString(addr: string): string {
|
||||
addr = addr.slice(-60)
|
||||
const isValid = /^[13456789abcdefghijkmnopqrstuwxyz]+$/.test(addr)
|
||||
if (isValid) {
|
||||
const keyBytes = this.nanoAddress.decodeNanoBase32(addr.substring(0, 52))
|
||||
const hashBytes = this.nanoAddress.decodeNanoBase32(addr.substring(52, 60))
|
||||
const blakeHash = blake2b(keyBytes, undefined, 5).reverse()
|
||||
if (Convert.ab2hex(hashBytes) == Convert.ab2hex(blakeHash)) {
|
||||
const key = Convert.ab2hex(keyBytes).toUpperCase()
|
||||
return key
|
||||
}
|
||||
throw new Error('Checksum mismatch in address')
|
||||
} else {
|
||||
throw new Error('Illegal characters in address')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface ReceiveBlock {
|
||||
|
|
@ -197,13 +180,16 @@ export interface RepresentativeBlock {
|
|||
work?: string
|
||||
}
|
||||
|
||||
export interface SignedBlock {
|
||||
export interface SignedBlock extends BlockData {
|
||||
type: 'state'
|
||||
work?: string
|
||||
}
|
||||
|
||||
export interface BlockData {
|
||||
account: string
|
||||
previous: string
|
||||
representative: string
|
||||
balance: string
|
||||
link: string
|
||||
signature: string
|
||||
work: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//@ts-ignore
|
||||
import { blake2b, blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs'
|
||||
|
||||
import Convert from './util/convert'
|
||||
import Curve25519 from './util/curve25519'
|
||||
|
||||
//@ts-ignore
|
||||
import { blake2b } from 'blakejs'
|
||||
import Util from './util/util'
|
||||
|
||||
export default class Ed25519 {
|
||||
|
||||
|
|
@ -120,11 +121,11 @@ export default class Ed25519 {
|
|||
/**
|
||||
* Generate a message signature
|
||||
* @param {Uint8Array} msg Message to be signed as byte array
|
||||
* @param {Uint8Array} secretKey Secret key as byte array
|
||||
* @param {Uint8Array} privateKey Secret key as byte array
|
||||
* @param {Uint8Array} Returns the signature as 64 byte typed array
|
||||
*/
|
||||
sign(msg: Uint8Array, secretKey: Uint8Array): Uint8Array {
|
||||
const signedMsg = this.naclSign(msg, secretKey)
|
||||
sign(msg: Uint8Array, privateKey: Uint8Array): Uint8Array {
|
||||
const signedMsg = this.naclSign(msg, privateKey)
|
||||
const sig = new Uint8Array(64)
|
||||
|
||||
for (let i = 0; i < sig.length; i++) {
|
||||
|
|
@ -134,6 +135,44 @@ export default class Ed25519 {
|
|||
return sig
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a message signature
|
||||
* @param {Uint8Array} msg Message to be signed as byte array
|
||||
* @param {Uint8Array} publicKey Public key as byte array
|
||||
* @param {Uint8Array} signature Signature as byte array
|
||||
* @param {Uint8Array} Returns the signature as 64 byte typed array
|
||||
*/
|
||||
verify(msg: Uint8Array, publicKey: Uint8Array, signature: Uint8Array): boolean {
|
||||
const CURVE = this.curve;
|
||||
const p = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()]
|
||||
const q = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()]
|
||||
|
||||
if (signature.length !== 64) {
|
||||
return false
|
||||
}
|
||||
if (publicKey.length !== 32) {
|
||||
return false
|
||||
}
|
||||
if (CURVE.unpackNeg(q, publicKey)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const ctx = blake2bInit(64, undefined)
|
||||
blake2bUpdate(ctx, signature.subarray(0, 32))
|
||||
blake2bUpdate(ctx, publicKey)
|
||||
blake2bUpdate(ctx, msg)
|
||||
let k = blake2bFinal(ctx)
|
||||
this.reduce(k)
|
||||
this.scalarmult(p, q, k)
|
||||
|
||||
let t = new Uint8Array(32)
|
||||
this.scalarbase(q, signature.subarray(32))
|
||||
CURVE.add(p, q)
|
||||
this.pack(t, p)
|
||||
|
||||
return Util.compare(signature.subarray(0, 32), t)
|
||||
}
|
||||
|
||||
private naclSign(msg: Uint8Array, secretKey: Uint8Array): Uint8Array {
|
||||
if (secretKey.length !== 32) {
|
||||
throw new Error('bad secret key size')
|
||||
|
|
|
|||
|
|
@ -108,7 +108,24 @@ export default class NanoAddress {
|
|||
return expectedChecksum === actualChecksum
|
||||
}
|
||||
|
||||
readChar(char: string): number {
|
||||
nanoAddressToHexString = (addr: string): string => {
|
||||
addr = addr.slice(-60)
|
||||
const isValid = /^[13456789abcdefghijkmnopqrstuwxyz]+$/.test(addr)
|
||||
if (isValid) {
|
||||
const keyBytes = this.decodeNanoBase32(addr.substring(0, 52))
|
||||
const hashBytes = this.decodeNanoBase32(addr.substring(52, 60))
|
||||
const blakeHash = blake2b(keyBytes, undefined, 5).reverse()
|
||||
if (Convert.ab2hex(hashBytes) == Convert.ab2hex(blakeHash)) {
|
||||
const key = Convert.ab2hex(keyBytes).toUpperCase()
|
||||
return key
|
||||
}
|
||||
throw new Error('Checksum mismatch in address')
|
||||
} else {
|
||||
throw new Error('Illegal characters in address')
|
||||
}
|
||||
}
|
||||
|
||||
private readChar(char: string): number {
|
||||
const idx = this.alphabet.indexOf(char)
|
||||
|
||||
if (idx === -1) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import Convert from './util/convert'
|
||||
import Ed25519 from './ed25519'
|
||||
|
||||
//@ts-ignore
|
||||
import { blake2bInit, blake2bUpdate, blake2bFinal } from 'blakejs'
|
||||
|
||||
import { blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs'
|
||||
|
||||
import Ed25519 from './ed25519'
|
||||
import Convert from './util/convert'
|
||||
|
||||
export default class Signer {
|
||||
|
||||
|
||||
ed25519 = new Ed25519()
|
||||
|
||||
/**
|
||||
* Signs any data using the ed25519 signature system
|
||||
*
|
||||
*
|
||||
* @param privateKey Private key to sign the data with
|
||||
* @param data Data to sign
|
||||
*/
|
||||
|
|
@ -21,9 +22,23 @@ export default class Signer {
|
|||
return Convert.ab2hex(signature)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the signature with a public key
|
||||
*
|
||||
* @param publicKey Public key to verify the data with
|
||||
* @param signature Signature to verify
|
||||
* @param data Data to verify
|
||||
*/
|
||||
verify(publicKey: string, signature: string, ...data: string[]): boolean {
|
||||
return this.ed25519.verify(
|
||||
this.generateHash(data),
|
||||
Convert.hex2ab(publicKey),
|
||||
Convert.hex2ab(signature));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a blake2b hash of the input data
|
||||
*
|
||||
*
|
||||
* @param data Data to hash
|
||||
*/
|
||||
generateHash(data: string[]): Uint8Array {
|
||||
|
|
|
|||
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "nanocurrency-web",
|
||||
"version": "1.3.2",
|
||||
"version": "1.3.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
@ -2247,9 +2247,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
|
||||
},
|
||||
"log-symbols": {
|
||||
|
|
@ -3463,9 +3463,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "nanocurrency-web",
|
||||
"version": "1.3.2",
|
||||
"version": "1.3.3",
|
||||
"description": "Toolkit for Nano cryptocurrency client side offline integrations",
|
||||
"author": "Miro Metsänheimo <miro@metsanheimo.fi>",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
53
test/test.js
53
test/test.js
|
|
@ -256,14 +256,59 @@ describe('unit conversion tests', () => {
|
|||
|
||||
describe('Signer tests', () => {
|
||||
|
||||
let testWallet;
|
||||
|
||||
before(() => {
|
||||
this.testWallet = wallet.generate();
|
||||
})
|
||||
|
||||
// Private key: 3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143
|
||||
// Public key: 5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4
|
||||
|
||||
it('should sign data with a single parameter', () => {
|
||||
const result = tools.sign('781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3', 'miro@metsanheimo.fi')
|
||||
expect(result).to.equal('0ede9f287b7d58a053aa9ad84419c856ac39ec4c2453098ef19abf9638b07b1993e0cd3747723aada71602e92e781060dc3b91c410d32def1b4780a62fd0eb02')
|
||||
const result = tools.sign('3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143', 'miro@metsanheimo.fi')
|
||||
expect(result).to.equal('fecb9b084065adc969904b55a0099c63746b68df41fecb713244d387eed83a80b9d4907278c5ebc0998a5fc8ba597fbaaabbfce0abd2ca2212acfe788637040c')
|
||||
})
|
||||
|
||||
it('should sign data with multiple parameters', () => {
|
||||
const result = tools.sign('781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3', 'miro@metsanheimo.fi', 'somePassword')
|
||||
expect(result).to.equal('a7b88357a160f54cf4db2826c86483eb60e66e8ccb36f9a37f3fb636c9d80f7b59d1fba88d0be27f85ac3fcbe5c6e13f911d7e5b713e86fb8e9a635932a2af05')
|
||||
const result = tools.sign('3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143', 'miro@metsanheimo.fi', 'somePassword')
|
||||
expect(result).to.equal('bb534f9b469af451b1941ffef8ee461fc5d284b5d393140900c6e13a65ef08d0ae2bc77131ee182922f66c250c7237a83878160457d5c39a70e55f7fce925804')
|
||||
})
|
||||
|
||||
it('should verify a signature using the public key', () => {
|
||||
const result = tools.verify('5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4', 'fecb9b084065adc969904b55a0099c63746b68df41fecb713244d387eed83a80b9d4907278c5ebc0998a5fc8ba597fbaaabbfce0abd2ca2212acfe788637040c', 'miro@metsanheimo.fi')
|
||||
expect(result).to.be.true
|
||||
|
||||
const result2 = tools.verify('5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4', 'fecb9b084065adc969904b55a0099c63746b68df41fecb713244d387eed83a80b9d4907278c5ebc0998a5fc8ba597fbaaabbfce0abd2ca2212acfe788637040c', 'mir@metsanheimo.fi')
|
||||
expect(result2).to.be.false
|
||||
|
||||
const result3 = tools.verify('5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4', 'aecb9b084065adc969904b55a0099c63746b68df41fecb713244d387eed83a80b9d4907278c5ebc0998a5fc8ba597fbaaabbfce0abd2ca2212acfe788637040c', 'miro@metsanheimo.fi')
|
||||
expect(result3).to.be.false
|
||||
})
|
||||
|
||||
it('should verify a block using the public key', () => {
|
||||
const sendBlock = block.send({
|
||||
walletBalanceRaw: '5618869000000000000000000000000',
|
||||
fromAddress: this.testWallet.accounts[0].address,
|
||||
toAddress: 'nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p',
|
||||
representativeAddress: 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou',
|
||||
frontier: '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D',
|
||||
amountRaw: '2000000000000000000000000000000',
|
||||
}, this.testWallet.accounts[0].privateKey)
|
||||
|
||||
const publicKey = tools.addressToPublicKey(this.testWallet.accounts[0].address)
|
||||
|
||||
const valid = tools.verifyBlock(publicKey, sendBlock)
|
||||
expect(valid).to.be.true
|
||||
|
||||
sendBlock.account = 'nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p'
|
||||
const valid2 = tools.verifyBlock(this.testWallet.accounts[0].publicKey, sendBlock)
|
||||
expect(valid2).to.be.false
|
||||
})
|
||||
|
||||
it('should convert a Nano address to public key', () => {
|
||||
const publicKey = tools.addressToPublicKey('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d')
|
||||
expect(publicKey).to.equal('5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4')
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue