@tnaka-dev/aws-kms-ethers-v6-signer 1.0.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nakagawa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # aws-kms-ethers-v6-signer
2
+
3
+ A signer implementation for ethers.js v6 that uses AWS Key Management Service (KMS) for secure cryptographic signing operations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @tnaka-dev/aws-kms-ethers-v6-signer
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Create AwsKmsSigner
14
+
15
+ ```typescript
16
+ import { ethers } from "ethers";
17
+ import { KMSClientConfig } from "@aws-sdk/client-kms";
18
+ import { AwsKmsSigner } from "@tnaka-dev/aws-kms-ethers-v6-signer";
19
+
20
+ const keyId = "<your AWS KMS key ID>"; // Replace with your actual AWS KMS key ID
21
+ const config = {
22
+ region: "<your AWS region>", // Replace with your actual AWS region
23
+ credentials: {
24
+ accessKeyId: "<your AWS access key ID>", // Replace with your actual AWS access key ID
25
+ secretAccessKey: "<your AWS secret access key>", // Replace with your actual AWS secret access key
26
+ },
27
+ } as KMSClientConfig;
28
+ // Create the signer
29
+ const signer = new AwsKmsSigner(keyId, config, provider);
30
+ // Connect the signer to the provider
31
+ const provider = new ethers.JsonRpcProvider("<your Ethereum JSON-RPC node URL>"); // Replace with your actual Ethereum JSON-RPC node URL
32
+ const connectedSigner = signer.connect(provider);
33
+ ```
34
+
35
+ ### getAddress
36
+
37
+ ```typescript
38
+ // Address
39
+ const address = await signer.getAddress();
40
+ console.log("Address:", address);
41
+ ```
42
+
43
+ ### signMessage
44
+
45
+ ```typescript
46
+ // Sign a message
47
+ const message = "Hello, AWS KMS Signer!";
48
+ const signature = await signer.signMessage(message);
49
+ console.log(
50
+ "signMessage verification:",
51
+ ethers.verifyMessage(message, signature) === address,
52
+ );
53
+ ```
54
+
55
+ ### sendTransaction
56
+
57
+ ```typescript
58
+ // Sign a transaction
59
+ const toAddress = "<target Ethereum address>"; // Replace with the actual target Ethereum address
60
+ const tx = await connectedSigner.sendTransaction({
61
+ to: toAddress,
62
+ value: ethers.parseEther("0.01"),
63
+ });
64
+ const receipt = await tx.wait();
65
+ console.log("Transaction:", receipt?.status === 1 ? "Success" : "Failure");
66
+ ```
67
+
68
+ ### Contract call
69
+
70
+ ```typescript
71
+ const toAddress = "<target Ethereum address>"; // Replace with the actual target Ethereum address
72
+ const contract = new ethers.Contract(
73
+ "<target ERC20 contract address>", // Replace with the actual target ERC20 contract address
74
+ [
75
+ "function decimals() public view returns (uint8)",
76
+ "function transfer(address to, uint256 value) external returns (bool)",
77
+ "function balanceOf(address account) external view returns (uint256)",
78
+ ],
79
+ connectedSigner,
80
+ );
81
+ const decimals = await contract.decimals();
82
+ const tx = await contract.transfer(
83
+ toAddress,
84
+ ethers.parseUnits("100", decimals),
85
+ );
86
+ const receipt = await tx.wait();
87
+ console.log("ERC20 Transfer:", receipt?.status === 1 ? "Success" : "Failure");
88
+ console.log(
89
+ "toAddress amount:",
90
+ ethers.formatUnits(await contract.balanceOf(toAddress), decimals),
91
+ );
92
+ ```
93
+
94
+ ### signTypedData
95
+
96
+ Example: Signing typed data for ERC20Permit (ERC-2612) using @openzeppelin/contracts
97
+
98
+ ```typescript
99
+ const contractAddress = "<target ERC20Permit(ERC-2612) contract address>"; // Replace with the actual target ERC20Permit(ERC-2612) contract address
100
+ const contractForView = new ethers.Contract(
101
+ contractAddress,
102
+ [
103
+ "function eip712Domain() public view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions)",
104
+ "function decimals() public view returns (uint8)",
105
+ "function nonces(address owner) public view returns (uint256)",
106
+ ],
107
+ provider,
108
+ );
109
+ // domain
110
+ const eip712Domain = await contractForView.eip712Domain();
111
+ const domain = {
112
+ name: eip712Domain.name, // domain name
113
+ version: eip712Domain.version, // domain version
114
+ verifyingContract: eip712Domain.verifyingContract, // contract address
115
+ chainId: eip712Domain.chainId, // chain ID
116
+ };
117
+ // types and value for permit
118
+ const TYPES = {
119
+ Permit: [
120
+ { name: "owner", type: "address" },
121
+ { name: "spender", type: "address" },
122
+ { name: "value", type: "uint256" },
123
+ { name: "nonce", type: "uint256" },
124
+ { name: "deadline", type: "uint256" },
125
+ ],
126
+ };
127
+ // value for permit
128
+ const owner = await signer.getAddress();
129
+ const nonce = await contractForView.nonces(owner);
130
+ const decimals = await contractForView.decimals();
131
+ const spender = "<target Ethereum address>"; // Replace with the actual target Ethereum address
132
+ const value = {
133
+ owner: owner,
134
+ spender: spender,
135
+ value: ethers.parseUnits("1", decimals),
136
+ nonce: nonce,
137
+ deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
138
+ };
139
+ // sign typed data
140
+ const signature = await signer.signTypedData(domain, TYPES, value);
141
+ const sign = ethers.Signature.from(signature);
142
+ // send permit transaction
143
+ const contract = new ethers.Contract(
144
+ contractAddress,
145
+ [
146
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public",
147
+ ],
148
+ otherConnectedSigner,
149
+ );
150
+ const tx = await contract.permit(
151
+ value.owner,
152
+ value.spender,
153
+ value.value,
154
+ value.deadline,
155
+ sign.v,
156
+ sign.r,
157
+ sign.s,
158
+ );
159
+ const receipt = await tx.wait();
160
+ console.log(
161
+ "Permit Transaction:",
162
+ receipt?.status === 1 ? "Success" : "Failure",
163
+ );
164
+ ```
165
+
166
+ ### authorize
167
+
168
+ Example: EIP-7702: Set Code for EOAs
169
+
170
+ ```typescript
171
+ const delegateContractAddress = "<>target delegate contract address>"; // Replace with the actual target delegate contract address
172
+
173
+ // address
174
+ const address = await connectedSigner.getAddress();
175
+ // code
176
+ const code = await ethers.provider.getCode(address);
177
+ if (code === "0x") {
178
+ // nonce
179
+ const nonce = await connectedSigner.getNonce();
180
+ // authorize
181
+ const auth = await connectedSigner.authorize({
182
+ address: delegateContractAddress,
183
+ nonce: nonce + 1,
184
+ });
185
+ // send transaction with authorization
186
+ const tx = await connectedSigner.sendTransaction({
187
+ to: address,
188
+ type: 4,
189
+ authorizationList: [auth],
190
+ });
191
+ const receipt = await tx.wait();
192
+ console.log(
193
+ "Authorization Transaction:",
194
+ receipt?.status === 1 ? "Success" : "Failure",
195
+ );
196
+ } else {
197
+ console.log("There is already code at this address.");
198
+ }
199
+ ```
200
+
201
+ ## Prerequisites
202
+
203
+ - AWS KMS key
204
+ - Key spec
205
+ - ECC_SECG_P256K1 (secp256k1)
206
+ - Action
207
+ - kms:Sign
208
+ - kms:GetPublicKey
209
+
210
+ ## Constructor
211
+
212
+ `new AwsKmsSigner(keyId, config?, provider?)`
213
+
214
+ Creates a new AwsKmsSigner instance.
215
+
216
+ - `keyId`: AWS KMS Key ID or ARN
217
+ - `config`: Optional AWS KMS client configuration
218
+ - `provider`: Optional ethers.js Provider
219
+
220
+
221
+
222
+ ## License
223
+
224
+ MIT
@@ -0,0 +1,83 @@
1
+ import { KMSClientConfig } from "@aws-sdk/client-kms";
2
+ import { AbstractSigner, Authorization, AuthorizationRequest, Provider, Signer, TransactionRequest, TypedDataDomain, TypedDataField } from "ethers";
3
+ /**
4
+ * A Signer implementation that uses AWS KMS to sign messages and transactions.
5
+ *
6
+ * The signer retrieves the public key from AWS KMS to compute the Ethereum address
7
+ */
8
+ export declare class AwsKmsSigner extends AbstractSigner {
9
+ private keyId;
10
+ private config;
11
+ private kmsClient;
12
+ address: string;
13
+ /**
14
+ * Creates a new AwsKmsSigner instance.
15
+ *
16
+ * @param keyId - The AWS KMS Key ID or ARN to use for signing
17
+ * @param config - Optional AWS KMS client configuration
18
+ * @param provider - Optional ethers.js Provider to use for resolving ENS names and addresses
19
+ */
20
+ constructor(keyId: string, config?: KMSClientConfig, provider?: Provider);
21
+ /**
22
+ * Retrieves the Ethereum address associated with the AWS KMS key. This is derived from the public key retrieved from AWS KMS. The address is cached after the first retrieval for efficiency.
23
+ *
24
+ * @returns A promise that resolves to the Ethereum address as a string
25
+ */
26
+ getAddress(): Promise<string>;
27
+ /**
28
+ * Connects the signer to a provider.
29
+ *
30
+ * @param provider - An ethers.js Provider to connect to
31
+ * @returns A new Signer instance connected to the provided provider
32
+ *
33
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L71
34
+ */
35
+ connect(provider: Provider): Signer;
36
+ /**
37
+ * Signs a transaction with the AWS KMS key.
38
+ *
39
+ * @param tx - The transaction to sign
40
+ * @returns A promise that resolves to the signed transaction as a string
41
+ *
42
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L75
43
+ */
44
+ signTransaction(tx: TransactionRequest): Promise<string>;
45
+ /**
46
+ * Signs a message with the AWS KMS key.
47
+ *
48
+ * @param message - The message to sign, which can be a string or a Uint8Array
49
+ * @returns A promise that resolves to the signed message as a string
50
+ *
51
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L109
52
+ */
53
+ signMessage(message: string | Uint8Array): Promise<string>;
54
+ /**
55
+ * Signs an authorization request with the AWS KMS key. The authorization request is first resolved to ensure that any ENS names are converted to addresses, and then the resulting data is hashed and signed.
56
+ *
57
+ * @param auth - The authorization request to sign, which includes the address, nonce, and chainId
58
+ * @returns A promise that resolves to the signed authorization, which includes the address, nonce, chainId, and signature
59
+ *
60
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L116
61
+ */
62
+ authorize(auth: AuthorizationRequest): Promise<Authorization>;
63
+ /**
64
+ * Signs typed data with the AWS KMS key. The typed data is first resolved to ensure that any ENS names are converted to addresses, and then the resulting data is hashed and signed.
65
+ *
66
+ * @param domain - The domain separator for the typed data, which includes the name, version, chainId, and verifyingContract
67
+ * @param types - The type definitions for the typed data, which is a mapping of type names to arrays of field definitions
68
+ * @param value - The actual data to sign, which is a mapping of field names to values
69
+ * @returns A promise that resolves to the signed typed data as a string
70
+ *
71
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L138
72
+ */
73
+ signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
74
+ /**
75
+ * Internal method to sign a digest with the AWS KMS key.
76
+ * This method sends a SignCommand to AWS KMS with the specified digest and retrieves the signature. The signature is then parsed from ASN.1 format and converted to the format expected by Ethereum, including handling low S values and computing the recovery parameter (v) based on the recovered address.
77
+ *
78
+ * @param digest - The digest to sign, which must be a 32-byte value (e.g., the hash of a message or transaction)
79
+ * @returns A promise that resolves to the signature in the format expected by Ethereum, including r, s, and v components
80
+ */
81
+ private _sign;
82
+ }
83
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EAGhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,cAAc,EAGd,aAAa,EACb,oBAAoB,EAWpB,QAAQ,EAMR,MAAM,EAKN,kBAAkB,EAClB,eAAe,EAEf,cAAc,EACf,MAAM,QAAQ,CAAC;AAEhB;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,cAAc;IAE9C,OAAO,CAAC,KAAK,CAAS;IAEtB,OAAO,CAAC,MAAM,CAAkB;IAEhC,OAAO,CAAC,SAAS,CAAY;IAE7B,OAAO,EAAG,MAAM,CAAC;IAEjB;;;;;;OAMG;gBACS,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,QAAQ;IAOxE;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAiCnC;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM;IAInC;;;;;;;OAOG;IACG,eAAe,CAAC,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC;IAiC9D;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhE;;;;;;;OAOG;IACG,SAAS,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IAyBnE;;;;;;;;;OASG;IACG,aAAa,CACjB,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,EAI5C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACzB,OAAO,CAAC,MAAM,CAAC;IAoClB;;;;;;OAMG;YACW,KAAK;CA2DpB"}
package/dist/index.js ADDED
@@ -0,0 +1,222 @@
1
+ import { KMSClient, GetPublicKeyCommand, SignCommand, } from "@aws-sdk/client-kms";
2
+ import { ECDSASigValue } from "@peculiar/asn1-ecc";
3
+ import { AsnConvert } from "@peculiar/asn1-schema";
4
+ import { SubjectPublicKeyInfo } from "@peculiar/asn1-x509";
5
+ import { AbstractSigner, assert, assertArgument, copyRequest, dataLength, getAddress, getBigInt, getBytes, hashAuthorization, hashMessage, keccak256, N, recoverAddress, resolveAddress, resolveProperties, Signature, toBeHex, toBigInt, Transaction, TypedDataEncoder, } from "ethers";
6
+ /**
7
+ * A Signer implementation that uses AWS KMS to sign messages and transactions.
8
+ *
9
+ * The signer retrieves the public key from AWS KMS to compute the Ethereum address
10
+ */
11
+ export class AwsKmsSigner extends AbstractSigner {
12
+ // AWS KMS Key ID or ARN
13
+ keyId;
14
+ // AWS KMS client configuration
15
+ config;
16
+ // AWS KMS client instance
17
+ kmsClient;
18
+ // Cached Ethereum address derived from the KMS public key
19
+ address;
20
+ /**
21
+ * Creates a new AwsKmsSigner instance.
22
+ *
23
+ * @param keyId - The AWS KMS Key ID or ARN to use for signing
24
+ * @param config - Optional AWS KMS client configuration
25
+ * @param provider - Optional ethers.js Provider to use for resolving ENS names and addresses
26
+ */
27
+ constructor(keyId, config, provider) {
28
+ super(provider);
29
+ this.keyId = keyId;
30
+ this.config = config || {};
31
+ this.kmsClient = new KMSClient(this.config);
32
+ }
33
+ /**
34
+ * Retrieves the Ethereum address associated with the AWS KMS key. This is derived from the public key retrieved from AWS KMS. The address is cached after the first retrieval for efficiency.
35
+ *
36
+ * @returns A promise that resolves to the Ethereum address as a string
37
+ */
38
+ async getAddress() {
39
+ // If the address is already cached, return it
40
+ if (!this.address) {
41
+ // Retrieve the public key from AWS KMS
42
+ const command = new GetPublicKeyCommand({
43
+ KeyId: this.keyId,
44
+ });
45
+ // Send the command to AWS KMS and get the response
46
+ const response = await this.kmsClient.send(command);
47
+ // Extract the public key from the response
48
+ const publicKey = response.PublicKey;
49
+ // If the public key is not available, throw an error
50
+ if (!publicKey) {
51
+ throw new Error("Failed to retrieve public key from AWS KMS");
52
+ }
53
+ // Parse the public key using ASN.1 to extract the EC public key
54
+ const ecPublicKey = AsnConvert.parse(Buffer.from(publicKey), SubjectPublicKeyInfo).subjectPublicKey;
55
+ // Compute the Ethereum address by taking the keccak256 hash of the public key (excluding the first byte) and taking the last 20 bytes of the hash
56
+ // https://ethereum.github.io/yellowpaper/paper.pdf - Appendix F. Signing Transactions - Ethereum address A(pr) (a 160-bit value)
57
+ // https://eips.ethereum.org/EIPS/eip-55 - Ethereum address checksum
58
+ this.address = getAddress("0x" +
59
+ keccak256(new Uint8Array(ecPublicKey.slice(1, ecPublicKey.byteLength))).slice(-40));
60
+ }
61
+ return this.address;
62
+ }
63
+ /**
64
+ * Connects the signer to a provider.
65
+ *
66
+ * @param provider - An ethers.js Provider to connect to
67
+ * @returns A new Signer instance connected to the provided provider
68
+ *
69
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L71
70
+ */
71
+ connect(provider) {
72
+ return new AwsKmsSigner(this.keyId, this.config, provider);
73
+ }
74
+ /**
75
+ * Signs a transaction with the AWS KMS key.
76
+ *
77
+ * @param tx - The transaction to sign
78
+ * @returns A promise that resolves to the signed transaction as a string
79
+ *
80
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L75
81
+ */
82
+ async signTransaction(tx) {
83
+ tx = copyRequest(tx);
84
+ // Replace any Addressable or ENS name with an address
85
+ const { to, from } = await resolveProperties({
86
+ to: tx.to ? resolveAddress(tx.to, this) : undefined,
87
+ from: tx.from ? resolveAddress(tx.from, this) : undefined,
88
+ });
89
+ if (to != null) {
90
+ tx.to = to;
91
+ }
92
+ if (from != null) {
93
+ tx.from = from;
94
+ }
95
+ if (tx.from != null) {
96
+ assertArgument(getAddress(tx.from) === (await this.getAddress()), "transaction from address mismatch", "tx.from", tx.from);
97
+ delete tx.from;
98
+ }
99
+ // Build the transaction
100
+ const btx = Transaction.from(tx);
101
+ btx.signature = await this._sign(btx.unsignedHash);
102
+ return btx.serialized;
103
+ }
104
+ /**
105
+ * Signs a message with the AWS KMS key.
106
+ *
107
+ * @param message - The message to sign, which can be a string or a Uint8Array
108
+ * @returns A promise that resolves to the signed message as a string
109
+ *
110
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L109
111
+ */
112
+ async signMessage(message) {
113
+ return (await this._sign(hashMessage(message))).serialized;
114
+ }
115
+ /**
116
+ * Signs an authorization request with the AWS KMS key. The authorization request is first resolved to ensure that any ENS names are converted to addresses, and then the resulting data is hashed and signed.
117
+ *
118
+ * @param auth - The authorization request to sign, which includes the address, nonce, and chainId
119
+ * @returns A promise that resolves to the signed authorization, which includes the address, nonce, chainId, and signature
120
+ *
121
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L116
122
+ */
123
+ async authorize(auth) {
124
+ auth = Object.assign({}, auth, {
125
+ address: await resolveAddress(auth.address, this),
126
+ });
127
+ auth = await this.populateAuthorization(auth);
128
+ assertArgument(typeof auth.address === "string", "invalid address for authorizeSync", "auth.address", auth);
129
+ const signature = await this._sign(hashAuthorization(auth));
130
+ return Object.assign({}, {
131
+ address: getAddress(auth.address),
132
+ nonce: getBigInt(auth.nonce || 0),
133
+ chainId: getBigInt(auth.chainId || 0),
134
+ }, { signature });
135
+ }
136
+ /**
137
+ * Signs typed data with the AWS KMS key. The typed data is first resolved to ensure that any ENS names are converted to addresses, and then the resulting data is hashed and signed.
138
+ *
139
+ * @param domain - The domain separator for the typed data, which includes the name, version, chainId, and verifyingContract
140
+ * @param types - The type definitions for the typed data, which is a mapping of type names to arrays of field definitions
141
+ * @param value - The actual data to sign, which is a mapping of field names to values
142
+ * @returns A promise that resolves to the signed typed data as a string
143
+ *
144
+ * @note https://github.com/ethers-io/ethers.js/blob/b746c3cf6cd191e2357fa55751696676ffda060e/src.ts/wallet/base-wallet.ts#L138
145
+ */
146
+ async signTypedData(domain, types,
147
+ // `ethers` typings use `any` here for the value mapping.
148
+ // This is intentional and matches the abstract signer interface, so we suppress the lint warning.
149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
+ value) {
151
+ // Populate any ENS names
152
+ const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (name) => {
153
+ // @TODO: this should use resolveName; addresses don't
154
+ // need a provider
155
+ assert(this.provider != null, "cannot resolve ENS names without a provider", "UNSUPPORTED_OPERATION", {
156
+ operation: "resolveName",
157
+ info: { name },
158
+ });
159
+ const address = await this.provider.resolveName(name);
160
+ assert(address != null, "unconfigured ENS name", "UNCONFIGURED_NAME", {
161
+ value: name,
162
+ });
163
+ return address;
164
+ });
165
+ return (await this._sign(TypedDataEncoder.hash(populated.domain, types, populated.value))).serialized;
166
+ }
167
+ /**
168
+ * Internal method to sign a digest with the AWS KMS key.
169
+ * This method sends a SignCommand to AWS KMS with the specified digest and retrieves the signature. The signature is then parsed from ASN.1 format and converted to the format expected by Ethereum, including handling low S values and computing the recovery parameter (v) based on the recovered address.
170
+ *
171
+ * @param digest - The digest to sign, which must be a 32-byte value (e.g., the hash of a message or transaction)
172
+ * @returns A promise that resolves to the signature in the format expected by Ethereum, including r, s, and v components
173
+ */
174
+ async _sign(digest) {
175
+ assertArgument(dataLength(digest) === 32, "invalid digest length", "digest", digest);
176
+ // Send the SignCommand to AWS KMS with the specified digest and signing parameters
177
+ const command = new SignCommand({
178
+ KeyId: this.keyId,
179
+ Message: getBytes(digest),
180
+ SigningAlgorithm: "ECDSA_SHA_256",
181
+ MessageType: "DIGEST",
182
+ });
183
+ // Retrieve the signature from AWS KMS
184
+ const response = await this.kmsClient.send(command);
185
+ const signature = response.Signature;
186
+ // If the signature is not available, throw an error
187
+ if (!signature) {
188
+ throw new Error("Failed to sign message with AWS KMS");
189
+ }
190
+ // Parse the signature from ASN.1 format to extract the r and s values
191
+ const ecdsaSig = AsnConvert.parse(Buffer.from(signature), ECDSASigValue);
192
+ // Handle low S values by ensuring that s is less than or equal to N/2.
193
+ // If s is greater than N/2, it is replaced with N - s to ensure that the signature is in canonical form, which is required by Ethereum.
194
+ // https://ethereum.github.io/yellowpaper/paper.pdf - Appendix F. Signing Transactions
195
+ let s = toBigInt(new Uint8Array(ecdsaSig.s));
196
+ const HALF = N >> 1n;
197
+ if (s > HALF) {
198
+ s = N - s;
199
+ }
200
+ // Construct the signature object with r, s, and a default v value of 0x1b (27 in decimal).
201
+ // The r and s values are converted to hexadecimal format with appropriate padding.
202
+ const sig = {
203
+ r: toBeHex(toBigInt(new Uint8Array(ecdsaSig.r))),
204
+ s: toBeHex(s, 32),
205
+ v: 0x1b,
206
+ };
207
+ // Compute the recovery parameter (v) by recovering the address from the signature and comparing it to the signer's address. If they do not match, set v to 0x1c.
208
+ const recover = recoverAddress(digest, {
209
+ r: sig.r,
210
+ s: sig.s,
211
+ v: sig.v,
212
+ });
213
+ // Retrieve the signer's address from AWS KMS (this will be cached after the first retrieval)
214
+ const address = await this.getAddress();
215
+ // If the recovered address does not match the signer's address, set v to 0x1c (28 in decimal) to indicate that the signature is valid but the recovery parameter is different.
216
+ if (address.toLowerCase() !== recover.toLowerCase()) {
217
+ sig.v = 0x1c;
218
+ }
219
+ return Signature.from(sig);
220
+ }
221
+ }
222
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@tnaka-dev/aws-kms-ethers-v6-signer",
3
+ "version": "1.0.5",
4
+ "description": "A signer implementation for ethers.js v6 that uses AWS Key Management Service (KMS) for secure cryptographic signing operations",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "test": "jest",
12
+ "test:coverage": "jest --coverage",
13
+ "lint": "eslint src --ext .ts",
14
+ "build": "tsc"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/tnakagawa/aws-kms-ethers-v6-signer.git"
19
+ },
20
+ "keywords": [],
21
+ "author": "Takatoshi Nakagawa",
22
+ "license": "MIT",
23
+ "type": "module",
24
+ "bugs": {
25
+ "url": "https://github.com/tnakagawa/aws-kms-ethers-v6-signer/issues"
26
+ },
27
+ "homepage": "https://github.com/tnakagawa/aws-kms-ethers-v6-signer#readme",
28
+ "devDependencies": {
29
+ "@eslint/js": "^10.0.1",
30
+ "@types/jest": "^30.0.0",
31
+ "@types/node": "^25.5.0",
32
+ "@typescript-eslint/eslint-plugin": "^8.57.0",
33
+ "@typescript-eslint/parser": "^8.57.0",
34
+ "eslint": "^10.0.3",
35
+ "eslint-plugin-jest": "^29.15.0",
36
+ "globals": "^17.4.0",
37
+ "jest": "^30.3.0",
38
+ "jiti": "^2.6.1",
39
+ "ts-jest": "^29.4.6",
40
+ "ts-node": "^10.9.2",
41
+ "typescript": "^5.9.3",
42
+ "typescript-eslint": "^8.57.0",
43
+ "asn1js": "^3.0.7",
44
+ "aws-sdk-client-mock": "^4.1.0",
45
+ "pkijs": "^3.3.3"
46
+ },
47
+ "dependencies": {
48
+ "@aws-sdk/client-kms": "^3.1009.0",
49
+ "@peculiar/asn1-ecc": "^2.6.1",
50
+ "@peculiar/asn1-schema": "^2.6.0",
51
+ "@peculiar/asn1-x509": "^2.6.1",
52
+ "ethers": "^6.16.0"
53
+ }
54
+ }