static #isInternal: boolean = false\r
\r
#address: string\r
- #locked: boolean\r
- #pub: string\r
- #prv: Uint8Array<ArrayBuffer>\r
+ #publicKey: Uint8Array<ArrayBuffer>\r
+ #privateKey: Uint8Array<ArrayBuffer>\r
\r
#balance?: bigint\r
#frontier?: string\r
#weight?: bigint\r
\r
get address () { return `${PREFIX}${this.#address}` }\r
- get isLocked () { return this.#locked }\r
- get isUnlocked () { return !this.#locked }\r
- get publicKey () { return this.#pub }\r
- get privateKey () { return bytes.toHex(this.#prv) }\r
+ get isLocked () { return this.#privateKey.buffer.detached }\r
+ get isUnlocked () { return !this.isLocked }\r
+ get publicKey () { return bytes.toHex(this.#publicKey) }\r
+ get privateKey () { return bytes.toHex(this.#privateKey) }\r
\r
get balance () { return this.#balance }\r
get frontier () { return this.#frontier }\r
}\r
set weight (v) { this.#weight = v ? BigInt(v) : undefined }\r
\r
- constructor (address: string, publicKey: string, privateKey: Uint8Array<ArrayBuffer>, index?: number) {\r
+ constructor (address: string, publicKey: Uint8Array<ArrayBuffer>, index?: number) {\r
if (!Account.#isInternal) {\r
throw new Error(`Account cannot be instantiated directly. Use factory methods instead.`)\r
}\r
.replace(PREFIX, '')\r
.replace(PREFIX_LEGACY, '')\r
this.#index = index\r
- this.#locked = false\r
- this.#pub = publicKey\r
- this.#prv = privateKey\r
+ this.#publicKey = publicKey\r
+ this.#privateKey = new Uint8Array(0)\r
+ bytes.erase(this.#privateKey)\r
}\r
\r
/**\r
* allow garbage collection.\r
*/\r
async destroy (): Promise<void> {\r
- bytes.erase(this.#prv)\r
+ bytes.erase(this.#privateKey)\r
await SafeWorker.add({\r
method: 'destroy',\r
- name: this.#pub\r
+ name: this.#publicKey\r
})\r
this.#index = undefined\r
this.#frontier = undefined\r
this.#isInternal = true\r
this.validate(address)\r
const publicKey = this.#addressToKey(address)\r
- const account = new this(address, publicKey, new Uint8Array(32), index)\r
- return account\r
+ return new this(address, publicKey, index)\r
}\r
\r
/**\r
- * Instantiates an Account object from its public key.\r
+ * Instantiates an Account object from its public key. It is unable to sign\r
+ * blocks or messages since it has no private key.\r
*\r
- * @param {string} publicKey - Public key of the account\r
+ * @param {(string|Uint8Array)} publicKey - Public key of the account\r
* @param {number} [index] - Account number used when deriving the key\r
* @returns {Account} The instantiated Account object\r
*/\r
- static fromPublicKey (publicKey: string, index?: number): Account {\r
- this.#isInternal = true\r
+ static fromPublicKey (publicKey: string | Uint8Array<ArrayBuffer>, index?: number): Account {\r
this.#validateKey(publicKey)\r
+ if (typeof publicKey === 'string') publicKey = hex.toBytes(publicKey)\r
const address = this.#keyToAddress(publicKey)\r
- const account = new this(address, publicKey, new Uint8Array(32), index)\r
- return account\r
+ this.#isInternal = true\r
+ return new this(address, publicKey, index)\r
}\r
\r
/**\r
* @returns {Account} A new Account object\r
*/\r
static async fromPrivateKey (privateKey: string | Uint8Array<ArrayBuffer>, index?: number): Promise<Account> {\r
- if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey)\r
- this.#isInternal = true\r
this.#validateKey(privateKey)\r
+ if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey)\r
let publicKey: string\r
try {\r
const headers = {\r
} catch (err) {\r
throw new Error(`Failed to derive public key from private key`, { cause: err })\r
}\r
- const address = this.#keyToAddress(publicKey)\r
- const account = new this(address, publicKey, privateKey, index)\r
- return account\r
+ try {\r
+ const self = await this.fromPublicKey(publicKey, index)\r
+ self.#privateKey = privateKey\r
+ return self\r
+ } catch (err) {\r
+ throw new Error(`Failed to lock new Account`, { cause: err })\r
+ }\r
}\r
\r
/**\r
* @returns True if successfully locked\r
*/\r
async lock (password: string | Uint8Array<ArrayBuffer>): Promise<boolean> {\r
+ if (this.isLocked) {\r
+ throw new Error(`Account ${this.address} is already locked`)\r
+ }\r
if (typeof password === 'string') password = utf8.toBytes(password)\r
if (password == null || !(password instanceof Uint8Array)) {\r
- throw new Error('Failed to lock account')\r
+ throw new Error(`Failed to lock Account ${this.address}`)\r
}\r
try {\r
const headers = {\r
method: 'set',\r
- name: this.#pub\r
+ name: this.publicKey\r
}\r
const data = {\r
password: password.buffer,\r
- id: hex.toBytes(this.#pub).buffer,\r
- privateKey: this.#prv.buffer\r
- }\r
- const success = await SafeWorker.add(headers, data)\r
- if (!success) {\r
- throw null\r
+ id: new Uint8Array(this.#publicKey).buffer,\r
+ privateKey: this.#privateKey.buffer\r
}\r
+ return await SafeWorker.add(headers, data)\r
} catch (err) {\r
- console.error(`Failed to lock account ${this.address}`, err)\r
- return false\r
+ throw new Error(`Failed to lock Account ${this.address}`, { cause: err })\r
} finally {\r
bytes.erase(password)\r
}\r
- bytes.erase(this.#prv)\r
- this.#locked = true\r
- return true\r
}\r
\r
/**\r
}\r
\r
/**\r
- * Signs a block using the private key of the account.\r
+ * Signs a block using the private key of the account. The signature is\r
+ * appended to the signature field of the block before being returned.\r
*\r
- * @param {(ChangeBlock|ReceiveBlock|SendBlock)} block -\r
+ * @param {(string|Uint8Array)} password - Required to decrypt the private key for signing\r
+ * @param {(ChangeBlock|ReceiveBlock|SendBlock)} block - The block data to be hashed and signed\r
+ * @returns {Promise<string>} Hexadecimal-formatted 64-byte signature\r
*/\r
async sign (block: ChangeBlock | ReceiveBlock | SendBlock): Promise<string> {\r
- if (this.isLocked || this.#prv.buffer.detached) {\r
- throw new Error('Failed to sign block with locked Account')\r
+ if (this.isLocked) {\r
+ throw new Error(`Account ${this.address} must be unlocked prior to signing`)\r
}\r
try {\r
const headers = {\r
method: 'detached'\r
}\r
const data = {\r
- privateKey: new Uint8Array(this.#prv).buffer,\r
+ privateKey: new Uint8Array(this.#privateKey).buffer,\r
msg: hex.toBytes(block.hash).buffer\r
}\r
- return await NanoNaClWorker.add(headers, data)\r
+ const result = await NanoNaClWorker.add(headers, data)\r
+ block.signature = result\r
+ return result\r
} catch (err) {\r
throw new Error(`Failed to sign block`, { cause: err })\r
}\r
async unlock (password: string | Uint8Array<ArrayBuffer>): Promise<boolean> {\r
if (typeof password === 'string') password = utf8.toBytes(password)\r
if (password == null || !(password instanceof Uint8Array)) {\r
- throw new Error('Failed to unlock account')\r
+ throw new Error('Password must be string or bytes')\r
}\r
try {\r
const headers = {\r
method: 'get',\r
- name: this.#pub\r
+ name: this.publicKey\r
}\r
const data = {\r
password: password.buffer\r
if (id !== this.publicKey) {\r
throw null\r
}\r
- this.#prv = new Uint8Array(privateKey as ArrayBuffer)\r
+ this.#privateKey = new Uint8Array(privateKey as ArrayBuffer)\r
} catch (err) {\r
- console.error(`Failed to unlock account ${this.address}`, err)\r
- return false\r
+ throw new Error(`Failed to export private key for Account ${this.address}`, { cause: err })\r
} finally {\r
bytes.erase(password)\r
+ return this.isUnlocked\r
}\r
- this.#locked = false\r
- return true\r
}\r
\r
/**\r
}\r
}\r
\r
- static #addressToKey (v: string): string {\r
- const publicKeyBytes = base32.toBytes(v.slice(-60, -8))\r
- const checksumBytes = base32.toBytes(v.slice(-8))\r
- const rechecksumBytes = new Blake2b(5).update(publicKeyBytes).digest().reverse()\r
- if (bytes.toHex(checksumBytes) !== bytes.toHex(rechecksumBytes)) {\r
+ static #addressToKey (address: string): Uint8Array<ArrayBuffer> {\r
+ const publicKey = base32.toBytes(address.slice(-60, -8))\r
+ const checksum = base32.toBytes(address.slice(-8))\r
+ const rechecksum = new Blake2b(5).update(publicKey).digest().reverse()\r
+ if (bytes.toHex(checksum) !== bytes.toHex(rechecksum)) {\r
throw new Error('Checksum mismatch in address')\r
}\r
- return bytes.toHex(publicKeyBytes)\r
+ return publicKey\r
}\r
\r
- static #keyToAddress (publicKey: string): string {\r
- const publicKeyBytes = hex.toBytes(publicKey)\r
- const checksumBytes = new Blake2b(5).update(publicKeyBytes).digest().reverse()\r
- const encodedPublicKey = bytes.toBase32(publicKeyBytes)\r
- const encodedChecksum = bytes.toBase32(checksumBytes)\r
+ static #keyToAddress (publicKey: Uint8Array<ArrayBuffer>): string {\r
+ const checksum = new Blake2b(5).update(publicKey).digest().reverse()\r
+ const encodedPublicKey = bytes.toBase32(publicKey)\r
+ const encodedChecksum = bytes.toBase32(checksum)\r
return `${PREFIX}${encodedPublicKey}${encodedChecksum}`\r
}\r
\r