@shapeshiftoss/hdwallet-gridplus 1.62.4-gridplus.alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE.md +21 -0
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +129 -0
- package/dist/adapter.js.map +1 -0
- package/dist/bitcoin.d.ts +7 -0
- package/dist/bitcoin.d.ts.map +1 -0
- package/dist/bitcoin.js +619 -0
- package/dist/bitcoin.js.map +1 -0
- package/dist/constants.d.ts +18 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/cosmos.d.ts +7 -0
- package/dist/cosmos.d.ts.map +1 -0
- package/dist/cosmos.js +156 -0
- package/dist/cosmos.js.map +1 -0
- package/dist/ethereum.d.ts +7 -0
- package/dist/ethereum.d.ts.map +1 -0
- package/dist/ethereum.js +294 -0
- package/dist/ethereum.js.map +1 -0
- package/dist/gridplus.d.ts +112 -0
- package/dist/gridplus.d.ts.map +1 -0
- package/dist/gridplus.js +574 -0
- package/dist/gridplus.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/mayachain.d.ts +7 -0
- package/dist/mayachain.d.ts.map +1 -0
- package/dist/mayachain.js +163 -0
- package/dist/mayachain.js.map +1 -0
- package/dist/solana.d.ts +5 -0
- package/dist/solana.d.ts.map +1 -0
- package/dist/solana.js +120 -0
- package/dist/solana.js.map +1 -0
- package/dist/thorchain.d.ts +5 -0
- package/dist/thorchain.d.ts.map +1 -0
- package/dist/thorchain.js +143 -0
- package/dist/thorchain.js.map +1 -0
- package/dist/transport.d.ts +28 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +148 -0
- package/dist/transport.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +117 -0
- package/dist/utils.js.map +1 -0
- package/package.json +38 -0
- package/package.json.bak +38 -0
- package/src/adapter.ts +109 -0
- package/src/bitcoin.ts +711 -0
- package/src/constants.ts +52 -0
- package/src/cosmos.ts +132 -0
- package/src/ethereum.ts +305 -0
- package/src/gridplus.ts +550 -0
- package/src/index.ts +3 -0
- package/src/mayachain.ts +150 -0
- package/src/solana.ts +97 -0
- package/src/thorchain.ts +125 -0
- package/src/transport.ts +131 -0
- package/src/utils.ts +101 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/constants.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { UtxoAccountType } from "@shapeshiftoss/types";
|
|
2
|
+
export { UtxoAccountType };
|
|
3
|
+
|
|
4
|
+
export enum PublicKeyType {
|
|
5
|
+
xpub = "0488b21e",
|
|
6
|
+
ypub = "049d7cb2",
|
|
7
|
+
zpub = "04b24746",
|
|
8
|
+
dgub = "02facafd",
|
|
9
|
+
Ltub = "019da462",
|
|
10
|
+
Mtub = "01b26ef6",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const accountTypeToVersion = (() => {
|
|
14
|
+
const Litecoin = {
|
|
15
|
+
[UtxoAccountType.P2pkh]: Buffer.from(PublicKeyType.Ltub, "hex"),
|
|
16
|
+
[UtxoAccountType.SegwitP2sh]: Buffer.from(PublicKeyType.Mtub, "hex"),
|
|
17
|
+
[UtxoAccountType.SegwitNative]: Buffer.from(PublicKeyType.zpub, "hex"),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const Dogecoin = {
|
|
21
|
+
[UtxoAccountType.P2pkh]: Buffer.from(PublicKeyType.dgub, "hex"),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const Bitcoin = {
|
|
25
|
+
[UtxoAccountType.P2pkh]: Buffer.from(PublicKeyType.xpub, "hex"),
|
|
26
|
+
[UtxoAccountType.SegwitP2sh]: Buffer.from(PublicKeyType.ypub, "hex"),
|
|
27
|
+
[UtxoAccountType.SegwitNative]: Buffer.from(PublicKeyType.zpub, "hex"),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (coin: string, type: UtxoAccountType) => {
|
|
31
|
+
switch (coin) {
|
|
32
|
+
case "Litecoin":
|
|
33
|
+
return Litecoin[type];
|
|
34
|
+
case "Bitcoin":
|
|
35
|
+
return Bitcoin[type];
|
|
36
|
+
case "Dogecoin":
|
|
37
|
+
if (type !== UtxoAccountType.P2pkh) throw new Error("Unsupported account type");
|
|
38
|
+
return Dogecoin[type];
|
|
39
|
+
default:
|
|
40
|
+
return Bitcoin[type];
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
|
|
45
|
+
export const convertVersions = ["Ltub", "xpub", "dgub"];
|
|
46
|
+
|
|
47
|
+
export const UTXO_NETWORK_PARAMS: Record<string, { pubKeyHash: number; scriptHash: number; bech32?: string }> = {
|
|
48
|
+
Bitcoin: { pubKeyHash: 0x00, scriptHash: 0x05, bech32: "bc" },
|
|
49
|
+
Dogecoin: { pubKeyHash: 0x1e, scriptHash: 0x16 },
|
|
50
|
+
Litecoin: { pubKeyHash: 0x30, scriptHash: 0x32, bech32: "ltc" },
|
|
51
|
+
BitcoinCash: { pubKeyHash: 0x00, scriptHash: 0x05 },
|
|
52
|
+
};
|
package/src/cosmos.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { pointCompress } from "@bitcoinerlab/secp256k1";
|
|
2
|
+
import type { StdTx } from "@cosmjs/amino";
|
|
3
|
+
import type { DirectSignResponse, OfflineDirectSigner } from "@cosmjs/proto-signing";
|
|
4
|
+
import type { SignerData } from "@cosmjs/stargate";
|
|
5
|
+
import * as core from "@shapeshiftoss/hdwallet-core";
|
|
6
|
+
import * as bech32 from "bech32";
|
|
7
|
+
import type { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
|
8
|
+
import CryptoJS from "crypto-js";
|
|
9
|
+
import { Client, Constants } from "gridplus-sdk";
|
|
10
|
+
import PLazy from "p-lazy";
|
|
11
|
+
|
|
12
|
+
const protoTxBuilder = PLazy.from(() => import("@shapeshiftoss/proto-tx-builder"));
|
|
13
|
+
const cosmJsProtoSigning = PLazy.from(() => import("@cosmjs/proto-signing"));
|
|
14
|
+
|
|
15
|
+
export const bech32ify = (address: ArrayLike<number>, prefix: string): string => {
|
|
16
|
+
const words = bech32.toWords(address);
|
|
17
|
+
return bech32.encode(prefix, words);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const createCosmosAddress = (publicKey: string, prefix: string): string => {
|
|
21
|
+
const message = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(publicKey));
|
|
22
|
+
const hash = CryptoJS.RIPEMD160(message as CryptoJS.lib.WordArray).toString();
|
|
23
|
+
const address = Buffer.from(hash, `hex`);
|
|
24
|
+
return bech32ify(address, prefix);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function cosmosGetAddress(client: Client, msg: core.CosmosGetAddress): Promise<string | null> {
|
|
28
|
+
// Get secp256k1 pubkey using GridPlus client instance
|
|
29
|
+
// Use FULL path - Cosmos uses standard BIP44: m/44'/118'/0'/0/0 (5 levels)
|
|
30
|
+
const addresses = await client.getAddresses({
|
|
31
|
+
startPath: msg.addressNList,
|
|
32
|
+
n: 1,
|
|
33
|
+
flag: Constants.GET_ADDR_FLAGS.SECP256K1_PUB,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!addresses.length) {
|
|
37
|
+
throw new Error("No address returned from device");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// GridPlus SDK returns uncompressed 65-byte pubkeys, but Cosmos needs compressed 33-byte pubkeys
|
|
41
|
+
const pubkeyBuffer = Buffer.isBuffer(addresses[0]) ? addresses[0] : Buffer.from(addresses[0], "hex");
|
|
42
|
+
const compressedPubkey = pointCompress(pubkeyBuffer, true);
|
|
43
|
+
const compressedHex = Buffer.from(compressedPubkey).toString("hex");
|
|
44
|
+
const cosmosAddress = createCosmosAddress(compressedHex, "cosmos");
|
|
45
|
+
|
|
46
|
+
return cosmosAddress;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function cosmosSignTx(client: Client, msg: core.CosmosSignTx): Promise<core.CosmosSignedTx | null> {
|
|
50
|
+
// Get the address for this path
|
|
51
|
+
const address = await cosmosGetAddress(client, { addressNList: msg.addressNList });
|
|
52
|
+
if (!address) throw new Error("Failed to get Cosmos address");
|
|
53
|
+
|
|
54
|
+
// Get the public key using client instance
|
|
55
|
+
const pubkeys = await client.getAddresses({
|
|
56
|
+
startPath: msg.addressNList,
|
|
57
|
+
n: 1,
|
|
58
|
+
flag: Constants.GET_ADDR_FLAGS.SECP256K1_PUB,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!pubkeys.length) {
|
|
62
|
+
throw new Error("No public key returned from device");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// GridPlus SDK returns uncompressed 65-byte pubkeys, but Cosmos needs compressed 33-byte pubkeys
|
|
66
|
+
const pubkeyBuffer = Buffer.isBuffer(pubkeys[0]) ? pubkeys[0] : Buffer.from(pubkeys[0], "hex");
|
|
67
|
+
const compressedPubkey = pointCompress(pubkeyBuffer, true);
|
|
68
|
+
const pubkey = Buffer.from(compressedPubkey);
|
|
69
|
+
|
|
70
|
+
// Create a signer adapter for GridPlus with Direct signing (Proto)
|
|
71
|
+
const signer: OfflineDirectSigner = {
|
|
72
|
+
getAccounts: async () => [
|
|
73
|
+
{
|
|
74
|
+
address,
|
|
75
|
+
pubkey,
|
|
76
|
+
algo: "secp256k1" as const,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
signDirect: async (signerAddress: string, signDoc: SignDoc): Promise<DirectSignResponse> => {
|
|
80
|
+
if (signerAddress !== address) {
|
|
81
|
+
throw new Error("Signer address mismatch");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Use CosmJS to create the sign bytes from the SignDoc
|
|
85
|
+
const signBytes = (await cosmJsProtoSigning).makeSignBytes(signDoc);
|
|
86
|
+
|
|
87
|
+
// Sign using GridPlus SDK general signing
|
|
88
|
+
// Pass unhashed signBytes and let device hash with SHA256
|
|
89
|
+
const signData = {
|
|
90
|
+
data: {
|
|
91
|
+
payload: signBytes,
|
|
92
|
+
curveType: Constants.SIGNING.CURVES.SECP256K1,
|
|
93
|
+
hashType: Constants.SIGNING.HASHES.SHA256,
|
|
94
|
+
encodingType: Constants.SIGNING.ENCODINGS.NONE,
|
|
95
|
+
signerPath: msg.addressNList,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const signedResult = await client.sign(signData);
|
|
100
|
+
|
|
101
|
+
if (!signedResult?.sig) {
|
|
102
|
+
throw new Error("No signature returned from device");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { r, s } = signedResult.sig;
|
|
106
|
+
const rHex = Buffer.isBuffer(r) ? r : Buffer.from(r);
|
|
107
|
+
const sHex = Buffer.isBuffer(s) ? s : Buffer.from(s);
|
|
108
|
+
|
|
109
|
+
// Combine r and s for signature
|
|
110
|
+
const signature = Buffer.concat([rHex, sHex]);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
signed: signDoc,
|
|
114
|
+
signature: {
|
|
115
|
+
pub_key: {
|
|
116
|
+
type: "tendermint/PubKeySecp256k1",
|
|
117
|
+
value: pubkey.toString("base64"),
|
|
118
|
+
},
|
|
119
|
+
signature: signature.toString("base64"),
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const signerData: SignerData = {
|
|
126
|
+
sequence: Number(msg.sequence),
|
|
127
|
+
accountNumber: Number(msg.account_number),
|
|
128
|
+
chainId: msg.chain_id,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (await protoTxBuilder).sign(address, msg.tx as StdTx, signer, signerData, "cosmos");
|
|
132
|
+
}
|
package/src/ethereum.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import Common from "@ethereumjs/common";
|
|
2
|
+
import { FeeMarketEIP1559Transaction, Transaction } from "@ethereumjs/tx";
|
|
3
|
+
import * as core from "@shapeshiftoss/hdwallet-core";
|
|
4
|
+
import { Client, Constants, Utils } from "gridplus-sdk";
|
|
5
|
+
import { encode } from "rlp";
|
|
6
|
+
|
|
7
|
+
export async function ethGetAddress(client: Client, msg: core.ETHGetAddress): Promise<core.Address | null> {
|
|
8
|
+
// Extract address index from EVM path: m/44'/60'/0'/0/X
|
|
9
|
+
// addressNList = [44', 60', 0', 0, X]
|
|
10
|
+
const addressIndex = msg.addressNList[4] || 0;
|
|
11
|
+
const startPath = [...msg.addressNList.slice(0, 4), addressIndex];
|
|
12
|
+
|
|
13
|
+
// TODO: testing only for @kaladinlight, revert me
|
|
14
|
+
// EXPERIMENTAL: Test different flags to see what GridPlus SDK returns
|
|
15
|
+
// Try SECP256K1_XPUB flag alongside default (no flag)
|
|
16
|
+
const addressesWithXpubFlag = await client.getAddresses({
|
|
17
|
+
startPath,
|
|
18
|
+
n: 1,
|
|
19
|
+
flag: Constants.GET_ADDR_FLAGS.SECP256K1_XPUB,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Fetch only the requested address using client instance (current working method)
|
|
23
|
+
const addresses = await client.getAddresses({
|
|
24
|
+
startPath,
|
|
25
|
+
n: 1,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!addresses.length) {
|
|
29
|
+
throw new Error("No address returned from device");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rawAddress = addresses[0];
|
|
33
|
+
const rawAddressWithXpubFlag = addressesWithXpubFlag[0];
|
|
34
|
+
|
|
35
|
+
let address: string;
|
|
36
|
+
|
|
37
|
+
// Handle response format (could be Buffer or string)
|
|
38
|
+
if (Buffer.isBuffer(rawAddress)) {
|
|
39
|
+
// Device returns raw address bytes without 0x prefix - add it for EVM compatibility
|
|
40
|
+
address = "0x" + rawAddress.toString("hex");
|
|
41
|
+
} else {
|
|
42
|
+
address = rawAddress.toString();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Device may return address without 0x prefix - ensure it's present for EVM compatibility
|
|
46
|
+
if (!address.startsWith("0x")) {
|
|
47
|
+
address = "0x" + address;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate Ethereum address format (should be 42 chars with 0x prefix)
|
|
51
|
+
if (address.length !== 42) {
|
|
52
|
+
throw new Error(`Invalid Ethereum address length: ${address}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// EXPERIMENTAL LOGGING: Compare what different flags return
|
|
56
|
+
console.log("=== GridPlus ethGetAddress Flag Comparison ===");
|
|
57
|
+
console.log("Path:", startPath);
|
|
58
|
+
console.log("No flag result:", {
|
|
59
|
+
type: Buffer.isBuffer(rawAddress) ? "Buffer" : typeof rawAddress,
|
|
60
|
+
raw: rawAddress,
|
|
61
|
+
asHex: Buffer.isBuffer(rawAddress) ? rawAddress.toString("hex") : rawAddress,
|
|
62
|
+
});
|
|
63
|
+
console.log("SECP256K1_XPUB flag result:", {
|
|
64
|
+
type: Buffer.isBuffer(rawAddressWithXpubFlag) ? "Buffer" : typeof rawAddressWithXpubFlag,
|
|
65
|
+
raw: rawAddressWithXpubFlag,
|
|
66
|
+
asString: Buffer.isBuffer(rawAddressWithXpubFlag) ? rawAddressWithXpubFlag.toString("hex") : rawAddressWithXpubFlag,
|
|
67
|
+
});
|
|
68
|
+
console.log("Final derived address:", address.toLowerCase());
|
|
69
|
+
console.log("==============================================");
|
|
70
|
+
|
|
71
|
+
// core.Address for ETH is just a string type `0x${string}`
|
|
72
|
+
return address.toLowerCase() as core.Address;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function ethSignTx(client: Client, msg: core.ETHSignTx): Promise<core.ETHSignedTx> {
|
|
76
|
+
const unsignedTxBase = {
|
|
77
|
+
to: msg.to,
|
|
78
|
+
value: msg.value,
|
|
79
|
+
data: msg.data,
|
|
80
|
+
nonce: msg.nonce,
|
|
81
|
+
gasLimit: msg.gasLimit,
|
|
82
|
+
chainId: msg.chainId,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const common = msg.maxFeePerGas
|
|
86
|
+
? Common.custom({ chainId: msg.chainId }, { hardfork: "london" })
|
|
87
|
+
: Common.custom({ chainId: msg.chainId });
|
|
88
|
+
const unsignedTx = msg.maxFeePerGas
|
|
89
|
+
? FeeMarketEIP1559Transaction.fromTxData(
|
|
90
|
+
{
|
|
91
|
+
...unsignedTxBase,
|
|
92
|
+
maxFeePerGas: msg.maxFeePerGas,
|
|
93
|
+
maxPriorityFeePerGas: msg.maxPriorityFeePerGas,
|
|
94
|
+
},
|
|
95
|
+
{ common }
|
|
96
|
+
)
|
|
97
|
+
: Transaction.fromTxData({ ...unsignedTxBase, gasPrice: msg.gasPrice }, { common });
|
|
98
|
+
|
|
99
|
+
const payload = msg.maxFeePerGas ? unsignedTx.getMessageToSign(false) : encode(unsignedTx.getMessageToSign(false));
|
|
100
|
+
|
|
101
|
+
const callDataDecoder = msg.to ? await Utils.fetchCalldataDecoder(msg.data, msg.to, msg.chainId) : undefined;
|
|
102
|
+
|
|
103
|
+
const signingData = {
|
|
104
|
+
data: {
|
|
105
|
+
payload,
|
|
106
|
+
curveType: Constants.SIGNING.CURVES.SECP256K1,
|
|
107
|
+
hashType: Constants.SIGNING.HASHES.KECCAK256,
|
|
108
|
+
encodingType: Constants.SIGNING.ENCODINGS.EVM,
|
|
109
|
+
signerPath: msg.addressNList,
|
|
110
|
+
decoder: callDataDecoder?.def,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const signedResult = await client.sign(signingData);
|
|
115
|
+
|
|
116
|
+
if (!signedResult?.sig) {
|
|
117
|
+
throw new Error("No signature returned from device");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { r, s, v } = signedResult.sig;
|
|
121
|
+
|
|
122
|
+
const rHex = "0x" + (Buffer.isBuffer(r) ? r.toString("hex") : core.toHexString(r));
|
|
123
|
+
const sHex = "0x" + (Buffer.isBuffer(s) ? s.toString("hex") : core.toHexString(s));
|
|
124
|
+
const vHex = "0x" + (Buffer.isBuffer(v) ? v.toString("hex") : core.toHexString(v));
|
|
125
|
+
|
|
126
|
+
const signedTx = msg.maxFeePerGas
|
|
127
|
+
? FeeMarketEIP1559Transaction.fromTxData(
|
|
128
|
+
{
|
|
129
|
+
...unsignedTxBase,
|
|
130
|
+
maxFeePerGas: msg.maxFeePerGas,
|
|
131
|
+
maxPriorityFeePerGas: msg.maxPriorityFeePerGas,
|
|
132
|
+
r: rHex,
|
|
133
|
+
s: sHex,
|
|
134
|
+
v: vHex,
|
|
135
|
+
},
|
|
136
|
+
{ common }
|
|
137
|
+
)
|
|
138
|
+
: Transaction.fromTxData(
|
|
139
|
+
{
|
|
140
|
+
...unsignedTxBase,
|
|
141
|
+
gasPrice: msg.gasPrice,
|
|
142
|
+
r: rHex,
|
|
143
|
+
s: sHex,
|
|
144
|
+
v: vHex,
|
|
145
|
+
},
|
|
146
|
+
{ common }
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const finalSerializedTx = `0x${signedTx.serialize().toString("hex")}`;
|
|
150
|
+
const vRaw = Buffer.isBuffer(v) ? v.readUInt8(0) : v;
|
|
151
|
+
|
|
152
|
+
const result = {
|
|
153
|
+
r: rHex,
|
|
154
|
+
s: sHex,
|
|
155
|
+
v: vRaw,
|
|
156
|
+
serialized: finalSerializedTx,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function ethSignTypedData(client: Client, msg: core.ETHSignTypedData): Promise<core.ETHSignedTypedData> {
|
|
163
|
+
const fwConstants = client.getFwConstants();
|
|
164
|
+
if (!fwConstants.eip712Supported) {
|
|
165
|
+
throw new Error("EIP-712 signing not supported by firmware version");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const addressResult = await ethGetAddress(client, {
|
|
169
|
+
addressNList: msg.addressNList,
|
|
170
|
+
showDisplay: false,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const signingOptions = {
|
|
174
|
+
currency: "ETH_MSG",
|
|
175
|
+
data: {
|
|
176
|
+
protocol: "eip712",
|
|
177
|
+
payload: msg.typedData,
|
|
178
|
+
signerPath: msg.addressNList,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// GridPlus SDK types don't properly support ETH_MSG currency, but runtime does
|
|
183
|
+
const signedResult = await client.sign(signingOptions as Parameters<typeof client.sign>[0]);
|
|
184
|
+
|
|
185
|
+
if (!signedResult?.sig) {
|
|
186
|
+
throw new Error("No signature returned from device");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Type assertion needed because GridPlus SDK incorrectly types ETH_MSG signatures
|
|
190
|
+
const { r, s, v } = signedResult.sig as { r: string | Buffer; s: string | Buffer; v: number | Buffer };
|
|
191
|
+
|
|
192
|
+
let rHex: string;
|
|
193
|
+
let sHex: string;
|
|
194
|
+
|
|
195
|
+
if (Buffer.isBuffer(r)) {
|
|
196
|
+
rHex = "0x" + r.toString("hex");
|
|
197
|
+
} else if (typeof r === "string") {
|
|
198
|
+
if (r.startsWith("0x")) {
|
|
199
|
+
rHex = r;
|
|
200
|
+
} else {
|
|
201
|
+
rHex = "0x" + r;
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error(`Unexpected r format: ${typeof r}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (Buffer.isBuffer(s)) {
|
|
208
|
+
sHex = "0x" + s.toString("hex");
|
|
209
|
+
} else if (typeof s === "string") {
|
|
210
|
+
if (s.startsWith("0x")) {
|
|
211
|
+
sHex = s;
|
|
212
|
+
} else {
|
|
213
|
+
sHex = "0x" + s;
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
throw new Error(`Unexpected s format: ${typeof s}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const vBuf = Buffer.isBuffer(v) ? v : typeof v === "number" ? Buffer.from([v]) : Buffer.from(v);
|
|
220
|
+
const vValue = vBuf.readUInt8(0);
|
|
221
|
+
const vHex = "0x" + vValue.toString(16);
|
|
222
|
+
|
|
223
|
+
const signature = rHex + sHex.slice(2) + vHex.slice(2);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
address: addressResult!,
|
|
227
|
+
signature: signature,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function ethSignMessage(client: Client, msg: core.ETHSignMessage): Promise<core.ETHSignedMessage> {
|
|
232
|
+
if (typeof client.sign !== "function") {
|
|
233
|
+
throw new Error("GridPlus client missing required sign method");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const addressResult = await ethGetAddress(client, {
|
|
237
|
+
addressNList: msg.addressNList,
|
|
238
|
+
showDisplay: false,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
let hexMessage: string;
|
|
242
|
+
if (msg.message.startsWith("0x")) {
|
|
243
|
+
hexMessage = msg.message;
|
|
244
|
+
} else {
|
|
245
|
+
const buffer = Buffer.from(msg.message, "utf8");
|
|
246
|
+
hexMessage = "0x" + buffer.toString("hex");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const signingOptions = {
|
|
250
|
+
currency: "ETH_MSG",
|
|
251
|
+
data: {
|
|
252
|
+
protocol: "signPersonal",
|
|
253
|
+
payload: hexMessage,
|
|
254
|
+
signerPath: msg.addressNList,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// GridPlus SDK types don't properly support ETH_MSG currency, but runtime does
|
|
259
|
+
const signedResult = await client.sign(signingOptions as Parameters<typeof client.sign>[0]);
|
|
260
|
+
|
|
261
|
+
if (!signedResult?.sig) {
|
|
262
|
+
throw new Error("No signature returned from device");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Type assertion needed because GridPlus SDK incorrectly types ETH_MSG signatures
|
|
266
|
+
const { r, s, v } = signedResult.sig as { r: string | Buffer; s: string | Buffer; v: number | Buffer };
|
|
267
|
+
|
|
268
|
+
let rHex: string;
|
|
269
|
+
let sHex: string;
|
|
270
|
+
|
|
271
|
+
if (Buffer.isBuffer(r)) {
|
|
272
|
+
rHex = "0x" + r.toString("hex");
|
|
273
|
+
} else if (typeof r === "string") {
|
|
274
|
+
if (r.startsWith("0x")) {
|
|
275
|
+
rHex = r;
|
|
276
|
+
} else {
|
|
277
|
+
rHex = "0x" + r;
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
throw new Error(`Unexpected r format: ${typeof r}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (Buffer.isBuffer(s)) {
|
|
284
|
+
sHex = "0x" + s.toString("hex");
|
|
285
|
+
} else if (typeof s === "string") {
|
|
286
|
+
if (s.startsWith("0x")) {
|
|
287
|
+
sHex = s;
|
|
288
|
+
} else {
|
|
289
|
+
sHex = "0x" + s;
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
throw new Error(`Unexpected s format: ${typeof s}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const vBuf = Buffer.isBuffer(v) ? v : typeof v === "number" ? Buffer.from([v]) : Buffer.from(v);
|
|
296
|
+
const vValue = vBuf.readUInt8(0);
|
|
297
|
+
const vHex = "0x" + vValue.toString(16);
|
|
298
|
+
|
|
299
|
+
const signature = rHex + sHex.slice(2) + vHex.slice(2);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
address: addressResult!,
|
|
303
|
+
signature: signature,
|
|
304
|
+
};
|
|
305
|
+
}
|