-
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.
- Loading branch information
Jin
committed
Sep 11, 2023
1 parent
5c53294
commit c0bf162
Showing
5 changed files
with
389 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// 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 { Memoize, derivePath } from "../utils"; | ||
import { Hex } from "./hex"; | ||
import { bytesToHex } from "@noble/hashes/utils"; | ||
import { Ed25519PublicKey } from "../crypto/ed25519"; | ||
import { AuthenticationKey } from "../crypto/authentication_key"; | ||
|
||
/** | ||
* Class for creating and managing account on Aptos network | ||
* | ||
* Use this class to create accounts, sign transactions, and more. | ||
*/ | ||
export class Account { | ||
/** | ||
* signing key of the account, which holds the public and private key | ||
*/ | ||
private readonly _signingKey: nacl.SignKeyPair; | ||
|
||
/** | ||
* Account address associated with the account | ||
*/ | ||
private readonly _accountAddress: AccountAddress; | ||
|
||
/** | ||
* Public key of the account | ||
* | ||
* @returns Hex - public key of the account | ||
*/ | ||
get publicKey(): Hex { | ||
return new Hex({ data: this._signingKey.publicKey }); | ||
} | ||
|
||
/** | ||
* Private key of the account | ||
* | ||
* @returns Hex - private key of the account | ||
*/ | ||
get privateKey(): Hex { | ||
return new Hex({ data: this._signingKey.secretKey }); | ||
} | ||
|
||
/** | ||
* Address of the account | ||
* | ||
* @returns AccountAddress - address of the account | ||
*/ | ||
get accountAddress(): AccountAddress { | ||
return this._accountAddress; | ||
} | ||
|
||
/** | ||
* private constructor for Account | ||
* | ||
* This method is private because it should only be called by the factory static methods. | ||
* @returns Account | ||
*/ | ||
private constructor(keyPair: nacl.SignKeyPair, address: AccountAddress) { | ||
this._signingKey = keyPair; | ||
this._accountAddress = address; | ||
} | ||
|
||
/** | ||
* Creates new account with random private key and address | ||
* | ||
* @returns Account | ||
*/ | ||
static create(): Account { | ||
const keyPair = nacl.sign.keyPair(); | ||
const address = new AccountAddress({data: Account.authKey(keyPair.publicKey).toUint8Array()}); | ||
return new Account(keyPair, address); | ||
} | ||
|
||
/** | ||
* Creates new account with provided private key | ||
* | ||
* @param privateKey Hex - private key of the account | ||
* @returns Account | ||
*/ | ||
static fromPrivateKey(privateKey: Hex): Account { | ||
const keyPair = nacl.sign.keyPair.fromSeed(privateKey.toUint8Array().slice(0, 32)); | ||
const address = new AccountAddress({data: Account.authKey(keyPair.publicKey).toUint8Array()}); | ||
return new Account(keyPair, address); | ||
} | ||
|
||
/** | ||
* Creates new account with provided private key and address | ||
* This is intended to be used for account that has it's key rotated | ||
* | ||
* @param privateKey Hex - private key of the account | ||
* @param address AccountAddress - address of the account | ||
* @returns Account | ||
*/ | ||
static fromPrivateKeyAndAddress(privateKey: Hex, address: AccountAddress): Account { | ||
const signingKey = nacl.sign.keyPair.fromSeed(privateKey.toUint8Array().slice(0, 32)); | ||
return new Account(signingKey, address); | ||
} | ||
|
||
/** | ||
* Creates new 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(path:string, mnemonics: string): Account { | ||
if (!Account.isValidPath(path)) { | ||
throw new Error("Invalid derivation path"); | ||
} | ||
|
||
const normalizeMnemonics = mnemonics | ||
.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 address = new AccountAddress({data: Account.authKey(signingKey.publicKey).toUint8Array()}); | ||
|
||
return new Account(signingKey, address); | ||
} | ||
|
||
/** | ||
* Check's if the derive path is valid | ||
*/ | ||
static isValidPath(path: string): boolean { | ||
return /^m\/44'\/637'\/[0-9]+'\/[0-9]+'\/[0-9]+'+$/.test(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 | ||
*/ | ||
@Memoize() | ||
static authKey(publicKey: Uint8Array): Hex { | ||
const pubKey = new Ed25519PublicKey(publicKey); | ||
const authKey = AuthenticationKey.fromEd25519PublicKey(pubKey); | ||
return authKey.data; | ||
} | ||
|
||
sign(data: Uint8Array): Hex { | ||
const signature = nacl.sign.detached(data, this._signingKey.secretKey); | ||
return new Hex({ data: signature }); | ||
} | ||
|
||
verifySignature(message: Hex, signature: Hex): boolean { | ||
const rawMessage = message.toUint8Array(); | ||
const rawSignature = signature.toUint8Array(); | ||
return nacl.sign.detached.verify(rawMessage, rawSignature, this._signingKey.publicKey); | ||
} | ||
} |
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,76 @@ | ||
import nacl from "tweetnacl"; | ||
import { hmac } from "@noble/hashes/hmac"; | ||
import { sha512 } from "@noble/hashes/sha512"; | ||
import { hexToBytes } from "@noble/hashes/utils"; | ||
|
||
export type Keys = { | ||
key: Uint8Array; | ||
chainCode: Uint8Array; | ||
}; | ||
|
||
const pathRegex = /^m(\/[0-9]+')+$/; | ||
|
||
const replaceDerive = (val: string): string => val.replace("'", ""); | ||
|
||
const HMAC_KEY = "ed25519 seed"; | ||
const HARDENED_OFFSET = 0x80000000; | ||
|
||
export const getMasterKeyFromSeed = (seed: string): Keys => { | ||
const h = hmac.create(sha512, HMAC_KEY); | ||
const I = h.update(hexToBytes(seed)).digest(); | ||
const IL = I.slice(0, 32); | ||
const IR = I.slice(32); | ||
return { | ||
key: IL, | ||
chainCode: IR, | ||
}; | ||
}; | ||
|
||
export const CKDPriv = ({ key, chainCode }: Keys, index: number): Keys => { | ||
const buffer = new ArrayBuffer(4); | ||
new DataView(buffer).setUint32(0, index); | ||
const indexBytes = new Uint8Array(buffer); | ||
const zero = new Uint8Array([0]); | ||
const data = new Uint8Array([...zero, ...key, ...indexBytes]); | ||
|
||
const I = hmac.create(sha512, chainCode).update(data).digest(); | ||
const IL = I.slice(0, 32); | ||
const IR = I.slice(32); | ||
return { | ||
key: IL, | ||
chainCode: IR, | ||
}; | ||
}; | ||
|
||
export const getPublicKey = (privateKey: Uint8Array, withZeroByte = true): Uint8Array => { | ||
const keyPair = nacl.sign.keyPair.fromSeed(privateKey); | ||
const signPk = keyPair.secretKey.subarray(32); | ||
const zero = new Uint8Array([0]); | ||
return withZeroByte ? new Uint8Array([...zero, ...signPk]) : signPk; | ||
}; | ||
|
||
export const isValidPath = (path: string): boolean => { | ||
if (!pathRegex.test(path)) { | ||
return false; | ||
} | ||
return !path | ||
.split("/") | ||
.slice(1) | ||
.map(replaceDerive) | ||
.some(Number.isNaN as any); | ||
}; | ||
|
||
export const derivePath = (path: string, seed: string, offset = HARDENED_OFFSET): Keys => { | ||
if (!isValidPath(path)) { | ||
throw new Error("Invalid derivation path"); | ||
} | ||
|
||
const { key, chainCode } = getMasterKeyFromSeed(seed); | ||
const segments = path | ||
.split("/") | ||
.slice(1) | ||
.map(replaceDerive) | ||
.map((el) => parseInt(el, 10)); | ||
|
||
return segments.reduce((parentKeys, segment) => CKDPriv(parentKeys, segment + offset), { key, chainCode }); | ||
}; |
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,2 @@ | ||
export * from "./hd-key"; | ||
export * from "./memoize-decorator"; |
Oops, something went wrong.