-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor core/accounts Remove private key from the Account interface.…
… Separate out the signing message functionality. Adds MultiKeyAccount (#379) * add accounts * separate the signing message * add multisig * clean up * update transaction submission * fix account imports * update singlekey sign * delete old accounts * update changelog * update * lint * fix imports * fix the domain separator salt * remove test.only * fix tests * fmt * Update CHANGELOG.md * address comments * address more comments * fix test * use test constant * remove old code * fmt * revert simulation changes * fix import * revert multikeysig rename * Update account interface * add docstrings and implement verifySignature * add test for verification * fmt * add test for misordering signers
- Loading branch information
1 parent
5b66470
commit b41ea4b
Showing
34 changed files
with
642 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
// Copyright © Aptos Foundation | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { Account } from "./Account"; | ||
import { MultiKey, MultiKeySignature, PublicKey } from "../core/crypto"; | ||
import { AccountAddress } from "../core/accountAddress"; | ||
import { HexInput, SigningScheme } from "../types"; | ||
import { AccountAuthenticatorMultiKey } from "../transactions/authenticator/account"; | ||
import { AnyRawTransaction } from "../transactions/types"; | ||
|
||
export interface VerifyMultiKeySignatureArgs { | ||
message: HexInput; | ||
signature: MultiKeySignature; | ||
} | ||
|
||
/** | ||
* Signer implementation for the MultiKey authentication scheme. | ||
* | ||
* This accounts to use a M of N signing scheme. M and N are specified in the {@link MultiKey} | ||
* It signs messages via the array of M number of Accounts that individually correspond to a public key in the {@link MultiKey}. | ||
* | ||
* Note: Generating a signer instance does not create the account on-chain. | ||
*/ | ||
export class MultiKeyAccount implements Account { | ||
/** | ||
* Public key associated with the account | ||
*/ | ||
readonly publicKey: MultiKey; | ||
|
||
/** | ||
* Account address associated with the account | ||
*/ | ||
readonly accountAddress: AccountAddress; | ||
|
||
/** | ||
* Signing scheme used to sign transactions | ||
*/ | ||
readonly signingScheme: SigningScheme; | ||
|
||
/** | ||
* The signers used to sign messages. These signers should correspond to public keys in the | ||
* MultiKeyAccount's public key. The number of signers should be equal or greater | ||
* than this.publicKey.signaturesRequired | ||
*/ | ||
readonly signers: Account[]; | ||
|
||
/** | ||
* An array of indicies where for signer[i], signerIndicies[i] is the index of the corresponding public key in | ||
* publicKey.publicKeys. Used to derive the right public key to use for verification. | ||
*/ | ||
readonly signerIndicies: number[]; | ||
|
||
readonly signaturesBitmap: Uint8Array; | ||
|
||
/** | ||
* constructor for MultiKeyAccount | ||
* | ||
* @param args.multiKey the multikey of the account which consists of N public keys and a number M which is | ||
* the number of required signatures. | ||
* @param args.signers an array of M signers that will be used to sign the transaction | ||
* @returns MultiKeyAccount | ||
*/ | ||
constructor(args: { multiKey: MultiKey; signers: Account[] }) { | ||
const { multiKey, signers } = args; | ||
|
||
this.publicKey = multiKey; | ||
this.signingScheme = SigningScheme.MultiKey; | ||
|
||
this.accountAddress = this.publicKey.authKey().derivedAddress(); | ||
|
||
// Get the index of each respective signer in the bitmap | ||
const bitPositions: number[] = []; | ||
for (const signer of signers) { | ||
bitPositions.push(this.publicKey.getIndex(signer.publicKey)); | ||
} | ||
// Zip signers and bit positions and sort signers by bit positions in order | ||
// to ensure the signature is signed in ascending order according to the bitmap. | ||
// Authentication on chain will fail otherwise. | ||
const signersAndBitPosition: [Account, number][] = signers.map((signer, index) => [signer, bitPositions[index]]); | ||
signersAndBitPosition.sort((a, b) => a[1] - b[1]); | ||
this.signers = signersAndBitPosition.map((value) => value[0]); | ||
this.signerIndicies = signersAndBitPosition.map((value) => value[1]); | ||
this.signaturesBitmap = this.publicKey.createBitmap({ bits: bitPositions }); | ||
} | ||
|
||
/** | ||
* Static constructor for MultiKeyAccount | ||
* | ||
* @param args.publicKeys the N public keys of the MultiKeyAccount | ||
* @param args.signaturesRequired the number of signatures required | ||
* @param args.signers an array of M signers that will be used to sign the transaction | ||
* @returns MultiKeyAccount | ||
*/ | ||
static fromPublicKeysAndSigners(args: { | ||
publicKeys: PublicKey[]; | ||
signaturesRequired: number; | ||
signers: Account[]; | ||
}): MultiKeyAccount { | ||
const { publicKeys, signaturesRequired, signers } = args; | ||
const multiKey = new MultiKey({ publicKeys, signaturesRequired }); | ||
return new MultiKeyAccount({ multiKey, signers }); | ||
} | ||
|
||
static isMultiKeySigner(account: Account): account is MultiKeyAccount { | ||
return account instanceof MultiKeyAccount; | ||
} | ||
|
||
/** | ||
* Sign a message using the account's signers. | ||
* @param message the signing message, as binary input | ||
* @return the AccountAuthenticator containing the signature, together with the account's public key | ||
*/ | ||
signWithAuthenticator(message: HexInput): AccountAuthenticatorMultiKey { | ||
return new AccountAuthenticatorMultiKey(this.publicKey, this.sign(message)); | ||
} | ||
|
||
/** | ||
* Sign a transaction using the account's signers. | ||
* @param transaction the raw transaction | ||
* @return the AccountAuthenticator containing the signature of the transaction, together with the account's public key | ||
*/ | ||
signTransactionWithAuthenticator(transaction: AnyRawTransaction): AccountAuthenticatorMultiKey { | ||
return new AccountAuthenticatorMultiKey(this.publicKey, this.signTransaction(transaction)); | ||
} | ||
|
||
/** | ||
* Sign the given message using the MultiKeyAccount's signers | ||
* @param message in HexInput format | ||
* @returns MultiKeySignature | ||
*/ | ||
sign(data: HexInput): MultiKeySignature { | ||
const signatures = []; | ||
for (const signer of this.signers) { | ||
signatures.push(signer.sign(data)); | ||
} | ||
return new MultiKeySignature({ signatures, bitmap: this.signaturesBitmap }); | ||
} | ||
|
||
/** | ||
* Sign the given transaction using the MultiKeyAccount's signers | ||
* @param transaction the transaction to be signed | ||
* @returns MultiKeySignature | ||
*/ | ||
signTransaction(transaction: AnyRawTransaction): MultiKeySignature { | ||
const signatures = []; | ||
for (const signer of this.signers) { | ||
signatures.push(signer.signTransaction(transaction)); | ||
} | ||
return new MultiKeySignature({ signatures, bitmap: this.signaturesBitmap }); | ||
} | ||
|
||
/** | ||
* Verify the given message and signature with the public key. | ||
* | ||
* @param args.message raw message data in HexInput format | ||
* @param args.signatures signed message MultiKeySignature | ||
* @returns boolean | ||
*/ | ||
verifySignature(args: VerifyMultiKeySignatureArgs): boolean { | ||
const { message, signature } = args; | ||
const isSignerIndiciesSorted = this.signerIndicies.every( | ||
(value, i) => i === 0 || value >= this.signerIndicies[i - 1], | ||
); | ||
if (!isSignerIndiciesSorted) { | ||
return false; | ||
} | ||
for (let i = 0; i < signature.signatures.length; i += 1) { | ||
const singleSignature = signature.signatures[i]; | ||
const publicKey = this.publicKey.publicKeys[this.signerIndicies[i]]; | ||
if (!publicKey.verifySignature({ message, signature: singleSignature })) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
} |
Oops, something went wrong.