-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TSSDKv2][3/n] Account and AuthenticationKey Classes (#10210)
* Add helper.ts and fixes based on comments * add asymmetric_crypto file which includes all crypto base classes as abstract. And have all concrete crypto classes to extend from based abstract classes * Update crypto classes name to include Ed25519 prefix * fixes based on comments * Add account and auth key classes * update Account class to accept abstract PublicKey and PrivateKey * update methods' comment
- Loading branch information
Showing
9 changed files
with
503 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// Copyright © Aptos Foundation | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import nacl from "tweetnacl"; | ||
import * as bip39 from "@scure/bip39"; | ||
import { AccountAddress } from "./account_address"; | ||
import { Hex } from "./hex"; | ||
import { bytesToHex } from "@noble/hashes/utils"; | ||
import { HexInput } from "../types"; | ||
import { PrivateKey, PublicKey, Signature } from "../crypto/asymmetric_crypto"; | ||
import { derivePath } from "../utils/hd-key"; | ||
import { AuthenticationKey } from "../crypto/authentication_key"; | ||
import { Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature } from "../crypto/ed25519"; | ||
|
||
/** | ||
* Class for creating and managing account on Aptos network | ||
* | ||
* Use this class to create accounts, sign transactions, and more. | ||
* Note: Creating an account instance does not create the account onchain. | ||
*/ | ||
export class Account { | ||
/** | ||
* A private key and public key, associated with the given account | ||
*/ | ||
readonly publicKey: PublicKey; | ||
readonly privateKey: PrivateKey; | ||
|
||
/** | ||
* Account address associated with the account | ||
*/ | ||
readonly accountAddress: AccountAddress; | ||
|
||
/** | ||
* constructor for Account | ||
* | ||
* TODO: This constructor uses the nacl library directly, which only works with ed25519 keys. | ||
* Need to update this to use the new crypto library if new schemes are added. | ||
* | ||
* @param args.privateKey PrivateKey - private key of the account | ||
* @param args.address AccountAddress - address of the account | ||
* | ||
* This method is private because it should only be called by the factory static methods. | ||
* @returns Account | ||
*/ | ||
private constructor(args: { privateKey: PrivateKey; address: AccountAddress }) { | ||
const { privateKey, address } = args; | ||
|
||
// Derive the public key from the private key | ||
const keyPair = nacl.sign.keyPair.fromSeed(privateKey.toUint8Array().slice(0, 32)); | ||
this.publicKey = new Ed25519PublicKey({ hexInput: keyPair.publicKey }); | ||
|
||
this.privateKey = privateKey; | ||
this.accountAddress = address; | ||
} | ||
|
||
/** | ||
* Derives an account with random private key and address | ||
* | ||
* @returns Account | ||
*/ | ||
static generate(): Account { | ||
const keyPair = nacl.sign.keyPair(); | ||
const privateKey = new Ed25519PrivateKey({ value: keyPair.secretKey.slice(0, 32) }); | ||
const address = new AccountAddress({ data: Account.authKey({ publicKey: keyPair.publicKey }).toUint8Array() }); | ||
return new Account({ privateKey, address }); | ||
} | ||
|
||
/** | ||
* Derives an account with provided private key | ||
* | ||
* @param args.privateKey Hex - private key of the account | ||
* @returns Account | ||
*/ | ||
static fromPrivateKey(args: { privateKey: HexInput }): Account { | ||
const privatekeyHex = Hex.fromHexInput({ hexInput: args.privateKey }); | ||
const keyPair = nacl.sign.keyPair.fromSeed(privatekeyHex.toUint8Array().slice(0, 32)); | ||
const privateKey = new Ed25519PrivateKey({ value: keyPair.secretKey.slice(0, 32) }); | ||
const address = new AccountAddress({ data: Account.authKey({ publicKey: keyPair.publicKey }).toUint8Array() }); | ||
return new Account({ privateKey, address }); | ||
} | ||
|
||
/** | ||
* Derives an account with provided private key and address | ||
* This is intended to be used for account that has it's key rotated | ||
* | ||
* @param args.privateKey Hex - private key of the account | ||
* @param args.address AccountAddress - address of the account | ||
* @returns Account | ||
*/ | ||
static fromPrivateKeyAndAddress(args: { privateKey: HexInput; address: AccountAddress }): Account { | ||
const privatekeyHex = Hex.fromHexInput({ hexInput: args.privateKey }); | ||
const signingKey = nacl.sign.keyPair.fromSeed(privatekeyHex.toUint8Array().slice(0, 32)); | ||
const privateKey = new Ed25519PrivateKey({ value: signingKey.secretKey.slice(0, 32) }); | ||
return new Account({ privateKey, address: args.address }); | ||
} | ||
|
||
/** | ||
* Derives an account with bip44 path and mnemonics, | ||
* | ||
* @param path. (e.g. m/44'/637'/0'/0'/0') | ||
* Detailed description: {@link https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki} | ||
* @param mnemonics. | ||
* @returns AptosAccount | ||
*/ | ||
static fromDerivationPath(args: { path: string; mnemonic: string }): Account { | ||
const { path, mnemonic } = args; | ||
if (!Account.isValidPath({ path })) { | ||
throw new Error("Invalid derivation path"); | ||
} | ||
|
||
const normalizeMnemonics = mnemonic | ||
.trim() | ||
.split(/\s+/) | ||
.map((part) => part.toLowerCase()) | ||
.join(" "); | ||
|
||
const { key } = derivePath(path, bytesToHex(bip39.mnemonicToSeedSync(normalizeMnemonics))); | ||
|
||
const signingKey = nacl.sign.keyPair.fromSeed(key.slice(0, 32)); | ||
const privateKey = new Ed25519PrivateKey({ value: signingKey.secretKey.slice(0, 32) }); | ||
const address = new AccountAddress({ data: Account.authKey({ publicKey: signingKey.publicKey }).toUint8Array() }); | ||
|
||
return new Account({ privateKey, address }); | ||
} | ||
|
||
/** | ||
* Check's if the derive path is valid | ||
*/ | ||
static isValidPath(args: { path: string }): boolean { | ||
return /^m\/44'\/637'\/[0-9]+'\/[0-9]+'\/[0-9]+'+$/.test(args.path); | ||
} | ||
|
||
/** | ||
* This key enables account owners to rotate their private key(s) | ||
* associated with the account without changing the address that hosts their account. | ||
* See here for more info: {@link https://aptos.dev/concepts/accounts#single-signer-authentication} | ||
* @returns Authentication key for the associated account | ||
*/ | ||
static authKey(args: { publicKey: HexInput }): Hex { | ||
const publicKey = new Ed25519PublicKey({ hexInput: args.publicKey }); | ||
const authKey = AuthenticationKey.fromPublicKey({ publicKey }); | ||
return authKey.data; | ||
} | ||
|
||
/** | ||
* Sign the given message with the private key. | ||
* | ||
* @param args.data in HexInput format | ||
* @returns Signature | ||
*/ | ||
sign(args: { data: HexInput }): Signature { | ||
const signature = this.privateKey.sign({ message: args.data }); | ||
return signature; | ||
} | ||
|
||
/** | ||
* Verify the given message and signature with the public key. | ||
* | ||
* @param args.message raw message data in HexInput format | ||
* @param args.signature signed message Signature | ||
* @returns | ||
*/ | ||
verifySignature(args: { message: HexInput; signature: Signature }): boolean { | ||
const { message, signature } = args; | ||
const rawMessage = Hex.fromHexInput({ hexInput: message }).toUint8Array(); | ||
return this.publicKey.verifySignature({ data: rawMessage, signature }); | ||
} | ||
} |
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
104 changes: 104 additions & 0 deletions
104
ecosystem/typescript/sdk_v2/src/crypto/authentication_key.ts
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,104 @@ | ||
// Copyright © Aptos Foundation | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; | ||
import { AccountAddress, Hex } from "../core"; | ||
import { HexInput } from "../types"; | ||
import { MultiEd25519PublicKey } from "./multi_ed25519"; | ||
import { PublicKey } from "./asymmetric_crypto"; | ||
|
||
/** | ||
* Each account stores an authentication key. Authentication key enables account owners to rotate | ||
* their private key(s) associated with the account without changing the address that hosts their account. | ||
* @see {@link https://aptos.dev/concepts/accounts | Account Basics} | ||
* | ||
* Note: AuthenticationKey only supports Ed25519 and MultiEd25519 public keys for now. | ||
* | ||
* Account addresses can be derived from AuthenticationKey | ||
*/ | ||
export class AuthenticationKey { | ||
// Length of AuthenticationKey in bytes(Uint8Array) | ||
static readonly LENGTH: number = 32; | ||
|
||
// Scheme identifier for MultiEd25519 signatures used to derive authentication keys for MultiEd25519 public keys | ||
static readonly MULTI_ED25519_SCHEME: number = 1; | ||
|
||
// Scheme identifier for Ed25519 signatures used to derive authentication key for MultiEd25519 public key | ||
static readonly ED25519_SCHEME: number = 0; | ||
|
||
// Scheme identifier used when hashing an account's address together with a seed to derive the address (not the | ||
// authentication key) of a resource account. | ||
static readonly DERIVE_RESOURCE_ACCOUNT_SCHEME: number = 255; | ||
|
||
// Actual data of AuthenticationKey, in Hex format | ||
public readonly data: Hex; | ||
|
||
constructor(args: { data: HexInput }) { | ||
const { data } = args; | ||
const hex = Hex.fromHexInput({ hexInput: data }); | ||
if (hex.toUint8Array().length !== AuthenticationKey.LENGTH) { | ||
throw new Error(`Authentication Key length should be ${AuthenticationKey.LENGTH}`); | ||
} | ||
this.data = hex; | ||
} | ||
|
||
toString(): string { | ||
return this.data.toString(); | ||
} | ||
|
||
toUint8Array(): Uint8Array { | ||
return this.data.toUint8Array(); | ||
} | ||
|
||
/** | ||
* Converts a K-of-N MultiEd25519PublicKey to AuthenticationKey with: | ||
* `auth_key = sha3-256(p_1 | … | p_n | K | 0x01)`. `K` represents the K-of-N required for | ||
* authenticating the transaction. `0x01` is the 1-byte scheme for multisig. | ||
* | ||
* @param multiPublicKey A K-of-N MultiPublicKey | ||
* @returns AuthenticationKey | ||
*/ | ||
static fromMultiPublicKey(args: { multiPublicKey: MultiEd25519PublicKey }): AuthenticationKey { | ||
const { multiPublicKey } = args; | ||
const multiPubKeyBytes = multiPublicKey.toUint8Array(); | ||
|
||
const bytes = new Uint8Array(multiPubKeyBytes.length + 1); | ||
bytes.set(multiPubKeyBytes); | ||
bytes.set([AuthenticationKey.MULTI_ED25519_SCHEME], multiPubKeyBytes.length); | ||
|
||
const hash = sha3Hash.create(); | ||
hash.update(bytes); | ||
|
||
return new AuthenticationKey({ data: hash.digest() }); | ||
} | ||
|
||
/** | ||
* Converts a PublicKey(s) to AuthenticationKey | ||
* | ||
* @param publicKey | ||
* @returns AuthenticationKey | ||
*/ | ||
static fromPublicKey(args: { publicKey: PublicKey }): AuthenticationKey { | ||
const { publicKey } = args; | ||
const pubKeyBytes = publicKey.toUint8Array(); | ||
|
||
const bytes = new Uint8Array(pubKeyBytes.length + 1); | ||
bytes.set(pubKeyBytes); | ||
bytes.set([AuthenticationKey.ED25519_SCHEME], pubKeyBytes.length); | ||
|
||
const hash = sha3Hash.create(); | ||
hash.update(bytes); | ||
|
||
return new AuthenticationKey({ data: hash.digest() }); | ||
} | ||
|
||
/** | ||
* Derives an account address from AuthenticationKey. Since current AccountAddress is 32 bytes, | ||
* AuthenticationKey bytes are directly translated to AccountAddress. | ||
* | ||
* @returns AccountAddress | ||
*/ | ||
derivedAddress(): AccountAddress { | ||
return new AccountAddress({ data: this.data.toUint8Array() }); | ||
} | ||
} |
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
Oops, something went wrong.