Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
parity
Mirrored projects
parity-signer-companion
Commits
94def670
Unverified
Commit
94def670
authored
Oct 15, 2021
by
Andrei Eres
Committed by
GitHub
Oct 15, 2021
Browse files
Fix working along with polkadot-js-extension (#75)
parent
d1349123
Pipeline
#162267
passed with stages
in 1 minute and 7 seconds
Changes
35
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
src/background.ts
View file @
94def670
import
handlers
from
'
@polkadot/extension-base/background/handlers
'
import
{
PORT_CONTENT
,
PORT_EXTENSION
}
from
'
@polkadot/extension-base/defaults
'
import
{
AccountsStore
}
from
'
@polkadot/extension-base/stores
'
import
chrome
from
'
@polkadot/extension-inject/chrome
'
import
keyring
from
'
@polkadot/ui-keyring
'
import
{
assert
}
from
'
@polkadot/util
'
import
{
cryptoWaitReady
}
from
'
@polkadot/util-crypto
'
import
chrome
from
'
@polkadot/extension-inject/chrome
'
import
{
handler
}
from
'
./base/handler
'
import
{
AccountsStorage
}
from
'
./storages/AccountsStorage
'
import
{
PORT_CONTENT
,
PORT_EXTENSION
}
from
'
./utils/constants
'
// setup the notification (same a FF default background, white text)
void
chrome
.
browserAction
.
setBadgeBackgroundColor
({
color
:
'
#d90000
'
})
...
...
@@ -16,7 +16,7 @@ chrome.runtime.onConnect.addListener((port) => {
`Unknown connection from
${
port
.
name
}
`
)
port
.
onMessage
.
addListener
((
data
)
=>
handler
s
(
data
,
port
))
port
.
onMessage
.
addListener
((
data
)
=>
handler
(
data
,
port
))
port
.
onDisconnect
.
addListener
(()
=>
console
.
log
(
`Disconnected from
${
port
.
name
}
`
)
)
...
...
@@ -25,7 +25,7 @@ chrome.runtime.onConnect.addListener((port) => {
cryptoWaitReady
()
.
then
(()
=>
{
console
.
log
(
'
crypto initialized
'
)
keyring
.
loadAll
({
store
:
new
AccountsStore
(),
type
:
'
sr25519
'
})
keyring
.
loadAll
({
store
:
new
AccountsStor
ag
e
(),
type
:
'
sr25519
'
})
console
.
log
(
'
initialization completed
'
)
})
.
catch
((
error
)
=>
{
...
...
src/base/Extension.ts
0 → 100644
View file @
94def670
import
{
createSubscription
,
unsubscribe
,
}
from
'
@polkadot/extension-base/background/handlers/subscriptions
'
import
{
AllowedPath
,
AuthorizeRequest
,
MessageTypes
,
MetadataRequest
,
RequestAccountBatchExport
,
RequestAccountChangePassword
,
RequestAccountCreateExternal
,
RequestAccountCreateHardware
,
RequestAccountCreateSuri
,
RequestAccountEdit
,
RequestAccountExport
,
RequestAccountForget
,
RequestAccountShow
,
RequestAccountTie
,
RequestAccountValidate
,
RequestAuthorizeApprove
,
RequestAuthorizeReject
,
RequestBatchRestore
,
RequestDeriveCreate
,
RequestDeriveValidate
,
RequestJsonRestore
,
RequestMetadataApprove
,
RequestMetadataReject
,
RequestSeedCreate
,
RequestSeedValidate
,
RequestSigningApprovePassword
,
RequestSigningApproveSignature
,
RequestSigningCancel
,
RequestSigningIsLocked
,
RequestTypes
,
ResponseAccountExport
,
ResponseAccountsExport
,
ResponseAuthorizeList
,
ResponseDeriveValidate
,
ResponseJsonGetAccountInfo
,
ResponseSeedCreate
,
ResponseSeedValidate
,
ResponseSigningIsLocked
,
ResponseType
,
SigningRequest
,
}
from
'
@polkadot/extension-base/background/types
'
import
{
ALLOWED_PATH
,
PASSWORD_EXPIRY_MS
,
}
from
'
@polkadot/extension-base/defaults
'
import
chrome
from
'
@polkadot/extension-inject/chrome
'
import
{
MetadataDef
}
from
'
@polkadot/extension-inject/types
'
import
{
KeyringPair
,
KeyringPair$Json
,
KeyringPair$Meta
,
}
from
'
@polkadot/keyring/types
'
import
keyring
from
'
@polkadot/ui-keyring
'
import
{
accounts
as
accountsObservable
}
from
'
@polkadot/ui-keyring/observable/accounts
'
import
{
SubjectInfo
}
from
'
@polkadot/ui-keyring/observable/types
'
import
{
assert
,
isHex
}
from
'
@polkadot/util
'
import
{
keyExtractSuri
,
mnemonicGenerate
,
mnemonicValidate
,
}
from
'
@polkadot/util-crypto
'
import
{
REGISTRY
}
from
'
../utils/constants
'
import
{
isJsonPayload
}
from
'
../utils/guards
'
import
{
transformExtensionAccounts
}
from
'
../utils/transformExtensionAccounts
'
import
{
State
}
from
'
./State
'
type
CachedUnlocks
=
Record
<
string
,
number
>
const
SEED_DEFAULT_LENGTH
=
12
const
SEED_LENGTHS
=
[
12
,
15
,
18
,
21
,
24
]
export
class
Extension
{
readonly
#
cachedUnlocks
:
CachedUnlocks
=
{}
readonly
#
state
:
State
constructor
(
state
:
State
)
{
this
.
#
state
=
state
}
public
async
handle
<
TMessageType
extends
MessageTypes
>
(
id
:
string
,
type
:
TMessageType
,
request
:
RequestTypes
[
TMessageType
],
port
:
chrome
.
runtime
.
Port
):
Promise
<
ResponseType
<
TMessageType
>>
{
switch
(
type
)
{
case
'
pri(authorize.approve)
'
:
return
this
.
authorizeApprove
(
request
as
RequestAuthorizeApprove
)
case
'
pri(authorize.list)
'
:
return
this
.
getAuthList
()
case
'
pri(authorize.reject)
'
:
return
this
.
authorizeReject
(
request
as
RequestAuthorizeReject
)
case
'
pri(authorize.toggle)
'
:
return
this
.
toggleAuthorization
(
request
as
string
)
case
'
pri(authorize.requests)
'
:
return
this
.
authorizeSubscribe
(
id
,
port
)
case
'
pri(accounts.create.external)
'
:
return
this
.
accountsCreateExternal
(
request
as
RequestAccountCreateExternal
)
case
'
pri(accounts.create.hardware)
'
:
return
this
.
accountsCreateHardware
(
request
as
RequestAccountCreateHardware
)
case
'
pri(accounts.create.suri)
'
:
return
this
.
accountsCreateSuri
(
request
as
RequestAccountCreateSuri
)
case
'
pri(accounts.changePassword)
'
:
return
this
.
accountsChangePassword
(
request
as
RequestAccountChangePassword
)
case
'
pri(accounts.edit)
'
:
return
this
.
accountsEdit
(
request
as
RequestAccountEdit
)
case
'
pri(accounts.export)
'
:
return
this
.
accountsExport
(
request
as
RequestAccountExport
)
case
'
pri(accounts.batchExport)
'
:
return
this
.
accountsBatchExport
(
request
as
RequestAccountBatchExport
)
case
'
pri(accounts.forget)
'
:
return
this
.
accountsForget
(
request
as
RequestAccountForget
)
case
'
pri(accounts.show)
'
:
return
this
.
accountsShow
(
request
as
RequestAccountShow
)
case
'
pri(accounts.subscribe)
'
:
return
this
.
accountsSubscribe
(
id
,
port
)
case
'
pri(accounts.tie)
'
:
return
this
.
accountsTie
(
request
as
RequestAccountTie
)
case
'
pri(accounts.validate)
'
:
return
this
.
accountsValidate
(
request
as
RequestAccountValidate
)
case
'
pri(metadata.approve)
'
:
return
this
.
metadataApprove
(
request
as
RequestMetadataApprove
)
case
'
pri(metadata.get)
'
:
return
this
.
metadataGet
(
request
as
string
)
case
'
pri(metadata.list)
'
:
return
this
.
metadataList
()
case
'
pri(metadata.reject)
'
:
return
this
.
metadataReject
(
request
as
RequestMetadataReject
)
case
'
pri(metadata.requests)
'
:
return
this
.
metadataSubscribe
(
id
,
port
)
case
'
pri(derivation.create)
'
:
return
this
.
derivationCreate
(
request
as
RequestDeriveCreate
)
case
'
pri(derivation.validate)
'
:
return
this
.
derivationValidate
(
request
as
RequestDeriveValidate
)
case
'
pri(json.restore)
'
:
return
this
.
jsonRestore
(
request
as
RequestJsonRestore
)
case
'
pri(json.batchRestore)
'
:
return
this
.
batchRestore
(
request
as
RequestBatchRestore
)
case
'
pri(json.account.info)
'
:
return
this
.
jsonGetAccountInfo
(
request
as
KeyringPair$Json
)
case
'
pri(seed.create)
'
:
return
this
.
seedCreate
(
request
as
RequestSeedCreate
)
case
'
pri(seed.validate)
'
:
return
this
.
seedValidate
(
request
as
RequestSeedValidate
)
case
'
pri(settings.notification)
'
:
return
this
.
#
state
.
setNotification
(
request
as
string
)
case
'
pri(signing.approve.password)
'
:
return
this
.
signingApprovePassword
(
request
as
RequestSigningApprovePassword
)
case
'
pri(signing.approve.signature)
'
:
return
this
.
signingApproveSignature
(
request
as
RequestSigningApproveSignature
)
case
'
pri(signing.cancel)
'
:
return
this
.
signingCancel
(
request
as
RequestSigningCancel
)
case
'
pri(signing.isLocked)
'
:
return
this
.
signingIsLocked
(
request
as
RequestSigningIsLocked
)
case
'
pri(signing.requests)
'
:
return
this
.
signingSubscribe
(
id
,
port
)
case
'
pri(window.open)
'
:
return
this
.
windowOpen
(
request
as
AllowedPath
)
default
:
throw
new
Error
(
`Unable to handle message of type
${
type
}
`
)
}
}
private
accountsCreateExternal
({
address
,
genesisHash
,
name
,
}:
RequestAccountCreateExternal
):
boolean
{
keyring
.
addExternal
(
address
,
{
genesisHash
,
name
})
return
true
}
private
accountsCreateHardware
({
accountIndex
,
address
,
addressOffset
,
genesisHash
,
hardwareType
,
name
,
}:
RequestAccountCreateHardware
):
boolean
{
keyring
.
addHardware
(
address
,
hardwareType
,
{
accountIndex
,
addressOffset
,
genesisHash
,
name
,
})
return
true
}
private
accountsCreateSuri
({
genesisHash
,
name
,
password
,
suri
,
type
,
}:
RequestAccountCreateSuri
):
boolean
{
keyring
.
addUri
(
suri
,
password
,
{
genesisHash
,
name
},
type
)
return
true
}
private
accountsChangePassword
({
address
,
newPass
,
oldPass
,
}:
RequestAccountChangePassword
):
boolean
{
const
pair
=
keyring
.
getPair
(
address
)
assert
(
pair
,
'
Unable to find pair
'
)
try
{
if
(
!
pair
.
isLocked
)
pair
.
lock
()
pair
.
decodePkcs8
(
oldPass
)
}
catch
(
error
)
{
throw
new
Error
(
'
oldPass is invalid
'
)
}
keyring
.
encryptAccount
(
pair
,
newPass
)
return
true
}
private
accountsEdit
({
address
,
name
}:
RequestAccountEdit
):
boolean
{
const
pair
=
keyring
.
getPair
(
address
)
assert
(
pair
,
'
Unable to find pair
'
)
keyring
.
saveAccountMeta
(
pair
,
{
...
pair
.
meta
,
name
})
return
true
}
private
accountsExport
({
address
,
password
,
}:
RequestAccountExport
):
ResponseAccountExport
{
return
{
exportedJson
:
keyring
.
backupAccount
(
keyring
.
getPair
(
address
),
password
),
}
}
private
async
accountsBatchExport
({
addresses
,
password
,
}:
RequestAccountBatchExport
):
Promise
<
ResponseAccountsExport
>
{
return
{
exportedJson
:
await
keyring
.
backupAccounts
(
addresses
,
password
),
}
}
private
accountsForget
({
address
}:
RequestAccountForget
):
boolean
{
keyring
.
forgetAccount
(
address
)
return
true
}
private
refreshAccountPasswordCache
(
pair
:
KeyringPair
):
number
{
const
{
address
}
=
pair
const
savedExpiry
=
this
.
#
cachedUnlocks
[
address
]
||
0
const
remainingTime
=
savedExpiry
-
Date
.
now
()
if
(
remainingTime
<
0
)
{
this
.
#
cachedUnlocks
[
address
]
=
0
pair
.
lock
()
return
0
}
return
remainingTime
}
private
accountsShow
({
address
,
isShowing
}:
RequestAccountShow
):
boolean
{
const
pair
=
keyring
.
getPair
(
address
)
assert
(
pair
,
'
Unable to find pair
'
)
keyring
.
saveAccountMeta
(
pair
,
{
...
pair
.
meta
,
isHidden
:
!
isShowing
})
return
true
}
private
accountsTie
({
address
,
genesisHash
}:
RequestAccountTie
):
boolean
{
const
pair
=
keyring
.
getPair
(
address
)
assert
(
pair
,
'
Unable to find pair
'
)
keyring
.
saveAccountMeta
(
pair
,
{
...
pair
.
meta
,
genesisHash
})
return
true
}
private
accountsValidate
({
address
,
password
,
}:
RequestAccountValidate
):
boolean
{
try
{
keyring
.
backupAccount
(
keyring
.
getPair
(
address
),
password
)
return
true
}
catch
(
e
)
{
return
false
}
}
// FIXME This looks very much like what we have in Tabs
private
accountsSubscribe
(
id
:
string
,
port
:
chrome
.
runtime
.
Port
):
boolean
{
const
cb
=
createSubscription
<
'
pri(accounts.subscribe)
'
>
(
id
,
port
)
const
subscription
=
accountsObservable
.
subject
.
subscribe
(
(
accounts
:
SubjectInfo
)
=>
cb
(
transformExtensionAccounts
(
accounts
))
)
port
.
onDisconnect
.
addListener
(()
=>
{
unsubscribe
(
id
)
subscription
.
unsubscribe
()
})
return
true
}
private
authorizeApprove
({
id
}:
RequestAuthorizeApprove
):
boolean
{
const
queued
=
this
.
#
state
.
getAuthRequest
(
id
)
assert
(
queued
,
'
Unable to find request
'
)
const
{
resolve
}
=
queued
resolve
(
true
)
return
true
}
private
getAuthList
():
ResponseAuthorizeList
{
return
{
list
:
this
.
#
state
.
authUrls
}
}
private
authorizeReject
({
id
}:
RequestAuthorizeReject
):
boolean
{
const
queued
=
this
.
#
state
.
getAuthRequest
(
id
)
assert
(
queued
,
'
Unable to find request
'
)
const
{
reject
}
=
queued
reject
(
new
Error
(
'
Rejected
'
))
return
true
}
// FIXME This looks very much like what we have in accounts
private
authorizeSubscribe
(
id
:
string
,
port
:
chrome
.
runtime
.
Port
):
boolean
{
const
cb
=
createSubscription
<
'
pri(authorize.requests)
'
>
(
id
,
port
)
const
subscription
=
this
.
#
state
.
authSubject
.
subscribe
(
(
requests
:
AuthorizeRequest
[])
=>
cb
(
requests
)
)
port
.
onDisconnect
.
addListener
(()
=>
{
unsubscribe
(
id
)
subscription
.
unsubscribe
()
})
return
true
}
private
metadataApprove
({
id
}:
RequestMetadataApprove
):
boolean
{
const
queued
=
this
.
#
state
.
getMetaRequest
(
id
)
assert
(
queued
,
'
Unable to find request
'
)
const
{
request
,
resolve
}
=
queued
this
.
#
state
.
saveMetadata
(
request
)
resolve
(
true
)
return
true
}
private
metadataGet
(
genesisHash
:
string
|
null
):
MetadataDef
|
null
{
return
(
this
.
#
state
.
knownMetadata
.
find
(
(
result
)
=>
result
.
genesisHash
===
genesisHash
)
||
null
)
}
private
metadataList
():
MetadataDef
[]
{
return
this
.
#
state
.
knownMetadata
}
private
metadataReject
({
id
}:
RequestMetadataReject
):
boolean
{
const
queued
=
this
.
#
state
.
getMetaRequest
(
id
)
assert
(
queued
,
'
Unable to find request
'
)
const
{
reject
}
=
queued
reject
(
new
Error
(
'
Rejected
'
))
return
true
}
private
metadataSubscribe
(
id
:
string
,
port
:
chrome
.
runtime
.
Port
):
boolean
{
const
cb
=
createSubscription
<
'
pri(metadata.requests)
'
>
(
id
,
port
)
const
subscription
=
this
.
#
state
.
metaSubject
.
subscribe
(
(
requests
:
MetadataRequest
[])
=>
cb
(
requests
)
)
port
.
onDisconnect
.
addListener
(()
=>
{
unsubscribe
(
id
)
subscription
.
unsubscribe
()
})
return
true
}
private
jsonRestore
({
file
,
password
}:
RequestJsonRestore
)
{
try
{
keyring
.
restoreAccount
(
file
,
password
)
}
catch
(
error
)
{
throw
new
Error
((
error
as
Error
).
message
)
}
}
private
batchRestore
({
file
,
password
}:
RequestBatchRestore
)
{
try
{
keyring
.
restoreAccounts
(
file
,
password
)
}
catch
(
error
)
{
throw
new
Error
((
error
as
Error
).
message
)
}
}
private
jsonGetAccountInfo
(
json
:
KeyringPair$Json
):
ResponseJsonGetAccountInfo
{
try
{
const
{
address
,
meta
:
{
genesisHash
,
name
},
type
,
}
=
keyring
.
createFromJson
(
json
)
return
{
address
,
genesisHash
,
name
,
type
,
}
as
ResponseJsonGetAccountInfo
}
catch
(
e
)
{
console
.
error
(
e
)
throw
new
Error
((
e
as
Error
).
message
)
}
}
private
seedCreate
({
length
=
SEED_DEFAULT_LENGTH
,
type
,
}:
RequestSeedCreate
):
ResponseSeedCreate
{
const
seed
=
mnemonicGenerate
(
length
)
return
{
address
:
keyring
.
createFromUri
(
seed
,
{},
type
).
address
,
seed
,
}
}
private
seedValidate
({
suri
,
type
,
}:
RequestSeedValidate
):
ResponseSeedValidate
{
const
{
phrase
}
=
keyExtractSuri
(
suri
)
if
(
isHex
(
phrase
))
{
assert
(
isHex
(
phrase
,
256
),
'
Hex seed needs to be 256-bits
'
)
}
else
{
// sadly isHex detects as string, so we need a cast here
assert
(
SEED_LENGTHS
.
includes
(
phrase
.
split
(
'
'
).
length
),
`Mnemonic needs to contain
${
SEED_LENGTHS
.
join
(
'
,
'
)}
words`
)
assert
(
mnemonicValidate
(
phrase
),
'
Not a valid mnemonic seed
'
)
}
return
{
address
:
keyring
.
createFromUri
(
suri
,
{},
type
).
address
,
suri
,
}
}
private
signingApprovePassword
({
id
,
password
,
savePass
,
}:
RequestSigningApprovePassword
):
boolean
{
const
queued
=
this
.
#
state
.
getSignRequest
(
id
)
assert
(
queued
,
'
Unable to find request
'
)
const
{
reject
,
request
,
resolve
}
=
queued
const
pair
=
keyring
.
getPair
(
queued
.
account
.
address
)
// unlike queued.account.address the following
// address is encoded with the default prefix
// which what is used for password caching mapping
const
{
address
}
=
pair
if
(
!
pair
)
{
reject
(
new
Error
(
'
Unable to find pair
'
))
return
false
}
this
.
refreshAccountPasswordCache
(
pair
)
// if the keyring pair is locked, the password is needed
if
(
pair
.
isLocked
&&
!
password
)
reject
(
new
Error
(
'
Password needed to unlock the account
'
))
if
(
pair
.
isLocked
)
pair
.
decodePkcs8
(
password
)
const
{
payload
}
=
request
if
(
isJsonPayload
(
payload
))
{
// Get the metadata for the genesisHash
const
currentMetadata
=
this
.
#
state
.
knownMetadata
.
find
(
(
meta
:
MetadataDef
)
=>
meta
.
genesisHash
===
payload
.
genesisHash
)
// set the registry before calling the sign function
REGISTRY
.
setSignedExtensions
(
payload
.
signedExtensions
,
currentMetadata
?.
userExtensions
)
if
(
currentMetadata
)
{
REGISTRY
.
register
(
currentMetadata
?.
types
)
}
}
const
result
=
request
.
sign
(
REGISTRY
,
pair
)
if
(
savePass
)
{
this
.
#
cachedUnlocks
[
address
]
=
Date
.
now
()
+
PASSWORD_EXPIRY_MS
}
else
{
pair
.
lock
()
}
resolve
({
id
,
...
result
})
return
true
}
private
signingApproveSignature
({
id
,
signature
,