Skip to content

Commit

Permalink
Merge pull request #60 from EOSIO/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jlamarr22 authored Jan 17, 2020
2 parents 4d2e0c8 + d5af79a commit 6a110e2
Show file tree
Hide file tree
Showing 6 changed files with 709 additions and 554 deletions.
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ual-scatter",
"version": "0.1.6",
"version": "0.1.8",
"main": "dist/index.js",
"license": "MIT",
"author": {
Expand All @@ -21,10 +21,10 @@
"test": "jest"
},
"dependencies": {
"eosjs": "20.0.0",
"eosjs-ecc": "4.0.4",
"scatterjs-core": "2.7.17",
"scatterjs-plugin-eosjs2": "^1.5.0",
"@scatterjs/core": "2.7.51",
"@scatterjs/eosjs2": "1.5.33",
"elliptic": "6.5.2",
"eosjs": "21.0.1-rc1",
"universal-authenticator-library": "0.1.4"
},
"resolutions": {
Expand Down Expand Up @@ -54,8 +54,10 @@
"devDependencies": {
"@babel/runtime": "^7.2.0",
"@blockone/tslint-config-blockone": "^3.0.0",
"@types/elliptic": "^6.4.10",
"@types/jest": "^24.0.15",
"jest": "^24.8.0",
"@types/node": "^12.12.20",
"jest": "^24.9.0",
"ts-jest": "^24.0.2",
"tslint": "^5.11.0",
"typescript": "^3.2.2"
Expand Down
4 changes: 2 additions & 2 deletions src/Scatter.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Chain, RpcEndpoint, UALErrorType } from 'universal-authenticator-library'
import ScatterJS from 'scatterjs-core'
import ScatterJS from '@scatterjs/core'
import { Name } from './interfaces'
import { Scatter } from './Scatter'
import { UALScatterError } from './UALScatterError'

declare var window: any

jest.mock('scatterjs-core')
jest.mock('@scatterjs/core')

const endpoint: RpcEndpoint = {
protocol: 'https',
Expand Down
4 changes: 2 additions & 2 deletions src/Scatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ScatterJS from 'scatterjs-core'
import ScatterEOS from 'scatterjs-plugin-eosjs2'
import ScatterJS from '@scatterjs/core'
import ScatterEOS from '@scatterjs/eosjs2'
import {
Authenticator, ButtonStyle, Chain,
UALError, UALErrorType, User
Expand Down
143 changes: 139 additions & 4 deletions src/ScatterUser.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ec as EC } from 'elliptic'
import { Chain, RpcEndpoint, UALError, UALErrorType } from 'universal-authenticator-library'
import { ScatterUser } from './ScatterUser'
import { Signature, PrivateKey } from 'eosjs/dist/eosjs-jssig'
import { Numeric } from 'eosjs'

const { KeyType } = Numeric

const endpoint: RpcEndpoint = {
protocol: 'https',
Expand All @@ -12,11 +17,26 @@ const chain: Chain = {
rpcEndpoints: [endpoint]
}

const api: any = {}
const scatter: any = {
eos: jest.fn().mockImplementation(() => api),
authenticate: jest.fn().mockImplementation(() => {
return new Promise((resolve) => {
resolve(signatureValue)
})
})
}

const challenges = ['12345678', '87654321']
const privateKeys = [
'5Juww5SS6aLWxopXBAWzwqrwadiZKz7XpKAiktXTKcfBGi1DWg8',
'5JnHjSFwe4r7xyqAUAaVs51G7HmzE86DWGa3VAA5VvQriGYnSUr',
'5K4XZH5XR2By7Q5KTcZnPAmUMU5yjUNBdoKzzXyrLfmiEZJqoKE'
]
const signatureValue = 'SIG_K1_HKkqi3zray76i63ZQwAHWMjoLk3wTa1ajZWPcUnrhgmSWQYEHDJsxkny6VDTWEmVdfktxpGoTA81qe6QuCrDmazeQndmxh'
const publicKeys = ['PUB_K1_7tgwU6E7pAUQJgqEJt66Yi8cWvanTUW8ZfBjeXeJBQvhYTBFvY', 'PUB_K1_8aBcRwL2xrEGQNShB6SyUszUxATXZFDyEza4vGypUJtHBdNeDa']

describe('ScatterUser', () => {
const api: any = {}
const scatter: any = {
eos: jest.fn().mockImplementation(() => api)
}
let user

beforeEach(() => {
Expand Down Expand Up @@ -78,4 +98,119 @@ describe('ScatterUser', () => {
}
})
})

describe('verifyKeyOwnership', () => {
it('should reject promise with an error if timeout is reached', async () => {
user.authenticate = jest.fn().mockImplementation((challenge, resolve) => {
jest.runOnlyPendingTimers()
setTimeout(() => {
resolve(challenge)
})
})
jest.useFakeTimers()

await expect(user.verifyKeyOwnership(challenges[0])).rejects.toThrow()

expect(user.authenticate).toHaveBeenCalledTimes(1)
})

it('should execute properly without an error if timeout is not reached', async () => {
user.authenticate = jest.fn().mockImplementation((challenge, resolve) => {
resolve(challenge)
})

const result = await user.verifyKeyOwnership(challenges[0])

expect(result).toBe(challenges[0])
expect(user.authenticate).toHaveBeenCalledTimes(1)
})
})

describe('getPublicKey', () => {
it('should be able to get public key from signature', () => {
const ec = new EC('secp256k1')
const KPriv = privateKeys[0]
const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic()

const dataAsString = 'some string'
const ellipticHashedString = ec.hash().update(dataAsString).digest()

const ellipticSig = KPrivElliptic.sign(ellipticHashedString)
const ellipticSigString = Signature.fromElliptic(ellipticSig, KeyType.k1).toString()

const eosioPubKey = user.getPublicKey(dataAsString, ellipticSigString)
expect(eosioPubKey.toString()).toEqual(publicKeys[0])
})

it('should fail to get public key from signature using invalid elliptic hash ', () => {
const ec = new EC('secp256k1')
const KPriv = privateKeys[0]
const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic()

const dataAsString = 'some string'
const ellipticHashedString = ec.hash().update(dataAsString).digest()

const ellipticSig = KPrivElliptic.sign(ellipticHashedString)
const ellipticSigString = Signature.fromElliptic(ellipticSig, KeyType.k1).toString()

const eosioPubKey = user.getPublicKey('other string', ellipticSigString)
expect(eosioPubKey.toString()).not.toEqual(publicKeys[0])
})

it('verify invalid signature string results in invalid public key from signature', () => {
const dataAsString = 'some string'
const eosioPubKey = user.getPublicKey(dataAsString, signatureValue)
expect(eosioPubKey.toString()).not.toEqual(publicKeys[0])
})
})

describe('authenticate', () => {
it('should resolve promise unsuccessfully when no keys associated with user', () => {
user.getPublicKey = jest.fn().mockImplementation(() => {
return publicKeys[0]
})
user.getKeys = jest.fn().mockImplementation(() => {
return new Promise((resolve) => {
resolve([])
})
})
const callback = (resolvedValue) => {
expect(resolvedValue).toBeFalsy()
}

user.authenticate(challenges[0], callback)
})

it('should resolve promise unsuccessfully when publicKey is not in list of keys', () => {
user.getPublicKey = jest.fn().mockImplementation(() => {
return publicKeys[0]
})
user.getKeys = jest.fn().mockImplementation(() => {
return new Promise((resolve) => {
resolve([publicKeys[1]])
})
})
const callback = (resolvedValue) => {
expect(resolvedValue).toBeFalsy()
}

user.authenticate(challenges[0], callback)
})

it('should resolve promise successfully when publicKey is in list of keys', () => {
user.getPublicKey = jest.fn().mockImplementation(() => {
return publicKeys[0]
})
user.getKeys = jest.fn().mockImplementation(() => {
return new Promise((resolve) => {
resolve([publicKeys[0]])
})
})
const callback = (resolvedValue) => {
expect(resolvedValue).toBeTruthy()
}

user.authenticate(challenges[0], callback)
})
})
})
73 changes: 48 additions & 25 deletions src/ScatterUser.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Api, JsonRpc } from 'eosjs'
import * as ecc from 'eosjs-ecc'
import { Api, JsonRpc, Numeric } from 'eosjs'
import { ec as EC } from 'elliptic'
import { Signature, PublicKey } from 'eosjs/dist/eosjs-jssig'
import { Chain, SignTransactionResponse, UALErrorType, User } from 'universal-authenticator-library'
import { UALScatterError } from './UALScatterError'

const { KeyType } = Numeric
const ec = new EC('secp256k1')

export class ScatterUser extends User {
private api: Api
private rpc: JsonRpc
Expand All @@ -11,8 +15,8 @@ export class ScatterUser extends User {
private accountName: string = ''

constructor(
private chain: Chain,
private scatter: any,
private chain: Chain,
private scatter: any,
) {
super()
const rpcEndpoint = this.chain.rpcEndpoints[0]
Expand All @@ -26,25 +30,25 @@ export class ScatterUser extends User {
port: rpcEndpoint.port,
}
const rpc = this.rpc
this.api = this.scatter.eos(network, Api, { rpc, beta3: true })
this.api = this.scatter.eos(network, Api, { rpc })
}

public async signTransaction(
transaction: any,
{ broadcast = true, blocksBehind = 3, expireSeconds = 30 }
transaction: any,
{ broadcast = true, blocksBehind = 3, expireSeconds = 30 }
): Promise<SignTransactionResponse> {
try {
const completedTransaction = await this.api.transact(
transaction,
{ broadcast, blocksBehind, expireSeconds }
transaction,
{ broadcast, blocksBehind, expireSeconds }
)

return this.returnEosjsTransaction(broadcast, completedTransaction)
} catch (e) {
throw new UALScatterError(
'Unable to sign the given transaction',
UALErrorType.Signing,
e)
'Unable to sign the given transaction',
UALErrorType.Signing,
e)
}
}

Expand All @@ -54,16 +58,7 @@ export class ScatterUser extends User {
reject(new Error('verifyKeyOwnership failed'))
}, 1000)

this.scatter.authenticate(challenge).then(async (signature) => {
const pubKey = ecc.recover(signature, challenge)
const myKeys = await this.getKeys()
for (const key of myKeys) {
if (key === pubKey) {
resolve(true)
}
}
resolve(false)
})
this.authenticate(challenge, resolve)
})
}

Expand Down Expand Up @@ -107,9 +102,37 @@ export class ScatterUser extends User {
this.accountName = identity.accounts[0].name
} catch (e) {
throw new UALScatterError(
'Unable load user\'s identity',
UALErrorType.DataRequest,
e)
'Unable load user\'s identity',
UALErrorType.DataRequest,
e)
}
}

private authenticate(challenge: string, resolve): void {
this.scatter.authenticate(challenge).then(async (signature) => {
const publicKey = this.getPublicKey(challenge, signature)
const myKeys = await this.getKeys()
let resolvedValue = false
for (const key of myKeys) {
if (key === publicKey) {
resolvedValue = true
}
}
resolve(resolvedValue)
})
}

private getPublicKey(challenge: string, signature: string): string {
const ellipticSignature = Signature.fromString(signature).toElliptic()
const ellipticHashedStringAsBuffer = Buffer.from(ec.hash().update(challenge).digest(), 'hex')
const ellipticRecoveredPublicKey =
ec.recoverPubKey(
ellipticHashedStringAsBuffer,
ellipticSignature,
ellipticSignature.recoveryParam,
'hex'
)
const ellipticPublicKey = ec.keyFromPublic(ellipticRecoveredPublicKey)
return PublicKey.fromElliptic(ellipticPublicKey, KeyType.k1).toString()
}
}
Loading

0 comments on commit 6a110e2

Please sign in to comment.