-
-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure all signatures returned by beforeSign hooks are valid #25
base: v0.3.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import { | |
NameType, | ||
PermissionLevel, | ||
PermissionLevelType, | ||
Serializer, | ||
Signature, | ||
SignedTransaction, | ||
} from '@greymass/eosio' | ||
|
@@ -77,6 +78,7 @@ export interface SessionOptions { | |
permissionLevel?: PermissionLevelType | string | ||
transactPlugins?: AbstractTransactPlugin[] | ||
transactPluginsOptions?: TransactPluginsOptions | ||
validatePluginSignatures?: boolean | ||
walletPlugin: WalletPlugin | ||
} | ||
|
||
|
@@ -90,6 +92,7 @@ export class Session { | |
readonly permissionLevel: PermissionLevel | ||
readonly transactPlugins: TransactPlugin[] | ||
readonly transactPluginsOptions: TransactPluginsOptions = {} | ||
readonly validatePluginSignatures: boolean = true | ||
readonly wallet: WalletPlugin | ||
|
||
constructor(options: SessionOptions) { | ||
|
@@ -125,6 +128,9 @@ export class Session { | |
'Either a permissionLevel or actor/permission must be provided when creating a new Session.' | ||
) | ||
} | ||
if (options.validatePluginSignatures !== undefined) { | ||
this.validatePluginSignatures = options.validatePluginSignatures | ||
} | ||
this.wallet = options.walletPlugin | ||
} | ||
|
||
|
@@ -292,6 +298,7 @@ export class Session { | |
// Create response template to this transact call | ||
const result: TransactResult = { | ||
chain: this.chain, | ||
keys: [], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Store an array of all the public keys used to sign the request to provide in the response and for use during signature validation after the |
||
request, | ||
resolved: undefined, | ||
revisions: new TransactRevisions(request), | ||
|
@@ -314,6 +321,12 @@ export class Session { | |
const willBroadcast = | ||
options && typeof options.broadcast !== 'undefined' ? options.broadcast : this.broadcast | ||
|
||
// Whether all signatures generated | ||
const willValidatePluginSignatures = | ||
options && typeof options.validatePluginSignatures !== 'undefined' | ||
? options.validatePluginSignatures | ||
: this.validatePluginSignatures | ||
|
||
// Run the `beforeSign` hooks | ||
for (const hook of context.hooks.beforeSign) { | ||
// Get the response of the hook by passing a clonied request. | ||
|
@@ -326,12 +339,29 @@ export class Session { | |
if (allowModify) { | ||
request = await this.updateRequest(request, response.request, abiCache) | ||
} | ||
// If signatures were returned, append them | ||
if (response.signatures) { | ||
|
||
// If signatures were returned, record them in the response. | ||
if (response.signatures?.length) { | ||
// Merge new signatures alongside existing signatures into the TransactResult. | ||
result.signatures = [...result.signatures, ...response.signatures] | ||
|
||
// Recover the keys used to generate the signatures at the time of the request. | ||
const recoveredKeys = response.signatures.map((signature) => { | ||
const requestTransaction = request.getRawTransaction() | ||
const requestDigest = requestTransaction.signingDigest(this.chain.id) | ||
return signature.recoverDigest(requestDigest) | ||
}) | ||
|
||
// Merge newly discovered keys into the TransactResult. | ||
result.keys = [...result.keys, ...recoveredKeys] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When signatures are returned by |
||
} | ||
} | ||
|
||
// Validate all the signatures returned by the plugins against the current request | ||
if (willValidatePluginSignatures) { | ||
this.validateBeforeSignSignatures(context, request, result) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once the transaction is finalized (after all This will throw an error if any of the signatures provided by the plugins are invalid (since the transaction would fail upon broadcast anyways). |
||
} | ||
|
||
// Resolve the SigningRequest and assign it to the TransactResult | ||
result.request = request | ||
result.resolved = await context.resolve(request, expireSeconds) | ||
|
@@ -362,5 +392,26 @@ export class Session { | |
|
||
return result | ||
} | ||
validateBeforeSignSignatures( | ||
context: TransactContext, | ||
request: SigningRequest, | ||
result: TransactResult | ||
): void { | ||
const requestTransaction = request.getRawTransaction() | ||
const requestDigest = requestTransaction.signingDigest(this.chain.id) | ||
const publicKeys = Serializer.objectify(result.keys) | ||
result.signatures.forEach((signature) => { | ||
const recoveredKey = signature.recoverDigest(requestDigest) | ||
const verified = signature.verifyDigest(requestDigest, recoveredKey) | ||
if (!verified || !publicKeys.includes(String(recoveredKey))) { | ||
throw new Error( | ||
`A signature (${signature}) provided by a beforeSign hook using ` + | ||
`a key (${recoveredKey}) has been invalidated, likely due to the transaction ` + | ||
`being modified by another hook after the signature was created. To disable ` + | ||
`this error, set validatePluginSignatures equal to false.` | ||
) | ||
} | ||
}) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method takes the current request and all public keys recovered from the signatures provided by the |
||
} | ||
export {AbstractTransactPlugin} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an option to turn on and off this functionality.