-
Notifications
You must be signed in to change notification settings - Fork 6
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
add new method: signTypedData() #207
Changes from all commits
1512954
5663247
340907a
3e4c9b9
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 |
---|---|---|
@@ -1,7 +1,5 @@ | ||
|
||
|
||
import { AbstractHDWallet, NeuteredAddressInfo, SerializedHDWallet } from './hdwallet'; | ||
import { HDNodeWallet } from "./hdnodewallet"; | ||
import { HDNodeWallet } from './hdnodewallet'; | ||
import { QiTransactionRequest, Provider, TransactionResponse } from '../providers/index.js'; | ||
import { computeAddress } from '../address/index.js'; | ||
import { getBytes, hexlify } from '../utils/index.js'; | ||
|
@@ -13,6 +11,7 @@ import { musigCrypto } from '../crypto/index.js'; | |
import { Outpoint } from '../transaction/utxo.js'; | ||
import { getZoneForAddress } from '../utils/index.js'; | ||
import { AllowedCoinType, Zone } from '../constants/index.js'; | ||
import { Mnemonic } from './mnemonic.js'; | ||
|
||
type OutpointInfo = { | ||
outpoint: Outpoint; | ||
|
@@ -21,15 +20,14 @@ type OutpointInfo = { | |
account?: number; | ||
}; | ||
|
||
interface SerializedQiHDWallet extends SerializedHDWallet{ | ||
outpoints: OutpointInfo[]; | ||
changeAddresses: NeuteredAddressInfo[]; | ||
gapAddresses: NeuteredAddressInfo[]; | ||
gapChangeAddresses: NeuteredAddressInfo[]; | ||
interface SerializedQiHDWallet extends SerializedHDWallet { | ||
outpoints: OutpointInfo[]; | ||
changeAddresses: NeuteredAddressInfo[]; | ||
gapAddresses: NeuteredAddressInfo[]; | ||
gapChangeAddresses: NeuteredAddressInfo[]; | ||
} | ||
|
||
export class QiHDWallet extends AbstractHDWallet { | ||
|
||
protected static _version: number = 1; | ||
|
||
protected static _GAP_LIMIT: number = 20; | ||
|
@@ -81,10 +79,8 @@ export class QiHDWallet extends AbstractHDWallet { | |
} | ||
|
||
public importOutpoints(outpoints: OutpointInfo[]): void { | ||
outpoints.forEach((outpoint) => { | ||
this.validateZone(outpoint.zone); | ||
this._outpoints.push(outpoint); | ||
}); | ||
this.validateOutpointInfo(outpoints); | ||
this._outpoints.push(...outpoints); | ||
} | ||
|
||
public getOutpoints(zone: Zone): OutpointInfo[] { | ||
|
@@ -319,71 +315,121 @@ export class QiHDWallet extends AbstractHDWallet { | |
return gapAddresses; | ||
} | ||
|
||
public getGapChangeAddressesForZone(zone: Zone): NeuteredAddressInfo[] { | ||
this.validateZone(zone); | ||
const gapChangeAddresses = this._gapChangeAddresses.filter((addressInfo) => addressInfo.zone === zone); | ||
return gapChangeAddresses; | ||
} | ||
|
||
public async signMessage(address: string, message: string | Uint8Array): Promise<string> { | ||
const addrNode = this._getHDNodeForAddress(address); | ||
const privKey = addrNode.privateKey; | ||
const digest = keccak_256(message); | ||
const signature = schnorr.sign(digest, getBytes(privKey)); | ||
return hexlify(signature); | ||
} | ||
|
||
public async serialize(): Promise<SerializedQiHDWallet> { | ||
const hdwalletSerialized = await super.serialize(); | ||
return { | ||
outpoints: this._outpoints, | ||
changeAddresses: Array.from(this._changeAddresses.values()), | ||
gapAddresses: this._gapAddresses, | ||
gapChangeAddresses: this._gapChangeAddresses, | ||
...hdwalletSerialized, | ||
}; | ||
} | ||
|
||
public static async deserialize(serialized: SerializedQiHDWallet): Promise<QiHDWallet> { | ||
const wallet = await super.deserialize<QiHDWallet>(serialized) as QiHDWallet; | ||
// import the change addresses | ||
wallet.importSerializedAddresses(wallet._changeAddresses, serialized.changeAddresses); | ||
|
||
// import the gap addresses, verifying they exist in the wallet | ||
for (const gapAddressInfo of serialized.gapAddresses) { | ||
const gapAddress = gapAddressInfo.address; | ||
if (!wallet._addresses.has(gapAddress)) { | ||
throw new Error(`Address ${gapAddress} not found in wallet`); | ||
} | ||
wallet._gapAddresses.push(gapAddressInfo); | ||
|
||
} | ||
// import the gap change addresses, verifying they exist in the wallet | ||
for (const gapChangeAddressInfo of serialized.gapChangeAddresses) { | ||
const gapChangeAddress = gapChangeAddressInfo.address; | ||
if (!wallet._changeAddresses.has(gapChangeAddress)) { | ||
throw new Error(`Address ${gapChangeAddress} not found in wallet`); | ||
} | ||
wallet._gapChangeAddresses.push(gapChangeAddressInfo); | ||
} | ||
|
||
// validate the outpoints and import them | ||
for (const outpointInfo of serialized.outpoints) { | ||
// check the zone is valid | ||
wallet.validateZone(outpointInfo.zone); | ||
// check the outpoint address is known to the wallet | ||
if (!wallet._addresses.has(outpointInfo.address)) { | ||
throw new Error(`Address ${outpointInfo.address} not found in wallet`); | ||
} | ||
const outpoint = outpointInfo.outpoint; | ||
// TODO: implement a more robust check for Outpoint | ||
// check the Outpoint fields are not empty | ||
if (outpoint.Txhash == null || outpoint.Index == null || outpoint.Denomination == null) { | ||
throw new Error(`Invalid Outpoint: ${JSON.stringify(outpoint)} `); | ||
} | ||
wallet._outpoints.push(outpointInfo); | ||
} | ||
return wallet; | ||
|
||
} | ||
public getGapChangeAddressesForZone(zone: Zone): NeuteredAddressInfo[] { | ||
this.validateZone(zone); | ||
const gapChangeAddresses = this._gapChangeAddresses.filter((addressInfo) => addressInfo.zone === zone); | ||
return gapChangeAddresses; | ||
} | ||
|
||
/** | ||
* Signs a message using the private key associated with the given address. | ||
* | ||
* @param {string} address - The address for which the message is to be signed. | ||
* @param {string | Uint8Array} message - The message to be signed, either as a string or Uint8Array. | ||
* | ||
* @returns {Promise<string>} A promise that resolves to the signature of the message in hexadecimal string format. | ||
* @throws {Error} If the address does not correspond to a valid HD node or if signing fails. | ||
*/ | ||
public async signMessage(address: string, message: string | Uint8Array): Promise<string> { | ||
const addrNode = this._getHDNodeForAddress(address); | ||
const privKey = addrNode.privateKey; | ||
const digest = keccak_256(message); | ||
const signature = schnorr.sign(digest, getBytes(privKey)); | ||
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. Lets use the 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. There are some incompatibilites between the |
||
return hexlify(signature); | ||
} | ||
|
||
/** | ||
* Serializes the HD wallet state into a format suitable for storage or transmission. | ||
* | ||
* @returns {SerializedQiHDWallet} An object representing the serialized state of the HD wallet, including | ||
* outpoints, change addresses, gap addresses, and other inherited properties. | ||
*/ | ||
public serialize(): SerializedQiHDWallet { | ||
const hdwalletSerialized = super.serialize(); | ||
return { | ||
outpoints: this._outpoints, | ||
changeAddresses: Array.from(this._changeAddresses.values()), | ||
gapAddresses: this._gapAddresses, | ||
gapChangeAddresses: this._gapChangeAddresses, | ||
...hdwalletSerialized, | ||
}; | ||
} | ||
|
||
/** | ||
* Deserializes a serialized QiHDWallet object and reconstructs the wallet instance. | ||
* | ||
* @param {SerializedQiHDWallet} serialized - The serialized object representing the state of a QiHDWallet. | ||
* | ||
* @returns {Promise<QiHDWallet>} A promise that resolves to a reconstructed QiHDWallet instance. | ||
* @throws {Error} If the serialized data is invalid or if any addresses in the gap addresses or gap change | ||
* addresses do not exist in the wallet. | ||
*/ | ||
public static async deserialize(serialized: SerializedQiHDWallet): Promise<QiHDWallet> { | ||
super.validateSerializedWallet(serialized); | ||
// create the wallet instance | ||
const mnemonic = Mnemonic.fromPhrase(serialized.phrase); | ||
const path = (this as any).parentPath(serialized.coinType); | ||
const root = HDNodeWallet.fromMnemonic(mnemonic, path); | ||
const wallet = new this(root); | ||
|
||
// import the addresses | ||
wallet.importSerializedAddresses(wallet._addresses, serialized.addresses); | ||
// import the change addresses | ||
wallet.importSerializedAddresses(wallet._changeAddresses, serialized.changeAddresses); | ||
|
||
// import the gap addresses, verifying they already exist in the wallet | ||
for (const gapAddressInfo of serialized.gapAddresses) { | ||
const gapAddress = gapAddressInfo.address; | ||
if (!wallet._addresses.has(gapAddress)) { | ||
throw new Error(`Address ${gapAddress} not found in wallet`); | ||
} | ||
wallet._gapAddresses.push(gapAddressInfo); | ||
} | ||
// import the gap change addresses, verifying they already exist in the wallet | ||
for (const gapChangeAddressInfo of serialized.gapChangeAddresses) { | ||
const gapChangeAddress = gapChangeAddressInfo.address; | ||
if (!wallet._changeAddresses.has(gapChangeAddress)) { | ||
throw new Error(`Address ${gapChangeAddress} not found in wallet`); | ||
} | ||
wallet._gapChangeAddresses.push(gapChangeAddressInfo); | ||
} | ||
|
||
// validate the outpoints and import them | ||
wallet.validateOutpointInfo(serialized.outpoints); | ||
wallet._outpoints.push(...serialized.outpoints); | ||
return wallet; | ||
} | ||
|
||
/** | ||
* Validates an array of OutpointInfo objects. | ||
* | ||
* This method checks the validity of each OutpointInfo object by performing the following validations: | ||
* | ||
* - Validates the zone using the `validateZone` method. | ||
* - Checks if the address exists in the wallet. | ||
* - Checks if the account (if provided) exists in the wallet. | ||
* - Validates the Outpoint by ensuring that `Txhash`, `Index`, and `Denomination` are not null. | ||
* | ||
* @private | ||
* @param {OutpointInfo[]} outpointInfo - An array of OutpointInfo objects to be validated. | ||
* @throws {Error} If any of the validations fail, an error is thrown with a descriptive message. | ||
*/ | ||
private validateOutpointInfo(outpointInfo: OutpointInfo[]): void { | ||
outpointInfo.forEach((info) => { | ||
// validate zone | ||
this.validateZone(info.zone); | ||
// validate address | ||
if (!this._addresses.has(info.address)) { | ||
throw new Error(`Address ${info.address} not found in wallet`); | ||
} | ||
// validate account | ||
if (info.account && !this._accounts.has(info.account)) { | ||
throw new Error(`Account ${info.account} not found in wallet`); | ||
} | ||
// validate Outpoint | ||
if (info.outpoint.Txhash == null || info.outpoint.Index == null || info.outpoint.Denomination == null) { | ||
throw new Error(`Invalid Outpoint: ${JSON.stringify(info)} `); | ||
} | ||
}); | ||
} | ||
} |
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.
This file looks good. Only thing I noticed that needs to be changed is to remove
async
from theserialize
method signature since it is not async.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.
I have removed
async
fromserialize